create-universal-ai-context 2.4.0 → 2.6.0-final

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 (153) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +331 -294
  3. package/bin/create-ai-context.js +1507 -764
  4. package/lib/adapters/aider.js +131 -131
  5. package/lib/adapters/antigravity.js +205 -205
  6. package/lib/adapters/claude.js +397 -397
  7. package/lib/adapters/cline.js +125 -125
  8. package/lib/adapters/continue.js +138 -138
  9. package/lib/adapters/copilot.js +131 -131
  10. package/lib/adapters/index.js +78 -78
  11. package/lib/adapters/windsurf.js +138 -138
  12. package/lib/ai-context-generator.js +234 -234
  13. package/lib/ai-orchestrator.js +432 -432
  14. package/lib/call-tracer.js +444 -444
  15. package/lib/content-preservation.js +243 -243
  16. package/lib/cross-tool-sync/file-watcher.js +274 -274
  17. package/lib/cross-tool-sync/index.js +41 -40
  18. package/lib/cross-tool-sync/sync-manager.js +540 -512
  19. package/lib/cross-tool-sync/sync-service.js +297 -297
  20. package/lib/detector.js +726 -726
  21. package/lib/doc-discovery.js +741 -741
  22. package/lib/drift-checker.js +920 -920
  23. package/lib/environment-detector.js +239 -239
  24. package/lib/index.js +399 -399
  25. package/lib/install-hooks.js +82 -82
  26. package/lib/installer.js +419 -419
  27. package/lib/migrate.js +328 -328
  28. package/lib/placeholder.js +632 -632
  29. package/lib/prompts.js +341 -341
  30. package/lib/smart-merge.js +540 -540
  31. package/lib/spinner.js +60 -60
  32. package/lib/static-analyzer.js +729 -729
  33. package/lib/template-coordination.js +148 -148
  34. package/lib/template-populator.js +843 -843
  35. package/lib/template-renderer.js +392 -392
  36. package/lib/utils/fs-wrapper.js +79 -79
  37. package/lib/utils/path-utils.js +60 -60
  38. package/lib/validate.js +155 -155
  39. package/package.json +1 -1
  40. package/templates/AI_CONTEXT.md.template +245 -245
  41. package/templates/base/README.md +260 -257
  42. package/templates/base/RPI_WORKFLOW_PLAN.md +325 -320
  43. package/templates/base/agents/api-developer.md +76 -76
  44. package/templates/base/agents/context-engineer.md +525 -525
  45. package/templates/base/agents/core-architect.md +76 -76
  46. package/templates/base/agents/database-ops.md +76 -76
  47. package/templates/base/agents/deployment-ops.md +76 -76
  48. package/templates/base/agents/integration-hub.md +76 -76
  49. package/templates/base/analytics/README.md +114 -114
  50. package/templates/base/automation/config.json +58 -58
  51. package/templates/base/automation/generators/code-mapper.js +308 -308
  52. package/templates/base/automation/generators/index-builder.js +321 -321
  53. package/templates/base/automation/hooks/post-commit.sh +83 -83
  54. package/templates/base/automation/hooks/pre-commit.sh +103 -103
  55. package/templates/base/ci-templates/README.md +108 -108
  56. package/templates/base/ci-templates/github-actions/context-check.yml +144 -144
  57. package/templates/base/ci-templates/github-actions/validate-docs.yml +105 -105
  58. package/templates/base/commands/analytics.md +238 -238
  59. package/templates/base/commands/auto-sync.md +172 -172
  60. package/templates/base/commands/collab.md +194 -194
  61. package/templates/base/commands/context-optimize.md +226 -0
  62. package/templates/base/commands/help.md +485 -450
  63. package/templates/base/commands/rpi-implement.md +164 -115
  64. package/templates/base/commands/rpi-plan.md +147 -93
  65. package/templates/base/commands/rpi-research.md +145 -88
  66. package/templates/base/commands/session-resume.md +144 -144
  67. package/templates/base/commands/session-save.md +112 -112
  68. package/templates/base/commands/validate-all.md +77 -77
  69. package/templates/base/commands/verify-docs-current.md +86 -86
  70. package/templates/base/config/base.json +57 -57
  71. package/templates/base/config/environments/development.json +13 -13
  72. package/templates/base/config/environments/production.json +17 -17
  73. package/templates/base/config/environments/staging.json +13 -13
  74. package/templates/base/config/local.json.example +21 -21
  75. package/templates/base/context/.meta/generated-at.json +18 -18
  76. package/templates/base/context/ARCHITECTURE_SNAPSHOT.md +156 -156
  77. package/templates/base/context/CODE_TO_WORKFLOW_MAP.md +94 -94
  78. package/templates/base/context/FILE_OWNERSHIP.md +57 -57
  79. package/templates/base/context/INTEGRATION_POINTS.md +92 -92
  80. package/templates/base/context/KNOWN_GOTCHAS.md +195 -195
  81. package/templates/base/context/TESTING_MAP.md +95 -95
  82. package/templates/base/context/WORKFLOW_INDEX.md +129 -129
  83. package/templates/base/context/workflows/WORKFLOW_TEMPLATE.md +294 -294
  84. package/templates/base/indexes/agents/CAPABILITY_MATRIX.md +255 -255
  85. package/templates/base/indexes/agents/CATEGORY_INDEX.md +44 -44
  86. package/templates/base/indexes/code/CATEGORY_INDEX.md +38 -38
  87. package/templates/base/indexes/routing/CATEGORY_INDEX.md +39 -39
  88. package/templates/base/indexes/search/CATEGORY_INDEX.md +39 -39
  89. package/templates/base/indexes/workflows/CATEGORY_INDEX.md +38 -38
  90. package/templates/base/knowledge/README.md +98 -98
  91. package/templates/base/knowledge/sessions/README.md +88 -88
  92. package/templates/base/knowledge/sessions/TEMPLATE.md +150 -150
  93. package/templates/base/knowledge/shared/decisions/0001-adopt-context-engineering.md +144 -144
  94. package/templates/base/knowledge/shared/decisions/README.md +49 -49
  95. package/templates/base/knowledge/shared/decisions/TEMPLATE.md +123 -123
  96. package/templates/base/knowledge/shared/patterns/README.md +62 -62
  97. package/templates/base/knowledge/shared/patterns/TEMPLATE.md +120 -120
  98. package/templates/base/plans/PLAN_TEMPLATE.md +316 -250
  99. package/templates/base/research/RESEARCH_TEMPLATE.md +245 -153
  100. package/templates/base/schemas/agent.schema.json +141 -141
  101. package/templates/base/schemas/anchors.schema.json +54 -54
  102. package/templates/base/schemas/automation.schema.json +93 -93
  103. package/templates/base/schemas/command.schema.json +134 -134
  104. package/templates/base/schemas/hashes.schema.json +40 -40
  105. package/templates/base/schemas/manifest.schema.json +117 -117
  106. package/templates/base/schemas/plan.schema.json +136 -136
  107. package/templates/base/schemas/research.schema.json +115 -115
  108. package/templates/base/schemas/roles.schema.json +34 -34
  109. package/templates/base/schemas/session.schema.json +77 -77
  110. package/templates/base/schemas/settings.schema.json +244 -244
  111. package/templates/base/schemas/staleness.schema.json +53 -53
  112. package/templates/base/schemas/team-config.schema.json +42 -42
  113. package/templates/base/schemas/workflow.schema.json +126 -126
  114. package/templates/base/session/checkpoints/.gitkeep +2 -2
  115. package/templates/base/session/current/state.json +20 -20
  116. package/templates/base/session/history/.gitkeep +2 -2
  117. package/templates/base/settings.json +3 -3
  118. package/templates/base/standards/COMPATIBILITY.md +219 -219
  119. package/templates/base/standards/EXTENSION_GUIDELINES.md +280 -280
  120. package/templates/base/standards/QUALITY_CHECKLIST.md +211 -211
  121. package/templates/base/standards/README.md +66 -66
  122. package/templates/base/sync/anchors.json +6 -6
  123. package/templates/base/sync/hashes.json +6 -6
  124. package/templates/base/sync/staleness.json +10 -10
  125. package/templates/base/team/README.md +168 -168
  126. package/templates/base/team/config.json +79 -79
  127. package/templates/base/team/roles.json +145 -145
  128. package/templates/base/tools/bin/claude-context.js +151 -151
  129. package/templates/base/tools/lib/anchor-resolver.js +276 -276
  130. package/templates/base/tools/lib/config-loader.js +363 -363
  131. package/templates/base/tools/lib/detector.js +350 -350
  132. package/templates/base/tools/lib/diagnose.js +206 -206
  133. package/templates/base/tools/lib/drift-detector.js +373 -373
  134. package/templates/base/tools/lib/errors.js +199 -199
  135. package/templates/base/tools/lib/index.js +36 -36
  136. package/templates/base/tools/lib/init.js +192 -192
  137. package/templates/base/tools/lib/logger.js +230 -230
  138. package/templates/base/tools/lib/placeholder.js +201 -201
  139. package/templates/base/tools/lib/session-manager.js +354 -354
  140. package/templates/base/tools/lib/validate.js +521 -521
  141. package/templates/base/tools/package.json +49 -49
  142. package/templates/handlebars/aider-config.hbs +146 -80
  143. package/templates/handlebars/antigravity.hbs +377 -377
  144. package/templates/handlebars/claude.hbs +183 -183
  145. package/templates/handlebars/cline.hbs +62 -62
  146. package/templates/handlebars/continue-config.hbs +116 -116
  147. package/templates/handlebars/copilot.hbs +130 -130
  148. package/templates/handlebars/partials/gotcha-list.hbs +11 -11
  149. package/templates/handlebars/partials/header.hbs +3 -3
  150. package/templates/handlebars/partials/workflow-summary.hbs +16 -16
  151. package/templates/handlebars/windsurf-rules.hbs +69 -69
  152. package/templates/hooks/post-commit.hbs +28 -29
  153. package/templates/hooks/pre-commit.hbs +46 -46
