docguard-cli 0.9.6 → 0.9.7
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/cli/commands/diff.mjs +16 -3
- package/cli/commands/init.mjs +4 -0
- package/cli/commands/setup.mjs +455 -0
- package/cli/docguard.mjs +12 -0
- package/cli/ensure-skills.mjs +96 -0
- package/cli/validators/doc-quality.mjs +2 -2
- package/cli/validators/docs-sync.mjs +41 -6
- package/cli/validators/todo-tracking.mjs +11 -6
- package/extensions/spec-kit-docguard/README.md +13 -15
- package/extensions/spec-kit-docguard/extension.yml +23 -40
- package/extensions/spec-kit-docguard/templates/extensions.yml +5 -5
- package/package.json +1 -1
package/cli/commands/diff.mjs
CHANGED
|
@@ -157,7 +157,12 @@ function diffEntities(dir) {
|
|
|
157
157
|
'EntityName', 'Entity', 'metadata', 'tbd', 'cascade', 'fields',
|
|
158
158
|
'purpose', 'version', 'author', 'example', 'TODO', 'Overview',
|
|
159
159
|
'Revision', 'History', 'Entities', 'Relationships', 'Indexes',
|
|
160
|
-
'Migration', 'Strategy',
|
|
160
|
+
'Migration', 'Strategy', 'Trade-offs', 'Tradeoffs', 'Notes',
|
|
161
|
+
'Summary', 'Details', 'Configuration', 'Setup', 'Reference',
|
|
162
|
+
'Appendix', 'Glossary', 'FAQ', 'Introduction', 'Background',
|
|
163
|
+
'Prerequisites', 'Requirements', 'Assumptions', 'Constraints',
|
|
164
|
+
'Dependencies', 'Architecture', 'Design', 'Implementation',
|
|
165
|
+
'Testing', 'Deployment', 'Monitoring', 'Operations', 'Security',
|
|
161
166
|
]);
|
|
162
167
|
|
|
163
168
|
const headerRegex = /^### (\S+)/gm;
|
|
@@ -165,9 +170,11 @@ function diffEntities(dir) {
|
|
|
165
170
|
while ((match = headerRegex.exec(content)) !== null) {
|
|
166
171
|
const name = match[1].replace(/[`*]/g, '');
|
|
167
172
|
// Skip template placeholders (<!-- ... -->) and noise words
|
|
168
|
-
if (name.startsWith('<!--') || name.length <=
|
|
173
|
+
if (name.startsWith('<!--') || name.length <= 2 || HEADER_NOISE.has(name) || HEADER_NOISE.has(name.toLowerCase())) {
|
|
169
174
|
continue;
|
|
170
175
|
}
|
|
176
|
+
// Skip hyphenated words (e.g., 'Trade-offs', 'Set-up') — these are section titles, not entities
|
|
177
|
+
if (name.includes('-')) continue;
|
|
171
178
|
docEntities.add(name.toLowerCase());
|
|
172
179
|
}
|
|
173
180
|
|
|
@@ -194,10 +201,16 @@ function diffEntities(dir) {
|
|
|
194
201
|
// Common table headers and template words
|
|
195
202
|
'true', 'false', 'header', 'checks', 'project', 'count', 'grade',
|
|
196
203
|
'breakdown', 'issuecount', 'autofixable', 'projectname', 'projecttype',
|
|
204
|
+
// Common doc section words (not entity names)
|
|
205
|
+
'trade', 'offs', 'tradeoffs', 'setup', 'overview', 'summary',
|
|
206
|
+
'details', 'configuration', 'reference', 'pattern', 'patterns',
|
|
207
|
+
'strategy', 'approach', 'impact', 'benefit', 'risk', 'concern',
|
|
208
|
+
'action', 'result', 'outcome', 'inverted', 'composite', 'secondary',
|
|
197
209
|
]);
|
|
198
210
|
while ((match = tableRegex.exec(content)) !== null) {
|
|
199
211
|
const name = match[1];
|
|
200
|
-
|
|
212
|
+
// Skip short names (<=3 chars) and noise words
|
|
213
|
+
if (name.length > 3 && !TABLE_NOISE.has(name.toLowerCase())) {
|
|
201
214
|
docEntities.add(name.toLowerCase());
|
|
202
215
|
}
|
|
203
216
|
}
|
package/cli/commands/init.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { resolve, dirname } from 'node:path';
|
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { createInterface } from 'node:readline';
|
|
10
10
|
import { c, PROFILES } from '../shared.mjs';
|
|
11
|
+
import { ensureSkills } from '../ensure-skills.mjs';
|
|
11
12
|
|
|
12
13
|
function detectProjectType(dir) {
|
|
13
14
|
const pkgPath = resolve(dir, 'package.json');
|
|
@@ -285,4 +286,7 @@ export async function runInit(projectDir, config, flags) {
|
|
|
285
286
|
} else {
|
|
286
287
|
console.log(`\n ${c.dim}Run${c.reset} ${c.cyan}docguard diagnose${c.reset} ${c.dim}to check for issues.${c.reset}\n`);
|
|
287
288
|
}
|
|
289
|
+
|
|
290
|
+
// Auto-install skills and commands
|
|
291
|
+
ensureSkills(projectDir, flags);
|
|
288
292
|
}
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup Command — Interactive onboarding wizard for DocGuard
|
|
3
|
+
*
|
|
4
|
+
* Walks through 7 steps to ensure DocGuard is fully configured:
|
|
5
|
+
* 1. Project detection & config
|
|
6
|
+
* 2. Canonical docs
|
|
7
|
+
* 3. AI skills
|
|
8
|
+
* 4. Slash commands
|
|
9
|
+
* 5. Agent configs
|
|
10
|
+
* 6. External integrations (spec-kit, understanding)
|
|
11
|
+
* 7. Git hooks
|
|
12
|
+
*
|
|
13
|
+
* Each step shows current status (✅/⚠️) and offers to fix what's missing.
|
|
14
|
+
* Supports --skip-prompts for non-interactive CI mode.
|
|
15
|
+
*
|
|
16
|
+
* Zero dependencies — pure Node.js built-ins only.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
20
|
+
import { resolve, dirname, basename } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { createInterface } from 'node:readline';
|
|
23
|
+
import { execSync } from 'node:child_process';
|
|
24
|
+
import { c } from '../shared.mjs';
|
|
25
|
+
import { ensureSkills } from '../ensure-skills.mjs';
|
|
26
|
+
|
|
27
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
28
|
+
const __dirname = dirname(__filename);
|
|
29
|
+
const TEMPLATES_DIR = resolve(__dirname, '../../templates');
|
|
30
|
+
const SKILLS_SOURCE = resolve(__dirname, '../../extensions/spec-kit-docguard/skills');
|
|
31
|
+
const COMMANDS_SOURCE = resolve(__dirname, '../../commands');
|
|
32
|
+
|
|
33
|
+
// ── Readline Helper ─────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
function askQuestion(prompt) {
|
|
36
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
37
|
+
return new Promise(res => {
|
|
38
|
+
rl.question(prompt, answer => {
|
|
39
|
+
rl.close();
|
|
40
|
+
res(answer.trim().toLowerCase());
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function askYesNo(prompt, defaultYes = true) {
|
|
46
|
+
const label = defaultYes ? 'Y/n' : 'y/N';
|
|
47
|
+
const answer = await askQuestion(`${prompt} [${label}]: `);
|
|
48
|
+
if (answer === '') return defaultYes;
|
|
49
|
+
return answer === 'y' || answer === 'yes';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── Project Type Detection ──────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
function detectProjectType(dir) {
|
|
55
|
+
const pkgPath = resolve(dir, 'package.json');
|
|
56
|
+
if (existsSync(pkgPath)) {
|
|
57
|
+
try {
|
|
58
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
59
|
+
const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
60
|
+
if (pkg.bin) return 'cli';
|
|
61
|
+
if (allDeps.next || allDeps.react || allDeps.vue || allDeps['@angular/core'] ||
|
|
62
|
+
allDeps.svelte || allDeps.nuxt) return 'webapp';
|
|
63
|
+
if (allDeps.express || allDeps.fastify || allDeps.hono || allDeps.koa) return 'api';
|
|
64
|
+
if (pkg.main || pkg.exports || pkg.module) return 'library';
|
|
65
|
+
} catch { /* fall through */ }
|
|
66
|
+
}
|
|
67
|
+
if (existsSync(resolve(dir, 'manage.py'))) return 'webapp';
|
|
68
|
+
if (existsSync(resolve(dir, 'setup.py')) || existsSync(resolve(dir, 'pyproject.toml'))) return 'library';
|
|
69
|
+
return 'unknown';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── CLI Detection ───────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function isCliAvailable(name) {
|
|
75
|
+
try {
|
|
76
|
+
const cmd = process.platform === 'win32' ? `where ${name}` : `which ${name}`;
|
|
77
|
+
execSync(`${cmd} 2>/dev/null`, { encoding: 'utf-8', timeout: 3000 });
|
|
78
|
+
return true;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function detectAgentDirs(projectDir) {
|
|
85
|
+
const agentDirs = [
|
|
86
|
+
{ name: 'GitHub Copilot', dir: '.github', commandsPath: '.github/commands' },
|
|
87
|
+
{ name: 'Cursor', dir: '.cursor', commandsPath: '.cursor/rules' },
|
|
88
|
+
{ name: 'Google Gemini', dir: '.gemini', commandsPath: '.gemini/commands' },
|
|
89
|
+
{ name: 'Claude Code', dir: '.claude', commandsPath: '.claude/commands' },
|
|
90
|
+
{ name: 'Antigravity', dir: '.agents', commandsPath: '.agents/workflows' },
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
return agentDirs.filter(a => existsSync(resolve(projectDir, a.dir)));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Main Setup Wizard ───────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
export async function runSetup(projectDir, config, flags) {
|
|
99
|
+
console.log(`${c.bold}🧙 DocGuard Setup Wizard${c.reset}`);
|
|
100
|
+
console.log(`${c.dim} Directory: ${projectDir}${c.reset}\n`);
|
|
101
|
+
|
|
102
|
+
const interactive = !flags.skipPrompts;
|
|
103
|
+
let configured = 0;
|
|
104
|
+
let alreadyGood = 0;
|
|
105
|
+
|
|
106
|
+
// ── Step 1: Project Detection & Config ──────────────────────────────
|
|
107
|
+
|
|
108
|
+
console.log(` ${c.bold}Step 1/7: Project Detection${c.reset}`);
|
|
109
|
+
|
|
110
|
+
const detectedType = detectProjectType(projectDir);
|
|
111
|
+
console.log(` ${c.green}✅${c.reset} Project type: ${c.cyan}${detectedType}${c.reset}`);
|
|
112
|
+
|
|
113
|
+
const configPath = resolve(projectDir, '.docguard.json');
|
|
114
|
+
if (existsSync(configPath)) {
|
|
115
|
+
const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
116
|
+
console.log(` ${c.green}✅${c.reset} .docguard.json exists (profile: ${c.cyan}${cfg.profile || 'standard'}${c.reset})`);
|
|
117
|
+
alreadyGood++;
|
|
118
|
+
} else {
|
|
119
|
+
console.log(` ${c.yellow}⚠️${c.reset} .docguard.json missing`);
|
|
120
|
+
const create = interactive
|
|
121
|
+
? await askYesNo(` → Create config file?`)
|
|
122
|
+
: true;
|
|
123
|
+
|
|
124
|
+
if (create) {
|
|
125
|
+
const typeDefaults = {
|
|
126
|
+
cli: { needsEnvVars: false, needsEnvExample: false, needsE2E: false, needsDatabase: false },
|
|
127
|
+
library: { needsEnvVars: false, needsEnvExample: false, needsE2E: false, needsDatabase: false },
|
|
128
|
+
webapp: { needsEnvVars: true, needsEnvExample: true, needsE2E: true, needsDatabase: true },
|
|
129
|
+
api: { needsEnvVars: true, needsEnvExample: true, needsE2E: false, needsDatabase: true },
|
|
130
|
+
unknown: { needsEnvVars: true, needsEnvExample: true, needsE2E: false, needsDatabase: true },
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const defaultConfig = {
|
|
134
|
+
projectName: config.projectName,
|
|
135
|
+
version: '0.4',
|
|
136
|
+
profile: 'standard',
|
|
137
|
+
projectType: detectedType,
|
|
138
|
+
projectTypeConfig: typeDefaults[detectedType] || typeDefaults.unknown,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + '\n', 'utf-8');
|
|
142
|
+
console.log(` ${c.green}✅ Created .docguard.json${c.reset}`);
|
|
143
|
+
configured++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log('');
|
|
148
|
+
|
|
149
|
+
// ── Step 2: Canonical Docs ──────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
console.log(` ${c.bold}Step 2/7: Canonical Docs${c.reset}`);
|
|
152
|
+
|
|
153
|
+
const canonicalDocs = [
|
|
154
|
+
{ file: 'docs-canonical/ARCHITECTURE.md', template: 'ARCHITECTURE.md.template', label: 'Architecture', defaultYes: true },
|
|
155
|
+
{ file: 'docs-canonical/DATA-MODEL.md', template: 'DATA-MODEL.md.template', label: 'Data Model', defaultYes: ['webapp', 'api'].includes(detectedType) },
|
|
156
|
+
{ file: 'docs-canonical/SECURITY.md', template: 'SECURITY.md.template', label: 'Security', defaultYes: ['webapp', 'api'].includes(detectedType) },
|
|
157
|
+
{ file: 'docs-canonical/TEST-SPEC.md', template: 'TEST-SPEC.md.template', label: 'Test Spec', defaultYes: true },
|
|
158
|
+
{ file: 'docs-canonical/ENVIRONMENT.md', template: 'ENVIRONMENT.md.template', label: 'Environment', defaultYes: ['webapp', 'api'].includes(detectedType) },
|
|
159
|
+
{ file: 'docs-canonical/REQUIREMENTS.md', template: 'REQUIREMENTS.md.template', label: 'Requirements', defaultYes: true },
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const trackingFiles = [
|
|
163
|
+
{ file: 'AGENTS.md', template: 'AGENTS.md.template', label: 'Agent Instructions' },
|
|
164
|
+
{ file: 'CHANGELOG.md', template: 'CHANGELOG.md.template', label: 'Changelog' },
|
|
165
|
+
{ file: 'DRIFT-LOG.md', template: 'DRIFT-LOG.md.template', label: 'Drift Log' },
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
let missingDocs = [];
|
|
169
|
+
|
|
170
|
+
// Check canonical docs
|
|
171
|
+
for (const doc of [...canonicalDocs, ...trackingFiles]) {
|
|
172
|
+
const fullPath = resolve(projectDir, doc.file);
|
|
173
|
+
if (existsSync(fullPath)) {
|
|
174
|
+
console.log(` ${c.green}✅${c.reset} ${doc.file}`);
|
|
175
|
+
alreadyGood++;
|
|
176
|
+
} else {
|
|
177
|
+
console.log(` ${c.yellow}⚠️${c.reset} ${doc.file} ${c.dim}(missing)${c.reset}`);
|
|
178
|
+
missingDocs.push(doc);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (missingDocs.length > 0) {
|
|
183
|
+
const create = interactive
|
|
184
|
+
? await askYesNo(` → Create ${missingDocs.length} missing doc(s) from templates?`)
|
|
185
|
+
: true;
|
|
186
|
+
|
|
187
|
+
if (create) {
|
|
188
|
+
const today = new Date().toISOString().split('T')[0];
|
|
189
|
+
for (const doc of missingDocs) {
|
|
190
|
+
const destPath = resolve(projectDir, doc.file);
|
|
191
|
+
const templatePath = resolve(TEMPLATES_DIR, doc.template);
|
|
192
|
+
|
|
193
|
+
const destDir = dirname(destPath);
|
|
194
|
+
if (!existsSync(destDir)) {
|
|
195
|
+
mkdirSync(destDir, { recursive: true });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (existsSync(templatePath)) {
|
|
199
|
+
const content = readFileSync(templatePath, 'utf-8').replace(/YYYY-MM-DD/g, today);
|
|
200
|
+
writeFileSync(destPath, content, 'utf-8');
|
|
201
|
+
console.log(` ${c.green}✅ Created ${doc.file}${c.reset}`);
|
|
202
|
+
configured++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log('');
|
|
209
|
+
|
|
210
|
+
// ── Step 3: AI Skills ──────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
console.log(` ${c.bold}Step 3/7: AI Skills${c.reset}`);
|
|
213
|
+
|
|
214
|
+
const skillNames = ['docguard-guard', 'docguard-fix', 'docguard-review', 'docguard-score'];
|
|
215
|
+
const skillsDest = resolve(projectDir, '.agent/skills');
|
|
216
|
+
let missingSkills = [];
|
|
217
|
+
|
|
218
|
+
for (const skill of skillNames) {
|
|
219
|
+
const skillPath = resolve(skillsDest, skill, 'SKILL.md');
|
|
220
|
+
if (existsSync(skillPath)) {
|
|
221
|
+
console.log(` ${c.green}✅${c.reset} ${skill}`);
|
|
222
|
+
alreadyGood++;
|
|
223
|
+
} else {
|
|
224
|
+
console.log(` ${c.yellow}⚠️${c.reset} ${skill} ${c.dim}(not installed)${c.reset}`);
|
|
225
|
+
missingSkills.push(skill);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (missingSkills.length > 0) {
|
|
230
|
+
const install = interactive
|
|
231
|
+
? await askYesNo(` → Install ${missingSkills.length} AI skill(s) to .agent/skills/?`)
|
|
232
|
+
: true;
|
|
233
|
+
|
|
234
|
+
if (install) {
|
|
235
|
+
for (const skill of missingSkills) {
|
|
236
|
+
const srcSkill = resolve(SKILLS_SOURCE, skill, 'SKILL.md');
|
|
237
|
+
const destDir = resolve(skillsDest, skill);
|
|
238
|
+
if (existsSync(srcSkill)) {
|
|
239
|
+
if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
|
|
240
|
+
writeFileSync(resolve(destDir, 'SKILL.md'), readFileSync(srcSkill, 'utf-8'), 'utf-8');
|
|
241
|
+
console.log(` ${c.green}✅ Installed ${skill}${c.reset}`);
|
|
242
|
+
configured++;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log('');
|
|
249
|
+
|
|
250
|
+
// ── Step 4: Slash Commands ─────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
console.log(` ${c.bold}Step 4/7: Slash Commands${c.reset}`);
|
|
253
|
+
|
|
254
|
+
// Check root commands/ dir
|
|
255
|
+
const rootCommandsDir = resolve(projectDir, 'commands');
|
|
256
|
+
const rootCommandsExist = existsSync(resolve(rootCommandsDir, 'docguard.guard.md'));
|
|
257
|
+
|
|
258
|
+
if (rootCommandsExist) {
|
|
259
|
+
console.log(` ${c.green}✅${c.reset} commands/ ${c.dim}(root)${c.reset}`);
|
|
260
|
+
alreadyGood++;
|
|
261
|
+
} else {
|
|
262
|
+
console.log(` ${c.yellow}⚠️${c.reset} commands/ ${c.dim}(not installed)${c.reset}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Detect agent directories and sync commands
|
|
266
|
+
const detectedAgents = detectAgentDirs(projectDir);
|
|
267
|
+
let unsyncedAgents = [];
|
|
268
|
+
|
|
269
|
+
for (const agent of detectedAgents) {
|
|
270
|
+
const agentCommandCheck = resolve(projectDir, agent.commandsPath, 'docguard.guard.md');
|
|
271
|
+
if (existsSync(agentCommandCheck)) {
|
|
272
|
+
console.log(` ${c.green}✅${c.reset} ${agent.commandsPath}/ ${c.dim}(${agent.name})${c.reset}`);
|
|
273
|
+
alreadyGood++;
|
|
274
|
+
} else {
|
|
275
|
+
console.log(` ${c.yellow}⚠️${c.reset} ${agent.commandsPath}/ ${c.dim}(${agent.name} — not synced)${c.reset}`);
|
|
276
|
+
unsyncedAgents.push(agent);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const needsCommands = !rootCommandsExist || unsyncedAgents.length > 0;
|
|
281
|
+
|
|
282
|
+
if (needsCommands && existsSync(COMMANDS_SOURCE)) {
|
|
283
|
+
const install = interactive
|
|
284
|
+
? await askYesNo(` → Install/sync slash commands?`)
|
|
285
|
+
: true;
|
|
286
|
+
|
|
287
|
+
if (install) {
|
|
288
|
+
const commandFiles = readdirSync(COMMANDS_SOURCE).filter(f => f.endsWith('.md'));
|
|
289
|
+
|
|
290
|
+
// Install to root commands/
|
|
291
|
+
if (!rootCommandsExist) {
|
|
292
|
+
if (!existsSync(rootCommandsDir)) mkdirSync(rootCommandsDir, { recursive: true });
|
|
293
|
+
for (const file of commandFiles) {
|
|
294
|
+
const destPath = resolve(rootCommandsDir, file);
|
|
295
|
+
if (!existsSync(destPath)) {
|
|
296
|
+
writeFileSync(destPath, readFileSync(resolve(COMMANDS_SOURCE, file), 'utf-8'), 'utf-8');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
console.log(` ${c.green}✅ Installed to commands/${c.reset}`);
|
|
300
|
+
configured++;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Sync to agent-specific dirs
|
|
304
|
+
for (const agent of unsyncedAgents) {
|
|
305
|
+
const destDir = resolve(projectDir, agent.commandsPath);
|
|
306
|
+
if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
|
|
307
|
+
for (const file of commandFiles) {
|
|
308
|
+
const destPath = resolve(destDir, file);
|
|
309
|
+
if (!existsSync(destPath)) {
|
|
310
|
+
writeFileSync(destPath, readFileSync(resolve(COMMANDS_SOURCE, file), 'utf-8'), 'utf-8');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
console.log(` ${c.green}✅ Synced to ${agent.commandsPath}/ (${agent.name})${c.reset}`);
|
|
314
|
+
configured++;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
console.log('');
|
|
320
|
+
|
|
321
|
+
// ── Step 5: Agent Configs ──────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
console.log(` ${c.bold}Step 5/7: Agent Configs${c.reset}`);
|
|
324
|
+
|
|
325
|
+
const agentConfigs = [
|
|
326
|
+
{ file: 'AGENTS.md', label: 'Agent Instructions' },
|
|
327
|
+
{ file: 'CLAUDE.md', label: 'Claude Code' },
|
|
328
|
+
{ file: '.cursor/rules/cdd.mdc', label: 'Cursor' },
|
|
329
|
+
{ file: '.github/copilot-instructions.md', label: 'GitHub Copilot' },
|
|
330
|
+
];
|
|
331
|
+
|
|
332
|
+
let missingConfigs = [];
|
|
333
|
+
for (const cfg of agentConfigs) {
|
|
334
|
+
const fullPath = resolve(projectDir, cfg.file);
|
|
335
|
+
if (existsSync(fullPath)) {
|
|
336
|
+
console.log(` ${c.green}✅${c.reset} ${cfg.file} ${c.dim}(${cfg.label})${c.reset}`);
|
|
337
|
+
alreadyGood++;
|
|
338
|
+
} else {
|
|
339
|
+
// AGENTS.md is handled in step 2, skip it here
|
|
340
|
+
if (cfg.file !== 'AGENTS.md') {
|
|
341
|
+
console.log(` ${c.dim}──${c.reset} ${cfg.file} ${c.dim}(${cfg.label} — not generated)${c.reset}`);
|
|
342
|
+
missingConfigs.push(cfg);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (missingConfigs.length > 0) {
|
|
348
|
+
console.log(` ${c.dim} Run ${c.cyan}docguard agents${c.dim} to generate agent-specific configs${c.reset}`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
console.log('');
|
|
352
|
+
|
|
353
|
+
// ── Step 6: Integrations ───────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
console.log(` ${c.bold}Step 6/7: Integrations${c.reset}`);
|
|
356
|
+
|
|
357
|
+
// Check spec-kit framework
|
|
358
|
+
const speckitDir = resolve(projectDir, '.speckit');
|
|
359
|
+
const hasSpeckit = existsSync(speckitDir) || existsSync(resolve(projectDir, 'spec.md'));
|
|
360
|
+
if (hasSpeckit) {
|
|
361
|
+
console.log(` ${c.green}✅${c.reset} spec-kit ${c.dim}(spec-driven development configured)${c.reset}`);
|
|
362
|
+
alreadyGood++;
|
|
363
|
+
} else {
|
|
364
|
+
console.log(` ${c.dim}──${c.reset} spec-kit ${c.dim}(not configured — optional)${c.reset}`);
|
|
365
|
+
console.log(` ${c.dim} Spec Kit enables spec-driven development with AI agents${c.reset}`);
|
|
366
|
+
console.log(` ${c.dim} See: ${c.cyan}https://github.com/github/spec-kit${c.reset}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Check for spec-kit extensions
|
|
370
|
+
const extensionsDir = resolve(projectDir, 'extensions');
|
|
371
|
+
|
|
372
|
+
// DocGuard extension (this project IS DocGuard, so check if extension is bundled)
|
|
373
|
+
const docguardExt = resolve(extensionsDir, 'spec-kit-docguard', 'extension.yml');
|
|
374
|
+
if (existsSync(docguardExt)) {
|
|
375
|
+
console.log(` ${c.green}✅${c.reset} docguard extension ${c.dim}(spec-kit CDD enforcement)${c.reset}`);
|
|
376
|
+
alreadyGood++;
|
|
377
|
+
} else {
|
|
378
|
+
// DocGuard is installed as a CLI, not necessarily as a spec-kit extension
|
|
379
|
+
console.log(` ${c.green}✅${c.reset} docguard CLI ${c.dim}(standalone — 19 validators + 31 quality metrics)${c.reset}`);
|
|
380
|
+
alreadyGood++;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Understanding extension (spec-kit community extension)
|
|
384
|
+
const understandingExt = resolve(extensionsDir, 'spec-kit-understanding', 'extension.yml');
|
|
385
|
+
if (existsSync(understandingExt)) {
|
|
386
|
+
console.log(` ${c.green}✅${c.reset} understanding ${c.dim}(spec-kit deep doc analysis)${c.reset}`);
|
|
387
|
+
alreadyGood++;
|
|
388
|
+
} else {
|
|
389
|
+
console.log(` ${c.dim}──${c.reset} understanding ${c.dim}(spec-kit extension — optional)${c.reset}`);
|
|
390
|
+
console.log(` ${c.dim} Install via spec-kit: ${c.cyan}https://github.com/github/spec-kit/tree/main/extensions${c.reset}`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
console.log('');
|
|
394
|
+
|
|
395
|
+
// ── Step 7: Git Hooks ──────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
console.log(` ${c.bold}Step 7/7: Git Hooks${c.reset}`);
|
|
398
|
+
|
|
399
|
+
const gitDir = resolve(projectDir, '.git');
|
|
400
|
+
if (!existsSync(gitDir)) {
|
|
401
|
+
console.log(` ${c.dim}──${c.reset} No .git directory ${c.dim}(not a git repo)${c.reset}`);
|
|
402
|
+
} else {
|
|
403
|
+
const preCommitHook = resolve(gitDir, 'hooks', 'pre-commit');
|
|
404
|
+
let hasDocguardHook = false;
|
|
405
|
+
|
|
406
|
+
if (existsSync(preCommitHook)) {
|
|
407
|
+
const content = readFileSync(preCommitHook, 'utf-8');
|
|
408
|
+
hasDocguardHook = content.includes('docguard');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (hasDocguardHook) {
|
|
412
|
+
console.log(` ${c.green}✅${c.reset} pre-commit hook ${c.dim}(docguard guard)${c.reset}`);
|
|
413
|
+
alreadyGood++;
|
|
414
|
+
} else {
|
|
415
|
+
console.log(` ${c.dim}──${c.reset} pre-commit hook ${c.dim}(not installed)${c.reset}`);
|
|
416
|
+
if (interactive) {
|
|
417
|
+
const install = await askYesNo(` → Install docguard guard as pre-commit hook?`, false);
|
|
418
|
+
if (install) {
|
|
419
|
+
try {
|
|
420
|
+
const hooksDir = resolve(gitDir, 'hooks');
|
|
421
|
+
if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });
|
|
422
|
+
|
|
423
|
+
const hookContent = existsSync(preCommitHook)
|
|
424
|
+
? readFileSync(preCommitHook, 'utf-8') + '\n\n# DocGuard CDD validation\nnpx docguard guard --fail-on-warning\n'
|
|
425
|
+
: '#!/bin/sh\n\n# DocGuard CDD validation\nnpx docguard guard --fail-on-warning\n';
|
|
426
|
+
|
|
427
|
+
writeFileSync(preCommitHook, hookContent, { mode: 0o755 });
|
|
428
|
+
console.log(` ${c.green}✅ Pre-commit hook installed${c.reset}`);
|
|
429
|
+
configured++;
|
|
430
|
+
} catch (e) {
|
|
431
|
+
console.log(` ${c.yellow}⚠️ Failed to install hook: ${e.message}${c.reset}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ── Summary ────────────────────────────────────────────────────────
|
|
439
|
+
|
|
440
|
+
console.log(`\n ${c.bold}─────────────────────────────────────${c.reset}`);
|
|
441
|
+
|
|
442
|
+
if (configured > 0) {
|
|
443
|
+
console.log(` ${c.green}✅ Setup complete!${c.reset} ${configured} item(s) configured, ${alreadyGood} already good.`);
|
|
444
|
+
} else if (alreadyGood > 0) {
|
|
445
|
+
console.log(` ${c.green}✅ Everything is set up!${c.reset} ${alreadyGood} item(s) verified.`);
|
|
446
|
+
} else {
|
|
447
|
+
console.log(` ${c.dim}No changes made.${c.reset}`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
console.log(`\n ${c.bold}Next steps:${c.reset}`);
|
|
451
|
+
console.log(` ${c.dim}Fill docs:${c.reset} ${c.cyan}docguard diagnose${c.reset}`);
|
|
452
|
+
console.log(` ${c.dim}Validate:${c.reset} ${c.cyan}docguard guard${c.reset}`);
|
|
453
|
+
console.log(` ${c.dim}Check score:${c.reset} ${c.cyan}docguard score${c.reset}`);
|
|
454
|
+
console.log('');
|
|
455
|
+
}
|
package/cli/docguard.mjs
CHANGED
|
@@ -38,6 +38,8 @@ import { runDiagnose } from './commands/diagnose.mjs';
|
|
|
38
38
|
import { runPublish } from './commands/publish.mjs';
|
|
39
39
|
import { runTrace } from './commands/trace.mjs';
|
|
40
40
|
import { runLlms } from './commands/llms.mjs';
|
|
41
|
+
import { runSetup } from './commands/setup.mjs';
|
|
42
|
+
import { ensureSkills } from './ensure-skills.mjs';
|
|
41
43
|
|
|
42
44
|
// ── Shared constants (imported to break circular dependencies) ──────────
|
|
43
45
|
import { c, PROFILES } from './shared.mjs';
|
|
@@ -218,6 +220,7 @@ function printHelp() {
|
|
|
218
220
|
|
|
219
221
|
${c.bold}Getting Started:${c.reset}
|
|
220
222
|
${c.green}init${c.reset} Initialize CDD docs (interactive setup)
|
|
223
|
+
${c.green}setup${c.reset} Full onboarding wizard (skills, integrations, hooks)
|
|
221
224
|
${c.green}generate${c.reset} Reverse-engineer canonical docs from existing code
|
|
222
225
|
|
|
223
226
|
${c.bold}Enforcement:${c.reset}
|
|
@@ -376,6 +379,11 @@ async function main() {
|
|
|
376
379
|
|
|
377
380
|
const config = loadConfig(projectDir);
|
|
378
381
|
|
|
382
|
+
// Silent auto-check: install skills/commands if missing
|
|
383
|
+
if (command !== 'setup' && command !== 'init') {
|
|
384
|
+
ensureSkills(projectDir, flags);
|
|
385
|
+
}
|
|
386
|
+
|
|
379
387
|
switch (command) {
|
|
380
388
|
case 'audit':
|
|
381
389
|
// audit is an alias for guard — guard does everything the old audit did + 50 more checks
|
|
@@ -384,6 +392,10 @@ async function main() {
|
|
|
384
392
|
case 'init':
|
|
385
393
|
await runInit(projectDir, config, flags);
|
|
386
394
|
break;
|
|
395
|
+
case 'setup':
|
|
396
|
+
case 'onboard':
|
|
397
|
+
await runSetup(projectDir, config, flags);
|
|
398
|
+
break;
|
|
387
399
|
case 'guard':
|
|
388
400
|
runGuard(projectDir, config, flags);
|
|
389
401
|
break;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure Skills — Silent auto-check for DocGuard AI skills and commands
|
|
3
|
+
*
|
|
4
|
+
* Called before every command execution. If skills or commands are missing,
|
|
5
|
+
* copies them from the package's bundled assets into the project directory.
|
|
6
|
+
*
|
|
7
|
+
* Zero dependencies — pure Node.js built-ins only.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, cpSync } from 'node:fs';
|
|
11
|
+
import { resolve, dirname, join } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { c } from './shared.mjs';
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// Source locations in the npm package
|
|
19
|
+
const SKILLS_SOURCE = resolve(__dirname, '..', 'extensions', 'spec-kit-docguard', 'skills');
|
|
20
|
+
const COMMANDS_SOURCE = resolve(__dirname, '..', 'commands');
|
|
21
|
+
|
|
22
|
+
// Destination in the user's project
|
|
23
|
+
const SKILLS_DEST = '.agent/skills';
|
|
24
|
+
const COMMANDS_DEST = 'commands';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Silently ensure skills and commands are installed in the project.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} projectDir - The project root directory
|
|
30
|
+
* @param {object} flags - CLI flags (format, etc.)
|
|
31
|
+
* @returns {{ skillsInstalled: boolean, commandsInstalled: boolean }}
|
|
32
|
+
*/
|
|
33
|
+
export function ensureSkills(projectDir, flags = {}) {
|
|
34
|
+
const result = { skillsInstalled: false, commandsInstalled: false };
|
|
35
|
+
const silent = flags.format === 'json';
|
|
36
|
+
|
|
37
|
+
// ── Skills ────────────────────────────────────────────────────────────
|
|
38
|
+
const skillsCheck = resolve(projectDir, SKILLS_DEST, 'docguard-guard', 'SKILL.md');
|
|
39
|
+
if (!existsSync(skillsCheck) && existsSync(SKILLS_SOURCE)) {
|
|
40
|
+
try {
|
|
41
|
+
const skillDirs = readdirSync(SKILLS_SOURCE).filter(d =>
|
|
42
|
+
existsSync(resolve(SKILLS_SOURCE, d, 'SKILL.md'))
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
for (const skillDir of skillDirs) {
|
|
46
|
+
const destDir = resolve(projectDir, SKILLS_DEST, skillDir);
|
|
47
|
+
if (!existsSync(destDir)) {
|
|
48
|
+
mkdirSync(destDir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
const srcSkill = resolve(SKILLS_SOURCE, skillDir, 'SKILL.md');
|
|
51
|
+
const destSkill = resolve(destDir, 'SKILL.md');
|
|
52
|
+
if (!existsSync(destSkill)) {
|
|
53
|
+
writeFileSync(destSkill, readFileSync(srcSkill, 'utf-8'), 'utf-8');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
result.skillsInstalled = true;
|
|
58
|
+
if (!silent) {
|
|
59
|
+
console.log(` ${c.cyan}✨ DocGuard AI skills installed → ${SKILLS_DEST}/${c.reset}`);
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// Silent failure — skills are optional enhancement
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Slash Commands ────────────────────────────────────────────────────
|
|
67
|
+
const commandsCheck = resolve(projectDir, COMMANDS_DEST, 'docguard.guard.md');
|
|
68
|
+
if (!existsSync(commandsCheck) && existsSync(COMMANDS_SOURCE)) {
|
|
69
|
+
try {
|
|
70
|
+
const commandFiles = readdirSync(COMMANDS_SOURCE).filter(f => f.endsWith('.md'));
|
|
71
|
+
|
|
72
|
+
if (commandFiles.length > 0) {
|
|
73
|
+
const destDir = resolve(projectDir, COMMANDS_DEST);
|
|
74
|
+
if (!existsSync(destDir)) {
|
|
75
|
+
mkdirSync(destDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const file of commandFiles) {
|
|
79
|
+
const destPath = resolve(destDir, file);
|
|
80
|
+
if (!existsSync(destPath)) {
|
|
81
|
+
writeFileSync(destPath, readFileSync(resolve(COMMANDS_SOURCE, file), 'utf-8'), 'utf-8');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
result.commandsInstalled = true;
|
|
86
|
+
if (!silent) {
|
|
87
|
+
console.log(` ${c.cyan}✨ DocGuard slash commands installed → ${COMMANDS_DEST}/${c.reset}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
// Silent failure — commands are optional enhancement
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
@@ -33,8 +33,8 @@ const THRESHOLDS = {
|
|
|
33
33
|
passiveVoiceRatio: { warn: 0.25, label: 'Passive voice ratio' }, // >25% passive = warn
|
|
34
34
|
ambiguousPronounRatio: { warn: 0.15, label: 'Ambiguous pronoun ratio' }, // >15% ambiguous pronouns = warn
|
|
35
35
|
atomicityScore: { warn: 0.35, label: 'Non-atomic sentence ratio' }, // >35% compound sentences = warn
|
|
36
|
-
fleschReadingEase: { warn:
|
|
37
|
-
fleschKincaidGrade: { warn:
|
|
36
|
+
fleschReadingEase: { warn: 5, label: 'Flesch reading ease' }, // <5 = truly unreadable prose (tech docs typically score 10-30)
|
|
37
|
+
fleschKincaidGrade: { warn: 22, label: 'Flesch-Kincaid grade' }, // >22 = PhD level+ (tech docs typically 14-20)
|
|
38
38
|
avgSentenceLength: { warn: 30, label: 'Avg sentence length' }, // >30 words = too long
|
|
39
39
|
negationLoad: { warn: 0.20, label: 'Negation load' }, // >20% sentences with negation = warn
|
|
40
40
|
conditionalLoad: { warn: 0.30, label: 'Conditional load' }, // >30% sentences conditional = warn
|
|
@@ -118,18 +118,53 @@ export function validateDocsSync(projectDir, config) {
|
|
|
118
118
|
if (!['.ts', '.js', '.mjs'].includes(ext)) continue;
|
|
119
119
|
|
|
120
120
|
// Skip index/middleware files
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
121
|
+
const rawName = basename(file, ext).toLowerCase();
|
|
122
|
+
if (rawName === 'index' || rawName === 'middleware' || rawName.startsWith('_')) continue;
|
|
123
123
|
|
|
124
124
|
results.total++;
|
|
125
125
|
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
126
|
+
// Strategy 1: Parse the route file for actual route paths
|
|
127
|
+
// Look for router.get('/path'), app.post('/path'), etc.
|
|
128
|
+
let routeFileContent = '';
|
|
129
|
+
try { routeFileContent = readFileSync(file, 'utf-8').toLowerCase(); } catch { /* skip */ }
|
|
130
|
+
|
|
131
|
+
const actualRoutes = [];
|
|
132
|
+
const routeDefRegex = /(?:router|app|route)\s*\.\s*(?:get|post|put|delete|patch|all|use)\s*\(\s*['"`](\/[^'"`]*)['"`]/gi;
|
|
133
|
+
let routeMatch;
|
|
134
|
+
while ((routeMatch = routeDefRegex.exec(routeFileContent)) !== null) {
|
|
135
|
+
actualRoutes.push(routeMatch[1]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let matched = false;
|
|
139
|
+
|
|
140
|
+
if (actualRoutes.length > 0) {
|
|
141
|
+
// Check if ANY of the actual route paths appear in the OpenAPI spec
|
|
142
|
+
matched = actualRoutes.some(route => {
|
|
143
|
+
// Normalize: /api/conversations/:id → /api/conversations
|
|
144
|
+
const basePath = route.replace(/\/:[^/]+/g, '').replace(/\/{[^}]+}/g, '');
|
|
145
|
+
return openapiContent.includes(basePath) || openapiContent.includes(route);
|
|
146
|
+
});
|
|
147
|
+
} else {
|
|
148
|
+
// Strategy 2 (fallback): Strip common suffixes and check filename
|
|
149
|
+
// userRoutes.ts → 'user', conversationRoutes.ts → 'conversation'
|
|
150
|
+
const cleanName = rawName
|
|
151
|
+
.replace(/routes?$/i, '')
|
|
152
|
+
.replace(/controllers?$/i, '')
|
|
153
|
+
.replace(/handlers?$/i, '')
|
|
154
|
+
.replace(/router$/i, '');
|
|
155
|
+
|
|
156
|
+
if (cleanName.length > 0) {
|
|
157
|
+
matched = openapiContent.includes(`/${cleanName}`) ||
|
|
158
|
+
openapiContent.includes(`"${cleanName}"`) ||
|
|
159
|
+
openapiContent.includes(`'${cleanName}'`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (matched) {
|
|
129
164
|
results.passed++;
|
|
130
165
|
} else {
|
|
131
166
|
results.warnings.push(
|
|
132
|
-
`Route file ${basename(file)} exists but no
|
|
167
|
+
`Route file ${basename(file)} exists but no matching paths found in ${openapiFile}. ` +
|
|
133
168
|
`Run your spec generator (e.g., zod-to-openapi) to update the API spec`
|
|
134
169
|
);
|
|
135
170
|
}
|
|
@@ -110,14 +110,19 @@ function checkSkippedTests(projectDir) {
|
|
|
110
110
|
const isSkipped = SKIP_PATTERNS.some(p => p.test(line));
|
|
111
111
|
if (!isSkipped) continue;
|
|
112
112
|
|
|
113
|
-
// Check surrounding lines (
|
|
114
|
-
|
|
115
|
-
const
|
|
113
|
+
// Check surrounding lines (3 above, 1 below, and inline) for explanation
|
|
114
|
+
// Developers commonly place block comments above the skip call
|
|
115
|
+
const surroundingLines = [];
|
|
116
|
+
for (let j = Math.max(0, i - 3); j <= Math.min(lines.length - 1, i + 1); j++) {
|
|
117
|
+
surroundingLines.push(lines[j]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Also check for block comment pattern: /* REASON: ... */ or /** ... REASON: ... */
|
|
121
|
+
const blockCommentPattern = /\/\*[\s\S]*?(REASON|SKIP|TODO|FIXME|NOTE|WHY)\s*:/i;
|
|
116
122
|
|
|
117
123
|
const hasReason =
|
|
118
|
-
SKIP_REASON_PATTERN.test(
|
|
119
|
-
|
|
120
|
-
SKIP_REASON_PATTERN.test(nextLine);
|
|
124
|
+
surroundingLines.some(l => SKIP_REASON_PATTERN.test(l)) ||
|
|
125
|
+
blockCommentPattern.test(surroundingLines.join('\n'));
|
|
121
126
|
|
|
122
127
|
if (hasReason) {
|
|
123
128
|
skippedWithReason++;
|
|
@@ -8,7 +8,7 @@ Enterprise-grade Canonical-Driven Development (CDD) enforcement for [Spec Kit](h
|
|
|
8
8
|
- **4 AI Skills** — Enterprise-grade AI behavior protocols (not just step-lists)
|
|
9
9
|
- **3 Bash Scripts** — JSON-output orchestration for AI consumption
|
|
10
10
|
- **Workflow Chaining** — YAML handoffs enable guard → fix → review → score flows
|
|
11
|
-
- **Spec Kit Hooks** —
|
|
11
|
+
- **Spec Kit Hooks** — Quality gate integrations at implement, tasks, and review phases
|
|
12
12
|
- **Zero Dependencies** — Pure Node.js built-ins only
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
@@ -40,16 +40,14 @@ docguard score
|
|
|
40
40
|
|
|
41
41
|
## Commands
|
|
42
42
|
|
|
43
|
-
| Command | Purpose |
|
|
44
|
-
|
|
45
|
-
| `docguard.guard` | Run 19-validator quality gate with severity triage |
|
|
46
|
-
| `docguard.fix` | AI-driven documentation repair with codebase research |
|
|
47
|
-
| `docguard.review` | Cross-document semantic consistency analysis (read-only) |
|
|
48
|
-
| `docguard.score` | CDD maturity score with ROI improvement roadmap |
|
|
49
|
-
| `docguard.diagnose` | Diagnose issues + generate multi-perspective AI prompts |
|
|
50
|
-
| `docguard.generate` | Reverse-engineer canonical docs from codebase |
|
|
51
|
-
| `docguard.init` | Initialize CDD structure in a project |
|
|
52
|
-
| `docguard.trace` | Generate requirements traceability matrix |
|
|
43
|
+
| Command | Alias | Purpose |
|
|
44
|
+
|---------|-------|---------|
|
|
45
|
+
| `speckit.docguard.guard` | `docguard.guard` | Run 19-validator quality gate with severity triage |
|
|
46
|
+
| `speckit.docguard.fix` | `docguard.fix` | AI-driven documentation repair with codebase research |
|
|
47
|
+
| `speckit.docguard.review` | `docguard.review` | Cross-document semantic consistency analysis (read-only) |
|
|
48
|
+
| `speckit.docguard.score` | `docguard.score` | CDD maturity score with ROI improvement roadmap |
|
|
49
|
+
| `speckit.docguard.diagnose` | — | Diagnose issues + generate multi-perspective AI prompts |
|
|
50
|
+
| `speckit.docguard.generate` | — | Reverse-engineer canonical docs from codebase |
|
|
53
51
|
|
|
54
52
|
## AI Skills
|
|
55
53
|
|
|
@@ -72,12 +70,12 @@ DocGuard integrates into the spec-kit workflow through hooks:
|
|
|
72
70
|
|
|
73
71
|
```yaml
|
|
74
72
|
hooks:
|
|
75
|
-
after_implement: #
|
|
76
|
-
command: docguard.guard
|
|
73
|
+
after_implement: # Optional — quality gate after /speckit.implement
|
|
74
|
+
command: speckit.docguard.guard
|
|
77
75
|
before_tasks: # Optional — review docs before task generation
|
|
78
|
-
command: docguard.review
|
|
76
|
+
command: speckit.docguard.review
|
|
79
77
|
after_tasks: # Optional — show score after tasks
|
|
80
|
-
command: docguard.score
|
|
78
|
+
command: speckit.docguard.score
|
|
81
79
|
```
|
|
82
80
|
|
|
83
81
|
### Workflow Chaining
|
|
@@ -3,7 +3,7 @@ schema_version: "1.0"
|
|
|
3
3
|
extension:
|
|
4
4
|
id: "docguard"
|
|
5
5
|
name: "DocGuard — CDD Enforcement"
|
|
6
|
-
version: "0.9.
|
|
6
|
+
version: "0.9.7"
|
|
7
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."
|
|
8
8
|
author: "Ricardo Accioly"
|
|
9
9
|
repository: "https://github.com/raccioly/docguard"
|
|
@@ -21,78 +21,62 @@ requires:
|
|
|
21
21
|
|
|
22
22
|
provides:
|
|
23
23
|
commands:
|
|
24
|
-
- name: "docguard.guard"
|
|
24
|
+
- name: "speckit.docguard.guard"
|
|
25
25
|
file: "commands/guard.md"
|
|
26
26
|
description: "Run 19-validator quality gate with severity triage and remediation plan"
|
|
27
|
-
aliases: ["
|
|
27
|
+
aliases: ["docguard.guard"]
|
|
28
28
|
|
|
29
|
-
- name: "docguard.fix"
|
|
30
|
-
file: "commands/
|
|
29
|
+
- name: "speckit.docguard.fix"
|
|
30
|
+
file: "commands/generate.md"
|
|
31
31
|
description: "AI-driven documentation repair with codebase research and validation loops"
|
|
32
|
-
aliases: ["
|
|
32
|
+
aliases: ["docguard.fix"]
|
|
33
33
|
|
|
34
|
-
- name: "docguard.review"
|
|
35
|
-
file: "commands/
|
|
34
|
+
- name: "speckit.docguard.review"
|
|
35
|
+
file: "commands/diagnose.md"
|
|
36
36
|
description: "Cross-document semantic consistency analysis (read-only)"
|
|
37
|
-
aliases: ["
|
|
37
|
+
aliases: ["docguard.review"]
|
|
38
38
|
|
|
39
|
-
- name: "docguard.score"
|
|
39
|
+
- name: "speckit.docguard.score"
|
|
40
40
|
file: "commands/score.md"
|
|
41
41
|
description: "CDD maturity score with ROI-based improvement roadmap"
|
|
42
|
-
aliases: ["
|
|
42
|
+
aliases: ["docguard.score"]
|
|
43
43
|
|
|
44
44
|
- name: "speckit.docguard.diagnose"
|
|
45
|
-
file: "commands/
|
|
45
|
+
file: "commands/diagnose.md"
|
|
46
46
|
description: "Diagnose all issues and generate a complete AI remediation plan"
|
|
47
47
|
|
|
48
48
|
- name: "speckit.docguard.generate"
|
|
49
|
-
file: "commands/
|
|
49
|
+
file: "commands/generate.md"
|
|
50
50
|
description: "Reverse-engineer canonical docs from existing codebase"
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
- name: "docguard-guard"
|
|
54
|
-
path: "skills/docguard-guard/SKILL.md"
|
|
55
|
-
description: "19-validator quality gate with severity triage and structured reporting"
|
|
56
|
-
|
|
57
|
-
- name: "docguard-fix"
|
|
58
|
-
path: "skills/docguard-fix/SKILL.md"
|
|
59
|
-
description: "AI-driven documentation repair with research workflow and validation loops"
|
|
60
|
-
|
|
61
|
-
- name: "docguard-review"
|
|
62
|
-
path: "skills/docguard-review/SKILL.md"
|
|
63
|
-
description: "Cross-document semantic consistency analysis with quality scoring"
|
|
64
|
-
|
|
65
|
-
- name: "docguard-score"
|
|
66
|
-
path: "skills/docguard-score/SKILL.md"
|
|
67
|
-
description: "CDD maturity assessment with ROI-based improvement roadmap"
|
|
68
|
-
|
|
52
|
+
# Helper scripts for CI/CD and automation
|
|
69
53
|
scripts:
|
|
70
54
|
- name: "docguard-check-docs"
|
|
71
|
-
|
|
55
|
+
file: "scripts/bash/docguard-check-docs.sh"
|
|
72
56
|
description: "Discover project docs, return JSON inventory with metadata"
|
|
73
57
|
|
|
74
58
|
- name: "docguard-suggest-fix"
|
|
75
|
-
|
|
59
|
+
file: "scripts/bash/docguard-suggest-fix.sh"
|
|
76
60
|
description: "Run guard, parse results, output prioritized fix suggestions"
|
|
77
61
|
|
|
78
62
|
- name: "docguard-init-doc"
|
|
79
|
-
|
|
63
|
+
file: "scripts/bash/docguard-init-doc.sh"
|
|
80
64
|
description: "Initialize a new canonical document with metadata header"
|
|
81
65
|
|
|
82
66
|
hooks:
|
|
83
67
|
after_implement:
|
|
84
|
-
command: "docguard.guard"
|
|
85
|
-
optional:
|
|
86
|
-
prompt: "Run DocGuard validation after implementation"
|
|
87
|
-
description: "
|
|
68
|
+
command: "speckit.docguard.guard"
|
|
69
|
+
optional: true
|
|
70
|
+
prompt: "Run DocGuard validation after implementation?"
|
|
71
|
+
description: "Quality gate — ensures docs stay in sync with code"
|
|
88
72
|
|
|
89
73
|
before_tasks:
|
|
90
|
-
command: "docguard.review"
|
|
74
|
+
command: "speckit.docguard.review"
|
|
91
75
|
optional: true
|
|
92
76
|
prompt: "Review documentation consistency before generating tasks?"
|
|
93
77
|
|
|
94
78
|
after_tasks:
|
|
95
|
-
command: "docguard.score"
|
|
79
|
+
command: "speckit.docguard.score"
|
|
96
80
|
optional: true
|
|
97
81
|
prompt: "Show CDD maturity score after task generation?"
|
|
98
82
|
|
|
@@ -106,4 +90,3 @@ tags:
|
|
|
106
90
|
- "ai-agents"
|
|
107
91
|
- "enforcement"
|
|
108
92
|
- "spec-kit"
|
|
109
|
-
- "skills"
|
|
@@ -14,16 +14,16 @@ hooks:
|
|
|
14
14
|
# This ensures documentation stays in sync with code changes
|
|
15
15
|
after_implement:
|
|
16
16
|
- extension: docguard
|
|
17
|
-
command: docguard.guard
|
|
17
|
+
command: speckit.docguard.guard
|
|
18
18
|
description: "Validate documentation passes CDD standards after implementation"
|
|
19
19
|
enabled: true
|
|
20
|
-
optional:
|
|
21
|
-
prompt: "Run DocGuard guard to verify documentation quality after implementation changes"
|
|
20
|
+
optional: true
|
|
21
|
+
prompt: "Run DocGuard guard to verify documentation quality after implementation changes?"
|
|
22
22
|
|
|
23
23
|
# Run DocGuard review before /speckit.tasks to catch doc drift early
|
|
24
24
|
before_tasks:
|
|
25
25
|
- extension: docguard
|
|
26
|
-
command: docguard.review
|
|
26
|
+
command: speckit.docguard.review
|
|
27
27
|
description: "Review documentation consistency before generating tasks"
|
|
28
28
|
enabled: true
|
|
29
29
|
optional: true # Optional — user can skip
|
|
@@ -32,7 +32,7 @@ hooks:
|
|
|
32
32
|
# Run DocGuard guard after /speckit.tasks to validate doc coverage
|
|
33
33
|
after_tasks:
|
|
34
34
|
- extension: docguard
|
|
35
|
-
command: docguard.score
|
|
35
|
+
command: speckit.docguard.score
|
|
36
36
|
description: "Show CDD maturity score after task generation"
|
|
37
37
|
enabled: true
|
|
38
38
|
optional: true
|