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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/PHILOSOPHY.md +150 -0
  3. package/README.md +309 -0
  4. package/STANDARD.md +751 -0
  5. package/cli/commands/agents.mjs +221 -0
  6. package/cli/commands/audit.mjs +92 -0
  7. package/cli/commands/badge.mjs +72 -0
  8. package/cli/commands/ci.mjs +80 -0
  9. package/cli/commands/diagnose.mjs +273 -0
  10. package/cli/commands/diff.mjs +360 -0
  11. package/cli/commands/fix.mjs +610 -0
  12. package/cli/commands/generate.mjs +842 -0
  13. package/cli/commands/guard.mjs +158 -0
  14. package/cli/commands/hooks.mjs +227 -0
  15. package/cli/commands/init.mjs +249 -0
  16. package/cli/commands/score.mjs +396 -0
  17. package/cli/commands/watch.mjs +143 -0
  18. package/cli/docguard.mjs +458 -0
  19. package/cli/validators/architecture.mjs +380 -0
  20. package/cli/validators/changelog.mjs +39 -0
  21. package/cli/validators/docs-sync.mjs +110 -0
  22. package/cli/validators/drift.mjs +101 -0
  23. package/cli/validators/environment.mjs +70 -0
  24. package/cli/validators/freshness.mjs +224 -0
  25. package/cli/validators/security.mjs +101 -0
  26. package/cli/validators/structure.mjs +88 -0
  27. package/cli/validators/test-spec.mjs +115 -0
  28. package/docs/ai-integration.md +179 -0
  29. package/docs/commands.md +239 -0
  30. package/docs/configuration.md +96 -0
  31. package/docs/faq.md +155 -0
  32. package/docs/installation.md +81 -0
  33. package/docs/profiles.md +103 -0
  34. package/docs/quickstart.md +79 -0
  35. package/package.json +57 -0
  36. package/templates/ADR.md.template +64 -0
  37. package/templates/AGENTS.md.template +88 -0
  38. package/templates/ARCHITECTURE.md.template +78 -0
  39. package/templates/CHANGELOG.md.template +16 -0
  40. package/templates/CURRENT-STATE.md.template +64 -0
  41. package/templates/DATA-MODEL.md.template +66 -0
  42. package/templates/DEPLOYMENT.md.template +66 -0
  43. package/templates/DRIFT-LOG.md.template +18 -0
  44. package/templates/ENVIRONMENT.md.template +43 -0
  45. package/templates/KNOWN-GOTCHAS.md.template +69 -0
  46. package/templates/ROADMAP.md.template +82 -0
  47. package/templates/RUNBOOKS.md.template +115 -0
  48. package/templates/SECURITY.md.template +42 -0
  49. package/templates/TEST-SPEC.md.template +55 -0
  50. package/templates/TROUBLESHOOTING.md.template +96 -0
  51. package/templates/VENDOR-BUGS.md.template +74 -0
  52. package/templates/ci/github-actions.yml +39 -0
  53. package/templates/commands/docguard.fix.md +65 -0
  54. package/templates/commands/docguard.guard.md +40 -0
  55. package/templates/commands/docguard.init.md +62 -0
  56. package/templates/commands/docguard.review.md +44 -0
  57. package/templates/commands/docguard.update.md +44 -0
