docguard-cli 0.40.5 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +3246 -0
  2. package/README.md +25 -16
  3. package/cli/assessment.mjs +94 -0
  4. package/cli/commands/ci.mjs +15 -5
  5. package/cli/commands/diagnose.mjs +20 -13
  6. package/cli/commands/fix.mjs +14 -45
  7. package/cli/commands/guard.mjs +53 -25
  8. package/cli/commands/hooks.mjs +51 -10
  9. package/cli/commands/init.mjs +15 -0
  10. package/cli/commands/reconcile.mjs +10 -3
  11. package/cli/commands/report.mjs +5 -1
  12. package/cli/commands/score.mjs +2 -1
  13. package/cli/commands/specs.mjs +15 -4
  14. package/cli/commands/upgrade.mjs +4 -1
  15. package/cli/commands/verify.mjs +9 -2
  16. package/cli/commands/watch.mjs +3 -2
  17. package/cli/config.mjs +23 -0
  18. package/cli/evidence/adapters.mjs +14 -0
  19. package/cli/evidence/manifest.mjs +15 -0
  20. package/cli/evidence/python-literal.mjs +304 -0
  21. package/cli/findings.mjs +17 -3
  22. package/cli/scanners/instruction-audit.mjs +88 -11
  23. package/cli/scanners/js-ast.mjs +156 -18
  24. package/cli/scanners/reconciliation.mjs +56 -6
  25. package/cli/scanners/routes.mjs +84 -9
  26. package/cli/scanners/spec-registry.mjs +29 -0
  27. package/cli/shared-git.mjs +98 -0
  28. package/cli/shared-ignore.mjs +1 -1
  29. package/cli/shared.mjs +30 -1
  30. package/cli/validators/api-doc-smells.mjs +2 -2
  31. package/cli/validators/api-surface.mjs +4 -9
  32. package/cli/validators/diff-suspicion.mjs +3 -2
  33. package/cli/validators/docs-sync.mjs +45 -29
  34. package/cli/validators/environment.mjs +64 -6
  35. package/cli/validators/metrics-consistency.mjs +52 -11
  36. package/cli/validators/reference-existence.mjs +4 -2
  37. package/cli/validators/security.mjs +37 -12
  38. package/cli/validators/spec-registry.mjs +10 -7
  39. package/cli/validators/todo-tracking.mjs +31 -11
  40. package/cli/validators/traceability.mjs +29 -4
  41. package/cli/writers/junit.mjs +3 -3
  42. package/cli/writers/sarif.mjs +13 -9
  43. package/docs/configuration.md +12 -1
  44. package/extensions/spec-kit-docguard/extension.yml +1 -1
  45. package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +2 -2
  46. package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +2 -2
  47. package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +2 -2
  48. package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +2 -2
  49. package/extensions/spec-kit-docguard/skills/docguard-sync/SKILL.md +2 -2
  50. package/extensions/spec-kit-docguard/templates/github-workflows/docguard-autofix.yml +1 -1
  51. package/extensions/spec-kit-docguard/templates/github-workflows/docguard-guard.yml +1 -1
  52. package/package.json +2 -1
  53. package/schemas/docguard-config.schema.json +15 -1
  54. package/schemas/docguard-evidence.schema.json +12 -0
  55. package/templates/ci/github-actions.yml +1 -1
  56. package/templates/evidence-manifest.json +16 -0
@@ -18,6 +18,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
18
18
  import { resolve, join, relative, basename, extname } from 'node:path';
19
19
  import { TRACE_MAP, isTraceableSource } from '../shared-trace-patterns.mjs';
20
20
  import { walkFiles as sharedWalkFiles, listCanonicalDocs } from '../shared-ignore.mjs';
21
+ /** @implements docguard.adoption-workflow-integrity#FR-006 */
21
22
  import { mkFinding, resultFromFindings } from '../findings.mjs';
22
23
  import { tokenize } from '../shared-diff.mjs';
23
24
  import { rankBySimilarity } from '../shared-ir.mjs';
@@ -32,7 +33,7 @@ import {
32
33
  requirementPatterns,
33
34
  } from '../shared-requirements.mjs';
34
35
  import { readRetirementManifest } from '../scanners/retirement-manifest.mjs';
35
- import { parseSpecId } from '../scanners/spec-registry.mjs';
36
+ import { parseSpecId, trustedSpecLifecycleIndex } from '../scanners/spec-registry.mjs';
36
37
 
