docguard-cli 0.18.1 → 0.21.0

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 (33) hide show
  1. package/README.md +128 -34
  2. package/cli/commands/demo.mjs +241 -0
  3. package/cli/commands/guard.mjs +20 -2
  4. package/cli/commands/init.mjs +122 -0
  5. package/cli/docguard.mjs +125 -47
  6. package/cli/validators/canonical-sync.mjs +211 -0
  7. package/cli/validators/spec-kit.mjs +14 -0
  8. package/docs/quickstart.md +1 -1
  9. package/extensions/spec-kit-docguard/README.md +1 -1
  10. package/extensions/spec-kit-docguard/extension.yml +5 -5
  11. package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +2 -2
  12. package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +2 -2
  13. package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +2 -2
  14. package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +2 -2
  15. package/extensions/spec-kit-docguard/skills/docguard-sync/SKILL.md +1 -1
  16. package/package.json +1 -1
  17. package/templates/demo-fixture/.docguard.json +8 -0
  18. package/templates/demo-fixture/.env.example +5 -0
  19. package/templates/demo-fixture/AGENTS.md +14 -0
  20. package/templates/demo-fixture/CHANGELOG.md +13 -0
  21. package/templates/demo-fixture/DRIFT-LOG.md +3 -0
  22. package/templates/demo-fixture/README.md +17 -0
  23. package/templates/demo-fixture/docs-canonical/API-REFERENCE.md +36 -0
  24. package/templates/demo-fixture/docs-canonical/ARCHITECTURE.md +30 -0
  25. package/templates/demo-fixture/docs-canonical/DATA-MODEL.md +30 -0
  26. package/templates/demo-fixture/docs-canonical/ENVIRONMENT.md +20 -0
  27. package/templates/demo-fixture/docs-canonical/SECURITY.md +15 -0
  28. package/templates/demo-fixture/docs-canonical/TEST-SPEC.md +10 -0
  29. package/templates/demo-fixture/package.json +10 -0
  30. package/templates/demo-fixture/src/api.mjs +18 -0
  31. package/templates/demo-fixture/src/notifier.mjs +23 -0
  32. package/templates/demo-fixture/src/scheduler.mjs +8 -0
  33. package/templates/demo-fixture/src/worker.mjs +15 -0
package/cli/docguard.mjs CHANGED
@@ -44,6 +44,7 @@ import { runUpgrade } from './commands/upgrade.mjs';
44
44
  import { runImpact } from './commands/impact.mjs';
45
45
  import { runExplain } from './commands/explain.mjs';
46
46
  import { runMemory } from './commands/memory.mjs';
47
+ import { runDemo } from './commands/demo.mjs';
47
48
  import { ensureSkills } from './ensure-skills.mjs';
48
49
 
49
50
  // ── Shared constants (imported to break circular dependencies) ──────────