@@ -1,729 +1,729 @@
1
- /**
2
- * Static Analyzer
3
- *
4
- * Performs comprehensive static analysis of a codebase without AI.
5
- * Discovers entry points, workflows, architecture, and dependencies.
6
- */
7
-
8
- const fs = require('fs');
9
- const path = require('path');
10
- const { glob } = require('glob');
11
-
12
- /**
13
- * Entry point patterns by framework
14
- */
15
- const ENTRY_PATTERNS = {
16
- // Express.js
17
- express: {
18
- patterns: [
19
- /\.(get|post|put|delete|patch|all|use)\s*\(\s*['"]/gi,
20
- /router\.(get|post|put|delete|patch|all|use)\s*\(\s*['"]/gi,
21
- /app\.(get|post|put|delete|patch|all|use)\s*\(\s*['"]/gi
22
- ],
23
- filePatterns: ['**/routes/**/*.js', '**/router/**/*.js', '**/api/**/*.js', '**/controllers/**/*.js']
24
- },
25
-
26
- // FastAPI (Python)
27
- fastapi: {
28
- patterns: [
29
- /@(app|router)\.(get|post|put|delete|patch)\s*\(/gi,
30
- /@router\.api_route\s*\(/gi
31
- ],
32
- filePatterns: ['**/routes/**/*.py', '**/api/**/*.py', '**/routers/**/*.py', '**/endpoints/**/*.py']
33
- },
34
-
35
- // Next.js (App Router)
36
- nextjs: {
37
- patterns: [
38
- /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(/gi,
39
- /export\s+const\s+(GET|POST|PUT|DELETE|PATCH)\s*=/gi
40
- ],
41
- filePatterns: ['**/app/**/route.ts', '**/app/**/route.js', '**/pages/api/**/*.ts', '**/pages/api/**/*.js']
42
- },
43
-
44
- // Django
45
- django: {
46
- patterns: [
47
- /path\s*\(\s*['"]/gi,
48
- /url\s*\(\s*r?['"]/gi,
49
- /@api_view\s*\(\s*\[/gi
50
- ],
51
- filePatterns: ['**/urls.py', '**/views.py', '**/api/**/*.py']
52
- },
53
-
54
- // Rails
55
- rails: {
56
- patterns: [
57
- /(get|post|put|patch|delete|resources|resource)\s+['"]/gi,
58
- /match\s+['"]/gi
59
- ],
60
- filePatterns: ['**/config/routes.rb', '**/app/controllers/**/*.rb']
61
- },
62
-
63
- // NestJS
64
- nestjs: {
65
- patterns: [
66
- /@(Get|Post|Put|Delete|Patch|All)\s*\(/gi,
67
- /@Controller\s*\(/gi
68
- ],
69
- filePatterns: ['**/*.controller.ts', '**/controllers/**/*.ts']
70
- },
71
-
72
- // Gin (Go)
73
- gin: {
74
- patterns: [
75
- /\.(GET|POST|PUT|DELETE|PATCH|Handle)\s*\(\s*"/gi,
76
- /router\.(GET|POST|PUT|DELETE|PATCH)\s*\(/gi
77
- ],
78
- filePatterns: ['**/routes/**/*.go', '**/handlers/**/*.go', '**/api/**/*.go', '**/main.go']
79
- },
80
-
81
- // Flask (Python)
82
- flask: {
83
- patterns: [
84
- /@(app|bp|blueprint)\.(route|get|post|put|delete|patch)\s*\(/gi
85
- ],
86
- filePatterns: ['**/routes/**/*.py', '**/views/**/*.py', '**/api/**/*.py', '**/app.py']
87
- }
88
- };
89
-
90
- /**
91
- * Workflow discovery heuristics
92
- */
93
- const WORKFLOW_HEURISTICS = {
94
- authentication: {
95
- name: 'User Authentication',
96
- category: 'security',
97
- complexity: 'HIGH',
98
- keywords: ['login', 'logout', 'auth', 'authenticate', 'session', 'token', 'jwt', 'oauth', 'sso', 'password'],
99
- filePatterns: ['**/auth/**', '**/login/**', '**/session/**', '**/oauth/**'],
100
- priority: 1
101
- },
102
-
103
- userManagement: {
104
- name: 'User Management',
105
- category: 'core',
106
- complexity: 'MEDIUM',
107
- keywords: ['user', 'profile', 'account', 'registration', 'signup', 'register', 'onboarding'],
108
- filePatterns: ['**/users/**', '**/profile/**', '**/accounts/**', '**/members/**'],
109
- priority: 2
110
- },
111
-
112
- payments: {
113
- name: 'Payment Processing',
114
- category: 'core',
115
- complexity: 'HIGH',
116
- keywords: ['payment', 'stripe', 'paypal', 'invoice', 'billing', 'subscription', 'checkout', 'cart', 'order'],
117
- filePatterns: ['**/payments/**', '**/billing/**', '**/checkout/**', '**/orders/**', '**/subscriptions/**'],
118
- priority: 1
119
- },
120
-
121
- dataProcessing: {
122
- name: 'Data Processing',
123
- category: 'infrastructure',
124
- complexity: 'HIGH',
125
- keywords: ['process', 'transform', 'pipeline', 'batch', 'worker', 'job', 'queue', 'task', 'etl'],
126
- filePatterns: ['**/workers/**', '**/jobs/**', '**/tasks/**', '**/pipelines/**', '**/queues/**'],
127
- priority: 2
128
- },
129
-
130
- apiEndpoints: {
131
- name: 'API Endpoints',
132
- category: 'core',
133
- complexity: 'MEDIUM',
134
- keywords: ['api', 'endpoint', 'route', 'controller', 'handler', 'resource'],
135
- filePatterns: ['**/routes/**', '**/api/**', '**/controllers/**', '**/handlers/**'],
136
- priority: 3
137
- },
138
-
139
- database: {
140
- name: 'Database Operations',
141
- category: 'infrastructure',
142
- complexity: 'MEDIUM',
143
- keywords: ['model', 'schema', 'migration', 'repository', 'dao', 'orm', 'query', 'database'],
144
- filePatterns: ['**/models/**', '**/schemas/**', '**/migrations/**', '**/repositories/**', '**/entities/**'],
145
- priority: 2
146
- },
147
-
148
- notifications: {
149
- name: 'Notifications',
150
- category: 'features',
151
- complexity: 'MEDIUM',
152
- keywords: ['notification', 'email', 'sms', 'push', 'alert', 'message', 'mail', 'notify'],
153
- filePatterns: ['**/notifications/**', '**/emails/**', '**/mailers/**', '**/messaging/**'],
154
- priority: 3
155
- },
156
-
157
- fileHandling: {
158
- name: 'File Handling',
159
- category: 'features',
160
- complexity: 'MEDIUM',
161
- keywords: ['upload', 'download', 'file', 'storage', 's3', 'blob', 'attachment', 'media'],
162
- filePatterns: ['**/uploads/**', '**/storage/**', '**/files/**', '**/media/**'],
163
- priority: 3
164
- },
165
-
166
- search: {
167
- name: 'Search',
168
- category: 'features',
169
- complexity: 'MEDIUM',
170
- keywords: ['search', 'filter', 'query', 'elasticsearch', 'algolia', 'index', 'find'],
171
- filePatterns: ['**/search/**', '**/filters/**'],
172
- priority: 3
173
- },
174
-
175
- analytics: {
176
- name: 'Analytics',
177
- category: 'features',
178
- complexity: 'MEDIUM',
179
- keywords: ['analytics', 'tracking', 'metrics', 'stats', 'dashboard', 'report', 'insight'],
180
- filePatterns: ['**/analytics/**', '**/tracking/**', '**/metrics/**', '**/reports/**'],
181
- priority: 4
182
- },
183
-
184
- testing: {
185
- name: 'Testing',
186
- category: 'infrastructure',
187
- complexity: 'LOW',
188
- keywords: ['test', 'spec', 'mock', 'fixture', 'factory', 'stub'],
189
- filePatterns: ['**/tests/**', '**/test/**', '**/__tests__/**', '**/spec/**'],
190
- priority: 5
191
- },
192
-
193
- configuration: {
194
- name: 'Configuration',
195
- category: 'infrastructure',
196
- complexity: 'LOW',
197
- keywords: ['config', 'setting', 'env', 'constant', 'option'],
198
- filePatterns: ['**/config/**', '**/settings/**', '**/constants/**'],
199
- priority: 5
200
- }
201
- };
202
-
203
- /**
204
- * Source file extensions by language
205
- */
206
- const SOURCE_EXTENSIONS = {
207
- javascript: ['.js', '.jsx', '.mjs', '.cjs'],
208
- typescript: ['.ts', '.tsx', '.mts', '.cts'],
209
- python: ['.py', '.ipynb', '.pyw'],
210
- go: ['.go'],
211
- rust: ['.rs'],
212
- ruby: ['.rb'],
213
- java: ['.java'],
214
- csharp: ['.cs'],
215
- php: ['.php']
216
- };
217
-
218
- /**
219
- * Directories to exclude from analysis
220
- */
221
- const EXCLUDED_DIRS = [
222
- 'node_modules',
223
- '.git',
224
- 'dist',
225
- 'build',
226
- 'out',
227
- '.next',
228
- '__pycache__',
229
- '.venv',
230
- 'venv',
231
- 'vendor',
232
- 'target',
233
- '.cache',
234
- 'coverage',
235
- '.nyc_output'
236
- ];
237
-
238
- /**
239
- * Find all source files in the project
240
- * @param {string} projectRoot - Project root directory
241
- * @param {string[]} languages - Languages to include
242
- * @returns {Promise<string[]>}
243
- */
244
- async function findSourceFiles(projectRoot, languages = null) {
245
- const extensions = languages
246
- ? languages.flatMap(lang => SOURCE_EXTENSIONS[lang] || [])
247
- : Object.values(SOURCE_EXTENSIONS).flat();
248
-
249
- const pattern = `**/*{${extensions.join(',')}}`;
250
-
251
- const files = await glob(pattern, {
252
- cwd: projectRoot,
253
- nodir: true,
254
- ignore: EXCLUDED_DIRS.map(d => `**/${d}/**`)
255
- });
256
-
257
- return files;
258
- }
259
-
260
- /**
261
- * Discover entry points in the codebase
262
- * @param {string} projectRoot - Project root directory
263
- * @param {string[]} sourceFiles - List of source files
264
- * @param {object} techStack - Detected tech stack
265
- * @returns {Promise<object[]>}
266
- */
267
- async function discoverEntryPoints(projectRoot, sourceFiles, techStack) {
268
- const entryPoints = [];
269
- const frameworks = techStack.frameworks || [];
270
-
271
- // Get relevant pattern sets
272
- const patternSets = [];
273
- for (const framework of frameworks) {
274
- if (ENTRY_PATTERNS[framework]) {
275
- patternSets.push(ENTRY_PATTERNS[framework]);
276
- }
277
- }
278
-
279
- // If no specific frameworks, try all patterns
280
- if (patternSets.length === 0) {
281
- patternSets.push(...Object.values(ENTRY_PATTERNS));
282
- }
283
-
284
- for (const file of sourceFiles) {
285
- const filePath = path.join(projectRoot, file);
286
-
287
- // Check if file matches any file patterns
288
- let isRelevant = false;
289
- for (const patternSet of patternSets) {
290
- for (const fp of patternSet.filePatterns) {
291
- if (minimatch(file, fp)) {
292
- isRelevant = true;
293
- break;
294
- }
295
- }
296
- if (isRelevant) break;
297
- }
298
-
299
- // For efficiency, skip if not a likely entry point file
300
- if (!isRelevant && sourceFiles.length > 100) {
301
- continue;
302
- }
303
-
304
- try {
305
- const content = fs.readFileSync(filePath, 'utf-8');
306
- const lines = content.split('\n');
307
-
308
- for (const patternSet of patternSets) {
309
- for (const pattern of patternSet.patterns) {
310
- let match;
311
- pattern.lastIndex = 0;
312
-
313
- while ((match = pattern.exec(content)) !== null) {
314
- const lineNumber = content.substring(0, match.index).split('\n').length;
315
- const lineContent = lines[lineNumber - 1] || '';
316
-
317
- // Extract route/path from the match context
318
- const routeMatch = lineContent.match(/['"]([^'"]+)['"]/);
319
- const route = routeMatch ? routeMatch[1] : null;
320
-
321
- entryPoints.push({
322
- file,
323
- line: lineNumber,
324
- match: match[0].trim(),
325
- context: lineContent.trim(),
326
- route,
327
- method: extractMethod(match[0]),
328
- framework: Object.keys(ENTRY_PATTERNS).find(k =>
329
- ENTRY_PATTERNS[k].patterns.includes(pattern)
330
- ) || 'unknown'
331
- });
332
- }
333
- }
334
- }
335
- } catch (e) {
336
- // Skip files that can't be read
337
- }
338
- }
339
-
340
- // Deduplicate by file:line
341
- const seen = new Set();
342
- return entryPoints.filter(ep => {
343
- const key = `${ep.file}:${ep.line}`;
344
- if (seen.has(key)) return false;
345
- seen.add(key);
346
- return true;
347
- });
348
- }
349
-
350
- /**
351
- * Extract HTTP method from pattern match
352
- * @param {string} matchText - The matched text
353
- * @returns {string|null}
354
- */
355
- function extractMethod(matchText) {
356
- const methodMatch = matchText.match(/\.(get|post|put|delete|patch|all|use|GET|POST|PUT|DELETE|PATCH)/i);
357
- return methodMatch ? methodMatch[1].toUpperCase() : null;
358
- }
359
-
360
- /**
361
- * Simple minimatch implementation for glob patterns
362
- * @param {string} file - File path
363
- * @param {string} pattern - Glob pattern
364
- * @returns {boolean}
365
- */
366
- function minimatch(file, pattern) {
367
- // Convert glob to regex
368
- const regexPattern = pattern
369
- .replace(/\*\*/g, '{{DOUBLE}}')
370
- .replace(/\*/g, '[^/]*')
371
- .replace(/{{DOUBLE}}/g, '.*')
372
- .replace(/\//g, '\\/');
373
-
374
- const regex = new RegExp(`^${regexPattern}$`);
375
- return regex.test(file);
376
- }
377
-
378
- /**
379
- * Discover workflows in the codebase
380
- * @param {string} projectRoot - Project root directory
381
- * @param {string[]} sourceFiles - List of source files
382
- * @returns {Promise<object[]>}
383
- */
384
- async function discoverWorkflows(projectRoot, sourceFiles) {
385
- const workflows = [];
386
-
387
- for (const [workflowType, heuristics] of Object.entries(WORKFLOW_HEURISTICS)) {
388
- const matchingFiles = new Set();
389
- let keywordScore = 0;
390
-
391
- // Check file patterns
392
- for (const pattern of heuristics.filePatterns) {
393
- for (const file of sourceFiles) {
394
- if (minimatch(file, pattern)) {
395
- matchingFiles.add(file);
396
- }
397
- }
398
- }
399
-
400
- // Check keywords in file content (sample first 50 matching files)
401
- const filesToCheck = Array.from(matchingFiles).slice(0, 50);
402
-
403
- for (const file of filesToCheck) {
404
- try {
405
- const content = fs.readFileSync(path.join(projectRoot, file), 'utf-8').toLowerCase();
406
- const matchCount = heuristics.keywords.filter(kw => content.includes(kw)).length;
407
- keywordScore += matchCount;
408
- } catch {
409
- // Skip unreadable files
410
- }
411
- }
412
-
413
- // Also check remaining source files for keyword matches
414
- if (filesToCheck.length === 0) {
415
- const sampled = sourceFiles.slice(0, 200);
416
- for (const file of sampled) {
417
- try {
418
- const content = fs.readFileSync(path.join(projectRoot, file), 'utf-8').toLowerCase();
419
- const matchCount = heuristics.keywords.filter(kw => content.includes(kw)).length;
420
- if (matchCount >= 2) {
421
- matchingFiles.add(file);
422
- keywordScore += matchCount;
423
- }
424
- } catch {
425
- // Skip unreadable files
426
- }
427
- }
428
- }
429
-
430
- if (matchingFiles.size > 0 || keywordScore >= 3) {
431
- workflows.push({
432
- type: workflowType,
433
- name: heuristics.name,
434
- category: heuristics.category,
435
- complexity: heuristics.complexity,
436
- priority: heuristics.priority,
437
- files: Array.from(matchingFiles),
438
- fileCount: matchingFiles.size,
439
- keywordScore,
440
- confidence: calculateConfidence(matchingFiles.size, keywordScore),
441
- status: 'discovered'
442
- });
443
- }
444
- }
445
-
446
- // Sort by priority and confidence
447
- workflows.sort((a, b) => {
448
- if (a.priority !== b.priority) return a.priority - b.priority;
449
- return b.confidence - a.confidence;
450
- });
451
-
452
- return workflows;
453
- }
454
-
455
- /**
456
- * Calculate workflow confidence score
457
- * @param {number} fileCount - Number of matching files
458
- * @param {number} keywordScore - Keyword match score
459
- * @returns {number}
460
- */
461
- function calculateConfidence(fileCount, keywordScore) {
462
- // Scale: 0-100
463
- const fileScore = Math.min(fileCount * 10, 50);
464
- const kwScore = Math.min(keywordScore * 5, 50);
465
- return fileScore + kwScore;
466
- }
467
-
468
- /**
469
- * Map the architecture of the codebase
470
- * @param {string} projectRoot - Project root directory
471
- * @returns {Promise<object>}
472
- */
473
- async function mapArchitecture(projectRoot) {
474
- const architecture = {
475
- directories: [],
476
- layers: [],
477
- components: [],
478
- directoryTree: ''
479
- };
480
-
481
- // Get top-level directories
482
- const entries = fs.readdirSync(projectRoot, { withFileTypes: true });
483
- const dirs = entries
484
- .filter(e => e.isDirectory() && !EXCLUDED_DIRS.includes(e.name) && !e.name.startsWith('.'))
485
- .map(e => e.name);
486
-
487
- architecture.directories = dirs;
488
-
489
- // Identify architectural layers
490
- const layerPatterns = {
491
- presentation: ['components', 'views', 'pages', 'ui', 'templates', 'layouts', 'frontend'],
492
- application: ['services', 'usecases', 'handlers', 'controllers', 'actions'],
493
- domain: ['models', 'entities', 'domain', 'core', 'business'],
494
- infrastructure: ['repositories', 'database', 'db', 'cache', 'external', 'integrations', 'adapters'],
495
- api: ['api', 'routes', 'endpoints', 'rest', 'graphql'],
496
- config: ['config', 'settings', 'constants'],
497
- tests: ['tests', 'test', '__tests__', 'spec']
498
- };
499
-
500
- for (const [layer, patterns] of Object.entries(layerPatterns)) {
501
- const matchingDirs = dirs.filter(d =>
502
- patterns.some(p => d.toLowerCase().includes(p))
503
- );
504
- if (matchingDirs.length > 0) {
505
- architecture.layers.push({
506
- name: layer,
507
- directories: matchingDirs,
508
- purpose: getLayerPurpose(layer)
509
- });
510
- }
511
- }
512
-
513
- // Build directory tree (max 3 levels)
514
- architecture.directoryTree = buildDirectoryTree(projectRoot, 3);
515
-
516
- return architecture;
517
- }
518
-
519
- /**
520
- * Get purpose description for a layer
521
- * @param {string} layer - Layer name
522
- * @returns {string}
523
- */
524
- function getLayerPurpose(layer) {
525
- const purposes = {
526
- presentation: 'User interface components and views',
527
- application: 'Application logic and service orchestration',
528
- domain: 'Core business logic and domain models',
529
- infrastructure: 'External systems and data persistence',
530
- api: 'API endpoints and route handling',
531
- config: 'Configuration and environment settings',
532
- tests: 'Test files and fixtures'
533
- };
534
- return purposes[layer] || 'Unknown';
535
- }
536
-
537
- /**
538
- * Build ASCII directory tree
539
- * @param {string} dir - Directory path
540
- * @param {number} maxDepth - Maximum depth
541
- * @param {string} prefix - Current prefix
542
- * @param {number} depth - Current depth
543
- * @returns {string}
544
- */
545
- function buildDirectoryTree(dir, maxDepth, prefix = '', depth = 0) {
546
- if (depth >= maxDepth) return '';
547
-
548
- let tree = '';
549
- const entries = fs.readdirSync(dir, { withFileTypes: true });
550
- const filtered = entries.filter(e =>
551
- !EXCLUDED_DIRS.includes(e.name) &&
552
- !e.name.startsWith('.') &&
553
- e.isDirectory()
554
- );
555
-
556
- filtered.forEach((entry, index) => {
557
- const isLast = index === filtered.length - 1;
558
- const connector = isLast ? '└── ' : '├── ';
559
- const childPrefix = isLast ? ' ' : '│ ';
560
-
561
- tree += `${prefix}${connector}${entry.name}/\n`;
562
- tree += buildDirectoryTree(
563
- path.join(dir, entry.name),
564
- maxDepth,
565
- prefix + childPrefix,
566
- depth + 1
567
- );
568
- });
569
-
570
- return tree;
571
- }
572
-
573
- /**
574
- * Extract dependencies from the project
575
- * @param {string} projectRoot - Project root directory
576
- * @returns {Promise<object[]>}
577
- */
578
- async function extractDependencies(projectRoot) {
579
- const dependencies = [];
580
-
581
- // Check package.json (Node.js)
582
- const packageJsonPath = path.join(projectRoot, 'package.json');
583
- if (fs.existsSync(packageJsonPath)) {
584
- try {
585
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
586
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
587
-
588
- for (const [name, version] of Object.entries(deps)) {
589
- dependencies.push({
590
- name,
591
- version,
592
- type: pkg.devDependencies?.[name] ? 'dev' : 'runtime',
593
- ecosystem: 'npm'
594
- });
595
- }
596
- } catch {
597
- // Ignore parse errors
598
- }
599
- }
600
-
601
- // Check requirements.txt (Python)
602
- const requirementsPath = path.join(projectRoot, 'requirements.txt');
603
- if (fs.existsSync(requirementsPath)) {
604
- try {
605
- const content = fs.readFileSync(requirementsPath, 'utf-8');
606
- const lines = content.split('\n').filter(l => l.trim() && !l.startsWith('#'));
607
-
608
- for (const line of lines) {
609
- const match = line.match(/^([a-zA-Z0-9_-]+)([=<>!~]+.*)?/);
610
- if (match) {
611
- dependencies.push({
612
- name: match[1],
613
- version: match[2] || '*',
614
- type: 'runtime',
615
- ecosystem: 'pip'
616
- });
617
- }
618
- }
619
- } catch {
620
- // Ignore parse errors
621
- }
622
- }
623
-
624
- // Check go.mod (Go)
625
- const goModPath = path.join(projectRoot, 'go.mod');
626
- if (fs.existsSync(goModPath)) {
627
- try {
628
- const content = fs.readFileSync(goModPath, 'utf-8');
629
- const requireMatch = content.match(/require\s*\(([\s\S]*?)\)/);
630
- if (requireMatch) {
631
- const lines = requireMatch[1].split('\n').filter(l => l.trim());
632
- for (const line of lines) {
633
- const match = line.trim().match(/^([^\s]+)\s+([^\s]+)/);
634
- if (match) {
635
- dependencies.push({
636
- name: match[1],
637
- version: match[2],
638
- type: 'runtime',
639
- ecosystem: 'go'
640
- });
641
- }
642
- }
643
- }
644
- } catch {
645
- // Ignore parse errors
646
- }
647
- }
648
-
649
- return dependencies;
650
- }
651
-
652
- /**
653
- * Count lines of code by file
654
- * @param {string} projectRoot - Project root directory
655
- * @param {string[]} sourceFiles - List of source files
656
- * @returns {Promise<object>}
657
- */
658
- async function countLinesOfCode(projectRoot, sourceFiles) {
659
- const locByFile = {};
660
- let totalLoc = 0;
661
-
662
- for (const file of sourceFiles.slice(0, 500)) { // Limit for performance
663
- try {
664
- const content = fs.readFileSync(path.join(projectRoot, file), 'utf-8');
665
- const lines = content.split('\n').filter(l => l.trim()).length;
666
- locByFile[file] = lines;
667
- totalLoc += lines;
668
- } catch {
669
- // Skip unreadable files
670
- }
671
- }
672
-
673
- return { byFile: locByFile, total: totalLoc };
674
- }
675
-
676
- /**
677
- * Main analysis function
678
- * @param {string} projectRoot - Project root directory
679
- * @param {object} options - Analysis options
680
- * @returns {Promise<object>}
681
- */
682
- async function analyzeCodebase(projectRoot, options = {}) {
683
- const { techStack = {} } = options;
684
-
685
- // Find all source files
686
- const sourceFiles = await findSourceFiles(projectRoot, techStack.languages);
687
-
688
- // Run all analyses
689
- const [entryPoints, workflows, architecture, dependencies, loc] = await Promise.all([
690
- discoverEntryPoints(projectRoot, sourceFiles, techStack),
691
- discoverWorkflows(projectRoot, sourceFiles),
692
- mapArchitecture(projectRoot),
693
- extractDependencies(projectRoot),
694
- countLinesOfCode(projectRoot, sourceFiles)
695
- ]);
696
-
697
- return {
698
- projectRoot,
699
- sourceFiles: sourceFiles.length,
700
- entryPoints,
701
- workflows,
702
- architecture,
703
- dependencies,
704
- linesOfCode: loc,
705
- analyzedAt: new Date().toISOString(),
706
- summary: {
707
- totalFiles: sourceFiles.length,
708
- entryPointCount: entryPoints.length,
709
- workflowCount: workflows.length,
710
- layerCount: architecture.layers.length,
711
- dependencyCount: dependencies.length,
712
- totalLoc: loc.total
713
- }
714
- };
715
- }
716
-
717
- module.exports = {
718
- analyzeCodebase,
719
- findSourceFiles,
720
- discoverEntryPoints,
721
- discoverWorkflows,
722
- mapArchitecture,
723
- extractDependencies,
724
- countLinesOfCode,
725
- ENTRY_PATTERNS,
726
- WORKFLOW_HEURISTICS,
727
- SOURCE_EXTENSIONS,
728
- EXCLUDED_DIRS
729
- };
1
+ /**
2
+ * Static Analyzer
3
+ *
4
+ * Performs comprehensive static analysis of a codebase without AI.
5
+ * Discovers entry points, workflows, architecture, and dependencies.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { glob } = require('glob');
11
+
12
+ /**
13
+ * Entry point patterns by framework
14
+ */
15
+ const ENTRY_PATTERNS = {
16
+ // Express.js
17
+ express: {
18
+ patterns: [
19
+ /\.(get|post|put|delete|patch|all|use)\s*\(\s*['"]/gi,
20
+ /router\.(get|post|put|delete|patch|all|use)\s*\(\s*['"]/gi,
21
+ /app\.(get|post|put|delete|patch|all|use)\s*\(\s*['"]/gi
22
+ ],
23
+ filePatterns: ['**/routes/**/*.js', '**/router/**/*.js', '**/api/**/*.js', '**/controllers/**/*.js']
24
+ },
25
+
26
+ // FastAPI (Python)
27
+ fastapi: {
28
+ patterns: [
29
+ /@(app|router)\.(get|post|put|delete|patch)\s*\(/gi,
30
+ /@router\.api_route\s*\(/gi
31
+ ],
32
+ filePatterns: ['**/routes/**/*.py', '**/api/**/*.py', '**/routers/**/*.py', '**/endpoints/**/*.py']
33
+ },
34
+
35
+ // Next.js (App Router)
36
+ nextjs: {
37
+ patterns: [
38
+ /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(/gi,
39
+ /export\s+const\s+(GET|POST|PUT|DELETE|PATCH)\s*=/gi
40
+ ],
41
+ filePatterns: ['**/app/**/route.ts', '**/app/**/route.js', '**/pages/api/**/*.ts', '**/pages/api/**/*.js']
42
+ },
43
+
44
+ // Django
45
+ django: {
46
+ patterns: [
47
+ /path\s*\(\s*['"]/gi,
48
+ /url\s*\(\s*r?['"]/gi,
49
+ /@api_view\s*\(\s*\[/gi
50
+ ],
51
+ filePatterns: ['**/urls.py', '**/views.py', '**/api/**/*.py']
52
+ },
53
+
54
+ // Rails
55
+ rails: {
56
+ patterns: [
57
+ /(get|post|put|patch|delete|resources|resource)\s+['"]/gi,
58
+ /match\s+['"]/gi
59
+ ],
60
+ filePatterns: ['**/config/routes.rb', '**/app/controllers/**/*.rb']
61
+ },
62
+
63
+ // NestJS
64
+ nestjs: {
65
+ patterns: [
66
+ /@(Get|Post|Put|Delete|Patch|All)\s*\(/gi,
67
+ /@Controller\s*\(/gi
68
+ ],
69
+ filePatterns: ['**/*.controller.ts', '**/controllers/**/*.ts']
70
+ },
71
+
72
+ // Gin (Go)
73
+ gin: {
74
+ patterns: [
75
+ /\.(GET|POST|PUT|DELETE|PATCH|Handle)\s*\(\s*"/gi,
76
+ /router\.(GET|POST|PUT|DELETE|PATCH)\s*\(/gi
77
+ ],
78
+ filePatterns: ['**/routes/**/*.go', '**/handlers/**/*.go', '**/api/**/*.go', '**/main.go']
79
+ },
80
+
81
+ // Flask (Python)
82
+ flask: {
83
+ patterns: [
84
+ /@(app|bp|blueprint)\.(route|get|post|put|delete|patch)\s*\(/gi
85
+ ],
86
+ filePatterns: ['**/routes/**/*.py', '**/views/**/*.py', '**/api/**/*.py', '**/app.py']
87
+ }
88
+ };
89
+
90
+ /**
91
+ * Workflow discovery heuristics
92
+ */
93
+ const WORKFLOW_HEURISTICS = {
94
+ authentication: {
95
+ name: 'User Authentication',
96
+ category: 'security',
97
+ complexity: 'HIGH',
98
+ keywords: ['login', 'logout', 'auth', 'authenticate', 'session', 'token', 'jwt', 'oauth', 'sso', 'password'],
99
+ filePatterns: ['**/auth/**', '**/login/**', '**/session/**', '**/oauth/**'],
100
+ priority: 1
101
+ },
102
+
103
+ userManagement: {
104
+ name: 'User Management',
105
+ category: 'core',
106
+ complexity: 'MEDIUM',
107
+ keywords: ['user', 'profile', 'account', 'registration', 'signup', 'register', 'onboarding'],
108
+ filePatterns: ['**/users/**', '**/profile/**', '**/accounts/**', '**/members/**'],
109
+ priority: 2
110
+ },
111
+
112
+ payments: {
113
+ name: 'Payment Processing',
114
+ category: 'core',
115
+ complexity: 'HIGH',
116
+ keywords: ['payment', 'stripe', 'paypal', 'invoice', 'billing', 'subscription', 'checkout', 'cart', 'order'],
117
+ filePatterns: ['**/payments/**', '**/billing/**', '**/checkout/**', '**/orders/**', '**/subscriptions/**'],
118
+ priority: 1
119
+ },
120
+
121
+ dataProcessing: {
122
+ name: 'Data Processing',
123
+ category: 'infrastructure',
124
+ complexity: 'HIGH',
125
+ keywords: ['process', 'transform', 'pipeline', 'batch', 'worker', 'job', 'queue', 'task', 'etl'],
126
+ filePatterns: ['**/workers/**', '**/jobs/**', '**/tasks/**', '**/pipelines/**', '**/queues/**'],
127
+ priority: 2
128
+ },
129
+
130
+ apiEndpoints: {
131
+ name: 'API Endpoints',
132
+ category: 'core',
133
+ complexity: 'MEDIUM',
134
+ keywords: ['api', 'endpoint', 'route', 'controller', 'handler', 'resource'],
135
+ filePatterns: ['**/routes/**', '**/api/**', '**/controllers/**', '**/handlers/**'],
136
+ priority: 3
137
+ },
138
+
139
+ database: {
140
+ name: 'Database Operations',
141
+ category: 'infrastructure',
142
+ complexity: 'MEDIUM',
143
+ keywords: ['model', 'schema', 'migration', 'repository', 'dao', 'orm', 'query', 'database'],
144
+ filePatterns: ['**/models/**', '**/schemas/**', '**/migrations/**', '**/repositories/**', '**/entities/**'],
145
+ priority: 2
146
+ },
147
+
148
+ notifications: {
149
+ name: 'Notifications',
150
+ category: 'features',
151
+ complexity: 'MEDIUM',
152
+ keywords: ['notification', 'email', 'sms', 'push', 'alert', 'message', 'mail', 'notify'],
153
+ filePatterns: ['**/notifications/**', '**/emails/**', '**/mailers/**', '**/messaging/**'],
154
+ priority: 3
155
+ },
156
+
157
+ fileHandling: {
158
+ name: 'File Handling',
159
+ category: 'features',
160
+ complexity: 'MEDIUM',
161
+ keywords: ['upload', 'download', 'file', 'storage', 's3', 'blob', 'attachment', 'media'],
162
+ filePatterns: ['**/uploads/**', '**/storage/**', '**/files/**', '**/media/**'],
163
+ priority: 3
164
+ },
165
+
166
+ search: {
167
+ name: 'Search',
168
+ category: 'features',
169
+ complexity: 'MEDIUM',
170
+ keywords: ['search', 'filter', 'query', 'elasticsearch', 'algolia', 'index', 'find'],
171
+ filePatterns: ['**/search/**', '**/filters/**'],
172
+ priority: 3
173
+ },
174
+
175
+ analytics: {
176
+ name: 'Analytics',
177
+ category: 'features',
178
+ complexity: 'MEDIUM',
179
+ keywords: ['analytics', 'tracking', 'metrics', 'stats', 'dashboard', 'report', 'insight'],
180
+ filePatterns: ['**/analytics/**', '**/tracking/**', '**/metrics/**', '**/reports/**'],
181
+ priority: 4
182
+ },
183
+
184
+ testing: {
185
+ name: 'Testing',
186
+ category: 'infrastructure',
187
+ complexity: 'LOW',
188
+ keywords: ['test', 'spec', 'mock', 'fixture', 'factory', 'stub'],
189
+ filePatterns: ['**/tests/**', '**/test/**', '**/__tests__/**', '**/spec/**'],
190
+ priority: 5
191
+ },
192
+
193
+ configuration: {
194
+ name: 'Configuration',
195
+ category: 'infrastructure',
196
+ complexity: 'LOW',
197
+ keywords: ['config', 'setting', 'env', 'constant', 'option'],
198
+ filePatterns: ['**/config/**', '**/settings/**', '**/constants/**'],
199
+ priority: 5
200
+ }
201
+ };
202
+
203
+ /**
204
+ * Source file extensions by language
205
+ */
206
+ const SOURCE_EXTENSIONS = {
207
+ javascript: ['.js', '.jsx', '.mjs', '.cjs'],
208
+ typescript: ['.ts', '.tsx', '.mts', '.cts'],
209
+ python: ['.py', '.ipynb', '.pyw'],
210
+ go: ['.go'],
211
+ rust: ['.rs'],
212
+ ruby: ['.rb'],
213
+ java: ['.java'],
214
+ csharp: ['.cs'],
215
+ php: ['.php']
216
+ };
217
+
218
+ /**
219
+ * Directories to exclude from analysis
220
+ */
221
+ const EXCLUDED_DIRS = [
222
+ 'node_modules',
223
+ '.git',
224
+ 'dist',
225
+ 'build',
226
+ 'out',
227
+ '.next',
228
+ '__pycache__',
229
+ '.venv',
230
+ 'venv',
231
+ 'vendor',
232
+ 'target',
233
+ '.cache',
234
+ 'coverage',
235
+ '.nyc_output'
236
+ ];
237
+
238
+ /**
239
+ * Find all source files in the project
240
+ * @param {string} projectRoot - Project root directory
241
+ * @param {string[]} languages - Languages to include
242
+ * @returns {Promise<string[]>}
243
+ */
244
+ async function findSourceFiles(projectRoot, languages = null) {
245
+ const extensions = languages
246
+ ? languages.flatMap(lang => SOURCE_EXTENSIONS[lang] || [])
247
+ : Object.values(SOURCE_EXTENSIONS).flat();
248
+
249
+ const pattern = `**/*{${extensions.join(',')}}`;
250
+
251
+ const files = await glob(pattern, {
252
+ cwd: projectRoot,
253
+ nodir: true,
254
+ ignore: EXCLUDED_DIRS.map(d => `**/${d}/**`)
255
+ });
256
+
257
+ return files;
258
+ }
259
+
260
+ /**
261
+ * Discover entry points in the codebase
262
+ * @param {string} projectRoot - Project root directory
263
+ * @param {string[]} sourceFiles - List of source files
264
+ * @param {object} techStack - Detected tech stack
265
+ * @returns {Promise<object[]>}
266
+ */
267
+ async function discoverEntryPoints(projectRoot, sourceFiles, techStack) {
268
+ const entryPoints = [];
269
+ const frameworks = techStack.frameworks || [];
270
+
271
+ // Get relevant pattern sets
272
+ const patternSets = [];
273
+ for (const framework of frameworks) {
274
+ if (ENTRY_PATTERNS[framework]) {
275
+ patternSets.push(ENTRY_PATTERNS[framework]);
276
+ }
277
+ }
278
+
279
+ // If no specific frameworks, try all patterns
280
+ if (patternSets.length === 0) {
281
+ patternSets.push(...Object.values(ENTRY_PATTERNS));
282
+ }
283
+
284
+ for (const file of sourceFiles) {
285
+ const filePath = path.join(projectRoot, file);
286
+
287
+ // Check if file matches any file patterns
288
+ let isRelevant = false;
289
+ for (const patternSet of patternSets) {
290
+ for (const fp of patternSet.filePatterns) {
291
+ if (minimatch(file, fp)) {
292
+ isRelevant = true;
293
+ break;
294
+ }
295
+ }
296
+ if (isRelevant) break;
297
+ }
298
+
299
+ // For efficiency, skip if not a likely entry point file
300
+ if (!isRelevant && sourceFiles.length > 100) {
301
+ continue;
302
+ }
303
+
304
+ try {
305
+ const content = fs.readFileSync(filePath, 'utf-8');
306
+ const lines = content.split('\n');
307
+
308
+ for (const patternSet of patternSets) {
309
+ for (const pattern of patternSet.patterns) {
310
+ let match;
311
+ pattern.lastIndex = 0;
312
+
313
+ while ((match = pattern.exec(content)) !== null) {
314
+ const lineNumber = content.substring(0, match.index).split('\n').length;
315
+ const lineContent = lines[lineNumber - 1] || '';
316
+
317
+ // Extract route/path from the match context
318
+ const routeMatch = lineContent.match(/['"]([^'"]+)['"]/);
319
+ const route = routeMatch ? routeMatch[1] : null;
320
+
321
+ entryPoints.push({
322
+ file,
323
+ line: lineNumber,
324
+ match: match[0].trim(),
325
+ context: lineContent.trim(),
326
+ route,
327
+ method: extractMethod(match[0]),
328
+ framework: Object.keys(ENTRY_PATTERNS).find(k =>
329
+ ENTRY_PATTERNS[k].patterns.includes(pattern)
330
+ ) || 'unknown'
331
+ });
332
+ }
333
+ }
334
+ }
335
+ } catch (e) {
336
+ // Skip files that can't be read
337
+ }
338
+ }
339
+
340
+ // Deduplicate by file:line
341
+ const seen = new Set();
342
+ return entryPoints.filter(ep => {
343
+ const key = `${ep.file}:${ep.line}`;
344
+ if (seen.has(key)) return false;
345
+ seen.add(key);
346
+ return true;
347
+ });
348
+ }
349
+
350
+ /**
351
+ * Extract HTTP method from pattern match
352
+ * @param {string} matchText - The matched text
353
+ * @returns {string|null}
354
+ */
355
+ function extractMethod(matchText) {
356
+ const methodMatch = matchText.match(/\.(get|post|put|delete|patch|all|use|GET|POST|PUT|DELETE|PATCH)/i);
357
+ return methodMatch ? methodMatch[1].toUpperCase() : null;
358
+ }
359
+
360
+ /**
361
+ * Simple minimatch implementation for glob patterns
362
+ * @param {string} file - File path
363
+ * @param {string} pattern - Glob pattern
364
+ * @returns {boolean}
365
+ */
366
+ function minimatch(file, pattern) {
367
+ // Convert glob to regex
368
+ const regexPattern = pattern
369
+ .replace(/\*\*/g, '{{DOUBLE}}')
370
+ .replace(/\*/g, '[^/]*')
371
+ .replace(/{{DOUBLE}}/g, '.*')
372
+ .replace(/\//g, '\\/');
373
+
374
+ const regex = new RegExp(`^${regexPattern}$`);
375
+ return regex.test(file);
376
+ }
377
+
378
+ /**
379
+ * Discover workflows in the codebase
380
+ * @param {string} projectRoot - Project root directory
381
+ * @param {string[]} sourceFiles - List of source files
382
+ * @returns {Promise<object[]>}
383
+ */
384
+ async function discoverWorkflows(projectRoot, sourceFiles) {
385
+ const workflows = [];
386
+
387
+ for (const [workflowType, heuristics] of Object.entries(WORKFLOW_HEURISTICS)) {
388
+ const matchingFiles = new Set();
389
+ let keywordScore = 0;
390
+
391
+ // Check file patterns
392
+ for (const pattern of heuristics.filePatterns) {
393
+ for (const file of sourceFiles) {
394
+ if (minimatch(file, pattern)) {
395
+ matchingFiles.add(file);
396
+ }
397
+ }
398
+ }
399
+
400
+ // Check keywords in file content (sample first 50 matching files)
401
+ const filesToCheck = Array.from(matchingFiles).slice(0, 50);
402
+
403
+ for (const file of filesToCheck) {
404
+ try {
405
+ const content = fs.readFileSync(path.join(projectRoot, file), 'utf-8').toLowerCase();
406
+ const matchCount = heuristics.keywords.filter(kw => content.includes(kw)).length;
407
+ keywordScore += matchCount;
408
+ } catch {
409
+ // Skip unreadable files
410
+ }
411
+ }
412
+
413
+ // Also check remaining source files for keyword matches
414
+ if (filesToCheck.length === 0) {
415
+ const sampled = sourceFiles.slice(0, 200);
416
+ for (const file of sampled) {
417
+ try {
418
+ const content = fs.readFileSync(path.join(projectRoot, file), 'utf-8').toLowerCase();
419
+ const matchCount = heuristics.keywords.filter(kw => content.includes(kw)).length;
420
+ if (matchCount >= 2) {
421
+ matchingFiles.add(file);
422
+ keywordScore += matchCount;
423
+ }
424
+ } catch {
425
+ // Skip unreadable files
426
+ }
427
+ }
428
+ }
429
+
430
+ if (matchingFiles.size > 0 || keywordScore >= 3) {
431
+ workflows.push({
432
+ type: workflowType,
433
+ name: heuristics.name,
434
+ category: heuristics.category,
435
+ complexity: heuristics.complexity,
436
+ priority: heuristics.priority,
437
+ files: Array.from(matchingFiles),
438
+ fileCount: matchingFiles.size,
439
+ keywordScore,
440
+ confidence: calculateConfidence(matchingFiles.size, keywordScore),
441
+ status: 'discovered'
442
+ });
443
+ }
444
+ }
445
+
446
+ // Sort by priority and confidence
447
+ workflows.sort((a, b) => {
448
+ if (a.priority !== b.priority) return a.priority - b.priority;
449
+ return b.confidence - a.confidence;
450
+ });
451
+
452
+ return workflows;
453
+ }
454
+
455
+ /**
456
+ * Calculate workflow confidence score
457
+ * @param {number} fileCount - Number of matching files
458
+ * @param {number} keywordScore - Keyword match score
459
+ * @returns {number}
460
+ */
461
+ function calculateConfidence(fileCount, keywordScore) {
462
+ // Scale: 0-100
463
+ const fileScore = Math.min(fileCount * 10, 50);
464
+ const kwScore = Math.min(keywordScore * 5, 50);
465
+ return fileScore + kwScore;
466
+ }
467
+
468
+ /**
469
+ * Map the architecture of the codebase
470
+ * @param {string} projectRoot - Project root directory
471
+ * @returns {Promise<object>}
472
+ */
473
+ async function mapArchitecture(projectRoot) {
474
+ const architecture = {
475
+ directories: [],
476
+ layers: [],
477
+ components: [],
478
+ directoryTree: ''
479
+ };
480
+
481
+ // Get top-level directories
482
+ const entries = fs.readdirSync(projectRoot, { withFileTypes: true });
483
+ const dirs = entries
484
+ .filter(e => e.isDirectory() && !EXCLUDED_DIRS.includes(e.name) && !e.name.startsWith('.'))
485
+ .map(e => e.name);
486
+
487
+ architecture.directories = dirs;
488
+
489
+ // Identify architectural layers
490
+ const layerPatterns = {
491
+ presentation: ['components', 'views', 'pages', 'ui', 'templates', 'layouts', 'frontend'],
492
+ application: ['services', 'usecases', 'handlers', 'controllers', 'actions'],
493
+ domain: ['models', 'entities', 'domain', 'core', 'business'],
494
+ infrastructure: ['repositories', 'database', 'db', 'cache', 'external', 'integrations', 'adapters'],
495
+ api: ['api', 'routes', 'endpoints', 'rest', 'graphql'],
496
+ config: ['config', 'settings', 'constants'],
497
+ tests: ['tests', 'test', '__tests__', 'spec']
498
+ };
499
+
500
+ for (const [layer, patterns] of Object.entries(layerPatterns)) {
501
+ const matchingDirs = dirs.filter(d =>
502
+ patterns.some(p => d.toLowerCase().includes(p))
503
+ );
504
+ if (matchingDirs.length > 0) {
505
+ architecture.layers.push({
506
+ name: layer,
507
+ directories: matchingDirs,
508
+ purpose: getLayerPurpose(layer)
509
+ });
510
+ }
511
+ }
512
+
513
+ // Build directory tree (max 3 levels)
514
+ architecture.directoryTree = buildDirectoryTree(projectRoot, 3);
515
+
516
+ return architecture;
517
+ }
518
+
519
+ /**
520
+ * Get purpose description for a layer
521
+ * @param {string} layer - Layer name
522
+ * @returns {string}
523
+ */
524
+ function getLayerPurpose(layer) {
525
+ const purposes = {
526
+ presentation: 'User interface components and views',
527
+ application: 'Application logic and service orchestration',
528
+ domain: 'Core business logic and domain models',
529
+ infrastructure: 'External systems and data persistence',
530
+ api: 'API endpoints and route handling',
531
+ config: 'Configuration and environment settings',
532
+ tests: 'Test files and fixtures'
533
+ };
534
+ return purposes[layer] || 'Unknown';
535
+ }
536
+
537
+ /**
538
+ * Build ASCII directory tree
539
+ * @param {string} dir - Directory path
540
+ * @param {number} maxDepth - Maximum depth
541
+ * @param {string} prefix - Current prefix
542
+ * @param {number} depth - Current depth
543
+ * @returns {string}
544
+ */
545
+ function buildDirectoryTree(dir, maxDepth, prefix = '', depth = 0) {
546
+ if (depth >= maxDepth) return '';
547
+
548
+ let tree = '';
549
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
550
+ const filtered = entries.filter(e =>
551
+ !EXCLUDED_DIRS.includes(e.name) &&
552
+ !e.name.startsWith('.') &&
553
+ e.isDirectory()
554
+ );
555
+
556
+ filtered.forEach((entry, index) => {
557
+ const isLast = index === filtered.length - 1;
558
+ const connector = isLast ? '└── ' : '├── ';
559
+ const childPrefix = isLast ? ' ' : '│ ';
560
+
561
+ tree += `${prefix}${connector}${entry.name}/\n`;
562
+ tree += buildDirectoryTree(
563
+ path.join(dir, entry.name),
564
+ maxDepth,
565
+ prefix + childPrefix,
566
+ depth + 1
567
+ );
568
+ });
569
+
570
+ return tree;
571
+ }
572
+
573
+ /**
574
+ * Extract dependencies from the project
575
+ * @param {string} projectRoot - Project root directory
576
+ * @returns {Promise<object[]>}
577
+ */
578
+ async function extractDependencies(projectRoot) {
579
+ const dependencies = [];
580
+
581
+ // Check package.json (Node.js)
582
+ const packageJsonPath = path.join(projectRoot, 'package.json');
583
+ if (fs.existsSync(packageJsonPath)) {
584
+ try {
585
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
586
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
587
+
588
+ for (const [name, version] of Object.entries(deps)) {
589
+ dependencies.push({
590
+ name,
591
+ version,
592
+ type: pkg.devDependencies?.[name] ? 'dev' : 'runtime',
593
+ ecosystem: 'npm'
594
+ });
595
+ }
596
+ } catch {
597
+ // Ignore parse errors
598
+ }
599
+ }
600
+
601
+ // Check requirements.txt (Python)
602
+ const requirementsPath = path.join(projectRoot, 'requirements.txt');
603
+ if (fs.existsSync(requirementsPath)) {
604
+ try {
605
+ const content = fs.readFileSync(requirementsPath, 'utf-8');
606
+ const lines = content.split('\n').filter(l => l.trim() && !l.startsWith('#'));
607
+
608
+ for (const line of lines) {
609
+ const match = line.match(/^([a-zA-Z0-9_-]+)([=<>!~]+.*)?/);
610
+ if (match) {
611
+ dependencies.push({
612
+ name: match[1],
613
+ version: match[2] || '*',
614
+ type: 'runtime',
615
+ ecosystem: 'pip'
616
+ });
617
+ }
618
+ }
619
+ } catch {
620
+ // Ignore parse errors
621
+ }
622
+ }
623
+
624
+ // Check go.mod (Go)
625
+ const goModPath = path.join(projectRoot, 'go.mod');
626
+ if (fs.existsSync(goModPath)) {
627
+ try {
628
+ const content = fs.readFileSync(goModPath, 'utf-8');
629
+ const requireMatch = content.match(/require\s*\(([\s\S]*?)\)/);
630
+ if (requireMatch) {
631
+ const lines = requireMatch[1].split('\n').filter(l => l.trim());
632
+ for (const line of lines) {
633
+ const match = line.trim().match(/^([^\s]+)\s+([^\s]+)/);
634
+ if (match) {
635
+ dependencies.push({
636
+ name: match[1],
637
+ version: match[2],
638
+ type: 'runtime',
639
+ ecosystem: 'go'
640
+ });
641
+ }
642
+ }
643
+ }
644
+ } catch {
645
+ // Ignore parse errors
646
+ }
647
+ }
648
+
649
+ return dependencies;
650
+ }
651
+
652
+ /**
653
+ * Count lines of code by file
654
+ * @param {string} projectRoot - Project root directory
655
+ * @param {string[]} sourceFiles - List of source files
656
+ * @returns {Promise<object>}
657
+ */
658
+ async function countLinesOfCode(projectRoot, sourceFiles) {
659
+ const locByFile = {};
660
+ let totalLoc = 0;
661
+
662
+ for (const file of sourceFiles.slice(0, 500)) { // Limit for performance
663
+ try {
664
+ const content = fs.readFileSync(path.join(projectRoot, file), 'utf-8');
665
+ const lines = content.split('\n').filter(l => l.trim()).length;
666
+ locByFile[file] = lines;
667
+ totalLoc += lines;
668
+ } catch {
669
+ // Skip unreadable files
670
+ }
671
+ }
672
+
673
+ return { byFile: locByFile, total: totalLoc };
674
+ }
675
+
676
+ /**
677
+ * Main analysis function
678
+ * @param {string} projectRoot - Project root directory
679
+ * @param {object} options - Analysis options
680
+ * @returns {Promise<object>}
681
+ */
682
+ async function analyzeCodebase(projectRoot, options = {}) {
683
+ const { techStack = {} } = options;
684
+
685
+ // Find all source files
686
+ const sourceFiles = await findSourceFiles(projectRoot, techStack.languages);
687
+
688
+ // Run all analyses
689
+ const [entryPoints, workflows, architecture, dependencies, loc] = await Promise.all([
690
+ discoverEntryPoints(projectRoot, sourceFiles, techStack),
691
+ discoverWorkflows(projectRoot, sourceFiles),
692
+ mapArchitecture(projectRoot),
693
+ extractDependencies(projectRoot),
694
+ countLinesOfCode(projectRoot, sourceFiles)
695
+ ]);
696
+
697
+ return {
698
+ projectRoot,
699
+ sourceFiles: sourceFiles.length,
700
+ entryPoints,
701
+ workflows,
702
+ architecture,
703
+ dependencies,
704
+ linesOfCode: loc,
705
+ analyzedAt: new Date().toISOString(),
706
+ summary: {
707
+ totalFiles: sourceFiles.length,
708
+ entryPointCount: entryPoints.length,
709
+ workflowCount: workflows.length,
710
+ layerCount: architecture.layers.length,
711
+ dependencyCount: dependencies.length,
712
+ totalLoc: loc.total
713
+ }
714
+ };
715
+ }
716
+
717
+ module.exports = {
718
+ analyzeCodebase,
719
+ findSourceFiles,
720
+ discoverEntryPoints,
721
+ discoverWorkflows,
722
+ mapArchitecture,
723
+ extractDependencies,
724
+ countLinesOfCode,
725
+ ENTRY_PATTERNS,
726
+ WORKFLOW_HEURISTICS,
727
+ SOURCE_EXTENSIONS,
728
+ EXCLUDED_DIRS
729
+ };