37
38
  /**
38
39
  * Optional graphify interop (github.com/Graphify-Labs/graphify, MIT).
@@ -250,7 +251,9 @@ export function validateTraceability(projectDir, config) {
250
251
  passed += reqResult.passed;
251
252
  total += reqResult.total;
252
253
 
253
- return resultFromFindings(findings, { passed, total });
254
+ const result = resultFromFindings(findings, { passed, total });
255
+ result.requirementCoverage = reqResult.requirementCoverage;
256
+ return result;
254
257
  }
255
258
 
256
259
  // ──── Requirement ID Traceability ────────────────────────────────────────────
@@ -274,6 +277,7 @@ function validateRequirementTraceability(projectDir, config, projectFiles) {
274
277
  // ── Step 1: Collect requirement IDs from documentation ──
275
278
  const reqIds = collectRequirementIds(projectDir, config, patterns);
276
279
  const retiredReqIds = loadRetiredRequirementIds(projectDir);
280
+ const lifecycleIndex = trustedSpecLifecycleIndex(projectDir);
277
281
 
278
282
  // ── Step 2: Scan test files for requirement ID references ──
279
283
  const testRefs = scanTestFilesForReferences(projectDir, projectFiles, patterns);
@@ -295,9 +299,18 @@ function validateRequirementTraceability(projectDir, config, projectFiles) {
295
299
  const softThreshold = config.traceability?.irSoftThreshold ?? 0.10;
296
300
  let testCorpus = null;
297
301
 
298
- // Check each documented requirement has at least one test reference
302
+ let deferred = 0;
303
+ let lifecycleUnknown = 0;
304
+ // Planned specs are intent awaiting implementation. Only committed,
305
+ // digest-current reviewed lifecycle can defer their test linkage.
299
306
  for (const [key, location] of reqIds) {
300
307
  const reqId = location.id;
308
+ const lifecycle = location.specId ? lifecycleIndex.get(`${location.specId}\0${location.file}`) : null;
309
+ if (lifecycle?.delivery === 'planned') {
310
+ deferred++;
311
+ continue;
312
+ }
313
+ if (location.specId && !lifecycle) lifecycleUnknown++;
301
314
  total++;
302
315
  if (resolvedRefs.has(key)) {
303
316
  passed++;
@@ -326,6 +339,8 @@ function validateRequirementTraceability(projectDir, config, projectFiles) {
326
339
  }));
327
340
  }
328
341
  }
342
+ const applicableRequirements = total;
343
+ const tracedRequirements = passed;
329
344
 
330
345
  // Check for orphaned test refs (tests referencing non-existent requirements)
331
346
  for (const [reqId, refs] of testRefs) {
@@ -346,7 +361,17 @@ function validateRequirementTraceability(projectDir, config, projectFiles) {
346
361
  }
347
362
  }
348
363
 
349
- return { findings, passed, total };
364
+ return {
365
+ findings, passed, total,
366
+ requirementCoverage: {
367
+ discovered: reqIds.size,
368
+ applicable: applicableRequirements,
369
+ traced: tracedRequirements,
370
+ missing: findings.filter(finding => finding.code === 'TRC004').length,
371
+ deferred,
372
+ lifecycleUnknown,
373
+ },
374
+ };
350
375
  }
351
376
 
352
377
  /**
@@ -48,8 +48,8 @@ export function toJUnit(data) {
48
48
  const vFindings = Array.isArray(v.findings)
49
49
  ? v.findings
50
50
  : (data.findings || []).filter(f => f.validator === v.key || f.validator === v.name);
51
- const errors = vFindings.filter(f => f.severity === 'error');
52
- const warns = vFindings.filter(f => f.severity !== 'error');
51
+ const errors = vFindings.filter(f => (f.effectiveSeverity || f.severity) === 'error');
52
+ const warns = vFindings.filter(f => (f.effectiveSeverity || f.severity) !== 'error');
53
53
  const attrs = `name="${esc(v.name)}" classname="docguard.guard"`;
54
54
 
55
55
  if (v.status === 'skipped' || v.status === 'na') {
@@ -63,7 +63,7 @@ export function toJUnit(data) {
63
63
  ` <failure message="${esc(errors[0].message)}" type="${esc(errors[0].code || 'docguard')}">${esc(body)}</failure>\n` +
64
64
  ` </testcase>`
65
65
  );
66
- } else if (v.status === 'fail') {
66
+ } else if (v.status === 'fail' && vFindings.length === 0) {
67
67
  // A validator that failed WITHOUT structured error findings — the
68
68
  // crash path (guard catches the throw and records string errors only).
69
69
  // This must go red in CI, not render as a passing testcase (M1).
@@ -25,9 +25,9 @@ function pkgInfo() {
25
25
  }
26
26
  }
27
27
 
28
- /** severity 'error' → SARIF 'error'; everything else (warn) → 'warning'. */
28
+ /** Effective severity → SARIF level. */
29
29
  function toLevel(severity) {
30
- return severity === 'error' ? 'error' : 'warning';
30
+ return severity === 'error' ? 'error' : severity === 'info' ? 'note' : 'warning';
31
31
  }
