docguard-cli 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -3
- package/cli/commands/hooks.mjs +167 -2
- package/cli/commands/impact.mjs +213 -5
- package/cli/commands/mcp.mjs +179 -53
- package/cli/docguard.mjs +46 -3
- package/cli/findings.mjs +6 -0
- package/cli/scanners/agent-readability.mjs +6 -1
- package/cli/scanners/semantic-claims.mjs +10 -2
- package/cli/validators/architecture.mjs +8 -1
- package/cli/validators/cross-reference.mjs +124 -3
- package/cli/validators/reference-existence.mjs +172 -18
- package/cli/validators/traceability.mjs +63 -0
- package/extensions/spec-kit-docguard/extension.yml +1 -1
- package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-sync/SKILL.md +2 -2
- package/package.json +1 -1
|
@@ -27,14 +27,24 @@
|
|
|
27
27
|
* mode (b) ("literal deleted but logic remains") is bounded by compound
|
|
28
28
|
* shape + the two-revision gate.
|
|
29
29
|
* All findings are confidence:'low' / soft — "verify", never a hard failure.
|
|
30
|
+
*
|
|
31
|
+
* REF002 (ADR citations, the code→doc direction): a code comment citing
|
|
32
|
+
* `ADR-NNN` is a reference into the docs — if no ADR document defines that
|
|
33
|
+
* number, the citation is stale (renumbered, removed, or never written).
|
|
34
|
+
* ADRs have no external registry, so a missing number is a real signal.
|
|
35
|
+
* RFC citations are deliberately OUT of scope: `RFC 793` in a comment almost
|
|
36
|
+
* always cites the IETF registry (external, unverifiable) and would
|
|
37
|
+
* false-positive on every network stack. Numbers compare as integers, so
|
|
38
|
+
* `ADR-00NN` in code matches `ADR-NN` in docs.
|
|
30
39
|
*/
|
|
31
40
|
|
|
32
41
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
33
|
-
import { resolve, extname } from 'node:path';
|
|
42
|
+
import { resolve, extname, relative, basename } from 'node:path';
|
|
34
43
|
import { isGitRepo, lastCommitHash, symbolExistsAtRev } from '../shared-git.mjs';
|
|
35
|
-
import { walkFiles } from '../shared-ignore.mjs';
|
|
44
|
+
import { walkFiles, isNonProductPath } from '../shared-ignore.mjs';
|
|
36
45
|
import { readScannable } from '../shared-source.mjs';
|
|
37
|
-
import {
|
|
46
|
+
import { resolveDocDirs } from '../shared.mjs';
|
|
47
|
+
import { mkFinding, resultFromFindings, lineSuppresses } from '../findings.mjs';
|
|
38
48
|
|
|
39
49
|
const CODE_EXT = new Set([
|
|
40
50
|
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.go', '.rs', '.java',
|
|
@@ -51,17 +61,93 @@ function isCodeIdentifier(s) {
|
|
|
51
61
|
return COMPOUND.test(s);
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
// ── REF002 helpers ──────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
// Uppercase-only by design: teams cite decision records as "ADR-NNN" / "ADR NN";
|
|
67
|
+
// lowercase "adr" is too often an abbreviation for something else.
|
|
68
|
+
const ADR_CITE_SRC = String.raw`\bADR[- ]?(\d{1,5})\b`;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Index of the comment portion of a code line, or -1 when the line has no
|
|
72
|
+
* comment. A citation only counts when it sits inside a comment — `ADR-NN`
|
|
73
|
+
* in a string literal or identifier is data, not a citation. Whole-line
|
|
74
|
+
* comments (incl. `*` block-comment continuations) count from column 0;
|
|
75
|
+
* trailing comments are recognised by their marker. ` # ` requires spacing so
|
|
76
|
+
* `"#fff"`-style literals don't read as Python/shell comments.
|
|
77
|
+
*/
|
|
78
|
+
function commentIndex(line) {
|
|
79
|
+
const t = line.trimStart();
|
|
80
|
+
if (/^(\/\/|\/\*|\*|#|--|<!--)/.test(t)) return 0;
|
|
81
|
+
let idx = -1;
|
|
82
|
+
for (const marker of ['//', '/*', '<!--', ' # ', ' -- ']) {
|
|
83
|
+
const i = line.indexOf(marker);
|
|
84
|
+
if (i >= 0 && (idx < 0 || i < idx)) idx = i;
|
|
85
|
+
}
|
|
86
|
+
return idx;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Collect ADR citations found in comments of one source file. */
|
|
90
|
+
function collectAdrCitations(content, relPath, out) {
|
|
91
|
+
const lines = content.split('\n');
|
|
92
|
+
for (let i = 0; i < lines.length; i++) {
|
|
93
|
+
const line = lines[i];
|
|
94
|
+
if (!line.includes('ADR')) continue;
|
|
95
|
+
const ci = commentIndex(line);
|
|
96
|
+
if (ci < 0) continue;
|
|
97
|
+
const re = new RegExp(ADR_CITE_SRC, 'g'); // local: shared stateful g-regexes are a footgun
|
|
98
|
+
let m;
|
|
99
|
+
while ((m = re.exec(line)) !== null) {
|
|
100
|
+
if (m.index < ci) continue;
|
|
101
|
+
if (lineSuppresses('REF002', line, lines[i - 1] || '')) continue;
|
|
102
|
+
out.push({ num: parseInt(m[1], 10), raw: m[0], file: relPath, line: i + 1 });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Discover which ADR numbers the project's docs actually define.
|
|
109
|
+
* Sources, in decreasing specificity:
|
|
110
|
+
* - files/dirs named after ADRs (`ADR.md`, `docs/adr/`, `docs/decisions/`):
|
|
111
|
+
* every ADR-NNN mention counts, plus madr-style numeric filenames
|
|
112
|
+
* (`0001-use-postgres.md`);
|
|
113
|
+
* - any other markdown in a doc home: heading lines only (`## ADR-NNN: …`),
|
|
114
|
+
* so a prose mention ("see ADR-NN") never counts as a definition.
|
|
115
|
+
*/
|
|
116
|
+
function collectAdrNumbers(projectDir, config) {
|
|
117
|
+
const nums = new Set();
|
|
118
|
+
const addAll = (text) => {
|
|
119
|
+
for (const m of text.matchAll(new RegExp(ADR_CITE_SRC, 'g'))) nums.add(parseInt(m[1], 10));
|
|
120
|
+
};
|
|
121
|
+
const files = new Set();
|
|
122
|
+
try {
|
|
123
|
+
for (const f of readdirSync(projectDir)) {
|
|
124
|
+
if (f.endsWith('.md')) files.add(resolve(projectDir, f));
|
|
125
|
+
}
|
|
126
|
+
} catch { /* unreadable root */ }
|
|
127
|
+
for (const d of resolveDocDirs(projectDir, config)) {
|
|
128
|
+
const abs = resolve(projectDir, d);
|
|
129
|
+
if (!existsSync(abs)) continue;
|
|
130
|
+
walkFiles(abs, (full) => { if (full.endsWith('.md')) files.add(full); });
|
|
131
|
+
}
|
|
132
|
+
for (const full of files) {
|
|
133
|
+
let content;
|
|
134
|
+
try { content = readFileSync(full, 'utf-8'); } catch { continue; }
|
|
135
|
+
const base = basename(full);
|
|
136
|
+
const norm = full.replace(/\\/g, '/');
|
|
137
|
+
const isAdrHome = /adr/i.test(base) || /\/(adrs?|decisions?)\//i.test(norm);
|
|
138
|
+
const numericName = base.match(/^(\d{1,5})[-_.]/);
|
|
139
|
+
if (isAdrHome && numericName) nums.add(parseInt(numericName[1], 10));
|
|
140
|
+
const adrInName = base.match(/adr[-_ ]?(\d{1,5})/i);
|
|
141
|
+
if (adrInName) nums.add(parseInt(adrInName[1], 10));
|
|
142
|
+
if (isAdrHome) {
|
|
143
|
+
addAll(content);
|
|
144
|
+
} else {
|
|
145
|
+
for (const line of content.split('\n')) {
|
|
146
|
+
if (/^#{1,6}\s/.test(line)) addAll(line);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return nums;
|
|
65
151
|
}
|
|
66
152
|
|
|
67
153
|
function extractRefs(content) {
|
|
@@ -100,16 +186,44 @@ function indexDocs(projectDir) {
|
|
|
100
186
|
export function validateReferenceExistence(projectDir, config = {}) {
|
|
101
187
|
const cfg = config.referenceExistence || {};
|
|
102
188
|
const maxRefsPerDoc = cfg.maxRefsPerDoc || 80;
|
|
189
|
+
const adrEnabled = cfg.adrCitations !== false;
|
|
103
190
|
|
|
104
191
|
if (!isGitRepo(projectDir)) {
|
|
105
192
|
return resultFromFindings([], { passed: 0, total: 0, applicable: false });
|
|
106
193
|
}
|
|
107
194
|
const docs = indexDocs(projectDir);
|
|
108
|
-
|
|
195
|
+
const needIds = docs.some(d => d.refs.length > 0);
|
|
196
|
+
if (!needIds && !adrEnabled) {
|
|
197
|
+
return resultFromFindings([], { passed: 0, total: 0, applicable: false });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ONE walk over the source tree serves both checks: the REF001 identifier
|
|
201
|
+
// set and the REF002 ADR-citation scan. Each part is skipped when its check
|
|
202
|
+
// has nothing to do, so the walk stays as cheap as before for either alone.
|
|
203
|
+
const headIds = new Set();
|
|
204
|
+
const citations = [];
|
|
205
|
+
walkFiles(projectDir, (full) => {
|
|
206
|
+
if (!CODE_EXT.has(extname(full))) return;
|
|
207
|
+
const content = readScannable(full);
|
|
208
|
+
if (!content) return;
|
|
209
|
+
if (needIds) {
|
|
210
|
+
const m = content.match(IDENT);
|
|
211
|
+
if (m) for (const id of m) headIds.add(id);
|
|
212
|
+
}
|
|
213
|
+
if (adrEnabled && content.includes('ADR')) {
|
|
214
|
+
// Tests/fixtures/examples cite ADRs as fixture data, not as real
|
|
215
|
+
// citations — same non-product scoping the surface scanners use.
|
|
216
|
+
const rel = relative(projectDir, full);
|
|
217
|
+
if (!isNonProductPath(rel.replace(/\\/g, '/'), config)) {
|
|
218
|
+
collectAdrCitations(content, rel, citations);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (!needIds && citations.length === 0) {
|
|
109
224
|
return resultFromFindings([], { passed: 0, total: 0, applicable: false });
|
|
110
225
|
}
|
|
111
226
|
|
|
112
|
-
const headIds = buildHeadIdentifierSet(projectDir); // cheap gate, built once
|
|
113
227
|
const revPresence = new Map(); // `${sym}\0${rev}` → bool
|
|
114
228
|
// Authoritative HEAD absence (covers dot-dirs the walk skipped). Cached.
|
|
115
229
|
const headAbsent = new Map();
|
|
@@ -147,9 +261,49 @@ export function validateReferenceExistence(projectDir, config = {}) {
|
|
|
147
261
|
}
|
|
148
262
|
}
|
|
149
263
|
|
|
264
|
+
// ── REF002: every distinct cited ADR number is one check ──
|
|
265
|
+
const MAX_ADR_FINDINGS = 10; // calm cap — a flood means a systemic numbering change, not 40 separate problems
|
|
266
|
+
let adrDistinct = 0;
|
|
267
|
+
let adrMissing = 0;
|
|
268
|
+
if (adrEnabled && citations.length > 0) {
|
|
269
|
+
const known = collectAdrNumbers(projectDir, config);
|
|
270
|
+
const byNum = new Map(); // num → [{raw, file, line}]
|
|
271
|
+
for (const c of citations) {
|
|
272
|
+
if (!byNum.has(c.num)) byNum.set(c.num, []);
|
|
273
|
+
byNum.get(c.num).push(c);
|
|
274
|
+
}
|
|
275
|
+
adrDistinct = byNum.size;
|
|
276
|
+
for (const [num, locs] of byNum) {
|
|
277
|
+
if (known.has(num)) continue;
|
|
278
|
+
adrMissing++;
|
|
279
|
+
if (adrMissing > MAX_ADR_FINDINGS) continue;
|
|
280
|
+
const first = locs[0];
|
|
281
|
+
const where = `${first.file}:${first.line}${locs.length > 1 ? ` (+${locs.length - 1} more)` : ''}`;
|
|
282
|
+
const message = known.size > 0
|
|
283
|
+
? `Code cites ${first.raw} (${where}) but no ADR document defines that number — renumbered, removed, or never written.`
|
|
284
|
+
: `Code cites ${first.raw} (${where}) but the repo has no ADR documents — the decision record it points to is missing.`;
|
|
285
|
+
findings.push(mkFinding({
|
|
286
|
+
code: 'REF002',
|
|
287
|
+
validator: 'reference-existence',
|
|
288
|
+
severity: 'warn',
|
|
289
|
+
confidence: 'low',
|
|
290
|
+
message,
|
|
291
|
+
location: { file: first.file, line: first.line },
|
|
292
|
+
suggestion: {
|
|
293
|
+
summary: known.size > 0
|
|
294
|
+
? `Fix the number or write the missing ADR entry (or suppress with // docguard:ignore REF002 on the citation line).`
|
|
295
|
+
: `Create an ADR doc (docguard init writes templates/ADR.md) or suppress with // docguard:ignore REF002.`,
|
|
296
|
+
},
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// A missing number beyond the finding cap is still a failed check.
|
|
302
|
+
const total = totalChecked + adrDistinct;
|
|
303
|
+
const overCap = Math.max(0, adrMissing - MAX_ADR_FINDINGS);
|
|
150
304
|
const res = resultFromFindings(findings, {
|
|
151
|
-
passed:
|
|
152
|
-
total
|
|
305
|
+
passed: total - findings.length - overCap,
|
|
306
|
+
total,
|
|
153
307
|
applicable: true,
|
|
154
308
|
});
|
|
155
309
|
res.absentAtHead = absentAtHead; // instrumentation: proves the pipeline reaches the rev check
|
|
@@ -21,6 +21,56 @@ import { mkFinding, resultFromFindings } from '../findings.mjs';
|
|
|
21
21
|
import { tokenize } from '../shared-diff.mjs';
|
|
22
22
|
import { rankBySimilarity } from '../shared-ir.mjs';
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Optional graphify interop (github.com/Graphify-Labs/graphify, MIT).
|
|
26
|
+
* Teams that commit `graphify-out/graph.json` already carry a knowledge graph
|
|
27
|
+
* whose CODE side is deterministic tree-sitter extraction. If it's there, its
|
|
28
|
+
* doc↔code edges are one more linkage-evidence source for traceability.
|
|
29
|
+
*
|
|
30
|
+
* Trust rules (the determinism anchor):
|
|
31
|
+
* - ONLY edges tagged confidence:"EXTRACTED" count — INFERRED/AMBIGUOUS
|
|
32
|
+
* edges can come from graphify's LLM pass and must not vouch for a doc.
|
|
33
|
+
* - Evidence-only: this can turn a would-be "unlinked doc" into a pass;
|
|
34
|
+
* it never produces a finding.
|
|
35
|
+
* - Zero-dep: one JSON read. Any parse/shape mismatch → null (no evidence).
|
|
36
|
+
*
|
|
37
|
+
* @returns {Map<string, Set<string>>|null} doc basename → linked code files
|
|
38
|
+
*/
|
|
39
|
+
function loadGraphifyDocLinks(projectDir) {
|
|
40
|
+
const p = resolve(projectDir, 'graphify-out', 'graph.json');
|
|
41
|
+
if (!existsSync(p)) return null;
|
|
42
|
+
try {
|
|
43
|
+
const raw = JSON.parse(readFileSync(p, 'utf-8'));
|
|
44
|
+
const nodes = Array.isArray(raw.nodes) ? raw.nodes : [];
|
|
45
|
+
// networkx serializes edges as "links"; older graphify exports used "edges".
|
|
46
|
+
const links = Array.isArray(raw.links) ? raw.links
|
|
47
|
+
: Array.isArray(raw.edges) ? raw.edges : [];
|
|
48
|
+
const nodeFile = new Map(); // node id → source_file
|
|
49
|
+
for (const n of nodes) {
|
|
50
|
+
if (n && n.id !== undefined && typeof n.source_file === 'string') {
|
|
51
|
+
nodeFile.set(n.id, n.source_file);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const docLinks = new Map();
|
|
55
|
+
for (const l of links) {
|
|
56
|
+
if (!l || l.confidence !== 'EXTRACTED') continue;
|
|
57
|
+
const sf = nodeFile.get(l.source);
|
|
58
|
+
const tf = nodeFile.get(l.target);
|
|
59
|
+
if (!sf || !tf) continue;
|
|
60
|
+
for (const [a, b] of [[sf, tf], [tf, sf]]) {
|
|
61
|
+
if (a.endsWith('.md') && !b.endsWith('.md') && isTraceableSource(b)) {
|
|
62
|
+
const doc = basename(a);
|
|
63
|
+
if (!docLinks.has(doc)) docLinks.set(doc, new Set());
|
|
64
|
+
docLinks.get(doc).add(b);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return docLinks.size > 0 ? docLinks : null;
|
|
69
|
+
} catch {
|
|
70
|
+
return null; // malformed graph = no evidence, never a finding
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
24
74
|
// IR soft-link recovery (feat 5): tokenize test files once so an untraced
|
|
25
75
|
// requirement can be matched to the test that most likely already covers it
|
|
26
76
|
// (TF-IDF cosine, VSM). Capped so a huge test suite can't blow up guard.
|
|
@@ -98,6 +148,10 @@ export function validateTraceability(projectDir, config) {
|
|
|
98
148
|
const projectFiles = [];
|
|
99
149
|
scanDir(projectDir, projectDir, projectFiles);
|
|
100
150
|
|
|
151
|
+
// Optional graphify interop: a committed knowledge graph is one more
|
|
152
|
+
// deterministic evidence source for doc↔code linkage.
|
|
153
|
+
const graphifyLinks = loadGraphifyDocLinks(projectDir);
|
|
154
|
+
|
|
101
155
|
// Scan source files for `// @doc <filename>.md` annotations. An annotation
|
|
102
156
|
// is an explicit author signal that a source file documents (or is
|
|
103
157
|
// documented by) a canonical doc. It is the user-facing escape hatch when
|
|
@@ -149,6 +203,15 @@ export function validateTraceability(projectDir, config) {
|
|
|
149
203
|
}
|
|
150
204
|
}
|
|
151
205
|
|
|
206
|
+
// Graphify interop: an EXTRACTED doc↔code edge in a committed
|
|
207
|
+
// graphify-out/graph.json is author-grade linkage evidence (the graph's
|
|
208
|
+
// code side is deterministic AST extraction). Only trusted when at least
|
|
209
|
+
// one linked code file still exists — a stale graph must not vouch.
|
|
210
|
+
if (!hasSource && graphifyLinks && graphifyLinks.has(docName)) {
|
|
211
|
+
hasSource = [...graphifyLinks.get(docName)]
|
|
212
|
+
.some(f => existsSync(resolve(projectDir, f)) || existsSync(f));
|
|
213
|
+
}
|
|
214
|
+
|
|
152
215
|
if (hasSource) {
|
|
153
216
|
passed++;
|
|
154
217
|
} else {
|
|
@@ -3,7 +3,7 @@ schema_version: "1.0"
|
|
|
3
3
|
extension:
|
|
4
4
|
id: "docguard"
|
|
5
5
|
name: "DocGuard — CDD Enforcement"
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.32.0"
|
|
7
7
|
description: "Canonical-Driven Development enforcement as a true spec-kit extension. LLM-first design with automated validators, 4 AI behavior skills, spec-kit skill chaining, and workflow hooks. One pinned runtime dependency (@babel/parser); pure Node.js otherwise."
|
|
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.
|
|
9
|
+
version: 0.32.0
|
|
10
10
|
source: extensions/spec-kit-docguard/skills/docguard-fix
|
|
11
11
|
---
|
|
12
|
-
<!-- docguard:version: 0.
|
|
12
|
+
<!-- docguard:version: 0.32.0 -->
|
|
13
13
|
|
|
14
14
|
# DocGuard Fix Skill
|
|
15
15
|
|
|
@@ -7,10 +7,10 @@ description: Run DocGuard guard validation against Canonical-Driven Development
|
|
|
7
7
|
compatibility: Requires DocGuard CLI installed (npm i -g docguard-cli or npx docguard-cli)
|
|
8
8
|
metadata:
|
|
9
9
|
author: docguard
|
|
10
|
-
version: 0.
|
|
10
|
+
version: 0.32.0
|
|
11
11
|
source: extensions/spec-kit-docguard/skills/docguard-guard
|
|
12
12
|
---
|
|
13
|
-
<!-- docguard:version: 0.
|
|
13
|
+
<!-- docguard:version: 0.32.0 -->
|
|
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.
|
|
9
|
+
version: 0.32.0
|
|
10
10
|
source: extensions/spec-kit-docguard/skills/docguard-review
|
|
11
11
|
---
|
|
12
|
-
<!-- docguard:version: 0.
|
|
12
|
+
<!-- docguard:version: 0.32.0 -->
|
|
13
13
|
|
|
14
14
|
# DocGuard Review Skill
|
|
15
15
|
|
|
@@ -6,10 +6,10 @@ description: CDD maturity assessment with category-aware improvement roadmap. Ru
|
|
|
6
6
|
compatibility: Requires DocGuard CLI installed (npm i -g docguard-cli or npx docguard-cli)
|
|
7
7
|
metadata:
|
|
8
8
|
author: docguard
|
|
9
|
-
version: 0.
|
|
9
|
+
version: 0.32.0
|
|
10
10
|
source: extensions/spec-kit-docguard/skills/docguard-score
|
|
11
11
|
---
|
|
12
|
-
<!-- docguard:version: 0.
|
|
12
|
+
<!-- docguard:version: 0.32.0 -->
|
|
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.
|
|
7
|
+
version: 0.32.0
|
|
8
8
|
source: extensions/spec-kit-docguard/skills/docguard-sync
|
|
9
9
|
---
|
|
10
|
-
<!-- docguard:version: 0.
|
|
10
|
+
<!-- docguard:version: 0.32.0 -->
|
|
11
11
|
|
|
12
12
|
# DocGuard Sync Skill
|
|
13
13
|
|
package/package.json
CHANGED