docguard-cli 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/PHILOSOPHY.md +59 -106
  2. package/README.md +23 -1
  3. package/cli/commands/diagnose.mjs +157 -52
  4. package/cli/commands/fix.mjs +113 -1
  5. package/cli/commands/generate.mjs +91 -0
  6. package/cli/commands/hooks.mjs +40 -2
  7. package/cli/commands/score.mjs +22 -0
  8. package/cli/commands/sync.mjs +123 -0
  9. package/cli/docguard.mjs +22 -0
  10. package/cli/scanners/cdk.mjs +10 -0
  11. package/cli/scanners/frontend.mjs +438 -0
  12. package/cli/scanners/iac.mjs +235 -0
  13. package/cli/scanners/integrations.mjs +116 -0
  14. package/cli/scanners/memory-plan.mjs +242 -0
  15. package/cli/scanners/project-type.mjs +310 -0
  16. package/cli/scanners/routes.mjs +149 -0
  17. package/cli/scanners/schemas.mjs +174 -1
  18. package/cli/shared-ignore.mjs +29 -2
  19. package/cli/shared-source.mjs +2 -1
  20. package/cli/validators/api-surface.mjs +112 -37
  21. package/cli/validators/changelog.mjs +3 -2
  22. package/cli/validators/docs-coverage.mjs +125 -6
  23. package/cli/validators/docs-sync.mjs +49 -8
  24. package/cli/validators/metadata-sync.mjs +6 -1
  25. package/cli/validators/metrics-consistency.mjs +5 -2
  26. package/cli/validators/test-spec.mjs +129 -11
  27. package/cli/validators/todo-tracking.mjs +55 -2
  28. package/cli/writers/api-reference.mjs +101 -0
  29. package/cli/writers/mechanical.mjs +116 -0
  30. package/cli/writers/sections.mjs +148 -0
  31. package/commands/docguard.fix.md +19 -3
  32. package/docs/doc-sections.md +37 -0
  33. package/extensions/spec-kit-docguard/README.md +7 -4
  34. package/extensions/spec-kit-docguard/commands/fix.md +74 -0
  35. package/extensions/spec-kit-docguard/commands/generate.md +25 -2
  36. package/extensions/spec-kit-docguard/commands/sync.md +62 -0
  37. package/extensions/spec-kit-docguard/extension.yml +1 -1
  38. package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +13 -3
  39. package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +2 -2
  40. package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +2 -2
  41. package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +2 -2
  42. package/extensions/spec-kit-docguard/skills/docguard-sync/SKILL.md +111 -0
  43. package/package.json +1 -1
  44. package/templates/ARCHITECTURE.md.template +52 -0
