create-claude-context 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,835 @@
1
+ /**
2
+ * Template Populator
3
+ *
4
+ * Takes analysis results and populates all template files
5
+ * with real, project-specific content.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { glob } = require('glob');
11
+
12
+ /**
13
+ * Slugify a string for use in filenames
14
+ * @param {string} str - String to slugify
15
+ * @returns {string}
16
+ */
17
+ function slugify(str) {
18
+ return str
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9]+/g, '-')
21
+ .replace(/^-|-$/g, '');
22
+ }
23
+
24
+ /**
25
+ * Populate all templates with analysis results
26
+ * @param {string} claudeDir - .claude directory path
27
+ * @param {object} analysis - Analysis results from static analyzer
28
+ * @param {object} config - Configuration from CLI
29
+ * @returns {Promise<object>} Results of population
30
+ */
31
+ async function populateAllTemplates(claudeDir, analysis, config) {
32
+ const results = {
33
+ populated: [],
34
+ skipped: [],
35
+ errors: [],
36
+ created: []
37
+ };
38
+
39
+ const projectRoot = path.dirname(claudeDir);
40
+ const projectName = config.projectName || path.basename(projectRoot);
41
+
42
+ // 1. Populate CLAUDE.md at project root
43
+ try {
44
+ await populateClaudeMd(projectRoot, analysis, config);
45
+ results.populated.push('CLAUDE.md');
46
+ } catch (error) {
47
+ results.errors.push({ file: 'CLAUDE.md', error: error.message });
48
+ }
49
+
50
+ // 2. Generate ARCHITECTURE_SNAPSHOT.md
51
+ try {
52
+ const content = generateArchitectureSnapshot(analysis, config);
53
+ const filePath = path.join(claudeDir, 'context', 'ARCHITECTURE_SNAPSHOT.md');
54
+ ensureDir(path.dirname(filePath));
55
+ fs.writeFileSync(filePath, content);
56
+ results.populated.push('context/ARCHITECTURE_SNAPSHOT.md');
57
+ } catch (error) {
58
+ results.errors.push({ file: 'ARCHITECTURE_SNAPSHOT.md', error: error.message });
59
+ }
60
+
61
+ // 3. Generate WORKFLOW_INDEX.md
62
+ try {
63
+ const content = generateWorkflowIndex(analysis, config);
64
+ const filePath = path.join(claudeDir, 'context', 'WORKFLOW_INDEX.md');
65
+ ensureDir(path.dirname(filePath));
66
+ fs.writeFileSync(filePath, content);
67
+ results.populated.push('context/WORKFLOW_INDEX.md');
68
+ } catch (error) {
69
+ results.errors.push({ file: 'WORKFLOW_INDEX.md', error: error.message });
70
+ }
71
+
72
+ // 4. Generate CODE_TO_WORKFLOW_MAP.md
73
+ try {
74
+ const content = generateCodeToWorkflowMap(analysis, config);
75
+ const filePath = path.join(claudeDir, 'context', 'CODE_TO_WORKFLOW_MAP.md');
76
+ ensureDir(path.dirname(filePath));
77
+ fs.writeFileSync(filePath, content);
78
+ results.populated.push('context/CODE_TO_WORKFLOW_MAP.md');
79
+ } catch (error) {
80
+ results.errors.push({ file: 'CODE_TO_WORKFLOW_MAP.md', error: error.message });
81
+ }
82
+
83
+ // 5. Generate individual workflow files
84
+ const workflowsDir = path.join(claudeDir, 'context', 'workflows');
85
+ ensureDir(workflowsDir);
86
+
87
+ for (const workflow of (analysis.workflows || [])) {
88
+ try {
89
+ const content = generateWorkflowFile(workflow, analysis, config);
90
+ const filename = `${slugify(workflow.name)}.md`;
91
+ const filePath = path.join(workflowsDir, filename);
92
+ fs.writeFileSync(filePath, content);
93
+ results.created.push(`context/workflows/${filename}`);
94
+ } catch (error) {
95
+ results.errors.push({ file: `workflow:${workflow.name}`, error: error.message });
96
+ }
97
+ }
98
+
99
+ // 6. Update category indexes
100
+ try {
101
+ await updateCategoryIndexes(claudeDir, analysis, config);
102
+ results.populated.push('indexes/workflows/CATEGORY_INDEX.md');
103
+ } catch (error) {
104
+ results.errors.push({ file: 'CATEGORY_INDEX.md', error: error.message });
105
+ }
106
+
107
+ return results;
108
+ }
109
+
110
+ /**
111
+ * Ensure directory exists
112
+ * @param {string} dirPath - Directory path
113
+ */
114
+ function ensureDir(dirPath) {
115
+ if (!fs.existsSync(dirPath)) {
116
+ fs.mkdirSync(dirPath, { recursive: true });
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Populate CLAUDE.md with real project info
122
+ * @param {string} projectRoot - Project root directory
123
+ * @param {object} analysis - Analysis results
124
+ * @param {object} config - Configuration
125
+ */
126
+ async function populateClaudeMd(projectRoot, analysis, config) {
127
+ const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
128
+
129
+ if (!fs.existsSync(claudeMdPath)) {
130
+ return;
131
+ }
132
+
133
+ let content = fs.readFileSync(claudeMdPath, 'utf-8');
134
+ const projectName = config.projectName || path.basename(projectRoot);
135
+
136
+ // Build replacement map from analysis
137
+ const replacements = {
138
+ '{{PROJECT_NAME}}': projectName,
139
+ '{{PROJECT_DESCRIPTION}}': `${projectName} application`,
140
+ '{{TECH_STACK}}': analysis.techStack?.summary || config.techStack?.summary || 'Unknown',
141
+ '{{PROJECT_STATUS}}': 'Development',
142
+ '{{PRODUCTION_URL}}': `https://${slugify(projectName)}.example.com`,
143
+ '{{API_URL}}': `https://api.${slugify(projectName)}.example.com`,
144
+ '{{REPO_URL}}': `https://github.com/user/${slugify(projectName)}`,
145
+ '{{DEPLOYMENT_PLATFORM}}': detectDeploymentPlatform(projectRoot),
146
+ '{{DATE}}': new Date().toISOString().split('T')[0],
147
+
148
+ // Commands
149
+ '{{INSTALL_COMMAND}}': getInstallCommand(analysis.techStack || config.techStack),
150
+ '{{DEV_START_COMMAND}}': getDevCommand(analysis.techStack || config.techStack),
151
+ '{{TEST_COMMAND}}': getTestCommand(analysis.techStack || config.techStack),
152
+ '{{TEST_E2E_COMMAND}}': 'npm run test:e2e',
153
+ '{{TEST_COVERAGE_COMMAND}}': 'npm run test:coverage',
154
+ '{{MIGRATION_CREATE_COMMAND}}': getMigrationCreateCommand(analysis.techStack || config.techStack),
155
+ '{{MIGRATION_RUN_COMMAND}}': getMigrationRunCommand(analysis.techStack || config.techStack),
156
+ '{{DEPLOY_COMMAND}}': 'npm run deploy',
157
+
158
+ // Paths
159
+ '{{MODELS_PATH}}': findModelsPath(analysis),
160
+ '{{MIGRATIONS_PATH}}': findMigrationsPath(analysis),
161
+ '{{CORE_FILES_LIST}}': formatCoreFiles(analysis),
162
+
163
+ // Counts
164
+ '{{WORKFLOWS_COUNT}}': String(analysis.workflows?.length || 0),
165
+ '{{AGENTS_COUNT}}': '6',
166
+ '{{COMMANDS_COUNT}}': '11',
167
+ '{{INDEX_FILES_COUNT}}': '15',
168
+ '{{WORKFLOW_DOMAINS_COUNT}}': String(analysis.workflows?.length || 0),
169
+ '{{CODE_DOMAINS_COUNT}}': String(analysis.architecture?.layers?.length || 0),
170
+
171
+ // Architecture
172
+ '{{ARCHITECTURE_DIAGRAM}}': generateAsciiArchitecture(analysis),
173
+
174
+ // Misc
175
+ '{{EXTERNAL_INTEGRATIONS_LIST}}': formatExternalIntegrations(analysis),
176
+ '{{CONFIG_SEARCH_PATTERN}}': 'grep -r "process.env" --include="*.js" --include="*.ts"',
177
+ '{{URL_SEARCH_PATTERN}}': 'grep -rE "https?://" --include="*.js" --include="*.ts" --include="*.json"',
178
+
179
+ // Placeholders for manual filling
180
+ '{{EXAMPLE_REFACTOR_TASK}}': 'Refactor the authentication flow',
181
+ '{{EXAMPLE_LOWLEVEL_TASK}}': 'Fix hardcoded API URL in config',
182
+ '{{EXAMPLE_FEATURE_TASK}}': 'Add user notifications feature',
183
+ '{{CRITICAL_URLS}}': `- Production: https://${slugify(projectName)}.example.com`,
184
+ '{{BUSINESS_CONSTANTS}}': '- TBD (document key business constants)',
185
+ '{{DEBUGGING_QUICK_REFS}}': 'KNOWN_GOTCHAS.md, logs/',
186
+ '{{AGENT_ROUTING_TABLE}}': '@context-engineer for setup, @core-architect for design',
187
+ '{{GOTCHA_CATEGORY_1}}': 'Database',
188
+ '{{GOTCHA_1_ITEMS}}': '- TBD (document database gotchas)',
189
+ '{{GOTCHA_CATEGORY_2}}': 'API',
190
+ '{{GOTCHA_2_ITEMS}}': '- TBD (document API gotchas)',
191
+ '{{PRODUCTION_PLATFORM}}': detectDeploymentPlatform(projectRoot),
192
+ '{{PRODUCTION_SERVICES}}': 'Web, API, Database',
193
+ '{{MONITORING_COMMANDS}}': 'Check logs, health endpoints',
194
+ '{{MIGRATION_CONSTRAINTS}}': 'Always backup before migrations',
195
+ '{{TESTING_CONSTRAINTS}}': 'Run tests before merging',
196
+ '{{SECURITY_CONSTRAINTS}}': 'Never commit secrets',
197
+ '{{CONTACT_INFO}}': 'TBD (add contact info)'
198
+ };
199
+
200
+ // Apply replacements
201
+ for (const [placeholder, value] of Object.entries(replacements)) {
202
+ content = content.replace(new RegExp(escapeRegex(placeholder), 'g'), value);
203
+ }
204
+
205
+ fs.writeFileSync(claudeMdPath, content);
206
+ }
207
+
208
+ /**
209
+ * Escape regex special characters
210
+ * @param {string} str - String to escape
211
+ * @returns {string}
212
+ */
213
+ function escapeRegex(str) {
214
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
215
+ }
216
+
217
+ /**
218
+ * Generate ARCHITECTURE_SNAPSHOT.md content
219
+ * @param {object} analysis - Analysis results
220
+ * @param {object} config - Configuration
221
+ * @returns {string}
222
+ */
223
+ function generateArchitectureSnapshot(analysis, config) {
224
+ const projectName = config.projectName || 'Project';
225
+ const date = new Date().toISOString().split('T')[0];
226
+
227
+ return `# Architecture Snapshot - ${projectName}
228
+
229
+ **Purpose:** High-level system map for rapid orientation
230
+ **Load:** When starting a new session or onboarding
231
+ **Size:** ~10k tokens (5% of 200k budget)
232
+ **Last Updated:** ${date}
233
+
234
+ ---
235
+
236
+ ## System Overview
237
+
238
+ \`\`\`
239
+ ${generateAsciiArchitecture(analysis)}
240
+ \`\`\`
241
+
242
+ ---
243
+
244
+ ## Technology Stack
245
+
246
+ | Layer | Technology | Purpose |
247
+ |-------|------------|---------|
248
+ ${formatTechStackTable(analysis)}
249
+
250
+ ---
251
+
252
+ ## Core Components
253
+
254
+ ${formatComponents(analysis)}
255
+
256
+ ---
257
+
258
+ ## Directory Structure
259
+
260
+ \`\`\`
261
+ ${analysis.architecture?.directoryTree || 'TBD - Run AI analysis for complete structure'}
262
+ \`\`\`
263
+
264
+ ---
265
+
266
+ ## Entry Points
267
+
268
+ | Type | Count | Key Files |
269
+ |------|-------|-----------|
270
+ | API Routes | ${analysis.entryPoints?.length || 0} | ${getTopEntryPointFiles(analysis)} |
271
+ | Workflows | ${analysis.workflows?.length || 0} | See WORKFLOW_INDEX.md |
272
+
273
+ ---
274
+
275
+ ## External Integrations
276
+
277
+ ${formatExternalIntegrations(analysis)}
278
+
279
+ ---
280
+
281
+ ## Data Flow
282
+
283
+ \`\`\`
284
+ Request → Router → Controller → Service → Repository → Database
285
+
286
+ External APIs
287
+ \`\`\`
288
+
289
+ ---
290
+
291
+ ## Key Metrics
292
+
293
+ | Metric | Value |
294
+ |--------|-------|
295
+ | Total Source Files | ${analysis.sourceFiles || 'TBD'} |
296
+ | Lines of Code | ${analysis.linesOfCode?.total || 'TBD'} |
297
+ | Dependencies | ${analysis.dependencies?.length || 0} |
298
+ | Entry Points | ${analysis.entryPoints?.length || 0} |
299
+ | Workflows | ${analysis.workflows?.length || 0} |
300
+
301
+ ---
302
+
303
+ *Auto-generated by create-claude-context. Enhance with AI analysis for complete details.*
304
+ `;
305
+ }
306
+
307
+ /**
308
+ * Generate WORKFLOW_INDEX.md content
309
+ * @param {object} analysis - Analysis results
310
+ * @param {object} config - Configuration
311
+ * @returns {string}
312
+ */
313
+ function generateWorkflowIndex(analysis, config) {
314
+ const projectName = config.projectName || 'Project';
315
+ const date = new Date().toISOString().split('T')[0];
316
+ const workflows = analysis.workflows || [];
317
+
318
+ return `# Workflow Index - ${projectName}
319
+
320
+ **Purpose:** Master catalog of all documented workflows
321
+ **Load:** At session start for navigation
322
+ **Size:** ~15k tokens (7.5% of 200k budget)
323
+ **Last Updated:** ${date}
324
+
325
+ ---
326
+
327
+ ## Quick Navigation
328
+
329
+ | Workflow | Category | Complexity | Files | Link |
330
+ |----------|----------|------------|-------|------|
331
+ ${workflows.map(wf =>
332
+ `| ${wf.name} | ${wf.category} | ${wf.complexity} | ${wf.fileCount || wf.files?.length || 0} | [Details](./workflows/${slugify(wf.name)}.md) |`
333
+ ).join('\n') || '| *No workflows discovered* | - | - | - | - |'}
334
+
335
+ ---
336
+
337
+ ## Workflows by Category
338
+
339
+ ${formatWorkflowsByCategory(workflows)}
340
+
341
+ ---
342
+
343
+ ## Entry Points Summary
344
+
345
+ | Entry Point | Workflow | Method |
346
+ |-------------|----------|--------|
347
+ ${formatEntryPointsTable(analysis.entryPoints)}
348
+
349
+ ---
350
+
351
+ ## Cross-Reference: Files → Workflows
352
+
353
+ See [CODE_TO_WORKFLOW_MAP.md](./CODE_TO_WORKFLOW_MAP.md) for reverse lookup.
354
+
355
+ ---
356
+
357
+ ## Maintenance
358
+
359
+ - **Verification Frequency:** Weekly spot-checks, quarterly full review
360
+ - **Last Verified:** ${date}
361
+ - **Next Review:** TBD
362
+
363
+ ---
364
+
365
+ *Auto-generated by create-claude-context. Run AI analysis for complete workflow documentation.*
366
+ `;
367
+ }
368
+
369
+ /**
370
+ * Generate CODE_TO_WORKFLOW_MAP.md content
371
+ * @param {object} analysis - Analysis results
372
+ * @param {object} config - Configuration
373
+ * @returns {string}
374
+ */
375
+ function generateCodeToWorkflowMap(analysis, config) {
376
+ const projectName = config.projectName || 'Project';
377
+ const date = new Date().toISOString().split('T')[0];
378
+
379
+ // Build file to workflow mapping
380
+ const fileMap = buildFileToWorkflowMap(analysis);
381
+
382
+ return `# Code to Workflow Map - ${projectName}
383
+
384
+ **Purpose:** Reverse lookup - find documentation for any file
385
+ **Load:** When investigating a specific file
386
+ **Size:** ~20k tokens (10% of 200k budget)
387
+ **Last Updated:** ${date}
388
+
389
+ ---
390
+
391
+ ## How to Use
392
+
393
+ When you modify a file:
394
+ 1. Find it in this index
395
+ 2. Check which workflows reference it
396
+ 3. Update those workflow docs accordingly
397
+
398
+ ---
399
+
400
+ ## File Index
401
+
402
+ ${formatFileIndex(fileMap)}
403
+
404
+ ---
405
+
406
+ ## Undocumented Files
407
+
408
+ The following files are not yet mapped to workflows:
409
+
410
+ ${formatUndocumentedFiles(analysis, fileMap)}
411
+
412
+ ---
413
+
414
+ ## Update Checklist
415
+
416
+ After modifying code:
417
+
418
+ - [ ] Find affected file(s) in this index
419
+ - [ ] Update line numbers in referenced workflows
420
+ - [ ] Verify function signatures still match
421
+ - [ ] Run \`/verify-docs-current\` to validate
422
+
423
+ ---
424
+
425
+ *Auto-generated by create-claude-context. Run \`/auto-sync --rebuild-map\` to refresh.*
426
+ `;
427
+ }
428
+
429
+ /**
430
+ * Generate individual workflow file
431
+ * @param {object} workflow - Workflow data
432
+ * @param {object} analysis - Full analysis
433
+ * @param {object} config - Configuration
434
+ * @returns {string}
435
+ */
436
+ function generateWorkflowFile(workflow, analysis, config) {
437
+ const date = new Date().toISOString().split('T')[0];
438
+
439
+ // Find entry points for this workflow
440
+ const workflowEntryPoints = (analysis.entryPoints || []).filter(ep =>
441
+ workflow.files?.some(f => ep.file.includes(f) || f.includes(ep.file))
442
+ );
443
+
444
+ return `---
445
+ name: ${slugify(workflow.name)}
446
+ category: ${workflow.category || 'general'}
447
+ complexity: ${workflow.complexity || 'MEDIUM'}
448
+ last_updated: ${date}
449
+ status: discovered
450
+ ---
451
+
452
+ # ${workflow.name}
453
+
454
+ ## Overview
455
+
456
+ **Purpose:** ${getWorkflowPurpose(workflow)}
457
+ **Complexity:** ${workflow.complexity || 'MEDIUM'}
458
+ **Confidence:** ${workflow.confidence || 0}%
459
+ **Files Involved:** ${workflow.fileCount || workflow.files?.length || 0}
460
+
461
+ ---
462
+
463
+ ## Entry Points
464
+
465
+ ${workflowEntryPoints.length > 0 ? formatWorkflowEntryPoints(workflowEntryPoints) : '*Entry points to be documented during AI analysis*'}
466
+
467
+ ---
468
+
469
+ ## Key Files
470
+
471
+ | File | Purpose |
472
+ |------|---------|
473
+ ${(workflow.files || []).slice(0, 20).map(f => `| \`${f}\` | TBD |`).join('\n') || '| *No files mapped* | - |'}
474
+
475
+ ---
476
+
477
+ ## Call Chain
478
+
479
+ \`\`\`
480
+ ${workflow.name.toLowerCase().replace(/\s+/g, '_')}()
481
+ ├─ [To be traced during AI analysis]
482
+ └─ [To be traced during AI analysis]
483
+ \`\`\`
484
+
485
+ ---
486
+
487
+ ## Database Operations
488
+
489
+ *Database operations to be documented during AI analysis*
490
+
491
+ ---
492
+
493
+ ## External Dependencies
494
+
495
+ *External dependencies to be documented during AI analysis*
496
+
497
+ ---
498
+
499
+ ## Test Coverage
500
+
501
+ | Test File | Type | Coverage |
502
+ |-----------|------|----------|
503
+ | *TBD* | - | - |
504
+
505
+ ---
506
+
507
+ ## Known Gotchas
508
+
509
+ *No gotchas documented yet. Add as discovered.*
510
+
511
+ ---
512
+
513
+ ## Maintenance
514
+
515
+ - **Last Verified:** ${date}
516
+ - **Verification Method:** Auto-discovered
517
+ - **Next Review:** TBD
518
+
519
+ ---
520
+
521
+ *Auto-generated by create-claude-context. Enhance with AI analysis for complete documentation.*
522
+ `;
523
+ }
524
+
525
+ /**
526
+ * Update category indexes
527
+ * @param {string} claudeDir - .claude directory path
528
+ * @param {object} analysis - Analysis results
529
+ * @param {object} config - Configuration
530
+ */
531
+ async function updateCategoryIndexes(claudeDir, analysis, config) {
532
+ const indexPath = path.join(claudeDir, 'indexes', 'workflows', 'CATEGORY_INDEX.md');
533
+ ensureDir(path.dirname(indexPath));
534
+
535
+ const workflows = analysis.workflows || [];
536
+ const categories = [...new Set(workflows.map(w => w.category))];
537
+ const date = new Date().toISOString().split('T')[0];
538
+
539
+ const content = `# Workflow Category Index
540
+
541
+ **Purpose:** Quick navigation to workflow categories
542
+ **Load:** At session start
543
+ **Last Updated:** ${date}
544
+
545
+ ---
546
+
547
+ ## Categories
548
+
549
+ ${categories.map(cat => {
550
+ const catWorkflows = workflows.filter(w => w.category === cat);
551
+ return `### ${cat.charAt(0).toUpperCase() + cat.slice(1)}
552
+
553
+ | Workflow | Complexity | Link |
554
+ |----------|------------|------|
555
+ ${catWorkflows.map(w => `| ${w.name} | ${w.complexity} | [Details](../../context/workflows/${slugify(w.name)}.md) |`).join('\n')}
556
+ `;
557
+ }).join('\n') || '*No workflows discovered yet*'}
558
+
559
+ ---
560
+
561
+ ## Summary
562
+
563
+ | Category | Count |
564
+ |----------|-------|
565
+ ${categories.map(cat => `| ${cat} | ${workflows.filter(w => w.category === cat).length} |`).join('\n') || '| - | 0 |'}
566
+
567
+ ---
568
+
569
+ *Auto-generated by create-claude-context*
570
+ `;
571
+
572
+ fs.writeFileSync(indexPath, content);
573
+ }
574
+
575
+ // Helper functions
576
+
577
+ function getInstallCommand(techStack) {
578
+ const languages = techStack?.languages || [];
579
+ if (languages.includes('python')) return 'pip install -r requirements.txt';
580
+ if (languages.includes('go')) return 'go mod download';
581
+ if (languages.includes('rust')) return 'cargo build';
582
+ if (languages.includes('ruby')) return 'bundle install';
583
+ return 'npm install';
584
+ }
585
+
586
+ function getDevCommand(techStack) {
587
+ const languages = techStack?.languages || [];
588
+ if (languages.includes('python')) return 'python main.py';
589
+ if (languages.includes('go')) return 'go run .';
590
+ if (languages.includes('rust')) return 'cargo run';
591
+ if (languages.includes('ruby')) return 'rails server';
592
+ return 'npm run dev';
593
+ }
594
+
595
+ function getTestCommand(techStack) {
596
+ const languages = techStack?.languages || [];
597
+ if (languages.includes('python')) return 'pytest';
598
+ if (languages.includes('go')) return 'go test ./...';
599
+ if (languages.includes('rust')) return 'cargo test';
600
+ if (languages.includes('ruby')) return 'rspec';
601
+ return 'npm test';
602
+ }
603
+
604
+ function getMigrationCreateCommand(techStack) {
605
+ const languages = techStack?.languages || [];
606
+ if (languages.includes('python')) return 'alembic revision --autogenerate -m "description"';
607
+ if (languages.includes('ruby')) return 'rails generate migration MigrationName';
608
+ return 'npm run migration:create';
609
+ }
610
+
611
+ function getMigrationRunCommand(techStack) {
612
+ const languages = techStack?.languages || [];
613
+ if (languages.includes('python')) return 'alembic upgrade head';
614
+ if (languages.includes('ruby')) return 'rails db:migrate';
615
+ return 'npm run migration:run';
616
+ }
617
+
618
+ function detectDeploymentPlatform(projectRoot) {
619
+ if (fs.existsSync(path.join(projectRoot, 'vercel.json'))) return 'Vercel';
620
+ if (fs.existsSync(path.join(projectRoot, 'netlify.toml'))) return 'Netlify';
621
+ if (fs.existsSync(path.join(projectRoot, 'Dockerfile'))) return 'Docker';
622
+ if (fs.existsSync(path.join(projectRoot, 'fly.toml'))) return 'Fly.io';
623
+ if (fs.existsSync(path.join(projectRoot, 'render.yaml'))) return 'Render';
624
+ if (fs.existsSync(path.join(projectRoot, 'railway.json'))) return 'Railway';
625
+ return 'TBD';
626
+ }
627
+
628
+ function findModelsPath(analysis) {
629
+ const layers = analysis.architecture?.layers || [];
630
+ const domainLayer = layers.find(l => l.name === 'domain');
631
+ if (domainLayer?.directories?.length > 0) {
632
+ return domainLayer.directories[0] + '/';
633
+ }
634
+ return 'models/';
635
+ }
636
+
637
+ function findMigrationsPath(analysis) {
638
+ return 'migrations/';
639
+ }
640
+
641
+ function formatCoreFiles(analysis) {
642
+ const entryPoints = analysis.entryPoints || [];
643
+ const uniqueFiles = [...new Set(entryPoints.map(ep => ep.file))].slice(0, 5);
644
+ return uniqueFiles.map(f => `- \`${f}\``).join('\n') || '- TBD';
645
+ }
646
+
647
+ function generateAsciiArchitecture(analysis) {
648
+ const layers = analysis.architecture?.layers || [];
649
+
650
+ if (layers.length === 0) {
651
+ return `┌─────────────────────────────────────┐
652
+ │ [Application] │
653
+ │ │
654
+ │ ┌───────────┐ ┌───────────┐ │
655
+ │ │ API │ │ Services │ │
656
+ │ └───────────┘ └───────────┘ │
657
+ │ │
658
+ │ ┌───────────┐ ┌───────────┐ │
659
+ │ │ Models │ │ Database │ │
660
+ │ └───────────┘ └───────────┘ │
661
+ └─────────────────────────────────────┘`;
662
+ }
663
+
664
+ let diagram = '┌─────────────────────────────────────┐\n';
665
+ for (const layer of layers.slice(0, 4)) {
666
+ diagram += `│ [${layer.name.padEnd(30)}] │\n`;
667
+ diagram += `│ ${layer.directories?.slice(0, 2).join(', ').padEnd(32)}│\n`;
668
+ }
669
+ diagram += '└─────────────────────────────────────┘';
670
+
671
+ return diagram;
672
+ }
673
+
674
+ function formatTechStackTable(analysis) {
675
+ const techStack = analysis.techStack || {};
676
+ const rows = [];
677
+
678
+ if (techStack.languages?.length > 0) {
679
+ rows.push(`| **Languages** | ${techStack.languages.join(', ')} | Core development |`);
680
+ }
681
+ if (techStack.frameworks?.length > 0) {
682
+ rows.push(`| **Frameworks** | ${techStack.frameworks.join(', ')} | Application framework |`);
683
+ }
684
+ if (techStack.databases?.length > 0) {
685
+ rows.push(`| **Databases** | ${techStack.databases.join(', ')} | Data persistence |`);
686
+ }
687
+
688
+ return rows.join('\n') || '| *TBD* | - | - |';
689
+ }
690
+
691
+ function formatComponents(analysis) {
692
+ const layers = analysis.architecture?.layers || [];
693
+ return layers.map(layer => `
694
+ ### ${layer.name.charAt(0).toUpperCase() + layer.name.slice(1)}
695
+
696
+ **Purpose:** ${layer.purpose || 'TBD'}
697
+ **Directories:** ${layer.directories?.join(', ') || 'TBD'}
698
+ `).join('\n') || '*Components will be documented during AI analysis*';
699
+ }
700
+
701
+ function getTopEntryPointFiles(analysis) {
702
+ const entryPoints = analysis.entryPoints || [];
703
+ const files = [...new Set(entryPoints.map(ep => ep.file))].slice(0, 3);
704
+ return files.join(', ') || 'TBD';
705
+ }
706
+
707
+ function formatExternalIntegrations(analysis) {
708
+ const deps = analysis.dependencies || [];
709
+ const integrations = deps.filter(d =>
710
+ ['stripe', 'aws', 'firebase', 'twilio', 'sendgrid', 'redis', 'elasticsearch'].some(
711
+ int => d.name.toLowerCase().includes(int)
712
+ )
713
+ );
714
+
715
+ if (integrations.length === 0) {
716
+ return '*No external integrations detected. Document manually if present.*';
717
+ }
718
+
719
+ return integrations.map(i => `- **${i.name}** (${i.version})`).join('\n');
720
+ }
721
+
722
+ function formatWorkflowsByCategory(workflows) {
723
+ const categories = [...new Set(workflows.map(w => w.category))];
724
+
725
+ return categories.map(cat => {
726
+ const catWorkflows = workflows.filter(w => w.category === cat);
727
+ return `### ${cat.charAt(0).toUpperCase() + cat.slice(1)}
728
+
729
+ ${catWorkflows.map(w => `- [${w.name}](./workflows/${slugify(w.name)}.md) - ${w.complexity}`).join('\n')}
730
+ `;
731
+ }).join('\n') || '*No workflows by category*';
732
+ }
733
+
734
+ function formatEntryPointsTable(entryPoints) {
735
+ if (!entryPoints || entryPoints.length === 0) {
736
+ return '| *TBD* | - | - |';
737
+ }
738
+
739
+ return entryPoints.slice(0, 20).map(ep =>
740
+ `| \`${ep.file}:${ep.line}\` | ${ep.route || 'TBD'} | ${ep.method || '-'} |`
741
+ ).join('\n');
742
+ }
743
+
744
+ function buildFileToWorkflowMap(analysis) {
745
+ const map = {};
746
+ const workflows = analysis.workflows || [];
747
+
748
+ for (const workflow of workflows) {
749
+ for (const file of (workflow.files || [])) {
750
+ if (!map[file]) map[file] = [];
751
+ map[file].push(workflow.name);
752
+ }
753
+ }
754
+
755
+ return map;
756
+ }
757
+
758
+ function formatFileIndex(fileMap) {
759
+ const entries = Object.entries(fileMap);
760
+
761
+ if (entries.length === 0) {
762
+ return '*No files mapped to workflows yet. Run AI analysis to populate.*';
763
+ }
764
+
765
+ // Group by directory
766
+ const byDir = {};
767
+ for (const [file, workflows] of entries) {
768
+ const dir = path.dirname(file) || '.';
769
+ if (!byDir[dir]) byDir[dir] = [];
770
+ byDir[dir].push({ file, workflows });
771
+ }
772
+
773
+ return Object.entries(byDir).map(([dir, files]) => `
774
+ ### ${dir}/
775
+
776
+ | File | Workflows |
777
+ |------|-----------|
778
+ ${files.map(f => `| \`${path.basename(f.file)}\` | ${f.workflows.join(', ')} |`).join('\n')}
779
+ `).join('\n');
780
+ }
781
+
782
+ function formatUndocumentedFiles(analysis, fileMap) {
783
+ const mappedFiles = new Set(Object.keys(fileMap));
784
+ const allEntryFiles = (analysis.entryPoints || []).map(ep => ep.file);
785
+ const unmapped = allEntryFiles.filter(f => !mappedFiles.has(f));
786
+
787
+ if (unmapped.length === 0) {
788
+ return '*All entry point files are mapped to workflows.*';
789
+ }
790
+
791
+ return unmapped.slice(0, 20).map(f => `- \`${f}\``).join('\n');
792
+ }
793
+
794
+ function getWorkflowPurpose(workflow) {
795
+ const purposes = {
796
+ authentication: 'Handles user authentication and session management',
797
+ userManagement: 'Manages user accounts, profiles, and preferences',
798
+ payments: 'Processes payments, billing, and subscriptions',
799
+ dataProcessing: 'Handles background jobs and data pipelines',
800
+ apiEndpoints: 'Exposes API endpoints for external consumption',
801
+ database: 'Manages database operations and queries',
802
+ notifications: 'Sends notifications via email, SMS, or push',
803
+ fileHandling: 'Handles file uploads, storage, and downloads',
804
+ search: 'Provides search functionality across the application',
805
+ analytics: 'Tracks metrics and generates analytics',
806
+ testing: 'Contains test suites and fixtures',
807
+ configuration: 'Manages application configuration'
808
+ };
809
+
810
+ return purposes[workflow.type] || `Handles ${workflow.name.toLowerCase()} functionality`;
811
+ }
812
+
813
+ function formatWorkflowEntryPoints(entryPoints) {
814
+ return entryPoints.slice(0, 10).map(ep => `
815
+ ### \`${ep.file}:${ep.line}\`
816
+
817
+ **Route:** ${ep.route || 'N/A'}
818
+ **Method:** ${ep.method || 'N/A'}
819
+
820
+ \`\`\`
821
+ ${ep.context || 'No context available'}
822
+ \`\`\`
823
+ `).join('\n');
824
+ }
825
+
826
+ module.exports = {
827
+ populateAllTemplates,
828
+ populateClaudeMd,
829
+ generateArchitectureSnapshot,
830
+ generateWorkflowIndex,
831
+ generateCodeToWorkflowMap,
832
+ generateWorkflowFile,
833
+ updateCategoryIndexes,
834
+ slugify
835
+ };