diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index bc24bcd7e3..863a76bb5e 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -109,10 +109,10 @@ Use `npm create veryfront` when you want to scaffold a new project. ## Coding-agent setup Starter templates include `AGENTS.md`. For older projects, install the shared -project guide: +project guide with `--target agents`: ```bash -veryfront install agents +veryfront install --target agents ``` Then run `veryfront dev` and connect your MCP-aware coding agent to the printed diff --git a/docs/guides/coding-agents.md b/docs/guides/coding-agents.md index 7305e9c3cd..69102e186d 100644 --- a/docs/guides/coding-agents.md +++ b/docs/guides/coding-agents.md @@ -26,18 +26,24 @@ bootstrap step, inference setup, and when to use https://veryfront.com/docs. For older projects, add the same guide with: ```bash -veryfront install agents +veryfront install --target agents ``` Tool-specific files are still available: ```bash -veryfront install claude-code -veryfront install cursor -veryfront install copilot -veryfront install windsurf +veryfront install --target claude-code +veryfront install --target cursor +veryfront install --target copilot +veryfront install --target windsurf ``` +Running `veryfront install` without `--target` opens an interactive picker +instead, preselecting the tools it detects in the project. Without a TTY (in CI, +behind a pipe, or from a coding agent) there is no prompt: the detected tools +are installed immediately, and a project with nothing to detect gets `SKILL.md`. +Always pass `--target` in non-interactive environments. + Use `AGENTS.md` as the shared source of truth when multiple coding agents work in the same project. diff --git a/tests/docs/cli-install-commands.test.ts b/tests/docs/cli-install-commands.test.ts new file mode 100644 index 0000000000..7a7fea7476 --- /dev/null +++ b/tests/docs/cli-install-commands.test.ts @@ -0,0 +1,121 @@ +/** + * Docs contract: every `veryfront install ...` command printed in the published + * guide set must actually select the AI-tool target the surrounding prose + * promises. "Published" means the same three directories the sibling docs + * contracts scan (`guide-contracts.test.ts`, `guide-code-examples.test.ts`): + * getting-started, guides, concepts. Generated pages (`docs/api-reference`) and + * unpublished notes (`docs/internal`, `docs/rfcs`, `docs/evidence`) are out of + * scope — they may quote a broken invocation deliberately. + * + * The install command reads its target from `--target` only. A bare positional + * (`veryfront install agents`) is silently ignored and the command falls back to + * auto-detection, which in a fresh project writes `SKILL.md` instead of the + * `AGENTS.md` the docs describe. + */ + +import "#veryfront/schemas/_test-setup.ts"; +import { assert, assertEquals } from "#veryfront/testing/assert.ts"; +import { describe, it } from "#veryfront/testing/bdd.ts"; +import { parseCliArgs } from "../../cli/shared/args.ts"; +import { parseInstallArgs } from "../../cli/commands/install/handler.ts"; +import { parseTargetFlag } from "../../cli/commands/install/install.ts"; +import { getToolById } from "../../cli/commands/install/registry.ts"; + +const DOC_DIRS = ["docs/getting-started", "docs/guides", "docs/concepts"] as const; + +interface DocumentedInstall { + file: string; + command: string; +} + +async function listDocFiles(dir: string): Promise { + const files: string[] = []; + for await (const entry of Deno.readDir(dir)) { + const path = `${dir}/${entry.name}`; + if (entry.isDirectory) { + files.push(...await listDocFiles(path)); + } else if (entry.isFile && entry.name.endsWith(".md")) { + files.push(path); + } + } + return files; +} + +/** Collect `veryfront install ...` lines from fenced shell blocks. */ +function extractInstallCommands(file: string, source: string): DocumentedInstall[] { + const found: DocumentedInstall[] = []; + let inShellFence = false; + + for (const rawLine of source.split("\n")) { + const line = rawLine.trim(); + + if (line.startsWith("```")) { + const lang = line.slice(3).trim().toLowerCase(); + inShellFence = inShellFence ? false : lang === "bash" || lang === "sh" || lang === "shell"; + continue; + } + + if (!inShellFence) continue; + if (!line.startsWith("veryfront install")) continue; + + found.push({ file, command: line }); + } + + return found; +} + +async function collectDocumentedInstalls(): Promise { + const commands: DocumentedInstall[] = []; + for (const dir of DOC_DIRS) { + for (const file of await listDocFiles(dir)) { + commands.push(...extractInstallCommands(file, await Deno.readTextFile(file))); + } + } + return commands.sort((a, b) => a.command.localeCompare(b.command)); +} + +/** Run a documented command line through the real CLI parsing pipeline. */ +function resolveTargets(command: string): string[] { + const argv = command.replace(/^veryfront\s+/, "").split(/\s+/).filter(Boolean); + const parsed = parseInstallArgs(parseCliArgs(argv)); + + assert(parsed.success, `\`${command}\` failed argument validation`); + if (parsed.data.target === undefined) return []; + + return parseTargetFlag(parsed.data.target); +} + +describe("docs: veryfront install commands", () => { + it("every documented install command selects a target non-interactively", async () => { + const documented = await collectDocumentedInstalls(); + assert(documented.length > 0, "expected the docs to document `veryfront install`"); + + const ignored = documented.filter(({ command }) => resolveTargets(command).length === 0); + + assertEquals( + ignored.map(({ file, command }) => `${file}: ${command}`), + [], + "these documented commands pass a target the CLI ignores; use `--target `", + ); + }); + + it("the pages that promise AGENTS.md document a command that writes AGENTS.md", async () => { + const pages = ["docs/getting-started/installation.md", "docs/guides/coding-agents.md"]; + + for (const page of pages) { + const source = await Deno.readTextFile(page); + assert(source.includes("AGENTS.md"), `${page} should describe AGENTS.md`); + + const files = extractInstallCommands(page, source) + .flatMap(({ command }) => resolveTargets(command)) + .map((id) => getToolById(id).file); + + assert( + files.includes("AGENTS.md"), + `${page} promises AGENTS.md but documents no install command that writes it (writes: ${ + files.join(", ") || "nothing" + })`, + ); + } + }); +}); diff --git a/tests/docs/guide-code-examples.test.ts b/tests/docs/guide-code-examples.test.ts index e0f43edbb1..ddf87de428 100644 --- a/tests/docs/guide-code-examples.test.ts +++ b/tests/docs/guide-code-examples.test.ts @@ -925,7 +925,7 @@ describe("Guide: installation.md", () => { "yarn global add veryfront", "bun add -g veryfront", "npx veryfront@latest", - "veryfront install agents", + "veryfront install --target agents", "veryfront --version", ]; diff --git a/tests/docs/guide-contracts.test.ts b/tests/docs/guide-contracts.test.ts index 20b493edce..071e63f19a 100644 --- a/tests/docs/guide-contracts.test.ts +++ b/tests/docs/guide-contracts.test.ts @@ -635,7 +635,7 @@ const GUIDE_CONTRACTS: Record = { "npm create veryfront", "npm install -g veryfront", "npx veryfront@latest", - "veryfront install agents", + "veryfront install --target agents", ], }, "getting-started/create-agent.md": {