From eb57bac2a25ac4893e0484224cd8d0096ccd5e9c Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Fri, 31 Jul 2026 19:44:17 +0000 Subject: [PATCH 1/2] docs: add file organization conventions in AGENTS.md Co-Authored-By: Claude Fable 5 --- AGENTS.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7e65a9c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,68 @@ +# File Organization + +Rules for where code goes in this repository. They describe the kind of code +each place holds, not the contents of specific files. Follow them when adding, +moving, or reviewing code. + +**Principle: keep logic at its call site. Move it to a shared location only +when sharing becomes necessary, never preemptively.** + +## Reviewing + +When reviewing a change, check every added or moved file against these rules, +and check that new code sits in the allowed layer for its location. Report +violations as findings even when the code works correctly. + +## `src/` + +Root files are cross-cutting concerns shared by many commands, one flat file +per concern. These files may know about Prismic and the CLI. + +## `src/commands/` + +One file per CLI command, flat. The filename is the full command path with +dashes: `repo-create.ts` handles `prismic repo create`. Parent commands are +routers only, with no logic. Leaf command files own the user experience: +options, output, and orchestration of everything else. Logic stays in the +command file unless another command needs it. + +## `src/lib/` + +Self-contained modules that know nothing about the application. A lib module +knows only its own subject and could in principle be extracted as a standalone +package. It does not know it is part of a CLI and does not reach into commands +or app state; callers pass in what it needs. + +### `src/lib/prismic/` + +Prismic-domain logic shared by multiple commands. Still application-unaware. + +### `src/lib/prismic/clients/` + +One file per backend service. Network calls only: build the request, validate +the response, return typed data. No orchestration, no business decisions, no +console output. + +## `src/adapters/` + +Framework-specific integrations. One file per framework, with a sibling +`*.templates.ts` file for generated file content. + +## `src/subprocesses/` + +Entry scripts for detached background work. Minimal: parse input, call one +function, exit quietly. + +## `test/` + +End-to-end tests organized by command, mirroring `src/commands/` filenames +one-to-one: `repo-create.test.ts` tests `repo-create.ts`. Never organized by +feature or concern. Non-test files are shared helpers; helpers never import +from `src/`. + +## `evals/` + +AI agent evaluations. One `*.eval.ts` file per agent capability, named as a +behavior phrase like `sync-models.eval.ts`. Organized by what an agent should +be able to do, not by command. Non-eval files are the shared harness; eval +files may reuse `test/` helpers. From 0d9afe391b105ec344d6ebcfe364fc4a3bced0f9 Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Fri, 31 Jul 2026 19:44:17 +0000 Subject: [PATCH 2/2] refactor: align code with file organization conventions Remove the env import from the docs client by hardcoding the docs host with an override option, and replace completeOnboardingStepsSilently with an inline catch at its call sites. Co-Authored-By: Claude Fable 5 --- src/commands/docs-list.ts | 5 +++-- src/commands/docs-view.ts | 3 ++- src/commands/pull.ts | 6 +++--- src/commands/push.ts | 9 +++------ src/commands/repo-create.ts | 6 +++--- src/commands/sync.ts | 6 +++--- src/lib/prismic/clients/docs.ts | 22 +++++++++++++--------- src/lib/prismic/clients/repository.ts | 10 ---------- 8 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/commands/docs-list.ts b/src/commands/docs-list.ts index c7909d4..2190c5c 100644 --- a/src/commands/docs-list.ts +++ b/src/commands/docs-list.ts @@ -1,3 +1,4 @@ +import { env } from "../env"; import { createCommand, type CommandConfig } from "../lib/command"; import { stringify } from "../lib/json"; import { getDocsIndex, getDocsPageIndex } from "../lib/prismic/clients/docs"; @@ -26,7 +27,7 @@ export default createCommand(config, async ({ positionals, values }) => { const { json } = values; if (path) { - const entry = await getDocsPageIndex(path); + const entry = await getDocsPageIndex(path, { host: env.PRISMIC_DOCS_HOST }); if (json) { console.info(stringify(entry)); @@ -41,7 +42,7 @@ export default createCommand(config, async ({ positionals, values }) => { const rows = entry.anchors.map((anchor) => [`${path}#${anchor.slug}`, anchor.excerpt]); console.info(formatTable(rows, { headers: ["PATH", "EXCERPT"] })); } else { - const pages = await getDocsIndex(); + const pages = await getDocsIndex({ host: env.PRISMIC_DOCS_HOST }); pages.sort((a, b) => a.path.localeCompare(b.path)); diff --git a/src/commands/docs-view.ts b/src/commands/docs-view.ts index 582ddaa..6bd2a78 100644 --- a/src/commands/docs-view.ts +++ b/src/commands/docs-view.ts @@ -1,5 +1,6 @@ import GithubSlugger from "github-slugger"; +import { env } from "../env"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { stringify } from "../lib/json"; import { getDocsPageContent } from "../lib/prismic/clients/docs"; @@ -30,7 +31,7 @@ export default createCommand(config, async ({ positionals, values }) => { const path = hashIndex >= 0 ? rawPath.slice(0, hashIndex) : rawPath; const anchor = hashIndex >= 0 ? rawPath.slice(hashIndex + 1) : undefined; - let markdown = await getDocsPageContent(path); + let markdown = await getDocsPageContent(path, { host: env.PRISMIC_DOCS_HOST }); if (anchor) { const section = extractSection(markdown, anchor); diff --git a/src/commands/pull.ts b/src/commands/pull.ts index d0aa8fd..7b287b4 100644 --- a/src/commands/pull.ts +++ b/src/commands/pull.ts @@ -4,7 +4,7 @@ import { CommandError, createCommand, type CommandConfig } from "../lib/command" import { diffArrays } from "../lib/diff"; import { getDirtyPaths, getGitRoot } from "../lib/git"; import { getCustomTypes, getSlices } from "../lib/prismic/clients/custom-types"; -import { completeOnboardingStepsSilently } from "../lib/prismic/clients/repository"; +import { completeOnboardingSteps } from "../lib/prismic/clients/repository"; import { canonicalizeCustomType, canonicalizeSlice } from "../lib/prismic/models"; import { isDescendant, relativePathname } from "../lib/url"; import { findProjectRoot, getRepositoryName } from "../project"; @@ -145,12 +145,12 @@ export default createCommand(config, async ({ values }) => { await adapter.generateTypes(); - await completeOnboardingStepsSilently({ + await completeOnboardingSteps({ repo: await getRepositoryName(), token, host, stepIds: ["connectPrismic"], - }); + }).catch(() => {}); const totalTypes = customTypeOps.insert.length + customTypeOps.update.length; const totalSlices = sliceOps.insert.length + sliceOps.update.length; diff --git a/src/commands/push.ts b/src/commands/push.ts index ea66f85..209a253 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -19,10 +19,7 @@ import { updateCustomType, updateSlice, } from "../lib/prismic/clients/custom-types"; -import { - completeOnboardingStepsSilently, - type OnboardingStep, -} from "../lib/prismic/clients/repository"; +import { completeOnboardingSteps, type OnboardingStep } from "../lib/prismic/clients/repository"; import { canonicalizeCustomType, canonicalizeSlice } from "../lib/prismic/models"; import { BadRequestError } from "../lib/request"; import { appendTrailingSlash, isDescendant, relativePathname } from "../lib/url"; @@ -172,12 +169,12 @@ export default createCommand(config, async ({ values }) => { onboardingSteps.push("createPageType"); } if (onboardingSteps.length > 0) { - await completeOnboardingStepsSilently({ + await completeOnboardingSteps({ repo: await getRepositoryName(), token, host, stepIds: onboardingSteps, - }); + }).catch(() => {}); } const totalTypes = customTypeOps.insert.length + customTypeOps.update.length; diff --git a/src/commands/repo-create.ts b/src/commands/repo-create.ts index a4b8709..dc4f083 100644 --- a/src/commands/repo-create.ts +++ b/src/commands/repo-create.ts @@ -4,7 +4,7 @@ import { detectAgent } from "../lib/ai"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { upsertLocale } from "../lib/prismic/clients/locale"; import { activateMCP } from "../lib/prismic/clients/mcp"; -import { completeOnboardingStepsSilently } from "../lib/prismic/clients/repository"; +import { completeOnboardingSteps } from "../lib/prismic/clients/repository"; import { checkIsDomainAvailable, createRepository } from "../lib/prismic/clients/wroom"; const MAX_DOMAIN_TRIES = 5; @@ -54,12 +54,12 @@ export async function createRepo(config: { // A new repository has no locale, so set the master locale to make it usable. await upsertLocale({ id: lang, isMaster: true }, { repo: domain, token, host }); - await completeOnboardingStepsSilently({ + await completeOnboardingSteps({ repo: domain, token, host, stepIds: ["createPrismicProject"], - }); + }).catch(() => {}); await activateMCP({ repo: domain, token, host }).catch(() => {}); diff --git a/src/commands/sync.ts b/src/commands/sync.ts index 20c79c4..f94e026 100644 --- a/src/commands/sync.ts +++ b/src/commands/sync.ts @@ -8,7 +8,7 @@ import { getErrorMessage } from "../error"; import { createCommand, type CommandConfig, CommandError } from "../lib/command"; import { diffArrays } from "../lib/diff"; import { getCustomTypes, getSlices } from "../lib/prismic/clients/custom-types"; -import { completeOnboardingStepsSilently } from "../lib/prismic/clients/repository"; +import { completeOnboardingSteps } from "../lib/prismic/clients/repository"; import { getRepositoryName } from "../project"; import { trackCommandStart, trackCommandEnd } from "../tracking"; @@ -121,12 +121,12 @@ export default createCommand(config, async ({ values }) => { lastHash = nextHash; if (isInitial) { - await completeOnboardingStepsSilently({ + await completeOnboardingSteps({ repo: await getRepositoryName(), token, host, stepIds: ["connectPrismic"], - }); + }).catch(() => {}); console.info("Initial sync complete."); } else { const timestamp = new Date().toLocaleTimeString(); diff --git a/src/lib/prismic/clients/docs.ts b/src/lib/prismic/clients/docs.ts index d2aa2f4..df11c0c 100644 --- a/src/lib/prismic/clients/docs.ts +++ b/src/lib/prismic/clients/docs.ts @@ -1,8 +1,13 @@ import * as z from "zod/mini"; -import { DEFAULT_PRISMIC_HOST, env } from "../../../env"; import { request } from "../../request"; +// Documentation is only published at prismic.io; the host option exists for +// overrides only. +const DEFAULT_DOCS_HOST = "prismic.io"; + +type DocsConfig = { host?: string }; + const DocsIndexEntrySchema = z.object({ path: z.string(), title: z.string(), @@ -23,16 +28,16 @@ const DocsPageSchema = z.object({ }); type DocsPage = z.infer; -export async function getDocsIndex(): Promise { - const url = new URL("api/index/", getDocsServiceUrl()); +export async function getDocsIndex(config?: DocsConfig): Promise { + const url = new URL("api/index/", getDocsServiceUrl(config?.host)); return request(url, { schema: z.array(DocsIndexEntrySchema), unknownErrorMessage: "Failed to fetch documentation index", }); } -export async function getDocsPageIndex(path: string): Promise { - const url = new URL(`api/index/${path}`, getDocsServiceUrl()); +export async function getDocsPageIndex(path: string, config?: DocsConfig): Promise { + const url = new URL(`api/index/${path}`, getDocsServiceUrl(config?.host)); return request(url, { schema: DocsPageSchema, notFoundMessage: `Documentation page not found: ${path}`, @@ -40,8 +45,8 @@ export async function getDocsPageIndex(path: string): Promise { }); } -export async function getDocsPageContent(path: string): Promise { - const url = new URL(path, getDocsServiceUrl()); +export async function getDocsPageContent(path: string, config?: DocsConfig): Promise { + const url = new URL(path, getDocsServiceUrl(config?.host)); return request(url, { headers: { Accept: "text/markdown" }, schema: z.string(), @@ -50,7 +55,6 @@ export async function getDocsPageContent(path: string): Promise { }); } -function getDocsServiceUrl(): URL { - const host = env.PRISMIC_DOCS_HOST ?? DEFAULT_PRISMIC_HOST; +function getDocsServiceUrl(host = DEFAULT_DOCS_HOST): URL { return new URL(`https://${host}/docs/`); } diff --git a/src/lib/prismic/clients/repository.ts b/src/lib/prismic/clients/repository.ts index fdfe1bf..3197b41 100644 --- a/src/lib/prismic/clients/repository.ts +++ b/src/lib/prismic/clients/repository.ts @@ -63,16 +63,6 @@ export async function completeOnboardingSteps( } } -export async function completeOnboardingStepsSilently( - config: OnboardingConfig & { stepIds: OnboardingStep[] }, -): Promise { - try { - await completeOnboardingSteps(config); - } catch { - // Ignore errors - } -} - function repositoryServiceRequest( url: URL, config: RepositoryConfig,