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.
- package/README.md +128 -34
- package/cli/commands/demo.mjs +241 -0
- package/cli/commands/guard.mjs +20 -2
- package/cli/commands/init.mjs +122 -0
- package/cli/docguard.mjs +125 -47
- package/cli/validators/canonical-sync.mjs +211 -0
- package/cli/validators/spec-kit.mjs +14 -0
- package/docs/quickstart.md +1 -1
- package/extensions/spec-kit-docguard/README.md +1 -1
- package/extensions/spec-kit-docguard/extension.yml +5 -5
- package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-sync/SKILL.md +1 -1
- package/package.json +1 -1
- package/templates/demo-fixture/.docguard.json +8 -0
- package/templates/demo-fixture/.env.example +5 -0
- package/templates/demo-fixture/AGENTS.md +14 -0
- package/templates/demo-fixture/CHANGELOG.md +13 -0
- package/templates/demo-fixture/DRIFT-LOG.md +3 -0
- package/templates/demo-fixture/README.md +17 -0
- package/templates/demo-fixture/docs-canonical/API-REFERENCE.md +36 -0
- package/templates/demo-fixture/docs-canonical/ARCHITECTURE.md +30 -0
- package/templates/demo-fixture/docs-canonical/DATA-MODEL.md +30 -0
- package/templates/demo-fixture/docs-canonical/ENVIRONMENT.md +20 -0
- package/templates/demo-fixture/docs-canonical/SECURITY.md +15 -0
- package/templates/demo-fixture/docs-canonical/TEST-SPEC.md +10 -0
- package/templates/demo-fixture/package.json +10 -0
- package/templates/demo-fixture/src/api.mjs +18 -0
- package/templates/demo-fixture/src/notifier.mjs +23 -0
- package/templates/demo-fixture/src/scheduler.mjs +8 -0
- 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}
|
|
286
|
-
${c.green}
|
|
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}
|
|
291
|
-
${c.green}
|
|
292
|
-
${c.green}
|
|
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}
|
|
295
|
-
${c.green}
|
|
296
|
-
${c.green}
|
|
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}
|
|
311
|
-
${c.green}
|
|
312
|
-
|
|
313
|
-
${c.
|
|
314
|
-
${c.
|
|
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
|
-
//
|
|
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
|
-
|
|
535
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
631
|
+
await runInit(projectDir, config, { ...flags, with: ['hooks'], skipPrompts: true });
|
|
555
632
|
break;
|
|
556
633
|
case 'badge':
|
|
557
|
-
|
|
558
|
-
runBadge(projectDir, config, flags);
|
|
634
|
+
await runInit(projectDir, config, { ...flags, with: ['badge'], skipPrompts: true });
|
|
559
635
|
break;
|
|
560
636
|
case 'ci':
|
|
561
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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';
|
package/docs/quickstart.md
CHANGED
|
@@ -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 (
|
|
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
|
-
- **
|
|
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.
|
|
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.
|
|
9
|
+
version: 0.21.0
|
|
10
10
|
source: extensions/spec-kit-docguard/skills/docguard-fix
|
|
11
11
|
---
|
|
12
|
-
<!-- docguard:version: 0.
|
|
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.
|
|
10
|
+
version: 0.21.0
|
|
11
11
|
source: extensions/spec-kit-docguard/skills/docguard-guard
|
|
12
12
|
---
|
|
13
|
-
<!-- docguard:version: 0.
|
|
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.
|
|
9
|
+
version: 0.21.0
|
|
10
10
|
source: extensions/spec-kit-docguard/skills/docguard-review
|
|
11
11
|
---
|
|
12
|
-
<!-- docguard:version: 0.
|
|
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.
|
|
9
|
+
version: 0.21.0
|
|
10
10
|
source: extensions/spec-kit-docguard/skills/docguard-score
|
|
11
11
|
---
|
|
12
|
-
<!-- docguard:version: 0.
|
|
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.
|
|
7
|
+
version: 0.21.0
|
|
8
8
|
source: extensions/spec-kit-docguard/skills/docguard-sync
|
|
9
9
|
---
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -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
|