docguard-cli 0.28.0 → 0.30.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.
Files changed (69) hide show
  1. package/README.es.md +102 -0
  2. package/README.md +80 -32
  3. package/README.pt-BR.md +101 -0
  4. package/STANDARD.md +20 -10
  5. package/cli/commands/agents.mjs +149 -0
  6. package/cli/commands/diff.mjs +6 -15
  7. package/cli/commands/generate.mjs +14 -1001
  8. package/cli/commands/guard.mjs +136 -8
  9. package/cli/commands/llms.mjs +67 -5
  10. package/cli/commands/mcp.mjs +263 -0
  11. package/cli/commands/memory.mjs +115 -0
  12. package/cli/commands/score.mjs +76 -12
  13. package/cli/commands/trace.mjs +364 -1
  14. package/cli/commands/verify.mjs +93 -6
  15. package/cli/docguard.mjs +42 -5
  16. package/cli/findings.mjs +511 -0
  17. package/cli/scanners/agent-readability.mjs +202 -0
  18. package/cli/scanners/instruction-audit.mjs +320 -0
  19. package/cli/scanners/semantic-claims.mjs +7 -1
  20. package/cli/scanners/speckit.mjs +443 -28
  21. package/cli/shared-ignore.mjs +148 -16
  22. package/cli/shared.mjs +45 -1
  23. package/cli/validators/api-surface.mjs +113 -26
  24. package/cli/validators/architecture.mjs +66 -43
  25. package/cli/validators/canonical-sync.mjs +59 -28
  26. package/cli/validators/changelog.mjs +41 -17
  27. package/cli/validators/cross-reference.mjs +28 -11
  28. package/cli/validators/doc-quality.mjs +78 -44
  29. package/cli/validators/docs-coverage.mjs +90 -63
  30. package/cli/validators/docs-diff.mjs +63 -64
  31. package/cli/validators/docs-sync.mjs +48 -33
  32. package/cli/validators/drift.mjs +40 -34
  33. package/cli/validators/environment.mjs +67 -27
  34. package/cli/validators/freshness.mjs +12 -5
  35. package/cli/validators/generated-staleness.mjs +26 -10
  36. package/cli/validators/metadata-sync.mjs +28 -25
  37. package/cli/validators/metrics-consistency.mjs +89 -47
  38. package/cli/validators/schema-sync.mjs +37 -32
  39. package/cli/validators/security.mjs +7 -20
  40. package/cli/validators/spec-kit.mjs +3 -0
  41. package/cli/validators/structure.mjs +58 -23
  42. package/cli/validators/surface-sync.mjs +34 -15
  43. package/cli/validators/test-spec.mjs +87 -29
  44. package/cli/validators/todo-tracking.mjs +83 -74
  45. package/cli/validators/traceability.mjs +67 -39
  46. package/cli/writers/doc-generators.mjs +853 -0
  47. package/cli/writers/generate-io.mjs +142 -0
  48. package/cli/writers/sarif.mjs +129 -0
  49. package/commands/docguard.fix.md +56 -53
  50. package/commands/docguard.guard.md +53 -47
  51. package/commands/docguard.review.md +49 -31
  52. package/docs/ai-integration.md +133 -134
  53. package/docs/commands.md +49 -3
  54. package/docs/configuration.md +38 -0
  55. package/docs/faq.md +15 -0
  56. package/extensions/spec-kit-docguard/extension.yml +1 -1
  57. package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +2 -2
  58. package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +2 -2
  59. package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +2 -2
  60. package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +2 -2
  61. package/extensions/spec-kit-docguard/skills/docguard-sync/SKILL.md +2 -2
  62. package/package.json +2 -1
  63. package/schemas/docguard-config.schema.json +28 -0
  64. package/templates/ci/gitlab-component.yml +90 -0
  65. package/templates/commands/docguard.fix.md +33 -10
  66. package/templates/commands/docguard.guard.md +40 -26
  67. package/templates/commands/docguard.init.md +23 -11
  68. package/templates/commands/docguard.review.md +25 -8
  69. 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, writeFileSync, readdirSync, statSync, mkdirSync, copyFileSync } from 'node:fs';
9
- import { resolve, join, extname, basename, relative, dirname } from 'node:path';
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, generateERDiagram } from '../scanners/schemas.mjs';
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
- // ── Document Generators ────────────────────────────────────────────────────
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** | ![Status](https://img.shields.io/badge/status-draft-yellow) |
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** | ![Status](https://img.shields.io/badge/status-draft-yellow) |
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** | ![Status](https://img.shields.io/badge/status-draft-yellow) |
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** | ![Status](https://img.shields.io/badge/status-draft-yellow) |
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** | ![Status](https://img.shields.io/badge/status-draft-yellow) |
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** | ![Status](https://img.shields.io/badge/status-draft-yellow) |
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
- if (!existsSync(dir)) return;
1519
- const entries = readdirSync(dir);
1520
- for (const entry of entries) {
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
- if (!existsSync(dir)) return results;
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
  }