@@ -0,0 +1,101 @@
1
+ /**
2
+ * API-REFERENCE.md Writer — deterministic, structural edits only.
3
+ *
4
+ * Used by `docguard fix --write` to MECHANICALLY remove endpoints that are
5
+ * documented but no longer exist in the actual API surface. This performs NO
6
+ * content rewriting (that needs an LLM) — it only deletes the structural pieces
7
+ * that document a now-absent endpoint:
8
+ * 1. its summary-table row: | `GET` | `/api/...` | ... |
9
+ * 2. its detail block: #### GET `/api/...` … up to the next heading
10
+ *
11
+ * Pure string transform — idempotent, no disk I/O here.
12
+ *
13
+ * Zero NPM dependencies — pure Node.js built-ins only.
14
+ */
15
+
16
+ import { normalizePath, endpointKey } from '../scanners/api-doc.mjs';
17
+
18
+ const HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']);
19
+ const HEADING_RE = /^#{1,6}\s/;
20
+ // An endpoint detail heading: "#### GET `/path`" (backticks optional, any level).
21
+ const ENDPOINT_HEADING_RE = /^#{2,6}\s+`?(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)`?\s+`?(\/[^\s`|]+)`?/i;
22
+
23
+ /** True if the doc is DocGuard-generated (safe for `--write` to edit). */
24
+ export function hasGeneratedMarker(content) {
25
+ return /<!--\s*docguard:generated\s+true\s*-->/i.test(content || '');
26
+ }
27
+
28
+ /**
29
+ * If a markdown line is an API summary-table row, return its endpoint key.
30
+ * Row shape: | `GET` | `/api/...` | ... |
31
+ */
32
+ function tableRowKey(line) {
33
+ if (!line.includes('|')) return null;
34
+ const cells = line.split('|').map(s => s.trim()).filter(s => s.length > 0);
35
+ if (cells.length < 2) return null;
36
+ const method = cells[0].replace(/`/g, '').trim().toUpperCase();
37
+ if (!HTTP_METHODS.has(method)) return null;
38
+ for (let i = 1; i < cells.length; i++) {
39
+ const cand = cells[i].replace(/`/g, '').trim();
40
+ if (cand.startsWith('/')) return endpointKey(method, cand);
41
+ }
42
+ return null;
43
+ }
44
+
45
+ /** If a line is an endpoint detail heading, return its endpoint key. */
46
+ function headingKey(line) {
47
+ const m = line.match(ENDPOINT_HEADING_RE);
48
+ if (!m) return null;
49
+ return endpointKey(m[1], m[2]);
50
+ }
51
+
52
+ /**
53
+ * Remove the table row(s) and detail block(s) for the given endpoints.
54
+ *
55
+ * @param {string} content - API-REFERENCE.md content
56
+ * @param {Array<{method:string,path:string}>} endpoints - endpoints to remove
57
+ * @returns {{ content: string, removed: string[] }} new content + removed keys
58
+ */
59
+ export function removeEndpoints(content, endpoints) {
60
+ const targets = new Set((endpoints || []).map(e => endpointKey(e.method, e.path)));
61
+ if (targets.size === 0) return { content, removed: [] };
62
+
63
+ const lines = content.split('\n');
64
+ const out = [];
65
+ const removed = new Set();
66
+ let skippingBlock = false;
67
+
68
+ for (const line of lines) {
69
+ const isHeading = HEADING_RE.test(line);
70
+
71
+ if (isHeading) {
72
+ // Any heading terminates a block we were skipping.
73
+ const hk = headingKey(line);
74
+ if (hk && targets.has(hk)) {
75
+ // Start (or continue into a new) skipped detail block.
76
+ skippingBlock = true;
77
+ removed.add(hk);
78
+ continue; // drop the heading line itself
79
+ }
80
+ // A non-target heading ends skipping and is kept.
81
+ skippingBlock = false;
82
+ out.push(line);
83
+ continue;
84
+ }
85
+
86
+ if (skippingBlock) continue; // inside a removed detail block
87
+
88
+ // Not skipping: drop a matching summary-table row.
89
+ const rk = tableRowKey(line);
90
+ if (rk && targets.has(rk)) {
91
+ removed.add(rk);
92
+ continue;
93
+ }
94
+
95
+ out.push(line);
96
+ }
97
+
98
+ return { content: out.join('\n'), removed: [...removed] };
99
+ }
100
+
101
+ export { normalizePath };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Mechanical Fix Registry — applies deterministic, no-LLM fixes in place.
3
+ *
4
+ * Validators surface structured `fixes[]` actions; this module knows how to
5
+ * apply each TYPE safely and idempotently. These are surgical token/structure
6
+ * edits the validator already located precisely — never prose rewrites.
7
+ *
8
+ * Fix types:
9
+ * - replace-count : stale "N validators/checks" → actual count (Metrics-Consistency)
10
+ * - replace-version : stale version ref → current version (Metadata-Sync)
11
+ * - insert-changelog-unreleased : add a `## [Unreleased]` header (Changelog)
12
+ * - remove-endpoint : delete a documented-but-absent endpoint (API-Surface; delegated)
13
+ *
14
+ * Pure file edits, no LLM. Zero NPM dependencies.
15
+ */
16
+
17
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
18
+ import { resolve } from 'node:path';
19
+ import { removeEndpoints, hasGeneratedMarker } from './api-reference.mjs';
20
+
21
+ const esc = (s) => String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
22
+
23
+ /** replace-count: "<found> <label>" → "<actual> <label>" in the file. */
24
+ function applyReplaceCount(projectDir, fix) {
25
+ const full = resolve(projectDir, fix.file);
26
+ if (!existsSync(full)) return { applied: false };
27
+ const content = readFileSync(full, 'utf-8');
28
+ const re = new RegExp(`\\b${esc(fix.found)}(\\s+(?:automated\\s+)?${esc(fix.label)}\\b)`, 'g');
29
+ const next = content.replace(re, `${fix.actual}$1`);
30
+ if (next === content) return { applied: false };
31
+ writeFileSync(full, next, 'utf-8');
32
+ return { applied: true, detail: `${fix.file}: "${fix.found} ${fix.label}" → "${fix.actual} ${fix.label}"` };
33
+ }
34
+
35
+ /** replace-version: stale version → current, ONLY in actionable contexts. */
36
+ function applyReplaceVersion(projectDir, fix) {
37
+ const full = resolve(projectDir, fix.file);
38
+ if (!existsSync(full)) return { applied: false };
39
+ const content = readFileSync(full, 'utf-8');
40
+ const f = esc(fix.found);
41
+ // Mirror metadata-sync's actionable detection so we never touch prose.
42
+ const patterns = [
43
+ new RegExp(`((?:archive|tags|releases|download)\\/v?)${f}`, 'g'),
44
+ new RegExp(`(@)${f}`, 'g'),
45
+ new RegExp(`(version:\\s*["']?)${f}`, 'g'),
46
+ ];
47
+ let next = content;
48
+ for (const re of patterns) next = next.replace(re, `$1${fix.actual}`);
49
+ if (next === content) return { applied: false };
50
+ writeFileSync(full, next, 'utf-8');
51
+ return { applied: true, detail: `${fix.file}: v${fix.found} → v${fix.actual}` };
52
+ }
53
+
54
+ /** insert-changelog-unreleased: add `## [Unreleased]` after the title/intro. */
55
+ function applyInsertChangelogUnreleased(projectDir, fix) {
56
+ const full = resolve(projectDir, fix.file);
57
+ if (!existsSync(full)) return { applied: false };
58
+ const content = readFileSync(full, 'utf-8');
59
+ if (/\[unreleased\]/i.test(content)) return { applied: false }; // idempotent
60
+ const lines = content.split('\n');
61
+ // Insert before the first version heading `## [x.y.z]`, else after the H1, else top.
62
+ let idx = lines.findIndex(l => /^##\s*\[\d/.test(l));
63
+ if (idx < 0) {
64
+ const h1 = lines.findIndex(l => /^#\s/.test(l));
65
+ idx = h1 >= 0 ? h1 + 1 : 0;
66
+ }
67
+ const block = idx > 0 && lines[idx - 1].trim() !== '' ? ['', '## [Unreleased]', ''] : ['## [Unreleased]', ''];
68
+ lines.splice(idx, 0, ...block);
69
+ writeFileSync(full, lines.join('\n'), 'utf-8');
70
+ return { applied: true, detail: `${fix.file}: added ## [Unreleased]` };
71
+ }
72
+
73
+ /** remove-endpoint: delegate to the API-REFERENCE writer (marker-gated). */
74
+ function applyRemoveEndpoint(projectDir, fix, { force = false } = {}) {
75
+ const full = resolve(projectDir, fix.doc || 'docs-canonical/API-REFERENCE.md');
76
+ if (!existsSync(full)) return { applied: false };
77
+ const content = readFileSync(full, 'utf-8');
78
+ if (!hasGeneratedMarker(content) && !force) {
79
+ return { applied: false, skipped: `${fix.doc} not docguard:generated (use --force)` };
80
+ }
81
+ const { content: next, removed } = removeEndpoints(content, [{ method: fix.method, path: fix.path }]);
82
+ if (removed.length === 0 || next === content) return { applied: false };
83
+ writeFileSync(full, next, 'utf-8');
84
+ return { applied: true, detail: `${fix.doc}: removed ${fix.method} ${fix.path}` };
85
+ }
86
+
87
+ const APPLIERS = {
88
+ 'replace-count': applyReplaceCount,
89
+ 'replace-version': applyReplaceVersion,
90
+ 'insert-changelog-unreleased': applyInsertChangelogUnreleased,
91
+ 'remove-endpoint': applyRemoveEndpoint,
92
+ };
93
+
94
+ export const MECHANICAL_FIX_TYPES = Object.keys(APPLIERS);
95
+
96
+ /** Apply a single structured fix. Returns { applied, detail?, skipped? }. */
97
+ export function applyMechanicalFix(projectDir, fix, opts = {}) {
98
+ const fn = APPLIERS[fix.type];
99
+ if (!fn) return { applied: false, skipped: `unknown fix type: ${fix.type}` };
100
+ return fn(projectDir, fix, opts);
101
+ }
102
+
103
+ /**
104
+ * Apply a batch of fixes; returns a summary.
105
+ * @returns {{ applied: object[], skipped: object[] }}
106
+ */
107
+ export function applyMechanicalFixes(projectDir, fixes, opts = {}) {
108
+ const applied = [];
109
+ const skipped = [];
110
+ for (const fix of fixes) {
111
+ const r = applyMechanicalFix(projectDir, fix, opts);
112
+ if (r.applied) applied.push({ ...fix, detail: r.detail });
113
+ else if (r.skipped) skipped.push({ ...fix, reason: r.skipped });
114
+ }
115
+ return { applied, skipped };
116
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Section-addressable docs — the foundation for surgical, non-destructive doc
3
+ * maintenance.
4
+ *
5
+ * Canonical docs mix two kinds of content:
6
+ * - CODE-DERIVED sections (endpoint tables, entity lists, env-var tables) that
7
+ * DocGuard can regenerate from the codebase, and
8
+ * - HUMAN prose (rationale, "why", design intent) that must NEVER be clobbered.
9
+ *
10
+ * We mark the regenerable regions with HTML comments so the doc stays plain,
11
+ * readable markdown:
12
+ *
13
+ * <!-- docguard:section id=api-endpoints source=code -->
14
+ * | `GET` | `/api/x` | … |
15
+ * <!-- /docguard:section -->
16
+ *
17
+ * DocGuard rewrites ONLY the bytes between a section's open/close markers.
18
+ * Everything outside any marker (the human writing) is preserved exactly.
19
+ *
20
+ * Pure string transforms — idempotent, no disk I/O. Zero NPM dependencies.
21
+ */
22
+
23
+ const OPEN_RE = /^[ \t]*<!--\s*docguard:section\b([^>]*?)-->[ \t]*$/;
24
+ const CLOSE_RE = /^[ \t]*<!--\s*\/docguard:section\s*-->[ \t]*$/;
25
+
26
+ /** Parse `id=foo source=code` style attributes from an open-marker tail. */
27
+ function parseAttrs(attrStr) {
28
+ const attrs = {};
29
+ const re = /(\w[\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"']+))/g;
30
+ let m;
31
+ while ((m = re.exec(attrStr)) !== null) {
32
+ attrs[m[1]] = m[2] ?? m[3] ?? m[4] ?? '';
33
+ }
34
+ return attrs;
35
+ }
36
+
37
+ /**
38
+ * Parse all well-formed sections in a document.
39
+ * A section is a line matching the open marker, then content lines, then a
40
+ * close-marker line. An open with no matching close is ignored (not corrupted).
41
+ * @returns {Array<{ id, source, attrs, openLine, closeLine, body }>}
42
+ */
43
+ export function parseSections(content) {
44
+ const lines = String(content).split('\n');
45
+ const sections = [];
46
+ let open = null;
47
+
48
+ for (let i = 0; i < lines.length; i++) {
49
+ const line = lines[i];
50
+ if (open === null) {
51
+ const om = line.match(OPEN_RE);
52
+ if (om) {
53
+ const attrs = parseAttrs(om[1] || '');
54
+ open = { attrs, openLine: i };
55
+ }
56
+ } else if (CLOSE_RE.test(line)) {
57
+ sections.push({
58
+ id: open.attrs.id || '',
59
+ source: open.attrs.source || 'code',
60
+ attrs: open.attrs,
61
+ openLine: open.openLine,
62
+ closeLine: i,
63
+ body: lines.slice(open.openLine + 1, i).join('\n'),
64
+ });
65
+ open = null;
66
+ }
67
+ // Note: a second open before a close just extends the search for a close;
68
+ // we keep the FIRST open's start, so malformed nesting can't corrupt content.
69
+ }
70
+ return sections;
71
+ }
72
+
73
+ /** Get a single section by id, or null. */
74
+ export function getSection(content, id) {
75
+ return parseSections(content).find(s => s.id === id) || null;
76
+ }
77
+
78
+ /** List section ids present in a document. */
79
+ export function listSections(content) {
80
+ return parseSections(content).map(s => s.id);
81
+ }
82
+
83
+ /** Render a full marked section block (open marker + body + close marker). */
84
+ export function renderSection(id, body, { source = 'code' } = {}) {
85
+ const inner = String(body).replace(/^\n+/, '').replace(/\n+$/, '');
86
+ return `<!-- docguard:section id=${id} source=${source} -->\n${inner}\n<!-- /docguard:section -->`;
87
+ }
88
+
89
+ /**
90
+ * Replace ONLY the body of an existing section, preserving its markers and all
91
+ * surrounding content. Idempotent: if the new body matches, returns unchanged.
92
+ * @returns {{ content: string, replaced: boolean }}
93
+ */
94
+ export function replaceSection(content, id, newBody) {
95
+ const lines = String(content).split('\n');
96
+ const sections = parseSections(content);
97
+ const sec = sections.find(s => s.id === id);
98
+ if (!sec) return { content, replaced: false };
99
+
100
+ const inner = String(newBody).replace(/^\n+/, '').replace(/\n+$/, '');
101
+ if (sec.body === inner) return { content, replaced: false }; // idempotent no-op
102
+
103
+ const before = lines.slice(0, sec.openLine + 1);
104
+ const after = lines.slice(sec.closeLine);
105
+ const next = [...before, ...inner.split('\n'), ...after].join('\n');
106
+ return { content: next, replaced: true };
107
+ }
108
+
109
+ /**
110
+ * Replace a section's body if it exists; otherwise INSERT a new section.
111
+ * Insert position: `after:<id>` (after another section), 'top' (after the H1 /
112
+ * first heading), or 'end' (default, appended).
113
+ * @returns {{ content: string, action: 'replaced'|'inserted'|'unchanged' }}
114
+ */
115
+ export function upsertSection(content, id, newBody, { source = 'code', position = 'end' } = {}) {
116
+ if (getSection(content, id)) {
117
+ const r = replaceSection(content, id, newBody);
118
+ return { content: r.content, action: r.replaced ? 'replaced' : 'unchanged' };
119
+ }
120
+
121
+ const block = renderSection(id, newBody, { source });
122
+ const lines = String(content).split('\n');
123
+
124
+ // Insert after a named section.
125
+ const afterMatch = /^after:(.+)$/.exec(position);
126
+ if (afterMatch) {
127
+ const target = parseSections(content).find(s => s.id === afterMatch[1].trim());
128
+ if (target) {
129
+ const before = lines.slice(0, target.closeLine + 1);
130
+ const after = lines.slice(target.closeLine + 1);
131
+ return { content: [...before, '', block, ...after].join('\n'), action: 'inserted' };
132
+ }
133
+ }
134
+
135
+ // Insert just after the first heading (keeps the doc title on top).
136
+ if (position === 'top') {
137
+ const hIdx = lines.findIndex(l => /^#{1,6}\s/.test(l));
138
+ if (hIdx >= 0) {
139
+ const before = lines.slice(0, hIdx + 1);
140
+ const after = lines.slice(hIdx + 1);
141
+ return { content: [...before, '', block, ...after].join('\n'), action: 'inserted' };
142
+ }
143
+ }
144
+
145
+ // Default: append at end (single trailing newline).
146
+ const base = String(content).replace(/\n+$/, '');
147
+ return { content: `${base}\n\n${block}\n`, action: 'inserted' };
148
+ }
@@ -11,11 +11,27 @@ handoffs:
11
11
 
12
12
  # DocGuard Fix — AI-Assisted Documentation Repair
13
13
 
14
- Generate or repair canonical documentation by researching the actual codebase.
14
+ Generate or repair canonical documentation. DocGuard splits fixes into two kinds:
15
15
 
16
- ## What to do
16
+ - **Mechanical (deterministic, no AI):** structural edits DocGuard applies itself with
17
+ `docguard fix --write` — e.g. removing an endpoint from `docs-canonical/API-REFERENCE.md`
18
+ that the OpenAPI spec confirms no longer exists (its table row + detail block are deleted).
19
+ - **Agent (needs an AI):** content rewrites that require judgment — e.g. replacing an
20
+ X-Ray prose section with CloudWatch, or writing a new endpoint's request/response block.
21
+ These use the research-prompt workflow below.
17
22
 
18
- 1. **Identify what needs fixing**:
23
+ ## Apply mechanical fixes first (fast, safe)
24
+
25
+ ```bash
26
+ npx docguard-cli fix --write # removes stale documented endpoints; idempotent
27
+ ```
28
+ - Only edits docs marked `<!-- docguard:generated true -->` (use `--force` to override).
29
+ - Prints exactly what it removed. Re-run is a no-op if nothing changed.
30
+ - Run `docguard guard` afterward; whatever remains is agent work (below).
31
+
32
+ ## What to do (agent work)
33
+
34
+ 1. **Identify what needs fixing** (each issue is tagged `mechanical` or `agent`):
19
35
  ```bash
20
36
  npx docguard-cli diagnose
21
37
  ```
@@ -0,0 +1,37 @@
1
+ # Section-Addressable Docs
2
+
3
+ DocGuard maintains canonical docs *surgically*: it regenerates the parts that are
4
+ derived from code while never touching the prose a human wrote. It does this with
5
+ HTML-comment markers that keep the document plain, readable markdown.
6
+
7
+ ## The marker format
8
+
9
+ ```markdown
10
+ <!-- docguard:section id=api-endpoints source=code -->
11
+ | `GET` | `/api/users` | … |
12
+ <!-- /docguard:section -->
13
+ ```
14
+
15
+ - **`id`** — a stable identifier for the section (e.g. `api-endpoints`, `entities`,
16
+ `env-vars`, `screens`). DocGuard addresses sections by id.
17
+ - **`source`** — `code` means DocGuard owns and may regenerate this block from the
18
+ codebase; `human` means it is author-owned and DocGuard will not rewrite it.
19
+
20
+ Markers must each sit on their own line. An open marker with no matching close is
21
+ ignored (DocGuard never corrupts a malformed doc).
22
+
23
+ ## What DocGuard does and does not touch
24
+
25
+ - It rewrites **only** the bytes between a `source=code` section's open and close
26
+ markers when the underlying code changes.
27
+ - **Everything outside any marker — and any `source=human` section — is preserved
28
+ exactly.** Your rationale, "why" notes, and design intent are safe.
29
+
30
+ ## Why this matters
31
+
32
+ This is the foundation for two things:
33
+
34
+ 1. **Complete generation** — `docguard generate` writes code-derived sections inside
35
+ markers, then an AI agent fills the prose around them.
36
+ 2. **Always up to date** — `docguard sync` refreshes just the affected section when
37
+ code changes, instead of regenerating (and clobbering) the whole document.
@@ -1,13 +1,16 @@
1
1
  # DocGuard — CDD Enforcement Extension for Spec Kit
2
2
 
3
- Enterprise-grade Canonical-Driven Development (CDD) enforcement for [Spec Kit](https://github.com/github/spec-kit). Validates, scores, and fixes project documentation with 19 automated validators, AI-driven research workflows, and spec-kit integration hooks.
3
+ Enterprise-grade Canonical-Driven Development (CDD) enforcement and **AI-readable project memory** for [Spec Kit](https://github.com/github/spec-kit). DocGuard builds a complete, language-aware documentation memory of any codebase (`generate --plan`), keeps it always up to date as code changes (`sync`), and verifies it (`guard`) with deterministic mechanical fixes (`fix --write`) where it can and grounded agent prompts where prose is needed.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **20 Validators** — Structure, Security, Doc Quality, Test-Spec, Drift-Comments, API-Surface, Freshness, and 13 more
8
- - **4 AI Skills** — Enterprise-grade AI behavior protocols (not just step-lists)
9
- - **3 Bash Scripts** — JSON-output orchestration for AI consumption
10
- - **Workflow Chaining** — YAML handoffs enable guard fix review score flows
8
+ - **Language-agnostic** — JS/TS, Python, Rust, Go, Java/Kotlin, Ruby, PHP, C#. Polyglot/monorepo-aware.
9
+ - **AI-powered Generate** — `generate --plan` builds the code-truth skeleton in `<!-- docguard:section -->` markers and emits a structured agent task manifest; the AI writes the prose.
10
+ - **Always up to date** — `sync` surgically refreshes code-truth doc sections in place, **preserves human prose**, flags prose for agent review.
11
+ - **Mechanical `fix --write`** — deterministic, no-LLM: remove stale documented endpoints, refresh stale "N validators" counts, replace stale version refs, insert missing `## [Unreleased]`.
12
+ - **5 AI Skills** — docguard-fix, docguard-guard, docguard-sync, docguard-review, docguard-score (enterprise-grade behavior protocols, not just step-lists)
13
+ - **Workflow Chaining** — YAML handoffs enable guard → sync → fix → review → score flows
11
14
  - **Spec Kit Hooks** — Quality gate integrations at implement, tasks, and review phases
12
15
  - **Zero Dependencies** — Pure Node.js built-ins only
13
16
 
@@ -0,0 +1,74 @@
1
+ ---
2
+ description: Fix documentation drift — mechanical (no AI) or AI-driven research, depending on the issue
3
+ allowed-tools: Bash, Read, Edit
4
+ ---
5
+
6
+ # DocGuard Fix
7
+
8
+ DocGuard splits drift into two kinds and is explicit about which is which.
9
+
10
+ - **Mechanical** (deterministic, no AI): apply with `docguard fix --write`. Covers
11
+ removing endpoints documented but absent in code, refreshing stale "N validators"
12
+ counts, replacing stale version references, inserting a missing `## [Unreleased]`.
13
+ - **Agent** (needs judgment): content rewrites — e.g. updating an X-Ray section to
14
+ CloudWatch, writing a new endpoint's request/response block.
15
+
16
+ ## User Input
17
+
18
+ ```text
19
+ $ARGUMENTS
20
+ ```
21
+
22
+ You **MUST** consider the user input before proceeding (if not empty).
23
+
24
+ ## Execution
25
+
26
+ ### Step 1 — Apply mechanical fixes (fast, safe, no AI)
27
+
28
+ ```bash
29
+ npx --yes docguard-cli@latest fix --write
30
+ ```
31
+
32
+ Output lists every applied fix. Idempotent: re-running is a no-op if nothing changed.
33
+ Only edits `<!-- docguard:generated true -->` docs unless `--force`.
34
+
35
+ ### Step 2 — Identify remaining issues by kind
36
+
37
+ ```bash
38
+ npx --yes docguard-cli@latest diagnose --format json
39
+ ```
40
+
41
+ Each issue is tagged `fixKind: mechanical` (mostly handled by step 1) or
42
+ `fixKind: agent`. Focus on the agent ones.
43
+
44
+ ### Step 3 — Use the deep doc prompt for content rewrites
45
+
46
+ For each affected canonical doc, get a research-grounded prompt:
47
+
48
+ ```bash
49
+ npx --yes docguard-cli@latest fix --doc architecture
50
+ npx --yes docguard-cli@latest fix --doc data-model
51
+ npx --yes docguard-cli@latest fix --doc api-reference
52
+ npx --yes docguard-cli@latest fix --doc security
53
+ npx --yes docguard-cli@latest fix --doc test-spec
54
+ npx --yes docguard-cli@latest fix --doc environment
55
+ ```
56
+
57
+ Execute the research steps in each prompt: read actual code files, map modules,
58
+ trace routes, extract real schemas, identify real auth patterns. Then write the
59
+ sections — using real file paths, real module names, real dependencies. No placeholders.
60
+
61
+ ### Step 4 — Verify
62
+
63
+ ```bash
64
+ npx --yes docguard-cli@latest guard
65
+ ```
66
+
67
+ Iterate until clean (max 3 rounds; if still failing, report remaining issues).
68
+
69
+ ## Flags
70
+
71
+ - `--write` — apply deterministic fixes in place (step 1).
72
+ - `--doc <name>` — emit a research-grounded prompt for one specific document (step 3).
73
+ - `--force` — for `--write`, edit docs that lack the generated marker.
74
+ - `--format json` — machine-readable issue list (with `fixKind`).
@@ -11,7 +11,12 @@ handoffs:
11
11
 
12
12
  # DocGuard Generate
13
13
 
14
- Scans your codebase and generates canonical documentation: ARCHITECTURE.md, DATA-MODEL.md, TEST-SPEC.md, SECURITY.md, ENVIRONMENT.md, and API-REFERENCE.md.
14
+ Scans your codebase (JS/TS, Python, Rust, Go, Java/Kotlin, Ruby, PHP, C# — polyglot/monorepo-aware) and generates the canonical documentation memory: ARCHITECTURE.md, DATA-MODEL.md, TEST-SPEC.md, SECURITY.md, ENVIRONMENT.md, API-REFERENCE.md, SCREENS.md.
15
+
16
+ Two modes:
17
+
18
+ - **`--plan`** (AI-powered, recommended) — emits a structured agent task manifest + writes the code-truth skeleton inside `<!-- docguard:section -->` markers. The AI agent then writes the prose grounded in scanned facts. Human prose is preserved.
19
+ - **default** — purely deterministic generation: writes templated docs with TODO placeholders. Use when no AI agent is available.
15
20
 
16
21
  ## User Input
17
22
 
@@ -19,7 +24,25 @@ $ARGUMENTS
19
24
 
20
25
  ## Steps
21
26
 
22
- 1. Run DocGuard generate on the current project:
27
+ 1. **Preview the plan** what code-truth facts were captured + what the agent will write:
28
+
29
+ ```bash
30
+ npx --yes docguard-cli@latest generate --plan $ARGUMENTS
31
+ ```
32
+
33
+ 2. **Scaffold the skeleton docs** (marked sections filled with code-truth, prose sections as agent-task placeholders):
34
+
35
+ ```bash
36
+ npx --yes docguard-cli@latest generate --plan --write $ARGUMENTS
37
+ ```
38
+
39
+ 3. **Or get the machine-readable manifest** to drive an agent:
40
+
41
+ ```bash
42
+ npx --yes docguard-cli@latest generate --plan --format json $ARGUMENTS
43
+ ```
44
+
45
+ 4. **Fallback** (no AI, deterministic generation):
23
46
 
24
47
  ```bash
25
48
  npx --yes docguard-cli@latest generate $ARGUMENTS
@@ -0,0 +1,62 @@
1
+ ---
2
+ description: Keep canonical docs always up to date — refresh code-truth sections in place, preserve human prose
3
+ allowed-tools: Bash, Read, Edit
4
+ ---
5
+
6
+ # DocGuard Sync
7
+
8
+ Keep the documentation memory ALWAYS UP TO DATE. Sync re-derives every code-truth
9
+ section (endpoints, entities, screens, tech stack, env vars) and refreshes the
10
+ matching `source=code` sections of existing canonical docs in place. **Human prose
11
+ is never touched** — it lives outside markers or in `source=human` sections.
12
+
13
+ When a code section changes, the prose sections in that doc are flagged for agent review.
14
+
15
+ ## User Input
16
+
17
+ ```text
18
+ $ARGUMENTS
19
+ ```
20
+
21
+ You **MUST** consider the user input before proceeding (if not empty).
22
+
23
+ ## Execution
24
+
25
+ 1. Preview what will change (dry run, never writes):
26
+
27
+ ```bash
28
+ npx --yes docguard-cli@latest sync $ARGUMENTS
29
+ ```
30
+
31
+ 2. If only `source=code` sections are stale, apply the mechanical refresh:
32
+
33
+ ```bash
34
+ npx --yes docguard-cli@latest sync --write $ARGUMENTS
35
+ ```
36
+
37
+ 3. For each "prose to review" line, **read the affected doc and update the
38
+ surrounding human-written section to match the new code reality** (e.g. if the
39
+ endpoints table grew, update the API overview prose). Then re-run guard:
40
+
41
+ ```bash
42
+ npx --yes docguard-cli@latest guard
43
+ ```
44
+
45
+ ## Flags
46
+
47
+ - `--since <ref>` — also report which code files changed since this git ref (context for prose updates).
48
+ - `--write` — apply the mechanical refreshes. Default is a dry-run preview.
49
+ - `--force` — sync docs even without the `<!-- docguard:generated true -->` marker.
50
+ - `--format json` — machine-readable output (`updates`, `reviews`, `skipped`).
51
+
52
+ ## When to use
53
+
54
+ - **Before opening a PR:** `docguard sync --since main` to refresh any stale sections.
55
+ - **On a pre-commit hook:** auto-keep the memory current.
56
+ - **After a big refactor:** confirm the doc map still matches the territory.
57
+
58
+ ## Triage glyphs
59
+
60
+ - `↻` mechanically refreshed (code section updated)
61
+ - `•` stale (will refresh with `--write`)
62
+ - `🤖` prose to review — open the doc, update the relevant human-written section
@@ -3,7 +3,7 @@ schema_version: "1.0"
3
3
  extension:
4
4
  id: "docguard"
5
5
  name: "DocGuard — CDD Enforcement"
6
- version: "0.9.9"
6
+ version: "0.11.1"
7
7
  description: "Canonical-Driven Development enforcement as a true spec-kit extension. LLM-first design with 19 automated validators, 4 AI behavior skills, spec-kit skill chaining, and workflow hooks. Zero NPM runtime dependencies."
8
8
  author: "Ricardo Accioly"
9
9
  repository: "https://github.com/raccioly/docguard"