@@ -0,0 +1,458 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * DocGuard CLI — The enforcement tool for Canonical-Driven Development (CDD)
5
+ *
6
+ * Zero dependencies. Pure Node.js.
7
+ *
8
+ * Usage:
9
+ * npx docguard audit — Scan project, report what docs exist/missing
10
+ * npx docguard init — Initialize CDD docs from templates
11
+ * npx docguard guard — Validate project against its canonical docs
12
+ * npx docguard --help — Show help
13
+ *
14
+ * @see https://github.com/raccioly/docguard
15
+ */
16
+
17
+ import { readFileSync, existsSync } from 'node:fs';
18
+ import { resolve, basename } from 'node:path';
19
+ import { runAudit } from './commands/audit.mjs';
20
+ import { runInit } from './commands/init.mjs';
21
+ import { runGuard } from './commands/guard.mjs';
22
+ import { runScore } from './commands/score.mjs';
23
+ import { runDiff } from './commands/diff.mjs';
24
+ import { runAgents } from './commands/agents.mjs';
25
+ import { runGenerate } from './commands/generate.mjs';
26
+ import { runHooks } from './commands/hooks.mjs';
27
+ import { runBadge } from './commands/badge.mjs';
28
+ import { runCI } from './commands/ci.mjs';
29
+ import { runFix } from './commands/fix.mjs';
30
+ import { runWatch } from './commands/watch.mjs';
31
+ import { runDiagnose } from './commands/diagnose.mjs';
32
+
33
+ // ── Colors (ANSI escape codes, zero deps) ──────────────────────────────────
34
+ export const c = {
35
+ reset: '\x1b[0m',
36
+ bold: '\x1b[1m',
37
+ dim: '\x1b[2m',
38
+ red: '\x1b[31m',
39
+ green: '\x1b[32m',
40
+ yellow: '\x1b[33m',
41
+ blue: '\x1b[34m',
42
+ cyan: '\x1b[36m',
43
+ white: '\x1b[37m',
44
+ bgRed: '\x1b[41m',
45
+ bgGreen: '\x1b[42m',
46
+ bgYellow: '\x1b[43m',
47
+ };
48
+ // ── Compliance Profiles ───────────────────────────────────────────────────
49
+ // Profiles layer between defaults and user config — they're preset bundles
50
+ // that adjust what docs are required and which validators run.
51
+ const PROFILES = {
52
+ starter: {
53
+ description: 'Minimal CDD — just architecture + changelog. For side projects and prototypes.',
54
+ requiredFiles: {
55
+ canonical: [
56
+ 'docs-canonical/ARCHITECTURE.md',
57
+ ],
58
+ agentFile: ['AGENTS.md', 'CLAUDE.md'],
59
+ changelog: 'CHANGELOG.md',
60
+ driftLog: 'DRIFT-LOG.md',
61
+ },
62
+ validators: {
63
+ structure: true,
64
+ docsSync: true,
65
+ drift: false,
66
+ changelog: true,
67
+ architecture: false,
68
+ testSpec: false,
69
+ security: false,
70
+ environment: false,
71
+ freshness: false,
72
+ },
73
+ },
74
+ standard: {
75
+ description: 'Full CDD — all 5 canonical docs. For team projects.',
76
+ // Uses the defaults — no overrides needed
77
+ },
78
+ enterprise: {
79
+ description: 'Strict CDD — all docs, all validators, freshness enforced. For regulated/enterprise projects.',
80
+ validators: {
81
+ structure: true,
82
+ docsSync: true,
83
+ drift: true,
84
+ changelog: true,
85
+ architecture: true,
86
+ testSpec: true,
87
+ security: true,
88
+ environment: true,
89
+ freshness: true,
90
+ },
91
+ },
92
+ };
93
+
94
+ // ── Config Loading ─────────────────────────────────────────────────────────
95
+ export function loadConfig(projectDir) {
96
+ const configPath = resolve(projectDir, '.docguard.json');
97
+ const defaults = {
98
+ projectName: basename(projectDir),
99
+ version: '0.2',
100
+ profile: 'standard',
101
+ requiredFiles: {
102
+ canonical: [
103
+ 'docs-canonical/ARCHITECTURE.md',
104
+ 'docs-canonical/DATA-MODEL.md',
105
+ 'docs-canonical/SECURITY.md',
106
+ 'docs-canonical/TEST-SPEC.md',
107
+ 'docs-canonical/ENVIRONMENT.md',
108
+ ],
109
+ agentFile: ['AGENTS.md', 'CLAUDE.md'],
110
+ changelog: 'CHANGELOG.md',
111
+ driftLog: 'DRIFT-LOG.md',
112
+ },
113
+ // All CDD document types — required vs optional
114
+ documentTypes: {
115
+ // Canonical (design intent) — required by default
116
+ 'docs-canonical/ARCHITECTURE.md': { required: true, category: 'canonical', description: 'System design, components, layer boundaries' },
117
+ 'docs-canonical/DATA-MODEL.md': { required: true, category: 'canonical', description: 'Database schemas, entities, relationships' },
118
+ 'docs-canonical/SECURITY.md': { required: true, category: 'canonical', description: 'Authentication, authorization, secrets management' },
119
+ 'docs-canonical/TEST-SPEC.md': { required: true, category: 'canonical', description: 'Test categories, coverage rules, service-to-test map' },
120
+ 'docs-canonical/ENVIRONMENT.md': { required: true, category: 'canonical', description: 'Environment variables, setup steps, prerequisites' },
121
+ 'docs-canonical/DEPLOYMENT.md': { required: false, category: 'canonical', description: 'Infrastructure, CI/CD pipeline, DNS, monitoring' },
122
+ 'docs-canonical/ADR.md': { required: false, category: 'canonical', description: 'Architecture Decision Records with rationale' },
123
+ // Implementation (current state) — optional by default
124
+ 'docs-implementation/KNOWN-GOTCHAS.md': { required: false, category: 'implementation', description: 'Lessons learned — symptom/gotcha/fix format' },
125
+ 'docs-implementation/TROUBLESHOOTING.md': { required: false, category: 'implementation', description: 'Error diagnosis guides by category' },
126
+ 'docs-implementation/RUNBOOKS.md': { required: false, category: 'implementation', description: 'Operational procedures (deploy, rollback, backup)' },
127
+ 'docs-implementation/CURRENT-STATE.md': { required: false, category: 'implementation', description: 'Deployment status, feature completion, tech debt' },
128
+ 'docs-implementation/VENDOR-BUGS.md': { required: false, category: 'implementation', description: 'Third-party bug tracker with workarounds' },
129
+ // Root files
130
+ 'AGENTS.md': { required: true, category: 'agent', description: 'AI agent behavior rules and project context' },
131
+ 'CHANGELOG.md': { required: true, category: 'tracking', description: 'All notable changes per Keep a Changelog format' },
132
+ 'DRIFT-LOG.md': { required: true, category: 'tracking', description: 'Documented deviations from canonical docs' },
133
+ 'ROADMAP.md': { required: false, category: 'tracking', description: 'Project phases, feature tracking, vision' },
134
+ },
135
+ sourcePatterns: {
136
+ services: 'src/services/**/*.{ts,js,py,java}',
137
+ routes: 'src/routes/**/*.{ts,js,py,java}',
138
+ tests: 'tests/**/*.test.{ts,js,py,java}',
139
+ },
140
+ validators: {
141
+ structure: true,
142
+ docsSync: true,
143
+ drift: true,
144
+ changelog: true,
145
+ architecture: false,
146
+ testSpec: true,
147
+ security: false,
148
+ environment: true,
149
+ freshness: true,
150
+ },
151
+ };
152
+
153
+ if (existsSync(configPath)) {
154
+ try {
155
+ const userConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
156
+
157
+ // Apply profile presets BEFORE merging user config
158
+ // Profile sets the baseline, user config can override anything
159
+ const profileName = userConfig.profile || defaults.profile;
160
+ const profilePreset = PROFILES[profileName];
161
+ const withProfile = profilePreset
162
+ ? deepMerge(defaults, profilePreset)
163
+ : defaults;
164
+
165
+ const merged = deepMerge(withProfile, userConfig);
166
+ merged.profile = profileName;
167
+
168
+ // Auto-detect project type if not set
169
+ if (!merged.projectType) {
170
+ merged.projectType = autoDetectProjectType(projectDir);
171
+ }
172
+ // Ensure projectTypeConfig has sensible defaults based on type
173
+ merged.projectTypeConfig = {
174
+ ...getProjectTypeDefaults(merged.projectType),
175
+ ...(merged.projectTypeConfig || {}),
176
+ };
177
+ return merged;
178
+ } catch (e) {
179
+ console.error(`${c.red}Error parsing .docguard.json: ${e.message}${c.reset}`);
180
+ process.exit(1);
181
+ }
182
+ }
183
+
184
+ // No config file — auto-detect everything
185
+ defaults.projectType = autoDetectProjectType(projectDir);
186
+ defaults.projectTypeConfig = getProjectTypeDefaults(defaults.projectType);
187
+ return defaults;
188
+ }
189
+
190
+ // Export profiles for use by other commands (init --profile)
191
+ export { PROFILES };
192
+
193
+ /**
194
+ * Auto-detect project type from package.json and file structure.
195
+ * Returns: 'cli' | 'library' | 'webapp' | 'api' | 'unknown'
196
+ */
197
+ function autoDetectProjectType(dir) {
198
+ const pkgPath = resolve(dir, 'package.json');
199
+ if (existsSync(pkgPath)) {
200
+ try {
201
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
202
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
203
+
204
+ // CLI tool: has "bin" field
205
+ if (pkg.bin) return 'cli';
206
+
207
+ // Web app: has a frontend framework
208
+ if (allDeps.next || allDeps.react || allDeps.vue || allDeps['@angular/core'] ||
209
+ allDeps.svelte || allDeps.nuxt || allDeps['@sveltejs/kit']) return 'webapp';
210
+
211
+ // API: has a server framework but no frontend
212
+ if (allDeps.express || allDeps.fastify || allDeps.hono || allDeps.koa) return 'api';
213
+
214
+ // Library: has "main" or "exports" and no framework
215
+ if (pkg.main || pkg.exports || pkg.module) return 'library';
216
+ } catch { /* fall through */ }
217
+ }
218
+
219
+ // Python project
220
+ if (existsSync(resolve(dir, 'manage.py'))) return 'webapp';
221
+ if (existsSync(resolve(dir, 'setup.py')) || existsSync(resolve(dir, 'pyproject.toml'))) return 'library';
222
+
223
+ return 'unknown';
224
+ }
225
+
226
+ /**
227
+ * Get default projectTypeConfig for a given project type.
228
+ */
229
+ function getProjectTypeDefaults(type) {
230
+ const defaults = {
231
+ cli: { needsEnvVars: false, needsEnvExample: false, needsE2E: false, needsDatabase: false, testFramework: 'node:test', runCommand: null },
232
+ library: { needsEnvVars: false, needsEnvExample: false, needsE2E: false, needsDatabase: false, testFramework: 'vitest', runCommand: null },
233
+ webapp: { needsEnvVars: true, needsEnvExample: true, needsE2E: true, needsDatabase: true, testFramework: 'vitest', runCommand: 'npm run dev' },
234
+ api: { needsEnvVars: true, needsEnvExample: true, needsE2E: false, needsDatabase: true, testFramework: 'vitest', runCommand: 'npm run dev' },
235
+ unknown: { needsEnvVars: true, needsEnvExample: true, needsE2E: false, needsDatabase: true, testFramework: null, runCommand: null },
236
+ };
237
+ return defaults[type] || defaults.unknown;
238
+ }
239
+
240
+ function deepMerge(target, source) {
241
+ const result = { ...target };
242
+ for (const key of Object.keys(source)) {
243
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
244
+ result[key] = deepMerge(target[key] || {}, source[key]);
245
+ } else {
246
+ result[key] = source[key];
247
+ }
248
+ }
249
+ return result;
250
+ }
251
+
252
+ // ── Banner ─────────────────────────────────────────────────────────────────
253
+ function printBanner() {
254
+ console.log(`
255
+ ${c.cyan}${c.bold} ╔═══════════════════════════════════════════╗
256
+ ║ DocGuard v0.5.0 ║
257
+ ║ Canonical-Driven Development (CDD) ║
258
+ ╚═══════════════════════════════════════════╝${c.reset}
259
+ `);
260
+ }
261
+
262
+ // ── Help ───────────────────────────────────────────────────────────────────
263
+ function printHelp() {
264
+ printBanner();
265
+ console.log(`${c.bold}Usage:${c.reset}
266
+ docguard <command> [options]
267
+
268
+ ${c.bold}Commands:${c.reset}
269
+ ${c.green}audit${c.reset} Scan project, report what CDD docs exist or are missing
270
+ ${c.green}init${c.reset} Initialize CDD documentation from templates
271
+ ${c.green}guard${c.reset} Validate project against its canonical documentation
272
+ ${c.green}score${c.reset} Calculate CDD maturity score (0-100)
273
+ ${c.green}diagnose${c.reset} AI orchestrator — chains guard→fix in one command
274
+ ${c.green}diff${c.reset} Show gaps between canonical docs and code
275
+ ${c.green}agents${c.reset} Generate agent-specific config files from AGENTS.md
276
+ ${c.green}generate${c.reset} Reverse-engineer canonical docs from existing code
277
+ ${c.green}hooks${c.reset} Install git hooks (pre-commit, pre-push, commit-msg)
278
+ ${c.green}badge${c.reset} Generate CDD score badges for README
279
+ ${c.green}ci${c.reset} Single command for CI/CD pipelines (guard + score)
280
+ ${c.green}fix${c.reset} Find issues and generate AI fix instructions
281
+ ${c.green}watch${c.reset} Watch for file changes and re-run guard automatically
282
+
283
+ ${c.bold}Options:${c.reset}
284
+ --dir <path> Project directory (default: current directory)
285
+ --verbose Show detailed output
286
+ --format json Output results as JSON (for CI)
287
+ --fix Auto-create missing files from templates
288
+ --force Overwrite existing files (for agents/init)
289
+ --agent <name> Target specific agent (for agents command)
290
+ --type <name> Hook type: pre-commit, pre-push, commit-msg
291
+ --list List available hooks and their status
292
+ --remove Remove installed DocGuard hooks
293
+ --threshold <n> Minimum score for CI pass (used with ci command)
294
+ --fail-on-warning Fail CI on warnings (used with ci command)
295
+ --auto Auto-fix what's possible (used with fix command)
296
+ --doc <name> Generate AI prompt for specific doc (architecture, security, etc.)
297
+ --profile <p> Compliance profile: starter, standard, enterprise (init command)
298
+ --tax Show estimated documentation maintenance cost (with score)
299
+ --help Show this help message
300
+ --version Show version
301
+
302
+ ${c.bold}Profiles:${c.reset}
303
+ ${c.green}starter${c.reset} Minimal CDD — just ARCHITECTURE.md + CHANGELOG (side projects)
304
+ ${c.green}standard${c.reset} Full CDD — all 5 canonical docs (default, team projects)
305
+ ${c.green}enterprise${c.reset} Strict CDD — all docs + all validators + freshness enforced
306
+
307
+ ${c.bold}Examples:${c.reset}
308
+ ${c.dim}# AI auto-diagnose and fix${c.reset}
309
+ docguard diagnose
310
+
311
+ ${c.dim}# Quick start for a side project${c.reset}
312
+ docguard init --profile starter
313
+
314
+ ${c.dim}# Full CDD init (default)${c.reset}
315
+ docguard init
316
+
317
+ ${c.dim}# See documentation tax estimate${c.reset}
318
+ docguard score --tax
319
+
320
+ ${c.bold}Configuration:${c.reset}
321
+ Create ${c.cyan}.docguard.json${c.reset} in your project root to customize validators.
322
+ See: https://github.com/raccioly/docguard
323
+
324
+ ${c.bold}Learn more:${c.reset}
325
+ Canonical-Driven Development: ${c.cyan}PHILOSOPHY.md${c.reset}
326
+ Full standard: ${c.cyan}STANDARD.md${c.reset}
327
+ `);
328
+ }
329
+
330
+ // ── Main ───────────────────────────────────────────────────────────────────
331
+ function main() {
332
+ const args = process.argv.slice(2);
333
+ const command = args[0];
334
+
335
+ // Parse flags
336
+ const flags = {
337
+ dir: '.',
338
+ verbose: false,
339
+ format: 'text',
340
+ fix: false,
341
+ force: false,
342
+ agent: null,
343
+ };
344
+
345
+ for (let i = 1; i < args.length; i++) {
346
+ if (args[i] === '--dir' && args[i + 1]) {
347
+ flags.dir = args[i + 1];
348
+ i++;
349
+ } else if (args[i] === '--verbose') {
350
+ flags.verbose = true;
351
+ } else if (args[i] === '--format' && args[i + 1]) {
352
+ flags.format = args[i + 1];
353
+ i++;
354
+ } else if (args[i] === '--fix') {
355
+ flags.fix = true;
356
+ } else if (args[i] === '--force') {
357
+ flags.force = true;
358
+ } else if (args[i] === '--agent' && args[i + 1]) {
359
+ flags.agent = args[i + 1];
360
+ i++;
361
+ } else if (args[i] === '--type' && args[i + 1]) {
362
+ flags.type = args[i + 1];
363
+ i++;
364
+ } else if (args[i] === '--list') {
365
+ flags.list = true;
366
+ } else if (args[i] === '--remove') {
367
+ flags.remove = true;
368
+ } else if (args[i] === '--threshold' && args[i + 1]) {
369
+ flags.threshold = args[i + 1];
370
+ i++;
371
+ } else if (args[i] === '--fail-on-warning') {
372
+ flags.failOnWarning = true;
373
+ } else if (args[i] === '--auto') {
374
+ flags.auto = true;
375
+ } else if (args[i] === '--doc' && args[i + 1]) {
376
+ flags.doc = args[i + 1];
377
+ i++;
378
+ } else if (args[i] === '--profile' && args[i + 1]) {
379
+ flags.profile = args[i + 1];
380
+ i++;
381
+ } else if (args[i] === '--tax') {
382
+ flags.tax = true;
383
+ } else if (args[i] === '--auto-fix') {
384
+ flags.autoFix = true;
385
+ } else if (args[i] === '--skip-prompts') {
386
+ flags.skipPrompts = true;
387
+ }
388
+ }
389
+
390
+ const projectDir = resolve(flags.dir);
391
+
392
+ if (!command || command === '--help' || command === '-h') {
393
+ printHelp();
394
+ process.exit(0);
395
+ }
396
+
397
+ if (command === '--version' || command === '-v') {
398
+ console.log('docguard v0.5.0');
399
+ process.exit(0);
400
+ }
401
+
402
+ printBanner();
403
+
404
+ const config = loadConfig(projectDir);
405
+
406
+ switch (command) {
407
+ case 'audit':
408
+ runAudit(projectDir, config, flags);
409
+ break;
410
+ case 'init':
411
+ runInit(projectDir, config, flags);
412
+ break;
413
+ case 'guard':
414
+ runGuard(projectDir, config, flags);
415
+ break;
416
+ case 'score':
417
+ runScore(projectDir, config, flags);
418
+ break;
419
+ case 'diff':
420
+ runDiff(projectDir, config, flags);
421
+ break;
422
+ case 'agents':
423
+ runAgents(projectDir, config, flags);
424
+ break;
425
+ case 'generate':
426
+ case 'gen':
427
+ runGenerate(projectDir, config, flags);
428
+ break;
429
+ case 'hooks':
430
+ runHooks(projectDir, config, flags);
431
+ break;
432
+ case 'badge':
433
+ case 'badges':
434
+ runBadge(projectDir, config, flags);
435
+ break;
436
+ case 'ci':
437
+ case 'pipeline':
438
+ runCI(projectDir, config, flags);
439
+ break;
440
+ case 'fix':
441
+ case 'repair':
442
+ runFix(projectDir, config, flags);
443
+ break;
444
+ case 'diagnose':
445
+ case 'dx':
446
+ runDiagnose(projectDir, config, flags);
447
+ break;
448
+ case 'watch':
449
+ runWatch(projectDir, config, flags);
450
+ break;
451
+ default:
452
+ console.error(`${c.red}Unknown command: ${command}${c.reset}`);
453
+ console.log(`Run ${c.cyan}docguard --help${c.reset} for usage.`);
454
+ process.exit(1);
455
+ }
456
+ }
457
+
458
+ main();