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,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 = ``;
|
|
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 = ``;
|
|
35
|
+
|
|
36
|
+
// DocGuard badge
|
|
37
|
+
const sgBadgeUrl = `https://img.shields.io/badge/guarded_by-DocGuard-cyan`;
|
|
38
|
+
const sgBadgeMarkdown = ``;
|
|
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
|
+
}
|