feat: add /gsd:docs-update command for verified documentation generation (#1532)

* docs(01-02): complete gsd-doc-writer agent skeleton plan

- SUMMARY.md for plan 01-02
- STATE.md advanced to plan 2/2, progress 50%
- ROADMAP.md updated with phase 1 plan progress
- REQUIREMENTS.md marked DOCG-01 and DOCG-08 complete

* feat(01-01): create lib/docs.cjs with cmdDocsInit and detection helpers

- Add cmdDocsInit following cmdInitMapCodebase pattern
- Add hasGsdMarker(), scanExistingDocs(), detectProjectType()
- Add detectDocTooling(), detectMonorepoWorkspaces() private helpers
- GSD_MARKER constant for generated-by tracking
- Only Node.js built-ins and local lib requires used

* feat(01-01): wire docs-init into gsd-tools.cjs and register gsd-doc-writer model profile

- Add const docs = require('./lib/docs.cjs') to gsd-tools.cjs
- Add case 'docs-init' routing to docs.cmdDocsInit
- Add docs-init to help text and JSDoc header
- Register gsd-doc-writer in MODEL_PROFILES (quality:opus, balanced:sonnet, budget:haiku)
- Fix docs.cjs: inline withProjectRoot logic via checkAgentsInstalled (private in init.cjs)

* docs(01-01): complete docs-init command plan

- SUMMARY.md documenting cmdDocsInit, detection helpers, wiring
- STATE.md advanced, progress updated to 100%
- ROADMAP.md phase 1 marked Complete
- REQUIREMENTS.md INFRA-01, INFRA-02, CONS-03 marked complete

* feat(01-02): create gsd-doc-writer agent skeleton

- YAML frontmatter with name, description, tools, color: purple
- role block with doc_assignment receiving convention
- create_mode and update_mode sections
- 9 stub template sections (readme, architecture, getting_started, development, testing, api, configuration, deployment, contributing)
- Each template has Required Sections list and Phase 3 TODO
- critical_rules prohibiting GSD methodology and CHANGELOG
- success_criteria checklist
- No GSD methodology leaks in template sections

* feat(02-01): add docs-update workflow Steps 1-6 — init, classify, route, resolve, detect

- init_context step calling docs-init with @file: handling and agent-skills loading
- validate_agents step warns on missing gsd-doc-writer without halting
- classify_project step maps project_type signals to 5 primary labels plus conditional docs
- build_doc_queue step with always-on 6 docs and conditional API/CONTRIBUTING/DEPLOYMENT routing
- resolve_modes step with doc-type to canonical path mapping and create/update detection
- detect_runtime_capabilities step with Task tool detection and sequential fallback routing

* docs(02-01): complete docs-update workflow plan — 13-step orchestration for parallel doc generation

- 02-01-SUMMARY.md: plan results, decisions, file inventory
- STATE.md: advanced to last plan, progress 100%, decisions recorded
- ROADMAP.md: Phase 2 marked Complete (1/1 plans with summary)
- REQUIREMENTS.md: marked INFRA-04, DOCG-03, DOCG-04, CONS-01, CONS-02, CONS-04 complete

* docs(03-02): complete command entry point and workflow extension plan

- 03-02-SUMMARY.md: plan results, decisions, file inventory
- STATE.md: advanced to plan 2, progress 100%, decisions recorded
- ROADMAP.md: Phase 3 marked Complete (2/2 plans with summaries)
- REQUIREMENTS.md: marked INFRA-03, EXIST-01, EXIST-02, EXIST-04 complete

* feat(03-01): fill all 9 doc templates, add supplement mode and per-package README template

- Replace all 9 template stubs with full content guidance (Required Sections, Content Discovery, Format Notes)
- Add shared doc_tooling_guidance block for Docusaurus, VitePress, MkDocs, Storybook routing
- Add supplement_mode block: append-only strategy with heading comparison and safety rules
- Add template_readme_per_package for monorepo per-package README generation
- Update role block to list supplement as third mode; add rule 7 to critical_rules
- Add supplement mode check to success_criteria
- Remove all Phase 3 TODO stubs and placeholder comments

* feat(03-02): add docs-update command entry point with --force and --verify-only flags

- YAML frontmatter with name, argument-hint, allowed-tools
- objective block documents flag semantics with literal-token enforcement pattern
- execution_context references docs-update.md workflow
- context block passes $ARGUMENTS and documents flag derivation rules
- --force takes precedence over --verify-only when both present

* feat(03-02): extend docs-update workflow with preservation_check, monorepo dispatch, and verify-only

- preservation_check step between resolve_modes and detect_runtime_capabilities
- preservation_check skips on --force, --verify-only, or no hand-written docs
- per-file AskUserQuestion choice: preserve/supplement/regenerate with fallback default to preserve
- dispatch_monorepo_packages step after collect_wave_2 for per-package READMEs
- verify_only_report early-exit step with VERIFY marker count and Phase 4 deferral message
- preservation_mode field added to all doc_assignment blocks in dispatch_wave_1, dispatch_wave_2
- sequential_generation extended with monorepo per-package section
- commit_docs updated to include per-package README files pattern
- report extended with per-package README rows and preservation decisions
- success_criteria updated with preservation, --force, --verify-only, and monorepo checks

* feat(04-01): create gsd-doc-verifier agent with claim extraction and filesystem verification

- YAML frontmatter with name, description, tools, and color fields
- claim_extraction section with 5 categories: file paths, commands, API endpoints, functions, dependencies
- skip_rules section for VERIFY markers, placeholders, example prefixes, and diff blocks
- verification_process with 6 steps using filesystem tools only (no self-consistency checks)
- output_format with exact JSON shape per D-01
- critical_rules enforcing filesystem-only verification and read-only operation

* feat(04-01): add fix_mode to gsd-doc-writer with surgical correction instructions

- Add fix_mode section after supplement_mode in modes block
- Document fix mode as valid option in role block mode list
- Add failures field to doc_assignment fields (fix mode only)
- fix_mode enforces surgical precision: only correct listed failing lines
- VERIFY marker fallback when correct value cannot be determined

* test(04-03): add docs-init integration test suite

- 13 tests across 4 describe blocks covering JSON output shape, project type
  detection, existing doc scanning, GSD marker detection, and doc tooling
- Tests use node:test + node:assert/strict with beforeEach/afterEach lifecycle
- All 13 tests pass with `node --test tests/docs-update.test.cjs`

* feat(04-02): add verify_docs, fix_loop, scan_for_secrets steps to docs-update workflow

- verify_docs step spawns gsd-doc-verifier per generated doc and collects structured JSON results
- fix_loop step bounded at 2 iterations with regression detection (D-05/D-06)
- scan_for_secrets step uses exact map-codebase grep pattern before commit (D-07/D-08)
- verify_only_report updated to invoke real gsd-doc-verifier instead of VERIFY marker count stub
- success_criteria updated with 4 new verification gate checklist items

* docs(04-02): complete verification gate workflow steps plan

- SUMMARY.md: verify_docs, fix_loop, scan_for_secrets, and updated verify_only_report
- STATE.md: advanced to ready_for_verification, 100% progress, decisions logged
- ROADMAP.md: phase 4 marked Complete (3/3 plans with SUMMARYs)
- REQUIREMENTS.md: VERF-01, VERF-02, VERF-03 all marked complete

* refactor(profiles): Adds 'gsd-doc-verifier' to the 'MODEL_PROFILES'

* feat(agents): Add critical rules for file creation and update install test

* docs(05): create phase plan for docs output refinement

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(05-01): make scanExistingDocs recursive into docs/ subdirectories

- Replace flat docs/ scan with recursive walkDir helper (MAX_DEPTH=4)
- Add SKIP_DIRS filtering at every level of recursive walk
- Add fallback to documentation/ or doc/ when docs/ does not exist
- Update JSDoc to reflect recursive scanning behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(05-01): update gsd-doc-writer default path guidance to docs/

- Change "No tooling detected" guidance to default to docs/ directory
- Add README.md and CONTRIBUTING.md as root-level exceptions
- Add instruction to create docs/ directory if it does not exist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(05-02): invert path table to default docs to docs/ directory

- Invert resolve_modes path table: docs/ is primary for all types except readme and contributing
- Add mkdir -p docs/ instruction before agent dispatch
- Update all downstream path references: collect_wave_1, collect_wave_2, commit_docs, report, verify tables
- Update sequential_generation wave_1_outputs and resolved path references
- Update success criteria and verify_only_report examples to use docs/ paths

* feat(05-02): add CONTRIBUTING confirmation gate and existing doc review queue

- Add CONTRIBUTING.md user confirmation prompt in build_doc_queue (skipped with --force or when file exists)
- Add review_queue for non-canonical existing docs (verification only, not rewriting)
- Add review_queue verification in verify_docs step with fix_loop exclusion
- Add existing doc accuracy review section to report step with manual correction guidance

* docs(05-02): complete path table inversion and doc queue improvements plan

- Add 05-02-SUMMARY.md with execution results
- Update STATE.md with position, decisions, and metrics
- Update ROADMAP.md with phase 05 plan progress

* fix(05): replace plain text y/n prompts with AskUserQuestion in docs-update workflow

Three prompts were using plain text (y/n) instead of GSD's standard
AskUserQuestion pattern: CONTRIBUTING.md confirmation, doc queue
proceed gate, and secrets scan confirmation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(05): structure-aware paths, non-canonical doc fixes, and gap detection

- resolve_modes now inspects existing doc directory structure and places
  new docs in matching subdirectories (e.g., docs/architecture/ if that
  pattern exists), instead of dumping everything flat into docs/
- Non-canonical docs with inaccuracies are now sent to gsd-doc-writer
  in fix mode for surgical corrections, not just reported
- Added documentation gap detection step that scans the codebase for
  undocumented areas and prompts user to create missing docs
- Added type: custom support to gsd-doc-writer with template_custom
  section for gap-detected documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(05): smarter structure-aware path resolution for grouped doc directories

When a project uses grouped subdirectories (docs/architecture/,
docs/api/, docs/guides/), ALL canonical docs must be placed in
appropriate groups — none left flat in docs/. Added resolution
chain per doc type with fallback creation. Filenames now match
existing naming style (lowercase-kebab vs UPPERCASE). Queue
presentation shows actual resolved paths, not defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(05): restore mode resolution table as primary queue presentation

The table showing resolved paths, modes, and sources for each doc
must be displayed before the proceed/abort confirmation. It was
replaced by a simple list — now restored as the canonical queue view.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(05): use table format for existing docs review queue presentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(05): add work manifest for structured handoffs between workflow steps

Root cause from smoke test: orchestrator forgot to verify 45 non-canonical
docs because the review_queue had no structural scaffolding — it existed
only in orchestrator memory. Fix:

1. Write docs-work-manifest.json to .planning/tmp/ after resolve_modes
   with all canonical_queue, review_queue, and gap_queue items
2. Every subsequent step (dispatch, collect, verify, fix_loop, report)
   MUST read the manifest first — single source of truth
3. Restructured verify_docs into explicit Phase 1 (canonical) and
   Phase 2 (non-canonical) with separate dispatch for each
4. Both queues now eligible for fix_loop corrections
5. Added manifest read instructions to all dispatch/collect steps

Follows the same pattern as execute-phase's phase-plan-index for
tracking work items across multi-step orchestration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(05): update workflow purpose to reflect full command scope

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(05): remove redundant steps from docs-update workflow

- Remove validate_agents step (if command is available, agents are installed)
- Remove agents_installed/missing_agents extraction from init_context
- Remove available_agent_types block (agent types specified in each Task call)
- Remove detect_runtime_capabilities step (runtime knows its own tools)
- Replace hardcoded flat paths in collect_wave_1/2 with manifest resolved_paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(05): restore available_agent_types section required by test suite

Test enforces that workflows spawning named agents must declare them
in an <available_agent_types> block. Added back with both gsd-doc-writer
and gsd-doc-verifier listed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Luka Fagundes
2026-04-01 07:47:31 -07:00
committed by GitHub
parent 1421dc07bc
commit 067d411c9b
9 changed files with 2559 additions and 1 deletions

201
agents/gsd-doc-verifier.md Normal file
View File

@@ -0,0 +1,201 @@
---
name: gsd-doc-verifier
description: Verifies factual claims in generated docs against the live codebase. Returns structured JSON per doc.
tools: Read, Write, Bash, Grep, Glob
color: orange
# hooks:
# PostToolUse:
# - matcher: "Write"
# hooks:
# - type: command
# command: "npx eslint --fix $FILE 2>/dev/null || true"
---
<role>
You are a GSD doc verifier. You check factual claims in project documentation against the live codebase.
You are spawned by the `/gsd:docs-update` workflow. Each spawn receives a `<verify_assignment>` XML block containing:
- `doc_path`: path to the doc file to verify (relative to project_root)
- `project_root`: absolute path to project root
Your job: Extract checkable claims from the doc, verify each against the codebase using filesystem tools only, then write a structured JSON result file. Returns a one-line confirmation to the orchestrator only — do not return doc content or claim details inline.
**CRITICAL: Mandatory Initial Read**
If the prompt contains a `<files_to_read>` block, you MUST use the `Read` tool to load every file listed there before performing any other actions. This is your primary context.
</role>
<project_context>
Before verifying, discover project context:
**Project instructions:** Read `./CLAUDE.md` if it exists in the working directory. Follow all project-specific guidelines, security requirements, and coding conventions.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during verification
4. Do NOT load full `AGENTS.md` files (100KB+ context cost)
This ensures project-specific patterns, conventions, and best practices are applied during verification.
</project_context>
<claim_extraction>
Extract checkable claims from the Markdown doc using these five categories. Process each category in order.
**1. File path claims**
Backtick-wrapped tokens containing `/` or `.` followed by a known extension.
Extensions to detect: `.ts`, `.js`, `.cjs`, `.mjs`, `.md`, `.json`, `.yaml`, `.yml`, `.toml`, `.txt`, `.sh`, `.py`, `.go`, `.rs`, `.java`, `.rb`, `.css`, `.html`, `.tsx`, `.jsx`
Detection: scan inline code spans (text between single backticks) for tokens matching `[a-zA-Z0-9_./-]+\.(ts|js|cjs|mjs|md|json|yaml|yml|toml|txt|sh|py|go|rs|java|rb|css|html|tsx|jsx)`.
Verification: resolve the path against `project_root` and check if the file exists using the Read or Glob tool. Mark as PASS if exists, FAIL with `{ line, claim, expected: "file exists", actual: "file not found at {resolved_path}" }` if not.
**2. Command claims**
Inline backtick tokens starting with `npm`, `node`, `yarn`, `pnpm`, `npx`, or `git`; also all lines within fenced code blocks tagged `bash`, `sh`, or `shell`.
Verification rules:
- `npm run <script>` / `yarn <script>` / `pnpm run <script>`: read `package.json` and check the `scripts` field for the script name. PASS if found, FAIL with `{ ..., expected: "script '<name>' in package.json", actual: "script not found" }` if missing.
- `node <filepath>`: verify the file exists (same as file path claim).
- `npx <pkg>`: check if the package appears in `package.json` `dependencies` or `devDependencies`.
- Do NOT execute any commands. Existence check only.
- For multi-line bash blocks, process each line independently. Skip blank lines and comment lines (`#`).
**3. API endpoint claims**
Patterns like `GET /api/...`, `POST /api/...`, etc. in both prose and code blocks.
Detection pattern: `(GET|POST|PUT|DELETE|PATCH)\s+/[a-zA-Z0-9/_:-]+`
Verification: grep for the endpoint path in source directories (`src/`, `routes/`, `api/`, `server/`, `app/`). Use patterns like `router\.(get|post|put|delete|patch)` and `app\.(get|post|put|delete|patch)`. PASS if found in any source file. FAIL with `{ ..., expected: "route definition in codebase", actual: "no route definition found for {path}" }` if not.
**4. Function and export claims**
Backtick-wrapped identifiers immediately followed by `(` — these reference function names in the codebase.
Detection: inline code spans matching `[a-zA-Z_][a-zA-Z0-9_]*\(`.
Verification: grep for the function name in source files (`src/`, `lib/`, `bin/`). Accept matches for `function <name>`, `const <name> =`, `<name>(`, or `export.*<name>`. PASS if any match found. FAIL with `{ ..., expected: "function '<name>' in codebase", actual: "no definition found" }` if not.
**5. Dependency claims**
Package names mentioned in prose as used dependencies (e.g., "uses `express`" or "`lodash` for utilities"). These are backtick-wrapped names that appear in dependency context phrases: "uses", "requires", "depends on", "powered by", "built with".
Verification: read `package.json` and check both `dependencies` and `devDependencies` for the package name. PASS if found. FAIL with `{ ..., expected: "package in package.json dependencies", actual: "package not found" }` if not.
</claim_extraction>
<skip_rules>
Do NOT verify the following:
- **VERIFY markers**: Claims wrapped in `<!-- VERIFY: ... -->` — these are already flagged for human review. Skip entirely.
- **Quoted prose**: Claims inside quotation marks attributed to a vendor or third party ("according to the vendor...", "the npm documentation says...").
- **Example prefixes**: Any claim immediately preceded by "e.g.", "example:", "for instance", "such as", or "like:".
- **Placeholder paths**: Paths containing `your-`, `<name>`, `{...}`, `example`, `sample`, `placeholder`, or `my-`. These are templates, not real paths.
- **GSD marker**: The comment `<!-- generated-by: gsd-doc-writer -->` — skip entirely.
- **Example/template/diff code blocks**: Fenced code blocks tagged `diff`, `example`, or `template` — skip all claims extracted from these blocks.
- **Version numbers in prose**: Strings like "`3.0.2`" or "`v1.4`" that are version references, not paths or functions.
</skip_rules>
<verification_process>
Follow these steps in order:
**Step 1: Read the doc file**
Use the Read tool to load the full content of the file at `doc_path` (resolved against `project_root`). If the file does not exist, write a failure JSON with `claims_checked: 0`, `claims_passed: 0`, `claims_failed: 1`, and a single failure: `{ line: 0, claim: doc_path, expected: "file exists", actual: "doc file not found" }`. Then return the confirmation and stop.
**Step 2: Check for package.json**
Use the Read tool to load `{project_root}/package.json` if it exists. Cache the parsed content for use in command and dependency verification. If not present, note this — package.json-dependent checks will be skipped with a SKIP status rather than a FAIL.
**Step 3: Extract claims by line**
Process the doc line by line. Track the current line number. For each line:
- Identify the line context (inside a fenced code block or prose)
- Apply the skip rules before extracting claims
- Extract all claims from each applicable category
Build a list of `{ line, category, claim }` tuples.
**Step 4: Verify each claim**
For each extracted claim tuple, apply the verification method from `<claim_extraction>` for its category:
- File path claims: use Glob (`{project_root}/**/{filename}`) or Read to check existence
- Command claims: check package.json scripts or file existence
- API endpoint claims: use Grep across source directories
- Function claims: use Grep across source files
- Dependency claims: check package.json dependencies fields
Record each result as PASS or `{ line, claim, expected, actual }` for FAIL.
**Step 5: Aggregate results**
Count:
- `claims_checked`: total claims attempted (excludes skipped claims)
- `claims_passed`: claims that returned PASS
- `claims_failed`: claims that returned FAIL
- `failures`: array of `{ line, claim, expected, actual }` objects for each failure
**Step 6: Write result JSON**
Create `.planning/tmp/` directory if it does not exist. Write the result to `.planning/tmp/verify-{doc_filename}.json` where `{doc_filename}` is the basename of `doc_path` with extension (e.g., `README.md` → `verify-README.md.json`).
Use the exact JSON shape from `<output_format>`.
</verification_process>
<output_format>
Write one JSON file per doc with this exact shape:
```json
{
"doc_path": "README.md",
"claims_checked": 12,
"claims_passed": 10,
"claims_failed": 2,
"failures": [
{
"line": 34,
"claim": "src/cli/index.ts",
"expected": "file exists",
"actual": "file not found at src/cli/index.ts"
},
{
"line": 67,
"claim": "npm run test:unit",
"expected": "script 'test:unit' in package.json",
"actual": "script not found in package.json"
}
]
}
```
Fields:
- `doc_path`: the value from `verify_assignment.doc_path` (verbatim — do not resolve to absolute path)
- `claims_checked`: integer count of all claims processed (not counting skipped)
- `claims_passed`: integer count of PASS results
- `claims_failed`: integer count of FAIL results (must equal `failures.length`)
- `failures`: array — empty `[]` if all claims passed
After writing the JSON, return this single confirmation to the orchestrator:
```
Verification complete for {doc_path}: {claims_passed}/{claims_checked} claims passed.
```
If `claims_failed > 0`, append:
```
{claims_failed} failure(s) written to .planning/tmp/verify-{doc_filename}.json
```
</output_format>
<critical_rules>
1. Use ONLY filesystem tools (Read, Grep, Glob, Bash) for verification. No self-consistency checks. Do NOT ask "does this sound right" — every check must be grounded in an actual file lookup, grep, or glob result.
2. NEVER execute arbitrary commands from the doc. For command claims, only verify existence in package.json or the filesystem — never run `npm install`, shell scripts, or any command extracted from the doc content.
3. NEVER modify the doc file. The verifier is read-only. Only write the result JSON to `.planning/tmp/`.
4. Apply skip rules BEFORE extraction. Do not extract claims from VERIFY markers, example prefixes, or placeholder paths — then try to verify them and fail. Apply the rules during extraction.
5. Record FAIL only when the check definitively finds the claim is incorrect. If verification cannot run (e.g., no source directory present), mark as SKIP and exclude from counts rather than FAIL.
6. `claims_failed` MUST equal `failures.length`. Validate before writing.
7. **ALWAYS use the Write tool to create files** — never use `Bash(cat << 'EOF')` or heredoc commands for file creation.
</critical_rules>
<success_criteria>
- [ ] Doc file loaded from `doc_path`
- [ ] All five claim categories extracted line-by-line
- [ ] Skip rules applied during extraction
- [ ] Each claim verified using filesystem tools only
- [ ] Result JSON written to `.planning/tmp/verify-{doc_filename}.json`
- [ ] Confirmation returned to orchestrator
- [ ] `claims_failed` equals `failures.length`
- [ ] No modifications made to any doc file
</success_criteria>
</role>

602
agents/gsd-doc-writer.md Normal file
View File

@@ -0,0 +1,602 @@
---
name: gsd-doc-writer
description: Writes and updates project documentation. Spawned with a doc_assignment block specifying doc type, mode (create/update/supplement), and project context.
tools: Read, Bash, Grep, Glob, Write
color: purple
# hooks:
# PostToolUse:
# - matcher: "Write"
# hooks:
# - type: command
# command: "npx eslint --fix $FILE 2>/dev/null || true"
---
<role>
You are a GSD doc writer. You write and update project documentation files for a target project.
You are spawned by `/gsd:docs-update` workflow. Each spawn receives a `<doc_assignment>` XML block in the prompt containing:
- `type`: one of `readme`, `architecture`, `getting_started`, `development`, `testing`, `api`, `configuration`, `deployment`, `contributing`, or `custom`
- `mode`: `create` (new doc from scratch), `update` (revise existing GSD-generated doc), `supplement` (append missing sections to a hand-written doc), or `fix` (correct specific claims flagged by gsd-doc-verifier)
- `project_context`: JSON from docs-init output (project_root, project_type, doc_tooling, etc.)
- `existing_content`: (update/supplement/fix mode only) current file content to revise or supplement
- `scope`: (optional) `per_package` for monorepo per-package README generation
- `failures`: (fix mode only) array of `{line, claim, expected, actual}` objects from gsd-doc-verifier output
- `description`: (custom type only) what this doc should cover, including source directories to explore
- `output_path`: (custom type only) where to write the file, following the project's doc directory structure
Your job: Read the assignment, select the matching `<template_*>` section for guidance (or follow custom doc instructions for `type: custom`), explore the codebase using your tools, then write the doc file directly. Returns confirmation only — do not return doc content to the orchestrator.
**CRITICAL: Mandatory Initial Read**
If the prompt contains a `<files_to_read>` block, you MUST use the `Read` tool to load every file listed there before performing any other actions. This is your primary context.
</role>
<modes>
<create_mode>
Write the doc from scratch.
1. Parse the `<doc_assignment>` block to determine `type` and `project_context`.
2. Find the matching `<template_*>` section in this file for the assigned `type`. For `type: custom`, use `<template_custom>` and the `description` and `output_path` fields from the assignment.
3. Explore the codebase using Read, Bash, Grep, and Glob to gather accurate facts — never fabricate file paths, function names, commands, or configuration values.
4. Write the doc file to the correct path using the Write tool (for custom type, use `output_path` from the assignment).
5. Include the GSD marker `<!-- generated-by: gsd-doc-writer -->` as the very first line of the file.
6. Follow the Required Sections from the matching template section.
7. Place `<!-- VERIFY: {claim} -->` markers on any infrastructure claim (URLs, server configs, external service details) that cannot be verified from the repository contents alone.
</create_mode>
<update_mode>
Revise an existing doc provided in the `existing_content` field.
1. Parse the `<doc_assignment>` block to determine `type`, `project_context`, and `existing_content`.
2. Find the matching `<template_*>` section in this file for the assigned `type`.
3. Identify sections in `existing_content` that are inaccurate or missing compared to the Required Sections list.
4. Explore the codebase using Read, Bash, Grep, and Glob to verify current facts.
5. Rewrite only the inaccurate or missing sections. Preserve user-authored prose in sections that are still accurate.
6. Ensure the GSD marker `<!-- generated-by: gsd-doc-writer -->` is present as the first line. Add it if missing.
7. Write the updated file using the Write tool.
</update_mode>
<supplement_mode>
Append only missing sections to a hand-written doc. NEVER modify existing content.
1. Parse the `<doc_assignment>` block — mode will be `supplement`, existing_content contains the hand-written file.
2. Find the matching `<template_*>` section for the assigned type.
3. Extract all `## ` headings from existing_content.
4. Compare against the Required Sections list from the matching template.
5. Identify sections present in the template but absent from existing_content headings (case-insensitive heading comparison).
6. For each missing section only:
a. Explore the codebase to gather accurate facts for that section.
b. Generate the section content following the template guidance.
7. Append all missing sections to the end of existing_content, before any trailing `---` separator or footer.
8. Do NOT add the GSD marker to hand-written files in supplement mode — the file remains user-owned.
9. Write the updated file using the Write tool.
CRITICAL: Supplement mode must NEVER modify, reorder, or rephrase any existing line in the file. Only append new ## sections that are completely absent.
</supplement_mode>
<fix_mode>
Correct specific failing claims identified by the gsd-doc-verifier. ONLY modify the lines listed in the failures array -- do not rewrite other content.
1. Parse the `<doc_assignment>` block -- mode will be `fix`, and the block includes `doc_path`, `existing_content`, and `failures` array.
2. Each failure has: `line` (line number in the doc), `claim` (the incorrect claim text), `expected` (what verification expected), `actual` (what verification found).
3. For each failure:
a. Locate the line in existing_content.
b. Explore the codebase using Read, Grep, Glob to find the correct value.
c. Replace ONLY the incorrect claim with the verified-correct value.
d. If the correct value cannot be determined, replace the claim with a `<!-- VERIFY: {claim} -->` marker.
4. Write the corrected file using the Write tool.
5. Ensure the GSD marker `<!-- generated-by: gsd-doc-writer -->` remains on the first line.
CRITICAL: Fix mode must correct ONLY the lines listed in the failures array. Do not modify, reorder, rephrase, or "improve" any other content in the file. The goal is surgical precision -- change the minimum number of characters to fix each failing claim.
</fix_mode>
</modes>
<template_readme>
## README.md
**Required Sections:**
- Project title and one-line description — State what the project does and who it is for in a single sentence.
Discover: Read `package.json` `.name` and `.description`; fall back to directory name if no package.json exists.
- Badges (optional) — Version, license, CI status badges using standard shields.io format. Include only if
`package.json` has a `version` field or a LICENSE file is present. Do not fabricate badge URLs.
- Installation — Exact install command(s) the user must run. Discover the package manager by checking for
`package.json` (npm/yarn/pnpm), `setup.py` or `pyproject.toml` (pip), `Cargo.toml` (cargo), `go.mod` (go get).
Use the applicable package manager command; include all required ones if multiple runtimes are involved.
- Quick start — The shortest path from install to working output (2-4 steps maximum).
Discover: `package.json` `scripts.start` or `scripts.dev`; primary CLI bin entry from `package.json` `.bin`;
look for a `examples/` or `demo/` directory with a runnable entry point.
- Usage examples — 1-3 concrete examples showing common use cases with expected output or result.
Discover: Read entry-point files (`bin/`, `src/index.*`, `lib/index.*`) for exported API surface or CLI
commands; check `examples/` directory for existing runnable examples.
- Contributing link — One line: "See CONTRIBUTING.md for guidelines." Include only if CONTRIBUTING.md exists
in the project root or is in the current doc generation queue.
- License — One line stating the license type and a link to the LICENSE file.
Discover: Read LICENSE file first line; fall back to `package.json` `.license` field.
**Content Discovery:**
- `package.json` — name, description, version, license, scripts, bin
- `LICENSE` or `LICENSE.md` — license type (first line)
- `src/index.*`, `lib/index.*` — primary exports
- `bin/` directory — CLI commands
- `examples/` or `demo/` directory — existing usage examples
- `setup.py`, `pyproject.toml`, `Cargo.toml`, `go.mod` — alternate package managers
**Format Notes:**
- Code blocks use the project's primary language (TypeScript/JavaScript/Python/Rust/etc.)
- Installation block uses `bash` language tag
- Quick start uses a numbered list with bash commands
- Keep it scannable — a new user should understand the project within 60 seconds
**Doc Tooling Adaptation:** See `<doc_tooling_guidance>` section.
</template_readme>
<template_architecture>
## ARCHITECTURE.md
**Required Sections:**
- System overview — A single paragraph describing what the system does at the highest level, its primary
inputs and outputs, and the main architectural style (e.g., layered, event-driven, microservices).
Discover: Read the root-level `README.md` or `package.json` description; grep for top-level export patterns.
- Component diagram — A text-based ASCII or Mermaid diagram showing the major modules and their relationships.
Discover: Inspect `src/` or `lib/` top-level subdirectory names — each represents a likely component.
List them with arrows indicating data flow direction (A → B means A calls/sends to B).
- Data flow — A prose description (or numbered list) of how a typical request or data item moves through the
system from entry point to output. Discover: Grep for `app.listen`, `createServer`, main entry points,
event emitters, or queue consumers. Follow the call chain for 2-3 levels.
- Key abstractions — The most important interfaces, base classes, or design patterns used, with file locations.
Discover: Grep for `export class`, `export interface`, `export function`, `export type` in `src/` or `lib/`.
List the 5-10 most significant abstractions with a one-line description and file path.
- Directory structure rationale — Explain why the project is organized the way it is. List top-level
directories with a one-sentence description of each. Discover: Run `ls src/` or `ls lib/`; read index files
of each subdirectory to understand its purpose.
**Content Discovery:**
- `src/` or `lib/` top-level directory listing — major module boundaries
- Grep `export class|export interface|export function` in `src/**/*.ts` or `lib/**/*.js`
- Framework config files: `next.config.*`, `vite.config.*`, `webpack.config.*` — architecture signals
- Entry point: `src/index.*`, `lib/index.*`, `bin/` — top-level exports
- `package.json` `main` and `exports` fields — public API surface
**Format Notes:**
- Use Mermaid `graph TD` syntax for component diagrams when the doc tooling supports it; fall back to ASCII
- Keep component diagrams to 10 nodes maximum — omit leaf-level utilities
- Directory structure can use a code block with tree-style indentation
**Doc Tooling Adaptation:** See `<doc_tooling_guidance>` section.
</template_architecture>
<template_getting_started>
## GETTING-STARTED.md
**Required Sections:**
- Prerequisites — Runtime versions, required tools, and system dependencies the user must have installed
before they can use the project. Discover: `package.json` `engines` field, `.nvmrc` or `.node-version`
file, `Dockerfile` `FROM` line (indicates runtime), `pyproject.toml` `requires-python`.
List exact versions when discoverable; use ">=X.Y" format.
- Installation steps — Step-by-step commands to clone the repo and install dependencies. Always include:
1. Clone command (`git clone {remote URL if detectable, else placeholder}`), 2. `cd` into project dir,
3. Install command (detected from package manager). Discover: `package.json` for npm/yarn/pnpm, `Pipfile`
or `requirements.txt` for pip, `Makefile` for custom install targets.
- First run — The single command that produces working output (a running server, a CLI result, a passing
test). Discover: `package.json` `scripts.start` or `scripts.dev`; `Makefile` `run` or `serve` target;
`README.md` quick-start section if it exists.
- Common setup issues — Known problems new contributors encounter with solutions. Discover: Check for
`.env.example` (missing env var errors), `package.json` `engines` version constraints (wrong runtime
version), `README.md` existing troubleshooting section, common port conflict patterns.
Include at least 2 issues; leave as a placeholder list if none are discoverable.
- Next steps — Links to other generated docs (DEVELOPMENT.md, TESTING.md) so the user knows where to go
after first run.
**Content Discovery:**
- `package.json` `engines` field — Node.js/npm version requirements
- `.nvmrc`, `.node-version` — exact Node version pinned
- `.env.example` or `.env.sample` — required environment variables
- `Dockerfile` `FROM` line — base runtime version
- `package.json` `scripts.start` and `scripts.dev` — first run command
- `Makefile` targets — alternative install/run commands
**Format Notes:**
- Use numbered lists for sequential steps
- Commands use `bash` code blocks
- Version requirements use inline code: `Node.js >= 18.0.0`
**Doc Tooling Adaptation:** See `<doc_tooling_guidance>` section.
</template_getting_started>
<template_development>
## DEVELOPMENT.md
**Required Sections:**
- Local setup — How to fork, clone, install, and configure the project for development (vs production use).
Discover: Same as getting-started but include dev-only steps: `npm install` (not `npm ci`), copying
`.env.example` to `.env`, any `npm run build` or compile step needed before the dev server starts.
- Build commands — All scripts from `package.json` `scripts` field with a brief description of what each
does. Discover: Read `package.json` `scripts`; categorize into build, dev, lint, format, and other.
Omit lifecycle hooks (`prepublish`, `postinstall`) unless they require developer awareness.
- Code style — The linting and formatting tools in use and how to run them. Discover: Check for
`.eslintrc*`, `.eslintrc.json`, `.eslintrc.js`, `eslint.config.*` (ESLint), `.prettierrc*`, `prettier.config.*`
(Prettier), `biome.json` (Biome), `.editorconfig`. Report the tool name, config file location, and the
`package.json` script to run it (e.g., `npm run lint`).
- Branch conventions — How branches should be named and what the main/default branch is. Discover: Check
`.github/PULL_REQUEST_TEMPLATE.md` or `CONTRIBUTING.md` for branch naming rules. If not documented,
infer from recent git branches if accessible; otherwise state "No convention documented."
- PR process — How to submit a pull request. Discover: Read `.github/PULL_REQUEST_TEMPLATE.md` for
required checklist items; read `CONTRIBUTING.md` for review process. Summarize in 3-5 bullet points.
**Content Discovery:**
- `package.json` `scripts` — all build/dev/lint/format/test commands
- `.eslintrc*`, `eslint.config.*` — ESLint configuration presence
- `.prettierrc*`, `prettier.config.*` — Prettier configuration presence
- `biome.json` — Biome linter/formatter configuration
- `.editorconfig` — editor-level style settings
- `.github/PULL_REQUEST_TEMPLATE.md` — PR checklist
- `CONTRIBUTING.md` — branch and PR conventions
**Format Notes:**
- Build commands section uses a table: `| Command | Description |`
- Code style section names the tool (ESLint, Prettier, Biome) before the config detail
- Branch conventions use inline code for branch name patterns (e.g., `feat/my-feature`)
**Doc Tooling Adaptation:** See `<doc_tooling_guidance>` section.
</template_development>
<template_testing>
## TESTING.md
**Required Sections:**
- Test framework and setup — The testing framework(s) in use and any required setup before running tests.
Discover: Check `package.json` `devDependencies` for `jest`, `vitest`, `mocha`, `jasmine`, `pytest`,
`go test` patterns. Check for `jest.config.*`, `vitest.config.*`, `.mocharc.*`. State the framework name,
version (from devDependencies), and any global setup needed (e.g., `npm install` if not already done).
- Running tests — Exact commands to run the full test suite, a subset, or a single file. Discover:
`package.json` `scripts.test`, `scripts.test:unit`, `scripts.test:integration`, `scripts.test:e2e`.
Include the watch mode command if present (e.g., `scripts.test:watch`). Show the command and what it runs.
- Writing new tests — File naming convention and test helper patterns for new contributors. Discover: Inspect
existing test files to determine naming convention (e.g., `*.test.ts`, `*.spec.ts`, `__tests__/*.ts`).
Look for shared test helpers (e.g., `tests/helpers.*`, `test/setup.*`) and describe their purpose briefly.
- Coverage requirements — The minimum coverage thresholds configured for CI. Discover: Check `jest.config.*`
`coverageThreshold`, `vitest.config.*` coverage section, `.nycrc`, `c8` config in `package.json`. State
the thresholds by coverage type (lines, branches, functions, statements). If none configured, state "No
coverage threshold configured."
- CI integration — How tests run in CI. Discover: Read `.github/workflows/*.yml` files and extract the test
execution step(s). State the workflow name, trigger (push/PR), and the test command run.
**Content Discovery:**
- `package.json` `devDependencies` — test framework detection
- `package.json` `scripts.test*` — all test run commands
- `jest.config.*`, `vitest.config.*`, `.mocharc.*` — test configuration
- `.nycrc`, `c8` config — coverage thresholds
- `.github/workflows/*.yml` — CI test steps
- `tests/`, `test/`, `__tests__/` directories — test file naming patterns
**Format Notes:**
- Running tests section uses `bash` code blocks for each command
- Coverage thresholds use a table: `| Type | Threshold |`
- CI integration references the workflow file name and job name
**Doc Tooling Adaptation:** See `<doc_tooling_guidance>` section.
</template_testing>
<template_api>
## API.md
**Required Sections:**
- Authentication — The authentication mechanism used (API keys, JWT, OAuth, session cookies) and how to
include credentials in requests. Discover: Grep for `passport`, `jsonwebtoken`, `jwt-simple`, `express-session`,
`@auth0`, `clerk`, `supabase` in `package.json` dependencies. Grep for `Authorization` header, `Bearer`,
`apiKey`, `x-api-key` patterns in route/middleware files. Use VERIFY markers for actual key values or
external auth service URLs.
- Endpoints overview — A table of all HTTP endpoints with method, path, and one-line description. Discover:
Read files in `src/routes/`, `src/api/`, `app/api/`, `pages/api/` (Next.js), `routes/` directories.
Grep for `router.get|router.post|router.put|router.delete|app.get|app.post` patterns. Check for OpenAPI
or Swagger specs in `openapi.yaml`, `swagger.json`, `docs/openapi.*`.
- Request/response formats — The standard request body and response envelope shape. Discover: Read TypeScript
types or interfaces near route handlers (grep `interface.*Request|interface.*Response|type.*Payload`).
Check for Zod/Joi/Yup schema definitions near route files. Show a representative example per endpoint type.
- Error codes — The standard error response shape and common status codes with their meanings. Discover:
Grep for error handler middleware (Express: `app.use((err, req, res, next)` pattern; Fastify: `setErrorHandler`).
Look for an `errors.ts` or `error-codes.ts` file. List HTTP status codes used with their semantic meaning.
- Rate limits — Any rate limiting configuration applied to the API. Discover: Grep for `express-rate-limit`,
`rate-limiter-flexible`, `@upstash/ratelimit` in `package.json`. Check middleware files for rate limit
config. Use VERIFY marker if rate limit values are environment-dependent.
**Content Discovery:**
- `src/routes/`, `src/api/`, `app/api/`, `pages/api/` — route file locations
- `package.json` `dependencies` — auth and rate-limit library detection
- Grep `router\.(get|post|put|delete|patch)` in route files — endpoint discovery
- `openapi.yaml`, `swagger.json`, `docs/openapi.*` — existing API spec
- TypeScript interface/type files near routes — request/response shapes
- Middleware files — auth and rate-limit middleware
**Format Notes:**
- Endpoints table columns: `| Method | Path | Description | Auth Required |`
- Request/response examples use `json` code blocks
- Rate limits state the window and max requests: "100 requests per 15 minutes"
**VERIFY marker guidance:** Use `<!-- VERIFY: {claim} -->` for:
- External auth service URLs or dashboard links
- API key names not shown in `.env.example`
- Rate limit values that come from environment variables
- Actual base URLs for the deployed API
**Doc Tooling Adaptation:** See `<doc_tooling_guidance>` section.
</template_api>
<template_configuration>
## CONFIGURATION.md
**Required Sections:**
- Environment variables — A table listing every environment variable with name, required/optional status, and
description. Discover: Read `.env.example` or `.env.sample` for the canonical list. Grep for `process.env.`
patterns in `src/`, `lib/`, or `config/` to find variables not in the example file. Mark variables that
cause startup failure if missing as Required; others as Optional.
- Config file format — If the project uses config files (JSON, YAML, TOML) beyond environment variables,
describe the format and location. Discover: Check for `config/`, `config.json`, `config.yaml`, `*.config.js`,
`app.config.*`. Read the file and describe its top-level keys with one-line descriptions.
- Required vs optional settings — Which settings cause the application to fail on startup if absent, and which
have defaults. Discover: Grep for early validation patterns like `if (!process.env.X) throw` or
`z.string().min(1)` (Zod) near config loading. List required settings with their validation error message.
- Defaults — The default values for optional settings as defined in the source code. Discover: Look for
`const X = process.env.Y || 'default-value'` patterns or `schema.default(value)` in config loading code.
Show the variable name, default value, and where it is set.
- Per-environment overrides — How to configure different values for development, staging, and production.
Discover: Check for `.env.development`, `.env.production`, `.env.test` files, `NODE_ENV` conditionals in
config loading, or platform-specific config mechanisms (Vercel env vars, Railway secrets).
**Content Discovery:**
- `.env.example` or `.env.sample` — canonical environment variable list
- Grep `process.env\.` in `src/**` or `lib/**` — all env var references
- `config/`, `src/config.*`, `lib/config.*` — config file locations
- Grep `if.*process\.env|process\.env.*\|\|` — required vs optional detection
- `.env.development`, `.env.production`, `.env.test` — per-environment files
**VERIFY marker guidance:** Use `<!-- VERIFY: {claim} -->` for:
- Production URLs, CDN endpoints, or external service base URLs not in `.env.example`
- Specific secret key names used in production that are not documented in the repo
- Infrastructure-specific values (database cluster names, cloud region identifiers)
- Configuration values that vary per deployment and cannot be inferred from source
**Format Notes:**
- Environment variables table: `| Variable | Required | Default | Description |`
- Config file format uses a `yaml` or `json` code block showing a minimal working example
- Required settings are highlighted with bold or a "Required" label
**Doc Tooling Adaptation:** See `<doc_tooling_guidance>` section.
</template_configuration>
<template_deployment>
## DEPLOYMENT.md
**Required Sections:**
- Deployment targets — Where the project can be deployed and how. Discover: Check for `Dockerfile` (Docker/
container-based), `docker-compose.yml` (Docker Compose), `vercel.json` (Vercel), `netlify.toml` (Netlify),
`fly.toml` (Fly.io), `railway.json` (Railway), `serverless.yml` (Serverless Framework), `.github/workflows/`
files containing `deploy` in their name. List each detected target with its config file.
- Build pipeline — The CI/CD steps that produce the deployment artifact. Discover: Read `.github/workflows/`
YAML files that include a deploy step. Extract the trigger (push to main, tag creation), build command,
and deploy command sequence. If no CI config exists, state "No CI/CD pipeline detected."
- Environment setup — Required environment variables for production deployment, referencing CONFIGURATION.md
for the full list. Discover: Cross-reference `.env.example` Required variables with production deployment
context. Use VERIFY markers for values that must be set in the deployment platform's secret manager.
- Rollback procedure — How to revert a deployment if something goes wrong. Discover: Check CI workflows for
rollback steps; check `fly.toml`, `vercel.json`, or `netlify.toml` for rollback commands. If none found,
state the general approach (e.g., "Redeploy the previous Docker image tag" or "Use platform dashboard").
- Monitoring — How the deployed application is monitored. Discover: Check `package.json` `dependencies` for
Sentry (`@sentry/*`), Datadog (`dd-trace`), New Relic (`newrelic`), OpenTelemetry (`@opentelemetry/*`).
Check for `sentry.config.*` or similar files. Use VERIFY markers for dashboard URLs.
**Content Discovery:**
- `Dockerfile`, `docker-compose.yml` — container deployment
- `vercel.json`, `netlify.toml`, `fly.toml`, `railway.json`, `serverless.yml` — platform config
- `.github/workflows/*.yml` containing `deploy`, `release`, or `publish` — CI/CD pipeline
- `package.json` `dependencies` — monitoring library detection
- `sentry.config.*`, `datadog.config.*` — monitoring configuration files
**VERIFY marker guidance:** Use `<!-- VERIFY: {claim} -->` for:
- Hosting platform URLs, dashboard links, or team-specific project URLs
- Server specifications (RAM, CPU, instance type) not defined in config files
- Actual deployment commands run outside of CI (manual steps on production servers)
- Monitoring dashboard URLs or alert webhook endpoints
- DNS records, domain names, or CDN configuration
**Format Notes:**
- Deployment targets section uses a bullet list or table with config file references
- Build pipeline shows CI steps as a numbered list with the actual commands
- Rollback procedure uses numbered steps for clarity
**Doc Tooling Adaptation:** See `<doc_tooling_guidance>` section.
</template_deployment>
<template_contributing>
## CONTRIBUTING.md
**Required Sections:**
- Code of conduct link — A single line pointing to the code of conduct. Discover: Check for
`CODE_OF_CONDUCT.md` in the project root. If present: "Please read our [Code of Conduct](CODE_OF_CONDUCT.md)
before contributing." If absent: omit this section.
- Development setup — Brief setup instructions for new contributors, referencing DEVELOPMENT.md and
GETTING-STARTED.md rather than duplicating them. Discover: Confirm those docs exist or are being generated.
Include a one-liner: "See GETTING-STARTED.md for prerequisites and first-run instructions, and
DEVELOPMENT.md for local development setup."
- Coding standards — The linting and formatting standards contributors must follow. Discover: Same detection
as DEVELOPMENT.md (ESLint, Prettier, Biome, editorconfig). State the tool, the run command, and whether
CI enforces it (check `.github/workflows/` for lint steps). Keep to 2-4 bullet points.
- PR guidelines — How to submit a pull request and what reviewers look for. Discover: Read
`.github/PULL_REQUEST_TEMPLATE.md` for required checklist items. If absent, check `CONTRIBUTING.md`
patterns in the repo. Include: branch naming, commit message format (conventional commits?), test
requirements, review process. 4-6 bullet points.
- Issue reporting — How to report bugs or request features. Discover: Check `.github/ISSUE_TEMPLATE/`
for bug and feature request templates. State the GitHub Issues URL pattern and what information to include.
If no templates exist, provide standard guidance (steps to reproduce, expected/actual behavior, environment).
**Content Discovery:**
- `CODE_OF_CONDUCT.md` — code of conduct presence
- `.github/PULL_REQUEST_TEMPLATE.md` — PR checklist
- `.github/ISSUE_TEMPLATE/` — issue templates
- `.github/workflows/` — lint/test enforcement in CI
- `package.json` `scripts.lint` and related — code style commands
- `CONTRIBUTING.md` — if exists, use as additional source
**Format Notes:**
- Keep CONTRIBUTING.md concise — contributors should find what they need in under 2 minutes
- Use bullet lists for PR guidelines and coding standards
- Link to other generated docs rather than duplicating their content
**Doc Tooling Adaptation:** See `<doc_tooling_guidance>` section.
</template_contributing>
<template_readme_per_package>
## Per-Package README (monorepo scope)
Used when `scope: per_package` is set in `doc_assignment`.
**Required Sections:**
- Package name and one-line description — State what this specific package does and its role in the monorepo.
Discover: Read `{package_dir}/package.json` `.name` and `.description` fields. Use the scoped package
name (e.g., `@myorg/core`) as the heading.
- Installation — The scoped package install command for consumers of this package.
Discover: Read `{package_dir}/package.json` `.name` for the full scoped package name.
Format: `npm install @scope/pkg-name` (or yarn/pnpm equivalent if detected from root package manager).
Omit if the package is private (`"private": true` in package.json).
- Usage — Key exports or CLI commands specific to this package only. Show 1-2 realistic usage examples.
Discover: Read `{package_dir}/src/index.*` or `{package_dir}/index.*` for the primary export surface.
Check `{package_dir}/package.json` `.main`, `.module`, `.exports` for the entry point.
- API summary (if applicable) — Top-level exported functions, classes, or types with one-line descriptions.
Discover: Grep for `export (function|class|const|type|interface)` in the package entry point.
Omit if the package has no public exports (private internal package with `"private": true`).
- Testing — How to run tests for this package in isolation.
Discover: Read `{package_dir}/package.json` `scripts.test`. If a monorepo test runner is used (Turborepo,
Nx), also show the workspace-scoped command (e.g., `npm run test --workspace=packages/my-pkg`).
**Content Discovery (package-scoped):**
- Read `{package_dir}/package.json` — name, description, version, scripts, main/exports, private flag
- Read `{package_dir}/src/index.*` or `{package_dir}/index.*` — exports
- Check `{package_dir}/test/`, `{package_dir}/tests/`, `{package_dir}/__tests__/` — test structure
**Format Notes:**
- Scope to this package only — do not describe sibling packages or the monorepo root.
- Include a "Part of the [monorepo name] monorepo" line linking to the root README.
- Doc Tooling Adaptation: See `<doc_tooling_guidance>` section.
</template_readme_per_package>
<template_custom>
## Custom Documentation (gap-detected)
Used when `type: custom` is set in `doc_assignment`. These docs fill documentation gaps identified
by the workflow's gap detection step — areas of the codebase that need documentation but don't
have any yet (e.g., frontend components, service modules, utility libraries).
**Inputs from doc_assignment:**
- `description`: What this doc should cover (e.g., "Frontend components in src/components/")
- `output_path`: Where to write the file (follows project's existing doc structure)
**Writing approach:**
1. Read the `description` to understand what area of the codebase to document.
2. Explore the relevant source directories using Read, Grep, Glob to discover:
- What modules/components/services exist
- Their purpose (from exports, JSDoc, comments, naming)
- Key interfaces, props, parameters, return types
- Dependencies and relationships between modules
3. Follow the project's existing documentation style:
- If other docs in the same directory use a specific heading structure, match it
- If other docs include code examples, include them here too
- Match the level of detail present in sibling docs
4. Write the doc to `output_path`.
**Required Sections (adapt based on what's being documented):**
- Overview — One paragraph describing what this area of the codebase does
- Module/component listing — Each significant item with a one-line description
- Key interfaces or APIs — The most important exports, props, or function signatures
- Usage examples — 1-2 concrete examples if applicable
**Content Discovery:**
- Read source files in the directories mentioned in `description`
- Grep for `export`, `module.exports`, `export default` to find public APIs
- Check for existing JSDoc, docstrings, or README files in the source directory
- Read test files if present for usage patterns
**Format Notes:**
- Match the project's existing doc style (discovered from sibling docs in the same directory)
- Use the project's primary language for code blocks
- Keep it practical — focus on what a developer needs to know to use or modify these modules
**Doc Tooling Adaptation:** See `<doc_tooling_guidance>` section.
</template_custom>
<doc_tooling_guidance>
## Doc Tooling Adaptation
When `doc_tooling` in `project_context` indicates a documentation framework, adapt file
placement and frontmatter accordingly. Content structure (sections, headings) does not
change — only location and metadata change.
**Docusaurus** (`doc_tooling.docusaurus: true`):
- Write to `docs/{canonical-filename}` (e.g., `docs/ARCHITECTURE.md`)
- Add YAML frontmatter block at top of file (before GSD marker):
```yaml
---
title: Architecture
sidebar_position: 2
description: System architecture and component overview
---
```
- `sidebar_position`: use 1 for README/overview, 2 for Architecture, 3 for Getting Started, etc.
**VitePress** (`doc_tooling.vitepress: true`):
- Write to `docs/{canonical-filename}` (primary docs directory)
- Add YAML frontmatter:
```yaml
---
title: Architecture
description: System architecture and component overview
---
```
- No `sidebar_position` — VitePress sidebars are configured in `.vitepress/config.*`
**MkDocs** (`doc_tooling.mkdocs: true`):
- Write to `docs/{canonical-filename}` (MkDocs default docs directory)
- Add YAML frontmatter with `title` only:
```yaml
---
title: Architecture
---
```
- Respect the `nav:` section in `mkdocs.yml` if present — use matching filenames.
Read `mkdocs.yml` and check if a nav entry references the target doc before writing.
**Storybook** (`doc_tooling.storybook: true`):
- No special doc placement — Storybook handles component stories, not project docs.
- Generate docs to project root as normal. Storybook detection has no effect on
placement or frontmatter.
**No tooling detected:**
- Write to `docs/` directory by default. Exceptions: `README.md` and `CONTRIBUTING.md` stay at project root.
- The `resolve_modes` table in the workflow determines the exact path for each doc type.
- Create the `docs/` directory if it does not exist.
- No frontmatter added.
</doc_tooling_guidance>
<critical_rules>
1. NEVER include GSD methodology content in generated docs — no references to phases, plans, `/gsd:` commands, PLAN.md, ROADMAP.md, or any GSD workflow concepts. Generated docs describe the TARGET PROJECT exclusively.
2. NEVER touch CHANGELOG.md — it is managed by `/gsd:ship` and is out of scope.
3. ALWAYS include the GSD marker `<!-- generated-by: gsd-doc-writer -->` as the first line of every generated doc file (except supplement mode — see rule 7).
4. ALWAYS explore the actual codebase before writing — never fabricate file paths, function names, endpoints, or configuration values.
8. **ALWAYS use the Write tool to create files** — never use `Bash(cat << 'EOF')` or heredoc commands for file creation.
5. Use `<!-- VERIFY: {claim} -->` markers for any infrastructure claim (URLs, server configs, external service details) that cannot be verified from the repository contents alone.
6. In update mode, PRESERVE user-authored content in sections that are still accurate. Only rewrite inaccurate or missing sections.
7. In supplement mode, NEVER modify existing content. Only append missing sections. Do NOT add the GSD marker to hand-written files.
</critical_rules>
<success_criteria>
- [ ] Doc file written to the correct path
- [ ] GSD marker present as first line
- [ ] All required sections from template are present
- [ ] No GSD methodology references in output
- [ ] All file paths, function names, and commands verified against codebase
- [ ] VERIFY markers placed on undiscoverable infrastructure claims
- [ ] (update mode) User-authored accurate sections preserved
- [ ] (supplement mode) Only missing sections were appended; no existing content was modified
</success_criteria>

View File

@@ -0,0 +1,48 @@
---
name: gsd:docs-update
description: Generate or update project documentation verified against the codebase
argument-hint: "[--force] [--verify-only]"
allowed-tools:
- Read
- Write
- Edit
- Bash
- Glob
- Grep
- Task
- AskUserQuestion
---
<objective>
Generate and update up to 9 documentation files for the current project. Each doc type is written by a gsd-doc-writer subagent that explores the codebase directly — no hallucinated paths, phantom endpoints, or stale signatures.
Flag handling rule:
- The optional flags documented below are available behaviors, not implied active behaviors
- A flag is active only when its literal token appears in `$ARGUMENTS`
- If a documented flag is absent from `$ARGUMENTS`, treat it as inactive
- `--force`: skip preservation prompts, regenerate all docs regardless of existing content or GSD markers
- `--verify-only`: check existing docs for accuracy against codebase, no generation (full verification requires Phase 4 verifier)
- If `--force` and `--verify-only` both appear in `$ARGUMENTS`, `--force` takes precedence
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/docs-update.md
</execution_context>
<context>
Arguments: $ARGUMENTS
**Available optional flags (documentation only — not automatically active):**
- `--force` — Regenerate all docs. Overwrites hand-written and GSD docs alike. No preservation prompts.
- `--verify-only` — Check existing docs for accuracy against the codebase. No files are written. Reports VERIFY marker count. Full codebase fact-checking requires the gsd-doc-verifier agent (Phase 4).
**Active flags must be derived from `$ARGUMENTS`:**
- `--force` is active only if the literal `--force` token is present in `$ARGUMENTS`
- `--verify-only` is active only if the literal `--verify-only` token is present in `$ARGUMENTS`
- If neither token appears, run the standard full-phase generation flow
- Do not infer that a flag is active just because it is documented in this prompt
</context>
<process>
Execute the docs-update workflow from @~/.claude/get-shit-done/workflows/docs-update.md end-to-end.
Preserve all workflow gates (preservation_check, flag handling, wave execution, monorepo dispatch, commit, reporting).
</process>

View File

@@ -133,6 +133,9 @@
* init milestone-op All context for milestone operations
* init map-codebase All context for map-codebase workflow
* init progress All context for progress workflow
*
* Documentation:
* docs-init Project context for docs-update workflow
*/
const fs = require('fs');
@@ -152,6 +155,7 @@ const frontmatter = require('./lib/frontmatter.cjs');
const profilePipeline = require('./lib/profile-pipeline.cjs');
const profileOutput = require('./lib/profile-output.cjs');
const workstream = require('./lib/workstream.cjs');
const docs = require('./lib/docs.cjs');
// ─── Arg parsing helpers ──────────────────────────────────────────────────────
@@ -274,7 +278,7 @@ async function main() {
const command = args[0];
if (!command) {
error('Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>]\nCommands: state, resolve-model, find-phase, commit, verify-summary, verify, frontmatter, template, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, config-new-project, init, workstream');
error('Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>]\nCommands: state, resolve-model, find-phase, commit, verify-summary, verify, frontmatter, template, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, config-new-project, init, workstream, docs-init');
}
// Multi-repo guard: resolve project root for commands that read/write .planning/.
@@ -910,6 +914,13 @@ async function runCommand(command, args, cwd, raw) {
break;
}
// ─── Documentation ────────────────────────────────────────────────────
case 'docs-init': {
docs.cmdDocsInit(cwd, raw);
break;
}
default:
error(`Unknown command: ${command}`);
}

View File

@@ -0,0 +1,267 @@
/**
* Docs — Commands for the docs-update workflow
*
* Provides `cmdDocsInit` which returns project signals, existing doc inventory
* with GSD marker detection, doc tooling detection, monorepo awareness, and
* model resolution. Used by Phase 2 to route doc generation appropriately.
*/
const fs = require('fs');
const path = require('path');
const { output, loadConfig, resolveModelInternal, pathExistsInternal, toPosixPath, checkAgentsInstalled } = require('./core.cjs');
// ─── Constants ────────────────────────────────────────────────────────────────
const GSD_MARKER = '<!-- generated-by: gsd-doc-writer -->';
const SKIP_DIRS = new Set([
'node_modules', '.git', '.planning', '.claude', '__pycache__',
'target', 'dist', 'build', '.next', '.nuxt', 'coverage',
'.vscode', '.idea',
]);
// ─── Private helpers ──────────────────────────────────────────────────────────
/**
* Check whether a file begins with the GSD doc writer marker.
* Reads the first 500 bytes only — avoids loading large files.
*
* @param {string} filePath - Absolute path to the file
* @returns {boolean}
*/
function hasGsdMarker(filePath) {
try {
const buf = Buffer.alloc(500);
const fd = fs.openSync(filePath, 'r');
const bytesRead = fs.readSync(fd, buf, 0, 500, 0);
fs.closeSync(fd);
return buf.slice(0, bytesRead).toString('utf-8').includes(GSD_MARKER);
} catch {
return false;
}
}
/**
* Recursively scan the project root (immediate .md files) and docs/ directory
* (up to 4 levels deep) for Markdown files, excluding dirs in SKIP_DIRS.
*
* @param {string} cwd - Project root
* @returns {Array<{path: string, has_gsd_marker: boolean}>}
*/
function scanExistingDocs(cwd) {
const MAX_DEPTH = 4;
const results = [];
/**
* Recursively walk a directory for .md files up to MAX_DEPTH levels.
* @param {string} dir - Directory to scan
* @param {number} depth - Current depth (1-based)
*/
function walkDir(dir, depth) {
if (depth > MAX_DEPTH) return;
try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
if (SKIP_DIRS.has(entry.name)) continue;
const abs = path.join(dir, entry.name);
if (entry.isDirectory()) {
walkDir(abs, depth + 1);
} else if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) {
const rel = toPosixPath(path.relative(cwd, abs));
results.push({ path: rel, has_gsd_marker: hasGsdMarker(abs) });
}
}
} catch { /* directory may not exist — best-effort */ }
}
// Scan root-level .md files (non-recursive)
try {
const entries = fs.readdirSync(cwd, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) {
const abs = path.join(cwd, entry.name);
const rel = toPosixPath(path.relative(cwd, abs));
results.push({ path: rel, has_gsd_marker: hasGsdMarker(abs) });
}
}
} catch { /* best-effort */ }
// Recursively scan docs/ directory
const docsDir = path.join(cwd, 'docs');
walkDir(docsDir, 1);
// Fallback: if docs/ does not exist, try documentation/ or doc/
try {
fs.statSync(docsDir);
} catch {
const alternatives = ['documentation', 'doc'];
for (const alt of alternatives) {
const altDir = path.join(cwd, alt);
try {
const stat = fs.statSync(altDir);
if (stat.isDirectory()) {
walkDir(altDir, 1);
break;
}
} catch { /* not present */ }
}
}
return results.sort((a, b) => a.path.localeCompare(b.path));
}
/**
* Detect project type signals from the filesystem and package.json.
* All checks are best-effort and never throw.
*
* @param {string} cwd - Project root
* @returns {Object} Boolean signal fields
*/
function detectProjectType(cwd) {
const exists = (rel) => {
try { return pathExistsInternal(cwd, rel); } catch { return false; }
};
// has_cli_bin: package.json has a `bin` field
let has_cli_bin = false;
try {
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'));
has_cli_bin = !!(pkg.bin && (typeof pkg.bin === 'string' || Object.keys(pkg.bin).length > 0));
} catch { /* no package.json or invalid JSON */ }
// is_monorepo: pnpm-workspace.yaml, lerna.json, or package.json workspaces
let is_monorepo = exists('pnpm-workspace.yaml') || exists('lerna.json');
if (!is_monorepo) {
try {
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'));
is_monorepo = Array.isArray(pkg.workspaces) && pkg.workspaces.length > 0;
} catch { /* ignore */ }
}
// has_tests: common test directories or test frameworks in devDependencies
let has_tests = exists('test') || exists('tests') || exists('__tests__') || exists('spec');
if (!has_tests) {
try {
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'));
const devDeps = Object.keys(pkg.devDependencies || {});
has_tests = devDeps.some(d => ['vitest', 'jest', 'mocha', 'jasmine', 'ava'].includes(d));
} catch { /* ignore */ }
}
// has_deploy_config: various deployment config files
const deployFiles = [
'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml',
'fly.toml', 'render.yaml', 'vercel.json', 'netlify.toml', 'railway.json',
'.github/workflows/deploy.yml', '.github/workflows/deploy.yaml',
];
const has_deploy_config = deployFiles.some(f => exists(f));
return {
has_package_json: exists('package.json'),
has_api_routes: (
exists('src/app/api') || exists('routes') || exists('src/routes') ||
exists('api') || exists('server')
),
has_cli_bin,
is_open_source: exists('LICENSE') || exists('LICENSE.md'),
has_deploy_config,
is_monorepo,
has_tests,
};
}
/**
* Detect known documentation tooling in the project.
*
* @param {string} cwd - Project root
* @returns {Object} Boolean detection fields
*/
function detectDocTooling(cwd) {
const exists = (rel) => {
try { return pathExistsInternal(cwd, rel); } catch { return false; }
};
return {
docusaurus: exists('docusaurus.config.js') || exists('docusaurus.config.ts'),
vitepress: (
exists('.vitepress/config.js') ||
exists('.vitepress/config.ts') ||
exists('.vitepress/config.mts')
),
mkdocs: exists('mkdocs.yml'),
storybook: exists('.storybook'),
};
}
/**
* Extract monorepo workspace globs from pnpm-workspace.yaml, package.json
* workspaces, or lerna.json.
*
* @param {string} cwd - Project root
* @returns {string[]} Array of workspace glob patterns, or [] if not a monorepo
*/
function detectMonorepoWorkspaces(cwd) {
// pnpm-workspace.yaml
try {
const content = fs.readFileSync(path.join(cwd, 'pnpm-workspace.yaml'), 'utf-8');
const lines = content.split('\n');
const workspaces = [];
for (const line of lines) {
const m = line.match(/^\s*-\s+['"]?(.+?)['"]?\s*$/);
if (m) workspaces.push(m[1].trim());
}
if (workspaces.length > 0) return workspaces;
} catch { /* not present */ }
// package.json workspaces
try {
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'));
if (Array.isArray(pkg.workspaces) && pkg.workspaces.length > 0) {
return pkg.workspaces;
}
} catch { /* not present or invalid */ }
// lerna.json
try {
const lerna = JSON.parse(fs.readFileSync(path.join(cwd, 'lerna.json'), 'utf-8'));
if (Array.isArray(lerna.packages) && lerna.packages.length > 0) {
return lerna.packages;
}
} catch { /* not present or invalid */ }
return [];
}
// ─── Public commands ──────────────────────────────────────────────────────────
/**
* Return JSON context for the docs-update workflow: project signals, existing
* doc inventory, doc tooling detection, monorepo workspaces, and model
* resolution. Follows the cmdInitMapCodebase pattern.
*
* @example
* node gsd-tools.cjs docs-init --raw
*
* @param {string} cwd - Project root directory
* @param {boolean} raw - Pass raw JSON flag through to output()
*/
function cmdDocsInit(cwd, raw) {
const config = loadConfig(cwd);
const result = {
doc_writer_model: resolveModelInternal(cwd, 'gsd-doc-writer'),
commit_docs: config.commit_docs,
existing_docs: scanExistingDocs(cwd),
project_type: detectProjectType(cwd),
doc_tooling: detectDocTooling(cwd),
monorepo_workspaces: detectMonorepoWorkspaces(cwd),
planning_exists: pathExistsInternal(cwd, '.planning'),
};
// Inject project_root and agent installation status (mirrors withProjectRoot in init.cjs)
result.project_root = cwd;
const agentStatus = checkAgentsInstalled();
result.agents_installed = agentStatus.agents_installed;
result.missing_agents = agentStatus.missing_agents;
output(result, raw);
}
module.exports = { cmdDocsInit };

View File

@@ -22,6 +22,8 @@ const MODEL_PROFILES = {
'gsd-ui-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
'gsd-ui-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
'gsd-ui-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
'gsd-doc-writer': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
'gsd-doc-verifier': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
};
const VALID_PROFILES = Object.keys(MODEL_PROFILES['gsd-planner']);

File diff suppressed because it is too large Load Diff

View File

@@ -1166,6 +1166,8 @@ describe('E2E: Copilot full install verification', () => {
'gsd-assumptions-analyzer.agent.md',
'gsd-codebase-mapper.agent.md',
'gsd-debugger.agent.md',
'gsd-doc-verifier.agent.md',
'gsd-doc-writer.agent.md',
'gsd-executor.agent.md',
'gsd-integration-checker.agent.md',
'gsd-nyquist-auditor.agent.md',

272
tests/docs-update.test.cjs Normal file
View File

@@ -0,0 +1,272 @@
/**
* GSD Tools Tests - docs-update
*
* Integration tests for the docs-init gsd-tools subcommand.
* Covers: JSON output shape, project type detection, existing doc scanning,
* GSD marker detection, and doc tooling detection.
*
* Requirements: VERF-03
*/
const { test, describe, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
// ─── JSON output shape ────────────────────────────────────────────────────────
describe('docs-init command', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject();
});
afterEach(() => {
cleanup(tmpDir);
});
test('returns expected JSON shape', () => {
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
// Top-level scalar fields
assert.strictEqual(typeof data.doc_writer_model, 'string');
assert.strictEqual(typeof data.commit_docs, 'boolean');
assert.strictEqual(typeof data.planning_exists, 'boolean');
assert.strictEqual(typeof data.project_root, 'string');
assert.strictEqual(typeof data.agents_installed, 'boolean');
// Array fields
assert.ok(Array.isArray(data.existing_docs), 'existing_docs should be an array');
assert.ok(Array.isArray(data.monorepo_workspaces), 'monorepo_workspaces should be an array');
assert.ok(Array.isArray(data.missing_agents), 'missing_agents should be an array');
// project_type object with 7 boolean fields
assert.ok(data.project_type && typeof data.project_type === 'object', 'project_type should be an object');
assert.strictEqual(typeof data.project_type.has_package_json, 'boolean');
assert.strictEqual(typeof data.project_type.has_api_routes, 'boolean');
assert.strictEqual(typeof data.project_type.has_cli_bin, 'boolean');
assert.strictEqual(typeof data.project_type.is_open_source, 'boolean');
assert.strictEqual(typeof data.project_type.has_deploy_config, 'boolean');
assert.strictEqual(typeof data.project_type.is_monorepo, 'boolean');
assert.strictEqual(typeof data.project_type.has_tests, 'boolean');
// doc_tooling object with 4 boolean fields
assert.ok(data.doc_tooling && typeof data.doc_tooling === 'object', 'doc_tooling should be an object');
assert.strictEqual(typeof data.doc_tooling.docusaurus, 'boolean');
assert.strictEqual(typeof data.doc_tooling.vitepress, 'boolean');
assert.strictEqual(typeof data.doc_tooling.mkdocs, 'boolean');
assert.strictEqual(typeof data.doc_tooling.storybook, 'boolean');
// planning_exists is true since createTempProject creates .planning/
assert.strictEqual(data.planning_exists, true);
});
test('bare project returns all false signals', () => {
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
// All project_type fields should be false for a bare project
assert.strictEqual(data.project_type.has_package_json, false);
assert.strictEqual(data.project_type.has_api_routes, false);
assert.strictEqual(data.project_type.has_cli_bin, false);
assert.strictEqual(data.project_type.is_open_source, false);
assert.strictEqual(data.project_type.has_deploy_config, false);
assert.strictEqual(data.project_type.is_monorepo, false);
assert.strictEqual(data.project_type.has_tests, false);
// No docs, no workspaces, no doc tooling
assert.deepEqual(data.existing_docs, []);
assert.deepEqual(data.monorepo_workspaces, []);
assert.strictEqual(data.doc_tooling.docusaurus, false);
assert.strictEqual(data.doc_tooling.vitepress, false);
assert.strictEqual(data.doc_tooling.mkdocs, false);
assert.strictEqual(data.doc_tooling.storybook, false);
});
});
// ─── project type detection ───────────────────────────────────────────────────
describe('project type detection', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject();
});
afterEach(() => {
cleanup(tmpDir);
});
test('detects CLI tool from package.json bin field', () => {
fs.writeFileSync(
path.join(tmpDir, 'package.json'),
JSON.stringify({ name: 'my-cli', bin: { mycli: 'bin/cli.js' } }),
'utf-8'
);
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
assert.strictEqual(data.project_type.has_cli_bin, true);
assert.strictEqual(data.project_type.has_package_json, true);
});
test('detects open source from LICENSE file', () => {
fs.writeFileSync(path.join(tmpDir, 'LICENSE'), 'MIT License', 'utf-8');
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
assert.strictEqual(data.project_type.is_open_source, true);
});
test('detects monorepo from package.json workspaces', () => {
fs.writeFileSync(
path.join(tmpDir, 'package.json'),
JSON.stringify({ name: 'mono', workspaces: ['packages/*'] }),
'utf-8'
);
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
assert.strictEqual(data.project_type.is_monorepo, true);
assert.ok(data.monorepo_workspaces.includes('packages/*'), 'monorepo_workspaces should contain packages/*');
});
test('detects tests from tests directory', () => {
fs.mkdirSync(path.join(tmpDir, 'tests'), { recursive: true });
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
assert.strictEqual(data.project_type.has_tests, true);
});
test('detects deploy config from Dockerfile', () => {
fs.writeFileSync(path.join(tmpDir, 'Dockerfile'), 'FROM node:20', 'utf-8');
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
assert.strictEqual(data.project_type.has_deploy_config, true);
});
test('detects API routes from src/app/api directory', () => {
fs.mkdirSync(path.join(tmpDir, 'src', 'app', 'api'), { recursive: true });
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
assert.strictEqual(data.project_type.has_api_routes, true);
});
});
// ─── existing doc scanning ────────────────────────────────────────────────────
describe('existing doc scanning', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject();
});
afterEach(() => {
cleanup(tmpDir);
});
test('scans .md files in project root', () => {
fs.writeFileSync(path.join(tmpDir, 'README.md'), '# README\n', 'utf-8');
fs.writeFileSync(path.join(tmpDir, 'ARCHITECTURE.md'), '# Architecture\n', 'utf-8');
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
assert.ok(data.existing_docs.length >= 2, 'existing_docs should contain at least 2 entries');
const paths = data.existing_docs.map(d => d.path);
assert.ok(paths.includes('README.md'), 'existing_docs should contain README.md');
assert.ok(paths.includes('ARCHITECTURE.md'), 'existing_docs should contain ARCHITECTURE.md');
});
test('detects GSD marker in existing docs', () => {
fs.writeFileSync(
path.join(tmpDir, 'README.md'),
'<!-- generated-by: gsd-doc-writer -->\n# README\n',
'utf-8'
);
fs.writeFileSync(path.join(tmpDir, 'NOTES.md'), '# Notes\n', 'utf-8');
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
const readmeEntry = data.existing_docs.find(d => d.path === 'README.md');
assert.ok(readmeEntry, 'README.md should appear in existing_docs');
assert.strictEqual(readmeEntry.has_gsd_marker, true, 'README.md should have GSD marker');
const notesEntry = data.existing_docs.find(d => d.path === 'NOTES.md');
assert.ok(notesEntry, 'NOTES.md should appear in existing_docs');
assert.strictEqual(notesEntry.has_gsd_marker, false, 'NOTES.md should not have GSD marker');
});
});
// ─── doc tooling detection ────────────────────────────────────────────────────
describe('doc tooling detection', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject();
});
afterEach(() => {
cleanup(tmpDir);
});
test('detects Docusaurus config', () => {
fs.writeFileSync(path.join(tmpDir, 'docusaurus.config.js'), 'module.exports = {};', 'utf-8');
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
assert.strictEqual(data.doc_tooling.docusaurus, true);
});
test('detects VitePress config', () => {
fs.mkdirSync(path.join(tmpDir, '.vitepress'), { recursive: true });
fs.writeFileSync(path.join(tmpDir, '.vitepress', 'config.ts'), 'export default {};', 'utf-8');
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
assert.strictEqual(data.doc_tooling.vitepress, true);
});
test('detects MkDocs config', () => {
fs.writeFileSync(path.join(tmpDir, 'mkdocs.yml'), 'site_name: test', 'utf-8');
const result = runGsdTools(['docs-init'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const data = JSON.parse(result.output);
assert.strictEqual(data.doc_tooling.mkdocs, true);
});
});