docguard-cli 0.28.0 → 0.29.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/README.es.md +102 -0
- package/README.md +64 -31
- package/README.pt-BR.md +101 -0
- package/STANDARD.md +20 -10
- package/cli/commands/agents.mjs +149 -0
- package/cli/commands/diff.mjs +6 -15
- package/cli/commands/generate.mjs +14 -1001
- package/cli/commands/guard.mjs +136 -8
- package/cli/commands/llms.mjs +67 -5
- package/cli/commands/mcp.mjs +263 -0
- package/cli/commands/memory.mjs +115 -0
- package/cli/commands/score.mjs +76 -12
- package/cli/docguard.mjs +31 -2
- package/cli/findings.mjs +499 -0
- package/cli/scanners/agent-readability.mjs +202 -0
- package/cli/scanners/semantic-claims.mjs +7 -1
- package/cli/scanners/speckit.mjs +98 -28
- package/cli/shared-ignore.mjs +148 -16
- package/cli/shared.mjs +45 -1
- package/cli/validators/api-surface.mjs +113 -26
- package/cli/validators/architecture.mjs +66 -43
- package/cli/validators/canonical-sync.mjs +59 -28
- package/cli/validators/changelog.mjs +41 -17
- package/cli/validators/cross-reference.mjs +28 -11
- package/cli/validators/doc-quality.mjs +78 -44
- package/cli/validators/docs-coverage.mjs +90 -63
- package/cli/validators/docs-diff.mjs +63 -64
- package/cli/validators/docs-sync.mjs +48 -33
- package/cli/validators/drift.mjs +40 -34
- package/cli/validators/environment.mjs +67 -27
- package/cli/validators/freshness.mjs +12 -5
- package/cli/validators/generated-staleness.mjs +26 -10
- package/cli/validators/metadata-sync.mjs +28 -25
- package/cli/validators/metrics-consistency.mjs +89 -47
- package/cli/validators/schema-sync.mjs +37 -32
- package/cli/validators/security.mjs +7 -20
- package/cli/validators/spec-kit.mjs +3 -0
- package/cli/validators/structure.mjs +58 -23
- package/cli/validators/surface-sync.mjs +34 -15
- package/cli/validators/test-spec.mjs +87 -29
- package/cli/validators/todo-tracking.mjs +83 -74
- package/cli/validators/traceability.mjs +67 -39
- package/cli/writers/doc-generators.mjs +853 -0
- package/cli/writers/generate-io.mjs +142 -0
- package/cli/writers/sarif.mjs +129 -0
- package/commands/docguard.fix.md +56 -53
- package/commands/docguard.guard.md +53 -47
- package/commands/docguard.review.md +49 -31
- package/docs/ai-integration.md +133 -134
- package/docs/commands.md +49 -3
- package/docs/configuration.md +38 -0
- package/docs/faq.md +15 -0
- package/extensions/spec-kit-docguard/extension.yml +1 -1
- package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-sync/SKILL.md +2 -2
- package/package.json +1 -1
- package/schemas/docguard-config.schema.json +17 -0
- package/templates/commands/docguard.fix.md +33 -10
- package/templates/commands/docguard.guard.md +40 -26
- package/templates/commands/docguard.init.md +23 -11
- package/templates/commands/docguard.review.md +25 -8
- package/templates/commands/docguard.update.md +14 -4
|
@@ -5,14 +5,20 @@
|
|
|
5
5
|
* This is the "killer feature" — take any project and auto-generate CDD docs.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync, readFileSync,
|
|
9
|
-
import { resolve,
|
|
8
|
+
import { existsSync, readFileSync, mkdirSync } from 'node:fs';
|
|
9
|
+
import { resolve, extname, basename, relative } from 'node:path';
|
|
10
10
|
import { c } from '../shared.mjs';
|
|
11
|
+
import { walkFiles as sharedWalkFiles } from '../shared-ignore.mjs';
|
|
11
12
|
import { detectDocTools } from '../scanners/doc-tools.mjs';
|
|
12
13
|
import { scanRoutesDeep } from '../scanners/routes.mjs';
|
|
13
|
-
import { scanSchemasDeep
|
|
14
|
+
import { scanSchemasDeep } from '../scanners/schemas.mjs';
|
|
14
15
|
import { buildMemoryPlan } from '../scanners/memory-plan.mjs';
|
|
15
16
|
import { upsertSection } from '../writers/sections.mjs';
|
|
17
|
+
import { safeWrite, registerGeneratedCanonicalDocs, surfaceConfidence } from '../writers/generate-io.mjs';
|
|
18
|
+
import {
|
|
19
|
+
generateArchitecture, generateApiReference, generateDataModel,
|
|
20
|
+
generateEnvironment, generateTestSpec, generateSecurity, generateRootFiles,
|
|
21
|
+
} from '../writers/doc-generators.mjs';
|
|
16
22
|
|
|
17
23
|
const IGNORE_DIRS = new Set([
|
|
18
24
|
'node_modules', '.git', '.next', 'dist', 'build', 'coverage',
|
|
@@ -20,142 +26,11 @@ const IGNORE_DIRS = new Set([
|
|
|
20
26
|
'.amplify-hosting', '.serverless',
|
|
21
27
|
]);
|
|
22
28
|
|
|
23
|
-
/**
|
|
24
|
-
* Create a .bak backup of an existing file before --force overwrites it.
|
|
25
|
-
* Only backs up if the file exists and has content.
|
|
26
|
-
*/
|
|
27
|
-
function backupFile(filePath) {
|
|
28
|
-
if (existsSync(filePath)) {
|
|
29
|
-
try {
|
|
30
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
31
|
-
if (content.trim().length > 0) {
|
|
32
|
-
copyFileSync(filePath, filePath + '.bak');
|
|
33
|
-
}
|
|
34
|
-
} catch { /* backup failure is non-fatal */ }
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Safe write — creates a .bak backup before overwriting existing files.
|
|
40
|
-
* Call this instead of raw writeFileSync when generating docs.
|
|
41
|
-
*/
|
|
42
|
-
function safeWrite(filePath, content) {
|
|
43
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
44
|
-
backupFile(filePath);
|
|
45
|
-
writeFileSync(filePath, content, 'utf-8');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* B7 (field report): after generate emits canonical docs, register them in
|
|
50
|
-
* `.docguard.json` requiredFiles.canonical so `guard` doesn't immediately flag
|
|
51
|
-
* the generator's OWN output as an "orphaned" doc ("exists but not in your
|
|
52
|
-
* requiredFiles"). Only ADDS (never removes/deletes), only docs-canonical/*.md
|
|
53
|
-
* that actually exist on disk, and only when a config file already exists (init
|
|
54
|
-
* owns config creation). Idempotent — a second run with nothing new is a no-op.
|
|
55
|
-
* @returns {number} count of paths newly registered.
|
|
56
|
-
*/
|
|
57
|
-
function registerGeneratedCanonicalDocs(projectDir, candidatePaths) {
|
|
58
|
-
const cfgPath = resolve(projectDir, '.docguard.json');
|
|
59
|
-
if (!existsSync(cfgPath)) return 0;
|
|
60
|
-
let cfg;
|
|
61
|
-
try { cfg = JSON.parse(readFileSync(cfgPath, 'utf-8')); } catch { return 0; }
|
|
62
|
-
const canon = [...new Set(candidatePaths)].filter(p =>
|
|
63
|
-
p.startsWith('docs-canonical/') && p.endsWith('.md') && existsSync(resolve(projectDir, p))
|
|
64
|
-
);
|
|
65
|
-
if (canon.length === 0) return 0;
|
|
66
|
-
if (!cfg.requiredFiles || typeof cfg.requiredFiles !== 'object') cfg.requiredFiles = {};
|
|
67
|
-
const existing = Array.isArray(cfg.requiredFiles.canonical) ? cfg.requiredFiles.canonical : [];
|
|
68
|
-
const seen = new Set(existing);
|
|
69
|
-
let added = 0;
|
|
70
|
-
for (const p of canon) if (!seen.has(p)) { existing.push(p); seen.add(p); added++; }
|
|
71
|
-
if (added === 0) return 0;
|
|
72
|
-
cfg.requiredFiles.canonical = existing;
|
|
73
|
-
try { writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n', 'utf-8'); } catch { return 0; }
|
|
74
|
-
return added;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
29
|
const CODE_EXTENSIONS = new Set([
|
|
78
30
|
'.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx',
|
|
79
31
|
'.py', '.java', '.go', '.rs', '.rb', '.php', '.cs',
|
|
80
32
|
]);
|
|
81
33
|
|
|
82
|
-
/**
|
|
83
|
-
* Standards citation map — each doc type maps to its governing industry standard.
|
|
84
|
-
* Inspired by RAG-grounded standards alignment (Lopez et al., AITPG, IEEE TSE 2026).
|
|
85
|
-
*/
|
|
86
|
-
const STANDARDS_CITATIONS = {
|
|
87
|
-
'ARCHITECTURE.md': {
|
|
88
|
-
standard: 'arc42 Template + C4 Model',
|
|
89
|
-
reference: 'Starke, G. & Brown, S. "arc42 — Architecture communication template." https://arc42.org | Brown, S. "The C4 Model for visualising software architecture." https://c4model.com',
|
|
90
|
-
sections: '§1 Introduction, §2 Constraints, §3 Context, §4 Solution Strategy, §5 Building Blocks, §6 Runtime, §7 Deployment, §8 Crosscutting, §9 ADRs, §10 Quality, §11 Risks, §12 Glossary',
|
|
91
|
-
},
|
|
92
|
-
'DATA-MODEL.md': {
|
|
93
|
-
standard: 'C4 Component Diagram + Entity-Relationship (Chen notation)',
|
|
94
|
-
reference: 'Brown, S. "C4 Model — Component diagrams." https://c4model.com | Chen, P. "The Entity-Relationship Model." ACM TODS 1(1), 1976',
|
|
95
|
-
sections: 'Entities, Relationships, ER Diagrams (Mermaid), Field-level definitions',
|
|
96
|
-
},
|
|
97
|
-
'TEST-SPEC.md': {
|
|
98
|
-
standard: 'ISO/IEC/IEEE 29119-3:2022 — Test Documentation',
|
|
99
|
-
reference: 'ISO/IEC/IEEE, "Software and systems engineering — Software testing — Part 3: Test documentation." International Standard, 2022',
|
|
100
|
-
sections: 'Test Categories, Coverage Rules, Test Matrix, Tool Configuration',
|
|
101
|
-
},
|
|
102
|
-
'SECURITY.md': {
|
|
103
|
-
standard: 'OWASP ASVS v4.0 + CWE Top 25',
|
|
104
|
-
reference: 'OWASP Foundation, "Application Security Verification Standard v4.0." https://owasp.org/asvs | MITRE, "CWE Top 25." https://cwe.mitre.org/top25',
|
|
105
|
-
sections: 'Authentication, Secrets Management, Access Control, Input Validation',
|
|
106
|
-
},
|
|
107
|
-
'ENVIRONMENT.md': {
|
|
108
|
-
standard: '12-Factor App Methodology',
|
|
109
|
-
reference: 'Wiggins, A. "The Twelve-Factor App." https://12factor.net',
|
|
110
|
-
sections: 'Environment Variables, Config Separation, Setup Steps, Provider Configuration',
|
|
111
|
-
},
|
|
112
|
-
'API-REFERENCE.md': {
|
|
113
|
-
standard: 'OpenAPI Specification 3.1',
|
|
114
|
-
reference: 'OpenAPI Initiative, "OpenAPI Specification v3.1.0." https://spec.openapis.org/oas/v3.1.0',
|
|
115
|
-
sections: 'Endpoints, Request/Response schemas, Authentication, Error codes',
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Append a standards citation footer to generated doc content.
|
|
121
|
-
* @param {string} content - The generated markdown content
|
|
122
|
-
* @param {string} docName - The filename (e.g., 'ARCHITECTURE.md')
|
|
123
|
-
* @returns {string} Content with citation footer appended
|
|
124
|
-
*/
|
|
125
|
-
function appendStandardsCitation(content, docName) {
|
|
126
|
-
const citation = STANDARDS_CITATIONS[docName];
|
|
127
|
-
if (!citation) return content;
|
|
128
|
-
|
|
129
|
-
const footer = `
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## Standards Reference
|
|
133
|
-
|
|
134
|
-
> **Aligned with**: ${citation.standard}
|
|
135
|
-
>
|
|
136
|
-
> **Sections covered**: ${citation.sections}
|
|
137
|
-
>
|
|
138
|
-
> **Reference**: ${citation.reference}
|
|
139
|
-
>
|
|
140
|
-
> *Standards alignment inspired by RAG-grounded generation (Lopez et al., AITPG, IEEE TSE 2026).*
|
|
141
|
-
`;
|
|
142
|
-
|
|
143
|
-
return content.trimEnd() + '\n' + footer;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* F1 (field report): web-shaped surface (HTTP endpoints, SDK deps, routes)
|
|
148
|
-
* auto-extracted from a cli/library/unknown-kind project is often pattern-
|
|
149
|
-
* matches in the project's OWN source (e.g. a scanner/linter whose code mentions
|
|
150
|
-
* express, boto3, jwt as detection strings), not real usage. We do NOT suppress
|
|
151
|
-
* it — that could hide a real surface (a false-green) — we flag it 'low'
|
|
152
|
-
* confidence so the surface is verified before being documented. Web kinds
|
|
153
|
-
* (webapp/api/service) stay 'normal'.
|
|
154
|
-
*/
|
|
155
|
-
function surfaceConfidence(kind) {
|
|
156
|
-
return ['webapp', 'api', 'service'].includes(kind) ? 'normal' : 'low';
|
|
157
|
-
}
|
|
158
|
-
|
|
159
34
|
/**
|
|
160
35
|
* `docguard generate --plan` — AI-powered Generate.
|
|
161
36
|
* Builds the code-truth skeleton (marked sections) and emits the agent task
|
|
@@ -675,877 +550,15 @@ function countFilesAndLines(dir, scan) {
|
|
|
675
550
|
});
|
|
676
551
|
}
|
|
677
552
|
|
|
678
|
-
//
|
|
679
|
-
|
|
680
|
-
function generateArchitecture(dir, config, stack, scan, flags, docTools) {
|
|
681
|
-
const path = resolve(dir, 'docs-canonical/ARCHITECTURE.md');
|
|
682
|
-
if (existsSync(path) && !flags.force) {
|
|
683
|
-
console.log(` ${c.dim}⏭️ ARCHITECTURE.md (exists)${c.reset}`);
|
|
684
|
-
return false;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
const techRows = Object.entries(stack)
|
|
688
|
-
.filter(([, v]) => v)
|
|
689
|
-
.map(([k, v]) => `| ${k.charAt(0).toUpperCase() + k.slice(1)} | ${v} | | |`)
|
|
690
|
-
.join('\n');
|
|
691
|
-
|
|
692
|
-
const componentRows = [];
|
|
693
|
-
if (scan.routes.length > 0) componentRows.push(`| API Routes | HTTP request handling | ${scan.routes.length > 3 ? scan.routes.slice(0, 3).join(', ') + '...' : scan.routes.join(', ')} | |`);
|
|
694
|
-
if (scan.services.length > 0) componentRows.push(`| Services | Business logic | ${scan.services.length > 3 ? scan.services.slice(0, 3).join(', ') + '...' : scan.services.join(', ')} | |`);
|
|
695
|
-
if (scan.models.length > 0) componentRows.push(`| Models | Data entities | ${scan.models.length > 3 ? scan.models.slice(0, 3).join(', ') + '...' : scan.models.join(', ')} | |`);
|
|
696
|
-
if (scan.components.length > 0) componentRows.push(`| UI Components | Frontend components | ${scan.components.length} files | |`);
|
|
697
|
-
if (scan.middlewares.length > 0) componentRows.push(`| Middleware | Request processing | ${scan.middlewares.join(', ')} | |`);
|
|
698
|
-
|
|
699
|
-
// Storybook integration
|
|
700
|
-
if (docTools?.storybook?.found) {
|
|
701
|
-
componentRows.push(`| Storybook | UI component docs | .storybook/ (${docTools.storybook.storyCount || '?'} stories) | |`);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// Doc tools section — always include DocGuard since it generated these docs
|
|
705
|
-
const docToolRows = ['| DocGuard | `.docguard.json` | Active |'];
|
|
706
|
-
if (docTools?._detected?.length > 0) {
|
|
707
|
-
for (const tool of docTools._detected) {
|
|
708
|
-
const info = docTools[tool];
|
|
709
|
-
docToolRows.push(`| ${tool} | ${info.config || info.path || info.middleware || 'detected'} | Active |`);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
const content = `# Architecture
|
|
714
|
-
|
|
715
|
-
<!-- docguard:version 0.1.0 -->
|
|
716
|
-
<!-- docguard:status draft -->
|
|
717
|
-
<!-- docguard:last-reviewed ${new Date().toISOString().split('T')[0]} -->
|
|
718
|
-
<!-- docguard:generated true -->
|
|
719
|
-
<!-- docguard:standards arc42, C4 -->
|
|
720
|
-
|
|
721
|
-
> **Auto-generated by DocGuard.** Review and refine this document.
|
|
722
|
-
> Follows [arc42](https://arc42.org) structure and [C4 Model](https://c4model.com) diagrams.
|
|
723
|
-
|
|
724
|
-
| Metadata | Value |
|
|
725
|
-
|----------|-------|
|
|
726
|
-
| **Status** |  |
|
|
727
|
-
| **Version** | \`0.1.0\` |
|
|
728
|
-
| **Last Updated** | ${new Date().toISOString().split('T')[0]} |
|
|
729
|
-
| **Project Size** | ${scan.totalFiles} files, ~${Math.round(scan.totalLines / 1000)}K lines |
|
|
730
|
-
|
|
731
|
-
---
|
|
732
|
-
|
|
733
|
-
## 1. Introduction & Goals
|
|
734
|
-
<!-- arc42: §1 — Introduction and Goals -->
|
|
735
|
-
|
|
736
|
-
<!-- TBD: Describe what this system does, who it's for, and key quality goals -->
|
|
737
|
-
${config.projectName} is a ${stack.framework || stack.language || 'software'} application.
|
|
738
|
-
|
|
739
|
-
### Quality Goals
|
|
740
|
-
|
|
741
|
-
| Priority | Quality Goal | Scenario |
|
|
742
|
-
|----------|-------------|----------|
|
|
743
|
-
| 1 | <!-- e.g. Performance --> | <!-- e.g. Response time < 200ms --> |
|
|
744
|
-
| 2 | <!-- e.g. Security --> | <!-- e.g. All endpoints authenticated --> |
|
|
745
|
-
| 3 | <!-- e.g. Maintainability --> | <!-- e.g. New feature in < 1 day --> |
|
|
746
|
-
|
|
747
|
-
## 2. Constraints
|
|
748
|
-
<!-- arc42: §2 — Constraints -->
|
|
749
|
-
|
|
750
|
-
| Type | Constraint | Background |
|
|
751
|
-
|------|-----------|------------|
|
|
752
|
-
| Technical | ${stack.language || 'TBD'} | Primary language |
|
|
753
|
-
| Technical | ${stack.framework || 'TBD'} | Framework |
|
|
754
|
-
| Infrastructure | ${stack.hosting || 'TBD'} | Hosting provider |
|
|
755
|
-
|
|
756
|
-
## 3. Context & Scope
|
|
757
|
-
<!-- arc42: §3 — Context and Scope (C4 Level 1: System Context) -->
|
|
758
|
-
|
|
759
|
-
\\\`\\\`\\\`mermaid
|
|
760
|
-
graph TD
|
|
761
|
-
U[Users/Clients] --> S[${config.projectName}]
|
|
762
|
-
S --> DB[(${stack.database || 'Database'})]
|
|
763
|
-
S --> EXT[External Services]
|
|
764
|
-
\\\`\\\`\\\`
|
|
765
|
-
|
|
766
|
-
## 4. Solution Strategy
|
|
767
|
-
<!-- arc42: §4 — Solution Strategy -->
|
|
768
|
-
|
|
769
|
-
See \\\`docs-canonical/ADR.md\\\` for architecture decision records.
|
|
770
|
-
|
|
771
|
-
## 5. Building Block View
|
|
772
|
-
<!-- arc42: §5 — Building Block View (C4 Level 2: Container) -->
|
|
773
|
-
|
|
774
|
-
| Component | Responsibility | Location | Tests |
|
|
775
|
-
|-----------|---------------|----------|-------|
|
|
776
|
-
${componentRows.join('\\n') || '| <!-- Add components --> | | | |'}
|
|
777
|
-
|
|
778
|
-
\\\`\\\`\\\`mermaid
|
|
779
|
-
graph TD
|
|
780
|
-
A[Client] --> B[${stack.framework || 'API'}]
|
|
781
|
-
B --> C[Services]
|
|
782
|
-
C --> D[${stack.database || 'Database'}]
|
|
783
|
-
${scan.middlewares.length > 0 ? 'A --> M[Middleware] --> B' : ''}
|
|
784
|
-
${scan.components.length > 0 ? 'A --> UI[UI Components]' : ''}
|
|
785
|
-
\\\`\\\`\\\`
|
|
786
|
-
|
|
787
|
-
## 6. Runtime View
|
|
788
|
-
<!-- arc42: §6 — Runtime View -->
|
|
789
|
-
|
|
790
|
-
\\\`\\\`\\\`mermaid
|
|
791
|
-
sequenceDiagram
|
|
792
|
-
participant C as Client
|
|
793
|
-
participant A as ${stack.framework || 'API'}
|
|
794
|
-
participant S as Service
|
|
795
|
-
participant D as ${stack.database || 'DB'}
|
|
796
|
-
C->>A: Request
|
|
797
|
-
A->>S: Process
|
|
798
|
-
S->>D: Query
|
|
799
|
-
D-->>S: Result
|
|
800
|
-
S-->>A: Response
|
|
801
|
-
A-->>C: JSON
|
|
802
|
-
\\\`\\\`\\\`
|
|
803
|
-
|
|
804
|
-
## 7. Deployment View
|
|
805
|
-
<!-- arc42: §7 — Deployment View -->
|
|
806
|
-
|
|
807
|
-
See \\\`docs-canonical/DEPLOYMENT.md\\\` for details.
|
|
808
|
-
|
|
809
|
-
| Environment | Infrastructure | URL |
|
|
810
|
-
|-------------|---------------|-----|
|
|
811
|
-
| Development | localhost | http://localhost:3000 |
|
|
812
|
-
| Staging | ${stack.hosting || 'TBD'} | <!-- TBD --> |
|
|
813
|
-
| Production | ${stack.hosting || 'TBD'} | <!-- TBD --> |
|
|
814
|
-
|
|
815
|
-
## 8. Crosscutting Concepts
|
|
816
|
-
<!-- arc42: §8 — Crosscutting Concepts -->
|
|
817
|
-
|
|
818
|
-
### Tech Stack
|
|
819
|
-
|
|
820
|
-
| Category | Technology | Version | License |
|
|
821
|
-
|----------|-----------|---------|---------|
|
|
822
|
-
${techRows || '| <!-- Add technologies --> | | | |'}
|
|
823
|
-
${docToolRows.length > 0 ? `
|
|
824
|
-
### Documentation Tools
|
|
825
|
-
|
|
826
|
-
| Tool | Config | Status |
|
|
827
|
-
|------|--------|--------|
|
|
828
|
-
${docToolRows.join('\\n')}
|
|
829
|
-
` : ''}
|
|
830
|
-
|
|
831
|
-
### Layer Boundaries
|
|
832
|
-
|
|
833
|
-
| Layer | Can Import From | Cannot Import From |
|
|
834
|
-
|-------|----------------|-------------------|
|
|
835
|
-
${scan.routes.length > 0 ? '| Routes/Handlers | Services, Middleware | Models (direct) |' : ''}
|
|
836
|
-
${scan.services.length > 0 ? '| Services | Repositories, Utils | Routes |' : ''}
|
|
837
|
-
${scan.models.length > 0 ? '| Models/Repositories | Utils | Services, Routes |' : ''}
|
|
838
|
-
|
|
839
|
-
## 9. Architecture Decisions
|
|
840
|
-
<!-- arc42: §9 — Architecture Decisions -->
|
|
841
|
-
|
|
842
|
-
See \\\`docs-canonical/ADR.md\\\` for the full decision log.
|
|
843
|
-
|
|
844
|
-
## 10. Quality Requirements
|
|
845
|
-
<!-- arc42: §10 — Quality Requirements -->
|
|
846
|
-
|
|
847
|
-
See \\\`docs-canonical/TEST-SPEC.md\\\` for test requirements and coverage targets.
|
|
848
|
-
|
|
849
|
-
## 11. Risks & Technical Debt
|
|
850
|
-
<!-- arc42: §11 — Risk Assessment and Technical Debt -->
|
|
851
|
-
|
|
852
|
-
See \\\`DRIFT-LOG.md\\\` for documented deviations from canonical specs.
|
|
853
|
-
See \\\`docs-canonical/KNOWN-GOTCHAS.md\\\` for known issues.
|
|
854
|
-
|
|
855
|
-
## 12. Glossary
|
|
856
|
-
<!-- arc42: §12 — Glossary -->
|
|
857
|
-
|
|
858
|
-
| Term | Definition |
|
|
859
|
-
|------|-----------|
|
|
860
|
-
| CDD | Canonical-Driven Development — documentation as the source of truth |
|
|
861
|
-
| Canonical Doc | A specification document that defines system behavior |
|
|
862
|
-
| Drift | Conscious deviation from canonical documentation |
|
|
863
|
-
|
|
864
|
-
---
|
|
865
|
-
|
|
866
|
-
## Revision History
|
|
867
|
-
|
|
868
|
-
| Version | Date | Author | Changes |
|
|
869
|
-
|---------|------|--------|---------|
|
|
870
|
-
| 0.1.0 | ${new Date().toISOString().split('T')[0]} | DocGuard Generate | Auto-generated (arc42 + C4 aligned) |
|
|
871
|
-
`;
|
|
872
|
-
|
|
873
|
-
safeWrite(path, appendStandardsCitation(content, 'ARCHITECTURE.md'), 'utf-8');
|
|
874
|
-
console.log(` ${c.green}✅ ARCHITECTURE.md${c.reset} (arc42 §1-§12, ${componentRows.length} components, ${Object.values(stack).filter(Boolean).length} tech)`);
|
|
875
|
-
return true;
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
// ── API Reference Generator (NEW — from deep route scanning) ───────────────
|
|
879
|
-
|
|
880
|
-
function generateApiReference(dir, config, stack, deepRoutes, flags) {
|
|
881
|
-
const path = resolve(dir, 'docs-canonical/API-REFERENCE.md');
|
|
882
|
-
if (existsSync(path) && !flags.force) {
|
|
883
|
-
console.log(` ${c.dim}⏭️ API-REFERENCE.md (exists)${c.reset}`);
|
|
884
|
-
return false;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// Group routes by resource (first path segment after /api/)
|
|
888
|
-
const groups = {};
|
|
889
|
-
for (const route of deepRoutes) {
|
|
890
|
-
const parts = route.path.split('/').filter(Boolean);
|
|
891
|
-
const resource = parts[1] || parts[0] || 'root';
|
|
892
|
-
if (!groups[resource]) groups[resource] = [];
|
|
893
|
-
groups[resource].push(route);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
// Build endpoint table
|
|
897
|
-
const endpointRows = deepRoutes
|
|
898
|
-
.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method))
|
|
899
|
-
.map(r => `| \`${r.method}\` | \`${r.path}\` | ${r.handler || '—'} | ${r.auth ? '🔒' : '🔓'} | ${r.description || '—'} |`)
|
|
900
|
-
.join('\n');
|
|
901
|
-
|
|
902
|
-
// Build per-resource sections
|
|
903
|
-
const resourceSections = Object.entries(groups)
|
|
904
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
905
|
-
.map(([resource, routes]) => {
|
|
906
|
-
const routeDetails = routes.map(r => `#### ${r.method} \`${r.path}\`
|
|
907
|
-
|
|
908
|
-
> Source: \`${r.file}\`${r.source ? ` (${r.source})` : ''}
|
|
909
|
-
|
|
910
|
-
- **Auth:** ${r.auth ? 'Required' : 'None'}
|
|
911
|
-
- **Handler:** ${r.handler || '—'}
|
|
912
|
-
${r.description ? `- **Description:** ${r.description}` : ''}
|
|
913
|
-
|
|
914
|
-
| Parameter | In | Type | Required | Description |
|
|
915
|
-
|-----------|-----|------|:--------:|-------------|
|
|
916
|
-
| <!-- TBD --> | | | | |
|
|
917
|
-
|
|
918
|
-
| Status | Response |
|
|
919
|
-
|--------|----------|
|
|
920
|
-
| 200 | Success |
|
|
921
|
-
| 400 | Bad Request |
|
|
922
|
-
| 401 | Unauthorized |
|
|
923
|
-
`).join('\n');
|
|
924
|
-
|
|
925
|
-
return `### ${resource.charAt(0).toUpperCase() + resource.slice(1)}
|
|
926
|
-
|
|
927
|
-
${routeDetails}`;
|
|
928
|
-
}).join('\n---\n\n');
|
|
929
|
-
|
|
930
|
-
const content = `# API Reference
|
|
931
|
-
|
|
932
|
-
<!-- docguard:version 0.1.0 -->
|
|
933
|
-
<!-- docguard:status draft -->
|
|
934
|
-
<!-- docguard:last-reviewed ${new Date().toISOString().split('T')[0]} -->
|
|
935
|
-
<!-- docguard:generated true -->
|
|
936
|
-
|
|
937
|
-
> **Auto-generated by DocGuard.** Review and refine this document.
|
|
938
|
-
|
|
939
|
-
| Metadata | Value |
|
|
940
|
-
|----------|-------|
|
|
941
|
-
| **Status** |  |
|
|
942
|
-
| **Base URL** | \`http://localhost:3000\` |
|
|
943
|
-
| **Auth** | <!-- TBD: Describe auth mechanism --> |
|
|
944
|
-
| **Total Endpoints** | ${deepRoutes.length} |
|
|
945
|
-
| **Source** | ${deepRoutes[0]?.source || 'code scan'} |
|
|
946
|
-
|
|
947
|
-
---
|
|
948
|
-
|
|
949
|
-
## Endpoints Summary
|
|
950
|
-
|
|
951
|
-
| Method | Path | Handler | Auth | Description |
|
|
952
|
-
|--------|------|---------|:----:|-------------|
|
|
953
|
-
${endpointRows}
|
|
954
|
-
|
|
955
|
-
---
|
|
956
|
-
|
|
957
|
-
## Endpoint Details
|
|
958
|
-
|
|
959
|
-
${resourceSections}
|
|
960
|
-
|
|
961
|
-
---
|
|
962
|
-
|
|
963
|
-
## Revision History
|
|
964
|
-
|
|
965
|
-
| Version | Date | Author | Changes |
|
|
966
|
-
|---------|------|--------|---------|
|
|
967
|
-
| 0.1.0 | ${new Date().toISOString().split('T')[0]} | DocGuard Generate | Auto-generated (${deepRoutes.length} endpoints from ${deepRoutes[0]?.source || 'code'}) |
|
|
968
|
-
`;
|
|
969
|
-
|
|
970
|
-
safeWrite(path, appendStandardsCitation(content, 'API-REFERENCE.md'), 'utf-8');
|
|
971
|
-
console.log(` ${c.green}✅ API-REFERENCE.md${c.reset} (${deepRoutes.length} endpoints, ${Object.keys(groups).length} resources)`);
|
|
972
|
-
return true;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
// ── Enhanced Data Model Generator ──────────────────────────────────────────
|
|
976
|
-
|
|
977
|
-
function generateDataModel(dir, config, stack, scan, flags, deepSchemas) {
|
|
978
|
-
const path = resolve(dir, 'docs-canonical/DATA-MODEL.md');
|
|
979
|
-
if (existsSync(path) && !flags.force) {
|
|
980
|
-
console.log(` ${c.dim}⏭️ DATA-MODEL.md (exists)${c.reset}`);
|
|
981
|
-
return false;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// Use deep schemas if available, fallback to basic scan
|
|
985
|
-
let entities = [];
|
|
986
|
-
let relationships = [];
|
|
987
|
-
let schemaSource = 'file scan';
|
|
988
|
-
|
|
989
|
-
if (deepSchemas && deepSchemas.entities.length > 0) {
|
|
990
|
-
entities = deepSchemas.entities;
|
|
991
|
-
relationships = deepSchemas.relationships;
|
|
992
|
-
schemaSource = deepSchemas.source;
|
|
993
|
-
} else {
|
|
994
|
-
// Fallback: basic entity detection from file names
|
|
995
|
-
for (const modelFile of scan.models) {
|
|
996
|
-
const name = basename(modelFile, extname(modelFile));
|
|
997
|
-
if (name !== 'index' && name !== 'schema') {
|
|
998
|
-
entities.push({
|
|
999
|
-
name: name.charAt(0).toUpperCase() + name.slice(1),
|
|
1000
|
-
fields: [],
|
|
1001
|
-
file: modelFile,
|
|
1002
|
-
source: 'file',
|
|
1003
|
-
});
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
// Build entity summary table
|
|
1009
|
-
const entityRows = entities
|
|
1010
|
-
.filter(e => e.source !== 'prisma-enum')
|
|
1011
|
-
.map(e => {
|
|
1012
|
-
const pk = e.fields?.find(f => f.primaryKey);
|
|
1013
|
-
return `| ${e.name} | ${stack.database || 'TBD'} | ${pk ? pk.name : e.name.toLowerCase() + 'Id'} | ${e.file || '—'} | ${e.fields?.length || 0} fields |`;
|
|
1014
|
-
}).join('\n');
|
|
1015
|
-
|
|
1016
|
-
// Build detailed entity sections
|
|
1017
|
-
const entitySections = entities
|
|
1018
|
-
.filter(e => e.source !== 'prisma-enum')
|
|
1019
|
-
.map(e => {
|
|
1020
|
-
if (!e.fields || e.fields.length === 0) {
|
|
1021
|
-
return `### ${e.name}
|
|
1022
|
-
|
|
1023
|
-
> Source: \`${e.file || 'unknown'}\`
|
|
1024
|
-
|
|
1025
|
-
| Field | Type | Required | Default | Constraints | Description |
|
|
1026
|
-
|-------|------|----------|---------|-------------|-------------|
|
|
1027
|
-
| <!-- TBD: Fill in fields --> | | | | | |
|
|
1028
|
-
`;
|
|
1029
|
-
}
|
|
1030
|
-
const fieldRows = e.fields.map(f =>
|
|
1031
|
-
`| ${f.name} | ${f.type} | ${f.required ? '✓' : '✗'} | ${f.default || '—'} | ${f.primaryKey ? 'PK' : ''}${f.unique ? ' UK' : ''} | ${f.description || ''} |`
|
|
1032
|
-
).join('\n');
|
|
1033
|
-
|
|
1034
|
-
return `### ${e.name}
|
|
1035
|
-
|
|
1036
|
-
> Source: \`${e.file || 'unknown'}\` (${e.source || 'detected'})
|
|
1037
|
-
|
|
1038
|
-
| Field | Type | Required | Default | Constraints | Description |
|
|
1039
|
-
|-------|------|:--------:|---------|-------------|-------------|
|
|
1040
|
-
${fieldRows}
|
|
1041
|
-
`;
|
|
1042
|
-
}).join('\n');
|
|
1043
|
-
|
|
1044
|
-
// Build enum sections (if Prisma enums found)
|
|
1045
|
-
const enums = entities.filter(e => e.source === 'prisma-enum');
|
|
1046
|
-
const enumSection = enums.length > 0 ? `## Enums
|
|
1047
|
-
|
|
1048
|
-
${enums.map(e => `### ${e.name}
|
|
1049
|
-
|
|
1050
|
-
| Value |
|
|
1051
|
-
|-------|
|
|
1052
|
-
${e.fields.map(f => `| ${f.name} |`).join('\n')}
|
|
1053
|
-
`).join('\n')}` : '';
|
|
1054
|
-
|
|
1055
|
-
// Build relationship table
|
|
1056
|
-
const relRows = relationships.length > 0
|
|
1057
|
-
? relationships.map(r => `| ${r.from} | ${r.to} | ${r.type} | ${r.field} | — |`).join('\n')
|
|
1058
|
-
: '| <!-- No relationships detected --> | | | | |';
|
|
1059
|
-
|
|
1060
|
-
// Generate mermaid ER diagram
|
|
1061
|
-
const erDiagram = generateERDiagram(entities, relationships);
|
|
1062
|
-
|
|
1063
|
-
const content = `# Data Model
|
|
1064
|
-
|
|
1065
|
-
<!-- docguard:version 0.1.0 -->
|
|
1066
|
-
<!-- docguard:status draft -->
|
|
1067
|
-
<!-- docguard:last-reviewed ${new Date().toISOString().split('T')[0]} -->
|
|
1068
|
-
<!-- docguard:generated true -->
|
|
1069
|
-
|
|
1070
|
-
> **Auto-generated by DocGuard.** Review and refine this document.
|
|
1071
|
-
|
|
1072
|
-
| Metadata | Value |
|
|
1073
|
-
|----------|-------|
|
|
1074
|
-
| **Status** |  |
|
|
1075
|
-
| **Version** | \`0.1.0\` |
|
|
1076
|
-
| **Database** | ${stack.database || 'TBD'} |
|
|
1077
|
-
| **ORM** | ${stack.orm || 'None detected'} |
|
|
1078
|
-
| **Schema Source** | ${schemaSource} |
|
|
1079
|
-
| **Entities** | ${entities.filter(e => e.source !== 'prisma-enum').length} |
|
|
1080
|
-
| **Relationships** | ${relationships.length} |
|
|
1081
|
-
|
|
1082
|
-
---
|
|
1083
|
-
|
|
1084
|
-
## Entity Summary
|
|
1085
|
-
|
|
1086
|
-
| Entity | Storage | Primary Key | Source | Fields |
|
|
1087
|
-
|--------|---------|-------------|--------|--------|
|
|
1088
|
-
${entityRows || '| <!-- No models detected --> | | | | |'}
|
|
1089
|
-
|
|
1090
|
-
---
|
|
1091
|
-
|
|
1092
|
-
## Entity Details
|
|
1093
|
-
|
|
1094
|
-
${entitySections}
|
|
1095
|
-
${enumSection}
|
|
1096
|
-
|
|
1097
|
-
## Relationships
|
|
1098
|
-
|
|
1099
|
-
| From | To | Type | FK/Reference | Cascade |
|
|
1100
|
-
|------|-----|------|-------------|---------|
|
|
1101
|
-
${relRows}
|
|
1102
|
-
${erDiagram ? `
|
|
1103
|
-
## Entity-Relationship Diagram
|
|
1104
|
-
|
|
1105
|
-
\`\`\`mermaid
|
|
1106
|
-
${erDiagram}
|
|
1107
|
-
\`\`\`
|
|
1108
|
-
` : ''}
|
|
1109
|
-
|
|
1110
|
-
## Indexes
|
|
1111
|
-
|
|
1112
|
-
| Table | Index Name | Fields | Type | Purpose |
|
|
1113
|
-
|-------|-----------|--------|------|---------|
|
|
1114
|
-
| <!-- TBD: Document indexes --> | | | | |
|
|
1115
|
-
|
|
1116
|
-
---
|
|
1117
|
-
|
|
1118
|
-
## Revision History
|
|
1119
|
-
|
|
1120
|
-
| Version | Date | Author | Changes |
|
|
1121
|
-
|---------|------|--------|---------|
|
|
1122
|
-
| 0.1.0 | ${new Date().toISOString().split('T')[0]} | DocGuard Generate | Auto-generated (${entities.length} entities, ${relationships.length} relationships from ${schemaSource}) |
|
|
1123
|
-
`;
|
|
1124
|
-
|
|
1125
|
-
safeWrite(path, appendStandardsCitation(content, 'DATA-MODEL.md'), 'utf-8');
|
|
1126
|
-
console.log(` ${c.green}✅ DATA-MODEL.md${c.reset} (${entities.length} entities, ${relationships.length} relationships from ${schemaSource})`);
|
|
1127
|
-
return true;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
function generateEnvironment(dir, config, stack, scan, flags) {
|
|
1131
|
-
const path = resolve(dir, 'docs-canonical/ENVIRONMENT.md');
|
|
1132
|
-
if (existsSync(path) && !flags.force) {
|
|
1133
|
-
console.log(` ${c.dim}⏭️ ENVIRONMENT.md (exists)${c.reset}`);
|
|
1134
|
-
return false;
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
const envVarRows = scan.envVars.map(v =>
|
|
1138
|
-
`| \`${v.name}\` | ${categorizeEnvVar(v.name)} | Yes | \`${v.example}\` | |`
|
|
1139
|
-
).join('\n');
|
|
1140
|
-
|
|
1141
|
-
const content = `# Environment
|
|
1142
|
-
|
|
1143
|
-
<!-- docguard:version 0.1.0 -->
|
|
1144
|
-
<!-- docguard:status draft -->
|
|
1145
|
-
<!-- docguard:last-reviewed ${new Date().toISOString().split('T')[0]} -->
|
|
1146
|
-
<!-- docguard:generated true -->
|
|
1147
|
-
|
|
1148
|
-
> **Auto-generated by DocGuard.** Review and refine this document.
|
|
1149
|
-
|
|
1150
|
-
| Metadata | Value |
|
|
1151
|
-
|----------|-------|
|
|
1152
|
-
| **Status** |  |
|
|
1153
|
-
| **Version** | \`0.1.0\` |
|
|
1154
|
-
|
|
1155
|
-
---
|
|
1156
|
-
|
|
1157
|
-
## Prerequisites
|
|
1158
|
-
|
|
1159
|
-
| Tool | Version | Installation |
|
|
1160
|
-
|------|---------|-------------|
|
|
1161
|
-
${stack.language ? `| ${stack.language.split(' ')[0]} | ${stack.language.split(' ')[1] || 'latest'} | |` : ''}
|
|
1162
|
-
${stack.framework ? `| ${stack.framework.split(' ')[0]} | ${stack.framework.split(' ')[1] || 'latest'} | |` : ''}
|
|
1163
|
-
${stack.database ? `| ${stack.database} | latest | |` : ''}
|
|
1164
|
-
|
|
1165
|
-
## Environment Variables
|
|
1166
|
-
|
|
1167
|
-
| Variable | Category | Required | Example | Description |
|
|
1168
|
-
|----------|----------|:--------:|---------|-------------|
|
|
1169
|
-
${envVarRows || '| <!-- No .env.example found --> | | | | |'}
|
|
1170
|
-
|
|
1171
|
-
## Setup Steps
|
|
1172
|
-
|
|
1173
|
-
1. Clone the repository
|
|
1174
|
-
2. Install dependencies: \`${existsSync(resolve(dir, 'pnpm-lock.yaml')) ? 'pnpm install' : 'npm install'}\`
|
|
1175
|
-
3. Copy environment file: \`cp .env.example .env.local\`
|
|
1176
|
-
4. Fill in environment variables
|
|
1177
|
-
5. Start development server: \`${existsSync(resolve(dir, 'pnpm-lock.yaml')) ? 'pnpm' : 'npm'} run dev\`
|
|
1178
|
-
|
|
1179
|
-
---
|
|
1180
|
-
|
|
1181
|
-
## Revision History
|
|
1182
|
-
|
|
1183
|
-
| Version | Date | Author | Changes |
|
|
1184
|
-
|---------|------|--------|---------|
|
|
1185
|
-
| 0.1.0 | ${new Date().toISOString().split('T')[0]} | DocGuard Generate | Auto-generated (${scan.envVars.length} env vars found) |
|
|
1186
|
-
`;
|
|
1187
|
-
|
|
1188
|
-
safeWrite(path, appendStandardsCitation(content, 'ENVIRONMENT.md'), 'utf-8');
|
|
1189
|
-
console.log(` ${c.green}✅ ENVIRONMENT.md${c.reset} (${scan.envVars.length} env vars detected)`);
|
|
1190
|
-
return true;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
function generateTestSpec(dir, config, stack, scan, flags) {
|
|
1194
|
-
const path = resolve(dir, 'docs-canonical/TEST-SPEC.md');
|
|
1195
|
-
if (existsSync(path) && !flags.force) {
|
|
1196
|
-
console.log(` ${c.dim}⏭️ TEST-SPEC.md (exists)${c.reset}`);
|
|
1197
|
-
return false;
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
// Build service-to-test map
|
|
1201
|
-
const serviceMap = [];
|
|
1202
|
-
for (const svc of scan.services) {
|
|
1203
|
-
const svcName = basename(svc, extname(svc));
|
|
1204
|
-
const matchingTest = scan.tests.find(t =>
|
|
1205
|
-
t.includes(svcName) || t.includes(svcName.replace('.', '.test.'))
|
|
1206
|
-
);
|
|
1207
|
-
serviceMap.push({
|
|
1208
|
-
source: svc,
|
|
1209
|
-
test: matchingTest || '—',
|
|
1210
|
-
status: matchingTest ? '✅' : '❌',
|
|
1211
|
-
});
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
const serviceRows = serviceMap.map(s =>
|
|
1215
|
-
`| \`${s.source}\` | \`${s.test}\` | — | ${s.status} |`
|
|
1216
|
-
).join('\n');
|
|
1217
|
-
|
|
1218
|
-
const content = `# Test Specification
|
|
1219
|
-
|
|
1220
|
-
<!-- docguard:version 0.1.0 -->
|
|
1221
|
-
<!-- docguard:status draft -->
|
|
1222
|
-
<!-- docguard:last-reviewed ${new Date().toISOString().split('T')[0]} -->
|
|
1223
|
-
<!-- docguard:generated true -->
|
|
1224
|
-
|
|
1225
|
-
> **Auto-generated by DocGuard.** Review and refine this document.
|
|
1226
|
-
|
|
1227
|
-
| Metadata | Value |
|
|
1228
|
-
|----------|-------|
|
|
1229
|
-
| **Status** |  |
|
|
1230
|
-
| **Test Framework** | ${stack.testing || 'Not detected'} |
|
|
1231
|
-
| **Test Files Found** | ${scan.tests.length} |
|
|
1232
|
-
|
|
1233
|
-
---
|
|
1234
|
-
|
|
1235
|
-
## Test Categories
|
|
1236
|
-
|
|
1237
|
-
| Category | Framework | Location | Run Command |
|
|
1238
|
-
|----------|-----------|----------|-------------|
|
|
1239
|
-
| Unit | ${stack.testing || 'TBD'} | tests/unit/ | \`npm test\` |
|
|
1240
|
-
| Integration | ${stack.testing || 'TBD'} | tests/integration/ | \`npm run test:integration\` |
|
|
1241
|
-
| E2E | Playwright | tests/e2e/ | \`npm run test:e2e\` |
|
|
1242
|
-
|
|
1243
|
-
## Coverage Rules
|
|
1244
|
-
|
|
1245
|
-
| Metric | Target | Current |
|
|
1246
|
-
|--------|:------:|:-------:|
|
|
1247
|
-
| Line Coverage | 80% | <!-- TBD --> |
|
|
1248
|
-
| Branch Coverage | 70% | <!-- TBD --> |
|
|
1249
|
-
| Function Coverage | 80% | <!-- TBD --> |
|
|
1250
|
-
|
|
1251
|
-
## Service-to-Test Map
|
|
1252
|
-
|
|
1253
|
-
| Source File | Unit Test | Integration Test | Status |
|
|
1254
|
-
|------------|-----------|-----------------|:------:|
|
|
1255
|
-
${serviceRows || '| <!-- No services found --> | | | |'}
|
|
1256
|
-
|
|
1257
|
-
## Critical User Journeys
|
|
1258
|
-
|
|
1259
|
-
| # | Journey | Test File | Status |
|
|
1260
|
-
|---|---------|-----------|:------:|
|
|
1261
|
-
| 1 | <!-- e.g. User Registration --> | <!-- test file --> | ❌ |
|
|
1262
|
-
| 2 | <!-- e.g. Login Flow --> | | ❌ |
|
|
1263
|
-
|
|
1264
|
-
---
|
|
1265
|
-
|
|
1266
|
-
## Revision History
|
|
1267
|
-
|
|
1268
|
-
| Version | Date | Author | Changes |
|
|
1269
|
-
|---------|------|--------|---------|
|
|
1270
|
-
| 0.1.0 | ${new Date().toISOString().split('T')[0]} | DocGuard Generate | Auto-generated (${scan.tests.length} test files, ${serviceMap.filter(s => s.status === '✅').length}/${serviceMap.length} mapped) |
|
|
1271
|
-
`;
|
|
1272
|
-
|
|
1273
|
-
safeWrite(path, appendStandardsCitation(content, 'TEST-SPEC.md'), 'utf-8');
|
|
1274
|
-
console.log(` ${c.green}✅ TEST-SPEC.md${c.reset} (${scan.tests.length} tests, ${serviceMap.filter(s => s.status === '✅').length}/${serviceMap.length} services mapped)`);
|
|
1275
|
-
return true;
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
function generateSecurity(dir, config, stack, scan, flags) {
|
|
1279
|
-
const path = resolve(dir, 'docs-canonical/SECURITY.md');
|
|
1280
|
-
if (existsSync(path) && !flags.force) {
|
|
1281
|
-
console.log(` ${c.dim}⏭️ SECURITY.md (exists)${c.reset}`);
|
|
1282
|
-
return false;
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
const content = `# Security
|
|
1286
|
-
|
|
1287
|
-
<!-- docguard:version 0.1.0 -->
|
|
1288
|
-
<!-- docguard:status draft -->
|
|
1289
|
-
<!-- docguard:last-reviewed ${new Date().toISOString().split('T')[0]} -->
|
|
1290
|
-
<!-- docguard:generated true -->
|
|
1291
|
-
|
|
1292
|
-
> **Auto-generated by DocGuard.** Review and refine this document.
|
|
1293
|
-
|
|
1294
|
-
| Metadata | Value |
|
|
1295
|
-
|----------|-------|
|
|
1296
|
-
| **Status** |  |
|
|
1297
|
-
|
|
1298
|
-
---
|
|
1299
|
-
|
|
1300
|
-
## Authentication
|
|
1301
|
-
|
|
1302
|
-
| Method | Provider | Token Type | Expiry |
|
|
1303
|
-
|--------|---------|-----------|--------|
|
|
1304
|
-
| ${stack.auth || '<!-- TBD -->'} | | | |
|
|
1305
|
-
|
|
1306
|
-
## Authorization
|
|
1307
|
-
|
|
1308
|
-
| Role | Permissions | Notes |
|
|
1309
|
-
|------|-----------|-------|
|
|
1310
|
-
| <!-- e.g. admin --> | <!-- All --> | |
|
|
1311
|
-
| <!-- e.g. user --> | <!-- Read/Write --> | |
|
|
1312
|
-
|
|
1313
|
-
## Secrets Management
|
|
1314
|
-
|
|
1315
|
-
| Secret | Storage | Rotation | Access |
|
|
1316
|
-
|--------|---------|----------|--------|
|
|
1317
|
-
${scan.envVars.filter(v => isSecretVar(v.name)).map(v =>
|
|
1318
|
-
`| \`${v.name}\` | Environment Variable | <!-- TBD --> | Application |`
|
|
1319
|
-
).join('\n') || '| <!-- TBD --> | | | |'}
|
|
1320
|
-
|
|
1321
|
-
## Security Rules
|
|
1322
|
-
|
|
1323
|
-
- [ ] All secrets stored in environment variables (never in code)
|
|
1324
|
-
- [ ] \`.env\` is in \`.gitignore\`
|
|
1325
|
-
- [ ] API endpoints require authentication
|
|
1326
|
-
- [ ] Input validation on all user inputs
|
|
1327
|
-
- [ ] HTTPS enforced in production
|
|
1328
|
-
- [ ] CORS configured appropriately
|
|
1329
|
-
|
|
1330
|
-
---
|
|
1331
|
-
|
|
1332
|
-
## Revision History
|
|
1333
|
-
|
|
1334
|
-
| Version | Date | Author | Changes |
|
|
1335
|
-
|---------|------|--------|---------|
|
|
1336
|
-
| 0.1.0 | ${new Date().toISOString().split('T')[0]} | DocGuard Generate | Auto-generated |
|
|
1337
|
-
`;
|
|
1338
|
-
|
|
1339
|
-
safeWrite(path, appendStandardsCitation(content, 'SECURITY.md'), 'utf-8');
|
|
1340
|
-
console.log(` ${c.green}✅ SECURITY.md${c.reset} (auth: ${stack.auth || 'not detected'})`);
|
|
1341
|
-
return true;
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
function generateRootFiles(dir, config, stack, scan, flags, docTools) {
|
|
1345
|
-
let created = 0;
|
|
1346
|
-
let skipped = 0;
|
|
1347
|
-
|
|
1348
|
-
// AGENTS.md (AGENTS.md Standard compliant)
|
|
1349
|
-
const agentsPath = resolve(dir, 'AGENTS.md');
|
|
1350
|
-
if (!existsSync(agentsPath) || flags.force) {
|
|
1351
|
-
const content = `# AI Agent Instructions — ${config.projectName}
|
|
1352
|
-
|
|
1353
|
-
<!-- Standard: https://agents.md -->
|
|
1354
|
-
<!-- Generated by DocGuard — AGENTS.md standard compliant -->
|
|
1355
|
-
|
|
1356
|
-
> This project follows **Canonical-Driven Development (CDD)**.
|
|
1357
|
-
> Documentation is the source of truth. Read before coding.
|
|
1358
|
-
|
|
1359
|
-
## Workflow
|
|
1360
|
-
|
|
1361
|
-
1. **Read** \`docs-canonical/\` before suggesting changes
|
|
1362
|
-
2. **Check** existing patterns in the codebase
|
|
1363
|
-
3. **Run** \`npx docguard-cli diagnose\` to see what needs fixing
|
|
1364
|
-
4. **Confirm** your approach before writing code
|
|
1365
|
-
5. **Implement** matching existing code style
|
|
1366
|
-
6. **Log** any deviations in \`DRIFT-LOG.md\` with \`// DRIFT: reason\`
|
|
1367
|
-
7. **Verify** with \`npx docguard-cli guard\` — all checks must pass
|
|
1368
|
-
|
|
1369
|
-
## Project Stack
|
|
1370
|
-
|
|
1371
|
-
${Object.entries(stack).filter(([, v]) => v).map(([k, v]) => `- **${k}**: ${v}`).join('\n')}
|
|
1372
|
-
|
|
1373
|
-
## Key Files
|
|
1374
|
-
|
|
1375
|
-
| File | Purpose |
|
|
1376
|
-
|------|---------|
|
|
1377
|
-
| \`docs-canonical/ARCHITECTURE.md\` | System design (arc42 aligned) |
|
|
1378
|
-
| \`docs-canonical/API-REFERENCE.md\` | API endpoint documentation |
|
|
1379
|
-
| \`docs-canonical/DATA-MODEL.md\` | Database schemas & entities |
|
|
1380
|
-
| \`docs-canonical/SECURITY.md\` | Auth & secrets |
|
|
1381
|
-
| \`docs-canonical/TEST-SPEC.md\` | Test requirements |
|
|
1382
|
-
| \`docs-canonical/ENVIRONMENT.md\` | Environment setup |
|
|
1383
|
-
| \`AGENTS.md\` | AI agent instructions (this file) |
|
|
1384
|
-
| \`CHANGELOG.md\` | Change tracking |
|
|
1385
|
-
| \`DRIFT-LOG.md\` | Documented deviations |
|
|
1386
|
-
|
|
1387
|
-
## Permissions & Guardrails
|
|
1388
|
-
|
|
1389
|
-
> **IMPORTANT:** These limits apply to all AI agents working on this project.
|
|
1390
|
-
|
|
1391
|
-
### Allowed
|
|
1392
|
-
|
|
1393
|
-
- Read any file in the repository
|
|
1394
|
-
- Modify files within \`src/\`, \`tests/\`, and \`docs-canonical/\`
|
|
1395
|
-
- Run test commands (\`npm test\`, \`npx docguard-cli guard\`)
|
|
1396
|
-
- Create new files in appropriate directories
|
|
1397
|
-
|
|
1398
|
-
### Not Allowed
|
|
1399
|
-
|
|
1400
|
-
- Modify \`.env\` files or secrets
|
|
1401
|
-
- Push commits or create releases without explicit approval
|
|
1402
|
-
- Delete or rename canonical documentation files
|
|
1403
|
-
- Bypass DocGuard checks (\`docguard guard\` must pass)
|
|
1404
|
-
- Install new dependencies without approval
|
|
1405
|
-
|
|
1406
|
-
### Safety Rules
|
|
1407
|
-
|
|
1408
|
-
- Never hardcode secrets, tokens, or API keys
|
|
1409
|
-
- Always validate inputs before processing
|
|
1410
|
-
- Never expose internal paths or stack traces to users
|
|
1411
|
-
- Run \`npx docguard-cli guard\` before every commit
|
|
1412
|
-
|
|
1413
|
-
## Monorepo Support
|
|
1414
|
-
|
|
1415
|
-
<!-- If this is a monorepo, nested AGENTS.md files in subdirectories
|
|
1416
|
-
override these instructions for their scope. -->
|
|
1417
|
-
|
|
1418
|
-
| Scope | AGENTS.md Location |
|
|
1419
|
-
|-------|-------------------|
|
|
1420
|
-
| Root (default) | \`./AGENTS.md\` |
|
|
1421
|
-
| <!-- e.g. packages/api --> | <!-- packages/api/AGENTS.md --> |
|
|
1422
|
-
|
|
1423
|
-
## DocGuard Commands
|
|
1424
|
-
|
|
1425
|
-
\`\`\`bash
|
|
1426
|
-
npx docguard-cli guard # Validate compliance
|
|
1427
|
-
npx docguard-cli diagnose # Identify issues + AI fix prompts
|
|
1428
|
-
npx docguard-cli fix --doc ARCH # Fix specific document
|
|
1429
|
-
npx docguard-cli score # CDD maturity score (0-100)
|
|
1430
|
-
npx docguard-cli generate # Generate docs from code
|
|
1431
|
-
\`\`\`
|
|
1432
|
-
|
|
1433
|
-
### AI Agent Workflow (IMPORTANT)
|
|
1434
|
-
|
|
1435
|
-
1. **Before work**: Run \`npx docguard-cli guard\` — understand compliance state
|
|
1436
|
-
2. **After changes**: Run \`npx docguard-cli diagnose\` — get fix instructions
|
|
1437
|
-
3. **Fix issues**: Each issue has an \`ai_instruction\` — follow it exactly
|
|
1438
|
-
4. **Verify**: Run \`npx docguard-cli guard\` again — must pass before commit
|
|
1439
|
-
5. **Update CHANGELOG**: All changes need a changelog entry
|
|
1440
|
-
|
|
1441
|
-
## Rules
|
|
1442
|
-
|
|
1443
|
-
- Never commit without updating CHANGELOG.md
|
|
1444
|
-
- If code deviates from docs, add \`// DRIFT: reason\`
|
|
1445
|
-
- Security rules in SECURITY.md are mandatory
|
|
1446
|
-
- Test requirements in TEST-SPEC.md must be met
|
|
1447
|
-
- Documentation changes must pass \`docguard guard\`
|
|
1448
|
-
`;
|
|
1449
|
-
safeWrite(agentsPath, content);
|
|
1450
|
-
console.log(` ${c.green}✅ AGENTS.md${c.reset} (AGENTS.md standard compliant)`);
|
|
1451
|
-
created++;
|
|
1452
|
-
} else {
|
|
1453
|
-
console.log(` ${c.dim}⏭️ AGENTS.md (exists)${c.reset}`);
|
|
1454
|
-
skipped++;
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
// CHANGELOG.md
|
|
1458
|
-
const changelogPath = resolve(dir, 'CHANGELOG.md');
|
|
1459
|
-
if (!existsSync(changelogPath) || flags.force) {
|
|
1460
|
-
const content = `# Changelog
|
|
1461
|
-
|
|
1462
|
-
All notable changes to this project will be documented in this file.
|
|
1463
|
-
|
|
1464
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
1465
|
-
|
|
1466
|
-
## [Unreleased]
|
|
1467
|
-
|
|
1468
|
-
### Added
|
|
1469
|
-
- CDD documentation via DocGuard generate
|
|
1470
|
-
`;
|
|
1471
|
-
safeWrite(changelogPath, content);
|
|
1472
|
-
console.log(` ${c.green}✅ CHANGELOG.md${c.reset}`);
|
|
1473
|
-
created++;
|
|
1474
|
-
} else {
|
|
1475
|
-
console.log(` ${c.dim}⏭️ CHANGELOG.md (exists)${c.reset}`);
|
|
1476
|
-
skipped++;
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
// DRIFT-LOG.md
|
|
1480
|
-
const driftPath = resolve(dir, 'DRIFT-LOG.md');
|
|
1481
|
-
if (!existsSync(driftPath) || flags.force) {
|
|
1482
|
-
const content = `# Drift Log
|
|
1483
|
-
|
|
1484
|
-
> Documents conscious deviations from canonical specifications.
|
|
1485
|
-
> Every \`// DRIFT: reason\` in code must have a corresponding entry here.
|
|
1486
|
-
|
|
1487
|
-
| Date | File | Canonical Doc | Drift Description | Severity | Resolution |
|
|
1488
|
-
|------|------|---------------|-------------------|----------|------------|
|
|
1489
|
-
| | | | | | |
|
|
1490
|
-
`;
|
|
1491
|
-
safeWrite(driftPath, content);
|
|
1492
|
-
console.log(` ${c.green}✅ DRIFT-LOG.md${c.reset}`);
|
|
1493
|
-
created++;
|
|
1494
|
-
} else {
|
|
1495
|
-
console.log(` ${c.dim}⏭️ DRIFT-LOG.md (exists)${c.reset}`);
|
|
1496
|
-
skipped++;
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
return { created, skipped };
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
// ── Utility Functions ──────────────────────────────────────────────────────
|
|
1503
|
-
|
|
1504
|
-
function categorizeEnvVar(name) {
|
|
1505
|
-
if (name.includes('SECRET') || name.includes('KEY') || name.includes('TOKEN') || name.includes('PASSWORD')) return '🔐 Secret';
|
|
1506
|
-
if (name.includes('DATABASE') || name.includes('DB_') || name.includes('REDIS')) return '🗃️ Database';
|
|
1507
|
-
if (name.includes('AUTH') || name.includes('JWT') || name.includes('SESSION')) return '🔒 Auth';
|
|
1508
|
-
if (name.includes('AWS') || name.includes('CLOUD') || name.includes('S3')) return '☁️ Cloud';
|
|
1509
|
-
if (name.includes('URL') || name.includes('HOST') || name.includes('PORT')) return '🌐 Network';
|
|
1510
|
-
return '⚙️ Config';
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
function isSecretVar(name) {
|
|
1514
|
-
return name.includes('SECRET') || name.includes('KEY') || name.includes('TOKEN') || name.includes('PASSWORD');
|
|
1515
|
-
}
|
|
1516
|
-
|
|
553
|
+
// v0.29 consolidation: traversal delegates to the shared canonical walker.
|
|
1517
554
|
function walkDir(dir, callback) {
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
if (IGNORE_DIRS.has(entry) || entry.startsWith('.')) continue;
|
|
1522
|
-
const fullPath = join(dir, entry);
|
|
1523
|
-
try {
|
|
1524
|
-
const stat = statSync(fullPath);
|
|
1525
|
-
if (stat.isDirectory()) {
|
|
1526
|
-
walkDir(fullPath, callback);
|
|
1527
|
-
} else if (stat.isFile() && CODE_EXTENSIONS.has(extname(fullPath))) {
|
|
1528
|
-
callback(fullPath);
|
|
1529
|
-
}
|
|
1530
|
-
} catch { /* skip */ }
|
|
1531
|
-
}
|
|
555
|
+
sharedWalkFiles(dir, (fullPath) => {
|
|
556
|
+
if (CODE_EXTENSIONS.has(extname(fullPath))) callback(fullPath);
|
|
557
|
+
}, { ignoreDirs: IGNORE_DIRS });
|
|
1532
558
|
}
|
|
1533
559
|
|
|
1534
560
|
function getFilesRecursive(dir) {
|
|
1535
561
|
const results = [];
|
|
1536
|
-
|
|
1537
|
-
const entries = readdirSync(dir);
|
|
1538
|
-
for (const entry of entries) {
|
|
1539
|
-
if (IGNORE_DIRS.has(entry) || entry.startsWith('.')) continue;
|
|
1540
|
-
const fullPath = join(dir, entry);
|
|
1541
|
-
try {
|
|
1542
|
-
const stat = statSync(fullPath);
|
|
1543
|
-
if (stat.isDirectory()) {
|
|
1544
|
-
results.push(...getFilesRecursive(fullPath));
|
|
1545
|
-
} else if (stat.isFile()) {
|
|
1546
|
-
results.push(fullPath);
|
|
1547
|
-
}
|
|
1548
|
-
} catch { /* skip */ }
|
|
1549
|
-
}
|
|
562
|
+
sharedWalkFiles(dir, (fullPath) => results.push(fullPath), { ignoreDirs: IGNORE_DIRS });
|
|
1550
563
|
return results;
|
|
1551
564
|
}
|