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.
- package/cli/commands/diagnose.mjs +64 -24
- package/cli/commands/fix.mjs +1 -1
- package/cli/commands/guard.mjs +12 -1
- package/cli/commands/hooks.mjs +2 -2
- package/cli/commands/init.mjs +94 -73
- package/cli/commands/score.mjs +58 -16
- package/cli/commands/setup.mjs +60 -30
- package/cli/docguard.mjs +14 -5
- package/cli/ensure-skills.mjs +231 -13
- package/cli/scanners/speckit.mjs +1 -1
- package/cli/shared-ignore.mjs +76 -0
- package/cli/validators/architecture.mjs +21 -6
- package/cli/validators/doc-quality.mjs +1 -1
- package/cli/validators/docs-diff.mjs +79 -12
- package/cli/validators/schema-sync.mjs +1 -1
- package/cli/validators/security.mjs +49 -1
- package/cli/validators/todo-tracking.mjs +41 -15
- package/extensions/spec-kit-docguard/extension.yml +6 -2
- package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +2 -1
- package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +9 -4
- package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +5 -1
- package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +2 -1
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { c } from '../shared.mjs';
|
|
17
17
|
import { runGuardInternal } from './guard.mjs';
|
|
18
18
|
import { runScoreInternal } from './score.mjs';
|
|
19
|
+
import { detectAgentMode, isSpecKitInitialized } from '../ensure-skills.mjs';
|
|
19
20
|
import { existsSync, readFileSync } from 'node:fs';
|
|
20
21
|
import { resolve, dirname } from 'node:path';
|
|
21
22
|
import { fileURLToPath } from 'node:url';
|
|
@@ -35,23 +36,26 @@ const VALIDATOR_TO_DOC = {
|
|
|
35
36
|
'Freshness': null, // freshness — maps to stale doc
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
// Actionable fix instructions per validator
|
|
39
|
+
// Actionable fix instructions per validator (LLM-first: includes both skill and CLI commands)
|
|
39
40
|
const FIX_INSTRUCTIONS = {
|
|
40
41
|
'Structure': {
|
|
41
42
|
action: 'Create missing files',
|
|
42
43
|
command: 'docguard init',
|
|
44
|
+
llmCommand: '/docguard.init',
|
|
43
45
|
description: 'Run init to create missing documentation templates.',
|
|
44
46
|
autoFixable: true,
|
|
45
47
|
},
|
|
46
48
|
'Doc Sections': {
|
|
47
49
|
action: 'Fill document sections',
|
|
48
50
|
command: 'docguard fix --doc',
|
|
49
|
-
|
|
51
|
+
llmCommand: '/docguard.fix --doc',
|
|
52
|
+
description: 'Documents exist but have missing or placeholder sections. Use the docguard-fix skill to generate content.',
|
|
50
53
|
autoFixable: false,
|
|
51
54
|
},
|
|
52
55
|
'Docs-Sync': {
|
|
53
56
|
action: 'Sync documentation references',
|
|
54
57
|
command: 'docguard fix --doc architecture',
|
|
58
|
+
llmCommand: '/docguard.fix --doc architecture',
|
|
55
59
|
description: 'Documentation references are out of sync with code. Review and update component maps.',
|
|
56
60
|
autoFixable: false,
|
|
57
61
|
},
|
|
@@ -68,35 +72,44 @@ const FIX_INSTRUCTIONS = {
|
|
|
68
72
|
'Test-Spec': {
|
|
69
73
|
action: 'Update TEST-SPEC.md',
|
|
70
74
|
command: 'docguard fix --doc test-spec',
|
|
75
|
+
llmCommand: '/docguard.fix --doc test-spec',
|
|
71
76
|
description: 'Test documentation needs updating to match actual test structure.',
|
|
72
77
|
autoFixable: false,
|
|
73
78
|
},
|
|
74
79
|
'Environment': {
|
|
75
80
|
action: 'Update ENVIRONMENT.md',
|
|
76
81
|
command: 'docguard fix --doc environment',
|
|
82
|
+
llmCommand: '/docguard.fix --doc environment',
|
|
77
83
|
description: 'Environment documentation is missing or incomplete.',
|
|
78
84
|
autoFixable: false,
|
|
79
85
|
},
|
|
80
86
|
'Security': {
|
|
81
87
|
action: 'Update SECURITY.md',
|
|
82
88
|
command: 'docguard fix --doc security',
|
|
89
|
+
llmCommand: '/docguard.fix --doc security',
|
|
83
90
|
description: 'Security documentation needs updating.',
|
|
84
91
|
autoFixable: false,
|
|
85
92
|
},
|
|
86
93
|
'Architecture': {
|
|
87
94
|
action: 'Update ARCHITECTURE.md',
|
|
88
95
|
command: 'docguard fix --doc architecture',
|
|
96
|
+
llmCommand: '/docguard.fix --doc architecture',
|
|
89
97
|
description: 'Architecture documentation doesn\'t match the codebase.',
|
|
90
98
|
autoFixable: false,
|
|
91
99
|
},
|
|
92
100
|
'Freshness': {
|
|
93
101
|
action: 'Review stale documents',
|
|
102
|
+
command: 'docguard fix --doc',
|
|
103
|
+
llmCommand: '/docguard.fix --doc',
|
|
94
104
|
description: 'Documents haven\'t been reviewed since recent code changes. Re-run fix --doc for each stale doc.',
|
|
95
105
|
autoFixable: false,
|
|
96
106
|
},
|
|
97
107
|
};
|
|
98
108
|
|
|
99
109
|
export function runDiagnose(projectDir, config, flags) {
|
|
110
|
+
// ── Step 0: Detect agent mode (LLM-first) ──
|
|
111
|
+
const agentMode = detectAgentMode(projectDir);
|
|
112
|
+
|
|
100
113
|
// ── Step 1: Run guard internally ──
|
|
101
114
|
let guardData = runGuardInternal(projectDir, config);
|
|
102
115
|
const scoreData = runScoreInternal(projectDir, config);
|
|
@@ -140,10 +153,15 @@ export function runDiagnose(projectDir, config, flags) {
|
|
|
140
153
|
}
|
|
141
154
|
}
|
|
142
155
|
} else if (!shouldAutoFix && (hasStructural || autoFixable.length > 0) && (!flags.format || flags.format === 'text')) {
|
|
143
|
-
// Suggest-only mode: tell user what they can do
|
|
156
|
+
// Suggest-only mode: tell user what they can do (LLM-first)
|
|
144
157
|
console.log(` ${c.yellow}💡 ${autoFixable.length + (hasStructural ? 1 : 0)} issue(s) can be auto-fixed.${c.reset} Run with ${c.cyan}--auto${c.reset} to create/regenerate docs, or manually:`);
|
|
145
|
-
if (
|
|
146
|
-
|
|
158
|
+
if (agentMode === 'llm') {
|
|
159
|
+
if (hasStructural) console.log(` ${c.dim}/docguard.init${c.reset}`);
|
|
160
|
+
if (autoFixable.length > 0) console.log(` ${c.dim}/docguard.fix${c.reset}`);
|
|
161
|
+
} else {
|
|
162
|
+
if (hasStructural) console.log(` ${c.dim}docguard init --dir .${c.reset}`);
|
|
163
|
+
if (autoFixable.length > 0) console.log(` ${c.dim}docguard generate --dir . --force${c.reset}`);
|
|
164
|
+
}
|
|
147
165
|
console.log('');
|
|
148
166
|
}
|
|
149
167
|
}
|
|
@@ -157,7 +175,9 @@ export function runDiagnose(projectDir, config, flags) {
|
|
|
157
175
|
const docMap = { 'architecture': 'architecture', 'data-model': 'data-model', 'security': 'security', 'test-spec': 'test-spec', 'environment': 'environment' };
|
|
158
176
|
issue.docTarget = docMap[docName] || null;
|
|
159
177
|
if (issue.docTarget) {
|
|
160
|
-
issue.command =
|
|
178
|
+
issue.command = agentMode === 'llm'
|
|
179
|
+
? `/docguard.fix --doc ${issue.docTarget}`
|
|
180
|
+
: `docguard fix --doc ${issue.docTarget}`;
|
|
161
181
|
}
|
|
162
182
|
}
|
|
163
183
|
}
|
|
@@ -167,9 +187,9 @@ export function runDiagnose(projectDir, config, flags) {
|
|
|
167
187
|
if (flags.format === 'json') {
|
|
168
188
|
outputJSON(guardData, scoreData, issues);
|
|
169
189
|
} else if (flags.format === 'prompt') {
|
|
170
|
-
outputPrompt(projectDir, guardData, scoreData, issues, flags);
|
|
190
|
+
outputPrompt(projectDir, guardData, scoreData, issues, flags, agentMode);
|
|
171
191
|
} else {
|
|
172
|
-
outputText(projectDir, guardData, scoreData, issues, flags);
|
|
192
|
+
outputText(projectDir, guardData, scoreData, issues, flags, agentMode);
|
|
173
193
|
}
|
|
174
194
|
}
|
|
175
195
|
|
|
@@ -191,6 +211,7 @@ function collectIssues(guardData) {
|
|
|
191
211
|
message: err,
|
|
192
212
|
action: fixInfo.action,
|
|
193
213
|
command: fixInfo.command || null,
|
|
214
|
+
llmCommand: fixInfo.llmCommand || null,
|
|
194
215
|
docTarget,
|
|
195
216
|
autoFixable: fixInfo.autoFixable || false,
|
|
196
217
|
});
|
|
@@ -202,6 +223,7 @@ function collectIssues(guardData) {
|
|
|
202
223
|
message: warn,
|
|
203
224
|
action: fixInfo.action,
|
|
204
225
|
command: fixInfo.command || null,
|
|
226
|
+
llmCommand: fixInfo.llmCommand || null,
|
|
205
227
|
docTarget,
|
|
206
228
|
autoFixable: fixInfo.autoFixable || false,
|
|
207
229
|
});
|
|
@@ -233,14 +255,18 @@ function outputJSON(guardData, scoreData, issues) {
|
|
|
233
255
|
console.log(JSON.stringify(result, null, 2));
|
|
234
256
|
}
|
|
235
257
|
|
|
236
|
-
function outputText(projectDir, guardData, scoreData, issues, flags) {
|
|
258
|
+
function outputText(projectDir, guardData, scoreData, issues, flags, agentMode = 'llm') {
|
|
237
259
|
console.log(`${c.bold}🔍 DocGuard Diagnose — ${guardData.project}${c.reset}`);
|
|
238
|
-
console.log(`${c.dim} Profile: ${guardData.profile} | Score: ${scoreData.score}/100 (${scoreData.grade})${c.reset}`);
|
|
260
|
+
console.log(`${c.dim} Profile: ${guardData.profile} | Score: ${scoreData.score}/100 (${scoreData.grade}) | Mode: ${agentMode.toUpperCase()}${c.reset}`);
|
|
239
261
|
console.log(`${c.dim} Guard: ${guardData.passed}/${guardData.total} passed | Status: ${guardData.status}${c.reset}\n`);
|
|
240
262
|
|
|
241
263
|
if (issues.length === 0) {
|
|
242
264
|
console.log(` ${c.green}${c.bold}✅ All clear!${c.reset} No issues found.\n`);
|
|
243
|
-
|
|
265
|
+
if (agentMode === 'llm') {
|
|
266
|
+
console.log(` ${c.dim}Your documentation is healthy. Use ${c.cyan}/docguard.guard${c.dim} to re-validate after changes.${c.reset}\n`);
|
|
267
|
+
} else {
|
|
268
|
+
console.log(` ${c.dim}Your documentation is healthy. Run \`docguard score --tax\` to see maintenance estimate.${c.reset}\n`);
|
|
269
|
+
}
|
|
244
270
|
return;
|
|
245
271
|
}
|
|
246
272
|
|
|
@@ -252,7 +278,8 @@ function outputText(projectDir, guardData, scoreData, issues, flags) {
|
|
|
252
278
|
console.log(` ${c.red}${c.bold}Errors (${errors.length}):${c.reset}`);
|
|
253
279
|
for (const e of errors) {
|
|
254
280
|
console.log(` ${c.red}✗${c.reset} [${e.validator}] ${e.message}`);
|
|
255
|
-
|
|
281
|
+
const cmd = agentMode === 'llm' && e.llmCommand ? e.llmCommand : e.command;
|
|
282
|
+
if (cmd) console.log(` ${c.dim}Fix: ${cmd}${c.reset}`);
|
|
256
283
|
}
|
|
257
284
|
console.log('');
|
|
258
285
|
}
|
|
@@ -261,19 +288,21 @@ function outputText(projectDir, guardData, scoreData, issues, flags) {
|
|
|
261
288
|
console.log(` ${c.yellow}${c.bold}Warnings (${warnings.length}):${c.reset}`);
|
|
262
289
|
for (const w of warnings) {
|
|
263
290
|
console.log(` ${c.yellow}⚠${c.reset} [${w.validator}] ${w.message}`);
|
|
264
|
-
|
|
291
|
+
const cmd = agentMode === 'llm' && w.llmCommand ? w.llmCommand : w.command;
|
|
292
|
+
if (cmd) console.log(` ${c.dim}Fix: ${cmd}${c.reset}`);
|
|
265
293
|
}
|
|
266
294
|
console.log('');
|
|
267
295
|
}
|
|
268
296
|
|
|
269
|
-
// ── Remediation Plan ──
|
|
297
|
+
// ── Remediation Plan (LLM-first) ──
|
|
270
298
|
const commands = [...new Set(issues.filter(i => i.command).map(i => i.command))];
|
|
271
299
|
if (commands.length > 0) {
|
|
272
300
|
console.log(` ${c.bold}📋 Remediation Plan:${c.reset}`);
|
|
273
301
|
for (let i = 0; i < commands.length; i++) {
|
|
274
302
|
console.log(` ${c.cyan}${i + 1}. ${commands[i]}${c.reset}`);
|
|
275
303
|
}
|
|
276
|
-
|
|
304
|
+
const verifyCmd = agentMode === 'llm' ? '/docguard.guard' : 'docguard guard';
|
|
305
|
+
console.log(` ${c.cyan}${commands.length + 1}. ${verifyCmd}${c.reset} ${c.dim}← verify fixes${c.reset}`);
|
|
277
306
|
console.log('');
|
|
278
307
|
}
|
|
279
308
|
|
|
@@ -282,15 +311,15 @@ function outputText(projectDir, guardData, scoreData, issues, flags) {
|
|
|
282
311
|
// Multi-perspective debate prompts (AITPG/TRACE-inspired)
|
|
283
312
|
console.log(` ${c.bold}🤖 Multi-Perspective AI Debate Prompt:${c.reset}`);
|
|
284
313
|
console.log(` ${c.dim}Copy everything below and paste to your AI agent:${c.reset}\n`);
|
|
285
|
-
outputDebatePrompt(projectDir, guardData, scoreData, issues);
|
|
314
|
+
outputDebatePrompt(projectDir, guardData, scoreData, issues, agentMode);
|
|
286
315
|
} else {
|
|
287
316
|
console.log(` ${c.bold}🤖 AI-Ready Prompt:${c.reset}`);
|
|
288
317
|
console.log(` ${c.dim}Copy everything below and paste to your AI agent:${c.reset}\n`);
|
|
289
|
-
outputPrompt(undefined, guardData, scoreData, issues, flags);
|
|
318
|
+
outputPrompt(undefined, guardData, scoreData, issues, flags, agentMode);
|
|
290
319
|
}
|
|
291
320
|
}
|
|
292
321
|
|
|
293
|
-
function outputPrompt(projectDir, guardData, scoreData, issues, flags) {
|
|
322
|
+
function outputPrompt(projectDir, guardData, scoreData, issues, flags, agentMode = 'llm') {
|
|
294
323
|
if (issues.length === 0) {
|
|
295
324
|
console.log('No issues to fix. Documentation is healthy.');
|
|
296
325
|
return;
|
|
@@ -345,7 +374,11 @@ function outputPrompt(projectDir, guardData, scoreData, issues, flags) {
|
|
|
345
374
|
|
|
346
375
|
lines.push('');
|
|
347
376
|
lines.push('VALIDATION:');
|
|
348
|
-
|
|
377
|
+
if (agentMode === 'llm') {
|
|
378
|
+
lines.push('After making all fixes, use the /docguard.guard skill to verify');
|
|
379
|
+
} else {
|
|
380
|
+
lines.push('After making all fixes, run: docguard guard');
|
|
381
|
+
}
|
|
349
382
|
lines.push('Expected result: All checks pass (0 errors, 0 warnings)');
|
|
350
383
|
lines.push(`Target score: ≥${Math.min(scoreData.score + 5, 100)}/100`);
|
|
351
384
|
|
|
@@ -354,9 +387,15 @@ function outputPrompt(projectDir, guardData, scoreData, issues, flags) {
|
|
|
354
387
|
lines.push('');
|
|
355
388
|
lines.push('VERIFICATION CHECKLIST (complete each step):');
|
|
356
389
|
lines.push('□ Read each file in docs-canonical/ before editing');
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
390
|
+
if (agentMode === 'llm') {
|
|
391
|
+
lines.push('□ Run /docguard.guard after each file change');
|
|
392
|
+
lines.push('□ Confirm 0 errors before moving to next issue');
|
|
393
|
+
lines.push('□ Run /docguard.score to confirm improvement');
|
|
394
|
+
} else {
|
|
395
|
+
lines.push('□ Run `docguard guard` after each file change');
|
|
396
|
+
lines.push('□ Confirm 0 errors before moving to next issue');
|
|
397
|
+
lines.push('□ Run `docguard score` to confirm improvement');
|
|
398
|
+
}
|
|
360
399
|
}
|
|
361
400
|
|
|
362
401
|
console.log(lines.join('\n'));
|
|
@@ -368,7 +407,7 @@ function outputPrompt(projectDir, guardData, scoreData, issues, flags) {
|
|
|
368
407
|
* and TRACE adversarial debate (Advocate/Challenger/Mediator/Explainer).
|
|
369
408
|
* Lopez et al., IEEE TSE/TMLCN 2026.
|
|
370
409
|
*/
|
|
371
|
-
function outputDebatePrompt(projectDir, guardData, scoreData, issues) {
|
|
410
|
+
function outputDebatePrompt(projectDir, guardData, scoreData, issues, agentMode = 'llm') {
|
|
372
411
|
const lines = [];
|
|
373
412
|
|
|
374
413
|
lines.push('═══════════════════════════════════════════════════════');
|
|
@@ -427,7 +466,8 @@ function outputDebatePrompt(projectDir, guardData, scoreData, issues) {
|
|
|
427
466
|
lines.push(' a. Which file to edit');
|
|
428
467
|
lines.push(' b. What section to add or modify');
|
|
429
468
|
lines.push(' c. What content to write (be specific, not vague)');
|
|
430
|
-
|
|
469
|
+
const verifyCmd = agentMode === 'llm' ? '/docguard.guard' : 'docguard guard';
|
|
470
|
+
lines.push(`4. After all fixes, verify with: ${verifyCmd}`);
|
|
431
471
|
lines.push(`5. Target score: ≥${Math.min(scoreData.score + 10, 100)}/100`);
|
|
432
472
|
lines.push('');
|
|
433
473
|
lines.push('═══════════════════════════════════════════════════════');
|
package/cli/commands/fix.mjs
CHANGED
|
@@ -453,7 +453,7 @@ function generateDocPrompt(projectDir, config, docName) {
|
|
|
453
453
|
|
|
454
454
|
console.log(expectations.aiResearchInstructions.trim());
|
|
455
455
|
|
|
456
|
-
console.log(`\nVALIDATION: After writing, run \`npx docguard guard\` to verify the document passes all checks.`);
|
|
456
|
+
console.log(`\nVALIDATION: After writing, run \`npx docguard-cli guard\` to verify the document passes all checks.`);
|
|
457
457
|
console.log(`The document should have NO <!-- TODO --> or <!-- e.g. --> placeholders.`);
|
|
458
458
|
console.log(`Set the docguard:status header to 'active' (not 'draft').`);
|
|
459
459
|
}
|
package/cli/commands/guard.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { c } from '../shared.mjs';
|
|
11
|
+
import { detectAgentMode, isSpecKitInitialized } from '../ensure-skills.mjs';
|
|
11
12
|
import { validateStructure, validateDocSections } from '../validators/structure.mjs';
|
|
12
13
|
import { validateDrift } from '../validators/drift.mjs';
|
|
13
14
|
import { validateChangelog } from '../validators/changelog.mjs';
|
|
@@ -197,7 +198,12 @@ export function runGuard(projectDir, config, flags) {
|
|
|
197
198
|
|
|
198
199
|
// Next step hint — always point to diagnose when issues exist
|
|
199
200
|
if (data.status !== 'PASS') {
|
|
200
|
-
|
|
201
|
+
const agentMode = detectAgentMode(projectDir);
|
|
202
|
+
if (agentMode === 'llm') {
|
|
203
|
+
console.log(` ${c.dim}Use ${c.cyan}/docguard.diagnose${c.dim} to get AI fix prompts.${c.reset}`);
|
|
204
|
+
} else {
|
|
205
|
+
console.log(` ${c.dim}Run ${c.cyan}docguard diagnose${c.dim} to get AI fix prompts.${c.reset}`);
|
|
206
|
+
}
|
|
201
207
|
}
|
|
202
208
|
|
|
203
209
|
// Badge snippet
|
|
@@ -206,6 +212,11 @@ export function runGuard(projectDir, config, flags) {
|
|
|
206
212
|
const badgeUrl = `https://img.shields.io/badge/CDD_Guard-${data.passed}%2F${data.total}_passed-${bColor}`;
|
|
207
213
|
console.log(`\n ${c.dim}📎 Badge: ${c.reset}`);
|
|
208
214
|
|
|
215
|
+
// Spec-kit reminder — persistent nudge if not initialized
|
|
216
|
+
if (!isSpecKitInitialized(projectDir)) {
|
|
217
|
+
console.log(`\n ${c.yellow}💡${c.reset} ${c.dim}Enhance DocGuard with Spec Kit: ${c.cyan}uv tool install specify-cli --from git+https://github.com/github/spec-kit.git${c.reset}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
209
220
|
console.log('');
|
|
210
221
|
|
|
211
222
|
if (data.errors > 0) process.exit(1);
|
package/cli/commands/hooks.mjs
CHANGED
|
@@ -20,7 +20,7 @@ echo "🛡️ Running DocGuard guard..."
|
|
|
20
20
|
|
|
21
21
|
# Check if docguard is available
|
|
22
22
|
if command -v npx &> /dev/null; then
|
|
23
|
-
npx docguard guard
|
|
23
|
+
npx docguard-cli guard
|
|
24
24
|
EXIT_CODE=$?
|
|
25
25
|
elif command -v docguard &> /dev/null; then
|
|
26
26
|
docguard guard
|
|
@@ -60,7 +60,7 @@ echo "📊 Running DocGuard score check (minimum: $MIN_SCORE)..."
|
|
|
60
60
|
|
|
61
61
|
# Get score as JSON
|
|
62
62
|
if command -v npx &> /dev/null; then
|
|
63
|
-
RESULT=$(npx docguard score --format json 2>/dev/null)
|
|
63
|
+
RESULT=$(npx docguard-cli score --format json 2>/dev/null)
|
|
64
64
|
elif command -v docguard &> /dev/null; then
|
|
65
65
|
RESULT=$(docguard score --format json 2>/dev/null)
|
|
66
66
|
else
|
package/cli/commands/init.mjs
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Init Command — Initialize CDD documentation from templates
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Extension-First: When `specify` CLI is available, DocGuard delegates
|
|
5
|
+
* LLM/IDE detection and spec-kit skill installation to spec-kit.
|
|
6
|
+
* DocGuard then layers CDD-specific docs on top.
|
|
7
|
+
*
|
|
8
|
+
* Fallback: When `specify` is not available, DocGuard runs standalone
|
|
9
|
+
* with a warning suggesting spec-kit installation.
|
|
4
10
|
*/
|
|
5
11
|
|
|
6
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
7
13
|
import { resolve, dirname } from 'node:path';
|
|
8
14
|
import { fileURLToPath } from 'node:url';
|
|
9
15
|
import { createInterface } from 'node:readline';
|
|
16
|
+
import { execSync } from 'node:child_process';
|
|
10
17
|
import { c, PROFILES } from '../shared.mjs';
|
|
11
|
-
import { ensureSkills } from '../ensure-skills.mjs';
|
|
18
|
+
import { ensureSkills, detectAgentMode, detectAIAgent, isSpecKitAvailable, isSpecKitInitialized, getDetectedAgent } from '../ensure-skills.mjs';
|
|
12
19
|
|
|
13
20
|
function detectProjectType(dir) {
|
|
14
21
|
const pkgPath = resolve(dir, 'package.json');
|
|
@@ -189,61 +196,57 @@ export async function runInit(projectDir, config, flags) {
|
|
|
189
196
|
console.log(` ${c.yellow}⏭️${c.reset} .docguard.json ${c.dim}(already exists)${c.reset}`);
|
|
190
197
|
}
|
|
191
198
|
|
|
192
|
-
// ──
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const agentDirs = [
|
|
198
|
-
{ name: 'GitHub Copilot', path: '.github/commands' },
|
|
199
|
-
{ name: 'Cursor', path: '.cursor/rules' },
|
|
200
|
-
{ name: 'Google Gemini', path: '.gemini/commands' },
|
|
201
|
-
{ name: 'Claude Code', path: '.claude/commands' },
|
|
202
|
-
{ name: 'Antigravity', path: '.agents/workflows' },
|
|
203
|
-
];
|
|
204
|
-
|
|
205
|
-
const detected = agentDirs.filter(a =>
|
|
206
|
-
existsSync(resolve(projectDir, a.path.split('/')[0]))
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
const targets = detected.length > 0
|
|
210
|
-
? detected
|
|
211
|
-
: [{ name: 'GitHub (default)', path: '.github/commands' }];
|
|
212
|
-
|
|
213
|
-
let totalCreated = 0;
|
|
214
|
-
const installedLocations = [];
|
|
215
|
-
|
|
216
|
-
for (const target of targets) {
|
|
217
|
-
const destDir = resolve(projectDir, target.path);
|
|
218
|
-
if (!existsSync(destDir)) {
|
|
219
|
-
mkdirSync(destDir, { recursive: true });
|
|
220
|
-
}
|
|
199
|
+
// ── Spec-Kit Integration (Extension-First) ────────────────────────────
|
|
200
|
+
// Delegate LLM/IDE detection and spec-kit skill install to `specify init`
|
|
201
|
+
const specKitAvailable = isSpecKitAvailable();
|
|
202
|
+
const specKitInitialized = isSpecKitInitialized(projectDir);
|
|
221
203
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const destPath = resolve(destDir, file);
|
|
225
|
-
if (!existsSync(destPath)) {
|
|
226
|
-
const content = readFileSync(resolve(commandsSourceDir, file), 'utf-8');
|
|
227
|
-
writeFileSync(destPath, content, 'utf-8');
|
|
228
|
-
dirCreated++;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
204
|
+
if (specKitAvailable && !specKitInitialized) {
|
|
205
|
+
console.log(`\n ${c.bold}🌱 Spec Kit Integration${c.reset}`);
|
|
231
206
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
207
|
+
// Detect which AI agent is in use (matches spec-kit's --ai flag)
|
|
208
|
+
const detectedAgent = detectAIAgent(projectDir);
|
|
209
|
+
const aiFlag = detectedAgent
|
|
210
|
+
? `--ai ${detectedAgent}`
|
|
211
|
+
: '--ai generic --ai-commands-dir .agent/commands/';
|
|
237
212
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
console.log(` ${c.
|
|
213
|
+
console.log(` ${c.dim}Running specify init (agent: ${detectedAgent || 'generic'})...${c.reset}`);
|
|
214
|
+
try {
|
|
215
|
+
const scriptFlag = process.platform === 'win32' ? '--script ps' : '--script sh';
|
|
216
|
+
execSync(
|
|
217
|
+
`specify init --here --force ${aiFlag} --ai-skills --ignore-agent-tools --no-git ${scriptFlag}`,
|
|
218
|
+
{ cwd: projectDir, encoding: 'utf-8', stdio: 'pipe', timeout: 30000 }
|
|
219
|
+
);
|
|
220
|
+
console.log(` ${c.green}✅${c.reset} Spec Kit initialized ${c.dim}(.specify/, spec-kit skills, agent: ${detectedAgent || 'generic'})${c.reset}`);
|
|
221
|
+
created.push('.specify/ (spec-kit foundation)');
|
|
222
|
+
} catch (err) {
|
|
223
|
+
console.log(` ${c.yellow}⚠️${c.reset} Spec Kit init had issues ${c.dim}(continuing with DocGuard standalone)${c.reset}`);
|
|
224
|
+
if (flags.debug) console.log(` ${c.dim}${err.message}${c.reset}`);
|
|
246
225
|
}
|
|
226
|
+
} else if (specKitInitialized) {
|
|
227
|
+
const agent = getDetectedAgent(projectDir);
|
|
228
|
+
console.log(`\n ${c.green}✅${c.reset} Spec Kit already initialized${agent ? ` ${c.dim}(agent: ${agent})${c.reset}` : ''}`);
|
|
229
|
+
} else {
|
|
230
|
+
console.log(`\n ${c.red}┌─────────────────────────────────────────────────────────────┐${c.reset}`);
|
|
231
|
+
console.log(` ${c.red}│${c.reset} ${c.bold}⚠️ Spec Kit not installed — running in standalone mode${c.reset} ${c.red}│${c.reset}`);
|
|
232
|
+
console.log(` ${c.red}│${c.reset} ${c.red}│${c.reset}`);
|
|
233
|
+
console.log(` ${c.red}│${c.reset} DocGuard is designed as a Spec Kit extension. ${c.red}│${c.reset}`);
|
|
234
|
+
console.log(` ${c.red}│${c.reset} Without Spec Kit, you get ${c.bold}4 skills${c.reset}. With it: ${c.bold}13 skills${c.reset}. ${c.red}│${c.reset}`);
|
|
235
|
+
console.log(` ${c.red}│${c.reset} ${c.red}│${c.reset}`);
|
|
236
|
+
console.log(` ${c.red}│${c.reset} ${c.bold}What you're missing:${c.reset} ${c.red}│${c.reset}`);
|
|
237
|
+
console.log(` ${c.red}│${c.reset} • 9 Spec Kit AI skills (specify, plan, tasks, implement) ${c.red}│${c.reset}`);
|
|
238
|
+
console.log(` ${c.red}│${c.reset} • Project constitution (${c.cyan}constitution.md${c.reset}) ${c.red}│${c.reset}`);
|
|
239
|
+
console.log(` ${c.red}│${c.reset} • Full SDD + CDD integrated workflow ${c.red}│${c.reset}`);
|
|
240
|
+
console.log(` ${c.red}│${c.reset} • AI agent auto-detection and config ${c.red}│${c.reset}`);
|
|
241
|
+
console.log(` ${c.red}│${c.reset} ${c.red}│${c.reset}`);
|
|
242
|
+
console.log(` ${c.red}│${c.reset} ${c.bold}Install with:${c.reset} ${c.red}│${c.reset}`);
|
|
243
|
+
console.log(` ${c.red}│${c.reset} ${c.cyan}uv tool install specify-cli \\${c.reset} ${c.red}│${c.reset}`);
|
|
244
|
+
console.log(` ${c.red}│${c.reset} ${c.cyan} --from git+https://github.com/github/spec-kit.git${c.reset} ${c.red}│${c.reset}`);
|
|
245
|
+
console.log(` ${c.red}│${c.reset} ${c.red}│${c.reset}`);
|
|
246
|
+
console.log(` ${c.red}│${c.reset} ${c.dim}Alternative: ${c.cyan}pip install specify-cli${c.reset} ${c.red}│${c.reset}`);
|
|
247
|
+
console.log(` ${c.red}│${c.reset} ${c.red}│${c.reset}`);
|
|
248
|
+
console.log(` ${c.red}│${c.reset} ${c.dim}Then re-run: ${c.cyan}docguard init${c.reset} ${c.red}│${c.reset}`);
|
|
249
|
+
console.log(` ${c.red}└─────────────────────────────────────────────────────────────┘${c.reset}`);
|
|
247
250
|
}
|
|
248
251
|
|
|
249
252
|
// ── Summary ────────────────────────────────────────────────────────────
|
|
@@ -258,35 +261,53 @@ export async function runInit(projectDir, config, flags) {
|
|
|
258
261
|
console.log(` ${c.dim}Auto-guard on commit:${c.reset} ${c.cyan}docguard hooks --type pre-commit${c.reset}`);
|
|
259
262
|
console.log(` ${c.dim}Auto-guard on push:${c.reset} ${c.cyan}docguard hooks --type pre-push${c.reset}`);
|
|
260
263
|
|
|
261
|
-
// ── Next
|
|
264
|
+
// ── Next Steps (LLM-First) ─────────────────────────────────────────────
|
|
265
|
+
const agentMode = detectAgentMode(projectDir);
|
|
262
266
|
const createdDocs = created.filter(f => f.startsWith('docs-canonical/'));
|
|
263
267
|
|
|
264
268
|
if (createdDocs.length > 0) {
|
|
265
|
-
console.log(`\n ${c.bold}🤖
|
|
269
|
+
console.log(`\n ${c.bold}🤖 Next Steps${c.reset} ${c.dim}(${agentMode === 'llm' ? 'LLM mode' : 'CLI mode'})${c.reset}`);
|
|
266
270
|
console.log(` ${c.dim}The files above are skeleton templates. Your AI agent should fill them.${c.reset}`);
|
|
267
|
-
console.log(` ${c.dim}Run this single command to get a full remediation plan:${c.reset}\n`);
|
|
268
|
-
console.log(` ${c.cyan}${c.bold}docguard diagnose${c.reset}\n`);
|
|
269
|
-
console.log(` ${c.dim}Or generate prompts for individual docs:${c.reset}`);
|
|
270
|
-
|
|
271
|
-
const docNameMap = {
|
|
272
|
-
'docs-canonical/ARCHITECTURE.md': 'architecture',
|
|
273
|
-
'docs-canonical/DATA-MODEL.md': 'data-model',
|
|
274
|
-
'docs-canonical/SECURITY.md': 'security',
|
|
275
|
-
'docs-canonical/TEST-SPEC.md': 'test-spec',
|
|
276
|
-
'docs-canonical/ENVIRONMENT.md': 'environment',
|
|
277
|
-
};
|
|
278
271
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
272
|
+
if (agentMode === 'llm') {
|
|
273
|
+
// LLM-first: show skill commands
|
|
274
|
+
console.log(`\n ${c.bold}Use these skills in your AI agent:${c.reset}`);
|
|
275
|
+
if (isSpecKitInitialized(projectDir)) {
|
|
276
|
+
console.log(` ${c.cyan}1. /speckit.constitution${c.reset} ${c.dim}← establish project principles${c.reset}`);
|
|
277
|
+
}
|
|
278
|
+
console.log(` ${c.cyan}${isSpecKitInitialized(projectDir) ? '2' : '1'}. /docguard.guard${c.reset} ${c.dim}← validate documentation${c.reset}`);
|
|
279
|
+
|
|
280
|
+
const docNameMap = {
|
|
281
|
+
'docs-canonical/ARCHITECTURE.md': 'architecture',
|
|
282
|
+
'docs-canonical/DATA-MODEL.md': 'data-model',
|
|
283
|
+
'docs-canonical/SECURITY.md': 'security',
|
|
284
|
+
'docs-canonical/TEST-SPEC.md': 'test-spec',
|
|
285
|
+
'docs-canonical/ENVIRONMENT.md': 'environment',
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const fixTargets = createdDocs.map(d => docNameMap[d]).filter(Boolean);
|
|
289
|
+
if (fixTargets.length > 0) {
|
|
290
|
+
console.log(`\n ${c.dim}Fix individual docs with the docguard-fix skill:${c.reset}`);
|
|
291
|
+
for (const target of fixTargets) {
|
|
292
|
+
console.log(` ${c.cyan}/docguard.fix --doc ${target}${c.reset}`);
|
|
293
|
+
}
|
|
283
294
|
}
|
|
295
|
+
console.log(`\n ${c.dim}Then verify:${c.reset} ${c.cyan}/docguard.guard${c.reset}`);
|
|
296
|
+
} else {
|
|
297
|
+
// CLI fallback
|
|
298
|
+
console.log(`\n ${c.dim}Get a full remediation plan:${c.reset}`);
|
|
299
|
+
console.log(` ${c.cyan}${c.bold}docguard diagnose${c.reset}\n`);
|
|
300
|
+
console.log(` ${c.dim}Then verify:${c.reset} ${c.cyan}docguard guard${c.reset}`);
|
|
284
301
|
}
|
|
285
|
-
console.log(
|
|
302
|
+
console.log('');
|
|
286
303
|
} else {
|
|
287
|
-
|
|
304
|
+
if (agentMode === 'llm') {
|
|
305
|
+
console.log(`\n ${c.dim}Use${c.reset} ${c.cyan}/docguard.guard${c.reset} ${c.dim}in your AI agent to check for issues.${c.reset}\n`);
|
|
306
|
+
} else {
|
|
307
|
+
console.log(`\n ${c.dim}Run${c.reset} ${c.cyan}docguard diagnose${c.reset} ${c.dim}to check for issues.${c.reset}\n`);
|
|
308
|
+
}
|
|
288
309
|
}
|
|
289
310
|
|
|
290
|
-
// Auto-install skills and commands
|
|
311
|
+
// Auto-install DocGuard skills and commands (spec-kit skills handled by specify init)
|
|
291
312
|
ensureSkills(projectDir, flags);
|
|
292
313
|
}
|