docguard-cli 0.9.8 → 0.9.10

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.
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared Ignore Utility — Unified file filtering for all validators.
3
+ *
4
+ * Provides consistent glob matching for config ignore arrays:
5
+ * - config.ignore (global — all validators)
6
+ * - config.securityIgnore (security validator only)
7
+ * - config.todoIgnore (TODO-tracking validator only)
8
+ *
9
+ * Supports exact paths AND glob patterns:
10
+ * - "src/foo.ts" → exact match
11
+ * - "packages/cdk/**" → match any file under packages/cdk/
12
+ * - "backend/src/__tests__/**" → match any file under that path
13
+ * - "*.test.ts" → match files ending in .test.ts
14
+ *
15
+ * Zero NPM dependencies — pure Node.js built-ins only.
16
+ */
17
+
18
+ /**
19
+ * Convert a glob pattern to a RegExp.
20
+ * Supports: * (any chars except /), ** (any path segments), . (literal dot).
21
+ *
22
+ * @param {string} pattern - Glob pattern
23
+ * @returns {RegExp}
24
+ */
25
+ function globToRegex(pattern) {
26
+ const escaped = pattern
27
+ .replace(/\./g, '\\.')
28
+ .replace(/\*\*/g, '§§') // temp placeholder for **
29
+ .replace(/\*/g, '[^/]*')
30
+ .replace(/§§/g, '.*');
31
+ // Match if the relative path:
32
+ // - equals the pattern exactly
33
+ // - ends with /pattern
34
+ // - starts with pattern/
35
+ // - contains /pattern/
36
+ return new RegExp(`^${escaped}$|/${escaped}$|^${escaped}/|/${escaped}/`);
37
+ }
38
+
39
+ /**
40
+ * Build a filter function from an array of glob patterns.
41
+ * Returns a function that returns true if a relative path should be SKIPPED.
42
+ *
43
+ * @param {string[]} patterns - Glob patterns (from config.ignore, config.securityIgnore, etc.)
44
+ * @returns {(relPath: string) => boolean} - true if file should be ignored
45
+ */
46
+ export function buildIgnoreFilter(patterns = []) {
47
+ if (!patterns || patterns.length === 0) return () => false;
48
+
49
+ const regexes = patterns.map(p => globToRegex(p));
50
+ return (relPath) => regexes.some(regex => regex.test(relPath));
51
+ }
52
+
53
+ /**
54
+ * Check if a relative path should be ignored by BOTH
55
+ * global ignore + validator-specific ignore.
56
+ *
57
+ * @param {string} relPath - Relative file path (e.g., "backend/src/__tests__/foo.test.ts")
58
+ * @param {object} config - DocGuard config object
59
+ * @param {string} [validatorKey] - Optional validator-specific key (e.g., 'securityIgnore', 'todoIgnore')
60
+ * @returns {boolean} - true if file should be skipped
61
+ */
62
+ export function shouldIgnore(relPath, config, validatorKey) {
63
+ // Check global ignore
64
+ if (config.ignore && config.ignore.length > 0) {
65
+ const globalFilter = buildIgnoreFilter(config.ignore);
66
+ if (globalFilter(relPath)) return true;
67
+ }
68
+
69
+ // Check validator-specific ignore
70
+ if (validatorKey && config[validatorKey] && config[validatorKey].length > 0) {
71
+ const validatorFilter = buildIgnoreFilter(config[validatorKey]);
72
+ if (validatorFilter(relPath)) return true;
73
+ }
74
+
75
+ return false;
76
+ }
@@ -10,10 +10,14 @@
10
10
  * - Circular dependencies (A → B → A)
11
11
  * - Layer boundary violations (routes importing from routes, etc.)
12
12
  * - Orphan modules (code files with 0 inbound imports)
13
+ *
14
+ * Respects config.ignore (global) for file filtering.
15
+ * Uses shared-ignore.mjs for consistent filtering (Constitution IV, v1.1.0).
13
16
  */
14
17
 
15
18
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
16
19
  import { resolve, join, extname, relative, dirname, basename } from 'node:path';
20
+ import { shouldIgnore } from '../shared-ignore.mjs';
17
21
 
