docguard-cli 0.41.3 → 0.41.5

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 (32) hide show
  1. package/CHANGELOG.md +78 -3
  2. package/README.md +10 -4
  3. package/cli/commands/diff.mjs +1 -1
  4. package/cli/commands/guard.mjs +1 -1
  5. package/cli/commands/specs.mjs +8 -4
  6. package/cli/docguard.mjs +1 -1
  7. package/cli/findings.mjs +1 -1
  8. package/cli/scanners/instruction-audit.mjs +20 -3
  9. package/cli/scanners/spec-registry.mjs +44 -2
  10. package/cli/shared-ignore.mjs +49 -0
  11. package/cli/shared-validator-surface.mjs +17 -0
  12. package/cli/shared.mjs +1 -1
  13. package/cli/validators/canonical-sync.mjs +13 -18
  14. package/cli/validators/cross-reference.mjs +3 -3
  15. package/cli/validators/docs-coverage.mjs +5 -0
  16. package/cli/validators/environment.mjs +1 -1
  17. package/cli/validators/freshness.mjs +27 -0
  18. package/cli/validators/metrics-consistency.mjs +21 -9
  19. package/cli/validators/test-spec.mjs +1 -1
  20. package/cli/validators/todo-tracking.mjs +3 -2
  21. package/cli/validators/traceability.mjs +14 -5
  22. package/docs/quickstart.md +1 -1
  23. package/extensions/spec-kit-docguard/extension.yml +1 -1
  24. package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +2 -2
  25. package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +2 -2
  26. package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +2 -2
  27. package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +2 -2
  28. package/extensions/spec-kit-docguard/skills/docguard-sync/SKILL.md +2 -2
  29. package/extensions/spec-kit-docguard/templates/github-workflows/docguard-autofix.yml +1 -1
  30. package/extensions/spec-kit-docguard/templates/github-workflows/docguard-guard.yml +1 -1
  31. package/package.json +1 -1
  32. package/templates/ci/github-actions.yml +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.41.5] - 2026-09-17
