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,221 @@
1
+ /**
2
+ * Agents Command — Generate agent-specific config files from AGENTS.md
3
+ * Creates .cursor/rules/, .clinerules, .github/copilot-instructions.md, etc.
4
+ */
5
+
6
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
7
+ import { resolve, dirname } from 'node:path';
8
+ import { c } from '../docguard.mjs';
9
+
10
+ const AGENT_TARGETS = {
11
+ cursor: {
12
+ path: '.cursor/rules/cdd.mdc',
13
+ name: 'Cursor',
14
+ generate: generateCursorRules,
15
+ },
16
+ copilot: {
17
+ path: '.github/copilot-instructions.md',
18
+ name: 'GitHub Copilot',
19
+ generate: generateCopilotInstructions,
20
+ },
21
+ cline: {
22
+ path: '.clinerules',
23
+ name: 'Cline',
24
+ generate: generateClineRules,
25
+ },
26
+ windsurf: {
27
+ path: '.windsurfrules',
28
+ name: 'Windsurf',
29
+ generate: generateWindsurfRules,
30
+ },
31
+ claude: {
32
+ path: 'CLAUDE.md',
33
+ name: 'Claude Code',
34
+ generate: generateClaudeMd,
35
+ },
36
+ gemini: {
37
+ path: '.gemini/settings.json',
38
+ name: 'Gemini CLI',
39
+ generate: generateGeminiSettings,
40
+ },
41
+ };
42
+
43
+ export function runAgents(projectDir, config, flags) {
44
+ console.log(`${c.bold}🤖 DocGuard Agents — ${config.projectName}${c.reset}`);
45
+ console.log(`${c.dim} Directory: ${projectDir}${c.reset}\n`);
46
+
47
+ // Read AGENTS.md content
48
+ const agentsPath = resolve(projectDir, 'AGENTS.md');
49
+ if (!existsSync(agentsPath)) {
50
+ console.log(` ${c.red}❌ AGENTS.md not found. Run ${c.cyan}docguard init${c.red} first.${c.reset}\n`);
51
+ process.exit(1);
52
+ }
53
+
54
+ const agentsContent = readFileSync(agentsPath, 'utf-8');
55
+
56
+ // Parse which agents to generate for
57
+ let targets = Object.keys(AGENT_TARGETS);
58
+ const specificAgent = flags.agent;
59
+ if (specificAgent) {
60
+ if (!AGENT_TARGETS[specificAgent]) {
61
+ console.log(` ${c.red}Unknown agent: ${specificAgent}${c.reset}`);
62
+ console.log(` Available: ${targets.join(', ')}\n`);
63
+ process.exit(1);
64
+ }
65
+ targets = [specificAgent];
66
+ }
67
+
68
+ let created = 0;
69
+ let skipped = 0;
70
+
71
+ for (const key of targets) {
72
+ const target = AGENT_TARGETS[key];
73
+ const targetPath = resolve(projectDir, target.path);
74
+
75
+ if (existsSync(targetPath) && !flags.force) {
76
+ console.log(` ${c.dim}⏭️ ${target.name}: ${target.path} (exists, use --force to overwrite)${c.reset}`);
77
+ skipped++;
78
+ continue;
79
+ }
80
+
81
+ const content = target.generate(agentsContent, config);
82
+
83
+ // Create directories
84
+ const dir = dirname(targetPath);
85
+ if (!existsSync(dir)) {
86
+ mkdirSync(dir, { recursive: true });
87
+ }
88
+
89
+ writeFileSync(targetPath, content, 'utf-8');
90
+ console.log(` ${c.green}✅ ${target.name}${c.reset}: ${target.path}`);
91
+ created++;
92
+ }
93
+
94
+ console.log(`\n${c.bold} ─────────────────────────────────────${c.reset}`);
95
+ console.log(` Created: ${created} Skipped: ${skipped}\n`);
96
+ }
97
+
98
+ // ── Generator Functions ────────────────────────────────────────────────────
99
+
100
+ function getCddBlock(config) {
101
+ return `## Canonical-Driven Development (CDD)
102
+
103
+ This project follows the CDD methodology. Documentation is the source of truth.
104
+
105
+ ### Required Reading (Before Any Code Change)
106
+ - \`docs-canonical/ARCHITECTURE.md\` — System design and boundaries
107
+ - \`docs-canonical/DATA-MODEL.md\` — Database schemas
108
+ - \`docs-canonical/SECURITY.md\` — Auth and secrets rules
109
+ - \`docs-canonical/TEST-SPEC.md\` — Test requirements
110
+ - \`docs-canonical/ENVIRONMENT.md\` — Environment setup
111
+
112
+ ### Rules
113
+ 1. Read canonical docs BEFORE writing code
114
+ 2. If code deviates from docs, add \`// DRIFT: reason\` comment
115
+ 3. Log all drift in \`DRIFT-LOG.md\`
116
+ 4. Update \`CHANGELOG.md\` for every change
117
+ 5. Never modify canonical docs without team review`;
118
+ }
119
+
120
+ function generateCursorRules(agentsContent, config) {
121
+ return `---
122
+ description: CDD rules for ${config.projectName}
123
+ globs: "**/*"
124
+ ---
125
+
126
+ ${getCddBlock(config)}
127
+
128
+ ### Workflow
129
+ 1. Check \`docs-canonical/\` before suggesting changes
130
+ 2. Match existing code patterns
131
+ 3. Add \`// DRIFT: reason\` if deviating from canonical docs
132
+ 4. Update CHANGELOG.md for every meaningful change
133
+
134
+ ### Original AGENTS.md Content
135
+ ${agentsContent}
136
+ `;
137
+ }
138
+
139
+ function generateCopilotInstructions(agentsContent, config) {
140
+ return `# GitHub Copilot Instructions — ${config.projectName}
141
+
142
+ ${getCddBlock(config)}
143
+
144
+ ### For Copilot
145
+ - Prioritize suggestions that align with canonical documentation
146
+ - When generating new files, follow the patterns in \`docs-canonical/ARCHITECTURE.md\`
147
+ - Always suggest tests that match \`docs-canonical/TEST-SPEC.md\` requirements
148
+
149
+ ---
150
+
151
+ ${agentsContent}
152
+ `;
153
+ }
154
+
155
+ function generateClineRules(agentsContent, config) {
156
+ return `# Cline Rules — ${config.projectName}
157
+
158
+ ${getCddBlock(config)}
159
+
160
+ ### For Cline
161
+ - Always research docs-canonical/ before suggesting changes
162
+ - Show what docs you checked before proposing code
163
+ - Flag any drift from canonical docs
164
+
165
+ ---
166
+
167
+ ${agentsContent}
168
+ `;
169
+ }
170
+
171
+ function generateWindsurfRules(agentsContent, config) {
172
+ return `# Windsurf Rules — ${config.projectName}
173
+
174
+ ${getCddBlock(config)}
175
+
176
+ ---
177
+
178
+ ${agentsContent}
179
+ `;
180
+ }
181
+
182
+ function generateClaudeMd(agentsContent, config) {
183
+ return `# CLAUDE.md — ${config.projectName}
184
+
185
+ ${getCddBlock(config)}
186
+
187
+ ### Pre-Implementation Checklist
188
+ Before suggesting any code changes:
189
+ \`\`\`
190
+ 1. Docs reviewed: [which canonical docs you checked]
191
+ 2. Existing patterns: [similar code found]
192
+ 3. Proposed approach: [your plan]
193
+ 4. Files to change: [list]
194
+ 5. Risk level: LOW | MEDIUM | HIGH
195
+ \`\`\`
196
+
197
+ ---
198
+
199
+ ${agentsContent}
200
+ `;
201
+ }
202
+
203
+ function generateGeminiSettings(agentsContent, config) {
204
+ return JSON.stringify({
205
+ projectName: config.projectName,
206
+ methodology: 'Canonical-Driven Development (CDD)',
207
+ canonicalDocs: [
208
+ 'docs-canonical/ARCHITECTURE.md',
209
+ 'docs-canonical/DATA-MODEL.md',
210
+ 'docs-canonical/SECURITY.md',
211
+ 'docs-canonical/TEST-SPEC.md',
212
+ 'docs-canonical/ENVIRONMENT.md',
213
+ ],
214
+ rules: [
215
+ 'Read canonical docs before suggesting code changes',
216
+ 'Add // DRIFT: comments for deviations',
217
+ 'Update CHANGELOG.md for every change',
218
+ 'Log drift in DRIFT-LOG.md',
219
+ ],
220
+ }, null, 2);
221
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Audit Command — Scan project, report what CDD docs exist or are missing
3
+ * Now uses the full documentTypes config to show ALL docs with categories.
4
+ */
5
+
6
+ import { existsSync } from 'node:fs';
7
+ import { resolve } from 'node:path';
8
+ import { c } from '../docguard.mjs';
9
+
10
+ export function runAudit(projectDir, config, flags) {
11
+ console.log(`${c.bold}📋 DocGuard Audit — ${config.projectName}${c.reset}`);
12
+ console.log(`${c.dim} Directory: ${projectDir}${c.reset}\n`);
13
+
14
+ const results = { found: 0, missing: 0, optional: 0, total: 0, details: [] };
15
+
16
+ // Use documentTypes from config (all 16 doc types with required/optional)
17
+ const docTypes = config.documentTypes || {};
18
+
19
+ // Group by category
20
+ const categories = {};
21
+ for (const [filePath, meta] of Object.entries(docTypes)) {
22
+ const cat = meta.category || 'other';
23
+ if (!categories[cat]) categories[cat] = [];
24
+ categories[cat].push({ filePath, ...meta });
25
+ }
26
+
27
+ // Category display names and order
28
+ const categoryLabels = {
29
+ canonical: '📘 Canonical Documentation (Design Intent)',
30
+ implementation: '📗 Implementation Documentation (Current State)',
31
+ agent: '🤖 Agent Instructions',
32
+ tracking: '📑 Change Tracking',
33
+ };
34
+
35
+ const categoryOrder = ['canonical', 'implementation', 'agent', 'tracking'];
36
+
37
+ for (const cat of categoryOrder) {
38
+ const docs = categories[cat];
39
+ if (!docs || docs.length === 0) continue;
40
+
41
+ console.log(`${c.bold} ${categoryLabels[cat] || cat}${c.reset}`);
42
+
43
+ for (const doc of docs) {
44
+ const fullPath = resolve(projectDir, doc.filePath);
45
+ const exists = existsSync(fullPath);
46
+
47
+ if (exists) {
48
+ results.found++;
49
+ results.total++;
50
+ results.details.push({ file: doc.filePath, status: 'found', required: doc.required });
51
+ console.log(` ${c.green}✅${c.reset} ${doc.filePath} ${c.dim}— ${doc.description}${c.reset}`);
52
+ } else if (doc.required) {
53
+ results.missing++;
54
+ results.total++;
55
+ results.details.push({ file: doc.filePath, status: 'missing', required: true });
56
+ console.log(` ${c.red}❌${c.reset} ${doc.filePath} ${c.dim}— ${doc.description}${c.reset} ${c.red}(required)${c.reset}`);
57
+ } else {
58
+ results.optional++;
59
+ results.details.push({ file: doc.filePath, status: 'optional', required: false });
60
+ if (flags.verbose) {
61
+ console.log(` ${c.dim}○ ${doc.filePath} — ${doc.description} (optional)${c.reset}`);
62
+ }
63
+ }
64
+ }
65
+ console.log('');
66
+ }
67
+
68
+ // Score (only required files)
69
+ const requiredTotal = results.found + results.missing;
70
+ const pct = requiredTotal === 0 ? 100 : Math.round((results.found / requiredTotal) * 100);
71
+ const scoreColor = pct >= 80 ? c.green : pct >= 50 ? c.yellow : c.red;
72
+
73
+ console.log(`${c.bold} ─────────────────────────────────────${c.reset}`);
74
+ console.log(` ${c.bold}Required:${c.reset} ${scoreColor}${results.found - (results.found - requiredTotal + results.missing)}/${requiredTotal} files (${pct}%)${c.reset}`);
75
+
76
+ // Show optional count
77
+ if (!flags.verbose && results.optional > 0) {
78
+ console.log(` ${c.dim}Optional: ${results.optional} not present (use --verbose to see all)${c.reset}`);
79
+ }
80
+
81
+ if (results.missing > 0) {
82
+ console.log(`\n ${c.yellow}💡 Run ${c.cyan}docguard init${c.yellow} to create missing docs from templates.${c.reset}`);
83
+ console.log(` ${c.yellow}💡 Run ${c.cyan}docguard generate${c.yellow} to auto-fill docs from your codebase.${c.reset}`);
84
+ } else {
85
+ console.log(`\n ${c.green}🎉 All required CDD documentation present!${c.reset}`);
86
+ console.log(` ${c.dim}Run ${c.cyan}docguard guard${c.dim} to validate content alignment.${c.reset}`);
87
+ console.log(` ${c.dim}Run ${c.cyan}docguard score${c.dim} to check your CDD maturity.${c.reset}`);
88
+ }
89
+
90
+ console.log('');
91
+ return results;
92
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Badge Command — Generate shields.io badge URLs and markdown for CDD score
3
+ * Outputs badge markdown or JSON for README, CI, and dashboards.
4
+ */
5
+
6
+ import { c } from '../docguard.mjs';
7
+ import { runScoreInternal } from './score.mjs';
8
+
9
+ export function runBadge(projectDir, config, flags) {
10
+ console.log(`${c.bold}🏷️ DocGuard Badge — ${config.projectName}${c.reset}`);
11
+ console.log(`${c.dim} Directory: ${projectDir}${c.reset}\n`);
12
+
13
+ // Get score internally
14
+ const scoreData = runScoreInternal(projectDir, config);
15
+ const score = scoreData.score;
16
+ const grade = scoreData.grade;
17
+
18
+ // Determine badge color
19
+ let color;
20
+ if (score >= 90) color = 'brightgreen';
21
+ else if (score >= 80) color = 'green';
22
+ else if (score >= 70) color = 'yellowgreen';
23
+ else if (score >= 60) color = 'yellow';
24
+ else if (score >= 50) color = 'orange';
25
+ else color = 'red';
26
+
27
+ // Shields.io badge URL
28
+ const badgeUrl = `https://img.shields.io/badge/CDD_Score-${score}%2F100_(${grade})-${color}`;
29
+ const badgeMarkdown = `![CDD Score](${badgeUrl})`;
30
+
31
+ // Separate badge for project type
32
+ const projectType = config.projectType || 'unknown';
33
+ const typeBadgeUrl = `https://img.shields.io/badge/type-${projectType}-blue`;
34
+ const typeBadgeMarkdown = `![Type](${typeBadgeUrl})`;
35
+
36
+ // DocGuard badge
37
+ const sgBadgeUrl = `https://img.shields.io/badge/guarded_by-DocGuard-cyan`;
38
+ const sgBadgeMarkdown = `![DocGuard](${sgBadgeUrl})`;
39
+
40
+ if (flags.format === 'json') {
41
+ const result = {
42
+ score,
43
+ grade,
44
+ color,
45
+ projectType,
46
+ badges: {
47
+ score: { url: badgeUrl, markdown: badgeMarkdown },
48
+ type: { url: typeBadgeUrl, markdown: typeBadgeMarkdown },
49
+ docguard: { url: sgBadgeUrl, markdown: sgBadgeMarkdown },
50
+ },
51
+ readmeSnippet: `${badgeMarkdown} ${typeBadgeMarkdown} ${sgBadgeMarkdown}`,
52
+ };
53
+ console.log(JSON.stringify(result, null, 2));
54
+ return;
55
+ }
56
+
57
+ // Display badges
58
+ console.log(` ${c.bold}Score Badge:${c.reset}`);
59
+ console.log(` ${c.cyan}${badgeMarkdown}${c.reset}\n`);
60
+
61
+ console.log(` ${c.bold}Type Badge:${c.reset}`);
62
+ console.log(` ${c.cyan}${typeBadgeMarkdown}${c.reset}\n`);
63
+
64
+ console.log(` ${c.bold}DocGuard Badge:${c.reset}`);
65
+ console.log(` ${c.cyan}${sgBadgeMarkdown}${c.reset}\n`);
66
+
67
+ console.log(` ${c.bold}README snippet:${c.reset}`);
68
+ console.log(` ${c.dim}Add this to the top of your README.md:${c.reset}\n`);
69
+ console.log(` ${badgeMarkdown} ${typeBadgeMarkdown} ${sgBadgeMarkdown}`);
70
+
71
+ console.log('');
72
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * CI Command — Single command for CI/CD pipelines
3
+ * Uses runGuardInternal directly (no subprocess) for reliability.
4
+ *
5
+ * Exit codes:
6
+ * 0 = All pass, score meets threshold
7
+ * 1 = Guard errors or score below threshold
8
+ * 2 = Guard warnings only
9
+ */
10
+
11
+ import { c } from '../docguard.mjs';
12
+ import { runGuardInternal } from './guard.mjs';
13
+ import { runScoreInternal } from './score.mjs';
14
+
15
+ export function runCI(projectDir, config, flags) {
16
+ const threshold = parseInt(flags.threshold || '0', 10);
17
+ const failOnWarning = flags.failOnWarning || false;
18
+ const isJson = flags.format === 'json';
19
+
20
+ if (!isJson) {
21
+ console.log(`${c.bold}🔄 DocGuard CI — ${config.projectName}${c.reset}`);
22
+ console.log(`${c.dim} Directory: ${projectDir}${c.reset}`);
23
+ if (threshold > 0) console.log(`${c.dim} Score threshold: ${threshold}${c.reset}`);
24
+ console.log('');
25
+ }
26
+
27
+ // ── Run guard (internal — no subprocess) ──
28
+ const guardData = runGuardInternal(projectDir, config);
29
+ const hasErrors = guardData.errors > 0;
30
+ const hasWarnings = guardData.warnings > 0;
31
+
32
+ // ── Get score ──
33
+ const scoreData = runScoreInternal(projectDir, config);
34
+
35
+ // ── Output ──
36
+ if (isJson) {
37
+ const result = {
38
+ project: config.projectName,
39
+ profile: config.profile || 'standard',
40
+ projectType: config.projectType || 'unknown',
41
+ score: scoreData.score,
42
+ grade: scoreData.grade,
43
+ guard: {
44
+ passed: guardData.passed,
45
+ total: guardData.total,
46
+ status: guardData.status,
47
+ validators: guardData.validators.filter(v => v.status !== 'skipped'),
48
+ },
49
+ threshold,
50
+ thresholdMet: threshold <= 0 || scoreData.score >= threshold,
51
+ status: hasErrors ? 'FAIL' : hasWarnings ? 'WARN' : 'PASS',
52
+ timestamp: new Date().toISOString(),
53
+ };
54
+ console.log(JSON.stringify(result, null, 2));
55
+ } else {
56
+ // Text output
57
+ const guardStatus = hasErrors
58
+ ? `${c.red}❌ FAIL${c.reset}`
59
+ : hasWarnings
60
+ ? `${c.yellow}⚠️ WARN${c.reset}`
61
+ : `${c.green}✅ PASS${c.reset}`;
62
+
63
+ console.log(` ${c.bold}Guard:${c.reset} ${guardStatus} (${guardData.passed}/${guardData.total})`);
64
+ console.log(` ${c.bold}Score:${c.reset} ${scoreData.score}/100 (${scoreData.grade})`);
65
+
66
+ if (threshold > 0) {
67
+ const met = scoreData.score >= threshold;
68
+ console.log(` ${c.bold}Threshold:${c.reset} ${met ? `${c.green}✅ ≥${threshold}` : `${c.red}❌ <${threshold}`}${c.reset}`);
69
+ }
70
+
71
+ console.log('');
72
+ }
73
+
74
+ // Exit code determination
75
+ if (hasErrors) process.exit(1);
76
+ if (threshold > 0 && scoreData.score < threshold) process.exit(1);
77
+ if (failOnWarning && hasWarnings) process.exit(1);
78
+ if (hasWarnings) process.exit(2);
79
+ process.exit(0);
80
+ }