docguard-cli 0.41.5 → 0.41.6

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/CHANGELOG.md CHANGED
@@ -7,6 +7,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.41.6] - 2026-09-18
11
+
12
+ Automated weekly release — batches everything merged since `v0.41.5`.
13
+
14
+ ### Changed
15
+
16
+ - test: make this suite probeable by TestGuard, and close the gap it found (#415)
17
+ - fix: do not block projects that never adopted DocGuard; protect hook backups (#414)
18
+ - chore: complete the pass to generic project references (#413)
19
+
20
+
21
+ ### Added
22
+
23
+ - TestGuard claim probing for this repository: `testguard.claims.json` plus
24
+ `tools/node-test-json-reporter.mjs`, which translates `node --test` output into
25
+ the Jest-shape JSON TestGuard reads (node:test ships no `json` reporter). The
26
+ first claim covers `backupFile`; probing it found a real gap — one guard was
27
+ masked by another, so its fault survived while every test stayed green — and
28
+ the missing case is now covered.
29
+
30
+ ### Changed
31
+
32
+ - Completed the pass to generic project references: the earlier sweep matched a
33
+ narrower pattern than the one used to find them, so sixteen mentions survived
34
+ in source comments, tests and changelog prose. Two references are deliberately
35
+ kept: `.docguard-archive.json` records an archived spec path plus the
36
+ `git restore` command that recovers it, and renaming that string would point
37
+ the restore at a path that never existed.
38
+
39
+ ### Fixed
40
+
41
+ - Stop blocking commits in projects that never adopted DocGuard. A Git hook
42
+ lives in the common `.git/hooks` and governs every linked worktree, while
43
+ `.docguard.json` is a branch-local tracked file — during adoption the two
44
+ cannot be consistent, so a hook installed on one branch blocked commits on
45
+ every other branch and worktree, where `guard` exited 1 for missing canonical
46
+ docs. `guard` now exits **3** ("not initialised") instead of 1 when there is
47
+ no `.docguard.json`, and the generated hooks skip enforcement and allow the
48
+ operation. Exit 3 is still non-zero, so a CI gate that fails on any non-zero
49
+ status is unchanged, and an adopted project with real findings still exits 1
50
+ and still blocks. Existing installed hooks do not self-heal — re-run
51
+ `docguard hooks` to pick this up.
52
+ - Stop spending the single backup slot on a no-op. Re-installing a byte-identical
53
+ managed hook wrote a redundant `.bak`; with `--force` a second install
54
+ overwrote the backup holding the user's own original hook, which is
55
+ unrecoverable because a `.git/hooks` file is not in version control. Identical
56
+ writes are now skipped, and a `.bak` DocGuard did not write is preserved
57
+ alongside a timestamped copy rather than replaced.
58
+
59
+
10
60
  ## [0.41.5] - 2026-09-17
11
61
 
12
62
  Automated weekly release — batches everything merged since `v0.41.4`.
@@ -1809,7 +1859,7 @@ Python, and AWS/AppSync projects.
1809
1859
  longer scans `commands/docguard.*.md` (DocGuard's slash-command docs, which it
1810
1860
  installs into the project) for count claims. A stale "N validators" baked into
1811
1861
  those shipped docs was being reported as the *user's* drift in every project
1812
- that had them (field test: quick-recon-tool, hugocross). A user's own
1862
+ that had them (field test: a downstream project, a downstream project). A user's own
1813
1863
  `commands/<name>.md` is unaffected.
1814
1864
  - **Dogfooding closure** — Canonical-Sync now scans **AGENTS.md** in addition to
1815
1865
  README for "ships N commands"/"N validators" surface claims (it only checked
@@ -664,12 +664,23 @@ export function runGuard(projectDir, config, flags) {
664
664
 
665
665
  const data = runGuardInternal(projectDir, config);
666
666
 
667
+ // A project with no .docguard.json has not adopted DocGuard. Its "failures"
668
+ // are missing canonical docs, which is not the same fact as "this project
669
+ // failed its checks" — and consumers need to tell them apart. Exit 3 says
670
+ // "not initialised": still non-zero, so a CI gate that fails on any non-zero
671
+ // status is unchanged, but the generated Git hook can let the commit through
672
+ // instead of blocking a project that never adopted the tool.
673
+ const uninitialised = !existsSync(resolvePath(projectDir, '.docguard.json'));
674
+ const exitFor = d => (d.effectiveErrors > 0
675
+ ? (uninitialised ? 3 : 1)
676
+ : d.effectiveWarnings > 0 ? 2 : 0);
677
+
667
678
  // ── SARIF output (2.1.0) ──
668
679
  // Same flush discipline as the JSON branch below (bug-105): set exitCode and
669
680
  // write+return so a piped consumer never gets a truncated payload.
670
681
  if (flags.format === 'sarif') {
671
682
  const sarif = toSarif(data, { projectDir });
672
- process.exitCode = data.effectiveErrors > 0 ? 1 : data.effectiveWarnings > 0 ? 2 : 0;
683
+ process.exitCode = exitFor(data);
673
684
  process.stdout.write(JSON.stringify(sarif, null, 2) + '\n');
674
685
  return;
675
686
  }
@@ -680,7 +691,7 @@ export function runGuard(projectDir, config, flags) {
680
691
  // Exit-code semantics identical to sarif/json.
681
692
  if (flags.format === 'junit') {
682
693
  const xml = toJUnit(data);
683
- process.exitCode = data.effectiveErrors > 0 ? 1 : data.effectiveWarnings > 0 ? 2 : 0;
694
+ process.exitCode = exitFor(data);
684
695
  process.stdout.write(xml + '\n');
685
696
  return;
686
697
  }
@@ -689,7 +700,7 @@ export function runGuard(projectDir, config, flags) {
689
700
  if (flags.format === 'json') {
690
701
  // Use severity-aware effective counts for exit code; raw counts stay in the JSON
691
702
  // for display tools that want to show the full picture.
692
- const code = data.effectiveErrors > 0 ? 1 : data.effectiveWarnings > 0 ? 2 : 0;
703
+ const code = exitFor(data);
693
704
  // v0.28: set exitCode + return instead of process.exit(). A large JSON
694
705
  // payload (>~8 KB) written to a PIPE flushes asynchronously; an immediate
695
706
  // process.exit() truncates it mid-string, so a CI consumer parsing stdout
@@ -991,5 +1002,5 @@ export function runGuard(projectDir, config, flags) {
991
1002
  // v0.28: exitCode + return (not process.exit) so the buffered text output
992
1003
  // flushes to a pipe before the process exits — same truncation fix as the
993
1004
  // JSON path above.
994
- process.exitCode = data.effectiveErrors > 0 ? 1 : data.effectiveWarnings > 0 ? 2 : 0;
1005
+ process.exitCode = exitFor(data);
995
1006
  }
@@ -105,6 +105,17 @@ import { listCanonicalDocs } from '../shared-ignore.mjs';
105
105
 
106
106
  // Git enforcement is offline and fail-closed; agent nudges stay best-effort.
107
107
  const ENFORCEMENT_RUNTIME = `
108
+ # A Git hook is repo-wide (it lives in the common .git/hooks and is shared by
109
+ # every linked worktree), but .docguard.json is a branch-local tracked file.
110
+ # During adoption the two cannot be consistent: the hook is already active
111
+ # everywhere while the config exists on one branch only. A project that has not
112
+ # adopted DocGuard must not be blocked by a hook installed from another branch.
113
+ if [ ! -f ".docguard.json" ]; then
114
+ echo "DocGuard: not initialised here (no .docguard.json) — skipping."
115
+ echo " Adopt it with: docguard init"
116
+ exit 0
117
+ fi
118
+
108
119
  if ! command -v node >/dev/null 2>&1; then
109
120
  echo "❌ Node.js runtime not found — operation blocked" >&2
110
121
  exit 1
@@ -136,7 +147,10 @@ ${ENFORCEMENT_RUNTIME}
136
147
  "$DOCGUARD" guard
137
148
  EXIT_CODE=$?
138
149
 
139
- if [ "$EXIT_CODE" -ne 0 ] && [ "$EXIT_CODE" -ne 2 ]; then
150
+ if [ "$EXIT_CODE" -eq 3 ]; then
151
+ echo ""
152
+ echo "DocGuard: project not initialised — commit allowed"
153
+ elif [ "$EXIT_CODE" -ne 0 ] && [ "$EXIT_CODE" -ne 2 ]; then
140
154
  echo ""
141
155
  echo "❌ DocGuard guard FAILED — commit blocked"
142
156
  echo " Fix the errors above, then try again."
@@ -265,7 +279,10 @@ fi
265
279
  "$DOCGUARD" guard
266
280
  EXIT_CODE=$?
267
281
 
268
- if [ "$EXIT_CODE" -ne 0 ] && [ "$EXIT_CODE" -ne 2 ]; then
282
+ if [ "$EXIT_CODE" -eq 3 ]; then
283
+ echo ""
284
+ echo "DocGuard: project not initialised — commit allowed"
285
+ elif [ "$EXIT_CODE" -ne 0 ] && [ "$EXIT_CODE" -ne 2 ]; then
269
286
  echo ""
270
287
  echo "❌ DocGuard guard FAILED — commit blocked."
271
288
  echo " Remaining issues need an AI agent (content rewrites, not mechanical):"
@@ -26,7 +26,7 @@ const CODE_EXTENSIONS = new Set([
26
26
 
27
27
  // v0.16-P2: language-aware patterns. The original JS/TS-only sets created
28
28
  // false-negative warnings on Python/Rust/Go/Java projects (reported by the
29
- // quick-recon-tool Python user: TEST-SPEC.md was flagged unlinked even
29
+ // a downstream project Python user: TEST-SPEC.md was flagged unlinked even
30
30
  // though Python tests existed because `.test.mjs` didn't match `test_*.py`).
31
31
  import { TEST_PATTERNS, TRACE_MAP, isTraceableSource } from '../shared-trace-patterns.mjs';
32
32
 
@@ -32,7 +32,7 @@ const HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', '
32
32
  * optional c-all `/shop/[[...filters]]` → `/shop/{}`
33
33
  * Without the bracket rule, a doc written in Next.js `[id]` syntax never matched
34
34
  * the code-scan's `:id`, so every dynamic route double-fired as both
35
- * "documented-but-absent" and "undocumented" (field test: hugocross_revamp).
35
+ * "documented-but-absent" and "undocumented" (field test: a downstream project).
36
36
  * @param {string} raw
37
37
  * @returns {string} normalized path (e.g. "/api/users/{}") or '' if not a path
38
38
  */
@@ -47,7 +47,7 @@ const GENERIC_TOKENS = new Set([
47
47
  'font', 'color', 'colors', 'tracking', 'surface', 'auto', 'full', 'next',
48
48
  'body', 'sans', 'blue', 'accent', 'size', 'spacing', 'margin', 'padding',
49
49
  'width', 'height', 'flex', 'grid', 'bold', 'bg', 'rounded',
50
- // HTTP / REST / handler plumbing — generic across any API route (globalshares
50
+ // HTTP / REST / handler plumbing — generic across any API route (a downstream project
51
51
  // corpus: a route-inventory doc + heavy rewrites flooded findings with these)
52
52
  'code', 'json', 'err', 'error', 'message', 'auth', 'get', 'put', 'post',
53
53
  'patch', 'delete', 'req', 'res', 'route', 'routes', 'handler', 'endpoint',
@@ -56,7 +56,7 @@ const COMMON_DOTFILES = new Set([
56
56
  // Generated tool artifacts (caches, coverage data, lock-data) that land at the
57
57
  // repo root but are NOT configuration a human authors or documents. Treating
58
58
  // them as "undocumented config files" is a false positive (field test:
59
- // quick-recon-tool flagged pytest's `.coverage` SQLite data file). Matched by
59
+ // a downstream project flagged pytest's `.coverage` SQLite data file). Matched by
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) {
@@ -14,15 +14,33 @@ import { resolve, dirname } from 'node:path';
14
14
  * Create a .bak backup of an existing file before --force overwrites it.
15
15
  * Only backs up if the file exists and has content.
16
16
  */
17
- export function backupFile(filePath) {
18
- if (existsSync(filePath)) {
19
- try {
20
- const content = readFileSync(filePath, 'utf-8');
21
- if (content.trim().length > 0) {
22
- copyFileSync(filePath, filePath + '.bak');
17
+ export function backupFile(filePath, nextContent = null) {
18
+ if (!existsSync(filePath)) return;
19
+ try {
20
+ const content = readFileSync(filePath, 'utf-8');
21
+ if (content.trim().length === 0) return;
22
+
23
+ // Nothing to preserve when the file is already what we are about to write.
24
+ // Backing it up anyway produced a redundant .bak and, worse, consumed the
25
+ // single backup slot that may still hold the user's own original.
26
+ if (nextContent !== null && content === nextContent) return;
27
+
28
+ // The backup slot is single and unversioned. Overwriting it destroys the
29
+ // previous backup, which on a second forced install is the user's ORIGINAL
30
+ // file — unrecoverable, because a .git/hooks file is not in version control.
31
+ // Only ever replace a .bak we wrote ourselves; otherwise keep it and write
32
+ // a timestamped sibling.
33
+ const bak = filePath + '.bak';
34
+ if (existsSync(bak)) {
35
+ const prior = readFileSync(bak, 'utf-8');
36
+ if (prior !== content) {
37
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
38
+ copyFileSync(filePath, `${filePath}.${stamp}.bak`);
39
+ return;
23
40
  }
24
- } catch { /* backup failure is non-fatal */ }
25
- }
41
+ }
42
+ copyFileSync(filePath, bak);
43
+ } catch { /* backup failure is non-fatal */ }
26
44
  }
27
45
 
28
46
  /**
@@ -31,7 +49,7 @@ export function backupFile(filePath) {
31
49
  */
32
50
  export function safeWrite(filePath, content) {
33
51
  mkdirSync(dirname(filePath), { recursive: true });
34
- backupFile(filePath);
52
+ backupFile(filePath, content);
35
53
  writeFileSync(filePath, content, 'utf-8');
36
54
  }
37
55
 
@@ -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.5"
6
+ version: "0.41.6"
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.5
9
+ version: 0.41.6
10
10
  source: extensions/spec-kit-docguard/skills/docguard-fix
11
11
  ---
12
- <!-- docguard:version: 0.41.5 -->
12
+ <!-- docguard:version: 0.41.6 -->
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.5
10
+ version: 0.41.6
11
11
  source: extensions/spec-kit-docguard/skills/docguard-guard
12
12
  ---
13
- <!-- docguard:version: 0.41.5 -->
13
+ <!-- docguard:version: 0.41.6 -->
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.5
9
+ version: 0.41.6
10
10
  source: extensions/spec-kit-docguard/skills/docguard-review
11
11
  ---
12
- <!-- docguard:version: 0.41.5 -->
12
+ <!-- docguard:version: 0.41.6 -->
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.5
9
+ version: 0.41.6
10
10
  source: extensions/spec-kit-docguard/skills/docguard-score
11
11
  ---
12
- <!-- docguard:version: 0.41.5 -->
12
+ <!-- docguard:version: 0.41.6 -->
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.5
7
+ version: 0.41.6
8
8
  source: extensions/spec-kit-docguard/skills/docguard-sync
9
9
  ---
10
- <!-- docguard:version: 0.41.5 -->
10
+ <!-- docguard:version: 0.41.6 -->
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.5
47
+ uses: raccioly/docguard@v0.41.6
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.5
38
+ run: npm install --global --ignore-scripts docguard-cli@0.41.6
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.5",
3
+ "version": "0.41.6",
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.5
34
+ run: npm install --global --ignore-scripts docguard-cli@0.41.6
35
35
 
36
36
  - name: Run DocGuard
37
37
  shell: bash