docguard-cli 0.5.2 → 0.7.0

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/PHILOSOPHY.md CHANGED
@@ -143,6 +143,28 @@ CDD is designed for progressive adoption:
143
143
 
144
144
  ---
145
145
 
146
+ ## Academic Foundations
147
+
148
+ CDD is a practitioner methodology, but its core patterns align with peer-reviewed research in AI-driven documentation and quality evaluation:
149
+
150
+ ### The Three-Stage Pipeline
151
+
152
+ DocGuard's architecture follows a **generate → validate → evaluate** pipeline inspired by the AITPG framework (Lopez et al., IEEE TSE 2026), which demonstrated that multi-agent debate over RAG-grounded standards produces 32% more comprehensive documentation while maintaining semantic alignment with expert references.
153
+
154
+ ### Calibrated Quality Evaluation
155
+
156
+ DocGuard's quality scoring (HIGH/MEDIUM/LOW labels, multi-signal composite scores) adapts the Calibrated Judge Evaluation (CJE) framework from TRACE (Lopez et al., IEEE TMLCN 2026), which showed that weighted multi-signal scoring with confidence intervals provides actionable quality gating for enterprise deployment.
157
+
158
+ ### Standards-Grounded Generation
159
+
160
+ Both AITPG and TRACE demonstrate that grounding AI-generated content in vectorized standards (ISO 29119, 3GPP) dramatically improves output quality. DocGuard applies this principle by mapping each canonical document to its relevant industry standard (arc42, C4, OWASP ASVS, ISO 29119, OpenAPI 3.1, 12-Factor App).
161
+
162
+ > **Full citations**: See [CONTRIBUTING.md — Research & Academic Credits](CONTRIBUTING.md#research--academic-credits)
163
+ >
164
+ > **Lead researcher**: [Martin Manuel Lopez](https://github.com/martinmanuel9) · [ORCID 0009-0002-7652-2385](https://orcid.org/0009-0002-7652-2385), University of Arizona
165
+
166
+ ---
167
+
146
168
  ## License
147
169
 
148
170
  This philosophy document and the CDD methodology are released under the [MIT License](https://opensource.org/licenses/MIT).
package/README.md CHANGED
@@ -322,6 +322,19 @@ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
322
322
 
323
323
  ---
324
324
 
325
+ ## Research Credits
326
+
327
+ DocGuard's quality evaluation and documentation generation patterns are informed by peer-reviewed research from the University of Arizona and the Joint Interoperability Test Command (JITC), U.S. Department of Defense:
328
+
329
+ - **AITPG** — AI-driven Test Plan Generator using Multi-Agent Debate and RAG ([Lopez et al., IEEE TSE 2026](Research/AITPG.pdf))
330
+ - **TRACE** — Telecom Root Cause Analysis through Calibrated Explainability ([Lopez et al., IEEE TMLCN 2026](Research/TRACE.pdf))
331
+
332
+ Lead researcher: **[Martin Manuel Lopez](https://github.com/martinmanuel9)** · [ORCID 0009-0002-7652-2385](https://orcid.org/0009-0002-7652-2385)
333
+
334
+ See [CONTRIBUTING.md](CONTRIBUTING.md#research--academic-credits) for full citations and concept attributions.
335
+
336
+ ---
337
+
325
338
  ## License
326
339
 
327
340
  [MIT](LICENSE) — Free to use, modify, and distribute.
@@ -16,8 +16,9 @@
16
16
  import { c } from '../docguard.mjs';
17
17
  import { runGuardInternal } from './guard.mjs';
18
18
  import { runScoreInternal } from './score.mjs';
19
- import { existsSync, readFileSync } from 'node:fs';
19
+ import { existsSync, readFileSync, mkdirSync } from 'node:fs';
20
20
  import { resolve } from 'node:path';
21
+ import { execSync } from 'node:child_process';
21
22
 
22
23
  // Map validator failures to the right fix --doc target
23
24
  const VALIDATOR_TO_DOC = {
@@ -39,62 +40,141 @@ const FIX_INSTRUCTIONS = {
39
40
  action: 'Create missing files',
40
41
  command: 'docguard init',
41
42
  description: 'Run init to create missing documentation templates.',
43
+ autoFixable: true,
42
44
  },
43
45
  'Doc Sections': {
44
46
  action: 'Fill document sections',
45
47
  command: 'docguard fix --doc',
46
48
  description: 'Documents exist but have missing or placeholder sections. Use fix --doc to generate AI content prompts.',
49
+ autoFixable: false,
47
50
  },
48
51
  'Docs-Sync': {
49
52
  action: 'Sync documentation references',
50
53
  command: 'docguard fix --doc architecture',
51
54
  description: 'Documentation references are out of sync with code. Review and update component maps.',
55
+ autoFixable: false,
52
56
  },
53
57
  'Drift': {
54
58
  action: 'Update DRIFT-LOG.md',
55
59
  description: 'Code deviates from canonical docs without logged reasons. Add DRIFT entries or update the canonical docs.',
60
+ autoFixable: false,
56
61
  },
57
62
  'Changelog': {
58
63
  action: 'Update CHANGELOG.md',
59
64
  description: 'CHANGELOG.md is missing or has no [Unreleased] section. Add recent changes.',
65
+ autoFixable: false,
60
66
  },
61
67
  'Test-Spec': {
62
68
  action: 'Update TEST-SPEC.md',
63
69
  command: 'docguard fix --doc test-spec',
64
70
  description: 'Test documentation needs updating to match actual test structure.',
71
+ autoFixable: false,
65
72
  },
66
73
  'Environment': {
67
74
  action: 'Update ENVIRONMENT.md',
68
75
  command: 'docguard fix --doc environment',
69
76
  description: 'Environment documentation is missing or incomplete.',
77
+ autoFixable: false,
70
78
  },
71
79
  'Security': {
72
80
  action: 'Update SECURITY.md',
73
81
  command: 'docguard fix --doc security',
74
82
  description: 'Security documentation needs updating.',
83
+ autoFixable: false,
75
84
  },
76
85
  'Architecture': {
77
86
  action: 'Update ARCHITECTURE.md',
78
87
  command: 'docguard fix --doc architecture',
79
88
  description: 'Architecture documentation doesn\'t match the codebase.',
89
+ autoFixable: false,
80
90
  },
81
91
  'Freshness': {
82
92
  action: 'Review stale documents',
83
93
  description: 'Documents haven\'t been reviewed since recent code changes. Re-run fix --doc for each stale doc.',
94
+ autoFixable: false,
84
95
  },
85
96
  };
86
97
 
87
98
  export function runDiagnose(projectDir, config, flags) {
88
99
  // ── Step 1: Run guard internally ──
89
- const guardData = runGuardInternal(projectDir, config);
100
+ let guardData = runGuardInternal(projectDir, config);
90
101
  const scoreData = runScoreInternal(projectDir, config);
91
102
 
92
103
  // ── Step 2: Collect issues ──
104
+ let issues = collectIssues(guardData);
105
+
106
+ // ── Step 3: Auto-fix what we can (unless --no-fix) ──
107
+ const shouldAutoFix = !flags.noFix && flags.format !== 'json';
108
+ if (shouldAutoFix && issues.length > 0) {
109
+ const autoFixable = issues.filter(i => i.autoFixable);
110
+ const hasStructural = issues.some(i => i.validator === 'Structure');
111
+
112
+ if (hasStructural || autoFixable.length > 0) {
113
+ // Run init to create missing files
114
+ try {
115
+ const cliPath = resolve(import.meta.dirname, '..', 'docguard.mjs');
116
+ execSync(`node "${cliPath}" init --dir "${projectDir}"`, {
117
+ encoding: 'utf-8',
118
+ stdio: 'pipe',
119
+ });
120
+ } catch { /* init may partially succeed */ }
121
+
122
+ // Run generate to fill in content
123
+ try {
124
+ const cliPath = resolve(import.meta.dirname, '..', 'docguard.mjs');
125
+ execSync(`node "${cliPath}" generate --dir "${projectDir}" --force`, {
126
+ encoding: 'utf-8',
127
+ stdio: 'pipe',
128
+ });
129
+ } catch { /* generate may partially succeed */ }
130
+
131
+ // Re-run guard to see what's still broken
132
+ guardData = runGuardInternal(projectDir, config);
133
+ issues = collectIssues(guardData);
134
+
135
+ if (!flags.format || flags.format === 'text') {
136
+ const fixedCount = autoFixable.length - issues.filter(i => i.autoFixable).length;
137
+ if (fixedCount > 0) {
138
+ console.log(` ${c.green}⚡ Auto-fixed ${fixedCount} issue(s)${c.reset} (created/regenerated docs)\n`);
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ // Detect stale docs from freshness and map to specific fix --doc targets
145
+ for (const issue of issues) {
146
+ if (issue.validator === 'Freshness' && !issue.docTarget) {
147
+ const match = issue.message.match(/([\w-]+\.md)/i);
148
+ if (match) {
149
+ const docName = match[1].toLowerCase().replace('.md', '');
150
+ const docMap = { 'architecture': 'architecture', 'data-model': 'data-model', 'security': 'security', 'test-spec': 'test-spec', 'environment': 'environment' };
151
+ issue.docTarget = docMap[docName] || null;
152
+ if (issue.docTarget) {
153
+ issue.command = `docguard fix --doc ${issue.docTarget}`;
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ // ── Step 4: Output ──
160
+ if (flags.format === 'json') {
161
+ outputJSON(guardData, scoreData, issues);
162
+ } else if (flags.format === 'prompt') {
163
+ outputPrompt(projectDir, guardData, scoreData, issues, flags);
164
+ } else {
165
+ outputText(projectDir, guardData, scoreData, issues, flags);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Collect issues from guard results with fix metadata.
171
+ */
172
+ function collectIssues(guardData) {
93
173
  const issues = [];
94
174
  for (const v of guardData.validators) {
95
175
  if (v.status === 'skipped' || v.status === 'pass') continue;
96
176
 
97
- const fixInfo = FIX_INSTRUCTIONS[v.name] || { action: `Review ${v.name}`, description: 'Manual review needed.' };
177
+ const fixInfo = FIX_INSTRUCTIONS[v.name] || { action: `Review ${v.name}`, description: 'Manual review needed.', autoFixable: false };
98
178
  const docTarget = VALIDATOR_TO_DOC[v.name];
99
179
 
100
180
  for (const err of v.errors) {
@@ -105,6 +185,7 @@ export function runDiagnose(projectDir, config, flags) {
105
185
  action: fixInfo.action,
106
186
  command: fixInfo.command || null,
107
187
  docTarget,
188
+ autoFixable: fixInfo.autoFixable || false,
108
189
  });
109
190
  }
110
191
  for (const warn of v.warnings) {
@@ -115,34 +196,11 @@ export function runDiagnose(projectDir, config, flags) {
115
196
  action: fixInfo.action,
116
197
  command: fixInfo.command || null,
117
198
  docTarget,
199
+ autoFixable: fixInfo.autoFixable || false,
118
200
  });
119
201
  }
120
202
  }
121
-
122
- // Detect stale docs from freshness and map to specific fix --doc targets
123
- for (const issue of issues) {
124
- if (issue.validator === 'Freshness' && !issue.docTarget) {
125
- // Try to extract doc name from warning message
126
- const match = issue.message.match(/([\w-]+\.md)/i);
127
- if (match) {
128
- const docName = match[1].toLowerCase().replace('.md', '');
129
- const docMap = { 'architecture': 'architecture', 'data-model': 'data-model', 'security': 'security', 'test-spec': 'test-spec', 'environment': 'environment' };
130
- issue.docTarget = docMap[docName] || null;
131
- if (issue.docTarget) {
132
- issue.command = `docguard fix --doc ${issue.docTarget}`;
133
- }
134
- }
135
- }
136
- }
137
-
138
- // ── Step 3: Output ──
139
- if (flags.format === 'json') {
140
- outputJSON(guardData, scoreData, issues);
141
- } else if (flags.format === 'prompt') {
142
- outputPrompt(projectDir, guardData, scoreData, issues);
143
- } else {
144
- outputText(projectDir, guardData, scoreData, issues);
145
- }
203
+ return issues;
146
204
  }
147
205
 
148
206
  function outputJSON(guardData, scoreData, issues) {
@@ -168,7 +226,7 @@ function outputJSON(guardData, scoreData, issues) {
168
226
  console.log(JSON.stringify(result, null, 2));
169
227
  }
170
228
 
171
- function outputText(projectDir, guardData, scoreData, issues) {
229
+ function outputText(projectDir, guardData, scoreData, issues, flags) {
172
230
  console.log(`${c.bold}🔍 DocGuard Diagnose — ${guardData.project}${c.reset}`);
173
231
  console.log(`${c.dim} Profile: ${guardData.profile} | Score: ${scoreData.score}/100 (${scoreData.grade})${c.reset}`);
174
232
  console.log(`${c.dim} Guard: ${guardData.passed}/${guardData.total} passed | Status: ${guardData.status}${c.reset}\n`);
@@ -213,17 +271,27 @@ function outputText(projectDir, guardData, scoreData, issues) {
213
271
  }
214
272
 
215
273
  // ── AI Prompt (always shown in text mode for easy copy) ──
216
- console.log(` ${c.bold}🤖 AI-Ready Prompt:${c.reset}`);
217
- console.log(` ${c.dim}Copy everything below and paste to your AI agent:${c.reset}\n`);
218
- outputPrompt(undefined, guardData, scoreData, issues);
274
+ if (flags && flags.debate) {
275
+ // Multi-perspective debate prompts (AITPG/TRACE-inspired)
276
+ console.log(` ${c.bold}🤖 Multi-Perspective AI Debate Prompt:${c.reset}`);
277
+ console.log(` ${c.dim}Copy everything below and paste to your AI agent:${c.reset}\n`);
278
+ outputDebatePrompt(projectDir, guardData, scoreData, issues);
279
+ } else {
280
+ console.log(` ${c.bold}🤖 AI-Ready Prompt:${c.reset}`);
281
+ console.log(` ${c.dim}Copy everything below and paste to your AI agent:${c.reset}\n`);
282
+ outputPrompt(undefined, guardData, scoreData, issues, flags);
283
+ }
219
284
  }
220
285
 
221
- function outputPrompt(projectDir, guardData, scoreData, issues) {
286
+ function outputPrompt(projectDir, guardData, scoreData, issues, flags) {
222
287
  if (issues.length === 0) {
223
288
  console.log('No issues to fix. Documentation is healthy.');
224
289
  return;
225
290
  }
226
291
 
292
+ // Detect agent capability for prompt complexity (inspired by CJE equalizer effect, TRACE 2026)
293
+ const agentTier = detectAgentTier(projectDir || '.');
294
+
227
295
  const lines = [];
228
296
  lines.push(`TASK: Fix ${issues.length} documentation issue(s) in project "${guardData.project}"`);
229
297
  lines.push(`Profile: ${guardData.profile} | Score: ${scoreData.score}/100 | Guard: ${guardData.status}`);
@@ -257,6 +325,11 @@ function outputPrompt(projectDir, guardData, scoreData, issues) {
257
325
  if (group.docTarget) {
258
326
  lines.push(` Then research the codebase and write real content for this document.`);
259
327
  }
328
+ // Agent-aware: add extra detail for smaller models
329
+ if (agentTier === 'basic') {
330
+ lines.push(` NOTE: Review the codebase file by file. Look for patterns matching this issue.`);
331
+ lines.push(` Check docs-canonical/ for the expected format. Compare against existing docs.`);
332
+ }
260
333
  for (const msg of group.issues) {
261
334
  lines.push(` - ${msg}`);
262
335
  }
@@ -269,5 +342,123 @@ function outputPrompt(projectDir, guardData, scoreData, issues) {
269
342
  lines.push('Expected result: All checks pass (0 errors, 0 warnings)');
270
343
  lines.push(`Target score: ≥${scoreData.score + 5}/100`);
271
344
 
345
+ // Agent-aware: add explicit checklist for basic-tier agents
346
+ if (agentTier === 'basic') {
347
+ lines.push('');
348
+ lines.push('VERIFICATION CHECKLIST (complete each step):');
349
+ lines.push('□ Read each file in docs-canonical/ before editing');
350
+ lines.push('□ Run `docguard guard` after each file change');
351
+ lines.push('□ Confirm 0 errors before moving to next issue');
352
+ lines.push('□ Run `docguard score` to confirm improvement');
353
+ }
354
+
355
+ console.log(lines.join('\n'));
356
+ }
357
+
358
+ /**
359
+ * Generate multi-perspective debate prompts.
360
+ * Inspired by AITPG multi-agent role specialization (Positive/Negative/Edge + Critic)
361
+ * and TRACE adversarial debate (Advocate/Challenger/Mediator/Explainer).
362
+ * Lopez et al., IEEE TSE/TMLCN 2026.
363
+ */
364
+ function outputDebatePrompt(projectDir, guardData, scoreData, issues) {
365
+ const lines = [];
366
+
367
+ lines.push('═══════════════════════════════════════════════════════');
368
+ lines.push('MULTI-PERSPECTIVE DOCUMENTATION ANALYSIS');
369
+ lines.push(`Project: "${guardData.project}" | Score: ${scoreData.score}/100 | Issues: ${issues.length}`);
370
+ lines.push('Methodology: Multi-agent debate (Lopez et al., AITPG/TRACE, IEEE 2026)');
371
+ lines.push('═══════════════════════════════════════════════════════');
372
+ lines.push('');
373
+
374
+ // Issue context
375
+ lines.push('CONTEXT — Current Issues:');
376
+ for (let i = 0; i < issues.length; i++) {
377
+ lines.push(` ${i + 1}. [${issues[i].severity.toUpperCase()}] [${issues[i].validator}] ${issues[i].message}`);
378
+ }
379
+ lines.push('');
380
+
381
+ // ── Agent 1: Advocate ──
382
+ lines.push('───────────────────────────────────────────────────────');
383
+ lines.push('PERSPECTIVE 1: ADVOCATE (What is working well)');
384
+ lines.push('───────────────────────────────────────────────────────');
385
+ lines.push('Role: You are the Advocate agent. Your job is to identify what the project');
386
+ lines.push('documentation is doing RIGHT and which patterns should be PRESERVED.');
387
+ lines.push('');
388
+ lines.push('Instructions:');
389
+ lines.push('1. Read all files in docs-canonical/');
390
+ lines.push('2. Identify which documents are well-structured and complete');
391
+ lines.push('3. Note which naming conventions, section formats, and patterns are consistent');
392
+ lines.push('4. List 3-5 strengths that must be preserved during remediation');
393
+ lines.push('');
394
+
395
+ // ── Agent 2: Challenger ──
396
+ lines.push('───────────────────────────────────────────────────────');
397
+ lines.push('PERSPECTIVE 2: CHALLENGER (What is broken or risky)');
398
+ lines.push('───────────────────────────────────────────────────────');
399
+ lines.push('Role: You are the Challenger agent. Your job is to STRESS-TEST the documentation');
400
+ lines.push('and identify gaps, inconsistencies, and risks that the issues list might miss.');
401
+ lines.push('');
402
+ lines.push('Instructions:');
403
+ lines.push('1. For each issue listed above, explain WHY it matters and what the root cause is');
404
+ lines.push('2. Identify any ADDITIONAL issues not caught by docguard guard');
405
+ lines.push('3. Check: Are there undocumented API routes? Missing env vars? Stale references?');
406
+ lines.push('4. Rank all issues by business impact (P0 = blocks deployment, P1 = degrades quality, P2 = cosmetic)');
407
+ lines.push('');
408
+
409
+ // ── Agent 3: Synthesizer ──
410
+ lines.push('───────────────────────────────────────────────────────');
411
+ lines.push('PERSPECTIVE 3: SYNTHESIZER (Prioritized remediation plan)');
412
+ lines.push('───────────────────────────────────────────────────────');
413
+ lines.push('Role: You are the Synthesizer agent. Given the Advocate\'s strengths and the');
414
+ lines.push('Challenger\'s gaps, produce a PRIORITIZED and ACTIONABLE remediation plan.');
415
+ lines.push('');
416
+ lines.push('Instructions:');
417
+ lines.push('1. Preserve the patterns the Advocate identified as strengths');
418
+ lines.push('2. Address the Challenger\'s issues in priority order (P0 → P1 → P2)');
419
+ lines.push('3. For each fix, specify:');
420
+ lines.push(' a. Which file to edit');
421
+ lines.push(' b. What section to add or modify');
422
+ lines.push(' c. What content to write (be specific, not vague)');
423
+ lines.push('4. After all fixes, verify with: docguard guard');
424
+ lines.push(`5. Target score: ≥${Math.min(scoreData.score + 10, 100)}/100`);
425
+ lines.push('');
426
+ lines.push('═══════════════════════════════════════════════════════');
427
+ lines.push('Execute all three perspectives in sequence, then implement the Synthesizer\'s plan.');
428
+ lines.push('═══════════════════════════════════════════════════════');
429
+
272
430
  console.log(lines.join('\n'));
273
431
  }
432
+
433
+ /**
434
+ * Detect the AI agent tier from AGENTS.md or .docguard.json.
435
+ * Returns 'advanced' (concise prompts) or 'basic' (verbose step-by-step).
436
+ * Inspired by CJE equalizer effect (Lopez et al., TRACE, IEEE TMLCN 2026).
437
+ */
438
+ function detectAgentTier(projectDir) {
439
+ // Check .docguard.json for explicit agent config
440
+ const configPath = resolve(projectDir, '.docguard.json');
441
+ if (existsSync(configPath)) {
442
+ try {
443
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
444
+ if (config.agentTier) return config.agentTier;
445
+ } catch { /* ignore */ }
446
+ }
447
+
448
+ // Check AGENTS.md for known agent names
449
+ const agentFiles = ['AGENTS.md', 'CLAUDE.md', '.github/copilot-instructions.md'];
450
+ for (const file of agentFiles) {
451
+ const agentPath = resolve(projectDir, file);
452
+ if (existsSync(agentPath)) {
453
+ const content = readFileSync(agentPath, 'utf-8').toLowerCase();
454
+ // Advanced agents: Claude, GPT-4, Gemini Pro
455
+ const advancedMarkers = ['claude', 'gpt-4', 'gemini pro', 'gemini 2', 'opus', 'sonnet'];
456
+ if (advancedMarkers.some(m => content.includes(m))) {
457
+ return 'advanced';
458
+ }
459
+ }
460
+ }
461
+
462
+ // Default to advanced (most users run modern models)
463
+ return 'advanced';
464
+ }