docguard-cli 0.5.1 → 0.6.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.md +37 -15
- package/cli/commands/generate.mjs +432 -82
- package/cli/commands/publish.mjs +246 -0
- package/cli/docguard.mjs +19 -3
- package/cli/scanners/doc-tools.mjs +351 -0
- package/cli/scanners/routes.mjs +461 -0
- package/cli/scanners/schemas.mjs +567 -0
- package/docs/ai-integration.md +7 -7
- package/docs/commands.md +40 -40
- package/docs/faq.md +1 -1
- package/docs/profiles.md +6 -6
- package/docs/quickstart.md +10 -10
- package/package.json +1 -1
- package/templates/ci/github-actions.yml +1 -1
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, mkdirSync } from 'node:fs';
|
|
9
|
-
import { resolve, join, extname, basename, relative } from 'node:path';
|
|
9
|
+
import { resolve, join, extname, basename, relative, dirname } from 'node:path';
|
|
10
10
|
import { c } from '../docguard.mjs';
|
|
11
|
+
import { detectDocTools } from '../scanners/doc-tools.mjs';
|
|
12
|
+
import { scanRoutesDeep } from '../scanners/routes.mjs';
|
|
13
|
+
import { scanSchemasDeep, generateERDiagram } from '../scanners/schemas.mjs';
|
|
11
14
|
|
|
12
15
|
const IGNORE_DIRS = new Set([
|
|
13
16
|
'node_modules', '.git', '.next', 'dist', 'build', 'coverage',
|
|
@@ -33,10 +36,38 @@ export function runGenerate(projectDir, config, flags) {
|
|
|
33
36
|
}
|
|
34
37
|
console.log('');
|
|
35
38
|
|
|
36
|
-
// ── 2.
|
|
39
|
+
// ── 2. Detect Existing Doc Tools ──
|
|
40
|
+
const docTools = detectDocTools(projectDir);
|
|
41
|
+
if (docTools._detected.length > 0) {
|
|
42
|
+
console.log(` ${c.bold}Detected Documentation Tools:${c.reset}`);
|
|
43
|
+
for (const tool of docTools._detected) {
|
|
44
|
+
const info = docTools[tool];
|
|
45
|
+
const details = info.config || info.path || info.middleware || '';
|
|
46
|
+
let extra = '';
|
|
47
|
+
if (tool === 'openapi' && info.endpoints) extra = ` — ${info.endpoints.length} endpoints, ${info.schemas?.length || 0} schemas`;
|
|
48
|
+
if (tool === 'storybook' && info.storyCount) extra = ` — ${info.storyCount} stories`;
|
|
49
|
+
console.log(` ${c.cyan}${tool}:${c.reset} ${details}${extra}`);
|
|
50
|
+
}
|
|
51
|
+
console.log('');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── 3. Scan Project Structure ──
|
|
37
55
|
const scan = scanProject(projectDir);
|
|
38
56
|
|
|
39
|
-
// ──
|
|
57
|
+
// ── 4. Deep Scan Routes ──
|
|
58
|
+
const deepRoutes = scanRoutesDeep(projectDir, stack, docTools);
|
|
59
|
+
if (deepRoutes.length > 0) {
|
|
60
|
+
console.log(` ${c.bold}Route Scanning:${c.reset} ${deepRoutes.length} endpoints found (source: ${deepRoutes[0]?.source || 'code'})`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── 5. Deep Scan Schemas ──
|
|
64
|
+
const deepSchemas = scanSchemasDeep(projectDir, stack, docTools);
|
|
65
|
+
if (deepSchemas.entities.length > 0) {
|
|
66
|
+
console.log(` ${c.bold}Schema Scanning:${c.reset} ${deepSchemas.entities.length} entities, ${deepSchemas.relationships.length} relationships (source: ${deepSchemas.source})`);
|
|
67
|
+
}
|
|
68
|
+
console.log('');
|
|
69
|
+
|
|
70
|
+
// ── 6. Generate Documents ──
|
|
40
71
|
const docsDir = resolve(projectDir, 'docs-canonical');
|
|
41
72
|
if (!existsSync(docsDir)) {
|
|
42
73
|
mkdirSync(docsDir, { recursive: true });
|
|
@@ -45,12 +76,18 @@ export function runGenerate(projectDir, config, flags) {
|
|
|
45
76
|
let created = 0;
|
|
46
77
|
let skipped = 0;
|
|
47
78
|
|
|
48
|
-
// Generate ARCHITECTURE.md
|
|
49
|
-
const archResult = generateArchitecture(projectDir, config, stack, scan, flags);
|
|
79
|
+
// Generate ARCHITECTURE.md (arc42-aligned)
|
|
80
|
+
const archResult = generateArchitecture(projectDir, config, stack, scan, flags, docTools);
|
|
50
81
|
if (archResult) { created++; } else { skipped++; }
|
|
51
82
|
|
|
52
|
-
// Generate
|
|
53
|
-
|
|
83
|
+
// Generate API-REFERENCE.md (NEW — from deep route scanning)
|
|
84
|
+
if (deepRoutes.length > 0) {
|
|
85
|
+
const apiResult = generateApiReference(projectDir, config, stack, deepRoutes, flags);
|
|
86
|
+
if (apiResult) { created++; } else { skipped++; }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Generate DATA-MODEL.md (enhanced with deep schema scanning)
|
|
90
|
+
const dataResult = generateDataModel(projectDir, config, stack, scan, flags, deepSchemas);
|
|
54
91
|
if (dataResult) { created++; } else { skipped++; }
|
|
55
92
|
|
|
56
93
|
// Generate ENVIRONMENT.md
|
|
@@ -65,13 +102,16 @@ export function runGenerate(projectDir, config, flags) {
|
|
|
65
102
|
const secResult = generateSecurity(projectDir, config, stack, scan, flags);
|
|
66
103
|
if (secResult) { created++; } else { skipped++; }
|
|
67
104
|
|
|
68
|
-
// Generate root files
|
|
69
|
-
const rootResults = generateRootFiles(projectDir, config, stack, scan, flags);
|
|
105
|
+
// Generate root files (AGENTS.md, CHANGELOG, DRIFT-LOG)
|
|
106
|
+
const rootResults = generateRootFiles(projectDir, config, stack, scan, flags, docTools);
|
|
70
107
|
created += rootResults.created;
|
|
71
108
|
skipped += rootResults.skipped;
|
|
72
109
|
|
|
73
110
|
console.log(`\n${c.bold} ─────────────────────────────────────${c.reset}`);
|
|
74
111
|
console.log(` ${c.green}Generated: ${created}${c.reset} Skipped: ${skipped} (already exist)`);
|
|
112
|
+
if (docTools._detected.length > 0) {
|
|
113
|
+
console.log(` ${c.dim}Leveraged: ${docTools._detected.join(', ')} (existing tools detected)${c.reset}`);
|
|
114
|
+
}
|
|
75
115
|
console.log(`\n ${c.yellow}${c.bold}⚠️ Review all generated docs!${c.reset}`);
|
|
76
116
|
console.log(` ${c.dim}Generated docs are a starting point — review and refine them.${c.reset}`);
|
|
77
117
|
console.log(` ${c.dim}Run ${c.cyan}docguard score${c.dim} to check your CDD maturity.${c.reset}\n`);
|
|
@@ -278,7 +318,7 @@ function countFilesAndLines(dir, scan) {
|
|
|
278
318
|
|
|
279
319
|
// ── Document Generators ────────────────────────────────────────────────────
|
|
280
320
|
|
|
281
|
-
function generateArchitecture(dir, config, stack, scan, flags) {
|
|
321
|
+
function generateArchitecture(dir, config, stack, scan, flags, docTools) {
|
|
282
322
|
const path = resolve(dir, 'docs-canonical/ARCHITECTURE.md');
|
|
283
323
|
if (existsSync(path) && !flags.force) {
|
|
284
324
|
console.log(` ${c.dim}⏭️ ARCHITECTURE.md (exists)${c.reset}`);
|
|
@@ -297,14 +337,30 @@ function generateArchitecture(dir, config, stack, scan, flags) {
|
|
|
297
337
|
if (scan.components.length > 0) componentRows.push(`| UI Components | Frontend components | ${scan.components.length} files | |`);
|
|
298
338
|
if (scan.middlewares.length > 0) componentRows.push(`| Middleware | Request processing | ${scan.middlewares.join(', ')} | |`);
|
|
299
339
|
|
|
340
|
+
// Storybook integration
|
|
341
|
+
if (docTools?.storybook?.found) {
|
|
342
|
+
componentRows.push(`| Storybook | UI component docs | .storybook/ (${docTools.storybook.storyCount || '?'} stories) | |`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Doc tools section
|
|
346
|
+
const docToolRows = [];
|
|
347
|
+
if (docTools?._detected?.length > 0) {
|
|
348
|
+
for (const tool of docTools._detected) {
|
|
349
|
+
const info = docTools[tool];
|
|
350
|
+
docToolRows.push(`| ${tool} | ${info.config || info.path || info.middleware || 'detected'} | Active |`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
300
354
|
const content = `# Architecture
|
|
301
355
|
|
|
302
356
|
<!-- docguard:version 0.1.0 -->
|
|
303
357
|
<!-- docguard:status draft -->
|
|
304
358
|
<!-- docguard:last-reviewed ${new Date().toISOString().split('T')[0]} -->
|
|
305
359
|
<!-- docguard:generated true -->
|
|
360
|
+
<!-- docguard:standards arc42, C4 -->
|
|
306
361
|
|
|
307
362
|
> **Auto-generated by DocGuard.** Review and refine this document.
|
|
363
|
+
> Follows [arc42](https://arc42.org) structure and [C4 Model](https://c4model.com) diagrams.
|
|
308
364
|
|
|
309
365
|
| Metadata | Value |
|
|
310
366
|
|----------|-------|
|
|
@@ -315,26 +371,105 @@ function generateArchitecture(dir, config, stack, scan, flags) {
|
|
|
315
371
|
|
|
316
372
|
---
|
|
317
373
|
|
|
318
|
-
##
|
|
374
|
+
## 1. Introduction & Goals
|
|
375
|
+
<!-- arc42: §1 — Introduction and Goals -->
|
|
319
376
|
|
|
320
|
-
<!-- TODO: Describe what this system does
|
|
377
|
+
<!-- TODO: Describe what this system does, who it's for, and key quality goals -->
|
|
321
378
|
${config.projectName} is a ${stack.framework || stack.language || 'software'} application.
|
|
322
379
|
|
|
323
|
-
|
|
380
|
+
### Quality Goals
|
|
381
|
+
|
|
382
|
+
| Priority | Quality Goal | Scenario |
|
|
383
|
+
|----------|-------------|----------|
|
|
384
|
+
| 1 | <!-- e.g. Performance --> | <!-- e.g. Response time < 200ms --> |
|
|
385
|
+
| 2 | <!-- e.g. Security --> | <!-- e.g. All endpoints authenticated --> |
|
|
386
|
+
| 3 | <!-- e.g. Maintainability --> | <!-- e.g. New feature in < 1 day --> |
|
|
387
|
+
|
|
388
|
+
## 2. Constraints
|
|
389
|
+
<!-- arc42: §2 — Constraints -->
|
|
390
|
+
|
|
391
|
+
| Type | Constraint | Background |
|
|
392
|
+
|------|-----------|------------|
|
|
393
|
+
| Technical | ${stack.language || 'TBD'} | Primary language |
|
|
394
|
+
| Technical | ${stack.framework || 'TBD'} | Framework |
|
|
395
|
+
| Infrastructure | ${stack.hosting || 'TBD'} | Hosting provider |
|
|
396
|
+
|
|
397
|
+
## 3. Context & Scope
|
|
398
|
+
<!-- arc42: §3 — Context and Scope (C4 Level 1: System Context) -->
|
|
399
|
+
|
|
400
|
+
\\\`\\\`\\\`mermaid
|
|
401
|
+
graph TD
|
|
402
|
+
U[Users/Clients] --> S[${config.projectName}]
|
|
403
|
+
S --> DB[(${stack.database || 'Database'})]
|
|
404
|
+
S --> EXT[External Services]
|
|
405
|
+
\\\`\\\`\\\`
|
|
406
|
+
|
|
407
|
+
## 4. Solution Strategy
|
|
408
|
+
<!-- arc42: §4 — Solution Strategy -->
|
|
409
|
+
|
|
410
|
+
See \\\`docs-canonical/ADR.md\\\` for architecture decision records.
|
|
411
|
+
|
|
412
|
+
## 5. Building Block View
|
|
413
|
+
<!-- arc42: §5 — Building Block View (C4 Level 2: Container) -->
|
|
324
414
|
|
|
325
415
|
| Component | Responsibility | Location | Tests |
|
|
326
416
|
|-----------|---------------|----------|-------|
|
|
327
|
-
${componentRows.join('
|
|
417
|
+
${componentRows.join('\\n') || '| <!-- Add components --> | | | |'}
|
|
328
418
|
|
|
329
|
-
|
|
419
|
+
\\\`\\\`\\\`mermaid
|
|
420
|
+
graph TD
|
|
421
|
+
A[Client] --> B[${stack.framework || 'API'}]
|
|
422
|
+
B --> C[Services]
|
|
423
|
+
C --> D[${stack.database || 'Database'}]
|
|
424
|
+
${scan.middlewares.length > 0 ? 'A --> M[Middleware] --> B' : ''}
|
|
425
|
+
${scan.components.length > 0 ? 'A --> UI[UI Components]' : ''}
|
|
426
|
+
\\\`\\\`\\\`
|
|
427
|
+
|
|
428
|
+
## 6. Runtime View
|
|
429
|
+
<!-- arc42: §6 — Runtime View -->
|
|
430
|
+
|
|
431
|
+
\\\`\\\`\\\`mermaid
|
|
432
|
+
sequenceDiagram
|
|
433
|
+
participant C as Client
|
|
434
|
+
participant A as ${stack.framework || 'API'}
|
|
435
|
+
participant S as Service
|
|
436
|
+
participant D as ${stack.database || 'DB'}
|
|
437
|
+
C->>A: Request
|
|
438
|
+
A->>S: Process
|
|
439
|
+
S->>D: Query
|
|
440
|
+
D-->>S: Result
|
|
441
|
+
S-->>A: Response
|
|
442
|
+
A-->>C: JSON
|
|
443
|
+
\\\`\\\`\\\`
|
|
444
|
+
|
|
445
|
+
## 7. Deployment View
|
|
446
|
+
<!-- arc42: §7 — Deployment View -->
|
|
447
|
+
|
|
448
|
+
See \\\`docs-canonical/DEPLOYMENT.md\\\` for details.
|
|
449
|
+
|
|
450
|
+
| Environment | Infrastructure | URL |
|
|
451
|
+
|-------------|---------------|-----|
|
|
452
|
+
| Development | localhost | http://localhost:3000 |
|
|
453
|
+
| Staging | ${stack.hosting || 'TBD'} | <!-- TODO --> |
|
|
454
|
+
| Production | ${stack.hosting || 'TBD'} | <!-- TODO --> |
|
|
455
|
+
|
|
456
|
+
## 8. Crosscutting Concepts
|
|
457
|
+
<!-- arc42: §8 — Crosscutting Concepts -->
|
|
458
|
+
|
|
459
|
+
### Tech Stack
|
|
330
460
|
|
|
331
461
|
| Category | Technology | Version | License |
|
|
332
462
|
|----------|-----------|---------|---------|
|
|
333
463
|
${techRows || '| <!-- Add technologies --> | | | |'}
|
|
464
|
+
${docToolRows.length > 0 ? `
|
|
465
|
+
### Documentation Tools
|
|
334
466
|
|
|
335
|
-
|
|
467
|
+
| Tool | Config | Status |
|
|
468
|
+
|------|--------|--------|
|
|
469
|
+
${docToolRows.join('\\n')}
|
|
470
|
+
` : ''}
|
|
336
471
|
|
|
337
|
-
|
|
472
|
+
### Layer Boundaries
|
|
338
473
|
|
|
339
474
|
| Layer | Can Import From | Cannot Import From |
|
|
340
475
|
|-------|----------------|-------------------|
|
|
@@ -342,14 +477,127 @@ ${scan.routes.length > 0 ? '| Routes/Handlers | Services, Middleware | Models (d
|
|
|
342
477
|
${scan.services.length > 0 ? '| Services | Repositories, Utils | Routes |' : ''}
|
|
343
478
|
${scan.models.length > 0 ? '| Models/Repositories | Utils | Services, Routes |' : ''}
|
|
344
479
|
|
|
345
|
-
##
|
|
480
|
+
## 9. Architecture Decisions
|
|
481
|
+
<!-- arc42: §9 — Architecture Decisions -->
|
|
346
482
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
483
|
+
See \\\`docs-canonical/ADR.md\\\` for the full decision log.
|
|
484
|
+
|
|
485
|
+
## 10. Quality Requirements
|
|
486
|
+
<!-- arc42: §10 — Quality Requirements -->
|
|
487
|
+
|
|
488
|
+
See \\\`docs-canonical/TEST-SPEC.md\\\` for test requirements and coverage targets.
|
|
489
|
+
|
|
490
|
+
## 11. Risks & Technical Debt
|
|
491
|
+
<!-- arc42: §11 — Risk Assessment and Technical Debt -->
|
|
492
|
+
|
|
493
|
+
See \\\`DRIFT-LOG.md\\\` for documented deviations from canonical specs.
|
|
494
|
+
See \\\`docs-canonical/KNOWN-GOTCHAS.md\\\` for known issues.
|
|
495
|
+
|
|
496
|
+
## 12. Glossary
|
|
497
|
+
<!-- arc42: §12 — Glossary -->
|
|
498
|
+
|
|
499
|
+
| Term | Definition |
|
|
500
|
+
|------|-----------|
|
|
501
|
+
| CDD | Canonical-Driven Development — documentation as the source of truth |
|
|
502
|
+
| Canonical Doc | A specification document that defines system behavior |
|
|
503
|
+
| Drift | Conscious deviation from canonical documentation |
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Revision History
|
|
508
|
+
|
|
509
|
+
| Version | Date | Author | Changes |
|
|
510
|
+
|---------|------|--------|---------|
|
|
511
|
+
| 0.1.0 | ${new Date().toISOString().split('T')[0]} | DocGuard Generate | Auto-generated (arc42 + C4 aligned) |
|
|
512
|
+
`;
|
|
513
|
+
|
|
514
|
+
writeFileSync(path, content, 'utf-8');
|
|
515
|
+
console.log(` ${c.green}✅ ARCHITECTURE.md${c.reset} (arc42 §1-§12, ${componentRows.length} components, ${Object.values(stack).filter(Boolean).length} tech)`);
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// ── API Reference Generator (NEW — from deep route scanning) ───────────────
|
|
520
|
+
|
|
521
|
+
function generateApiReference(dir, config, stack, deepRoutes, flags) {
|
|
522
|
+
const path = resolve(dir, 'docs-canonical/API-REFERENCE.md');
|
|
523
|
+
if (existsSync(path) && !flags.force) {
|
|
524
|
+
console.log(` ${c.dim}⏭️ API-REFERENCE.md (exists)${c.reset}`);
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Group routes by resource (first path segment after /api/)
|
|
529
|
+
const groups = {};
|
|
530
|
+
for (const route of deepRoutes) {
|
|
531
|
+
const parts = route.path.split('/').filter(Boolean);
|
|
532
|
+
const resource = parts[1] || parts[0] || 'root';
|
|
533
|
+
if (!groups[resource]) groups[resource] = [];
|
|
534
|
+
groups[resource].push(route);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Build endpoint table
|
|
538
|
+
const endpointRows = deepRoutes
|
|
539
|
+
.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method))
|
|
540
|
+
.map(r => `| \`${r.method}\` | \`${r.path}\` | ${r.handler || '—'} | ${r.auth ? '🔒' : '🔓'} | ${r.description || '—'} |`)
|
|
541
|
+
.join('\n');
|
|
542
|
+
|
|
543
|
+
// Build per-resource sections
|
|
544
|
+
const resourceSections = Object.entries(groups)
|
|
545
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
546
|
+
.map(([resource, routes]) => {
|
|
547
|
+
const routeDetails = routes.map(r => `#### ${r.method} \`${r.path}\`
|
|
548
|
+
|
|
549
|
+
> Source: \`${r.file}\`${r.source ? ` (${r.source})` : ''}
|
|
550
|
+
|
|
551
|
+
- **Auth:** ${r.auth ? 'Required' : 'None'}
|
|
552
|
+
- **Handler:** ${r.handler || '—'}
|
|
553
|
+
${r.description ? `- **Description:** ${r.description}` : ''}
|
|
554
|
+
|
|
555
|
+
| Parameter | In | Type | Required | Description |
|
|
556
|
+
|-----------|-----|------|:--------:|-------------|
|
|
557
|
+
| <!-- TODO --> | | | | |
|
|
558
|
+
|
|
559
|
+
| Status | Response |
|
|
560
|
+
|--------|----------|
|
|
561
|
+
| 200 | Success |
|
|
562
|
+
| 400 | Bad Request |
|
|
563
|
+
| 401 | Unauthorized |
|
|
564
|
+
`).join('\n');
|
|
565
|
+
|
|
566
|
+
return `### ${resource.charAt(0).toUpperCase() + resource.slice(1)}
|
|
567
|
+
|
|
568
|
+
${routeDetails}`;
|
|
569
|
+
}).join('\n---\n\n');
|
|
570
|
+
|
|
571
|
+
const content = `# API Reference
|
|
572
|
+
|
|
573
|
+
<!-- docguard:version 0.1.0 -->
|
|
574
|
+
<!-- docguard:status draft -->
|
|
575
|
+
<!-- docguard:last-reviewed ${new Date().toISOString().split('T')[0]} -->
|
|
576
|
+
<!-- docguard:generated true -->
|
|
577
|
+
|
|
578
|
+
> **Auto-generated by DocGuard.** Review and refine this document.
|
|
579
|
+
|
|
580
|
+
| Metadata | Value |
|
|
581
|
+
|----------|-------|
|
|
582
|
+
| **Status** |  |
|
|
583
|
+
| **Base URL** | \`http://localhost:3000\` |
|
|
584
|
+
| **Auth** | <!-- TODO: Describe auth mechanism --> |
|
|
585
|
+
| **Total Endpoints** | ${deepRoutes.length} |
|
|
586
|
+
| **Source** | ${deepRoutes[0]?.source || 'code scan'} |
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## Endpoints Summary
|
|
591
|
+
|
|
592
|
+
| Method | Path | Handler | Auth | Description |
|
|
593
|
+
|--------|------|---------|:----:|-------------|
|
|
594
|
+
${endpointRows}
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
## Endpoint Details
|
|
599
|
+
|
|
600
|
+
${resourceSections}
|
|
353
601
|
|
|
354
602
|
---
|
|
355
603
|
|
|
@@ -357,49 +605,101 @@ graph TD
|
|
|
357
605
|
|
|
358
606
|
| Version | Date | Author | Changes |
|
|
359
607
|
|---------|------|--------|---------|
|
|
360
|
-
| 0.1.0 | ${new Date().toISOString().split('T')[0]} | DocGuard Generate | Auto-generated from
|
|
608
|
+
| 0.1.0 | ${new Date().toISOString().split('T')[0]} | DocGuard Generate | Auto-generated (${deepRoutes.length} endpoints from ${deepRoutes[0]?.source || 'code'}) |
|
|
361
609
|
`;
|
|
362
610
|
|
|
363
611
|
writeFileSync(path, content, 'utf-8');
|
|
364
|
-
console.log(` ${c.green}✅
|
|
612
|
+
console.log(` ${c.green}✅ API-REFERENCE.md${c.reset} (${deepRoutes.length} endpoints, ${Object.keys(groups).length} resources)`);
|
|
365
613
|
return true;
|
|
366
614
|
}
|
|
367
615
|
|
|
368
|
-
|
|
616
|
+
// ── Enhanced Data Model Generator ──────────────────────────────────────────
|
|
617
|
+
|
|
618
|
+
function generateDataModel(dir, config, stack, scan, flags, deepSchemas) {
|
|
369
619
|
const path = resolve(dir, 'docs-canonical/DATA-MODEL.md');
|
|
370
620
|
if (existsSync(path) && !flags.force) {
|
|
371
621
|
console.log(` ${c.dim}⏭️ DATA-MODEL.md (exists)${c.reset}`);
|
|
372
622
|
return false;
|
|
373
623
|
}
|
|
374
624
|
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (name !== 'index' && name !== 'schema') {
|
|
380
|
-
entities.push({
|
|
381
|
-
name: name.charAt(0).toUpperCase() + name.slice(1),
|
|
382
|
-
file: modelFile,
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
}
|
|
625
|
+
// Use deep schemas if available, fallback to basic scan
|
|
626
|
+
let entities = [];
|
|
627
|
+
let relationships = [];
|
|
628
|
+
let schemaSource = 'file scan';
|
|
386
629
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
630
|
+
if (deepSchemas && deepSchemas.entities.length > 0) {
|
|
631
|
+
entities = deepSchemas.entities;
|
|
632
|
+
relationships = deepSchemas.relationships;
|
|
633
|
+
schemaSource = deepSchemas.source;
|
|
634
|
+
} else {
|
|
635
|
+
// Fallback: basic entity detection from file names
|
|
636
|
+
for (const modelFile of scan.models) {
|
|
637
|
+
const name = basename(modelFile, extname(modelFile));
|
|
638
|
+
if (name !== 'index' && name !== 'schema') {
|
|
639
|
+
entities.push({
|
|
640
|
+
name: name.charAt(0).toUpperCase() + name.slice(1),
|
|
641
|
+
fields: [],
|
|
642
|
+
file: modelFile,
|
|
643
|
+
source: 'file',
|
|
644
|
+
});
|
|
396
645
|
}
|
|
397
646
|
}
|
|
398
647
|
}
|
|
399
648
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
649
|
+
// Build entity summary table
|
|
650
|
+
const entityRows = entities
|
|
651
|
+
.filter(e => e.source !== 'prisma-enum')
|
|
652
|
+
.map(e => {
|
|
653
|
+
const pk = e.fields?.find(f => f.primaryKey);
|
|
654
|
+
return `| ${e.name} | ${stack.database || 'TBD'} | ${pk ? pk.name : e.name.toLowerCase() + 'Id'} | ${e.file || '—'} | ${e.fields?.length || 0} fields |`;
|
|
655
|
+
}).join('\n');
|
|
656
|
+
|
|
657
|
+
// Build detailed entity sections
|
|
658
|
+
const entitySections = entities
|
|
659
|
+
.filter(e => e.source !== 'prisma-enum')
|
|
660
|
+
.map(e => {
|
|
661
|
+
if (!e.fields || e.fields.length === 0) {
|
|
662
|
+
return `### ${e.name}
|
|
663
|
+
|
|
664
|
+
> Source: \`${e.file || 'unknown'}\`
|
|
665
|
+
|
|
666
|
+
| Field | Type | Required | Default | Constraints | Description |
|
|
667
|
+
|-------|------|----------|---------|-------------|-------------|
|
|
668
|
+
| <!-- TODO: Fill in fields --> | | | | | |
|
|
669
|
+
`;
|
|
670
|
+
}
|
|
671
|
+
const fieldRows = e.fields.map(f =>
|
|
672
|
+
`| ${f.name} | ${f.type} | ${f.required ? '✓' : '✗'} | ${f.default || '—'} | ${f.primaryKey ? 'PK' : ''}${f.unique ? ' UK' : ''} | ${f.description || ''} |`
|
|
673
|
+
).join('\n');
|
|
674
|
+
|
|
675
|
+
return `### ${e.name}
|
|
676
|
+
|
|
677
|
+
> Source: \`${e.file || 'unknown'}\` (${e.source || 'detected'})
|
|
678
|
+
|
|
679
|
+
| Field | Type | Required | Default | Constraints | Description |
|
|
680
|
+
|-------|------|:--------:|---------|-------------|-------------|
|
|
681
|
+
${fieldRows}
|
|
682
|
+
`;
|
|
683
|
+
}).join('\n');
|
|
684
|
+
|
|
685
|
+
// Build enum sections (if Prisma enums found)
|
|
686
|
+
const enums = entities.filter(e => e.source === 'prisma-enum');
|
|
687
|
+
const enumSection = enums.length > 0 ? `## Enums
|
|
688
|
+
|
|
689
|
+
${enums.map(e => `### ${e.name}
|
|
690
|
+
|
|
691
|
+
| Value |
|
|
692
|
+
|-------|
|
|
693
|
+
${e.fields.map(f => `| ${f.name} |`).join('\n')}
|
|
694
|
+
`).join('\n')}` : '';
|
|
695
|
+
|
|
696
|
+
// Build relationship table
|
|
697
|
+
const relRows = relationships.length > 0
|
|
698
|
+
? relationships.map(r => `| ${r.from} | ${r.to} | ${r.type} | ${r.field} | — |`).join('\n')
|
|
699
|
+
: '| <!-- No relationships detected --> | | | | |';
|
|
700
|
+
|
|
701
|
+
// Generate mermaid ER diagram
|
|
702
|
+
const erDiagram = generateERDiagram(entities, relationships);
|
|
403
703
|
|
|
404
704
|
const content = `# Data Model
|
|
405
705
|
|
|
@@ -416,35 +716,43 @@ function generateDataModel(dir, config, stack, scan, flags) {
|
|
|
416
716
|
| **Version** | \`0.1.0\` |
|
|
417
717
|
| **Database** | ${stack.database || 'TBD'} |
|
|
418
718
|
| **ORM** | ${stack.orm || 'None detected'} |
|
|
719
|
+
| **Schema Source** | ${schemaSource} |
|
|
720
|
+
| **Entities** | ${entities.filter(e => e.source !== 'prisma-enum').length} |
|
|
721
|
+
| **Relationships** | ${relationships.length} |
|
|
419
722
|
|
|
420
723
|
---
|
|
421
724
|
|
|
422
|
-
##
|
|
725
|
+
## Entity Summary
|
|
423
726
|
|
|
424
|
-
| Entity | Storage | Primary Key |
|
|
425
|
-
|
|
426
|
-
${entityRows || '| <!-- No models detected --> | | | |'}
|
|
727
|
+
| Entity | Storage | Primary Key | Source | Fields |
|
|
728
|
+
|--------|---------|-------------|--------|--------|
|
|
729
|
+
${entityRows || '| <!-- No models detected --> | | | | |'}
|
|
427
730
|
|
|
428
|
-
|
|
731
|
+
---
|
|
429
732
|
|
|
430
|
-
|
|
733
|
+
## Entity Details
|
|
431
734
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
| <!-- TODO: Fill in fields --> | | | | | |
|
|
435
|
-
`).join('\n')}
|
|
735
|
+
${entitySections}
|
|
736
|
+
${enumSection}
|
|
436
737
|
|
|
437
738
|
## Relationships
|
|
438
739
|
|
|
439
740
|
| From | To | Type | FK/Reference | Cascade |
|
|
440
741
|
|------|-----|------|-------------|---------|
|
|
441
|
-
|
|
742
|
+
${relRows}
|
|
743
|
+
${erDiagram ? `
|
|
744
|
+
## Entity-Relationship Diagram
|
|
745
|
+
|
|
746
|
+
\`\`\`mermaid
|
|
747
|
+
${erDiagram}
|
|
748
|
+
\`\`\`
|
|
749
|
+
` : ''}
|
|
442
750
|
|
|
443
751
|
## Indexes
|
|
444
752
|
|
|
445
753
|
| Table | Index Name | Fields | Type | Purpose |
|
|
446
754
|
|-------|-----------|--------|------|---------|
|
|
447
|
-
| <!-- TODO --> | | | | |
|
|
755
|
+
| <!-- TODO: Document indexes --> | | | | |
|
|
448
756
|
|
|
449
757
|
---
|
|
450
758
|
|
|
@@ -452,11 +760,11 @@ ${entities.map(e => `### ${e.name}
|
|
|
452
760
|
|
|
453
761
|
| Version | Date | Author | Changes |
|
|
454
762
|
|---------|------|--------|---------|
|
|
455
|
-
| 0.1.0 | ${new Date().toISOString().split('T')[0]} | DocGuard Generate | Auto-generated (${entities.length} entities
|
|
763
|
+
| 0.1.0 | ${new Date().toISOString().split('T')[0]} | DocGuard Generate | Auto-generated (${entities.length} entities, ${relationships.length} relationships from ${schemaSource}) |
|
|
456
764
|
`;
|
|
457
765
|
|
|
458
766
|
writeFileSync(path, content, 'utf-8');
|
|
459
|
-
console.log(` ${c.green}✅ DATA-MODEL.md${c.reset} (${entities.length} entities
|
|
767
|
+
console.log(` ${c.green}✅ DATA-MODEL.md${c.reset} (${entities.length} entities, ${relationships.length} relationships from ${schemaSource})`);
|
|
460
768
|
return true;
|
|
461
769
|
}
|
|
462
770
|
|
|
@@ -674,15 +982,18 @@ ${scan.envVars.filter(v => isSecretVar(v.name)).map(v =>
|
|
|
674
982
|
return true;
|
|
675
983
|
}
|
|
676
984
|
|
|
677
|
-
function generateRootFiles(dir, config, stack, scan, flags) {
|
|
985
|
+
function generateRootFiles(dir, config, stack, scan, flags, docTools) {
|
|
678
986
|
let created = 0;
|
|
679
987
|
let skipped = 0;
|
|
680
988
|
|
|
681
|
-
// AGENTS.md
|
|
989
|
+
// AGENTS.md (AGENTS.md Standard compliant)
|
|
682
990
|
const agentsPath = resolve(dir, 'AGENTS.md');
|
|
683
991
|
if (!existsSync(agentsPath) || flags.force) {
|
|
684
992
|
const content = `# AI Agent Instructions — ${config.projectName}
|
|
685
993
|
|
|
994
|
+
<!-- Standard: https://agents.md -->
|
|
995
|
+
<!-- Generated by DocGuard — AGENTS.md standard compliant -->
|
|
996
|
+
|
|
686
997
|
> This project follows **Canonical-Driven Development (CDD)**.
|
|
687
998
|
> Documentation is the source of truth. Read before coding.
|
|
688
999
|
|
|
@@ -690,10 +1001,11 @@ function generateRootFiles(dir, config, stack, scan, flags) {
|
|
|
690
1001
|
|
|
691
1002
|
1. **Read** \`docs-canonical/\` before suggesting changes
|
|
692
1003
|
2. **Check** existing patterns in the codebase
|
|
693
|
-
3. **
|
|
694
|
-
4. **
|
|
695
|
-
5. **
|
|
696
|
-
6. **
|
|
1004
|
+
3. **Run** \`npx docguard-cli diagnose\` to see what needs fixing
|
|
1005
|
+
4. **Confirm** your approach before writing code
|
|
1006
|
+
5. **Implement** matching existing code style
|
|
1007
|
+
6. **Log** any deviations in \`DRIFT-LOG.md\` with \`// DRIFT: reason\`
|
|
1008
|
+
7. **Verify** with \`npx docguard-cli guard\` — all checks must pass
|
|
697
1009
|
|
|
698
1010
|
## Project Stack
|
|
699
1011
|
|
|
@@ -703,30 +1015,68 @@ ${Object.entries(stack).filter(([, v]) => v).map(([k, v]) => `- **${k}**: ${v}`)
|
|
|
703
1015
|
|
|
704
1016
|
| File | Purpose |
|
|
705
1017
|
|------|---------|
|
|
706
|
-
| \`docs-canonical/ARCHITECTURE.md\` | System design |
|
|
707
|
-
| \`docs-canonical/
|
|
1018
|
+
| \`docs-canonical/ARCHITECTURE.md\` | System design (arc42 aligned) |
|
|
1019
|
+
| \`docs-canonical/API-REFERENCE.md\` | API endpoint documentation |
|
|
1020
|
+
| \`docs-canonical/DATA-MODEL.md\` | Database schemas & entities |
|
|
708
1021
|
| \`docs-canonical/SECURITY.md\` | Auth & secrets |
|
|
709
1022
|
| \`docs-canonical/TEST-SPEC.md\` | Test requirements |
|
|
710
1023
|
| \`docs-canonical/ENVIRONMENT.md\` | Environment setup |
|
|
1024
|
+
| \`AGENTS.md\` | AI agent instructions (this file) |
|
|
711
1025
|
| \`CHANGELOG.md\` | Change tracking |
|
|
712
1026
|
| \`DRIFT-LOG.md\` | Documented deviations |
|
|
713
1027
|
|
|
714
|
-
##
|
|
1028
|
+
## Permissions & Guardrails
|
|
1029
|
+
|
|
1030
|
+
> **IMPORTANT:** These limits apply to all AI agents working on this project.
|
|
1031
|
+
|
|
1032
|
+
### Allowed
|
|
1033
|
+
|
|
1034
|
+
- Read any file in the repository
|
|
1035
|
+
- Modify files within \`src/\`, \`tests/\`, and \`docs-canonical/\`
|
|
1036
|
+
- Run test commands (\`npm test\`, \`npx docguard-cli guard\`)
|
|
1037
|
+
- Create new files in appropriate directories
|
|
1038
|
+
|
|
1039
|
+
### Not Allowed
|
|
1040
|
+
|
|
1041
|
+
- Modify \`.env\` files or secrets
|
|
1042
|
+
- Push commits or create releases without explicit approval
|
|
1043
|
+
- Delete or rename canonical documentation files
|
|
1044
|
+
- Bypass DocGuard checks (\`docguard guard\` must pass)
|
|
1045
|
+
- Install new dependencies without approval
|
|
1046
|
+
|
|
1047
|
+
### Safety Rules
|
|
1048
|
+
|
|
1049
|
+
- Never hardcode secrets, tokens, or API keys
|
|
1050
|
+
- Always validate inputs before processing
|
|
1051
|
+
- Never expose internal paths or stack traces to users
|
|
1052
|
+
- Run \`npx docguard-cli guard\` before every commit
|
|
1053
|
+
|
|
1054
|
+
## Monorepo Support
|
|
1055
|
+
|
|
1056
|
+
<!-- If this is a monorepo, nested AGENTS.md files in subdirectories
|
|
1057
|
+
override these instructions for their scope. -->
|
|
1058
|
+
|
|
1059
|
+
| Scope | AGENTS.md Location |
|
|
1060
|
+
|-------|-------------------|
|
|
1061
|
+
| Root (default) | \`./AGENTS.md\` |
|
|
1062
|
+
| <!-- e.g. packages/api --> | <!-- packages/api/AGENTS.md --> |
|
|
1063
|
+
|
|
1064
|
+
## DocGuard Commands
|
|
715
1065
|
|
|
716
1066
|
\`\`\`bash
|
|
717
|
-
npx docguard guard # Validate compliance
|
|
718
|
-
npx docguard
|
|
719
|
-
npx docguard fix --
|
|
720
|
-
npx docguard
|
|
721
|
-
npx docguard
|
|
1067
|
+
npx docguard-cli guard # Validate compliance
|
|
1068
|
+
npx docguard-cli diagnose # Identify issues + AI fix prompts
|
|
1069
|
+
npx docguard-cli fix --doc ARCH # Fix specific document
|
|
1070
|
+
npx docguard-cli score # CDD maturity score (0-100)
|
|
1071
|
+
npx docguard-cli generate # Generate docs from code
|
|
722
1072
|
\`\`\`
|
|
723
1073
|
|
|
724
1074
|
### AI Agent Workflow (IMPORTANT)
|
|
725
1075
|
|
|
726
|
-
1. **Before work**: Run \`npx docguard guard\` — understand compliance state
|
|
727
|
-
2. **After changes**: Run \`npx docguard
|
|
1076
|
+
1. **Before work**: Run \`npx docguard-cli guard\` — understand compliance state
|
|
1077
|
+
2. **After changes**: Run \`npx docguard-cli diagnose\` — get fix instructions
|
|
728
1078
|
3. **Fix issues**: Each issue has an \`ai_instruction\` — follow it exactly
|
|
729
|
-
4. **Verify**: Run \`npx docguard guard\` again — must pass before commit
|
|
1079
|
+
4. **Verify**: Run \`npx docguard-cli guard\` again — must pass before commit
|
|
730
1080
|
5. **Update CHANGELOG**: All changes need a changelog entry
|
|
731
1081
|
|
|
732
1082
|
## Rules
|
|
@@ -738,7 +1088,7 @@ npx docguard score # CDD maturity score
|
|
|
738
1088
|
- Documentation changes must pass \`docguard guard\`
|
|
739
1089
|
`;
|
|
740
1090
|
writeFileSync(agentsPath, content, 'utf-8');
|
|
741
|
-
console.log(` ${c.green}✅ AGENTS.md${c.reset}`);
|
|
1091
|
+
console.log(` ${c.green}✅ AGENTS.md${c.reset} (AGENTS.md standard compliant)`);
|
|
742
1092
|
created++;
|
|
743
1093
|
} else {
|
|
744
1094
|
console.log(` ${c.dim}⏭️ AGENTS.md (exists)${c.reset}`);
|