@@ -282,36 +283,41 @@ function printHelp() {
282
283
  console.log(`${c.bold}Usage:${c.reset}
283
284
  docguard <command> [options]
284
285
 
285
- ${c.bold}Getting Started:${c.reset}
286
- ${c.green}init${c.reset} Initialize CDD docs (interactive setup)
287
- ${c.green}setup${c.reset} Full onboarding wizard (skills, integrations, hooks)
288
- ${c.green}generate${c.reset} Reverse-engineer canonical docs from existing code
286
+ ${c.bold}First-time? Try the demo (no install, no setup):${c.reset}
287
+ ${c.green}npx docguard-cli demo${c.reset} ${c.dim}— 30-second tour against a sample project${c.reset}
289
288
 
290
- ${c.bold}Enforcement:${c.reset}
291
- ${c.green}guard${c.reset} Validate project against canonical docs (51+ checks)
292
- ${c.green}diagnose${c.reset} AI orchestrator guard fix in one command
289
+ ${c.bold}The Daily 5${c.reset} ${c.dim}— what you'll reach for 95% of the time${c.reset}
290
+ ${c.green}init${c.reset} Bootstrap a project auto-detects existing code and scans (${c.cyan}--skeleton${c.reset} for blank templates, ${c.cyan}--wizard${c.reset} for guided, ${c.cyan}--with <name>${c.reset} for scaffolders)
291
+ ${c.green}guard${c.reset} Validate against canonical docs (23 validators)
292
+ ${c.green}diff${c.reset} Show gaps between docs and code (add ${c.cyan}--since <ref>${c.reset} for changed-file impact)
293
+ ${c.green}sync${c.reset} Refresh code-truth doc sections — keeps memory always up to date
294
+ ${c.green}score${c.reset} CDD maturity score (0-100; ${c.cyan}--diff${c.reset} for delta between refs)
293
295
 
294
- ${c.bold}Memory (build & maintain docs):${c.reset}
295
- ${c.green}generate --plan${c.reset} AI-powered: scan any project, emit agent task manifest + skeleton
296
- ${c.green}sync${c.reset} Refresh code-truth doc sections to match current code (always up to date)
297
-
298
- ${c.bold}Analysis:${c.reset}
299
- ${c.green}score${c.reset} CDD maturity score (0-100)
300
- ${c.green}trace${c.reset} Requirements traceability matrix
301
- ${c.green}diff${c.reset} Show gaps between docs and code (detailed view)
302
-
303
- ${c.bold}CI/CD & Automation:${c.reset}
304
- ${c.green}ci${c.reset} Pipeline gate (guard + score, exit codes)
305
- ${c.green}hooks${c.reset} Install/manage git hooks
306
- ${c.green}watch${c.reset} Watch for changes, re-run guard
307
-
308
- ${c.bold}Utilities:${c.reset}
296
+ ${c.bold}Tools (situational, but day-to-day useful)${c.reset}
297
+ ${c.green}demo${c.reset} Zero-install tour: see what DocGuard catches against a sample project in 30s
298
+ ${c.green}diagnose${c.reset} AI orchestrator guard emit fix prompts in one command
309
299
  ${c.green}fix${c.reset} Generate AI fix instructions for specific docs
310
- ${c.green}agents${c.reset} Generate agent config files (AGENTS.md, CLAUDE.md)
311
- ${c.green}badge${c.reset} Generate CDD score badges for README
312
-
313
- ${c.bold}Experimental:${c.reset}
314
- ${c.dim}publish${c.reset} Scaffold external doc sites (Mintlify)
300
+ ${c.green}generate${c.reset} Reverse-engineer canonical docs from existing code (${c.cyan}--plan${c.reset} for AI scan)
301
+ ${c.green}explain${c.reset} Explain a validator key or warning text
302
+ ${c.green}memory${c.reset} Show what DocGuard remembers (${c.cyan}--diff${c.reset} drills into drift)
303
+ ${c.green}trace${c.reset} Requirements traceability matrix (${c.cyan}--reverse${c.reset} for code→doc map)
304
+ ${c.green}upgrade${c.reset} Migrate ${c.cyan}.docguard.json${c.reset} schema + CLI (${c.cyan}--apply --pr${c.reset} for team-wide PR)
305
+ ${c.green}watch${c.reset} Live mode: re-run guard on file changes
306
+
307
+ ${c.bold}init --with <name>${c.reset} ${c.dim}— optional scaffolders, picked at init time${c.reset}
308
+ ${c.dim}agents${c.reset} AGENTS.md / CLAUDE.md / .cursor/rules / Copilot instructions
309
+ ${c.dim}hooks${c.reset} Git pre-commit / pre-push hooks
310
+ ${c.dim}ci${c.reset} GitHub Actions / pipeline config
311
+ ${c.dim}badge${c.reset} Shields.io score badges for README
312
+ ${c.dim}llms${c.reset} llms.txt generation
313
+ ${c.dim}publish${c.reset} External doc-site scaffold (Mintlify) ${c.dim}— experimental${c.reset}
314
+
315
+ ${c.bold}Deprecation aliases${c.reset} ${c.dim}— still work in v0.20.x with a yellow warning${c.reset}
316
+ ${c.dim}setup${c.reset} → ${c.cyan}init --wizard${c.reset}
317
+ ${c.dim}agents · hooks · ci · badge · llms · publish${c.reset} → ${c.cyan}init --with <name>${c.reset}
318
+ ${c.dim}impact${c.reset} → ${c.cyan}diff --since <ref>${c.reset}
319
+ ${c.dim}audit${c.reset} → ${c.green}guard${c.reset} ${c.dim}(permanent — no warning, no removal planned)${c.reset}
320
+ ${c.dim}See docs-implementation/MIGRATION-v0.20.md for the full timeline.${c.reset}
315
321
 
316
322
  ${c.bold}Options:${c.reset}
317
323
  --dir <path> Project directory (default: current directory)
@@ -459,6 +465,26 @@ async function main() {
459
465
  // v0.17-P2: `docguard memory --diff` drills into accuracy mismatches.
460
466
  // Distinct from the `diff` command itself (which is a top-level cmd).
461
467
  flags.diff = true;
468
+ } else if (args[i] === '--with' && args[i + 1]) {
469
+ // v0.20: `docguard init --with agents,hooks,ci,badge,llms,publish`
470
+ // folds the six standalone scaffolders into init. Comma-separated
471
+ // names, each dispatched to the matching runner after init finishes.
472
+ flags.with = args[i + 1].split(',').map(s => s.trim()).filter(Boolean);
473
+ i++;
474
+ } else if (args[i] === '--wizard') {
475
+ // v0.20: `docguard init --wizard` runs the 7-step interactive
476
+ // onboarding flow (previously `docguard setup`). `setup` keeps
477
+ // working as a deprecation alias.
478
+ flags.wizard = true;
479
+ } else if (args[i] === '--skeleton') {
480
+ // v0.21: `docguard init --skeleton` opts out of smart "scan and propose"
481
+ // detection and forces the blank-template path. Useful for greenfield
482
+ // projects and CI flows that want deterministic output.
483
+ flags.skeleton = true;
484
+ } else if (args[i] === '--keep') {
485
+ // v0.21: `docguard demo --keep` doesn't delete the temp fixture after
486
+ // running (useful for poking around what DocGuard set up).
487
+ flags.keep = true;
462
488
  } else if (!args[i].startsWith('--') && i > 0) {
463
489
  // Positional args go into flags.args for commands that take them (e.g.
464
490
  // `docguard trace --reverse <path>`). Skip the command itself (i === 0).
@@ -522,17 +548,61 @@ async function main() {
522
548
  ensureSkills(projectDir, flags);
523
549
  }
524
550
 
551
+ // v0.20: deprecation aliases. The legacy command keeps working through v0.20
552
+ // and emits a yellow stderr warning suggesting the new shape. Quiet mode
553
+ // (e.g. inside hooks) suppresses the warning so CI output stays clean.
554
+ // The full deprecation timeline is in docs-implementation/MIGRATION-v0.20.md.
555
+ const DEPRECATED_COMMANDS = {
556
+ setup: { since: '0.20', replacement: 'docguard init --wizard' },
557
+ agents: { since: '0.20', replacement: 'docguard init --with agents' },
558
+ hooks: { since: '0.20', replacement: 'docguard init --with hooks' },
559
+ ci: { since: '0.20', replacement: 'docguard init --with ci' },
560
+ badge: { since: '0.20', replacement: 'docguard init --with badge' },
561
+ llms: { since: '0.20', replacement: 'docguard init --with llms' },
562
+ publish: { since: '0.20', replacement: 'docguard init --with publish' },
563
+ impact: { since: '0.20', replacement: 'docguard diff --since <ref>' },
564
+ };
565
+
566
+ // v0.20: dropped aliases — the 10 cute variants the audit identified.
567
+ // `audit` is intentionally NOT here; it remains a permanent silent alias
568
+ // for `guard` (per SURFACE-AUDIT §8.1 — older CI scripts depend on it).
569
+ const DROPPED_ALIASES = {
570
+ onboard: 'setup (deprecated) — try `docguard init --wizard`',
571
+ gen: 'generate',
572
+ badges: 'badge (deprecated) — try `docguard init --with badge`',
573
+ pipeline: 'ci (deprecated) — try `docguard init --with ci`',
574
+ repair: 'fix',
575
+ dx: 'diagnose',
576
+ pub: 'publish (deprecated) — try `docguard init --with publish`',
577
+ traceability: 'trace',
578
+ 'help-warning': 'explain',
579
+ update: 'upgrade',
580
+ };
581
+
582
+ if (DROPPED_ALIASES[command]) {
583
+ console.error(`${c.red}Unknown command: ${command}${c.reset}`);
584
+ console.error(`${c.yellow}Hint: this alias was removed in v0.20. Try ${c.cyan}docguard ${DROPPED_ALIASES[command]}${c.yellow}.${c.reset}`);
585
+ console.error(`${c.dim}See docs-implementation/MIGRATION-v0.20.md for the full list.${c.reset}`);
586
+ process.exit(1);
587
+ }
588
+
589
+ if (DEPRECATED_COMMANDS[command] && !flags.quiet) {
590
+ const { since, replacement } = DEPRECATED_COMMANDS[command];
591
+ console.error(`${c.yellow}⚠ Deprecated since v${since}:${c.reset} ${c.cyan}docguard ${command}${c.reset} → use ${c.cyan}${replacement}${c.reset}`);
592
+ console.error(`${c.dim} The old form still works in v0.20.x but will be removed in v1.0. See MIGRATION-v0.20.md.${c.reset}`);
593
+ }
594
+
525
595
  switch (command) {
526
596
  case 'audit':
527
- // audit is an alias for guard guard does everything the old audit did + 50 more checks
597
+ // Permanent silent alias for guard (SURFACE-AUDIT §8.1).
528
598
  runGuard(projectDir, config, flags);
529
599
  break;
530
600
  case 'init':
531
601
  await runInit(projectDir, config, flags);
532
602
  break;
533
603
  case 'setup':
534
- case 'onboard':
535
- await runSetup(projectDir, config, flags);
604
+ // v0.20: deprecated → dispatches to init --wizard
605
+ await runInit(projectDir, config, { ...flags, wizard: true });
536
606
  break;
537
607
  case 'guard':
538
608
  runGuard(projectDir, config, flags);
@@ -541,32 +611,35 @@ async function main() {
541
611
  runScore(projectDir, config, flags);
542
612
  break;
543
613
  case 'diff':
544
- runDiff(projectDir, config, flags);
614
+ // v0.20: `docguard diff --since <ref>` dispatches to the impact-mode
615
+ // analyzer (post-commit "which docs reference files changed since X").
616
+ // Without --since it's the standard current-state drift report.
617
+ if (flags.since) {
618
+ runImpact(projectDir, config, flags);
619
+ } else {
620
+ runDiff(projectDir, config, flags);
621
+ }
545
622
  break;
546
623
  case 'agents':
547
- runAgents(projectDir, config, flags);
624
+ // v0.20: deprecated → dispatches through init --with
625
+ await runInit(projectDir, config, { ...flags, with: ['agents'], skipPrompts: true });
548
626
  break;
549
627
  case 'generate':
550
- case 'gen':
551
628
  runGenerate(projectDir, config, flags);
552
629
  break;
553
630
  case 'hooks':
554
- runHooks(projectDir, config, flags);
631
+ await runInit(projectDir, config, { ...flags, with: ['hooks'], skipPrompts: true });
555
632
  break;
556
633
  case 'badge':
557
- case 'badges':
558
- runBadge(projectDir, config, flags);
634
+ await runInit(projectDir, config, { ...flags, with: ['badge'], skipPrompts: true });
559
635
  break;
560
636
  case 'ci':
561
- case 'pipeline':
562
- runCI(projectDir, config, flags);
637
+ await runInit(projectDir, config, { ...flags, with: ['ci'], skipPrompts: true });
563
638
  break;
564
639
  case 'fix':
565
- case 'repair':
566
640
  runFix(projectDir, config, flags);
567
641
  break;
568
642
  case 'diagnose':
569
- case 'dx':
570
643
  runDiagnose(projectDir, config, flags);
571
644
  break;
572
645
  case 'watch':
@@ -576,30 +649,35 @@ async function main() {
576
649
  runSync(projectDir, config, flags);
577
650
  break;
578
651
  case 'publish':
579
- case 'pub':
580
- runPublish(projectDir, config, flags);
652
+ await runInit(projectDir, config, { ...flags, with: ['publish'], skipPrompts: true });
581
653
  break;
582
654
  case 'trace':
583
- case 'traceability':
584
655
  runTrace(projectDir, config, flags);
585
656
  break;
586
657
  case 'llms':
587
- runLlms(projectDir, config, flags);
658
+ await runInit(projectDir, config, { ...flags, with: ['llms'], skipPrompts: true });
588
659
  break;
589
660
  case 'upgrade':
590
- case 'update':
591
661
  await runUpgrade(projectDir, config, flags);
592
662
  break;
593
663
  case 'impact':
594
- runImpact(projectDir, config, flags);
664
+ // v0.20: deprecated alias for `diff --since`. Default --since HEAD~1
665
+ // matches impact's historical behavior.
666
+ runImpact(projectDir, config, { ...flags, since: flags.since || 'HEAD~1' });
595
667
  break;
596
668
  case 'explain':
597
- case 'help-warning':
598
669
  runExplain(projectDir, config, flags);
599
670
  break;
600
671
  case 'memory':
601
672
  runMemory(projectDir, config, flags);
602
673
  break;
674
+ case 'demo':
675
+ // v0.21: zero-install "ah-ha" moment — runs guard against a baked-in
676
+ // fixture (templates/demo-fixture/) and prints curated drift findings
677
+ // with real-world-impact annotations. No state changes outside the
678
+ // temp fixture dir, which is cleaned up on exit.
679
+ runDemo(projectDir, config, flags);
680
+ break;
603
681
  default:
604
682
  console.error(`${c.red}Unknown command: ${command}${c.reset}`);
605
683
  console.log(`Run ${c.cyan}docguard --help${c.reset} for usage.`);
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Canonical-Sync Validator — v0.19-A.
3
+ *
4
+ * The missing self-check. Until this validator existed, `guard` could not
5
+ * see when the docs lied about DocGuard's own surface. README claimed
6
+ * "ships 19 commands" while 21 files existed in `cli/commands/`; the
7
+ * architecture diagram drifted across 5 releases (15 → 19 → still wrong)
8
+ * because no validator was checking. SURFACE-AUDIT.md §7 specifies the
9
+ * rules; this is the implementation.
10
+ *
11
+ * Scope: DocGuard's own repository only. Gated by `package.json` name ===
12
+ * "docguard-cli". For every other project, this validator returns N/A —
13
+ * the "ships N commands" pattern is meaningless in a generic project's
14
+ * docs (their N refers to their own product, not DocGuard's surface).
15
+ *
16
+ * What it checks:
17
+ * 1. README "ships N commands" matches `cli/commands/*.mjs` file count
18
+ * 2. README "N validators" matches `runGuardInternal()` output length
19
+ * (or, if no guardResults passed, falls back to file count + the 2
20
+ * inlined validators: Doc Sections in structure.mjs + Spec-Kit)
21
+ * 3. Validator names enumerated inline in README appear in guard output
22
+ *
23
+ * What it explicitly skips:
24
+ * - ROADMAP.md (historical phase logs — "Built with 9 validators" is
25
+ * legitimately about v0.7, not today)
26
+ * - CHANGELOG.md (same — entries describe what shipped, not current state)
27
+ * - docs-implementation/ (snapshots of past state)
28
+ * - `<!-- docguard:section source=human -->` blocks (prose, not inventory)
29
+ *
30
+ * Self-counting: per SURFACE-AUDIT §8.5, this validator counts itself.
31
+ * After v0.19 ships, README claims "23 validators" (the previous 22 +
32
+ * canonical-sync). The check passes only if the README matches reality
33
+ * INCLUDING the new validator.
34
+ *
35
+ * Severity: HIGH — a doc lying about the basic surface is a credibility
36
+ * killer for a documentation-quality tool.
37
+ *
38
+ * Zero NPM runtime dependencies — pure Node.js built-ins only.
39
+ */
40
+
41
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
42
+ import { resolve, join } from 'node:path';
43
+
44
+ /**
45
+ * Validate that README count claims about DocGuard's surface match code-truth.
46
+ * Returns N/A for non-DocGuard projects.
47
+ *
48
+ * @param {string} projectDir - Project root directory
49
+ * @param {object} config - DocGuard config (unused but required by validator interface)
50
+ * @param {Array} [guardResults] - Results array from runGuardInternal (optional but recommended)
51
+ * @returns {{ errors: string[], warnings: string[], fixes: object[], passed: number, total: number, na?: boolean, naReason?: string }}
52
+ */
53
+ export function validateCanonicalSync(projectDir, config, guardResults) {
54
+ const result = { errors: [], warnings: [], fixes: [], passed: 0, total: 0 };
55
+
56
+ // ── Gate: only run in DocGuard's own repo ─────────────────────────────
57
+ const pkgPath = resolve(projectDir, 'package.json');
58
+ if (!existsSync(pkgPath)) {
59
+ return { ...result, na: true, naReason: 'no package.json' };
60
+ }
61
+
62
+ let pkg;
63
+ try {
64
+ pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
65
+ } catch {
66
+ return { ...result, na: true, naReason: 'unreadable package.json' };
67
+ }
68
+
69
+ if (pkg.name !== 'docguard-cli') {
70
+ return {
71
+ ...result,
72
+ na: true,
73
+ naReason: 'canonical-sync only runs in the docguard-cli repo (it polices DocGuard\'s own surface)',
74
+ };
75
+ }
76
+
77
+ // ── Gather code-truth ────────────────────────────────────────────────
78
+ const cliDir = resolve(projectDir, 'cli');
79
+ const commandsDir = resolve(cliDir, 'commands');
80
+ const validatorsDir = resolve(cliDir, 'validators');
81
+
82
+ if (!existsSync(commandsDir) || !existsSync(validatorsDir)) {
83
+ return { ...result, na: true, naReason: 'cli/commands or cli/validators not found' };
84
+ }
85
+
86
+ const commandFiles = readdirSync(commandsDir).filter(f => f.endsWith('.mjs'));
87
+ const actualCommandFileCount = commandFiles.length;
88
+
89
+ // v0.20: the user-facing command count is what shows up in --help's "Daily 5"
90
+ // and "Tools" sections — NOT the file count, since deprecation aliases dispatch
91
+ // through the same files. Parse cli/docguard.mjs to find names in those
92
+ // sections so the README claim and code-truth stay in lockstep across renames.
93
+ const cliEntry = resolve(cliDir, 'docguard.mjs');
94
+ let actualUserFacingCount = actualCommandFileCount; // fallback if parse fails
95
+ if (existsSync(cliEntry)) {
96
+ try {
97
+ const cliSrc = readFileSync(cliEntry, 'utf-8');
98
+ // Match `${c.green}<name>${c.reset}` inside the Daily 5 / Tools blocks.
99
+ // Anything in init --with / Deprecation aliases sections uses c.dim, so
100
+ // they're naturally excluded.
101
+ const helpBlock = cliSrc.match(/The Daily 5[\s\S]*?Deprecation aliases/);
102
+ if (helpBlock) {
103
+ const greenNames = [...helpBlock[0].matchAll(/\$\{c\.green\}(\w+)\$\{c\.reset\}/g)]
104
+ .map(m => m[1]);
105
+ // Unique names (init shows up only once)
106
+ actualUserFacingCount = [...new Set(greenNames)].length;
107
+ }
108
+ } catch { /* fall through to file-count fallback */ }
109
+ }
110
+ const actualCommandCount = actualUserFacingCount;
111
+
112
+ // Validator count: always use the file-count truth source. It's run-order
113
+ // independent (canonical-sync runs BEFORE metrics-consistency at guard time,
114
+ // so guardResults.length would undercount by 1). File count + 1 for the
115
+ // single inlined validator (Doc Sections, exported alongside Structure from
116
+ // structure.mjs).
117
+ const validatorFiles = readdirSync(validatorsDir).filter(f => f.endsWith('.mjs'));
118
+ const actualValidatorCount = validatorFiles.length + 1; // +1 for Doc Sections inlined in structure.mjs
119
+
120
+ // Names list (currently unused for warnings, but kept for future Check 3
121
+ // where the README enumerates validator names inline).
122
+ let actualValidatorNames = [];
123
+ if (Array.isArray(guardResults) && guardResults.length > 0) {
124
+ actualValidatorNames = guardResults.map(r => r.name).filter(Boolean);
125
+ }
126
+
127
+ // ── Read README ─────────────────────────────────────────────────────
128
+ const readmePath = resolve(projectDir, 'README.md');
129
+ if (!existsSync(readmePath)) {
130
+ result.warnings.push('canonical-sync: README.md not found — cannot check surface claims');
131
+ result.total = 1;
132
+ return result;
133
+ }
134
+
135
+ let readme;
136
+ try {
137
+ readme = readFileSync(readmePath, 'utf-8');
138
+ } catch {
139
+ result.warnings.push('canonical-sync: README.md unreadable');
140
+ result.total = 1;
141
+ return result;
142
+ }
143
+
144
+ // ── Check 1: "ships N commands" ─────────────────────────────────────
145
+ result.total++;
146
+ const shipsCommandsRe = /ships\s+\*{0,2}(\d+)\s+commands?\*{0,2}/i;
147
+ const m1 = readme.match(shipsCommandsRe);
148
+ if (m1) {
149
+ const claimed = Number(m1[1]);
150
+ if (claimed === actualCommandCount) {
151
+ result.passed++;
152
+ } else {
153
+ const detail = actualUserFacingCount !== actualCommandFileCount
154
+ ? `${actualCommandCount} user-facing commands in --help (${actualCommandFileCount} files including deprecation aliases)`
155
+ : `${actualCommandCount} command file(s)`;
156
+ result.warnings.push(
157
+ `README.md claims "ships ${claimed} commands" but the real count is ${detail}. Update the README.`
158
+ );
159
+ }
160
+ } else {
161
+ // No claim found — that's OK, just don't check this one
162
+ result.passed++;
163
+ }
164
+
165
+ // ── Check 2: "N validators" in surface context ──────────────────────
166
+ // Match phrases like "22 validators", "all 22 validators", "the 22 validators"
167
+ // but NOT phase-log entries like "Built with 9 validators" (those are
168
+ // historical, and ROADMAP.md/CHANGELOG.md are skipped at the file level).
169
+ result.total++;
170
+ const validatorMatches = [...readme.matchAll(/(?:all|the|with|across|ships?)\s+\*{0,2}(\d+)\s+validators?\*{0,2}/gi)];
171
+ if (validatorMatches.length > 0) {
172
+ const wrongClaims = validatorMatches
173
+ .map(m => Number(m[1]))
174
+ .filter(n => n !== actualValidatorCount);
175
+ if (wrongClaims.length === 0) {
176
+ result.passed++;
177
+ } else {
178
+ const uniqueWrong = [...new Set(wrongClaims)];
179
+ result.warnings.push(
180
+ `README.md claims ${uniqueWrong.map(n => `"${n} validators"`).join(' / ')} but guard reports ${actualValidatorCount}. Update the README.`
181
+ );
182
+ }
183
+ } else {
184
+ result.passed++;
185
+ }
186
+
187
+ // ── Check 3: architecture-diagram counts ────────────────────────────
188
+ // Catches the specific "Commands (N)" and "Validators (N)" patterns in
189
+ // the mermaid block that drifted across 5 releases.
190
+ result.total++;
191
+ const archMatches = [
192
+ { re: /Commands\s*\((\d+)\)/, label: 'Commands', expected: actualCommandCount },
193
+ { re: /Validators\s*\((\d+)\)/, label: 'Validators', expected: actualValidatorCount },
194
+ ];
195
+ const archWrong = [];
196
+ for (const { re, label, expected } of archMatches) {
197
+ const m = readme.match(re);
198
+ if (m && Number(m[1]) !== expected) {
199
+ archWrong.push(`${label} (${m[1]}) → should be (${expected})`);
200
+ }
201
+ }
202
+ if (archWrong.length === 0) {
203
+ result.passed++;
204
+ } else {
205
+ result.warnings.push(
206
+ `README.md architecture diagram has stale counts: ${archWrong.join('; ')}. Update the mermaid block.`
207
+ );
208
+ }
209
+
210
+ return result;
211
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Spec-Kit Validator — Validates Spec Kit artifacts (specs/, plans/, tasks/, constitution).
3
+ *
4
+ * v0.19-D: Architectural move. The validation logic still lives in
5
+ * `scanners/speckit.mjs` (where the spec-kit data is scanned), but the
6
+ * validator entry-point now lives here in `validators/` so the file count
7
+ * matches the validator count. This also makes the canonical-sync validator's
8
+ * truth source (`ls cli/validators/*.mjs`) align with what `guard` reports.
9
+ *
10
+ * Re-exports `validateSpecKitIntegration` from the scanner. Importers should
11
+ * use this path (`validators/spec-kit.mjs`) going forward.
12
+ */
13
+
14
+ export { validateSpecKitIntegration } from '../scanners/speckit.mjs';
@@ -68,7 +68,7 @@ diagnose → AI reads prompts → AI fixes docs → guard verifies
68
68
  ## Verify
69
69
 
70
70
  ```bash
71
- npx docguard-cli guard # Pass/fail check (22 validators)
71
+ npx docguard-cli guard # Pass/fail check (23 validators)
72
72
  npx docguard-cli score # 0-100 maturity score
73
73
  ```
74
74
 
@@ -4,7 +4,7 @@ Enterprise-grade Canonical-Driven Development (CDD) enforcement and **AI-readabl
4
4
 
5
5
  ## Features
6
6
 
7
- - **22 Validators** — Structure, Security, Doc Quality, Test-Spec, Drift-Comments, API-Surface, Freshness, Cross-Reference, and 13 more
7
+ - **23 Validators** — Structure, Security, Doc Quality, Test-Spec, Drift-Comments, API-Surface, Freshness, Cross-Reference, and 13 more
8
8
  - **Language-agnostic** — JS/TS, Python, Rust, Go, Java/Kotlin, Ruby, PHP, C#. Polyglot/monorepo-aware.
9
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
10
  - **Always up to date** — `sync` surgically refreshes code-truth doc sections in place, **preserves human prose**, flags prose for agent review.
@@ -3,7 +3,7 @@ schema_version: "1.0"
3
3
  extension:
4
4
  id: "docguard"
5
5
  name: "DocGuard — CDD Enforcement"
6
- version: "0.18.1"
6
+ version: "0.21.0"
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"
@@ -28,22 +28,22 @@ provides:
28
28
  - name: "speckit.docguard.guard"
29
29
  file: "commands/guard.md"
30
30
  description: "Run 19-validator quality gate with severity triage and remediation plan"
31
- aliases: ["docguard.guard"]
31
+ aliases: ["speckit.docguard.guard"]
32
32
 
33
33
  - name: "speckit.docguard.fix"
34
34
  file: "commands/generate.md"
35
35
  description: "AI-driven documentation repair with codebase research and validation loops"
36
- aliases: ["docguard.fix"]
36
+ aliases: ["speckit.docguard.fix"]
37
37
 
38
38
  - name: "speckit.docguard.review"
39
39
  file: "commands/diagnose.md"
40
40
  description: "Cross-document semantic consistency analysis (read-only)"
41
- aliases: ["docguard.review"]
41
+ aliases: ["speckit.docguard.review"]
42
42
 
43
43
  - name: "speckit.docguard.score"
44
44
  file: "commands/score.md"
45
45
  description: "CDD maturity score with ROI-based improvement roadmap"
46
- aliases: ["docguard.score"]
46
+ aliases: ["speckit.docguard.score"]
47
47
 
48
48
  - name: "speckit.docguard.diagnose"
49
49
  file: "commands/diagnose.md"
@@ -6,10 +6,10 @@ description: AI-driven documentation repair with structured research workflow, t
6
6
  compatibility: Requires DocGuard CLI installed (npm i -g docguard-cli or npx docguard-cli)
7
7
  metadata:
8
8
  author: docguard
9
- version: 0.18.1
9
+ version: 0.21.0
10
10
  source: extensions/spec-kit-docguard/skills/docguard-fix
11
11
  ---
12
- <!-- docguard:version: 0.18.1 -->
12
+ <!-- docguard:version: 0.21.0 -->
13
13
 
14
14
  # DocGuard Fix Skill
15
15
 
@@ -7,10 +7,10 @@ description: Run DocGuard guard validation against Canonical-Driven Development
7
7
  compatibility: Requires DocGuard CLI installed (npm i -g docguard-cli or npx docguard-cli)
8
8
  metadata:
9
9
  author: docguard
10
- version: 0.18.1
10
+ version: 0.21.0
11
11
  source: extensions/spec-kit-docguard/skills/docguard-guard
12
12
  ---
13
- <!-- docguard:version: 0.18.1 -->
13
+ <!-- docguard:version: 0.21.0 -->
14
14
 
15
15
  # DocGuard Guard Skill
16
16
 
@@ -6,10 +6,10 @@ description: Cross-document consistency analysis and quality assessment. Perform
6
6
  compatibility: Requires DocGuard CLI installed (npm i -g docguard-cli or npx docguard-cli)
7
7
  metadata:
8
8
  author: docguard
9
- version: 0.18.1
9
+ version: 0.21.0
10
10
  source: extensions/spec-kit-docguard/skills/docguard-review
11
11
  ---
12
- <!-- docguard:version: 0.18.1 -->
12
+ <!-- docguard:version: 0.21.0 -->
13
13
 
14
14
  # DocGuard Review Skill
15
15
 
@@ -6,10 +6,10 @@ description: CDD maturity assessment with category-aware improvement roadmap. Ru
6
6
  compatibility: Requires DocGuard CLI installed (npm i -g docguard-cli or npx docguard-cli)
7
7
  metadata:
8
8
  author: docguard
9
- version: 0.18.1
9
+ version: 0.21.0
10
10
  source: extensions/spec-kit-docguard/skills/docguard-score
11
11
  ---
12
- <!-- docguard:version: 0.18.1 -->
12
+ <!-- docguard:version: 0.21.0 -->
13
13
 
14
14
  # DocGuard Score Skill
15
15
 
@@ -4,7 +4,7 @@ description: Keep canonical documentation ALWAYS UP TO DATE. Refreshes code-trut
4
4
  compatibility: Requires DocGuard CLI installed (npm i -g docguard-cli or npx docguard-cli)
5
5
  metadata:
6
6
  author: docguard
7
- version: 0.18.1
7
+ version: 0.21.0
8
8
  source: extensions/spec-kit-docguard/skills/docguard-sync
9
9
  ---
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docguard-cli",
3
- "version": "0.18.1",
3
+ "version": "0.21.0",
4
4
  "description": "The enforcement tool for Canonical-Driven Development (CDD). Audit, generate, and guard your project documentation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,8 @@
1
+ {
2
+ "projectName": "acme-payments",
3
+ "version": "0.5",
4
+ "profile": "standard",
5
+ "sourcePatterns": {
6
+ "routes": "src/**/*.mjs"
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ # Required at boot — see docs-canonical/ENVIRONMENT.md for context.
2
+ DATABASE_URL=postgres://acme:devpass@localhost:5432/payments
3
+ STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxx
4
+ # Intentional drift: JWT_SECRET is used in code but NOT documented in ENVIRONMENT.md
5
+ JWT_SECRET=change-me-in-prod
@@ -0,0 +1,14 @@
1
+ # AGENTS.md — Acme Payments
2
+
3
+ > Rules for AI agents working in this repo.
4
+
5
+ ## Project context
6
+ Acme Payments is a payments microservice. Money is involved — assume every change needs a test.
7
+
8
+ ## Architecture
9
+ Three services: API, Worker, Scheduler. See `docs-canonical/ARCHITECTURE.md` for the canonical map.
10
+
11
+ ## Style
12
+ - ES modules (`.mjs`)
13
+ - Async/await throughout, no callbacks
14
+ - Throw `PaymentError` for domain errors