docguard-cli 0.11.2 → 0.12.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.
@@ -40,6 +40,56 @@ export const DEFAULT_IGNORE_DIRS = new Set([
40
40
  const ALWAYS_REJECT_PATH_RE =
41
41
  /(?:^|[/\\])(?:node_modules|\.claude[/\\]worktrees|\.git[/\\]worktrees|\.jj)(?:[/\\]|$)/;
42
42
 
43
+ /**
44
+ * Read `.docguardignore` from a project directory and return its patterns.
45
+ *
46
+ * Format: gitignore-style — one pattern per line, `#` for comments, blank lines
47
+ * ignored. Returned patterns are normalized but not transformed (callers
48
+ * decide whether to expand directory globs).
49
+ *
50
+ * Returns [] if the file is missing or unreadable — never throws.
51
+ */
52
+ import { readFileSync, existsSync } from 'node:fs';
53
+ import { resolve as resolvePath } from 'node:path';
54
+
55
+ export function loadDocguardIgnore(projectDir) {
56
+ const p = resolvePath(projectDir, '.docguardignore');
57
+ if (!existsSync(p)) return [];
58
+ try {
59
+ const raw = readFileSync(p, 'utf-8');
60
+ return raw
61
+ .split(/\r?\n/)
62
+ .map(line => line.trim())
63
+ .filter(line => line && !line.startsWith('#'));
64
+ } catch {
65
+ return [];
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Merge `.docguardignore` patterns into a config object's `ignore` array.
71
+ *
72
+ * Used at config-load time so every validator sees the combined set without
73
+ * having to know about the file. Mutates and returns the config for ergonomics.
74
+ *
75
+ * Idempotent — calling twice produces the same result. Skips duplicates.
76
+ */
77
+ export function mergeIgnoreFile(projectDir, config) {
78
+ const filePatterns = loadDocguardIgnore(projectDir);
79
+ if (filePatterns.length === 0) return config;
80
+ const existing = Array.isArray(config.ignore) ? config.ignore : [];
81
+ const seen = new Set(existing);
82
+ const merged = [...existing];
83
+ for (const p of filePatterns) {
84
+ if (!seen.has(p)) {
85
+ merged.push(p);
86
+ seen.add(p);
87
+ }
88
+ }
89
+ config.ignore = merged;
90
+ return config;
91
+ }
92
+
43
93
  /**
44
94
  * Convert a glob pattern to a RegExp.
45
95
  * Supports: * (any chars except /), ** (any path segments), . (literal dot).
package/cli/shared.mjs CHANGED
@@ -4,6 +4,68 @@
4
4
  * All commands import from here instead of docguard.mjs.
5
5
  */
6
6
 
7
+ /**
8
+ * Current .docguard.json schema version that this CLI version writes via
9
+ * `docguard init`. Bump this when adding fields that need migration (e.g.
10
+ * v0.12 adds `severity` overrides per validator).
11
+ *
12
+ * The post-guard nudge fires when an existing project's stored
13
+ * `.docguard.json.version` is BEHIND this constant — pointing users at
14
+ * `docguard upgrade` to migrate.
15
+ */
16
+ export const CURRENT_SCHEMA_VERSION = '0.5';
17
+
18
+ /**
19
+ * Allowed severity values for per-validator `severity` overrides in
20
+ * `.docguard.json`. Affects EXIT-CODE behavior of `docguard guard`:
21
+ * - 'high': warnings from this validator fail CI (exit 1)
22
+ * - 'medium': default — warnings exit 2 (informational)
23
+ * - 'low': warnings ignored for exit code (exit 0)
24
+ *
25
+ * Display (the per-validator status lines and the summary) is unchanged
26
+ * regardless of severity — severity is a CI/operational knob, not a UI one.
27
+ */
28
+ export const SEVERITY_LEVELS = new Set(['high', 'medium', 'low']);
29
+
30
+ /**
31
+ * Resolve a validator's effective severity from config.
32
+ * Returns 'medium' (default) if no override is set or the override is bogus.
33
+ */
34
+ export function resolveSeverity(config, validatorKey) {
35
+ const s = config && config.severity && config.severity[validatorKey];
36
+ if (typeof s === 'string' && SEVERITY_LEVELS.has(s.toLowerCase())) {
37
+ return s.toLowerCase();
38
+ }
39
+ return 'medium';
40
+ }
41
+
42
+ /**
43
+ * Parse a dotted-decimal version string into a tuple of integers for
44
+ * comparison. Tolerates extra suffixes (e.g. `0.4-beta` → [0, 4]).
45
+ * Returns null when the string is unparseable.
46
+ */
47
+ export function parseVersion(v) {
48
+ if (!v || typeof v !== 'string') return null;
49
+ const m = v.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
50
+ if (!m) return null;
51
+ return [Number(m[1] || 0), Number(m[2] || 0), Number(m[3] || 0)];
52
+ }
53
+
54
+ /**
55
+ * Compare two version strings. Returns -1 if a<b, 0 if equal, 1 if a>b.
56
+ * Unparseable inputs sort as equal (no nag).
57
+ */
58
+ export function compareVersions(a, b) {
59
+ const pa = parseVersion(a);
60
+ const pb = parseVersion(b);
61
+ if (!pa || !pb) return 0;
62
+ for (let i = 0; i < 3; i++) {
63
+ if (pa[i] < pb[i]) return -1;
64
+ if (pa[i] > pb[i]) return 1;
65
+ }
66
+ return 0;
67
+ }
68
+
7
69
  // ── Colors (ANSI escape codes, zero deps) ──────────────────────────────────
8
70
  export const c = {
9
71
  reset: '\x1b[0m',
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Cross-Reference Validator — S-8 / K-7
3
+ *
4
+ * Scans canonical docs for cross-references between docs and verifies they
5
+ * resolve. Catches stale links when a section is renamed or removed.
6
+ *
7
+ * Reference forms supported (in rough order of frequency):
8
+ * - Markdown relative links: [text](./OTHER.md)
9
+ * [text](./OTHER.md#anchor)
10
+ * [text](#anchor-in-same-doc)
11
+ * - Bare anchor refs: see §3.2 ARCHITECTURE.md
12
+ * (Section 3.2 in DATA-MODEL.md)
13
+ * - Bracketed section refs: [Section X.Y]
14
+ * [ARCHITECTURE.md § Components]
15
+ *
16
+ * For each ref we extract: target_file (optional), anchor (optional). Then
17
+ * we check:
18
+ * 1. target_file exists (relative to projectDir or canonical doc dir)
19
+ * 2. anchor resolves to a heading or an explicit `<a name="...">` in that file
20
+ *
21
+ * Zero NPM dependencies. Pure Node.js built-ins.
22
+ *
23
+ * @req SC-K7-001 — broken markdown links between canonical docs are reported
24
+ * @req SC-K7-002 — broken intra-doc anchors are reported
25
+ * @req SC-K7-003 — external URLs (http/https) are NOT checked (those are not our problem)
26
+ * @req SC-K7-004 — code-fenced examples don't trigger false positives
27
+ */
28
+
29
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
30
+ import { resolve, join, dirname, basename } from 'node:path';
31
+
32
+ /**
33
+ * Slugify a heading the way GitHub's markdown anchors work.
34
+ * Mirrors GFM rules: lowercase, strip non-word chars, hyphens for spaces.
35
+ * Good-enough for the 95% case; not bit-perfect.
36
+ */
37
+ export function slugifyHeading(text) {
38
+ return text
39
+ // Remove leading "## " (markdown header marker) WITHOUT trimming the
40
+ // leading whitespace that may follow it — that whitespace is the
41
+ // position where an emoji used to live and GitHub's anchor builder
42
+ // converts it to a leading dash. Example: `## ⚡ Quick Start` →
43
+ // anchor `#-quick-start` (with leading dash). Stripping it here
44
+ // would cause false-positive "broken anchor" warnings on any TOC
45
+ // generated by GitHub itself.
46
+ .replace(/^#+\s+/, '')
47
+ .toLowerCase()
48
+ // Strip emoji + decorative pictographs (they leave a position which
49
+ // becomes a dash after whitespace-collapse below).
50
+ .replace(/[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE0F}]/gu, '')
51
+ // Strip GFM-deleted punctuation
52
+ .replace(/[.,;:!?'"`()[\]{}<>|\\/]/g, '')
53
+ // Convert whitespace runs to single dashes
54
+ .replace(/\s+/g, '-')
55
+ // Drop any remaining non-word/non-dash chars
56
+ .replace(/[^a-z0-9-]/g, '');
57
+ // NB: we do NOT collapse adjacent dashes and we do NOT strip leading/
58
+ // trailing dashes — GitHub's GFM keeps them. `& Academic` (with `&`
59
+ // removed) leaves two adjacent spaces which become `--`. README TOCs
60
+ // generated by GitHub itself rely on this exact behavior.
61
+ }
62
+
63
+ /**
64
+ * Extract all headings from a markdown file. Returns an array of
65
+ * { level, text, anchor } where anchor is the GFM-style slug.
66
+ *
67
+ * Skips headings inside ``` fenced code blocks to avoid false positives
68
+ * from example code.
69
+ */
70
+ export function extractHeadings(content) {
71
+ const lines = content.split('\n');
72
+ const headings = [];
73
+ let inCodeFence = false;
74
+ for (const line of lines) {
75
+ if (line.startsWith('```')) {
76
+ inCodeFence = !inCodeFence;
77
+ continue;
78
+ }
79
+ if (inCodeFence) continue;
80
+ const m = line.match(/^(#{1,6})\s+(.+?)\s*$/);
81
+ if (m) {
82
+ const text = m[2];
83
+ headings.push({ level: m[1].length, text, anchor: slugifyHeading(text) });
84
+ }
85
+ }
86
+ return headings;
87
+ }
88
+
89
+ /**
90
+ * Extract all candidate cross-references from a markdown file.
91
+ *
92
+ * Returns an array of { source, file, anchor, raw } where:
93
+ * - source = path of the document containing the reference (for reporting)
94
+ * - file = target file path, RELATIVE to the source file's directory.
95
+ * null if the link is intra-doc (just `#anchor`).
96
+ * - anchor = anchor part without `#`. null if no anchor.
97
+ * - raw = the original text matched (for error messages)
98
+ *
99
+ * Skips:
100
+ * - External http(s) URLs (not our problem)
101
+ * - mailto: links
102
+ * - Code-fenced blocks
103
+ * - Inline `code` with backticks
104
+ */
105
+ export function extractRefs(content, sourcePath) {
106
+ const refs = [];
107
+ const lines = content.split('\n');
108
+ let inCodeFence = false;
109
+
110
+ // [text](target) where target is one of:
111
+ // ./RELATIVE.md
112
+ // ../OTHER.md#anchor
113
+ // #intra-doc-anchor
114
+ // We DON'T match http(s) targets here.
115
+ const markdownLinkRe = /\[([^\]]+)\]\(((?!https?:|mailto:)[^)]+)\)/g;
116
+
117
+ for (let i = 0; i < lines.length; i++) {
118
+ const line = lines[i];
119
+ if (line.startsWith('```')) {
120
+ inCodeFence = !inCodeFence;
121
+ continue;
122
+ }
123
+ if (inCodeFence) continue;
124
+
125
+ // Strip inline `code` so we don't match links inside it
126
+ const stripped = line.replace(/`[^`]+`/g, '');
127
+
128
+ let m;
129
+ markdownLinkRe.lastIndex = 0;
130
+ while ((m = markdownLinkRe.exec(stripped)) !== null) {
131
+ const target = m[2].trim();
132
+ // Drop any title text: [foo](bar "title") → bar
133
+ const cleanTarget = target.split(/\s+/)[0];
134
+ const hashIdx = cleanTarget.indexOf('#');
135
+ let file, anchor;
136
+ if (hashIdx === 0) {
137
+ // Intra-doc anchor only
138
+ file = null;
139
+ anchor = cleanTarget.slice(1);
140
+ } else if (hashIdx > 0) {
141
+ file = cleanTarget.slice(0, hashIdx);
142
+ anchor = cleanTarget.slice(hashIdx + 1);
143
+ } else {
144
+ file = cleanTarget;
145
+ anchor = null;
146
+ }
147
+ refs.push({ source: sourcePath, file, anchor, raw: m[0], line: i + 1 });
148
+ }
149
+ }
150
+
151
+ return refs;
152
+ }
153
+
154
+ /**
155
+ * Resolve a target file path relative to a source markdown file.
156
+ * Returns the absolute path or null if the file doesn't exist.
157
+ */
158
+ function resolveTarget(sourcePath, targetRel, projectDir) {
159
+ if (!targetRel) return null;
160
+ // Try relative to source's directory first
161
+ const fromSource = resolve(dirname(sourcePath), targetRel);
162
+ if (existsSync(fromSource)) return fromSource;
163
+ // Also try from project root (some authors write `docs-canonical/X.md`)
164
+ const fromRoot = resolve(projectDir, targetRel);
165
+ if (existsSync(fromRoot)) return fromRoot;
166
+ return null;
167
+ }
168
+
169
+ /**
170
+ * Collect canonical markdown docs to scan. Same selection logic as other
171
+ * validators — `docs-canonical/`, root tracking files, and AGENTS.md.
172
+ */
173
+ function collectCanonicalDocs(projectDir) {
174
+ const docs = [];
175
+ const cdir = resolve(projectDir, 'docs-canonical');
176
+ if (existsSync(cdir)) {
177
+ try {
178
+ for (const f of readdirSync(cdir)) {
179
+ if (f.endsWith('.md')) {
180
+ const p = join(cdir, f);
181
+ if (statSync(p).isFile()) docs.push(p);
182
+ }
183
+ }
184
+ } catch {}
185
+ }
186
+ for (const f of ['AGENTS.md', 'CHANGELOG.md', 'DRIFT-LOG.md', 'ROADMAP.md', 'README.md']) {
187
+ const p = resolve(projectDir, f);
188
+ if (existsSync(p)) docs.push(p);
189
+ }
190
+ return docs;
191
+ }
192
+
193
+ /**
194
+ * Validator entrypoint — matches the standard signature returning
195
+ * { errors, warnings, passed, total }.
196
+ */
197
+ export function validateCrossReferences(projectDir, _config = {}) {
198
+ const errors = [];
199
+ const warnings = [];
200
+ let passed = 0;
201
+ let total = 0;
202
+
203
+ const docs = collectCanonicalDocs(projectDir);
204
+ if (docs.length === 0) {
205
+ return { errors, warnings, passed, total, applicable: false };
206
+ }
207
+
208
+ // Build a map of doc path → anchor set for fast lookups during ref resolution.
209
+ const anchorIndex = new Map();
210
+ for (const d of docs) {
211
+ try {
212
+ const content = readFileSync(d, 'utf-8');
213
+ const headings = extractHeadings(content);
214
+ anchorIndex.set(d, new Set(headings.map(h => h.anchor)));
215
+ } catch {
216
+ anchorIndex.set(d, new Set());
217
+ }
218
+ }
219
+
220
+ // Walk every doc and validate each ref.
221
+ for (const docPath of docs) {
222
+ let content;
223
+ try { content = readFileSync(docPath, 'utf-8'); } catch { continue; }
224
+ const refs = extractRefs(content, docPath);
225
+ const docName = basename(docPath);
226
+
227
+ for (const ref of refs) {
228
+ total++;
229
+
230
+ // Resolve the target file (if any)
231
+ let targetPath = null;
232
+ if (ref.file) {
233
+ // Skip non-markdown files — we only validate doc cross-refs, not
234
+ // links to images / code / config files. Those have their own truth.
235
+ if (!ref.file.toLowerCase().endsWith('.md')) {
236
+ passed++;
237
+ continue;
238
+ }
239
+ targetPath = resolveTarget(docPath, ref.file, projectDir);
240
+ if (!targetPath) {
241
+ warnings.push(
242
+ `${docName}:${ref.line} — broken link: target file "${ref.file}" not found`
243
+ );
244
+ continue;
245
+ }
246
+ } else {
247
+ // Intra-doc anchor reference
248
+ targetPath = docPath;
249
+ }
250
+
251
+ // If there's an anchor, verify it resolves in the target doc.
252
+ // URL-decode the anchor first — some editors percent-encode chars
253
+ // like `️` (variation selector) in TOC links that wouldn't
254
+ // appear in the actual GitHub-rendered anchor. Decoding makes
255
+ // `#%EF%B8%8F-cicd-integration` compare against `#-cicd-integration`.
256
+ if (ref.anchor) {
257
+ let decodedAnchor;
258
+ try {
259
+ decodedAnchor = decodeURIComponent(ref.anchor);
260
+ } catch {
261
+ decodedAnchor = ref.anchor;
262
+ }
263
+ // After decode, re-apply the slug pipeline so we compare like-for-like.
264
+ const normalizedAnchor = slugifyHeading(decodedAnchor);
265
+ const anchors = anchorIndex.get(targetPath);
266
+ const matches = anchors && (anchors.has(ref.anchor) || anchors.has(normalizedAnchor));
267
+ if (!matches) {
268
+ const where = targetPath === docPath ? 'same doc' : basename(targetPath);
269
+ warnings.push(
270
+ `${docName}:${ref.line} — broken anchor: "#${ref.anchor}" in ${where} doesn't match any heading`
271
+ );
272
+ continue;
273
+ }
274
+ }
275
+
276
+ passed++;
277
+ }
278
+ }
279
+
280
+ return { errors, warnings, passed, total };
281
+ }
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Run DocGuard guard validation — check project documentation against CDD standards with 20 validators
2
+ description: Run DocGuard guard validation — check project documentation against CDD standards with 21 validators
3
3
  handoffs:
4
4
  - label: Fix All Issues
5
5
  agent: docguard.fix
@@ -23,7 +23,7 @@ Run the DocGuard CLI to validate all documentation against Canonical-Driven Deve
23
23
  npx docguard-cli guard
24
24
  ```
25
25
 
26
- 2. **Parse the output**. Each of the 20 validators reports ✅ (pass), ⚠️ (warning), ❌ (fail), or ➖ (N/A — nothing to validate). **A ➖ N/A is NOT a pass**: it means the validator found nothing to check (e.g. no API-REFERENCE.md, no DB schema, no layer boundaries declared). Don't read N/A as "healthy" — read it as "not assessed".
26
+ 2. **Parse the output**. Each of the 21 validators reports ✅ (pass), ⚠️ (warning), ❌ (fail), or ➖ (N/A — nothing to validate). **A ➖ N/A is NOT a pass**: it means the validator found nothing to check (e.g. no API-REFERENCE.md, no DB schema, no layer boundaries declared). Don't read N/A as "healthy" — read it as "not assessed".
27
27
 
28
28
  | Validator | What It Checks |
29
29
  |-----------|---------------|
@@ -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 (20 validators)
71
+ npx docguard-cli guard # Pass/fail check (21 validators)
72
72
  npx docguard-cli score # 0-100 maturity score
73
73
  ```
74
74
 
@@ -4,7 +4,7 @@ Enterprise-grade Canonical-Driven Development (CDD) enforcement and **AI-readabl
4
4
 
5
5
  ## Features
6
6
 
7
- - **20 Validators** — Structure, Security, Doc Quality, Test-Spec, Drift-Comments, API-Surface, Freshness, and 13 more
7
+ - **21 Validators** — Structure, Security, Doc Quality, Test-Spec, Drift-Comments, API-Surface, Freshness, Cross-Reference, and 13 more
8
8
  - **Language-agnostic** — JS/TS, Python, Rust, Go, Java/Kotlin, Ruby, PHP, C#. Polyglot/monorepo-aware.
9
9
  - **AI-powered Generate** — `generate --plan` builds the code-truth skeleton in `<!-- docguard:section -->` markers and emits a structured agent task manifest; the AI writes the prose.
10
10
  - **Always up to date** — `sync` surgically refreshes code-truth doc sections in place, **preserves human prose**, flags prose for agent review.
@@ -14,7 +14,7 @@ handoffs:
14
14
 
15
15
  # DocGuard Guard
16
16
 
17
- Validate your project against its canonical documentation. Runs 160+ automated checks across 20 validators.
17
+ Validate your project against its canonical documentation. Runs 160+ automated checks across 21 validators.
18
18
 
19
19
  ## User Input
20
20
 
@@ -3,7 +3,7 @@ schema_version: "1.0"
3
3
  extension:
4
4
  id: "docguard"
5
5
  name: "DocGuard — CDD Enforcement"
6
- version: "0.11.2"
6
+ version: "0.12.0"
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"
@@ -53,6 +53,16 @@ provides:
53
53
  file: "commands/generate.md"
54
54
  description: "Reverse-engineer canonical docs from existing codebase"
55
55
 
56
+ # GitHub Actions workflow starters — copyable templates users drop into
57
+ # .github/workflows/ for guard/fix/sync/score integration.
58
+ workflows:
59
+ - name: "docguard-guard"
60
+ file: "templates/github-workflows/docguard-guard.yml"
61
+ description: "Mandatory CI gate — runs all 20 validators on PR + main push"
62
+ - name: "docguard-autofix"
63
+ file: "templates/github-workflows/docguard-autofix.yml"
64
+ description: "PR-time auto-fix — applies mechanical doc fixes + comments summary"
65
+
56
66
  # Helper scripts for CI/CD and automation
57
67
  scripts:
58
68
  - name: "docguard-check-docs"
@@ -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.11.2
9
+ version: 0.12.0
10
10
  source: extensions/spec-kit-docguard/skills/docguard-fix
11
11
  ---
12
- <!-- docguard:version: 0.11.2 -->
12
+ <!-- docguard:version: 0.12.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.11.2
10
+ version: 0.12.0
11
11
  source: extensions/spec-kit-docguard/skills/docguard-guard
12
12
  ---
13
- <!-- docguard:version: 0.11.2 -->
13
+ <!-- docguard:version: 0.12.0 -->
14
14
 
15
15
  # DocGuard Guard Skill
16
16
 
@@ -139,7 +139,7 @@ For each finding, provide a **specific, actionable fix** — not "fix the issue"
139
139
 
140
140
  Based on the triage results:
141
141
 
142
- - **If all PASS**: "All 20 validators passed. Project is CDD-compliant. Ready to commit."
142
+ - **If all PASS**: "All 21 validators passed. Project is CDD-compliant. Ready to commit."
143
143
  - **If only MEDIUM/LOW warnings**: "Non-blocking warnings found. Safe to commit, but consider running `/docguard.fix` for automated remediation."
144
144
  - **If HIGH or CRITICAL failures**: "Blocking issues found. Fix these before committing. Suggest running `/docguard.fix --doc [most impactful doc]` next."
145
145
 
@@ -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.11.2
9
+ version: 0.12.0
10
10
  source: extensions/spec-kit-docguard/skills/docguard-review
11
11
  ---
12
- <!-- docguard:version: 0.11.2 -->
12
+ <!-- docguard:version: 0.12.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.11.2
9
+ version: 0.12.0
10
10
  source: extensions/spec-kit-docguard/skills/docguard-score
11
11
  ---
12
- <!-- docguard:version: 0.11.2 -->
12
+ <!-- docguard:version: 0.12.0 -->
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.11.2
7
+ version: 0.12.0
8
8
  source: extensions/spec-kit-docguard/skills/docguard-sync
9
9
  ---
10
10
 
@@ -0,0 +1,51 @@
1
+ # DocGuard Auto-Fix — applies mechanical doc fixes on every PR.
2
+ #
3
+ # What this does on each PR:
4
+ # 1. Runs `docguard fix --write` (deterministic fixes: version bumps,
5
+ # counts, endpoint removal from API-REFERENCE, changelog stubs).
6
+ # 2. If anything changed, commits the diff back to the PR branch as
7
+ # `docguard-bot` and posts a summary comment.
8
+ # 3. If nothing changed, posts a "no fixes needed" comment.
9
+ #
10
+ # Prose rewrites (entire-section regenerations) still need an AI agent —
11
+ # run `/docguard.fix` in your editor for those.
12
+ #
13
+ # Setup:
14
+ # 1. Copy this file to .github/workflows/docguard-autofix.yml
15
+ # 2. Ensure the workflow has the permissions block below (write access).
16
+ # 3. (Optional) Pin to a specific DocGuard version by changing `@main` to a tag.
17
+ #
18
+ # Security note: this workflow makes commits back to the PR branch. It refuses
19
+ # to run on PRs from forks (where pushing back is impossible by design).
20
+ name: DocGuard Auto-Fix
21
+
22
+ on:
23
+ pull_request:
24
+ types: [opened, synchronize, reopened]
25
+
26
+ permissions:
27
+ contents: write # commit fixes back to the PR branch
28
+ pull-requests: write # post the summary comment
29
+
30
+ jobs:
31
+ autofix:
32
+ runs-on: ubuntu-latest
33
+ # Skip on fork PRs — the next step would error trying to push.
34
+ if: github.event.pull_request.head.repo.full_name == github.repository
35
+ steps:
36
+ - name: Checkout PR branch (with write token)
37
+ uses: actions/checkout@v4
38
+ with:
39
+ ref: ${{ github.event.pull_request.head.ref }}
40
+ # Default GITHUB_TOKEN has the contents:write scope granted above.
41
+ # For org-protected branches or commit signing, swap in a PAT/App token.
42
+ token: ${{ secrets.GITHUB_TOKEN }}
43
+ fetch-depth: 0
44
+
45
+ - name: Run DocGuard fix --write + auto-commit + PR comment
46
+ uses: raccioly/docguard@main
47
+ with:
48
+ command: fix
49
+ auto-commit: 'true'
50
+ comment-on-pr: 'true'
51
+ commit-message: 'docs: apply DocGuard mechanical fixes'
@@ -0,0 +1,48 @@
1
+ # DocGuard Guard — runs all 20 validators on every PR and main push.
2
+ #
3
+ # This is the canonical CI gate. It does NOT modify your repo — it only
4
+ # reports. Pair with `docguard-autofix.yml` if you want mechanical fixes
5
+ # applied automatically.
6
+ #
7
+ # Setup:
8
+ # 1. Copy this file to .github/workflows/docguard-guard.yml
9
+ # 2. (Optional) Set `fail-on-warning: 'true'` to make warnings block the PR.
10
+ name: DocGuard Guard
11
+
12
+ on:
13
+ push:
14
+ branches: [main]
15
+ pull_request:
16
+ branches: [main]
17
+
18
+ permissions:
19
+ contents: read
20
+ pull-requests: write # only needed for the optional score comment below
21
+
22
+ jobs:
23
+ guard:
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ with:
28
+ fetch-depth: 0
29
+
30
+ - name: Run all validators
31
+ uses: raccioly/docguard@main
32
+ with:
33
+ command: guard
34
+ # Flip to 'true' once your repo is clean — turns warnings into hard failures.
35
+ fail-on-warning: 'false'
36
+
37
+ # Optional: post the CDD score on every PR as a tracked metric.
38
+ score:
39
+ runs-on: ubuntu-latest
40
+ if: github.event_name == 'pull_request'
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+
44
+ - name: Score & comment
45
+ uses: raccioly/docguard@main
46
+ with:
47
+ command: score
48
+ format: json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docguard-cli",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
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": {