ai-first-cli 1.1.1 → 1.1.2

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 (192) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.es.md +137 -1
  3. package/README.md +136 -4
  4. package/ai/ai_context.md +2 -2
  5. package/ai/architecture.md +3 -3
  6. package/ai/cache.json +85 -57
  7. package/ai/ccp/jira-123/context.json +7 -0
  8. package/ai/context/repo.json +56 -0
  9. package/ai/context/utils.json +7 -0
  10. package/ai/dependencies.json +51 -1026
  11. package/ai/files.json +195 -3
  12. package/ai/git/commit-activity.json +8646 -0
  13. package/ai/git/recent-features.json +1 -0
  14. package/ai/git/recent-files.json +52 -0
  15. package/ai/git/recent-flows.json +1 -0
  16. package/ai/graph/knowledge-graph.json +43643 -0
  17. package/ai/graph/module-graph.json +4 -0
  18. package/ai/graph/symbol-graph.json +3307 -879
  19. package/ai/graph/symbol-references.json +119 -32
  20. package/ai/index-state.json +843 -188
  21. package/ai/index.db +0 -0
  22. package/ai/modules.json +4 -0
  23. package/ai/repo-map.json +81 -17
  24. package/ai/repo_map.json +81 -17
  25. package/ai/repo_map.md +21 -7
  26. package/ai/summary.md +5 -5
  27. package/ai/symbols.json +1 -20287
  28. package/dist/analyzers/androidResources.d.ts +23 -0
  29. package/dist/analyzers/androidResources.d.ts.map +1 -0
  30. package/dist/analyzers/androidResources.js +93 -0
  31. package/dist/analyzers/androidResources.js.map +1 -0
  32. package/dist/analyzers/dependencies.d.ts.map +1 -1
  33. package/dist/analyzers/dependencies.js +37 -0
  34. package/dist/analyzers/dependencies.js.map +1 -1
  35. package/dist/analyzers/entrypoints.d.ts.map +1 -1
  36. package/dist/analyzers/entrypoints.js +71 -1
  37. package/dist/analyzers/entrypoints.js.map +1 -1
  38. package/dist/analyzers/gradleModules.d.ts +22 -0
  39. package/dist/analyzers/gradleModules.d.ts.map +1 -0
  40. package/dist/analyzers/gradleModules.js +75 -0
  41. package/dist/analyzers/gradleModules.js.map +1 -0
  42. package/dist/analyzers/techStack.d.ts +7 -0
  43. package/dist/analyzers/techStack.d.ts.map +1 -1
  44. package/dist/analyzers/techStack.js +44 -1
  45. package/dist/analyzers/techStack.js.map +1 -1
  46. package/dist/commands/ai-first.d.ts.map +1 -1
  47. package/dist/commands/ai-first.js +311 -1
  48. package/dist/commands/ai-first.js.map +1 -1
  49. package/dist/core/adapters/adapterRegistry.d.ts +39 -0
  50. package/dist/core/adapters/adapterRegistry.d.ts.map +1 -0
  51. package/dist/core/adapters/adapterRegistry.js +155 -0
  52. package/dist/core/adapters/adapterRegistry.js.map +1 -0
  53. package/dist/core/adapters/baseAdapter.d.ts +49 -0
  54. package/dist/core/adapters/baseAdapter.d.ts.map +1 -0
  55. package/dist/core/adapters/baseAdapter.js +28 -0
  56. package/dist/core/adapters/baseAdapter.js.map +1 -0
  57. package/dist/core/adapters/community/fastapiAdapter.d.ts +7 -0
  58. package/dist/core/adapters/community/fastapiAdapter.d.ts.map +1 -0
  59. package/dist/core/adapters/community/fastapiAdapter.js +40 -0
  60. package/dist/core/adapters/community/fastapiAdapter.js.map +1 -0
  61. package/dist/core/adapters/community/index.d.ts +11 -0
  62. package/dist/core/adapters/community/index.d.ts.map +1 -0
  63. package/dist/core/adapters/community/index.js +11 -0
  64. package/dist/core/adapters/community/index.js.map +1 -0
  65. package/dist/core/adapters/community/laravelAdapter.d.ts +7 -0
  66. package/dist/core/adapters/community/laravelAdapter.d.ts.map +1 -0
  67. package/dist/core/adapters/community/laravelAdapter.js +47 -0
  68. package/dist/core/adapters/community/laravelAdapter.js.map +1 -0
  69. package/dist/core/adapters/community/nestjsAdapter.d.ts +7 -0
  70. package/dist/core/adapters/community/nestjsAdapter.d.ts.map +1 -0
  71. package/dist/core/adapters/community/nestjsAdapter.js +48 -0
  72. package/dist/core/adapters/community/nestjsAdapter.js.map +1 -0
  73. package/dist/core/adapters/community/phoenixAdapter.d.ts +7 -0
  74. package/dist/core/adapters/community/phoenixAdapter.d.ts.map +1 -0
  75. package/dist/core/adapters/community/phoenixAdapter.js +45 -0
  76. package/dist/core/adapters/community/phoenixAdapter.js.map +1 -0
  77. package/dist/core/adapters/community/springBootAdapter.d.ts +7 -0
  78. package/dist/core/adapters/community/springBootAdapter.d.ts.map +1 -0
  79. package/dist/core/adapters/community/springBootAdapter.js +44 -0
  80. package/dist/core/adapters/community/springBootAdapter.js.map +1 -0
  81. package/dist/core/adapters/dotnetAdapter.d.ts +20 -0
  82. package/dist/core/adapters/dotnetAdapter.d.ts.map +1 -0
  83. package/dist/core/adapters/dotnetAdapter.js +86 -0
  84. package/dist/core/adapters/dotnetAdapter.js.map +1 -0
  85. package/dist/core/adapters/index.d.ts +18 -0
  86. package/dist/core/adapters/index.d.ts.map +1 -0
  87. package/dist/core/adapters/index.js +19 -0
  88. package/dist/core/adapters/index.js.map +1 -0
  89. package/dist/core/adapters/javascriptAdapter.d.ts +11 -0
  90. package/dist/core/adapters/javascriptAdapter.d.ts.map +1 -0
  91. package/dist/core/adapters/javascriptAdapter.js +47 -0
  92. package/dist/core/adapters/javascriptAdapter.js.map +1 -0
  93. package/dist/core/adapters/pythonAdapter.d.ts +20 -0
  94. package/dist/core/adapters/pythonAdapter.d.ts.map +1 -0
  95. package/dist/core/adapters/pythonAdapter.js +99 -0
  96. package/dist/core/adapters/pythonAdapter.js.map +1 -0
  97. package/dist/core/adapters/railsAdapter.d.ts +10 -0
  98. package/dist/core/adapters/railsAdapter.d.ts.map +1 -0
  99. package/dist/core/adapters/railsAdapter.js +52 -0
  100. package/dist/core/adapters/railsAdapter.js.map +1 -0
  101. package/dist/core/adapters/salesforceAdapter.d.ts +16 -0
  102. package/dist/core/adapters/salesforceAdapter.d.ts.map +1 -0
  103. package/dist/core/adapters/salesforceAdapter.js +64 -0
  104. package/dist/core/adapters/salesforceAdapter.js.map +1 -0
  105. package/dist/core/adapters/sdk.d.ts +83 -0
  106. package/dist/core/adapters/sdk.d.ts.map +1 -0
  107. package/dist/core/adapters/sdk.js +114 -0
  108. package/dist/core/adapters/sdk.js.map +1 -0
  109. package/dist/core/ccp.d.ts +37 -0
  110. package/dist/core/ccp.d.ts.map +1 -0
  111. package/dist/core/ccp.js +184 -0
  112. package/dist/core/ccp.js.map +1 -0
  113. package/dist/core/gitAnalyzer.d.ts +74 -0
  114. package/dist/core/gitAnalyzer.d.ts.map +1 -0
  115. package/dist/core/gitAnalyzer.js +298 -0
  116. package/dist/core/gitAnalyzer.js.map +1 -0
  117. package/dist/core/incrementalAnalyzer.d.ts +28 -0
  118. package/dist/core/incrementalAnalyzer.d.ts.map +1 -0
  119. package/dist/core/incrementalAnalyzer.js +343 -0
  120. package/dist/core/incrementalAnalyzer.js.map +1 -0
  121. package/dist/core/knowledgeGraphBuilder.d.ts +31 -0
  122. package/dist/core/knowledgeGraphBuilder.d.ts.map +1 -0
  123. package/dist/core/knowledgeGraphBuilder.js +197 -0
  124. package/dist/core/knowledgeGraphBuilder.js.map +1 -0
  125. package/dist/core/lazyAnalyzer.d.ts +57 -0
  126. package/dist/core/lazyAnalyzer.d.ts.map +1 -0
  127. package/dist/core/lazyAnalyzer.js +204 -0
  128. package/dist/core/lazyAnalyzer.js.map +1 -0
  129. package/dist/core/schema.d.ts +57 -0
  130. package/dist/core/schema.d.ts.map +1 -0
  131. package/dist/core/schema.js +131 -0
  132. package/dist/core/schema.js.map +1 -0
  133. package/dist/core/semanticContexts.d.ts +40 -0
  134. package/dist/core/semanticContexts.d.ts.map +1 -0
  135. package/dist/core/semanticContexts.js +454 -0
  136. package/dist/core/semanticContexts.js.map +1 -0
  137. package/docs/es/guide/adapters.md +143 -0
  138. package/docs/es/guide/ai-repository-schema.md +119 -0
  139. package/docs/es/guide/features.md +67 -0
  140. package/docs/es/guide/flows.md +134 -0
  141. package/docs/es/guide/git-intelligence.md +170 -0
  142. package/docs/es/guide/incremental-analysis.md +131 -0
  143. package/docs/es/guide/knowledge-graph.md +135 -0
  144. package/docs/es/guide/lazy-indexing.md +144 -0
  145. package/docs/es/guide/performance.md +125 -0
  146. package/docs/guide/adapters.md +225 -0
  147. package/docs/guide/ai-repository-schema.md +119 -0
  148. package/docs/guide/architecture.md +69 -1
  149. package/docs/guide/flows.md +134 -0
  150. package/docs/guide/git-intelligence.md +170 -0
  151. package/docs/guide/incremental-analysis.md +131 -0
  152. package/docs/guide/knowledge-graph.md +135 -0
  153. package/docs/guide/lazy-indexing.md +144 -0
  154. package/docs/guide/performance.md +125 -0
  155. package/package.json +5 -2
  156. package/src/analyzers/androidResources.ts +113 -0
  157. package/src/analyzers/dependencies.ts +41 -0
  158. package/src/analyzers/entrypoints.ts +80 -1
  159. package/src/analyzers/gradleModules.ts +100 -0
  160. package/src/analyzers/techStack.ts +56 -0
  161. package/src/commands/ai-first.ts +342 -1
  162. package/src/core/adapters/adapterRegistry.ts +187 -0
  163. package/src/core/adapters/baseAdapter.ts +82 -0
  164. package/src/core/adapters/community/fastapiAdapter.ts +50 -0
  165. package/src/core/adapters/community/index.ts +11 -0
  166. package/src/core/adapters/community/laravelAdapter.ts +56 -0
  167. package/src/core/adapters/community/nestjsAdapter.ts +57 -0
  168. package/src/core/adapters/community/phoenixAdapter.ts +54 -0
  169. package/src/core/adapters/community/springBootAdapter.ts +53 -0
  170. package/src/core/adapters/dotnetAdapter.ts +104 -0
  171. package/src/core/adapters/index.ts +24 -0
  172. package/src/core/adapters/javascriptAdapter.ts +56 -0
  173. package/src/core/adapters/pythonAdapter.ts +118 -0
  174. package/src/core/adapters/railsAdapter.ts +65 -0
  175. package/src/core/adapters/salesforceAdapter.ts +76 -0
  176. package/src/core/adapters/sdk.ts +172 -0
  177. package/src/core/ccp.ts +240 -0
  178. package/src/core/gitAnalyzer.ts +391 -0
  179. package/src/core/incrementalAnalyzer.ts +382 -0
  180. package/src/core/knowledgeGraphBuilder.ts +181 -0
  181. package/src/core/lazyAnalyzer.ts +261 -0
  182. package/src/core/schema.ts +157 -0
  183. package/src/core/semanticContexts.ts +575 -0
  184. package/tests/adapters.test.ts +159 -0
  185. package/tests/gitAnalyzer.test.ts +133 -0
  186. package/tests/incrementalAnalyzer.test.ts +83 -0
  187. package/tests/knowledgeGraph.test.ts +146 -0
  188. package/tests/lazyAnalyzer.test.ts +230 -0
  189. package/tests/schema.test.ts +203 -0
  190. package/tests/semanticContexts.test.ts +435 -0
  191. package/ai/context/analyzers.Symbol.json +0 -19
  192. package/ai/context/analyzers.extractSymbols.json +0 -19
