docguard-cli 0.5.1
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/LICENSE +21 -0
- package/PHILOSOPHY.md +150 -0
- package/README.md +309 -0
- package/STANDARD.md +751 -0
- package/cli/commands/agents.mjs +221 -0
- package/cli/commands/audit.mjs +92 -0
- package/cli/commands/badge.mjs +72 -0
- package/cli/commands/ci.mjs +80 -0
- package/cli/commands/diagnose.mjs +273 -0
- package/cli/commands/diff.mjs +360 -0
- package/cli/commands/fix.mjs +610 -0
- package/cli/commands/generate.mjs +842 -0
- package/cli/commands/guard.mjs +158 -0
- package/cli/commands/hooks.mjs +227 -0
- package/cli/commands/init.mjs +249 -0
- package/cli/commands/score.mjs +396 -0
- package/cli/commands/watch.mjs +143 -0
- package/cli/docguard.mjs +458 -0
- package/cli/validators/architecture.mjs +380 -0
- package/cli/validators/changelog.mjs +39 -0
- package/cli/validators/docs-sync.mjs +110 -0
- package/cli/validators/drift.mjs +101 -0
- package/cli/validators/environment.mjs +70 -0
- package/cli/validators/freshness.mjs +224 -0
- package/cli/validators/security.mjs +101 -0
- package/cli/validators/structure.mjs +88 -0
- package/cli/validators/test-spec.mjs +115 -0
- package/docs/ai-integration.md +179 -0
- package/docs/commands.md +239 -0
- package/docs/configuration.md +96 -0
- package/docs/faq.md +155 -0
- package/docs/installation.md +81 -0
- package/docs/profiles.md +103 -0
- package/docs/quickstart.md +79 -0
- package/package.json +57 -0
- package/templates/ADR.md.template +64 -0
- package/templates/AGENTS.md.template +88 -0
- package/templates/ARCHITECTURE.md.template +78 -0
- package/templates/CHANGELOG.md.template +16 -0
- package/templates/CURRENT-STATE.md.template +64 -0
- package/templates/DATA-MODEL.md.template +66 -0
- package/templates/DEPLOYMENT.md.template +66 -0
- package/templates/DRIFT-LOG.md.template +18 -0
- package/templates/ENVIRONMENT.md.template +43 -0
- package/templates/KNOWN-GOTCHAS.md.template +69 -0
- package/templates/ROADMAP.md.template +82 -0
- package/templates/RUNBOOKS.md.template +115 -0
- package/templates/SECURITY.md.template +42 -0
- package/templates/TEST-SPEC.md.template +55 -0
- package/templates/TROUBLESHOOTING.md.template +96 -0
- package/templates/VENDOR-BUGS.md.template +74 -0
- package/templates/ci/github-actions.yml +39 -0
- package/templates/commands/docguard.fix.md +65 -0
- package/templates/commands/docguard.guard.md +40 -0
- package/templates/commands/docguard.init.md +62 -0
- package/templates/commands/docguard.review.md +44 -0
- package/templates/commands/docguard.update.md +44 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fix Command — The AI Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* The CLI does NOT write documentation content.
|
|
5
|
+
* It FLAGS what needs work and generates intelligent prompts
|
|
6
|
+
* that tell the AI exactly what to research and write.
|
|
7
|
+
*
|
|
8
|
+
* Output modes:
|
|
9
|
+
* --format text Human-readable issue list (default)
|
|
10
|
+
* --format json Machine-readable for VS Code / CI
|
|
11
|
+
* --format prompt Full AI-ready prompt with codebase research instructions
|
|
12
|
+
* --doc <name> Generate deep prompt for a specific document
|
|
13
|
+
* --auto Create skeleton files (NOT content) via init
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync, mkdirSync } from 'node:fs';
|
|
17
|
+
import { resolve, basename } from 'node:path';
|
|
18
|
+
import { execSync } from 'node:child_process';
|
|
19
|
+
import { c } from '../docguard.mjs';
|
|
20
|
+
|
|
21
|
+
// ── Document Quality Definitions ───────────────────────────────────────────
|
|
22
|
+
// What each doc SHOULD contain, and what to look for in the codebase
|
|
23
|
+
|
|
24
|
+
const DOC_EXPECTATIONS = {
|
|
25
|
+
'docs-canonical/ARCHITECTURE.md': {
|
|
26
|
+
label: 'Architecture',
|
|
27
|
+
purpose: 'Define the system design: layers, components, boundaries, and data flow',
|
|
28
|
+
qualitySignals: [
|
|
29
|
+
'Has a system overview (not a TODO placeholder)',
|
|
30
|
+
'Lists actual components/modules with responsibilities',
|
|
31
|
+
'Defines layer boundaries and allowed imports',
|
|
32
|
+
'Includes a data flow or request lifecycle description',
|
|
33
|
+
'References real file paths or directories',
|
|
34
|
+
],
|
|
35
|
+
aiResearchInstructions: `
|
|
36
|
+
RESEARCH STEPS:
|
|
37
|
+
1. Read package.json for project name, description, dependencies, and scripts
|
|
38
|
+
2. List the top-level directory structure (ls -la, focus on src/, lib/, cli/, app/, etc.)
|
|
39
|
+
3. Identify the entry point(s) — check "main", "bin", or "exports" in package.json
|
|
40
|
+
4. For each major directory, read 2-3 representative files to understand its purpose
|
|
41
|
+
5. Map the import graph — which modules import which
|
|
42
|
+
6. Identify external dependencies and what role they play
|
|
43
|
+
|
|
44
|
+
WRITE THE DOCUMENT:
|
|
45
|
+
- System Overview: 2-3 sentences on what this project does and who uses it
|
|
46
|
+
- Component Map: Table of each module/directory with its responsibility
|
|
47
|
+
- Layer Boundaries: Which layers can import from which (draw import rules)
|
|
48
|
+
- Data Flow: How a request/command flows through the system
|
|
49
|
+
- Key Design Decisions: Why the architecture is the way it is
|
|
50
|
+
- Technology Choices: List frameworks/tools and why they were chosen
|
|
51
|
+
|
|
52
|
+
FORMAT: Use the docguard metadata header (version, status, last-reviewed).
|
|
53
|
+
IMPORTANT: Use REAL file paths, REAL module names, REAL dependency names. No placeholders.`,
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
'docs-canonical/DATA-MODEL.md': {
|
|
57
|
+
label: 'Data Model',
|
|
58
|
+
purpose: 'Document all data structures, schemas, database tables, and relationships',
|
|
59
|
+
qualitySignals: [
|
|
60
|
+
'Lists actual database tables/collections or data structures',
|
|
61
|
+
'Shows field names, types, and constraints',
|
|
62
|
+
'Documents relationships (foreign keys, references)',
|
|
63
|
+
'Includes indexes if applicable',
|
|
64
|
+
'No TODO or example placeholders in table rows',
|
|
65
|
+
],
|
|
66
|
+
aiResearchInstructions: `
|
|
67
|
+
RESEARCH STEPS:
|
|
68
|
+
1. Search for schema definitions: grep -r "Schema\\|model\\|Table\\|Entity\\|interface\\|type " src/ lib/ --include="*.ts" --include="*.js" --include="*.mjs"
|
|
69
|
+
2. Look for database config: grep -r "database\\|sequelize\\|prisma\\|mongoose\\|drizzle\\|knex" package.json
|
|
70
|
+
3. Check for migration files in migrations/, db/, prisma/, etc.
|
|
71
|
+
4. Look for TypeScript interfaces/types that define data shapes
|
|
72
|
+
5. Check for Zod schemas, JSON schemas, or validation files
|
|
73
|
+
6. If no database: document the config file format (.docguard.json, etc.)
|
|
74
|
+
|
|
75
|
+
WRITE THE DOCUMENT:
|
|
76
|
+
- If database project: Document each table with columns, types, constraints, indexes, relationships
|
|
77
|
+
- If config-driven: Document each config file format with all fields, types, defaults, and validation rules
|
|
78
|
+
- If API project: Document request/response shapes
|
|
79
|
+
- If CLI project: Document any configuration formats, file formats the tool reads/writes
|
|
80
|
+
|
|
81
|
+
FORMAT: Use markdown tables. Include field name, type, required/optional, default, description.
|
|
82
|
+
IMPORTANT: Research the actual codebase. Do NOT use placeholder values.`,
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
'docs-canonical/SECURITY.md': {
|
|
86
|
+
label: 'Security',
|
|
87
|
+
purpose: 'Document authentication, authorization, secrets management, and security boundaries',
|
|
88
|
+
qualitySignals: [
|
|
89
|
+
'Documents actual auth mechanism (or explicitly states "no auth needed")',
|
|
90
|
+
'Lists secrets/credentials and where they are stored',
|
|
91
|
+
'Describes RBAC/permissions if applicable',
|
|
92
|
+
'Has a threat model or security boundaries section',
|
|
93
|
+
'References .gitignore patterns for sensitive files',
|
|
94
|
+
],
|
|
95
|
+
aiResearchInstructions: `
|
|
96
|
+
RESEARCH STEPS:
|
|
97
|
+
1. Check .gitignore for security-related patterns (.env, secrets, keys, credentials)
|
|
98
|
+
2. Search for auth: grep -r "auth\\|token\\|jwt\\|session\\|password\\|secret\\|apiKey\\|API_KEY" src/ lib/ --include="*.ts" --include="*.js" --include="*.mjs"
|
|
99
|
+
3. Check package.json for auth-related dependencies (passport, jwt, bcrypt, etc.)
|
|
100
|
+
4. Look for middleware or guards that enforce permissions
|
|
101
|
+
5. Check for .env files (but DON'T include secret values — only variable names)
|
|
102
|
+
6. Look for CORS configuration, rate limiting, input validation
|
|
103
|
+
|
|
104
|
+
WRITE THE DOCUMENT:
|
|
105
|
+
- Auth Mechanism: What auth does this project use? (OAuth, JWT, API key, none, etc.)
|
|
106
|
+
- Secrets Inventory: List all secrets/env vars needed (names only, never values)
|
|
107
|
+
- Secrets Storage: Where are secrets stored? (.env, Vault, AWS Secrets Manager, etc.)
|
|
108
|
+
- Permissions/RBAC: What roles exist? What can each role do?
|
|
109
|
+
- Security Boundaries: What is trusted vs untrusted input?
|
|
110
|
+
- .gitignore Audit: Confirm sensitive files are excluded from version control
|
|
111
|
+
- If CLI tool with no auth: State explicitly "No authentication required — this is a local CLI tool"
|
|
112
|
+
|
|
113
|
+
IMPORTANT: Be specific to THIS project. Don't add generic security boilerplate for features the project doesn't have.`,
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
'docs-canonical/TEST-SPEC.md': {
|
|
117
|
+
label: 'Test Spec',
|
|
118
|
+
purpose: 'Document test strategy, coverage requirements, and critical test scenarios',
|
|
119
|
+
qualitySignals: [
|
|
120
|
+
'References actual test files and test frameworks',
|
|
121
|
+
'Lists critical flows that must be tested',
|
|
122
|
+
'Documents test commands (npm test, etc.)',
|
|
123
|
+
'Has coverage thresholds or quality gates',
|
|
124
|
+
'No generic placeholder test scenarios',
|
|
125
|
+
],
|
|
126
|
+
aiResearchInstructions: `
|
|
127
|
+
RESEARCH STEPS:
|
|
128
|
+
1. Read package.json "scripts" for test commands
|
|
129
|
+
2. Find test files: find . -name "*.test.*" -o -name "*.spec.*" -o -name "__tests__" | head -20
|
|
130
|
+
3. Read the test configuration (jest.config, vitest.config, .mocharc, etc.)
|
|
131
|
+
4. Read 2-3 test files to understand the testing patterns used
|
|
132
|
+
5. Check for E2E test setup (playwright, cypress, puppeteer configs)
|
|
133
|
+
6. Look for CI config that runs tests (.github/workflows/)
|
|
134
|
+
|
|
135
|
+
WRITE THE DOCUMENT:
|
|
136
|
+
- Test Framework: What testing tool is used and why
|
|
137
|
+
- Test Structure: Where tests live, naming conventions
|
|
138
|
+
- Test Commands: Exact commands to run unit, integration, E2E tests
|
|
139
|
+
- Critical Flows: List the 5-10 most important things that MUST be tested
|
|
140
|
+
- Coverage: Current coverage and target thresholds
|
|
141
|
+
- CI Integration: How tests run in CI/CD
|
|
142
|
+
|
|
143
|
+
IMPORTANT: Reference REAL test files. If there are no tests yet, document what SHOULD be tested.`,
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
'docs-canonical/ENVIRONMENT.md': {
|
|
147
|
+
label: 'Environment',
|
|
148
|
+
purpose: 'Document setup steps, dependencies, and environment variables',
|
|
149
|
+
qualitySignals: [
|
|
150
|
+
'Has actual setup commands (not placeholders)',
|
|
151
|
+
'Lists real environment variables with descriptions',
|
|
152
|
+
'Documents Node/Python/runtime version requirements',
|
|
153
|
+
'Includes troubleshooting for common setup issues',
|
|
154
|
+
'Works as a "new contributor can follow this and get running" guide',
|
|
155
|
+
],
|
|
156
|
+
aiResearchInstructions: `
|
|
157
|
+
RESEARCH STEPS:
|
|
158
|
+
1. Read package.json for: engines, scripts, dependencies
|
|
159
|
+
2. Check for .nvmrc, .node-version, .python-version, .tool-versions
|
|
160
|
+
3. Search for process.env usage: grep -r "process.env\\|os.environ" src/ lib/ cli/ --include="*.ts" --include="*.js" --include="*.mjs" --include="*.py"
|
|
161
|
+
4. Check for .env.example or .env.template files
|
|
162
|
+
5. Check for Docker/docker-compose files
|
|
163
|
+
6. Look for setup scripts in scripts/ or Makefile
|
|
164
|
+
|
|
165
|
+
WRITE THE DOCUMENT:
|
|
166
|
+
- Prerequisites: Node version, package manager, any system deps
|
|
167
|
+
- Setup Steps: Exact commands from clone to running (git clone → npm install → npm run dev)
|
|
168
|
+
- Environment Variables: Table of all env vars (name, required/optional, description, example value — never real secrets)
|
|
169
|
+
- If CLI with no env vars: State "No environment variables required"
|
|
170
|
+
- Common Issues: Known gotchas during setup
|
|
171
|
+
|
|
172
|
+
IMPORTANT: A new contributor should be able to follow this doc and have the project running in under 10 minutes.`,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// ── Main Entry ─────────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
export function runFix(projectDir, config, flags) {
|
|
179
|
+
const isJson = flags.format === 'json';
|
|
180
|
+
const isPrompt = flags.format === 'prompt';
|
|
181
|
+
const autoFix = flags.auto || false;
|
|
182
|
+
const specificDoc = flags.doc || null;
|
|
183
|
+
|
|
184
|
+
// If --doc flag is provided, generate a deep prompt for that specific document
|
|
185
|
+
if (specificDoc) {
|
|
186
|
+
return generateDocPrompt(projectDir, config, specificDoc);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!isJson && !isPrompt) {
|
|
190
|
+
console.log(`${c.bold}🔧 DocGuard Fix — ${config.projectName}${c.reset}`);
|
|
191
|
+
console.log(`${c.dim} Directory: ${projectDir}${c.reset}`);
|
|
192
|
+
console.log(`${c.dim} Scanning for issues...${c.reset}\n`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const issues = collectIssues(projectDir, config);
|
|
196
|
+
|
|
197
|
+
if (autoFix) {
|
|
198
|
+
const fixed = autoFixIssues(projectDir, config, issues);
|
|
199
|
+
if (!isJson) {
|
|
200
|
+
console.log(` ${c.green}✅ Created ${fixed} skeleton file(s)${c.reset}`);
|
|
201
|
+
console.log(` ${c.dim} Now run ${c.cyan}docguard fix --format prompt${c.dim} to generate AI instructions for filling them in${c.reset}\n`);
|
|
202
|
+
}
|
|
203
|
+
const remaining = collectIssues(projectDir, config);
|
|
204
|
+
outputResults(remaining, projectDir, config, flags);
|
|
205
|
+
} else {
|
|
206
|
+
outputResults(issues, projectDir, config, flags);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Issue Collection ───────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
function collectIssues(projectDir, config) {
|
|
213
|
+
const issues = [];
|
|
214
|
+
const ptc = config.projectTypeConfig || {};
|
|
215
|
+
|
|
216
|
+
// 1. Missing required files
|
|
217
|
+
const requiredFiles = [
|
|
218
|
+
...config.requiredFiles.canonical,
|
|
219
|
+
config.requiredFiles.changelog,
|
|
220
|
+
config.requiredFiles.driftLog,
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
for (const file of requiredFiles) {
|
|
224
|
+
if (!existsSync(resolve(projectDir, file))) {
|
|
225
|
+
issues.push({
|
|
226
|
+
type: 'missing-file',
|
|
227
|
+
severity: 'error',
|
|
228
|
+
file,
|
|
229
|
+
message: `Missing: ${file}`,
|
|
230
|
+
autoFixable: true,
|
|
231
|
+
fix: {
|
|
232
|
+
action: 'create',
|
|
233
|
+
command: 'docguard fix --auto',
|
|
234
|
+
ai_instruction: `Create ${file} with real project content. Run: docguard fix --doc ${basename(file, '.md').toLowerCase()}`,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Agent file check
|
|
241
|
+
const hasAgent = config.requiredFiles.agentFile.some(f =>
|
|
242
|
+
existsSync(resolve(projectDir, f))
|
|
243
|
+
);
|
|
244
|
+
if (!hasAgent) {
|
|
245
|
+
issues.push({
|
|
246
|
+
type: 'missing-file',
|
|
247
|
+
severity: 'error',
|
|
248
|
+
file: 'AGENTS.md',
|
|
249
|
+
message: 'Missing: AGENTS.md (AI agent config)',
|
|
250
|
+
autoFixable: true,
|
|
251
|
+
fix: {
|
|
252
|
+
action: 'create',
|
|
253
|
+
command: 'docguard fix --auto',
|
|
254
|
+
ai_instruction: 'Create AGENTS.md with project stack, workflow rules, and DocGuard integration.',
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 2. Document quality assessment (not just placeholder detection)
|
|
260
|
+
for (const [filePath, expectations] of Object.entries(DOC_EXPECTATIONS)) {
|
|
261
|
+
const fullPath = resolve(projectDir, filePath);
|
|
262
|
+
if (!existsSync(fullPath)) continue;
|
|
263
|
+
|
|
264
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
265
|
+
const quality = assessDocQuality(content, expectations);
|
|
266
|
+
|
|
267
|
+
if (quality.score === 'empty') {
|
|
268
|
+
issues.push({
|
|
269
|
+
type: 'empty-doc',
|
|
270
|
+
severity: 'error',
|
|
271
|
+
file: filePath,
|
|
272
|
+
message: `${expectations.label} doc is a skeleton template with no real content`,
|
|
273
|
+
autoFixable: false,
|
|
274
|
+
fix: {
|
|
275
|
+
action: 'rewrite',
|
|
276
|
+
ai_instruction: `This document is just a template. Run: docguard fix --doc ${basename(filePath, '.md').toLowerCase()}\nThen have your AI assistant execute the generated prompt to write real content.`,
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
} else if (quality.score === 'partial') {
|
|
280
|
+
issues.push({
|
|
281
|
+
type: 'partial-doc',
|
|
282
|
+
severity: 'warning',
|
|
283
|
+
file: filePath,
|
|
284
|
+
message: `${expectations.label} doc has ${quality.placeholders} unfilled placeholder(s) — needs AI to complete`,
|
|
285
|
+
autoFixable: false,
|
|
286
|
+
fix: {
|
|
287
|
+
action: 'improve',
|
|
288
|
+
ai_instruction: `Improve ${filePath}. ${quality.failedSignals.join('. ')}.\nRun: docguard fix --doc ${basename(filePath, '.md').toLowerCase()} --format prompt`,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// quality.score === 'good' → no issue
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 3. Missing .docguard.json
|
|
296
|
+
if (!existsSync(resolve(projectDir, '.docguard.json'))) {
|
|
297
|
+
issues.push({
|
|
298
|
+
type: 'missing-config',
|
|
299
|
+
severity: 'info',
|
|
300
|
+
file: '.docguard.json',
|
|
301
|
+
message: 'No .docguard.json — using defaults',
|
|
302
|
+
autoFixable: true,
|
|
303
|
+
fix: {
|
|
304
|
+
action: 'create',
|
|
305
|
+
command: 'docguard fix --auto',
|
|
306
|
+
ai_instruction: 'Create .docguard.json with projectName, projectType, and projectTypeConfig.',
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 4. Check .env.example if needed
|
|
312
|
+
if (ptc.needsEnvExample !== false && ptc.needsEnvVars !== false) {
|
|
313
|
+
if (!existsSync(resolve(projectDir, '.env.example'))) {
|
|
314
|
+
const hasEnv = ['.env', '.env.local', '.env.development'].some(f =>
|
|
315
|
+
existsSync(resolve(projectDir, f))
|
|
316
|
+
);
|
|
317
|
+
if (hasEnv) {
|
|
318
|
+
issues.push({
|
|
319
|
+
type: 'missing-env-example',
|
|
320
|
+
severity: 'warning',
|
|
321
|
+
file: '.env.example',
|
|
322
|
+
message: '.env exists but no .env.example for contributors',
|
|
323
|
+
autoFixable: false,
|
|
324
|
+
fix: {
|
|
325
|
+
action: 'create',
|
|
326
|
+
ai_instruction: 'Create .env.example with all env var names from .env, replace secrets with descriptions.',
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 5. CHANGELOG quality
|
|
334
|
+
const changelogPath = resolve(projectDir, config.requiredFiles.changelog);
|
|
335
|
+
if (existsSync(changelogPath)) {
|
|
336
|
+
const content = readFileSync(changelogPath, 'utf-8');
|
|
337
|
+
if (!content.includes('[Unreleased]') && !content.includes('## [')) {
|
|
338
|
+
issues.push({
|
|
339
|
+
type: 'empty-changelog',
|
|
340
|
+
severity: 'warning',
|
|
341
|
+
file: config.requiredFiles.changelog,
|
|
342
|
+
message: 'CHANGELOG.md has no version entries',
|
|
343
|
+
autoFixable: false,
|
|
344
|
+
fix: {
|
|
345
|
+
action: 'edit',
|
|
346
|
+
ai_instruction: 'Add version entries to CHANGELOG.md following Keep a Changelog format. Check git log for recent changes.',
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return issues;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ── Document Quality Assessment ────────────────────────────────────────────
|
|
356
|
+
|
|
357
|
+
function assessDocQuality(content, expectations) {
|
|
358
|
+
const lines = content.split('\n');
|
|
359
|
+
const nonEmptyLines = lines.filter(l => l.trim() && !l.startsWith('<!--'));
|
|
360
|
+
const placeholders = (content.match(/<!-- TODO|<!-- e\.g\./g) || []).length;
|
|
361
|
+
const hasRealContent = nonEmptyLines.length > 10; // More than just headers
|
|
362
|
+
|
|
363
|
+
// Check if this is basically just a template
|
|
364
|
+
const contentLines = lines.filter(l =>
|
|
365
|
+
l.trim() &&
|
|
366
|
+
!l.startsWith('#') &&
|
|
367
|
+
!l.startsWith('<!--') &&
|
|
368
|
+
!l.startsWith('|--') &&
|
|
369
|
+
!l.startsWith('| *') &&
|
|
370
|
+
!l.match(/^\|\s*$/)
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
// Count lines that are actual content (not table headers, not metadata)
|
|
374
|
+
const realContentLines = contentLines.filter(l =>
|
|
375
|
+
!l.includes('<!-- TODO') &&
|
|
376
|
+
!l.includes('<!-- e.g.') &&
|
|
377
|
+
!l.match(/^\|\s*\|/) // empty table cells
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
const failedSignals = [];
|
|
381
|
+
for (const signal of expectations.qualitySignals) {
|
|
382
|
+
// Simple heuristic checks
|
|
383
|
+
if (signal.includes('TODO placeholder') && placeholders > 0) {
|
|
384
|
+
failedSignals.push(`Still has ${placeholders} placeholder(s)`);
|
|
385
|
+
}
|
|
386
|
+
if (signal.includes('actual') && realContentLines.length < 5) {
|
|
387
|
+
failedSignals.push('Lacks specific, project-relevant content');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (!hasRealContent || realContentLines.length < 5) {
|
|
392
|
+
return { score: 'empty', placeholders, failedSignals };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (placeholders > 0 || failedSignals.length > 2) {
|
|
396
|
+
return { score: 'partial', placeholders, failedSignals };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return { score: 'good', placeholders: 0, failedSignals: [] };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ── Deep Document Prompt Generator ─────────────────────────────────────────
|
|
403
|
+
|
|
404
|
+
function generateDocPrompt(projectDir, config, docName) {
|
|
405
|
+
// Normalize doc name: "architecture" → "docs-canonical/ARCHITECTURE.md"
|
|
406
|
+
const normalized = docName.toLowerCase().replace(/\.md$/, '');
|
|
407
|
+
const mapping = {
|
|
408
|
+
'architecture': 'docs-canonical/ARCHITECTURE.md',
|
|
409
|
+
'data-model': 'docs-canonical/DATA-MODEL.md',
|
|
410
|
+
'datamodel': 'docs-canonical/DATA-MODEL.md',
|
|
411
|
+
'security': 'docs-canonical/SECURITY.md',
|
|
412
|
+
'test-spec': 'docs-canonical/TEST-SPEC.md',
|
|
413
|
+
'testspec': 'docs-canonical/TEST-SPEC.md',
|
|
414
|
+
'environment': 'docs-canonical/ENVIRONMENT.md',
|
|
415
|
+
'env': 'docs-canonical/ENVIRONMENT.md',
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const filePath = mapping[normalized];
|
|
419
|
+
if (!filePath) {
|
|
420
|
+
console.error(`${c.red}Unknown document: ${docName}${c.reset}`);
|
|
421
|
+
console.log(`${c.dim}Available: architecture, data-model, security, test-spec, environment${c.reset}`);
|
|
422
|
+
process.exit(1);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const expectations = DOC_EXPECTATIONS[filePath];
|
|
426
|
+
if (!expectations) {
|
|
427
|
+
console.error(`${c.red}No prompt template for: ${filePath}${c.reset}`);
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Build context about the project
|
|
432
|
+
const projectType = config.projectType || 'unknown';
|
|
433
|
+
const projectName = config.projectName || basename(projectDir);
|
|
434
|
+
|
|
435
|
+
// Check if doc exists and what state it's in
|
|
436
|
+
const fullPath = resolve(projectDir, filePath);
|
|
437
|
+
const exists = existsSync(fullPath);
|
|
438
|
+
const currentContent = exists ? readFileSync(fullPath, 'utf-8') : null;
|
|
439
|
+
const quality = currentContent ? assessDocQuality(currentContent, expectations) : null;
|
|
440
|
+
|
|
441
|
+
const action = !exists ? 'CREATE' : quality?.score === 'empty' ? 'REWRITE (current file is just a template)' : 'IMPROVE';
|
|
442
|
+
|
|
443
|
+
console.log(`\nYou are documenting the project "${projectName}" (a ${projectType} project).`);
|
|
444
|
+
console.log(`Project directory: ${projectDir}\n`);
|
|
445
|
+
console.log(`TASK: ${action} the file ${filePath}`);
|
|
446
|
+
console.log(`PURPOSE: ${expectations.purpose}\n`);
|
|
447
|
+
|
|
448
|
+
if (exists && quality?.score !== 'good') {
|
|
449
|
+
console.log(`CURRENT STATE: The document ${quality?.score === 'empty' ? 'is just a skeleton template with TODO placeholders — it needs to be completely rewritten with REAL project content' : `has ${quality?.placeholders} placeholder(s) that need to be replaced with real content`}.`);
|
|
450
|
+
console.log('');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
console.log(expectations.aiResearchInstructions.trim());
|
|
454
|
+
|
|
455
|
+
console.log(`\nVALIDATION: After writing, run \`npx docguard guard\` to verify the document passes all checks.`);
|
|
456
|
+
console.log(`The document should have NO <!-- TODO --> or <!-- e.g. --> placeholders.`);
|
|
457
|
+
console.log(`Set the docguard:status header to 'active' (not 'draft').`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ── Auto-Fix (skeleton creation only) ──────────────────────────────────────
|
|
461
|
+
|
|
462
|
+
function autoFixIssues(projectDir, config, issues) {
|
|
463
|
+
let fixed = 0;
|
|
464
|
+
const autoFixable = issues.filter(i => i.autoFixable);
|
|
465
|
+
|
|
466
|
+
if (autoFixable.length === 0) return 0;
|
|
467
|
+
|
|
468
|
+
const docsDir = resolve(projectDir, 'docs-canonical');
|
|
469
|
+
if (!existsSync(docsDir)) {
|
|
470
|
+
mkdirSync(docsDir, { recursive: true });
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
try {
|
|
474
|
+
const cliPath = resolve(import.meta.dirname, '..', 'docguard.mjs');
|
|
475
|
+
execSync(`node ${cliPath} init --dir "${projectDir}"`, {
|
|
476
|
+
encoding: 'utf-8',
|
|
477
|
+
stdio: 'pipe',
|
|
478
|
+
});
|
|
479
|
+
fixed = autoFixable.length;
|
|
480
|
+
} catch { /* init may partially succeed */ }
|
|
481
|
+
|
|
482
|
+
return fixed;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ── Output ─────────────────────────────────────────────────────────────────
|
|
486
|
+
|
|
487
|
+
function outputResults(issues, projectDir, config, flags) {
|
|
488
|
+
const isJson = flags.format === 'json';
|
|
489
|
+
const isPrompt = flags.format === 'prompt';
|
|
490
|
+
|
|
491
|
+
if (issues.length === 0) {
|
|
492
|
+
if (isJson) {
|
|
493
|
+
console.log(JSON.stringify({ status: 'clean', issues: [], fixCount: 0 }));
|
|
494
|
+
} else if (isPrompt) {
|
|
495
|
+
console.log('No CDD issues found. All documentation is complete.');
|
|
496
|
+
} else {
|
|
497
|
+
console.log(` ${c.green}${c.bold}✅ No issues — documentation is complete!${c.reset}\n`);
|
|
498
|
+
}
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (isJson) {
|
|
503
|
+
console.log(JSON.stringify({
|
|
504
|
+
status: 'issues-found',
|
|
505
|
+
project: config.projectName,
|
|
506
|
+
projectType: config.projectType || 'unknown',
|
|
507
|
+
issueCount: issues.length,
|
|
508
|
+
autoFixable: issues.filter(i => i.autoFixable).length,
|
|
509
|
+
issues: issues.map(i => ({
|
|
510
|
+
type: i.type,
|
|
511
|
+
severity: i.severity,
|
|
512
|
+
file: i.file,
|
|
513
|
+
message: i.message,
|
|
514
|
+
autoFixable: i.autoFixable,
|
|
515
|
+
fix: i.fix,
|
|
516
|
+
})),
|
|
517
|
+
}, null, 2));
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (isPrompt) {
|
|
522
|
+
// Smart prompt that groups by action type
|
|
523
|
+
console.log(`You are working on "${config.projectName}" (${config.projectType || 'unknown'} project).`);
|
|
524
|
+
console.log(`DocGuard found ${issues.length} documentation issue(s).\n`);
|
|
525
|
+
|
|
526
|
+
// Group: empty/missing docs first (these need AI to write)
|
|
527
|
+
const needsWriting = issues.filter(i => i.type === 'empty-doc' || i.type === 'missing-file');
|
|
528
|
+
const needsFixing = issues.filter(i => i.type === 'partial-doc' || i.type === 'missing-env-example' || i.type === 'empty-changelog');
|
|
529
|
+
const other = issues.filter(i => !needsWriting.includes(i) && !needsFixing.includes(i));
|
|
530
|
+
|
|
531
|
+
if (needsWriting.length > 0) {
|
|
532
|
+
console.log('## Documents That Need To Be Written\n');
|
|
533
|
+
console.log('These documents are empty templates or missing. For each one, run the docguard fix --doc command to get detailed research instructions:\n');
|
|
534
|
+
for (const issue of needsWriting) {
|
|
535
|
+
const docKey = basename(issue.file, '.md').toLowerCase();
|
|
536
|
+
console.log(`- **${issue.file}**: ${issue.message}`);
|
|
537
|
+
console.log(` → Run: \`docguard fix --doc ${docKey}\` for AI research prompt\n`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (needsFixing.length > 0) {
|
|
542
|
+
console.log('## Documents That Need Improvement\n');
|
|
543
|
+
for (const issue of needsFixing) {
|
|
544
|
+
console.log(`- **${issue.file}**: ${issue.message}`);
|
|
545
|
+
console.log(` → ${issue.fix.ai_instruction}\n`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (other.length > 0) {
|
|
550
|
+
console.log('## Other Issues\n');
|
|
551
|
+
for (const issue of other) {
|
|
552
|
+
console.log(`- **${issue.file}**: ${issue.message}`);
|
|
553
|
+
console.log(` → ${issue.fix.ai_instruction}\n`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
console.log('\nAfter fixing, run `docguard guard` to verify compliance.');
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Text output
|
|
562
|
+
const errors = issues.filter(i => i.severity === 'error');
|
|
563
|
+
const warnings = issues.filter(i => i.severity === 'warning');
|
|
564
|
+
const infos = issues.filter(i => i.severity === 'info');
|
|
565
|
+
|
|
566
|
+
if (errors.length > 0) {
|
|
567
|
+
console.log(` ${c.red}${c.bold}Errors (${errors.length})${c.reset}`);
|
|
568
|
+
for (const e of errors) {
|
|
569
|
+
console.log(` ${c.red}✖${c.reset} ${e.message}`);
|
|
570
|
+
if (e.type === 'empty-doc') {
|
|
571
|
+
const docKey = basename(e.file, '.md').toLowerCase();
|
|
572
|
+
console.log(` ${c.dim}Run: ${c.cyan}docguard fix --doc ${docKey}${c.dim} → paste prompt into AI${c.reset}`);
|
|
573
|
+
} else {
|
|
574
|
+
console.log(` ${c.dim}Run: ${c.cyan}${e.fix.command || 'docguard fix --auto'}${c.reset}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
console.log('');
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (warnings.length > 0) {
|
|
581
|
+
console.log(` ${c.yellow}${c.bold}Warnings (${warnings.length})${c.reset}`);
|
|
582
|
+
for (const w of warnings) {
|
|
583
|
+
console.log(` ${c.yellow}⚠${c.reset} ${w.message}`);
|
|
584
|
+
if (w.type === 'partial-doc') {
|
|
585
|
+
const docKey = basename(w.file, '.md').toLowerCase();
|
|
586
|
+
console.log(` ${c.dim}Run: ${c.cyan}docguard fix --doc ${docKey}${c.dim} → paste prompt into AI${c.reset}`);
|
|
587
|
+
} else {
|
|
588
|
+
console.log(` ${c.dim}${w.fix.ai_instruction.slice(0, 100)}${c.reset}`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
console.log('');
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (infos.length > 0) {
|
|
595
|
+
console.log(` ${c.cyan}${c.bold}Info (${infos.length})${c.reset}`);
|
|
596
|
+
for (const info of infos) {
|
|
597
|
+
console.log(` ${c.cyan}ℹ${c.reset} ${info.message}`);
|
|
598
|
+
}
|
|
599
|
+
console.log('');
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
console.log(` ${c.bold}─────────────────────────────────────${c.reset}`);
|
|
603
|
+
console.log(` ${c.bold}Total: ${issues.length} issue(s)${c.reset}`);
|
|
604
|
+
console.log('');
|
|
605
|
+
console.log(` ${c.dim}${c.bold}Workflow:${c.reset}`);
|
|
606
|
+
console.log(` ${c.dim} 1. ${c.cyan}docguard fix --auto${c.dim} Create skeleton files${c.reset}`);
|
|
607
|
+
console.log(` ${c.dim} 2. ${c.cyan}docguard fix --doc architecture${c.dim} Get AI prompt for each doc${c.reset}`);
|
|
608
|
+
console.log(` ${c.dim} 3. Paste prompt into your AI assistant (Copilot, Cursor, Claude)${c.reset}`);
|
|
609
|
+
console.log(` ${c.dim} 4. ${c.cyan}docguard guard${c.dim} Verify compliance${c.reset}\n`);
|
|
610
|
+
}
|