18
22
  const IGNORE_DIRS = new Set([
19
23
  'node_modules', '.git', '.next', 'dist', 'build',
@@ -33,7 +37,7 @@ export function validateArchitecture(projectDir, config) {
33
37
  }
34
38
 
35
39
  // ── 2. Auto-detect import graph ──
36
- const importGraph = buildImportGraph(projectDir);
40
+ const importGraph = buildImportGraph(projectDir, config);
37
41
  if (importGraph.files.length === 0) return results;
38
42
 
39
43
  // ── 3. Detect circular dependencies ──
@@ -84,7 +88,7 @@ function validateConfigLayers(projectDir, config, layers, results) {
84
88
  const layerDir = resolve(projectDir, dir);
85
89
  if (!existsSync(layerDir)) continue;
86
90
 
87
- const files = getFilesRecursive(layerDir);
91
+ const files = getFilesRecursive(layerDir, config, projectDir);
88
92
  for (const file of files) {
89
93
  if (!CODE_EXTENSIONS.has(extname(file))) continue;
90
94
 
@@ -110,14 +114,18 @@ function validateConfigLayers(projectDir, config, layers, results) {
110
114
 
111
115
  // ── Import Graph Builder ────────────────────────────────────────────────────
112
116
 
113
- function buildImportGraph(projectDir) {
117
+ function buildImportGraph(projectDir, config) {
114
118
  const graph = { files: [], edges: [], fileMap: new Map() };
115
119
 
116
- const allFiles = getFilesRecursive(projectDir);
120
+ const allFiles = getFilesRecursive(projectDir, config, projectDir);
117
121
  const codeFiles = allFiles.filter(f => CODE_EXTENSIONS.has(extname(f)));
118
122
 
119
123
  for (const file of codeFiles) {
120
124
  const relPath = relative(projectDir, file);
125
+
126
+ // Skip files in ignored directories (config.ignore)
127
+ if (config && shouldIgnore(relPath, config)) continue;
128
+
121
129
  graph.files.push(relPath);
122
130
 
123
131
  try {
@@ -355,7 +363,7 @@ function getFileLayer(filePath, layerDirMap) {
355
363
 
356
364
  // ── Utilities ───────────────────────────────────────────────────────────────
357
365
 
358
- function getFilesRecursive(dir) {
366
+ function getFilesRecursive(dir, config, projectDir) {
359
367
  const results = [];
360
368
  if (!existsSync(dir)) return results;
361
369
 
@@ -366,11 +374,18 @@ function getFilesRecursive(dir) {
366
374
 
367
375
  for (const entry of entries) {
368
376
  if (IGNORE_DIRS.has(entry) || entry.startsWith('.')) continue;
377
+
378
+ // Check config.ignore for this directory
379
+ if (config && projectDir) {
380
+ const relPath = relative(projectDir, join(dir, entry));
381
+ if (shouldIgnore(relPath, config)) continue;
382
+ }
383
+
369
384
  const fullPath = join(dir, entry);
370
385
  try {
371
386
  const stat = statSync(fullPath);
372
387
  if (stat.isDirectory()) {
373
- results.push(...getFilesRecursive(fullPath));
388
+ results.push(...getFilesRecursive(fullPath, config, projectDir));
374
389
  } else {
375
390
  results.push(fullPath);
376
391
  }
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * Optional: If `understanding` CLI is installed, runs a full 31-metric deep scan.
20
20
  *
21
- * Zero dependencies — pure Node.js built-ins only.
21
+ * Zero NPM runtime dependencies — pure Node.js built-ins only.
22
22
  */
23
23
 
24
24
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
@@ -4,10 +4,14 @@
4
4
  * Runs as part of `docguard guard` on every invocation.
5
5
  * Detects undocumented code artifacts and documented items not found in code.
6
6
  * Returns warnings (not errors) since drift is a soft signal.
7
+ *
8
+ * Respects config.ignore and config.testPatterns for test file discovery.
9
+ * Uses shared-ignore.mjs for consistent filtering (Constitution IV, v1.1.0).
7
10
  */
8
11
 
9
12
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
10
- import { resolve, join, extname, basename } from 'node:path';
13
+ import { resolve, join, extname, basename, relative } from 'node:path';
14
+ import { shouldIgnore, buildIgnoreFilter } from '../shared-ignore.mjs';
11
15
 
12
16
  const IGNORE_DIRS = new Set([
13
17
  'node_modules', '.git', '.next', 'dist', 'build',
@@ -32,7 +36,7 @@ export function validateDocsDiff(projectDir, config) {
32
36
  const checks = [
33
37
  diffTechStack(projectDir),
34
38
  diffEnvVars(projectDir),
35
- diffTests(projectDir),
39
+ diffTests(projectDir, config),
36
40
  ];
37
41
 
38
42
  for (const result of checks) {
@@ -131,7 +135,13 @@ function diffEnvVars(dir) {
131
135
  };
132
136
  }
133
137
 
134
- function diffTests(dir) {
138
+ /**
139
+ * Diff test files between TEST-SPEC.md and actual code.
140
+ * Uses config.testPatterns if available, otherwise falls back to
141
+ * scanning standard test directories.
142
+ * Always ignores node_modules via shared ignore filter.
143
+ */
144
+ function diffTests(dir, config) {
135
145
  const testSpecPath = resolve(dir, 'docs-canonical/TEST-SPEC.md');
136
146
  if (!existsSync(testSpecPath)) return null;
137
147
 
@@ -144,13 +154,30 @@ function diffTests(dir) {
144
154
  }
145
155
 
146
156
  const codeTests = new Set();
147
- const testDirs = ['tests', 'test', '__tests__', 'spec', 'e2e'];
148
- for (const td of testDirs) {
149
- const testDir = resolve(dir, td);
150
- if (!existsSync(testDir)) continue;
151
- const files = getFilesRecursive(testDir);
152
- for (const f of files) {
153
- codeTests.add(f.replace(dir + '/', ''));
157
+
158
+ // Use testPatterns from config if available
159
+ const testPatterns = config?.testPatterns || [];
160
+ if (testPatterns.length > 0) {
161
+ // Use configured patterns to find test files
162
+ const patternFilter = buildIgnoreFilter(testPatterns.map(p => {
163
+ // Invert the pattern: we WANT files matching these patterns
164
+ return p;
165
+ }));
166
+ // Walk the project and collect matching test files
167
+ const allTestFiles = getTestFilesFromPatterns(dir, testPatterns, config);
168
+ for (const f of allTestFiles) {
169
+ codeTests.add(f);
170
+ }
171
+ } else {
172
+ // Fall back to standard test directories
173
+ const testDirs = ['tests', 'test', '__tests__', 'spec', 'e2e'];
174
+ for (const td of testDirs) {
175
+ const testDir = resolve(dir, td);
176
+ if (!existsSync(testDir)) continue;
177
+ const files = getFilesRecursive(testDir, config);
178
+ for (const f of files) {
179
+ codeTests.add(f.replace(dir + '/', ''));
180
+ }
154
181
  }
155
182
  }
156
183
 
@@ -163,7 +190,47 @@ function diffTests(dir) {
163
190
  };
164
191
  }
165
192
 
166
- function getFilesRecursive(dir) {
193
+ /**
194
+ * Find test files matching configured testPatterns.
195
+ * Walks the project tree, skipping node_modules and ignored dirs.
196
+ */
197
+ function getTestFilesFromPatterns(dir, patterns, config) {
198
+ const results = [];
199
+ const testFileRegex = /\.(test|spec)\.(mjs|cjs|[jt]sx?)$/;
200
+
201
+ function walk(currentDir) {
202
+ let entries;
203
+ try { entries = readdirSync(currentDir); } catch { return; }
204
+
205
+ for (const entry of entries) {
206
+ if (IGNORE_DIRS.has(entry) || entry.startsWith('.')) continue;
207
+ const fullPath = join(currentDir, entry);
208
+ try {
209
+ const stat = statSync(fullPath);
210
+ if (stat.isDirectory()) {
211
+ walk(fullPath);
212
+ } else if (stat.isFile()) {
213
+ const relPath = relative(dir, fullPath);
214
+ // Skip files in ignored paths
215
+ if (config && shouldIgnore(relPath, config)) continue;
216
+ // Check if it matches test file naming patterns
217
+ if (testFileRegex.test(entry) || /__(tests|test)__/.test(relPath)) {
218
+ // Check if it matches any of the configured test patterns
219
+ const patternFilter = buildIgnoreFilter(patterns);
220
+ if (patternFilter(relPath)) {
221
+ results.push(relPath);
222
+ }
223
+ }
224
+ }
225
+ } catch { /* skip */ }
226
+ }
227
+ }
228
+
229
+ walk(dir);
230
+ return results;
231
+ }
232
+
233
+ function getFilesRecursive(dir, config) {
167
234
  const results = [];
168
235
  if (!existsSync(dir)) return results;
169
236
  let entries;
@@ -175,7 +242,7 @@ function getFilesRecursive(dir) {
175
242
  try {
176
243
  const stat = statSync(fullPath);
177
244
  if (stat.isDirectory()) {
178
- results.push(...getFilesRecursive(fullPath));
245
+ results.push(...getFilesRecursive(fullPath, config));
179
246
  } else if (stat.isFile() && CODE_EXTENSIONS.has(extname(fullPath))) {
180
247
  results.push(fullPath);
181
248
  }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Supported: Prisma, Drizzle, Sequelize, TypeORM, Knex, Django, Rails
8
8
  *
9
- * Zero dependencies — pure Node.js built-ins only.
9
+ * Zero NPM runtime dependencies — pure Node.js built-ins only.
10
10
  */
11
11
 
12
12
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
@@ -1,9 +1,13 @@
1
1
  /**
2
2
  * Security Validator — Basic checks for secrets in code
3
+ *
4
+ * Respects config.securityIgnore (glob patterns) and config.ignore (global).
5
+ * Uses shared-ignore.mjs for consistent filtering (Constitution IV, v1.1.0).
3
6
  */
4
7
 
5
8
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
6
9
  import { resolve, join, extname } from 'node:path';
10
+ import { shouldIgnore } from '../shared-ignore.mjs';
7
11
 
8
12
  const CODE_EXTENSIONS = new Set([
9
13
  '.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx',
@@ -26,6 +30,30 @@ const SECRET_PATTERNS = [
26
30
  { pattern: /(?:sk-|sk_live_|sk_test_)[a-zA-Z0-9]{20,}/g, label: 'API secret key (Stripe/OpenAI pattern)' },
27
31
  ];
28
32
 
33
+ // Known-safe placeholder/example values that should never be flagged
34
+ const SAFE_PATTERNS = [
35
+ /EXAMPLE/i, // AWS docs example keys contain "EXAMPLE"
36
+ /placeholder\s*=\s*["']/i, // HTML placeholder attributes
37
+ /example\s*:/i, // OpenAPI example: blocks
38
+ /['"]password123['"]/, // Common test fixture value
39
+ /\/\/\s*example/i, // Code comments with "example"
40
+ /<!--.*-->/, // HTML comments
41
+ ];
42
+
43
+ /**
44
+ * Check if a match line is a known-safe placeholder/example.
45
+ * @param {string} line - The full source line containing the match
46
+ * @param {string} matchStr - The matched string
47
+ * @returns {boolean} - true if this is a safe/placeholder value
48
+ */
49
+ function isSafePlaceholder(line, matchStr) {
50
+ // Check if the matched string itself contains "EXAMPLE"
51
+ if (/EXAMPLE/i.test(matchStr)) return true;
52
+
53
+ // Check if the source line matches any safe pattern
54
+ return SAFE_PATTERNS.some(p => p.test(line));
55
+ }
56
+
29
57
  export function validateSecurity(projectDir, config) {
30
58
  const results = { name: 'security', errors: [], warnings: [], passed: 0, total: 0 };
31
59
 
@@ -40,13 +68,33 @@ export function validateSecurity(projectDir, config) {
40
68
  // Skip .env.example — it should have placeholder values
41
69
  if (filePath.endsWith('.env.example')) return;
42
70
 
43
- const content = readFileSync(filePath, 'utf-8');
44
71
  const relPath = filePath.replace(projectDir + '/', '');
45
72
 
73
+ // Apply config ignore patterns (securityIgnore + global ignore)
74
+ if (shouldIgnore(relPath, config, 'securityIgnore')) return;
75
+
76
+ const content = readFileSync(filePath, 'utf-8');
77
+ const lines = content.split('\n');
78
+
46
79
  for (const { pattern, label } of SECRET_PATTERNS) {
47
80
  pattern.lastIndex = 0;
48
81
  const match = pattern.exec(content);
49
82
  if (match) {
83
+ // Find the line containing this match for context-aware filtering
84
+ const matchPos = match.index;
85
+ let charCount = 0;
86
+ let matchLine = '';
87
+ for (const line of lines) {
88
+ charCount += line.length + 1; // +1 for newline
89
+ if (charCount > matchPos) {
90
+ matchLine = line;
91
+ break;
92
+ }
93
+ }
94
+
95
+ // Skip known-safe placeholder/example values
96
+ if (isSafePlaceholder(matchLine, match[0])) continue;
97
+
50
98
  findings.push({ file: relPath, label, match: match[0].substring(0, 30) + '...' });
51
99
  }
52
100
  }
@@ -6,14 +6,18 @@
6
6
  *
7
7
  * Also detects skipped tests without explanation.
8
8
  *
9
+ * Respects config.todoIgnore (glob patterns) and config.ignore (global).
10
+ * Uses shared-ignore.mjs for consistent filtering (Constitution IV, v1.1.0).
11
+ *
9
12
  * Inspired by spec-kit-cleanup (github.com/dsrednicki/spec-kit-cleanup)
10
13
  * which uses tiered issue classification for code hygiene.
11
14
  *
12
- * Zero dependencies — pure Node.js built-ins only.
15
+ * Zero NPM runtime dependencies — pure Node.js built-ins only.
13
16
  */
14
17
 
15
18
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
16
19
  import { resolve, join, relative, extname } from 'node:path';
20
+ import { shouldIgnore } from '../shared-ignore.mjs';
17
21
 
18
22
  const IGNORE_DIRS = new Set([
19
23
  'node_modules', '.git', '.next', 'dist', 'build', 'coverage',
@@ -78,14 +82,14 @@ export function validateTodoTracking(projectDir, config) {
78
82
  /**
79
83
  * Scan test files for skip/todo patterns without adjacent explanation comments.
80
84
  */
81
- function checkSkippedTests(projectDir) {
85
+ function checkSkippedTests(projectDir, config) {
82
86
  const errors = [];
83
87
  const warnings = [];
84
88
  let passed = 0;
85
89
  let total = 0;
86
90
 
87
91
  const testFiles = [];
88
- findTestFiles(projectDir, projectDir, testFiles);
92
+ findTestFiles(projectDir, projectDir, testFiles, config);
89
93
 
90
94
  if (testFiles.length === 0) return { errors, warnings, passed, total };
91
95
 
@@ -161,7 +165,7 @@ function checkUntrackedTodos(projectDir, config) {
161
165
 
162
166
  // Collect all TODO/FIXME items from source
163
167
  const todos = [];
164
- findTodos(projectDir, projectDir, todos);
168
+ findTodos(projectDir, projectDir, todos, config);
165
169
 
166
170
  if (todos.length === 0) {
167
171
  // No TODOs found — that's clean code
@@ -177,11 +181,26 @@ function checkUntrackedTodos(projectDir, config) {
177
181
  let untrackedCount = 0;
178
182
 
179
183
  for (const todo of todos) {
180
- // Check if the TODO text appears somewhere in tracking docs
181
- const isTracked = trackingContent.some(doc =>
182
- doc.content.includes(todo.keyword) ||
183
- doc.content.toLowerCase().includes(todo.text.toLowerCase().trim().substring(0, 30))
184
- );
184
+ // Check if the TODO is tracked in documentation
185
+ // Improved matching: check full text AND file location context
186
+ const isTracked = trackingContent.some(doc => {
187
+ const content = doc.content;
188
+ const contentLower = content.toLowerCase();
189
+ const todoTextLower = todo.text.toLowerCase().trim();
190
+
191
+ // Match 1: Full TODO text appears in the doc (at least 20 chars or full text)
192
+ const searchText = todoTextLower.length > 20
193
+ ? todoTextLower.substring(0, 40)
194
+ : todoTextLower;
195
+ const hasText = contentLower.includes(searchText);
196
+
197
+ // Match 2: File location appears nearby in the doc
198
+ const hasLocation = content.includes(todo.file) ||
199
+ content.includes(`${todo.file}:${todo.line}`);
200
+
201
+ // Either the full text matches, or the file location is referenced with partial text
202
+ return (hasText && hasLocation) || (hasText && todoTextLower.length > 30);
203
+ });
185
204
 
186
205
  if (!isTracked) {
187
206
  untrackedCount++;
@@ -231,7 +250,7 @@ function loadTrackingDocs(projectDir, config) {
231
250
 
232
251
  // ──── File Scanners ────────────────────────────────────────────────────────
233
252
 
234
- function findTestFiles(rootDir, dir, files) {
253
+ function findTestFiles(rootDir, dir, files, config) {
235
254
  let entries;
236
255
  try { entries = readdirSync(dir); } catch { return; }
237
256
 
@@ -244,7 +263,7 @@ function findTestFiles(rootDir, dir, files) {
244
263
  try { stat = statSync(full); } catch { continue; }
245
264
 
246
265
  if (stat.isDirectory()) {
247
- findTestFiles(rootDir, full, files);
266
+ findTestFiles(rootDir, full, files, config);
248
267
  } else {
249
268
  const ext = extname(entry).toLowerCase();
250
269
  if (!TEST_EXTENSIONS.has(ext)) continue;
@@ -252,13 +271,16 @@ function findTestFiles(rootDir, dir, files) {
252
271
  // Match test file patterns
253
272
  if (/\.(test|spec)\.(mjs|cjs|[jt]sx?)$/.test(entry) ||
254
273
  /__(tests|test)__/.test(relative(rootDir, full))) {
255
- files.push(relative(rootDir, full));
274
+ const relPath = relative(rootDir, full);
275
+ // Apply config ignore patterns (todoIgnore + global ignore)
276
+ if (config && shouldIgnore(relPath, config, 'todoIgnore')) continue;
277
+ files.push(relPath);
256
278
  }
257
279
  }
258
280
  }
259
281
  }
260
282
 
261
- function findTodos(rootDir, dir, todos) {
283
+ function findTodos(rootDir, dir, todos, config) {
262
284
  let entries;
263
285
  try { entries = readdirSync(dir); } catch { return; }
264
286
 
@@ -271,16 +293,20 @@ function findTodos(rootDir, dir, todos) {
271
293
  try { stat = statSync(full); } catch { continue; }
272
294
 
273
295
  if (stat.isDirectory()) {
274
- findTodos(rootDir, full, todos);
296
+ findTodos(rootDir, full, todos, config);
275
297
  } else {
276
298
  const ext = extname(entry).toLowerCase();
277
299
  if (!SOURCE_EXTENSIONS.has(ext)) continue;
278
300
 
301
+ const relPath = relative(rootDir, full);
302
+
303
+ // Apply config ignore patterns (todoIgnore + global ignore)
304
+ if (config && shouldIgnore(relPath, config, 'todoIgnore')) continue;
305
+
279
306
  let content;
280
307
  try { content = readFileSync(full, 'utf-8'); } catch { continue; }
281
308
 
282
309
  const lines = content.split('\n');
283
- const relPath = relative(rootDir, full);
284
310
 
285
311
  for (let i = 0; i < lines.length; i++) {
286
312
  if (TODO_PATTERN.test(lines[i])) {
@@ -3,8 +3,8 @@ schema_version: "1.0"
3
3
  extension:
4
4
  id: "docguard"
5
5
  name: "DocGuard — CDD Enforcement"
6
- version: "0.9.8"
7
- description: "Canonical-Driven Development enforcement with enterprise-grade AI skills. 19 automated validators, structured research workflows, quality validation loops, and spec-kit integration hooks. Zero dependencies."
6
+ version: "0.9.9"
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"
10
10
  license: "MIT"
@@ -12,12 +12,16 @@ extension:
12
12
 
13
13
  requires:
14
14
  speckit_version: ">=0.1.0"
15
+ framework: "spec-kit" # DocGuard builds on spec-kit conventions (.specify/, skills, constitution)
15
16
  tools:
16
17
  - name: "node"
17
18
  required: true
18
19
  version: ">=18.0.0"
19
20
  - name: "npx"
20
21
  required: true
22
+ - name: "specify"
23
+ required: false
24
+ description: "Spec Kit CLI — enables auto-initialization of SDD workflow during docguard init"
21
25
 
22
26
  provides:
23
27
  commands:
@@ -6,9 +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.5
9
+ version: 0.9.9
10
10
  source: extensions/spec-kit-docguard/skills/docguard-fix
11
11
  ---
12
+ <!-- docguard:version: 0.9.9 -->
12
13
 
13
14
  # DocGuard Fix Skill
14
15
 
@@ -7,9 +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.9.5
10
+ version: 0.9.9
11
11
  source: extensions/spec-kit-docguard/skills/docguard-guard
12
12
  ---
13
+ <!-- docguard:version: 0.9.9 -->
13
14
 
14
15
  # DocGuard Guard Skill
15
16
 
@@ -155,12 +156,16 @@ Present the user with options:
155
156
  - **Track progress** — if user runs guard multiple times, compare before/after
156
157
  - If user provides `$ARGUMENTS` like "just structure" or "only security", filter report to those validators
157
158
 
158
- ## Integration with Spec Kit
159
+ ## Integration with Spec Kit (Extension-First)
159
160
 
160
- If this project has `.specify/` directory (spec-kit enabled):
161
+ DocGuard is a spec-kit extension. When this project has a `.specify/` directory:
162
+ - Read `.specify/memory/constitution.md` for project principles that constrain documentation
161
163
  - Include Spec-Kit validator results in the triage
162
164
  - Cross-reference spec quality issues with `specs/*/spec.md` file paths
163
- - Suggest `/speckit.analyze` if spec-related findings exceed 3
165
+ - When specification issues found → suggest `/speckit.specify` or `/speckit.clarify`
166
+ - When architecture gaps found → suggest `/speckit.plan`
167
+ - When cross-artifact inconsistencies exceed 3 → suggest `/speckit.analyze`
168
+ - When no constitution exists → suggest `/speckit.constitution` as first step
164
169
 
165
170
  ## Context
166
171
 
@@ -6,9 +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.5
9
+ version: 0.9.9
10
10
  source: extensions/spec-kit-docguard/skills/docguard-review
11
11
  ---
12
+ <!-- docguard:version: 0.9.9 -->
12
13
 
13
14
  # DocGuard Review Skill
14
15
 
@@ -163,7 +164,10 @@ Output a structured markdown report (do NOT write to disk):
163
164
 
164
165
  Based on findings:
165
166
  - **If CRITICAL issues**: "Run `/docguard.fix --doc [name]` to resolve blocking issues"
167
+ - **If spec-related gaps**: "Run `/speckit.specify` to update specifications" or "/speckit.clarify to resolve ambiguities"
168
+ - **If architecture drift**: "Run `/speckit.plan` to realign implementation plan with codebase"
166
169
  - **If only LOW/MEDIUM**: "Documentation is healthy. Consider `/docguard.fix` for polish"
170
+ - **If constitution missing**: "Run `/speckit.constitution` to establish project principles"
167
171
  - **If all clean**: "Documentation is excellent. No action needed."
168
172
 
169
173
  Ask: "Would you like me to fix the top N issues? (I'll show you what I plan to change before applying)"
@@ -6,9 +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.5
9
+ version: 0.9.9
10
10
  source: extensions/spec-kit-docguard/skills/docguard-score
11
11
  ---
12
+ <!-- docguard:version: 0.9.9 -->
12
13
 
13
14
  # DocGuard Score Skill
14
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docguard-cli",
3
- "version": "0.9.8",
3
+ "version": "0.9.10",
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": {