@@ -0,0 +1,575 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { ensureDir, writeFile, readJsonFile } from "../utils/fileUtils.js";
4
+
5
+ export interface Feature {
6
+ name: string;
7
+ path: string;
8
+ files: string[];
9
+ entrypoints: string[];
10
+ dependencies: string[];
11
+ }
12
+
13
+ export interface Flow {
14
+ name: string;
15
+ entrypoint: string;
16
+ files: string[];
17
+ depth: number;
18
+ layers: string[];
19
+ }
20
+
21
+ export interface SemanticContexts {
22
+ features: Feature[];
23
+ flows: Flow[];
24
+ }
25
+
26
+ // ============================================================
27
+ // CONFIGURATION - Feature Detection Rules
28
+ // ============================================================
29
+
30
+ // 1. Candidate roots - scan inside these directories
31
+ const CANDIDATE_ROOTS = ["src", "app", "packages", "services", "modules", "features"];
32
+
33
+ // 2. Ignore folders - these are technical, not business features
34
+ const IGNORED_FOLDERS = new Set([
35
+ "utils",
36
+ "helpers",
37
+ "types",
38
+ "interfaces",
39
+ "constants",
40
+ "config",
41
+ "dto",
42
+ "models",
43
+ "common",
44
+ "shared"
45
+ ]);
46
+
47
+ // 3. Entrypoint patterns - files that indicate a business feature
48
+ const ENTRYPOINT_PATTERNS = [
49
+ "controller",
50
+ "route",
51
+ "handler",
52
+ "command",
53
+ "service"
54
+ ];
55
+
56
+ // 4. Flow-specific patterns
57
+ const FLOW_ENTRY_PATTERNS = ["controller", "route", "handler", "command"];
58
+
59
+ // 5. Flow exclusion patterns
60
+ const FLOW_EXCLUDE = new Set([
61
+ "repository", "repo", "utils", "helper", "model", "entity",
62
+ "dto", "type", "interface", "constant", "config"
63
+ ]);
64
+
65
+ // 6. Layer detection
66
+ const LAYER_PATTERNS: Record<string, string[]> = {
67
+ api: ["controller", "handler", "route", "router", "api", "endpoint"],
68
+ service: ["service", "services", "usecase", "interactor"],
69
+ data: ["repository", "repo", "dal", "dao", "data", "persistence"],
70
+ domain: ["model", "entity", "schema", "domain"],
71
+ util: ["util", "helper", "lib", "common"]
72
+ };
73
+
74
+ const LAYER_PRIORITY: Record<string, number> = {
75
+ api: 1, controller: 1, handler: 1, route: 1, router: 1,
76
+ service: 2, usecase: 2, interactor: 2,
77
+ data: 3, repository: 3, repo: 3, dal: 3, dao: 3, persistence: 3,
78
+ model: 4, entity: 4, domain: 4,
79
+ };
80
+
81
+ const MAX_FLOW_DEPTH = 5;
82
+ const MAX_FLOW_FILES = 30;
83
+
84
+ // Supported source file extensions
85
+ const SOURCE_EXTENSIONS = new Set([
86
+ ".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".kt", ".go", ".rs", ".rb", ".php", ".cs", ".vue", ".svelte"
87
+ ]);
88
+
89
+ // ============================================================
90
+ // HELPER FUNCTIONS
91
+ // ============================================================
92
+
93
+ function isSourceFile(filePath: string): boolean {
94
+ const ext = path.extname(filePath).toLowerCase();
95
+ return SOURCE_EXTENSIONS.has(ext);
96
+ }
97
+
98
+ function isEntrypoint(filePath: string): boolean {
99
+ const basename = path.basename(filePath).toLowerCase();
100
+ return ENTRYPOINT_PATTERNS.some(pattern => basename.includes(pattern));
101
+ }
102
+
103
+ function isFlowEntrypoint(filePath: string): boolean {
104
+ const basename = path.basename(filePath).toLowerCase();
105
+ return FLOW_ENTRY_PATTERNS.some(pattern => basename.includes(pattern));
106
+ }
107
+
108
+ function isIgnoredFolder(folderName: string): boolean {
109
+ return IGNORED_FOLDERS.has(folderName.toLowerCase());
110
+ }
111
+
112
+ function isFlowExcluded(filePath: string): boolean {
113
+ const lower = filePath.toLowerCase();
114
+ return Array.from(FLOW_EXCLUDE).some(p => lower.includes("/" + p) || lower.includes("\\" + p));
115
+ }
116
+
117
+ function getLayer(filePath: string): string {
118
+ const parts = filePath.split(/[/\\]/).map(s =>
119
+ s.toLowerCase().replace(/\.(ts|js|tsx|jsx)$/, "")
120
+ );
121
+
122
+ for (const [layer, patterns] of Object.entries(LAYER_PATTERNS)) {
123
+ if (parts.some(s => patterns.includes(s))) {
124
+ return layer;
125
+ }
126
+ }
127
+ return "unknown";
128
+ }
129
+
130
+ function getLayerPriority(filePath: string): number {
131
+ const parts = [...filePath.split(/[/\\]/)].reverse();
132
+ for (const p of parts) {
133
+ const name = p.replace(/\.(ts|js|tsx|jsx)$/, "").toLowerCase();
134
+ if (LAYER_PRIORITY[name] !== undefined) {
135
+ return LAYER_PRIORITY[name];
136
+ }
137
+ }
138
+ return 99;
139
+ }
140
+
141
+ // ============================================================
142
+ // FEATURE DETECTION ALGORITHM
143
+ // ============================================================
144
+
145
+ /**
146
+ * Find feature candidates by scanning file paths
147
+ *
148
+ * Rules:
149
+ * - Scan inside: src/*, app/*, packages/*, services/*, modules/*, features/*
150
+ * - Ignore: utils, helpers, types, interfaces, constants, config, dto, models, common, shared
151
+ * - Support depth 1 and 2: src/auth, src/modules/auth
152
+ * - Must have ≥3 source files and ≥1 entrypoint
153
+ */
154
+ function findFeatureCandidates(files: string[]): Map<string, string[]> {
155
+ const candidates = new Map<string, string[]>();
156
+
157
+ for (const file of files) {
158
+ const parts = file.split("/");
159
+
160
+ // Find candidate root index
161
+ const rootIdx = parts.findIndex(p =>
162
+ CANDIDATE_ROOTS.includes(p.toLowerCase())
163
+ );
164
+
165
+ if (rootIdx === -1) continue;
166
+
167
+ // Check depth 1: src/auth
168
+ for (let depth = 1; depth <= 2; depth++) {
169
+ const featureIdx = rootIdx + depth;
170
+
171
+ // Don't go beyond array bounds
172
+ if (featureIdx >= parts.length - 1) continue;
173
+
174
+ const featureName = parts[featureIdx];
175
+
176
+ // Skip ignored folders
177
+ if (isIgnoredFolder(featureName)) continue;
178
+
179
+ // Skip if feature name is a file (depth too deep)
180
+ if (featureName.includes(".")) continue;
181
+
182
+ // Build feature path
183
+ const featurePath = parts.slice(0, featureIdx + 1).join("/");
184
+
185
+ if (!candidates.has(featurePath)) {
186
+ candidates.set(featurePath, []);
187
+ }
188
+ candidates.get(featurePath)!.push(file);
189
+ }
190
+ }
191
+
192
+ return candidates;
193
+ }
194
+
195
+ /**
196
+ * Extract dependencies from modules.json for a feature
197
+ */
198
+ function getFeatureDependencies(
199
+ featurePath: string,
200
+ modules: Record<string, { path: string; files: string[] }>
201
+ ): string[] {
202
+ const deps: string[] = [];
203
+ const featureFiles = Object.values(modules).find(m => m.path === featurePath)?.files || [];
204
+
205
+ // Get all other modules and check if any of their files import from feature files
206
+ for (const [modName, mod] of Object.entries(modules)) {
207
+ if (mod.path === featurePath) continue;
208
+
209
+ for (const file of mod.files || []) {
210
+ // Simple heuristic: if file path contains feature name, it's related
211
+ const featureName = featurePath.split("/").pop() || "";
212
+ if (file.toLowerCase().includes(featureName.toLowerCase())) {
213
+ if (!deps.includes(modName)) {
214
+ deps.push(modName);
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ return deps;
221
+ }
222
+
223
+ /**
224
+ * Generate features from modules.json
225
+ *
226
+ * Output format:
227
+ * {
228
+ * "name": "auth",
229
+ * "path": "src/auth",
230
+ * "files": [],
231
+ * "entrypoints": [],
232
+ * "dependencies": []
233
+ * }
234
+ */
235
+ export function generateFeatures(
236
+ modulesJsonPath: string,
237
+ _symbolsJsonPath: string
238
+ ): Feature[] {
239
+ const features: Feature[] = [];
240
+
241
+ // Load modules
242
+ let modules: Record<string, { path: string; files: string[] }> = {};
243
+ try {
244
+ if (fs.existsSync(modulesJsonPath)) {
245
+ const data = readJsonFile(modulesJsonPath);
246
+ modules = (data as any).modules || {};
247
+ }
248
+ } catch (e) {
249
+ // Ignore errors, return empty
250
+ }
251
+
252
+ // Get all files from modules
253
+ const allFiles = Object.values(modules).flatMap(m => m.files || []);
254
+
255
+ // Find candidate features
256
+ const candidates = findFeatureCandidates(allFiles);
257
+
258
+ // Filter and validate candidates
259
+ for (const [featurePath, files] of candidates) {
260
+ // Filter to source files only
261
+ const sourceFiles = files.filter(isSourceFile);
262
+
263
+ // Must have at least 3 source files
264
+ if (sourceFiles.length < 3) continue;
265
+
266
+ // Must have at least one entrypoint
267
+ const entrypoints = sourceFiles.filter(isEntrypoint);
268
+ if (entrypoints.length === 0) continue;
269
+
270
+ // Extract feature name from path
271
+ const featureName = featurePath.split("/").pop() || featurePath;
272
+
273
+ // Get dependencies
274
+ const dependencies = getFeatureDependencies(featurePath, modules);
275
+
276
+ features.push({
277
+ name: featureName,
278
+ path: featurePath,
279
+ files: sourceFiles.slice(0, 50),
280
+ entrypoints: entrypoints.slice(0, 10),
281
+ dependencies
282
+ });
283
+ }
284
+
285
+ return features;
286
+ }
287
+
288
+ // ============================================================
289
+ // FLOW DETECTION ALGORITHM
290
+ // ============================================================
291
+
292
+ /**
293
+ * Generate flows from symbol graph
294
+ */
295
+ function flowsFromGraph(graphData: any): Flow[] {
296
+ const flows: Flow[] = [];
297
+ const { symbols = [], relationships = [] } = graphData;
298
+
299
+ // Build symbol lookup
300
+ const bySymbol: Record<string, any[]> = {};
301
+ for (const r of relationships) {
302
+ if (!bySymbol[r.symbolId]) bySymbol[r.symbolId] = [];
303
+ bySymbol[r.symbolId].push(r);
304
+ }
305
+
306
+ // Find entrypoint symbols
307
+ const entrypoints = symbols.filter((s: any) =>
308
+ s.file && isFlowEntrypoint(s.file)
309
+ );
310
+
311
+ for (const ep of entrypoints) {
312
+ const visited = new Set<string>();
313
+ const fileSet = new Set<string>();
314
+ const layerSet = new Set<string>();
315
+
316
+ const traverse = (symbolId: string, depth: number) => {
317
+ if (depth > MAX_FLOW_DEPTH || visited.has(symbolId) || fileSet.size >= MAX_FLOW_FILES) {
318
+ return;
319
+ }
320
+
321
+ visited.add(symbolId);
322
+
323
+ const symbol = symbols.find((s: any) => s.id === symbolId);
324
+ if (!symbol?.file || isFlowExcluded(symbol.file)) return;
325
+
326
+ fileSet.add(symbol.file);
327
+ layerSet.add(getLayer(symbol.file));
328
+
329
+ // Follow relationships
330
+ for (const r of bySymbol[symbolId] || []) {
331
+ if (["calls", "imports", "references"].includes(r.type)) {
332
+ traverse(r.targetId, depth + 1);
333
+ }
334
+ }
335
+ };
336
+
337
+ if (ep.file) {
338
+ fileSet.add(ep.file);
339
+ layerSet.add(getLayer(ep.file));
340
+
341
+ for (const r of bySymbol[ep.id] || []) {
342
+ if (["calls", "imports", "references"].includes(r.type)) {
343
+ traverse(r.targetId, 1);
344
+ }
345
+ }
346
+ }
347
+
348
+ // Must have at least 3 files and 2 layers
349
+ if (fileSet.size >= 3 && layerSet.size >= 2) {
350
+ flows.push({
351
+ name: path.basename(ep.file || ""),
352
+ entrypoint: ep.file || "",
353
+ files: Array.from(fileSet).slice(0, MAX_FLOW_FILES),
354
+ depth: Math.min(MAX_FLOW_DEPTH, [...visited].length),
355
+ layers: Array.from(layerSet)
356
+ });
357
+ }
358
+ }
359
+
360
+ return flows;
361
+ }
362
+
363
+ /**
364
+ * Generate flows from folder structure (fallback)
365
+ */
366
+ function flowsFromFolders(files: string[]): Flow[] {
367
+ const flows: Flow[] = [];
368
+
369
+ // Group by feature prefix (e.g., authController -> auth)
370
+ const byFeature = new Map<string, string[]>();
371
+
372
+ for (const file of files) {
373
+ if (!isSourceFile(file)) continue;
374
+
375
+ const baseName = path.basename(file).replace(/\.(ts|js|tsx|jsx)$/, "");
376
+ // Extract feature: authController -> auth, userService -> user
377
+ const feature = baseName.replace(/(Controller|Service|Repository|Handler|Route|Model|Entity)$/i, "");
378
+ const key = feature.toLowerCase();
379
+
380
+ if (!byFeature.has(key)) {
381
+ byFeature.set(key, []);
382
+ }
383
+ byFeature.get(key)!.push(file);
384
+ }
385
+
386
+ for (const [feature, featureFiles] of byFeature) {
387
+ if (featureFiles.length < 3) continue;
388
+
389
+ const layers = new Set(featureFiles.map(getLayer).filter(l => l !== "unknown"));
390
+ if (layers.size < 2) continue;
391
+
392
+ const sorted = [...featureFiles].sort((a, b) =>
393
+ getLayerPriority(a) - getLayerPriority(b)
394
+ );
395
+
396
+ const entrypoint = sorted.find(isFlowEntrypoint) || sorted[0];
397
+
398
+ flows.push({
399
+ name: feature,
400
+ entrypoint,
401
+ files: sorted.slice(0, MAX_FLOW_FILES),
402
+ depth: layers.size,
403
+ layers: Array.from(layers)
404
+ });
405
+ }
406
+
407
+ return flows;
408
+ }
409
+
410
+ /**
411
+ * Generate flows from dependencies (fallback)
412
+ */
413
+ function flowsFromImports(
414
+ dependenciesPath: string,
415
+ files: string[]
416
+ ): Flow[] {
417
+ const flows: Flow[] = [];
418
+
419
+ let deps: any = { byFile: {} };
420
+ try {
421
+ if (fs.existsSync(dependenciesPath)) {
422
+ deps = readJsonFile(dependenciesPath);
423
+ }
424
+ } catch {
425
+ return flows;
426
+ }
427
+
428
+ // Build import graph
429
+ const importsTo = new Map<string, Set<string>>();
430
+ for (const [file, imports] of Object.entries(deps.byFile || {})) {
431
+ if (!importsTo.has(file)) {
432
+ importsTo.set(file, new Set());
433
+ }
434
+ for (const imp of imports as string[]) {
435
+ importsTo.get(file)!.add(imp);
436
+ }
437
+ }
438
+
439
+ // Find entrypoints
440
+ const entrypoints = files.filter(isFlowEntrypoint);
441
+
442
+ for (const ep of entrypoints) {
443
+ const visited = new Set<string>([ep]);
444
+ const fileSet = new Set<string>([ep]);
445
+ const layerSet = new Set<string>();
446
+
447
+ const traverse = (file: string, depth: number) => {
448
+ if (depth > MAX_FLOW_DEPTH || fileSet.size >= MAX_FLOW_FILES) return;
449
+
450
+ for (const imp of importsTo.get(file) || []) {
451
+ if (visited.has(imp) || isFlowExcluded(imp)) continue;
452
+
453
+ visited.add(imp);
454
+ fileSet.add(imp);
455
+ layerSet.add(getLayer(imp));
456
+ traverse(imp, depth + 1);
457
+ }
458
+ };
459
+
460
+ traverse(ep, 1);
461
+
462
+ if (fileSet.size >= 3 && layerSet.size >= 2) {
463
+ flows.push({
464
+ name: path.basename(ep, path.extname(ep)),
465
+ entrypoint: ep,
466
+ files: Array.from(fileSet),
467
+ depth: Math.min(MAX_FLOW_DEPTH, [...visited].length),
468
+ layers: Array.from(layerSet)
469
+ });
470
+ }
471
+ }
472
+
473
+ return flows;
474
+ }
475
+
476
+ /**
477
+ * Generate flows using multiple fallback methods
478
+ */
479
+ export function generateFlows(
480
+ graphPath: string,
481
+ modulesPath: string,
482
+ dependenciesPath?: string
483
+ ): Flow[] {
484
+ // Load graph data
485
+ let graphData: any = { symbols: [], relationships: [] };
486
+ try {
487
+ if (fs.existsSync(graphPath)) {
488
+ graphData = readJsonFile(graphPath);
489
+ }
490
+ } catch {
491
+ // Ignore
492
+ }
493
+
494
+ // Load modules
495
+ let modules: Record<string, { path: string; files: string[] }> = {};
496
+ try {
497
+ if (fs.existsSync(modulesPath)) {
498
+ modules = (readJsonFile(modulesPath) as any).modules || {};
499
+ }
500
+ } catch {
501
+ // Ignore
502
+ }
503
+
504
+ const allFiles = Object.values(modules).flatMap(m => m.files || []);
505
+
506
+ // Determine if graph is weak
507
+ const relationshipCount = graphData.relationships?.length || 0;
508
+ const symbolCount = graphData.symbols?.length || 1;
509
+ const density = relationshipCount / symbolCount;
510
+ const isWeakGraph = density < 0.5 || relationshipCount < 10;
511
+
512
+ const flows: Flow[] = [];
513
+
514
+ // Try graph-based flow detection first (if graph is strong)
515
+ if (!isWeakGraph && relationshipCount > 0) {
516
+ flows.push(...flowsFromGraph(graphData));
517
+ }
518
+
519
+ // Fallback to folder-based detection
520
+ if (flows.length === 0 || isWeakGraph) {
521
+ flows.push(...flowsFromFolders(allFiles));
522
+ }
523
+
524
+ // Fallback to import-based detection
525
+ if (flows.length === 0 || isWeakGraph) {
526
+ flows.push(...flowsFromImports(dependenciesPath || "", allFiles));
527
+ }
528
+
529
+ // Deduplicate by name
530
+ const seen = new Set<string>();
531
+ return flows
532
+ .filter(f => {
533
+ if (seen.has(f.name)) return false;
534
+ seen.add(f.name);
535
+ return true;
536
+ })
537
+ .slice(0, 20);
538
+ }
539
+
540
+ // ============================================================
541
+ // MAIN ENTRY POINT
542
+ // ============================================================
543
+
544
+ /**
545
+ * Generate all semantic contexts (features and flows)
546
+ */
547
+ export function generateSemanticContexts(aiDir: string): SemanticContexts {
548
+ const featuresDir = path.join(aiDir, "context", "features");
549
+ const flowsDir = path.join(aiDir, "context", "flows");
550
+
551
+ const modulesPath = path.join(aiDir, "modules.json");
552
+ const symbolsPath = path.join(aiDir, "symbols.json");
553
+ const graphPath = path.join(aiDir, "graph", "symbol-graph.json");
554
+ const dependenciesPath = path.join(aiDir, "dependencies.json");
555
+
556
+ // Generate features and flows
557
+ const features = generateFeatures(modulesPath, symbolsPath);
558
+ const flows = generateFlows(graphPath, modulesPath, dependenciesPath);
559
+
560
+ // Write features
561
+ ensureDir(featuresDir);
562
+ for (const feature of features) {
563
+ const filePath = path.join(featuresDir, `${feature.name}.json`);
564
+ writeFile(filePath, JSON.stringify(feature, null, 2));
565
+ }
566
+
567
+ // Write flows
568
+ ensureDir(flowsDir);
569
+ for (const flow of flows) {
570
+ const filePath = path.join(flowsDir, `${flow.name}.json`);
571
+ writeFile(filePath, JSON.stringify(flow, null, 2));
572
+ }
573
+
574
+ return { features, flows };
575
+ }