docguard-cli 0.17.0 → 0.18.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.
@@ -200,23 +200,31 @@ export function diffEntities(dir, config = {}) {
200
200
  };
201
201
  }
202
202
 
203
- // v0.16-P4: common system environment variables that get backticked in
204
- // prose ("the venv `PATH`", "your `HOME` directory") but are NEVER user-set
205
- // application env vars. Excluding them from the docVars set kills the
206
- // false-positive class reported by the Python user where `PATH` was flagged
207
- // as "documented-but-not-implemented".
203
+ // v0.16-P4 (revised in v0.17.1): conservative denylist of system env vars
204
+ // that appear in prose ("the venv `PATH`") but are never user-set app env
205
+ // vars. v0.17.1-B7: trimmed to TRULY-system-only after wu feedback
206
+ // NODE_ENV / CI / GITHUB_* are legitimately app env vars when read via
207
+ // process.env. Including them caused diff to falsely flag `NODE_ENV` as
208
+ // "in code but not docs" even when ENVIRONMENT.md documented it.
208
209
  //
209
- // Conservative list only the names that are unambiguously OS/shell vars.
210
- // Application names like `DATABASE_URL`, `API_KEY` etc. still count.
210
+ // Rule of thumb for inclusion: would a sane Node/Python/Go app ever
211
+ // `process.env.X` this name and treat it as app config? If yes → NOT a
212
+ // system var. PATH/HOME/SHELL/TERM never satisfy that bar.
211
213
  const SYSTEM_ENV_VARS = new Set([
212
- 'PATH', 'HOME', 'USER', 'USERNAME', 'SHELL', 'PWD', 'OLDPWD', 'TMPDIR', 'TEMP', 'TMP',
214
+ // POSIX shell / OS
215
+ 'PATH', 'HOME', 'USER', 'USERNAME', 'SHELL', 'PWD', 'OLDPWD',
216
+ 'TMPDIR', 'TEMP', 'TMP',
217
+ // Locale
213
218
  'LANG', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES', 'TZ',
219
+ // Terminal / interactive
214
220
  'EDITOR', 'VISUAL', 'PAGER', 'TERM', 'COLORTERM',
221
+ // SSH / Display
215
222
  'DISPLAY', 'SSH_AUTH_SOCK', 'SSH_CONNECTION', 'SSH_TTY',
223
+ // XDG base directory spec
216
224
  'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME', 'XDG_RUNTIME_DIR',
217
- // CI/build platform vars (set by the platform, not by the app)
218
- 'CI', 'GITHUB_TOKEN', 'GITHUB_ACTIONS', 'GITHUB_REF', 'GITHUB_SHA',
219
- 'NODE_ENV', // could be app-set but more often platform-set; conservative skip
225
+ // NOTE: NODE_ENV / CI / GITHUB_* used to be here. Removed in v0.17.1
226
+ // because apps DO read them as app config (e.g. NODE_ENV=production
227
+ // gates branching in nearly every Node.js app).
220
228
  ]);
221
229
 