32
32
 
33
33
  /**
@@ -62,10 +62,11 @@ export function toSarif(guardData, opts = {}) {
62
62
  if (v.status === 'skipped' || v.status === 'na') continue;
63
63
  if (Array.isArray(v.findings) && v.findings.length > 0) continue;
64
64
  for (const msg of v.errors || []) {
65
- synthetic.push({ code: `DOCGUARD-${String(v.key || v.name || 'unknown').toUpperCase()}`, severity: 'error', message: msg, location: null, suggestion: null });
65
+ synthetic.push({ code: `DOCGUARD-${String(v.key || v.name || 'unknown').toUpperCase()}`, severity: 'error', effectiveSeverity: 'error', message: msg, location: null, suggestion: null });
66
66
  }
67
67
  for (const msg of v.warnings || []) {
68
- synthetic.push({ code: `DOCGUARD-${String(v.key || v.name || 'unknown').toUpperCase()}`, severity: 'warn', message: msg, location: null, suggestion: null });
68
+ const effectiveSeverity = v.severity === 'high' ? 'error' : v.severity === 'low' ? 'info' : 'warn';
69
+ synthetic.push({ code: `DOCGUARD-${String(v.key || v.name || 'unknown').toUpperCase()}`, severity: 'warn', effectiveSeverity, message: msg, location: null, suggestion: null });
69
70
  }
70
71
  }
71
72
  const all = [...findings, ...synthetic];
@@ -83,7 +84,7 @@ export function toSarif(guardData, opts = {}) {
83
84
  rule.fullDescription = { text: meta.help };
84
85
  }
85
86
  rule.helpUri = HELP_URI;
86
- rule.defaultConfiguration = { level: toLevel(f.severity) };
87
+ rule.defaultConfiguration = { level: toLevel(f.effectiveSeverity || f.severity) };
87
88
  ruleIndexByCode.set(f.code, rules.length);
88
89
  rules.push(rule);
89
90
  }
@@ -92,7 +93,7 @@ export function toSarif(guardData, opts = {}) {
92
93
  const result = {
93
94
  ruleId: f.code,
94
95
  ruleIndex: ruleIndexByCode.get(f.code),
95
- level: toLevel(f.severity),
96
+ level: toLevel(f.effectiveSeverity || f.severity),
96
97
  message: { text: f.suggestion && f.suggestion.text ? `${f.message}\n→ ${f.suggestion.text}` : f.message },
97
98
  };
98
99
  const loc = parseLocation(f.location);
@@ -101,9 +102,12 @@ export function toSarif(guardData, opts = {}) {
101
102
  if (loc.line) physicalLocation.region = { startLine: loc.line };
102
103
  result.locations = [{ physicalLocation }];
103
104
  }
104
- if (f.confidence === 'low') {
105
- result.properties = { confidence: 'low', reportable: !!f.reportable };
106
- }
105
+ result.properties = {
106
+ originalSeverity: f.severity,
107
+ effectiveSeverity: f.effectiveSeverity || f.severity,
108
+ ...(f.enforcement ? { enforcement: f.enforcement } : {}),
109
+ ...(f.confidence === 'low' ? { confidence: 'low', reportable: !!f.reportable } : {}),
110
+ };
107
111
  return result;
108
112
  });
109
113
 
@@ -7,7 +7,7 @@ DocGuard is configured via `.docguard.json` in the project root. If no config fi
7
7
  ```json
8
8
  {
9
9
  "projectName": "my-project",
10
- "version": "0.5",
10
+ "version": "0.6",
11
11
  "profile": "standard",
12
12
  "projectType": "webapp",
13
13
 
@@ -57,6 +57,10 @@ DocGuard is configured via `.docguard.json` in the project root. If no config fi
57
57
 
58
58
  "docs": {
59
59
  "dirs": ["reference", "website/docs"]
60
+ },
61
+ "findingSeverity": {
62
+ "TRC004": "low",
63
+ "SEC001": "high"
60
64
  }
61
65
  }
62
66
  ```
@@ -94,6 +98,13 @@ anything from display: `"high"` promotes its warnings to blocking (CI fails),
94
98
  `"low"` demotes them (shown, but never fail the build). Valid values:
95
99
  `high | medium | low`. To silence a validator entirely, use `validators.<key>: false`.
96
100
 
101
+ `findingSeverity.<CODE>` applies the same enforcement levels to one stable
102
+ finding code and takes precedence over the validator setting. This is the
103
+ preferred control for a noisy rule because neighboring findings retain their
104
+ policy. Intrinsic errors remain blocking unless their exact code is configured.
105
+ Machine outputs preserve `severity` and add `effectiveSeverity` plus the
106
+ enforcement source so audit consumers can distinguish detection from policy.
107
+
97
108
  ## Collections — verify documented counts against code
98
109
 
99
110
  `collections` binds a documentation noun to a glob whose **file count is the
@@ -3,7 +3,7 @@ schema_version: "1.0"
3
3
  extension:
4
4
  id: "docguard"
5
5
  name: "DocGuard — CDD Enforcement"
6
- version: "0.40.5"
6
+ version: "0.41.1"
7
7
  description: "Documentation integrity for AI-assisted repositories: lifecycle registry, drift validation, traceability, safe archival, SARIF/JUnit, MCP, GitHub Actions, and Spec Kit hooks."
8
8
  author: "Ricardo Accioly"
9
9
  repository: "https://github.com/raccioly/docguard"
@@ -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.40.5
9
+ version: 0.41.1
10
10
  source: extensions/spec-kit-docguard/skills/docguard-fix
11
11
  ---
12
- <!-- docguard:version: 0.40.5 -->
12
+ <!-- docguard:version: 0.41.1 -->
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.40.5
10
+ version: 0.41.1
11
11
  source: extensions/spec-kit-docguard/skills/docguard-guard
12
12
  ---
13
- <!-- docguard:version: 0.40.5 -->
13
+ <!-- docguard:version: 0.41.1 -->
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.40.5
9
+ version: 0.41.1
10
10
  source: extensions/spec-kit-docguard/skills/docguard-review
11
11
  ---
12
- <!-- docguard:version: 0.40.5 -->
12
+ <!-- docguard:version: 0.41.1 -->
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.40.5
9
+ version: 0.41.1
10
10
  source: extensions/spec-kit-docguard/skills/docguard-score
11
11
  ---
12
- <!-- docguard:version: 0.40.5 -->
12
+ <!-- docguard:version: 0.41.1 -->
13
13
 
14
14
  # DocGuard Score Skill
15
15
 
@@ -4,10 +4,10 @@ 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.40.5
7
+ version: 0.41.1
8
8
  source: extensions/spec-kit-docguard/skills/docguard-sync
9
9
  ---
10
- <!-- docguard:version: 0.40.5 -->
10
+ <!-- docguard:version: 0.41.1 -->
11
11
 
12
12
  # DocGuard Sync Skill
13
13
 
@@ -44,7 +44,7 @@ jobs:
44
44
  fetch-depth: 0
45
45
 
46
46
  - name: Run DocGuard fix --write + auto-commit + PR comment
47
- uses: raccioly/docguard@v0.40.5
47
+ uses: raccioly/docguard@v0.41.1
48
48
  with:
49
49
  command: fix
50
50
  auto-commit: 'true'
@@ -35,7 +35,7 @@ jobs:
35
35
  node-version: '20'
36
36
 
37
37
  - name: Install DocGuard
38
- run: npm install --global --ignore-scripts docguard-cli@0.40.5
38
+ run: npm install --global --ignore-scripts docguard-cli@0.41.1
39
39
 
40
40
  - name: Run DocGuard
41
41
  shell: bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docguard-cli",
3
- "version": "0.40.5",
3
+ "version": "0.41.1",
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": {
@@ -61,6 +61,7 @@
61
61
  "STANDARD.md",
62
62
  "PRIVACY.md",
63
63
  "PHILOSOPHY.md",
64
+ "CHANGELOG.md",
64
65
  "README.md",
65
66
  "LICENSE"
66
67
  ]
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "version": {
13
13
  "type": "string",
14
- "description": "Schema version (0.1, 0.2, ... 0.5). Bumped when fields are added or behavior changes. Migrate with `docguard upgrade --apply`.",
14
+ "description": "Schema version (0.1, 0.2, ... 0.6). Bumped when fields are added or behavior changes. Migrate with `docguard upgrade --apply`.",
15
15
  "pattern": "^\\d+\\.\\d+(\\.\\d+)?$"
16
16
  },
17
17
  "projectName": {
@@ -112,6 +112,9 @@
112
112
  "generatedStaleness":{ "type": "boolean" },
113
113
  "canonicalSync": { "type": "boolean" },
114
114
  "surfaceSync": { "type": "boolean" },
115
+ "diffSuspicion": { "type": "boolean" },
116
+ "referenceExistence":{ "type": "boolean" },
117
+ "apiDocSmells": { "type": "boolean" },
115
118
  "metricsConsistency":{ "type": "boolean" }
116
119
  },
117
120
  "additionalProperties": false
@@ -124,6 +127,17 @@
124
127
  "enum": ["high", "medium", "low"]
125
128
  }
126
129
  },
130
+ "findingSeverity": {
131
+ "type": "object",
132
+ "description": "Per-finding-code enforcement overrides. Exact codes take precedence over validator severity. high = blocking error, medium = warning, low = informational. Intrinsic errors can only be demoted with an explicit code entry.",
133
+ "patternProperties": {
134
+ "^[A-Z]{3}[0-9]{3}$": {
135
+ "type": "string",
136
+ "enum": ["high", "medium", "low"]
137
+ }
138
+ },
139
+ "additionalProperties": false
140
+ },
127
141
  "draftStalenessDays": {
128
142
  "type": "integer",
129
143
  "minimum": 1,
@@ -48,6 +48,7 @@
48
48
  "oneOf": [
49
49
  { "$ref": "#/$defs/jsonSource" },
50
50
  { "$ref": "#/$defs/collectionSource" },
51
+ { "$ref": "#/$defs/pythonLiteralSource" },
51
52
  { "$ref": "#/$defs/oasdiffSource" },
52
53
  { "$ref": "#/$defs/bufSource" }
53
54
  ]
@@ -82,6 +83,17 @@
82
83
  "allowEmpty": { "type": "boolean" }
83
84
  }
84
85
  },
86
+ "pythonLiteralSource": {
87
+ "type": "object",
88
+ "additionalProperties": false,
89
+ "required": ["adapter", "path", "symbol", "allowEmpty"],
90
+ "properties": {
91
+ "adapter": { "const": "python-literal-count" },
92
+ "path": { "allOf": [{ "$ref": "#/$defs/safePath" }, { "pattern": "\\.py$" }] },
93
+ "symbol": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$" },
94
+ "allowEmpty": { "type": "boolean" }
95
+ }
96
+ },
85
97
  "input": {
86
98
  "type": "object",
87
99
  "additionalProperties": false,
@@ -31,7 +31,7 @@ jobs:
31
31
  node-version: '20'
32
32
 
33
33
  - name: Install DocGuard
34
- run: npm install --global --ignore-scripts docguard-cli@0.40.5
34
+ run: npm install --global --ignore-scripts docguard-cli@0.41.1
35
35
 
36
36
  - name: Run DocGuard
37
37
  shell: bash
@@ -16,6 +16,22 @@
16
16
  "pointer": "/retentionDays"
17
17
  },
18
18
  "predicate": { "kind": "equals", "valueType": "number" }
19
+ },
20
+ {
21
+ "id": "surface.python-scanners",
22
+ "applicability": { "mode": "always" },
23
+ "target": {
24
+ "document": "docs-canonical/ARCHITECTURE.md",
25
+ "heading": "Scanner registry",
26
+ "statement": "The registry contains {{value}} scanners."
27
+ },
28
+ "source": {
29
+ "adapter": "python-literal-count",
30
+ "path": "src/package/scanners.py",
31
+ "symbol": "SCANNERS",
32
+ "allowEmpty": false
33
+ },
34
+ "predicate": { "kind": "count-equals" }
19
35
  }
20
36
  ]
21
37
  }