11
+
12
+ Automated weekly release — batches everything merged since `v0.41.4`.
13
+
14
+ ### Changed
15
+
16
+ - fix: four tool defects from a downstream field report (#410)
17
+ - chore: use generic project references and stop tracking .wolf/ (#411)
18
+
19
+
20
+ ### Changed
21
+
22
+ - Replaced named third-party project references in source comments, tests and
23
+ changelog history with generic terms. Field-test provenance is still recorded;
24
+ the projects are no longer identified by name. Stopped tracking `.wolf/`
25
+ (local agent working notes, never part of the published package).
26
+
27
+ ### Fixed
28
+
29
+ - Stop counting a review stamp as a content change. Adding or updating
30
+ `<!-- docguard:last-reviewed -->` in a spec artifact changed its registry
31
+ digest, so the review that Freshness (FRS002) asks for immediately made the
32
+ spec registry `STALE` and demanded `specs --write` — the tool created the work
33
+ it then reported. That marker is now excluded from the digest source;
34
+ `docguard:version` and `docguard:status` still re-digest, because those are
35
+ substantive lifecycle edits. Registries written by earlier releases keep their
36
+ trust (both digest forms are accepted); a project whose specs already carry a
37
+ review stamp needs one `docguard specs --write` to settle.
38
+ - Stop reporting a document as "review due" while it is staged in the commit
39
+ being checked. A pre-commit hook runs `guard` against the very change being
40
+ made, so an author updating `AGENTS.md` was told to review `AGENTS.md`. Being
41
+ edited in this commit is now treated as the strongest freshness signal and
42
+ outranks the repository-wide commit-count heuristic.
43
+ - Stop leaving `.docguard-specs.json.bak` behind. `specs --write` regenerates a
44
+ Git-tracked, fully derived file, so the backup was noise that DocGuard then
45
+ reported as an undocumented config file (DCV001). DocGuard also no longer
46
+ raises DCV001 for any `.bak` it wrote itself.
47
+ - Report what `specs --write` actually did. It printed the pre-write differences
48
+ under a "Differences:" heading after reporting `WRITTEN`, reading as though the
49
+ drift were still outstanding; resolved differences are now labelled as resolved.
50
+
51
+
52
+ ## [0.41.4] - 2026-09-17
53
+
54
+ Automated weekly release — batches everything merged since `v0.41.3`.
55
+
56
+ ### Changed
57
+
58
+ - fix: correct the validator-surface claim and document DOCGUARD_API_KEY (#408)
59
+ - fix: harden adoption precision boundaries (#406)
60
+
61
+
62
+ ### Fixed
63
+
64
+ - Document `DOCGUARD_API_KEY` in the canonical environment doc. `ENVIRONMENT.md`
65
+ claimed DocGuard uses "no API keys" and needs no environment variables, which
66
+ contradicted `SECURITY.md` and the HTTP MCP server, where the variable is
67
+ required to bind a non-loopback host. Core CLI commands still need no credential.
68
+ - Enforce the published validator count in `npm test`. Canonical-Sync and
69
+ Metrics-Consistency already detect a stale "N validators" claim, but they emit
70
+ warnings and the CI self-scan deliberately tolerates guard's warnings-only exit,
71
+ so a wrong count could reach `main` with every check green. The count is now
72
+ asserted against the shipped `cli/validators/*.mjs` modules, and README explains
73
+ why guard prints 30 result rows for 29 validators.
74
+ - Count DocGuard validator claims from one shared source of truth: the
75
+ validator modules shipped in the installed package. A consumer's intentionally
76
+ disabled validators and extra guard checks cannot create a false MET001 or
77
+ CSY003 finding, or an unsafe count rewrite.
78
+ - Keep instruction-pointer basename resolution out of Git-ignored directories
79
+ and nested Git checkouts, including linked worktree copies.
80
+ - Keep planned lifecycle registries that are new, removed from the Git index,
81
+ or modified pending commit non-authoritative for TRC004 while explaining how
82
+ to restore or commit the registry and current spec artifacts; the remediation
83
+ no longer suggests adding an artificial `@req` marker.
84
+
10
85
  ## [0.41.3] - 2026-09-15
11
86
 
12
87
  Automated weekly release — batches everything merged since `v0.41.2`.
@@ -1200,7 +1275,7 @@ Context7 identified in the platform review).
1200
1275
  `website/docs/`) are now claim-scanned and counted as "tracked" **without being
1201
1276
  enrolled** in `requiredFiles.canonical`. A folder literally named `documentation/`
1202
1277
  is unambiguously a doc home DocGuard governs; this stays distinct from the
1203
- arbitrary-subdir walk the wu-whatsappinbox scoping fix removed (a number buried
1278
+ arbitrary-subdir walk the downstream-project scoping fix removed (a number buried
1204
1279
  in `security/wolf-archive/` is still never scanned). `config.docs.dirs` EXTENDS
1205
1280
  the set with non-standard homes (it never replaces auto-detection); use
1206
1281
  `.docguardignore` to exclude a conventional dir. The doc-home set is now a single
@@ -1368,7 +1443,7 @@ dogfooding.
1368
1443
  ## [0.27.0] - 2026-06-19
1369
1444
 
1370
1445
  Acting on a third end-to-end LLM field report (a coding agent ran DocGuard on a
1371
- Vite+Vitest WhatsApp-inbox repo). The headline is architectural: DocGuard is a
1446
+ Vite+Vitest downstream repo). The headline is architectural: DocGuard is a
1372
1447
  tool *for LLMs*, so every run should end with a suggested next action and every
1373
1448
  finding it surfaces should be addressable, suppressible, and — when uncertain —
1374
1449
  reportable. This release introduces structured **findings** (stable codes +
@@ -2658,7 +2733,7 @@ and adds a cross-cutting "no validator throws" safety net. **22 validators,
2658
2733
  ### Fixed
2659
2734
 
2660
2735
  - **B-5: Freshness validator crashed with `getLastCommitDate is not defined`.** A an enterprise client project install of v0.13.0 produced this ReferenceError despite all the imports being correct in source — we couldn't reproduce locally, but the user's report was clear. Fix: defensive dynamic import in `freshness.mjs` that falls back to the pre-v0.13 inline implementation if `../shared-git.mjs` ever fails to load. Worst-case behavior is now "rename detection silently disabled" instead of "validator crashes with useless message". Also added an inline fallback for the same defensive layering. Reported by an enterprise client project.
2661
- - **B-6: Cross-Reference didn't URL-decode link target paths.** A markdown link like `[name](../WU%20Documentation/foo.md)` (where the directory has a space) was looked up with `existsSync('../WU%20Documentation/foo.md')` literally — the filesystem stores the decoded form. Now: `resolveTarget` tries BOTH the literal path (for paths that legitimately contain `%`) and the URL-decoded form. **Effect on an enterprise client project: Cross-Reference went from 28/28 to 101/101 checks — 73 previously-broken refs now resolve correctly.** Reported by an enterprise client project.
2736
+ - **B-6: Cross-Reference didn't URL-decode link target paths.** A markdown link like `[name](../Client%20Documentation/foo.md)` (where the directory has a space) was looked up with `existsSync('../Client%20Documentation/foo.md')` literally — the filesystem stores the decoded form. Now: `resolveTarget` tries BOTH the literal path (for paths that legitimately contain `%`) and the URL-decoded form. **Effect on an enterprise client project: Cross-Reference went from 28/28 to 101/101 checks — 73 previously-broken refs now resolve correctly.** Reported by an enterprise client project.
2662
2737
  - **Cross-cutting safety net**: new `tests/guard-no-throw.test.mjs` runs guard against a fixture repo and asserts no validator leaks a ReferenceError / TypeError / "is not defined" / "is not a function" / "Cannot read properties of undefined" pattern into user-facing output. Found a *second* lurking bug while writing the test: Structure validator threw `Cannot read properties of undefined (reading 'some')` when `config.requiredFiles.agentFile` was missing — fixed with defensive array-or-string coercion + skip-when-missing for `changelog` too. This safety net runs in CI, catching the entire class of developer-error-leaks before release.
2663
2738
 
2664
2739
  ### Added
package/README.md CHANGED
@@ -77,7 +77,7 @@ graph TD
77
77
  Commands --> setup["setup wizard"]
78
78
  Commands --> other["diff · init · fix · trace · impact · sync · reconcile · retire · specs<br/>explain · memory · upgrade · agents · hooks · badge · ci · watch"]
79
79
 
80
- guard --> Validators["Validators (30)"]
80
+ guard --> Validators["Validators (29)"]
81
81
  generate --> Scanners["Scanners (4)<br/>routes · schemas · doc-tools · speckit"]
82
82
  score --> Scoring["Weighted Scoring<br/>8 categories"]
83
83
  diagnose --> Validators
@@ -275,7 +275,7 @@ DocGuard ships **23 commands** (the "Daily 5" + 18 situational tools, including
275
275
  | Command | What It Does |
276
276
  |:--------|:-------------|
277
277
  | `init` | Bootstrap a project (`--wizard` for interactive · `--with <name>` for scaffolders) |
278
- | `guard` | Validate against canonical docs — 30 validators |
278
+ | `guard` | Validate against canonical docs — 29 validators |
279
279
  | `diff` | Show gaps between docs and code (`--since <ref>` for impact mode) |
280
280
  | `sync` | Refresh code-truth doc sections — keeps memory always up to date |
281
281
  | `score` | Structural CDD maturity score (0-100; not a guard verdict; `--diff` for delta between refs) |
@@ -411,7 +411,13 @@ $ npx docguard-cli generate
411
411
 
412
412
  ## 🔍 Validators
413
413
 
414
- DocGuard runs **30 automated validators** on every `guard` check. Source-facing validators are language-aware where their evidence model applies; repository and document validators operate independently of source language.
414
+ DocGuard runs **29 automated validators** on every `guard` check. Source-facing validators are language-aware where their evidence model applies; repository and document validators operate independently of source language.
415
+
416
+ > **Counting note:** `guard` prints 30 result rows, not 29. `Structure` emits a
417
+ > second check result (`Doc Sections`) under the same validator key, so rows are
418
+ > checks, not validators. The published number is the count of shipped
419
+ > `cli/validators/*.mjs` modules and is enforced by tests — don't derive it by
420
+ > counting output rows.
415
421
 
416
422
  | # | Validator | What It Checks | Default |
417
423
  |:--|:----------|:--------------|:--------|
@@ -544,7 +550,7 @@ DocGuard provides AI agent slash commands for integrated workflows. Installed au
544
550
  | Command | What It Does |
545
551
  |:--------|:-------------|
546
552
  | `/docguard.init` | Initialize Canonical-Driven Development in a new or existing project |
547
- | `/docguard.guard` | Run quality validation — check all 30 validators |
553
+ | `/docguard.guard` | Run quality validation — check all 29 validators |
548
554
  | `/docguard.review` | Analyze doc quality and suggest improvements |
549
555
  | `/docguard.fix` | Generate targeted fix prompts for specific issues |
550
556
  | `/docguard.update` | Update canonical docs after code changes — detect drift and sync documentation |
@@ -203,7 +203,7 @@ export function diffEntities(dir, config = {}) {
203
203
 
204
204
  // v0.16-P4 (revised in v0.17.1): conservative denylist of system env vars
205
205
  // that appear in prose ("the venv `PATH`") but are never user-set app env
206
- // vars. v0.17.1-B7: trimmed to TRULY-system-only after wu feedback —
206
+ // vars. v0.17.1-B7: trimmed to TRULY-system-only after downstream feedback —
207
207
  // NODE_ENV / CI / GITHUB_* are legitimately app env vars when read via
208
208
  // process.env. Including them caused diff to falsely flag `NODE_ENV` as
209
209
  // "in code but not docs" even when ENVIRONMENT.md documented it.
@@ -224,7 +224,7 @@ function renderableItems(v) {
224
224
  // .md could drift forever while guard stayed green — the human had to REMEMBER to
225
225
  // enroll each doc, which is exactly the step that fails silently. We deliberately
226
226
  // do NOT deep-scan every doc for claims (that floods false positives — see the
227
- // wu-whatsappinbox scar in metrics-consistency). Instead we cheaply report what's
227
+ // downstream-project scar in metrics-consistency). Instead we cheaply report what's
228
228
  // under a validation tier and what isn't, turning silent non-coverage into a
229
229
  // visible nudge. Pure visibility — never gates the build.
230
230
  //
@@ -7,9 +7,8 @@
7
7
 
8
8
  import { createHash } from 'node:crypto';
9
9
  import { execFileSync } from 'node:child_process';
10
- import { existsSync, readFileSync } from 'node:fs';
10
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
11
11
  import { resolve } from 'node:path';
12
- import { safeWrite } from '../writers/generate-io.mjs';
13
12
  import { commitFileTransaction } from '../writers/file-transaction.mjs';
14
13
  import { appendImplementationOutcome } from '../writers/spec-outcomes.mjs';
15
14
  import { serializeLifecycleContext } from '../scanners/lifecycle-context.mjs';
@@ -227,7 +226,9 @@ function printResult(result) {
227
226
  console.log(`Registry: ${SPEC_REGISTRY_PATH}`);
228
227
  console.log(`Specs: ${result.specs}; tombstones: ${result.tombstones}`);
229
228
  if (result.differences?.length) {
230
- console.log('Differences:');
229
+ // After --write the registry already matches; listing the pre-write diff
230
+ // under "Differences" read as though the drift were still outstanding.
231
+ console.log(result.status === 'WRITTEN' ? 'Resolved by this write:' : 'Differences:');
231
232
  for (const difference of result.differences) {
232
233
  console.log(` ${difference.path}: ${difference.message}`);
233
234
  }
@@ -264,7 +265,10 @@ export function runSpecs(projectDir, config, flags = {}) {
264
265
  }
265
266
  let status = projection.current ? 'CURRENT' : projection.exists ? 'STALE' : 'MISSING';
266
267
  if (flags.write && !projection.current) {
267
- safeWrite(resolve(projectDir, SPEC_REGISTRY_PATH), projection.serialized);
268
+ // Deliberately not safeWrite: that keeps a .bak, and the registry is
269
+ // generated and Git-tracked, so the backup is noise DocGuard then reports
270
+ // as an undocumented config file.
271
+ writeFileSync(resolve(projectDir, SPEC_REGISTRY_PATH), projection.serialized, 'utf-8');
268
272
  status = 'WRITTEN';
269
273
  }
270
274
  const result = {
package/cli/docguard.mjs CHANGED
@@ -341,7 +341,7 @@ const COMMAND_HELP = {
341
341
  summary: 'Maintain the deterministic spec lifecycle and evidence registry.',
342
342
  usage: 'docguard specs [--check|--write] | docguard specs preflight [--path <spec>] | docguard specs complete --id <spec-id> [--since <ref>] [--write --reason <text>]',
343
343
  flags: [
344
- ['--check', 'Exit 2 when the committed registry is missing, stale, or inconsistent'],
344
+ ['--check', 'Exit 2 when the committed registry is missing, stale, or inconsistent; planned lifecycle deferral requires a clean tracked registry'],
345
345
  ['--write', 'Refresh observed evidence while preserving reviewed lifecycle fields'],
346
346
  ['preflight', 'Brief prior specs, or gate a generated draft with --path'],
347
347
  ['complete', 'Plan or apply the implemented→verified evidence transaction'],
package/cli/findings.mjs CHANGED
@@ -425,7 +425,7 @@ export const CODES = {
425
425
  CSY003: {
426
426
  validator: 'canonicalSync',
427
427
  title: 'Stale "N validators" claim',
428
- help: "A surface doc (README.md/AGENTS.md) states a validator count that does not match guard's actual count (validator files + the inlined Doc Sections validator). Update the claim.",
428
+ help: 'A surface doc (README.md/AGENTS.md) states a validator count that does not match the validator modules shipped in the package. Guard can emit multiple check results from one module; those do not increase the public validator count. Update the claim.',
429
429
  suppress: null,
430
430
  },
431
431
  CSY004: {
@@ -34,7 +34,13 @@
34
34
  import { readFileSync, readdirSync, lstatSync, realpathSync } from 'node:fs';
35
35
  import { resolve, join, dirname, relative, isAbsolute, sep } from 'node:path';
36
36
  import { fileURLToPath } from 'node:url';
37
- import { buildIgnoreFilter, loadDocguardIgnore, DEFAULT_IGNORE_DIRS, relPosix } from '../shared-ignore.mjs';
37
+ import {
38
+ buildIgnoreFilter,
39
+ loadDocguardIgnore,
40
+ loadGitIgnoredFilter,
41
+ DEFAULT_IGNORE_DIRS,
42
+ relPosix,
43
+ } from '../shared-ignore.mjs';
38
44
 
39
45
  const __dirname = dirname(fileURLToPath(import.meta.url));
40
46
 
@@ -165,6 +171,7 @@ function basenameIndex(projectDir, wanted, config = {}) {
165
171
  let root;
166
172
  try { root = realpathSync(projectDir); }
167
173
  catch { return { matches, complete: false, visited: 0, reason: 'repository-unavailable' }; }
174
+ const gitIgnored = loadGitIgnoredFilter(root);
168
175
  let visited = 0;
169
176
  let complete = true;
170
177
  let reason = null;
@@ -177,12 +184,22 @@ function basenameIndex(projectDir, wanted, config = {}) {
177
184
  if (DEFAULT_IGNORE_DIRS.has(entry.name) || entry.name === '.local' || /^\.env(?:\.|$)/i.test(entry.name)) continue;
178
185
  const full = resolve(dir, entry.name);
179
186
  const rel = relPosix(root, full);
180
- if (ignored(rel)) continue;
187
+ if (ignored(rel) || (gitIgnored && gitIgnored(rel))) continue;
181
188
  let stat;
182
189
  try { stat = lstatSync(full); }
183
190
  catch { complete = false; reason ||= 'unreadable-entry'; continue; }
184
191
  if (stat.isSymbolicLink()) continue;
185
- if (stat.isDirectory()) { walk(full); if (reason === 'entry-budget') return; }
192
+ if (stat.isDirectory()) {
193
+ // A linked Git worktree has a `.git` FILE; a nested checkout has a
194
+ // `.git` directory. Neither is part of this repository's primary
195
+ // instruction-pointer namespace, even when its parent was not added
196
+ // to .gitignore. Stop before reading duplicate checkout contents.
197
+ try {
198
+ if (lstatSync(join(full, '.git')).isFile() || lstatSync(join(full, '.git')).isDirectory()) continue;
199
+ } catch { /* ordinary directory: keep walking */ }
200
+ walk(full);
201
+ if (reason === 'entry-budget') return;
202
+ }
186
203
  else if (stat.isFile() && matches.has(entry.name)) matches.get(entry.name).push(rel);
187
204
  }
188
205
  };
@@ -33,6 +33,22 @@ const STOP_TERMS = new Set(['must', 'should', 'with', 'from', 'that', 'this', 'h
33
33
 
34
34
  const posix = path => path.split(sep).join('/').replace(/^\.\//, '');
35
35
  const digest = content => `sha256:${createHash('sha256').update(content).digest('hex')}`;
36
+
37
+ // A review stamp is not a content change. `docguard:last-reviewed` is written by
38
+ // reviewing a document — the very thing Freshness (FRS002) asks for — so letting
39
+ // it reach the digest made every review a registry change: review the doc, then
40
+ // `specs --write` to settle the registry the review just invalidated. Strip only
41
+ // that marker; `docguard:version` and `docguard:status` are substantive lifecycle
42
+ // edits and must keep re-digesting.
43
+ const REVIEW_STAMP_RE = /^[ \t]*<!--[ \t]*docguard:last-reviewed[^>]*-->[ \t]*\r?\n?/gim;
44
+ export const digestSource = content => content.replace(REVIEW_STAMP_RE, '');
45
+ const contentDigest = content => digest(digestSource(content));
46
+
47
+ // Registries written before the review stamp was excluded carry a digest over the
48
+ // raw bytes. Accept either so upgrading does not silently drop trust in an
49
+ // otherwise untouched spec; the next `specs --write` migrates the entry.
50
+ const digestMatches = (recorded, content) =>
51
+ recorded === contentDigest(content) || recorded === digest(content);
36
52
  const sortedUnique = values => [...new Set(values)].sort((a, b) => a.localeCompare(b));
37
53
 
38
54
  function registryDifferences(left, right) {
@@ -281,13 +297,39 @@ export function trustedSpecLifecycleIndex(projectDir) {
281
297
  try { content = readFileSync(resolve(projectDir, entry.path), 'utf8'); } catch { continue; }
282
298
  if (parseSpecId(content) !== entry.specId) continue;
283
299
  const artifact = entry.observed?.artifacts?.find(item => item.path === entry.path);
284
- if (!artifact || artifact.digest !== digest(content)) continue;
300
+ if (!artifact || !digestMatches(artifact.digest, content)) continue;
285
301
  if (entry.reviewed?.lifecycle?.context !== 'current' || entry.reviewed.lifecycle.storage !== 'working_tree') continue;
286
302
  trusted.set(`${entry.specId}\0${entry.path}`, entry.reviewed.lifecycle);
287
303
  }
288
304
  return trusted;
289
305
  }
290
306
 
307
+ /**
308
+ * Planned lifecycle entries that are structurally current but cannot defer
309
+ * traceability because the registry is not a clean, tracked Git artifact.
310
+ * This includes a new registry, a registry removed from the index, and a
311
+ * modified registry awaiting a commit. It is advisory-only: callers may
312
+ * explain the state, never use it as evidence.
313
+ */
314
+ export function uncommittedPlannedSpecLifecycleIndex(projectDir) {
315
+ const candidates = new Map();
316
+ const loaded = readSpecRegistry(projectDir);
317
+ if (loaded.error || loaded.value?.schemaVersion !== SPEC_REGISTRY_SCHEMA_VERSION) return candidates;
318
+ if (trackedAndClean(projectDir, SPEC_REGISTRY_PATH)) return candidates;
319
+ for (const entry of loaded.value.specs) {
320
+ if (entry?.reviewed?.lifecycle?.delivery !== 'planned' || !entry.specId || !entry.path) continue;
321
+ if (!trackedAndClean(projectDir, entry.path)) continue;
322
+ let content;
323
+ try { content = readFileSync(resolve(projectDir, entry.path), 'utf8'); } catch { continue; }
324
+ if (parseSpecId(content) !== entry.specId) continue;
325
+ const artifact = entry.observed?.artifacts?.find(item => item.path === entry.path);
326
+ if (!artifact || !digestMatches(artifact.digest, content)) continue;
327
+ if (entry.reviewed.lifecycle.context !== 'current' || entry.reviewed.lifecycle.storage !== 'working_tree') continue;
328
+ candidates.set(`${entry.specId}\0${entry.path}`, entry.reviewed.lifecycle);
329
+ }
330
+ return candidates;
331
+ }
332
+
291
333
  function taskCompletion(path) {
292
334
  if (!path || !existsSync(path)) return { checked: 0, total: 0 };
293
335
  const content = readFileSync(path, 'utf8');
@@ -468,7 +510,7 @@ export function projectSpecRegistry(projectDir, config = {}, options = {}) {
468
510
  const artifacts = artifactsForFeature
469
511
  .map(artifact => ({
470
512
  path: posix(relative(projectDir, artifact)),
471
- digest: digest(readFileSync(artifact, 'utf8')),
513
+ digest: contentDigest(readFileSync(artifact, 'utf8')),
472
514
  }));
473
515
  specs.push({
474
516
  specId,
@@ -91,6 +91,7 @@ export function isNonProductPath(relPath, config = {}) {
91
91
  * Returns [] if the file is missing or unreadable — never throws.
92
92
  */
93
93
  import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
94
+ import { spawnSync } from 'node:child_process';
94
95
  import { resolve as resolvePath, relative as relativePath, join as joinPath, sep } from 'node:path';
95
96
 
96
97
  /**
@@ -122,6 +123,54 @@ export function loadDocguardIgnore(projectDir) {
122
123
  }
123
124
  }
124
125
 
126
+ /**
127
+ * Return a predicate for paths Git currently classifies as ignored.
128
+ *
129
+ * This deliberately delegates pattern semantics (including nested
130
+ * `.gitignore` files and negation) to Git instead of maintaining a second,
131
+ * incomplete gitignore parser. `--directory` lets callers prune a wholly
132
+ * ignored directory before reading its descendants. A missing Git repository
133
+ * or an unreadable/overflowing result returns null so callers can retain their
134
+ * existing conservative traversal behavior.
135
+ *
136
+ * Only untracked ignored paths are returned by Git, which is intentional:
137
+ * tracked source remains part of the repository even if a later ignore rule
138
+ * happens to mention its name.
139
+ *
140
+ * @param {string} projectDir
141
+ * @returns {((relPath: string) => boolean)|null}
142
+ */
143
+ export function loadGitIgnoredFilter(projectDir) {
144
+ let result;
145
+ try {
146
+ result = spawnSync('git', [
147
+ 'ls-files', '--others', '--ignored', '--exclude-standard', '--directory', '-z',
148
+ ], {
149
+ cwd: projectDir,
150
+ encoding: 'utf8',
151
+ maxBuffer: 8 * 1024 * 1024,
152
+ windowsHide: true,
153
+ });
154
+ } catch {
155
+ return null;
156
+ }
157
+ if (result.status !== 0 || result.error || typeof result.stdout !== 'string') return null;
158
+
159
+ const exact = new Set();
160
+ const directories = [];
161
+ for (const raw of result.stdout.split('\0')) {
162
+ const path = raw.replace(/\\/g, '/').replace(/^\.\//, '');
163
+ if (!path) continue;
164
+ if (path.endsWith('/')) directories.push(path);
165
+ else exact.add(path);
166
+ }
167
+ if (exact.size === 0 && directories.length === 0) return () => false;
168
+ return relPath => {
169
+ const path = String(relPath || '').replace(/\\/g, '/').replace(/^\.\//, '');
170
+ return exact.has(path) || directories.some(directory => path.startsWith(directory));
171
+ };
172
+ }
173
+
125
174
  /**
126
175
  * Merge `.docguardignore` patterns into a config object's `ignore` array.
127
176
  *
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Validator Surface — package capability facts shared by self-governance
3
+ * validators. A validator module is one shipped `cli/validators/*.mjs` file;
4
+ * guard may emit additional check results from a module, but those are not
5
+ * extra public validator modules.
6
+ */
7
+
8
+ import { readdirSync } from 'node:fs';
9
+
10
+ /** Return the number of validator modules in one package directory, or null. */
11
+ export function countValidatorModules(validatorsDir) {
12
+ try {
13
+ return readdirSync(validatorsDir).filter(name => name.endsWith('.mjs')).length;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
package/cli/shared.mjs CHANGED
@@ -289,7 +289,7 @@ import { resolve, relative } from 'node:path';
289
289
  * Conventional documentation-home directory names. A folder named one of these
290
290
  * is unambiguously "docs DocGuard governs" — distinct from arbitrary markdown
291
291
  * buried in a non-doc subdir (security/wolf-archive/, vendored toolkits), which
292
- * the wu-whatsappinbox scoping fix deliberately excludes. We auto-track the
292
+ * the downstream-project scoping fix deliberately excludes. We auto-track the
293
293
  * former and never blanket-walk the latter.
294
294
  */
295
295
  export const DEFAULT_DOC_DIRS = [
@@ -15,9 +15,9 @@
15
15
  *
16
16
  * What it checks:
17
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)
18
+ * 2. README "N validators" matches the shipped `cli/validators/*.mjs`
19
+ * module count. Guard may emit multiple check results from one module;
20
+ * those are not extra public validator modules.
21
21
  * 3. Validator names enumerated inline in README appear in guard output
22
22
  *
23
23
  * What it explicitly skips:
@@ -41,6 +41,7 @@
41
41
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
42
42
  import { resolve, join } from 'node:path';
43
43
  import { mkFinding, resultFromFindings } from '../findings.mjs';
44
+ import { countValidatorModules } from '../shared-validator-surface.mjs';
44
45
 
45
46
  /**
46
47
  * Validate that README count claims about DocGuard's surface match code-truth.
@@ -118,19 +119,13 @@ export function validateCanonicalSync(projectDir, config, guardResults) {
118
119
  }
119
120
  const actualCommandCount = actualUserFacingCount;
120
121
 
121
- // Validator count: always use the file-count truth source. It's run-order
122
- // independent (canonical-sync runs BEFORE metrics-consistency at guard time,
123
- // so guardResults.length would undercount by 1). File count + 1 for the
124
- // single inlined validator (Doc Sections, exported alongside Structure from
125
- // structure.mjs).
126
- const validatorFiles = readdirSync(validatorsDir).filter(f => f.endsWith('.mjs'));
127
- const actualValidatorCount = validatorFiles.length + 1; // +1 for Doc Sections inlined in structure.mjs
128
-
129
- // Names list (currently unused for warnings, but kept for future Check 3
130
- // where the README enumerates validator names inline).
131
- let actualValidatorNames = [];
132
- if (Array.isArray(guardResults) && guardResults.length > 0) {
133
- actualValidatorNames = guardResults.map(r => r.name).filter(Boolean);
122
+ // Validator count is the package capability count used by
123
+ // Metrics-Consistency too. It is run-order independent and excludes extra
124
+ // sub-check results emitted by a validator module (for example, Doc Sections
125
+ // from structure.mjs), which are checks rather than shipped validators.
126
+ const actualValidatorCount = countValidatorModules(validatorsDir);
127
+ if (actualValidatorCount === null) {
128
+ return compose({ na: true, naReason: 'cli/validators could not be read' });
134
129
  }
135
130
 
136
131
  // ── Read surface docs (README.md + AGENTS.md) ──────────────────────
@@ -204,9 +199,9 @@ export function validateCanonicalSync(projectDir, config, guardResults) {
204
199
  code: 'CSY003',
205
200
  validator: 'canonicalSync',
206
201
  severity: 'warn',
207
- message: `A surface doc (README.md/AGENTS.md) claims ${uniqueWrong.map(n => `"${n} validators"`).join(' / ')} but guard reports ${actualValidatorCount}. Update it.`,
202
+ message: `A surface doc (README.md/AGENTS.md) claims ${uniqueWrong.map(n => `"${n} validators"`).join(' / ')} but DocGuard ships ${actualValidatorCount} validator modules. Update it.`,
208
203
  location: null,
209
- suggestion: { kind: 'fix', text: 'Update the "N validators" claim in README.md/AGENTS.md to match guard\'s count' },
204
+ suggestion: { kind: 'fix', text: 'Update the "N validators" claim in README.md/AGENTS.md to match DocGuard\'s shipped validator modules' },
210
205
  }));
211
206
  }
212
207
  } else {
@@ -295,9 +295,9 @@ function editDistance(a, b) {
295
295
  function resolveTarget(sourcePath, targetRel, projectDir) {
296
296
  if (!targetRel) return null;
297
297
  // B-6: try BOTH the literal path and the URL-decoded form. CommonMark
298
- // accepts `[name](../WU%20Documentation/foo.md)` for paths with spaces,
299
- // and the decoded form (`../WU Documentation/foo.md`) is what hits the
300
- // filesystem. The angle-bracket form `<../WU Documentation/foo.md>` is
298
+ // accepts `[name](../Client%20Documentation/foo.md)` for paths with spaces,
299
+ // and the decoded form (`../Client Documentation/foo.md`) is what hits the
300
+ // filesystem. The angle-bracket form `<../Client Documentation/foo.md>` is
301
301
  // already non-URL-encoded by the time it reaches us. Try literal first
302
302
  // (handles paths that legitimately contain `%`), then decoded.
303
303
  const candidates = [targetRel];
@@ -60,6 +60,11 @@ const COMMON_DOTFILES = new Set([
60
60
  // exact name OR prefix (`.coverage.<host>.<pid>` is coverage.py's parallel form).
61
61
  const GENERATED_DOTFILE_PREFIXES = ['.coverage', '.eslintcache', '.stylelintcache', '.tsbuildinfo'];
62
62
  function isGeneratedArtifact(name) {
63
+ // `.bak` is written by DocGuard's own safeWrite before it overwrites a file.
64
+ // Reporting it as an undocumented config file made the tool flag its own
65
+ // backup (field report: `specs --write` -> DCV001 on
66
+ // `.docguard-specs.json.bak`).
67
+ if (name.endsWith('.bak')) return true;
63
68
  return GENERATED_DOTFILE_PREFIXES.some(p => name === p || name.startsWith(p + '.'));
64
69
  }
65
70
 
@@ -139,7 +139,7 @@ export function validateEnvironment(projectDir, config) {
139
139
  const varRe = /`([A-Z][A-Z0-9_]*[A-Z0-9])`/g;
140
140
  // v0.16-P4 (revised in v0.17.1-B7): skip backticked SYSTEM env vars
141
141
  // (PATH, HOME, USER, etc.) that appear in ENVIRONMENT.md prose. Trimmed
142
- // to TRULY-system-only after wu feedback — NODE_ENV / CI / GITHUB_* were
142
+ // to TRULY-system-only after downstream feedback — NODE_ENV / CI / GITHUB_* were
143
143
  // causing asymmetric flagging between diff and this validator. Apps
144
144
  // legitimately treat NODE_ENV as app config; keep the list to vars that
145
145
  // no sane application would read as runtime config.
@@ -188,6 +188,24 @@ function getCodeHistory(dir, ignored) {
188
188
  }).filter(commit => commit.paths.length);
189
189
  }
190
190
 
191
+ /**
192
+ * Documents staged in the commit currently being checked.
193
+ *
194
+ * A pre-commit hook runs guard against the very change being made, so a doc the
195
+ * author is updating right now was still being reported "review due" — the work
196
+ * the finding asks for is in the index (field report: AGENTS.md flagged while
197
+ * staged). Returns POSIX-relative paths; empty when git is unavailable.
198
+ */
199
+ function getStagedPaths(dir) {
200
+ try {
201
+ const out = execFileSync('git', ['diff', '--cached', '--name-only', '-z'],
202
+ { cwd: dir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
203
+ return new Set(out.split('\0').filter(Boolean));
204
+ } catch {
205
+ return new Set();
206
+ }
207
+ }
208
+
191
209
  /**
192
210
  * Check if git is available in this project.
193
211
  */
@@ -256,9 +274,18 @@ export function validateFreshness(dir, config) {
256
274
  const REVIEW_THRESHOLD_DAYS = 30; // Repository-wide trigger, not proof of drift
257
275
  const WARNING_THRESHOLD_COMMITS = 10; // Repository-wide review trigger
258
276
 
277
+ const stagedPaths = getStagedPaths(dir);
278
+
259
279
  for (const docFile of docFiles) {
260
280
  const docPath = resolve(dir, docFile);
261
281
  if (!existsSync(docPath)) continue;
282
+ // Being edited in this very commit is the strongest possible freshness
283
+ // signal; a repository-wide commit count cannot override it.
284
+ if (stagedPaths.has(relPosix(dir, docPath))) {
285
+ results.push({ status: 'skip', doc: docFile,
286
+ message: `${docFile} is staged in this change — review not due; the document is being updated now` });
287
+ continue;
288
+ }
262
289
  const docStatus = readDocStatus(docPath);
263
290
  if (['historical', 'superseded', 'deprecated'].includes(docStatus)) {
264
291
  results.push({ status: 'skip', doc: docFile,
@@ -7,8 +7,10 @@
7
7
  */
8
8
 
9
9
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
10
- import { resolve, join, relative } from 'node:path';
10
+ import { resolve, join, relative, dirname } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
11
12
  import { loadIgnorePatterns, resolveDocDirs } from '../shared.mjs';
13
+ import { countValidatorModules } from '../shared-validator-surface.mjs';
12
14
  // v0.29 consolidation: walker + glob counting live in shared-ignore.mjs (the
13
15
  // single implementations) — this file previously carried private copies.
14
16
  import { walkFiles, countGlobFiles } from '../shared-ignore.mjs';
@@ -33,18 +35,19 @@ export function validateMetricsConsistency(projectDir, config, guardResults) {
33
35
  // ── Collect actual metrics ──
34
36
  const actuals = {};
35
37
 
36
- // Guard check count (from guard results if available)
38
+ // Guard check count is configuration-dependent, so it comes from this run.
39
+ // Validator count is a package capability: it must not shrink when a project
40
+ // disables a validator. Resolve this module's installed directory rather
41
+ // than projectDir, which points at the consumer repository.
37
42
  if (guardResults && Array.isArray(guardResults)) {
38
43
  const totalChecks = guardResults.reduce((sum, r) => {
39
44
  if (r.status === 'skipped') return sum;
40
45
  return sum + (r.total || 0);
41
46
  }, 0);
42
- // +1 because Metrics-Consistency itself hasn't been added to results yet
43
- const validatorCount = guardResults.filter(r => r.status !== 'skipped').length + 1;
44
-
45
47
  actuals.checks = totalChecks;
46
- actuals.validators = validatorCount;
47
48
  }
49
+ const shippedValidatorCount = countShippedValidators();
50
+ if (shippedValidatorCount !== null) actuals.validators = shippedValidatorCount;
48
51
 
49
52
  // Test count — count test files on disk
50
53
  const testFiles = findTestFiles(projectDir);
@@ -68,7 +71,7 @@ export function validateMetricsConsistency(projectDir, config, guardResults) {
68
71
  // subject before overwriting.
69
72
  const patterns = [
70
73
  { key: 'checks', regex: /(?<!\d\/)\b(\d{2,})\s+(?:automated\s+)?checks?\b/gi, label: 'checks', requireBind: true, subject: "DocGuard's own", actualSource: 'docguard.guard.checks' },
71
- { key: 'validators', regex: /(?<!\d\/)\b(\d{2,})\s+validators?\b/gi, label: 'validators', requireBind: true, subject: "DocGuard's own", actualSource: 'docguard.guard.validators' },
74
+ { key: 'validators', regex: /(?<!\d\/)\b(\d{2,})\s+validators?\b/gi, label: 'validators', requireBind: true, subject: "DocGuard's shipped", actualSource: 'docguard.package.validators' },
72
75
  ];
73
76
 
74
77
  // v0.29 (field report #6): project-declared collections. `config.collections`
@@ -245,6 +248,15 @@ function isHistoricalMetricContext(content, index) {
245
248
  return /<!--\s*docguard:status\s+(?:historical|superseded|deprecated|archived)\s*-->/i.test(prefix);
246
249
  }
247
250
 
251
+ /**
252
+ * Count validator modules shipped beside this file. This is deliberately
253
+ * package-local: a consumer's disabled validators or repository layout cannot
254
+ * alter a statement about DocGuard's own capability surface.
255
+ */
256
+ function countShippedValidators() {
257
+ return countValidatorModules(dirname(fileURLToPath(import.meta.url)));
258
+ }
259
+
248
260
  function findTestFiles(dir) {
249
261
  const tests = [];
250
262
  const testDirs = ['tests', 'test', '__tests__', 'spec', 'e2e'];
@@ -296,7 +308,7 @@ function findMarkdownFiles(dir, config = {}) {
296
308
  // The old code recursively walked the WHOLE repo from the root, so it swept in
297
309
  // OpenWolf session archives (security/wolf-archive/**/memory.md) and vendored
298
310
  // toolkit READMEs whose unrelated "N checks" prose was then reported as the
299
- // USER's drift (field test: wu-whatsappinbox, ~39 false warnings the author
311
+ // USER's drift (field test: downstream-project, ~39 false warnings the author
300
312
  // could not act on). Scoping to the docs DocGuard actually governs fixes it.
301
313
  try {
302
314
  for (const entry of readdirSync(dir)) {
@@ -311,7 +323,7 @@ function findMarkdownFiles(dir, config = {}) {
311
323
  // conventional doc dirs (docs/, documentation/, guides/, …) or honors an
312
324
  // explicit config.docs.dirs. NAMED dirs only — code/tooling dirs (security/,
313
325
  // backend/, src/, …) and arbitrary subdirs are still NEVER walked (the
314
- // wu-whatsappinbox false-positive flood the scoping fix removed).
326
+ // downstream-project false-positive flood the scoping fix removed).
315
327
  const canonical = config && config.requiredFiles && Array.isArray(config.requiredFiles.canonical)
316
328
  ? config.requiredFiles.canonical : [];
317
329
  for (const rel of canonical) {
@@ -78,7 +78,7 @@ export function validateTestSpec(projectDir, config) {
78
78
  // different shapes (Controllers, Services, an "Integration Tests" inventory
79
79
  // like `| test-file | what it covers |`). Without this guard a prose
80
80
  // "what it covers" cell — "Health endpoint with real dependencies" — gets
81
- // checked as a missing test file (false positive; field test: wu-whatsappinbox).
81
+ // checked as a missing test file (false positive; field test: downstream-project).
82
82
  const isPathLike = (v) => !!v && !/\s/.test(v) && (/[\\/]/.test(v) || /\.[A-Za-z0-9]{1,6}$/.test(v));
83
83
 
84
84
  for (const row of pipeRows.slice(1)) { // skip the header row
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * TODO/FIXME Tracking Validator — Ensures code annotations are documented
3
3
  *
4
- * Scans source files for TODO:, FIXME:, HACK:, XXX: annotations and checks
5
- * if they are tracked in documentation (ROADMAP.md, CURRENT-STATE.md, etc.).
4
+ * Scans source files for debt tags (todo / fixme / hack / xxx, with a
5
+ * trailing colon or paren) and checks if they are tracked in documentation
6
+ * (ROADMAP.md, CURRENT-STATE.md, etc.).
6
7
  *
7
8
  * Also detects skipped tests without explanation.
8
9
  *
@@ -33,7 +33,11 @@ import {
33
33
  requirementPatterns,
34
34
  } from '../shared-requirements.mjs';
35
35
  import { readRetirementManifest } from '../scanners/retirement-manifest.mjs';
36
- import { parseSpecId, trustedSpecLifecycleIndex } from '../scanners/spec-registry.mjs';
36
+ import {
37
+ parseSpecId,
38
+ trustedSpecLifecycleIndex,
39
+ uncommittedPlannedSpecLifecycleIndex,
40
+ } from '../scanners/spec-registry.mjs';
37
41
 
38
42
  /**
39
43
  * Optional graphify interop (github.com/Graphify-Labs/graphify, MIT).
@@ -278,6 +282,7 @@ function validateRequirementTraceability(projectDir, config, projectFiles) {
278
282
  const reqIds = collectRequirementIds(projectDir, config, patterns);
279
283
  const retiredReqIds = loadRetiredRequirementIds(projectDir);
280
284
  const lifecycleIndex = trustedSpecLifecycleIndex(projectDir);
285
+ const uncommittedPlannedIndex = uncommittedPlannedSpecLifecycleIndex(projectDir);
281
286
 
282
287
  // ── Step 2: Scan test files for requirement ID references ──
283
288
  const testRefs = scanTestFilesForReferences(projectDir, projectFiles, patterns);
@@ -305,11 +310,13 @@ function validateRequirementTraceability(projectDir, config, projectFiles) {
305
310
  // digest-current reviewed lifecycle can defer their test linkage.
306
311
  for (const [key, location] of reqIds) {
307
312
  const reqId = location.id;
308
- const lifecycle = location.specId ? lifecycleIndex.get(`${location.specId}\0${location.file}`) : null;
313
+ const lifecycleKey = location.specId ? `${location.specId}\0${location.file}` : null;
314
+ const lifecycle = lifecycleKey ? lifecycleIndex.get(lifecycleKey) : null;
309
315
  if (lifecycle?.delivery === 'planned') {
310
316
  deferred++;
311
317
  continue;
312
318
  }
319
+ const uncommittedPlanned = lifecycleKey && uncommittedPlannedIndex.has(lifecycleKey);
313
320
  if (location.specId && !lifecycle) lifecycleUnknown++;
314
321
  total++;
315
322
  if (resolvedRefs.has(key)) {
@@ -317,10 +324,12 @@ function validateRequirementTraceability(projectDir, config, projectFiles) {
317
324
  } else {
318
325
  // Try to recover a likely-but-unannotated test via TF-IDF cosine.
319
326
  let softHint = '';
320
- let softText = `Review existing tests for this requirement. If a test verifies it, add an @req ${key} annotation or requirement ID test label; write a test only if behavioral coverage is actually missing.`;
327
+ let softText = uncommittedPlanned
328
+ ? 'The on-disk lifecycle registry says this requirement is planned, but .docguard-specs.json is not a clean tracked Git artifact. Restore it to its committed state, or commit the registry and its current spec artifacts, before treating the requirement as deferred; do not add an @req marker merely to silence this warning.'
329
+ : `Review existing tests for this requirement. If a test verifies it, add an @req ${key} annotation or requirement ID test label; write a test only if behavioral coverage is actually missing.`;
321
330
  const queryText = location.text && location.text.length > reqId.length ? location.text : reqId;
322
- if (testCorpus === null) testCorpus = buildTestCorpus(projectDir, projectFiles);
323
- if (testCorpus.length > 0) {
331
+ if (!uncommittedPlanned && testCorpus === null) testCorpus = buildTestCorpus(projectDir, projectFiles);
332
+ if (!uncommittedPlanned && testCorpus.length > 0) {
324
333
  const ranked = rankBySimilarity(tokenize(queryText), testCorpus);
325
334
  const top = ranked[0];
326
335
  if (top && top.score >= softThreshold) {
@@ -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 (30 validators)
71
+ npx docguard-cli guard # Pass/fail check (29 validators)
72
72
  npx docguard-cli score # 0-100 maturity score
73
73
  ```
74
74
 
@@ -3,7 +3,7 @@ schema_version: "1.0"
3
3
  extension:
4
4
  id: "docguard"
5
5
  name: "DocGuard — CDD Enforcement"
6
- version: "0.41.3"
6
+ version: "0.41.5"
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.41.3
9
+ version: 0.41.5
10
10
  source: extensions/spec-kit-docguard/skills/docguard-fix
11
11
  ---
12
- <!-- docguard:version: 0.41.3 -->
12
+ <!-- docguard:version: 0.41.5 -->
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.41.3
10
+ version: 0.41.5
11
11
  source: extensions/spec-kit-docguard/skills/docguard-guard
12
12
  ---
13
- <!-- docguard:version: 0.41.3 -->
13
+ <!-- docguard:version: 0.41.5 -->
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.41.3
9
+ version: 0.41.5
10
10
  source: extensions/spec-kit-docguard/skills/docguard-review
11
11
  ---
12
- <!-- docguard:version: 0.41.3 -->
12
+ <!-- docguard:version: 0.41.5 -->
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.41.3
9
+ version: 0.41.5
10
10
  source: extensions/spec-kit-docguard/skills/docguard-score
11
11
  ---
12
- <!-- docguard:version: 0.41.3 -->
12
+ <!-- docguard:version: 0.41.5 -->
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.41.3
7
+ version: 0.41.5
8
8
  source: extensions/spec-kit-docguard/skills/docguard-sync
9
9
  ---
10
- <!-- docguard:version: 0.41.3 -->
10
+ <!-- docguard:version: 0.41.5 -->
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.41.3
47
+ uses: raccioly/docguard@v0.41.5
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.41.3
38
+ run: npm install --global --ignore-scripts docguard-cli@0.41.5
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.41.3",
3
+ "version": "0.41.5",
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": {
@@ -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.41.3
34
+ run: npm install --global --ignore-scripts docguard-cli@0.41.5
35
35
 
36
36
  - name: Run DocGuard
37
37
  shell: bash