222
230
  export function diffEnvVars(dir, config = {}) {
@@ -66,6 +66,40 @@ function _checkVersionPin(config) {
66
66
  return null;
67
67
  }
68
68
 
69
+ /**
70
+ * v0.17.1: small in-code highlight reel surfaced when a project's pinned
71
+ * version is behind the running CLI. The biggest recurring user pattern is
72
+ * "I asked for feature X" → "X shipped two releases ago". This eliminates
73
+ * the need to grep the CHANGELOG. Keep entries short and command-oriented.
74
+ *
75
+ * Add to this table on every release. Format: [introducedIn, oneLineFeature].
76
+ */
77
+ const _RELEASE_HIGHLIGHTS = [
78
+ ['0.13.0', '`docguard sync --since <ref>` — surgical refresh of code-truth doc sections'],
79
+ ['0.13.1', '`docguard impact --since <ref>` — changed files → affected canonical docs map'],
80
+ ['0.13.1', '`Cross-Reference` validator + "did you mean #X?" hints for broken anchors'],
81
+ ['0.14.1', '`docguard fix --write` auto-fixes high-confidence anchor matches'],
82
+ ['0.15.0', '`docguard guard --timings` — per-validator wall-time profile'],
83
+ ['0.15.0', '`.docguard.json` JSON Schema for VS Code autocomplete'],
84
+ ['0.16.0', '`docguard explain "<warning>"` — paste any warning, get the validator help'],
85
+ ['0.16.0', '`docguard guard --quiet` — suppress banner in hooks/CI'],
86
+ ['0.16.0', '`docguard init --no-spec-kit` — opt out of Spec Kit scaffolding'],
87
+ ['0.16.0', 'Language-aware test patterns (Python `test_*.py`, Rust `tests/*.rs`, Go `*_test.go`, ...)'],
88
+ ['0.17.0', '`docguard memory --diff` — drill into accuracy mismatches (which claim ≠ code)'],
89
+ ['0.17.0', '`docguard guard --pin` — record running CLI version into .docguard.json'],
90
+ ];
91
+
92
+ function _whatsNewSince(pinnedVersion) {
93
+ if (!pinnedVersion) return [];
94
+ const out = [];
95
+ for (const [introducedIn, feature] of _RELEASE_HIGHLIGHTS) {
96
+ if (_semverCompare(introducedIn, pinnedVersion) > 0) {
97
+ out.push(`v${introducedIn}: ${feature}`);
98
+ }
99
+ }
100
+ return out;
101
+ }
102
+
69
103
  /**
70
104
  * v0.17-P1: update the docguardVersion field in .docguard.json after a
71
105
  * successful guard run. Triggered by `docguard guard --pin`. Idempotent.
@@ -445,6 +479,18 @@ export function runGuard(projectDir, config, flags) {
445
479
  const pinHint = _checkVersionPin(config);
446
480
  if (pinHint) {
447
481
  console.log(`\n ${c.yellow}📌 ${pinHint}${c.reset}`);
482
+ // v0.17.1: surface features added since the pinned version so users
483
+ // who pinned at v0.12 and just upgraded actually KNOW about sync,
484
+ // impact, explain, memory --diff, etc. The biggest user complaint
485
+ // pattern is "I asked for X but X already shipped two releases ago."
486
+ const whatsNew = _whatsNewSince(config.docguardVersion);
487
+ if (whatsNew.length > 0) {
488
+ console.log(` ${c.dim}New since v${config.docguardVersion}:${c.reset}`);
489
+ for (const item of whatsNew.slice(0, 5)) {
490
+ console.log(` ${c.dim}• ${item}${c.reset}`);
491
+ }
492
+ if (whatsNew.length > 5) console.log(` ${c.dim}... ${whatsNew.length - 5} more in CHANGELOG.md${c.reset}`);
493
+ }
448
494
  }
449
495
 
450
496
  // K-6 / S-2: sweep-needed nudge. Aggregates freshness warnings — if 2+
@@ -8,6 +8,63 @@ import { resolve, join, extname } from 'node:path';
8
8
  import { execSync } from 'node:child_process';
9
9
  import { c } from '../shared.mjs';
10
10
  import { validateSecurity } from '../validators/security.mjs';
11
+ import { runGuardInternal } from './guard.mjs';
12
+
13
+ /**
14
+ * v0.18-P3: map score categories to the validator keys that contribute.
15
+ * One category can roll up multiple validators (e.g. "environment" pulls
16
+ * from Environment validator findings). When --diff fires, we use this
17
+ * to surface the underlying warnings.
18
+ */
19
+ const _SCORE_TO_VALIDATORS = {
20
+ structure: ['structure'],
21
+ docQuality: ['docQuality', 'docsCoverage', 'docsSync'],
22
+ testing: ['testSpec', 'todoTracking'],
23
+ security: ['security'],
24
+ environment: ['environment'],
25
+ drift: ['drift'],
26
+ changelog: ['changelog'],
27
+ architecture: ['architecture'],
28
+ };
29
+
30
+ function _showScoreDiff(projectDir, config, scores) {
31
+ console.log(` ${c.bold}── Drill-down (--diff) ──${c.reset}\n`);
32
+ // Pull live guard data; reuses the in-process plan cache so this is
33
+ // cheap when run right after the score calc.
34
+ const guard = runGuardInternal(projectDir, config);
35
+ const byKey = new Map(guard.validators.map(v => [v.key, v]));
36
+
37
+ let anyShown = false;
38
+ for (const [category, score] of Object.entries(scores)) {
39
+ if (score === 100) continue;
40
+ const validatorKeys = _SCORE_TO_VALIDATORS[category];
41
+ if (!validatorKeys) continue;
42
+ const warnings = [];
43
+ const errors = [];
44
+ for (const k of validatorKeys) {
45
+ const v = byKey.get(k);
46
+ if (!v) continue;
47
+ warnings.push(...(v.warnings || []));
48
+ errors.push(...(v.errors || []));
49
+ }
50
+ if (warnings.length === 0 && errors.length === 0) continue;
51
+ anyShown = true;
52
+ console.log(` ${c.yellow}${category}${c.reset} ${c.dim}(${score}/100)${c.reset}`);
53
+ for (const e of errors.slice(0, 5)) console.log(` ${c.red}✗${c.reset} ${e}`);
54
+ for (const w of warnings.slice(0, 5)) console.log(` ${c.yellow}⚠${c.reset} ${w}`);
55
+ const totalIssues = errors.length + warnings.length;
56
+ if (totalIssues > 5) console.log(` ${c.dim}... ${totalIssues - 5} more${c.reset}`);
57
+ console.log('');
58
+ }
59
+
60
+ if (!anyShown) {
61
+ console.log(` ${c.dim}No specific findings available for the weakest categories. They may be scoring below 100 due to structural/quality heuristics rather than discrete check failures.${c.reset}\n`);
62
+ } else {
63
+ console.log(` ${c.dim}Fix options:${c.reset}`);
64
+ console.log(` ${c.dim}• Run ${c.cyan}docguard explain "<warning>"${c.dim} for the full validator help on any line above${c.reset}`);
65
+ console.log(` ${c.dim}• Run ${c.cyan}docguard fix --write${c.dim} for the mechanical fixes${c.reset}`);
66
+ }
67
+ }
11
68
 
12
69
  const WEIGHTS = {
13
70
  structure: 25, // Required files exist
@@ -116,6 +173,14 @@ export function runScore(projectDir, config, flags) {
116
173
  console.log('');
117
174
  }
118
175
 
176
+ // v0.18-P3: --diff drill-down. Symmetric to v0.17 memory --diff.
177
+ // Shows WHICH specific checks dragged each weak category down by joining
178
+ // the guard validator warnings to score categories. Cheap: we already
179
+ // import runGuardInternal; one extra guard run on `--diff` is acceptable.
180
+ if (flags.diff) {
181
+ _showScoreDiff(projectDir, config, scores);
182
+ }
183
+
119
184
  // ── Tax Estimate (--tax flag) ──
120
185
  if (flags.tax) {
121
186
  const tax = estimateDocTax(projectDir, config, scores);
@@ -30,21 +30,112 @@ const md = {
30
30
  };
31
31
 
32
32
  /**
33
- * v0.15-P1: in-process cache. buildMemoryPlan is expensive (~400ms on
34
- * an enterprise client project, 33% of total guard validator time) because it triggers
35
- * routes/schemas/screens/frontend scanners — all of which walk the source
36
- * tree. Within a single guard run, sync, generate, and the Generated-
37
- * Staleness validator all ask for the SAME plan; without caching they each
38
- * re-pay the cost.
33
+ * v0.15-P1: in-process cache (Map). buildMemoryPlan is expensive (~400ms on
34
+ * an enterprise client project) because it triggers routes/schemas/screens/
35
+ * frontend scanners — all of which walk the source tree.
39
36
  *
40
- * Cache key: projectDir + a config fingerprint that captures the fields the
41
- * scanners actually consume (sourceRoot, ignore, projectType). Other config
42
- * mutations (e.g. changedFiles per-validator) don't invalidate the plan.
37
+ * v0.18-P2: cross-process cache (`.docguard/plan.cache.json`). CI flows that
38
+ * run guard sync fix as separate processes each pay the build cost.
39
+ * The disk cache shares the plan across processes, keyed by a tree-state
40
+ * hash so we invalidate when the source tree changes.
43
41
  *
44
- * Bypass with `_skipCache: true` in opts used by tests and any caller that
45
- * wants a fresh scan.
42
+ * Cache key: projectDir + a config fingerprint (sourceRoot, ignore,
43
+ * projectType, profile). Other config mutations (e.g. changedFiles
44
+ * per-validator) don't invalidate the plan.
45
+ *
46
+ * Bypass with `_skipCache: true` in opts — used by tests.
46
47
  */
48
+ import { createHash } from 'node:crypto';
49
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'node:fs';
50
+ import { resolve as resolvePath, join as joinPath } from 'node:path';
51
+ import { execFileSync } from 'node:child_process';
52
+
47
53
  const _memoryPlanCache = new Map(); // key → plan
54
+ const _DISK_CACHE_PATH = '.docguard/plan.cache.json';
55
+ const _DISK_CACHE_VERSION = '1'; // bump if cache shape changes
56
+
57
+ /**
58
+ * v0.18-P2: tree-state hash. Cheap signature of the source tree that
59
+ * changes whenever something a scanner would care about changes. We use:
60
+ * - git HEAD commit SHA (when in a git repo) — captures committed state
61
+ * - mtime sum of top-level config files (package.json, pyproject.toml,
62
+ * Cargo.toml, etc.) — captures uncommitted bumps to deps
63
+ * Combined into a 12-char hex fingerprint.
64
+ *
65
+ * NOT a perfect cache key — a user editing src/foo.ts without bumping a
66
+ * config file won't invalidate. But guard/sync/fix all run in quick
67
+ * succession within a CI step, and the user's flow IS bump + commit + run.
68
+ * The tradeoff favors speed: the worst case is one stale plan per CI run,
69
+ * recoverable with `--no-plan-cache` or a tree change.
70
+ */
71
+ function _treeStateHash(projectDir) {
72
+ let signal = '';
73
+ // git HEAD
74
+ try {
75
+ const sha = execFileSync('git', ['rev-parse', 'HEAD'], {
76
+ cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
77
+ }).trim();
78
+ signal += `git:${sha};`;
79
+ } catch { /* not a git repo, or no commits */ }
80
+ // mtime of common manifest files
81
+ const manifests = [
82
+ 'package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod',
83
+ 'pom.xml', 'build.gradle', 'Gemfile', 'composer.json',
84
+ '.docguard.json',
85
+ ];
86
+ for (const m of manifests) {
87
+ try {
88
+ const s = statSync(resolvePath(projectDir, m));
89
+ signal += `${m}:${s.mtimeMs};`;
90
+ } catch { /* not present */ }
91
+ }
92
+ return createHash('sha256').update(signal).digest('hex').slice(0, 12);
93
+ }
94
+
95
+ /**
96
+ * v0.18-P2: read the disk cache. Returns null when the file is missing,
97
+ * the schema version mismatches, the tree hash doesn't match, or anything
98
+ * about the load is suspicious. Never throws — cache miss is silent.
99
+ */
100
+ function _readDiskCache(projectDir, configKey) {
101
+ try {
102
+ const p = resolvePath(projectDir, _DISK_CACHE_PATH);
103
+ if (!existsSync(p)) return null;
104
+ const data = JSON.parse(readFileSync(p, 'utf-8'));
105
+ if (data.v !== _DISK_CACHE_VERSION) return null;
106
+ if (data.configKey !== configKey) return null;
107
+ const currentHash = _treeStateHash(projectDir);
108
+ if (data.treeHash !== currentHash) return null;
109
+ return data.plan || null;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * v0.18-P2: write the disk cache. Best-effort — failures are silent (the
117
+ * in-process cache still works). `.docguard/` directory created if needed.
118
+ */
119
+ function _writeDiskCache(projectDir, configKey, plan) {
120
+ try {
121
+ const fullDir = resolvePath(projectDir, '.docguard');
122
+ if (!existsSync(fullDir)) mkdirSync(fullDir, { recursive: true });
123
+ const payload = {
124
+ v: _DISK_CACHE_VERSION,
125
+ configKey,
126
+ treeHash: _treeStateHash(projectDir),
127
+ plan,
128
+ writtenAt: new Date().toISOString(),
129
+ };
130
+ writeFileSync(
131
+ resolvePath(projectDir, _DISK_CACHE_PATH),
132
+ JSON.stringify(payload), // compact — this file isn't human-edited
133
+ 'utf-8'
134
+ );
135
+ } catch {
136
+ // swallow — the cache is auxiliary
137
+ }
138
+ }
48
139
 
49
140
  export function clearMemoryPlanCache() {
50
141
  _memoryPlanCache.clear();
@@ -67,14 +158,35 @@ function _cacheKey(projectDir, config) {
67
158
  * agentTasks: flattened prose tasks the AI must write.
68
159
  */
69
160
  export function buildMemoryPlan(projectDir, config = {}, opts = {}) {
70
- if (!opts._skipCache) {
71
- const key = _cacheKey(projectDir, config);
161
+ const useCache = !opts._skipCache;
162
+ const key = useCache ? _cacheKey(projectDir, config) : null;
163
+
164
+ // L1: in-process Map (same-run guard → sync → fix).
165
+ if (useCache) {
72
166
  const cached = _memoryPlanCache.get(key);
73
167
  if (cached) return cached;
74
168
  }
169
+
170
+ // L2: cross-process disk cache (CI guard → CI sync → CI fix).
171
+ // v0.18-P2: opt-in via config.diskCache !== false (default ON).
172
+ // Tree-state hash invalidates when source files change.
173
+ const diskCacheEnabled = useCache && config.diskCache !== false;
174
+ if (diskCacheEnabled) {
175
+ const onDisk = _readDiskCache(projectDir, key);
176
+ if (onDisk) {
177
+ _memoryPlanCache.set(key, onDisk); // promote to L1
178
+ return onDisk;
179
+ }
180
+ }
181
+
182
+ // Miss — build fresh.
75
183
  const result = _buildMemoryPlanUncached(projectDir, config);
76
- if (!opts._skipCache) {
77
- _memoryPlanCache.set(_cacheKey(projectDir, config), result);
184
+
185
+ if (useCache) {
186
+ _memoryPlanCache.set(key, result);
187
+ }
188
+ if (diskCacheEnabled) {
189
+ _writeDiskCache(projectDir, key, result);
78
190
  }
79
191
  return result;
80
192
  }
@@ -45,16 +45,18 @@ export function validateEnvironment(projectDir, config) {
45
45
  // tokens like `VITE_` (the convention prefix) from being treated as a real
46
46
  // variable name.
47
47
  const varRe = /`([A-Z][A-Z0-9_]*[A-Z0-9])`/g;
48
- // v0.16-P4: skip backticked SYSTEM env vars (PATH, HOME, USER, etc.).
49
- // They appear in ENVIRONMENT.md prose ("the venv `PATH`") but aren't
50
- // user-set application vars. Mirrors the same skip in diff.mjs.
48
+ // v0.16-P4 (revised in v0.17.1-B7): skip backticked SYSTEM env vars
49
+ // (PATH, HOME, USER, etc.) that appear in ENVIRONMENT.md prose. Trimmed
50
+ // to TRULY-system-only after wu feedback NODE_ENV / CI / GITHUB_* were
51
+ // causing asymmetric flagging between diff and this validator. Apps
52
+ // legitimately treat NODE_ENV as app config; keep the list to vars that
53
+ // no sane application would read as runtime config.
51
54
  const SYSTEM = new Set([
52
55
  'PATH','HOME','USER','USERNAME','SHELL','PWD','OLDPWD','TMPDIR','TEMP','TMP',
53
56
  'LANG','LC_ALL','LC_CTYPE','LC_MESSAGES','TZ',
54
57
  'EDITOR','VISUAL','PAGER','TERM','COLORTERM',
55
58
  'DISPLAY','SSH_AUTH_SOCK','SSH_CONNECTION','SSH_TTY',
56
59
  'XDG_CONFIG_HOME','XDG_DATA_HOME','XDG_CACHE_HOME','XDG_RUNTIME_DIR',
57
- 'CI','GITHUB_TOKEN','GITHUB_ACTIONS','GITHUB_REF','GITHUB_SHA','NODE_ENV',
58
60
  ]);
59
61
  let m;
60
62
  while ((m = varRe.exec(content)) !== null) {
@@ -23,12 +23,55 @@
23
23
  * @req SC-M1-004 — N/A when no source=code sections present in any doc
24
24
  */
25
25
 
26
- import { existsSync, readFileSync, statSync } from 'node:fs';
27
- import { resolve, basename } from 'node:path';
26
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
27
+ import { resolve, basename, join } from 'node:path';
28
28
 
29
29
  import { buildMemoryPlan } from '../scanners/memory-plan.mjs';
30
30
  import { getSection } from '../writers/sections.mjs';
31
31
 
32
+ /**
33
+ * v0.18-P1 fast-path: cheap pre-flight to detect whether ANY canonical doc
34
+ * has a `<!-- docguard:section ... source=code -->` marker OR a `status:
35
+ * draft` frontmatter. If neither exists anywhere, this validator has
36
+ * nothing to do — skip the expensive buildMemoryPlan call (~400ms on
37
+ * mid-sized repos, was 26-33% of total guard validator time).
38
+ *
39
+ * Returns { hasMarkers, hasDrafts }.
40
+ */
41
+ function _quickScan(projectDir) {
42
+ const out = { hasMarkers: false, hasDrafts: false };
43
+ const candidateDirs = [
44
+ resolve(projectDir, 'docs-canonical'),
45
+ projectDir, // for README.md, AGENTS.md, etc.
46
+ ];
47
+ // We only need a single match in any file to know the validator has work.
48
+ // Short-circuit aggressively: stop the moment we find either signal.
49
+ for (const dir of candidateDirs) {
50
+ if (!existsSync(dir)) continue;
51
+ let entries;
52
+ try { entries = readdirSync(dir); } catch { continue; }
53
+ for (const entry of entries) {
54
+ if (!entry.endsWith('.md')) continue;
55
+ // Skip very large files quickly — for canonical docs, > 200 KB is unusual
56
+ // and almost certainly not the marker-heavy file we're looking for.
57
+ let stat;
58
+ try { stat = statSync(join(dir, entry)); } catch { continue; }
59
+ if (!stat.isFile()) continue;
60
+ if (stat.size > 200_000) continue;
61
+ let content;
62
+ try { content = readFileSync(join(dir, entry), 'utf-8'); } catch { continue; }
63
+ if (!out.hasMarkers && /<!--\s*docguard:section\s+[^>]*source=code/i.test(content)) {
64
+ out.hasMarkers = true;
65
+ }
66
+ if (!out.hasDrafts && /(?:^---\s*\n[\s\S]*?\bstatus:\s*draft\b[\s\S]*?\n---|<!--\s*status:\s*draft\s*-->)/im.test(content)) {
67
+ out.hasDrafts = true;
68
+ }
69
+ if (out.hasMarkers && out.hasDrafts) return out;
70
+ }
71
+ }
72
+ return out;
73
+ }
74
+
32
75
  /**
33
76
  * S-7: how long a generated doc may sit in `status: draft` before we warn.
34
77
  * 14 days is the v0.13.1 default — long enough to absorb a typical sprint,
@@ -62,6 +105,17 @@ export function validateGeneratedStaleness(projectDir, config = {}) {
62
105
  // of just warning. No AI needed — the scanner already knows the right body.
63
106
  const result = { errors: [], warnings: [], passed: 0, total: 0, fixes: [] };
64
107
 
108
+ // v0.18-P1: cheap pre-flight. If no canonical doc has a source=code marker
109
+ // AND no doc is in status:draft, this validator has nothing to do — skip
110
+ // the expensive buildMemoryPlan call. Generated-Staleness used to be
111
+ // 26-33% of total validator time on projects with NO markers, all
112
+ // wasted. The fast-path scans markdown files for the marker substring
113
+ // only — no parsing, no tree walk.
114
+ const quick = _quickScan(projectDir);
115
+ if (!quick.hasMarkers && !quick.hasDrafts) {
116
+ return { ...result, applicable: false, note: 'no docguard:section markers and no status:draft docs' };
117
+ }
118
+
65
119
  // Build the canonical memory plan (what the docs SHOULD contain). If this
66
120
  // fails or produces no docs, the validator is N/A.
67
121
  let plan;
@@ -3,7 +3,7 @@ schema_version: "1.0"
3
3
  extension:
4
4
  id: "docguard"
5
5
  name: "DocGuard — CDD Enforcement"
6
- version: "0.17.0"
6
+ version: "0.18.1"
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"
@@ -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.17.0
9
+ version: 0.18.1
10
10
  source: extensions/spec-kit-docguard/skills/docguard-fix
11
11
  ---
12
- <!-- docguard:version: 0.17.0 -->
12
+ <!-- docguard:version: 0.18.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.17.0
10
+ version: 0.18.1
11
11
  source: extensions/spec-kit-docguard/skills/docguard-guard
12
12
  ---
13
- <!-- docguard:version: 0.17.0 -->
13
+ <!-- docguard:version: 0.18.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.17.0
9
+ version: 0.18.1
10
10
  source: extensions/spec-kit-docguard/skills/docguard-review
11
11
  ---
12
- <!-- docguard:version: 0.17.0 -->
12
+ <!-- docguard:version: 0.18.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.17.0
9
+ version: 0.18.1
10
10
  source: extensions/spec-kit-docguard/skills/docguard-score
11
11
  ---
12
- <!-- docguard:version: 0.17.0 -->
12
+ <!-- docguard:version: 0.18.1 -->
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.17.0
7
+ version: 0.18.1
8
8
  source: extensions/spec-kit-docguard/skills/docguard-sync
9
9
  ---
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docguard-cli",
3
- "version": "0.17.0",
3
+ "version": "0.18.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": {