docguard-cli 0.16.0 → 0.17.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/cli/commands/diff.mjs +4 -4
- package/cli/commands/guard.mjs +101 -0
- package/cli/commands/memory.mjs +143 -0
- package/cli/docguard.mjs +57 -1
- package/commands/docguard.guard.md +2 -2
- package/extensions/spec-kit-docguard/commands/guard.md +1 -1
- package/extensions/spec-kit-docguard/extension.yml +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +3 -3
- 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/extensions/spec-kit-docguard/templates/github-workflows/docguard-guard.yml +1 -1
- package/package.json +1 -1
- package/templates/commands/docguard.guard.md +2 -2
package/cli/commands/diff.mjs
CHANGED
|
@@ -101,7 +101,7 @@ export function runDiff(projectDir, config, flags) {
|
|
|
101
101
|
|
|
102
102
|
// ── Diff Functions ─────────────────────────────────────────────────────────
|
|
103
103
|
|
|
104
|
-
function diffRoutes(dir, config = {}) {
|
|
104
|
+
export function diffRoutes(dir, config = {}) {
|
|
105
105
|
// Documented surface: prefer the dedicated API reference, fall back to ARCHITECTURE.md.
|
|
106
106
|
const apiRefPath = resolve(dir, 'docs-canonical/API-REFERENCE.md');
|
|
107
107
|
const archPath = resolve(dir, 'docs-canonical/ARCHITECTURE.md');
|
|
@@ -133,7 +133,7 @@ const CODE_ENTITY_NOISE = new Set([
|
|
|
133
133
|
'models', 'model', 'utils', 'helpers', 'constants', 'config', 'common', 'base',
|
|
134
134
|
]);
|
|
135
135
|
|
|
136
|
-
function diffEntities(dir, config = {}) {
|
|
136
|
+
export function diffEntities(dir, config = {}) {
|
|
137
137
|
const dataModelPath = resolve(dir, 'docs-canonical/DATA-MODEL.md');
|
|
138
138
|
if (!existsSync(dataModelPath)) return null;
|
|
139
139
|
|
|
@@ -219,7 +219,7 @@ const SYSTEM_ENV_VARS = new Set([
|
|
|
219
219
|
'NODE_ENV', // could be app-set but more often platform-set; conservative skip
|
|
220
220
|
]);
|
|
221
221
|
|
|
222
|
-
function diffEnvVars(dir, config = {}) {
|
|
222
|
+
export function diffEnvVars(dir, config = {}) {
|
|
223
223
|
const envDocPath = resolve(dir, 'docs-canonical/ENVIRONMENT.md');
|
|
224
224
|
if (!existsSync(envDocPath)) return null;
|
|
225
225
|
|
|
@@ -263,7 +263,7 @@ function diffEnvVars(dir, config = {}) {
|
|
|
263
263
|
};
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
function diffTechStack(dir, config = {}) {
|
|
266
|
+
export function diffTechStack(dir, config = {}) {
|
|
267
267
|
const archPath = resolve(dir, 'docs-canonical/ARCHITECTURE.md');
|
|
268
268
|
if (!existsSync(archPath)) return null;
|
|
269
269
|
|
package/cli/commands/guard.mjs
CHANGED
|
@@ -11,6 +11,80 @@ import { c, resolveSeverity } from '../shared.mjs';
|
|
|
11
11
|
import { detectAgentMode, isSpecKitInitialized } from '../ensure-skills.mjs';
|
|
12
12
|
import { checkUpgradeStatus } from './upgrade.mjs';
|
|
13
13
|
import { changedFilesSince, isGitRepo } from '../shared-git.mjs';
|
|
14
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
15
|
+
import { resolve as resolvePath } from 'node:path';
|
|
16
|
+
import { fileURLToPath as fp } from 'node:url';
|
|
17
|
+
import { dirname as dn } from 'node:path';
|
|
18
|
+
|
|
19
|
+
// v0.17-P1: CLI version for version-pin checks (F8). Reproducibility for CDD —
|
|
20
|
+
// users can pin the docguard version their config was last validated against.
|
|
21
|
+
const _PKG = JSON.parse(readFileSync(resolvePath(dn(fp(import.meta.url)), '..', '..', 'package.json'), 'utf-8'));
|
|
22
|
+
const CLI_VERSION = _PKG.version;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* v0.17-P1: parse a semver-ish version string into a comparable tuple.
|
|
26
|
+
* Tolerates trailing pre-release tags (`0.16.0-rc.1`). Returns null on garbage.
|
|
27
|
+
*/
|
|
28
|
+
function _parseSemver(v) {
|
|
29
|
+
if (!v || typeof v !== 'string') return null;
|
|
30
|
+
const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
31
|
+
if (!m) return null;
|
|
32
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* v0.17-P1: returns +1 if a > b, 0 if equal, -1 if a < b. Unparseable
|
|
37
|
+
* input sorts as equal (silent — never blocks a guard run).
|
|
38
|
+
*/
|
|
39
|
+
function _semverCompare(a, b) {
|
|
40
|
+
const pa = _parseSemver(a);
|
|
41
|
+
const pb = _parseSemver(b);
|
|
42
|
+
if (!pa || !pb) return 0;
|
|
43
|
+
for (let i = 0; i < 3; i++) {
|
|
44
|
+
if (pa[i] > pb[i]) return 1;
|
|
45
|
+
if (pa[i] < pb[i]) return -1;
|
|
46
|
+
}
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* v0.17-P1: emit a "you're running a newer CLI than the config was pinned
|
|
52
|
+
* against" nudge. Cheap, file-local check. Returns the nudge text or null.
|
|
53
|
+
*/
|
|
54
|
+
function _checkVersionPin(config) {
|
|
55
|
+
const pinned = config.docguardVersion;
|
|
56
|
+
if (!pinned) return null;
|
|
57
|
+
const cmp = _semverCompare(CLI_VERSION, pinned);
|
|
58
|
+
if (cmp > 0) {
|
|
59
|
+
return `Running CLI v${CLI_VERSION} but config pins v${pinned}. ` +
|
|
60
|
+
`New validators/rules may have appeared. Run \`docguard guard --pin\` to update the pin once you've reviewed any new findings.`;
|
|
61
|
+
}
|
|
62
|
+
if (cmp < 0) {
|
|
63
|
+
return `Running CLI v${CLI_VERSION} but config pins v${pinned} (newer). ` +
|
|
64
|
+
`Older CLI may be missing checks the config expects. Upgrade with \`npm i -g docguard-cli@latest\`.`;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* v0.17-P1: update the docguardVersion field in .docguard.json after a
|
|
71
|
+
* successful guard run. Triggered by `docguard guard --pin`. Idempotent.
|
|
72
|
+
*/
|
|
73
|
+
function _updateVersionPin(projectDir) {
|
|
74
|
+
const cfgPath = resolvePath(projectDir, '.docguard.json');
|
|
75
|
+
if (!existsSync(cfgPath)) return { written: false, reason: '.docguard.json not found — run `docguard init` first' };
|
|
76
|
+
let raw, cfg;
|
|
77
|
+
try { raw = readFileSync(cfgPath, 'utf-8'); cfg = JSON.parse(raw); } catch (e) {
|
|
78
|
+
return { written: false, reason: `could not parse .docguard.json: ${e.message}` };
|
|
79
|
+
}
|
|
80
|
+
if (cfg.docguardVersion === CLI_VERSION) {
|
|
81
|
+
return { written: false, reason: `already pinned at v${CLI_VERSION}` };
|
|
82
|
+
}
|
|
83
|
+
const prev = cfg.docguardVersion || '(unset)';
|
|
84
|
+
cfg.docguardVersion = CLI_VERSION;
|
|
85
|
+
writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n', 'utf-8');
|
|
86
|
+
return { written: true, from: prev, to: CLI_VERSION };
|
|
87
|
+
}
|
|
14
88
|
import { validateStructure, validateDocSections } from '../validators/structure.mjs';
|
|
15
89
|
import { validateDrift } from '../validators/drift.mjs';
|
|
16
90
|
import { validateChangelog } from '../validators/changelog.mjs';
|
|
@@ -364,6 +438,15 @@ export function runGuard(projectDir, config, flags) {
|
|
|
364
438
|
console.log(`\n ${c.yellow}↑ ${upgradeHint}${c.reset}`);
|
|
365
439
|
}
|
|
366
440
|
|
|
441
|
+
// v0.17-P1: version-pin nudge. When .docguard.json carries a
|
|
442
|
+
// docguardVersion field and the running CLI doesn't match, emit a
|
|
443
|
+
// one-line note. Keeps CDD reproducibility honest — "same project,
|
|
444
|
+
// same docs, different score across versions" no longer silent.
|
|
445
|
+
const pinHint = _checkVersionPin(config);
|
|
446
|
+
if (pinHint) {
|
|
447
|
+
console.log(`\n ${c.yellow}📌 ${pinHint}${c.reset}`);
|
|
448
|
+
}
|
|
449
|
+
|
|
367
450
|
// K-6 / S-2: sweep-needed nudge. Aggregates freshness warnings — if 2+
|
|
368
451
|
// canonical docs are stale (matching the "X code commits since last doc
|
|
369
452
|
// update" pattern), suggest a single `docguard sync --write` pass that
|
|
@@ -402,6 +485,24 @@ export function runGuard(projectDir, config, flags) {
|
|
|
402
485
|
|
|
403
486
|
console.log('');
|
|
404
487
|
|
|
488
|
+
// v0.17-P1: --pin updates docguardVersion in .docguard.json to the running
|
|
489
|
+
// CLI version. Only meaningful AFTER a clean (or near-clean) guard run —
|
|
490
|
+
// pinning to a version that just failed defeats the reproducibility goal.
|
|
491
|
+
// We allow pinning when status is PASS or WARN; refuse on FAIL.
|
|
492
|
+
if (flags.pin) {
|
|
493
|
+
if (data.status === 'FAIL') {
|
|
494
|
+
console.log(` ${c.red}✗ Cannot --pin after a FAIL run.${c.reset} Fix the errors first, then retry.`);
|
|
495
|
+
} else {
|
|
496
|
+
const r = _updateVersionPin(projectDir);
|
|
497
|
+
if (r.written) {
|
|
498
|
+
console.log(` ${c.green}📌 docguardVersion pinned: ${r.from} → ${r.to}${c.reset}`);
|
|
499
|
+
} else {
|
|
500
|
+
console.log(` ${c.dim}📌 ${r.reason}${c.reset}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
console.log('');
|
|
504
|
+
}
|
|
505
|
+
|
|
405
506
|
// v0.5: severity-aware exit codes (see runGuardInternal for the rollup).
|
|
406
507
|
if (data.effectiveErrors > 0) process.exit(1);
|
|
407
508
|
if (data.effectiveWarnings > 0) process.exit(2);
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Command — v0.17-P2.
|
|
3
|
+
*
|
|
4
|
+
* `docguard memory` shows the documentation-memory accuracy headline that
|
|
5
|
+
* already appears in `docguard score`, but adds a `--diff` mode that drills
|
|
6
|
+
* into WHICH claims don't match code. Reported by a Python user:
|
|
7
|
+
*
|
|
8
|
+
* "Memory accuracy 83% with no drill-down. The headline number was the
|
|
9
|
+
* only signal — there's no `docguard memory --diff` to show which doc
|
|
10
|
+
* claim doesn't match the code."
|
|
11
|
+
*
|
|
12
|
+
* The numbers are the same ones `score` shows; this command's value is
|
|
13
|
+
* making them inspectable per-domain.
|
|
14
|
+
*
|
|
15
|
+
* Domains drilled into:
|
|
16
|
+
* - Endpoints: API-REFERENCE.md vs scanned routes
|
|
17
|
+
* - Entities: DATA-MODEL.md vs scanned schemas
|
|
18
|
+
* - Env vars: ENVIRONMENT.md vs process.env / import.meta.env usage
|
|
19
|
+
* - Tech: ARCHITECTURE.md vs detected stack
|
|
20
|
+
*
|
|
21
|
+
* Zero NPM dependencies. Pure orchestration of existing diff helpers.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { c } from '../shared.mjs';
|
|
25
|
+
import { diffRoutes, diffEntities, diffEnvVars, diffTechStack } from './diff.mjs';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compute an accuracy score for a single domain. Returns:
|
|
29
|
+
* { matched, total, accuracy: 0..100, onlyInDocs, onlyInCode }
|
|
30
|
+
* `null` when the domain isn't applicable (e.g. no API-REFERENCE.md).
|
|
31
|
+
*/
|
|
32
|
+
function _domainAccuracy(d) {
|
|
33
|
+
if (!d) return null;
|
|
34
|
+
const matched = (d.matched || []).length;
|
|
35
|
+
const onlyDocs = (d.onlyInDocs || []).length;
|
|
36
|
+
const onlyCode = (d.onlyInCode || []).length;
|
|
37
|
+
const total = matched + onlyDocs + onlyCode;
|
|
38
|
+
if (total === 0) return null;
|
|
39
|
+
return {
|
|
40
|
+
title: d.title,
|
|
41
|
+
icon: d.icon,
|
|
42
|
+
matched,
|
|
43
|
+
onlyInDocs: d.onlyInDocs || [],
|
|
44
|
+
onlyInCode: d.onlyInCode || [],
|
|
45
|
+
total,
|
|
46
|
+
accuracy: Math.round((matched / total) * 100),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function runMemory(projectDir, config, flags) {
|
|
51
|
+
const isJson = flags.format === 'json';
|
|
52
|
+
const wantsDiff = flags.diff || (flags.args || []).includes('--diff');
|
|
53
|
+
|
|
54
|
+
const domains = {
|
|
55
|
+
endpoints: _domainAccuracy(diffRoutes(projectDir, config)),
|
|
56
|
+
entities: _domainAccuracy(diffEntities(projectDir, config)),
|
|
57
|
+
envVars: _domainAccuracy(diffEnvVars(projectDir, config)),
|
|
58
|
+
techStack: _domainAccuracy(diffTechStack(projectDir, config)),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Roll up across applicable domains.
|
|
62
|
+
let totalMatched = 0;
|
|
63
|
+
let totalChecks = 0;
|
|
64
|
+
for (const d of Object.values(domains)) {
|
|
65
|
+
if (!d) continue;
|
|
66
|
+
totalMatched += d.matched;
|
|
67
|
+
totalChecks += d.total;
|
|
68
|
+
}
|
|
69
|
+
const overallAccuracy = totalChecks > 0
|
|
70
|
+
? Math.round((totalMatched / totalChecks) * 100)
|
|
71
|
+
: 0;
|
|
72
|
+
|
|
73
|
+
if (isJson) {
|
|
74
|
+
console.log(JSON.stringify({
|
|
75
|
+
project: config.projectName,
|
|
76
|
+
accuracy: overallAccuracy,
|
|
77
|
+
domains,
|
|
78
|
+
totals: { matched: totalMatched, checks: totalChecks },
|
|
79
|
+
timestamp: new Date().toISOString(),
|
|
80
|
+
}, null, 2));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Text output ──
|
|
85
|
+
console.log(`${c.bold}🧠 DocGuard Memory${c.reset} ${c.dim}— ${config.projectName}${c.reset}\n`);
|
|
86
|
+
|
|
87
|
+
const accColor = overallAccuracy >= 90 ? c.green : overallAccuracy >= 70 ? c.yellow : c.red;
|
|
88
|
+
console.log(` ${c.bold}Accuracy:${c.reset} ${accColor}${overallAccuracy}%${c.reset} ${c.dim}(${totalMatched}/${totalChecks} doc claims match code)${c.reset}\n`);
|
|
89
|
+
|
|
90
|
+
if (totalChecks === 0) {
|
|
91
|
+
console.log(` ${c.dim}No applicable domains found — add canonical docs (API-REFERENCE.md, DATA-MODEL.md, ENVIRONMENT.md) and rerun.${c.reset}`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Per-domain breakdown
|
|
96
|
+
console.log(` ${c.bold}By domain:${c.reset}`);
|
|
97
|
+
for (const [name, d] of Object.entries(domains)) {
|
|
98
|
+
if (!d) continue;
|
|
99
|
+
const domainColor = d.accuracy >= 90 ? c.green : d.accuracy >= 70 ? c.yellow : c.red;
|
|
100
|
+
console.log(` ${d.icon} ${c.cyan}${d.title.padEnd(22)}${c.reset} ${domainColor}${String(d.accuracy).padStart(3)}%${c.reset} ${c.dim}${d.matched}/${d.total} matched${c.reset}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!wantsDiff) {
|
|
104
|
+
if (overallAccuracy < 100) {
|
|
105
|
+
console.log(`\n ${c.dim}Run ${c.cyan}docguard memory --diff${c.dim} to see WHICH claims don't match.${c.reset}`);
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// --diff mode: detail per domain
|
|
111
|
+
console.log(`\n ${c.bold}── Drill-down ──${c.reset}`);
|
|
112
|
+
let anyShown = false;
|
|
113
|
+
for (const [_, d] of Object.entries(domains)) {
|
|
114
|
+
if (!d) continue;
|
|
115
|
+
if (d.onlyInDocs.length === 0 && d.onlyInCode.length === 0) continue;
|
|
116
|
+
anyShown = true;
|
|
117
|
+
console.log(`\n ${d.icon} ${c.bold}${d.title}${c.reset} ${c.dim}(${d.accuracy}%)${c.reset}`);
|
|
118
|
+
|
|
119
|
+
if (d.onlyInDocs.length > 0) {
|
|
120
|
+
console.log(` ${c.red}✗ In docs but missing from code${c.reset} ${c.dim}(${d.onlyInDocs.length}):${c.reset}`);
|
|
121
|
+
for (const item of d.onlyInDocs.slice(0, 10)) {
|
|
122
|
+
console.log(` ${c.red}-${c.reset} ${item}`);
|
|
123
|
+
}
|
|
124
|
+
if (d.onlyInDocs.length > 10) console.log(` ${c.dim}... ${d.onlyInDocs.length - 10} more${c.reset}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (d.onlyInCode.length > 0) {
|
|
128
|
+
console.log(` ${c.yellow}⚠ In code but missing from docs${c.reset} ${c.dim}(${d.onlyInCode.length}):${c.reset}`);
|
|
129
|
+
for (const item of d.onlyInCode.slice(0, 10)) {
|
|
130
|
+
console.log(` ${c.yellow}+${c.reset} ${item}`);
|
|
131
|
+
}
|
|
132
|
+
if (d.onlyInCode.length > 10) console.log(` ${c.dim}... ${d.onlyInCode.length - 10} more${c.reset}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!anyShown) {
|
|
137
|
+
console.log(`\n ${c.green}✅ All claims match — nothing to drill into.${c.reset}`);
|
|
138
|
+
} else {
|
|
139
|
+
console.log(`\n ${c.dim}Fix options:${c.reset}`);
|
|
140
|
+
console.log(` ${c.dim}• Removed-from-code items: ${c.cyan}docguard fix --write${c.dim} (deletes documented-but-absent endpoints)${c.reset}`);
|
|
141
|
+
console.log(` ${c.dim}• Missing-from-docs items: ${c.cyan}/docguard.fix --doc <name>${c.dim} (AI fills in the gap)${c.reset}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
package/cli/docguard.mjs
CHANGED
|
@@ -43,6 +43,7 @@ import { runSetup } from './commands/setup.mjs';
|
|
|
43
43
|
import { runUpgrade } from './commands/upgrade.mjs';
|
|
44
44
|
import { runImpact } from './commands/impact.mjs';
|
|
45
45
|
import { runExplain } from './commands/explain.mjs';
|
|
46
|
+
import { runMemory } from './commands/memory.mjs';
|
|
46
47
|
import { ensureSkills } from './ensure-skills.mjs';
|
|
47
48
|
|
|
48
49
|
// ── Shared constants (imported to break circular dependencies) ──────────
|
|
@@ -121,7 +122,10 @@ export function loadConfig(projectDir) {
|
|
|
121
122
|
? deepMerge(defaults, profilePreset)
|
|
122
123
|
: defaults;
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
// v0.17-P4: normalize validator/severity keys before merging so the
|
|
126
|
+
// user can write either kebab-case (`test-spec`) or camelCase (`testSpec`)
|
|
127
|
+
// and the internal lookups (always camelCase) still hit.
|
|
128
|
+
const merged = deepMerge(withProfile, normalizeConfig(userConfig));
|
|
125
129
|
merged.profile = profileName;
|
|
126
130
|
|
|
127
131
|
// Auto-detect project type if not set
|
|
@@ -210,6 +214,46 @@ function getProjectTypeDefaults(type) {
|
|
|
210
214
|
return defaults[type] || defaults.unknown;
|
|
211
215
|
}
|
|
212
216
|
|
|
217
|
+
/**
|
|
218
|
+
* v0.17-P4: normalize validator-key naming so users can write either
|
|
219
|
+
* `validators: { "test-spec": true }` (kebab-case, matches CLI display)
|
|
220
|
+
* or `validators: { testSpec: true }` (camelCase, matches JSON internals)
|
|
221
|
+
* in `.docguard.json`. We normalize the WHOLE config tree's known validator
|
|
222
|
+
* keys to camelCase before merging. Same treatment applied to `severity`.
|
|
223
|
+
*
|
|
224
|
+
* Non-validator keys are left alone. Unknown keys (forward-compat) are
|
|
225
|
+
* normalized blindly: kebab-case→camelCase always.
|
|
226
|
+
*/
|
|
227
|
+
const _KNOWN_VALIDATORS = [
|
|
228
|
+
'structure', 'docsSync', 'drift', 'changelog', 'testSpec', 'environment',
|
|
229
|
+
'security', 'architecture', 'freshness', 'traceability', 'docsDiff',
|
|
230
|
+
'apiSurface', 'metadataSync', 'docsCoverage', 'docQuality', 'todoTracking',
|
|
231
|
+
'schemaSync', 'specKit', 'crossReference', 'generatedStaleness',
|
|
232
|
+
'metricsConsistency',
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
function _kebabToCamel(k) {
|
|
236
|
+
return k.replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function _normalizeValidatorKeys(map) {
|
|
240
|
+
if (!map || typeof map !== 'object' || Array.isArray(map)) return map;
|
|
241
|
+
const out = {};
|
|
242
|
+
for (const [k, v] of Object.entries(map)) {
|
|
243
|
+
const normalized = k.includes('-') ? _kebabToCamel(k) : k;
|
|
244
|
+
out[normalized] = v;
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function normalizeConfig(cfg) {
|
|
250
|
+
if (!cfg || typeof cfg !== 'object') return cfg;
|
|
251
|
+
const out = { ...cfg };
|
|
252
|
+
if (out.validators) out.validators = _normalizeValidatorKeys(out.validators);
|
|
253
|
+
if (out.severity) out.severity = _normalizeValidatorKeys(out.severity);
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
|
|
213
257
|
function deepMerge(target, source) {
|
|
214
258
|
const result = { ...target };
|
|
215
259
|
for (const key of Object.keys(source)) {
|
|
@@ -406,6 +450,15 @@ async function main() {
|
|
|
406
450
|
// Default stays on (discoverability), but lets minimalist library
|
|
407
451
|
// projects skip the .specify/.agent/commands scaffolding.
|
|
408
452
|
flags.noSpecKit = true;
|
|
453
|
+
} else if (args[i] === '--pin') {
|
|
454
|
+
// v0.17-P1: `docguard guard --pin` records the running CLI version
|
|
455
|
+
// into .docguard.json (`docguardVersion` field) after a successful run.
|
|
456
|
+
// Different from `--pr` (used by upgrade) — this is for guard.
|
|
457
|
+
flags.pin = true;
|
|
458
|
+
} else if (args[i] === '--diff') {
|
|
459
|
+
// v0.17-P2: `docguard memory --diff` drills into accuracy mismatches.
|
|
460
|
+
// Distinct from the `diff` command itself (which is a top-level cmd).
|
|
461
|
+
flags.diff = true;
|
|
409
462
|
} else if (!args[i].startsWith('--') && i > 0) {
|
|
410
463
|
// Positional args go into flags.args for commands that take them (e.g.
|
|
411
464
|
// `docguard trace --reverse <path>`). Skip the command itself (i === 0).
|
|
@@ -544,6 +597,9 @@ async function main() {
|
|
|
544
597
|
case 'help-warning':
|
|
545
598
|
runExplain(projectDir, config, flags);
|
|
546
599
|
break;
|
|
600
|
+
case 'memory':
|
|
601
|
+
runMemory(projectDir, config, flags);
|
|
602
|
+
break;
|
|
547
603
|
default:
|
|
548
604
|
console.error(`${c.red}Unknown command: ${command}${c.reset}`);
|
|
549
605
|
console.log(`Run ${c.cyan}docguard --help${c.reset} for usage.`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Run DocGuard guard validation — check project documentation against CDD standards with
|
|
2
|
+
description: Run DocGuard guard validation — check project documentation against CDD standards with all validators
|
|
3
3
|
handoffs:
|
|
4
4
|
- label: Fix All Issues
|
|
5
5
|
agent: docguard.fix
|
|
@@ -23,7 +23,7 @@ Run the DocGuard CLI to validate all documentation against Canonical-Driven Deve
|
|
|
23
23
|
npx docguard-cli guard
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
2. **Parse the output**. Each of the
|
|
26
|
+
2. **Parse the output**. Each of the validators reports ✅ (pass), ⚠️ (warning), ❌ (fail), or ➖ (N/A — nothing to validate). **A ➖ N/A is NOT a pass**: it means the validator found nothing to check (e.g. no API-REFERENCE.md, no DB schema, no layer boundaries declared). Don't read N/A as "healthy" — read it as "not assessed".
|
|
27
27
|
|
|
28
28
|
| Validator | What It Checks |
|
|
29
29
|
|-----------|---------------|
|
|
@@ -14,7 +14,7 @@ handoffs:
|
|
|
14
14
|
|
|
15
15
|
# DocGuard Guard
|
|
16
16
|
|
|
17
|
-
Validate your project against its canonical documentation. Runs 160+ automated checks across
|
|
17
|
+
Validate your project against its canonical documentation. Runs 160+ automated checks across validators.
|
|
18
18
|
|
|
19
19
|
## User Input
|
|
20
20
|
|
|
@@ -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.17.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"
|
|
@@ -58,7 +58,7 @@ provides:
|
|
|
58
58
|
workflows:
|
|
59
59
|
- name: "docguard-guard"
|
|
60
60
|
file: "templates/github-workflows/docguard-guard.yml"
|
|
61
|
-
description: "Mandatory CI gate — runs all
|
|
61
|
+
description: "Mandatory CI gate — runs all validators on PR + main push"
|
|
62
62
|
- name: "docguard-autofix"
|
|
63
63
|
file: "templates/github-workflows/docguard-autofix.yml"
|
|
64
64
|
description: "PR-time auto-fix — applies mechanical doc fixes + comments summary"
|
|
@@ -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.17.0
|
|
10
10
|
source: extensions/spec-kit-docguard/skills/docguard-fix
|
|
11
11
|
---
|
|
12
|
-
<!-- docguard:version: 0.
|
|
12
|
+
<!-- docguard:version: 0.17.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.17.0
|
|
11
11
|
source: extensions/spec-kit-docguard/skills/docguard-guard
|
|
12
12
|
---
|
|
13
|
-
<!-- docguard:version: 0.
|
|
13
|
+
<!-- docguard:version: 0.17.0 -->
|
|
14
14
|
|
|
15
15
|
# DocGuard Guard Skill
|
|
16
16
|
|
|
@@ -139,7 +139,7 @@ For each finding, provide a **specific, actionable fix** — not "fix the issue"
|
|
|
139
139
|
|
|
140
140
|
Based on the triage results:
|
|
141
141
|
|
|
142
|
-
- **If all PASS**: "All
|
|
142
|
+
- **If all PASS**: "All validators passed. Project is CDD-compliant. Ready to commit."
|
|
143
143
|
- **If only MEDIUM/LOW warnings**: "Non-blocking warnings found. Safe to commit, but consider running `/docguard.fix` for automated remediation."
|
|
144
144
|
- **If HIGH or CRITICAL failures**: "Blocking issues found. Fix these before committing. Suggest running `/docguard.fix --doc [most impactful doc]` next."
|
|
145
145
|
|
|
@@ -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.17.0
|
|
10
10
|
source: extensions/spec-kit-docguard/skills/docguard-review
|
|
11
11
|
---
|
|
12
|
-
<!-- docguard:version: 0.
|
|
12
|
+
<!-- docguard:version: 0.17.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.17.0
|
|
10
10
|
source: extensions/spec-kit-docguard/skills/docguard-score
|
|
11
11
|
---
|
|
12
|
-
<!-- docguard:version: 0.
|
|
12
|
+
<!-- docguard:version: 0.17.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.17.0
|
|
8
8
|
source: extensions/spec-kit-docguard/skills/docguard-sync
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# DocGuard Guard — runs all
|
|
1
|
+
# DocGuard Guard — runs all validators on every PR and main push.
|
|
2
2
|
#
|
|
3
3
|
# This is the canonical CI gate. It does NOT modify your repo — it only
|
|
4
4
|
# reports. Pair with `docguard-autofix.yml` if you want mechanical fixes
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Run DocGuard guard validation — check all
|
|
2
|
+
description: Run DocGuard guard validation — check all validators and fix any issues
|
|
3
3
|
handoffs:
|
|
4
4
|
- label: Fix Issues
|
|
5
5
|
agent: docguard.fix
|
|
@@ -19,7 +19,7 @@ You are an AI agent enforcing Canonical-Driven Development (CDD) compliance usin
|
|
|
19
19
|
npx docguard-cli guard
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Read the output. It shows pass (✅), warn (⚠️), or fail (❌) for each of the
|
|
22
|
+
Read the output. It shows pass (✅), warn (⚠️), or fail (❌) for each of the validators:
|
|
23
23
|
|
|
24
24
|
| Priority | Validators |
|
|
25
25
|
|----------|-----------|
|