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,391 @@
1
+ /**
2
+ * Git Intelligence Analyzer
3
+ *
4
+ * Analyzes recent git activity to provide AI agents with context about
5
+ * recently changed files, features, and flows.
6
+ */
7
+
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { execSync } from "child_process";
11
+ import { ensureDir, writeFile, readJsonFile } from "../utils/fileUtils.js";
12
+
13
+ export interface GitCommit {
14
+ hash: string;
15
+ date: string;
16
+ message: string;
17
+ author: string;
18
+ files: string[];
19
+ }
20
+
21
+ export interface RecentFile {
22
+ path: string;
23
+ commitCount: number;
24
+ lastChanged: string;
25
+ }
26
+
27
+ export interface GitActivity {
28
+ totalCommits: number;
29
+ dateRange: {
30
+ start: string;
31
+ end: string;
32
+ };
33
+ files: Record<string, number>;
34
+ features: Record<string, number>;
35
+ flows: Record<string, number>;
36
+ }
37
+
38
+ export interface GitAnalyzerOptions {
39
+ /** Number of commits to analyze (default: 50) */
40
+ commitLimit?: number;
41
+ /** Ignore commits older than N days (default: 30) */
42
+ maxAgeDays?: number;
43
+ /** Maximum number of files to track (default: 50) */
44
+ maxFiles?: number;
45
+ }
46
+
47
+ const DEFAULT_OPTIONS: Required<GitAnalyzerOptions> = {
48
+ commitLimit: 50,
49
+ maxAgeDays: 30,
50
+ maxFiles: 50
51
+ };
52
+
53
+ /**
54
+ * Check if a directory is a git repository
55
+ */
56
+ export function detectGitRepository(rootDir: string): boolean {
57
+ const gitDir = path.join(rootDir, ".git");
58
+ return fs.existsSync(gitDir);
59
+ }
60
+
61
+ /**
62
+ * Execute git command and return output
63
+ */
64
+ function gitExec(rootDir: string, command: string): string {
65
+ try {
66
+ return execSync(command, {
67
+ cwd: rootDir,
68
+ encoding: "utf-8",
69
+ stdio: ["pipe", "pipe", "ignore"]
70
+ }).trim();
71
+ } catch {
72
+ return "";
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Get recent commits from git repository
78
+ */
79
+ export function getRecentCommits(rootDir: string, limit: number = 50): GitCommit[] {
80
+ if (!detectGitRepository(rootDir)) {
81
+ return [];
82
+ }
83
+
84
+ const commits: GitCommit[] = [];
85
+
86
+ // Get commit hashes
87
+ const logFormat = "%H|%ai|%s|%an";
88
+ const logOutput = gitExec(rootDir, `git log --format="${logFormat}" -n ${limit}`);
89
+
90
+ if (!logOutput) {
91
+ return [];
92
+ }
93
+
94
+ const lines = logOutput.split("\n");
95
+ const maxAgeDate = new Date();
96
+ maxAgeDate.setDate(maxAgeDate.getDate() - DEFAULT_OPTIONS.maxAgeDays);
97
+
98
+ for (const line of lines) {
99
+ if (!line.trim()) continue;
100
+
101
+ const [hash, dateStr, message, author] = line.split("|");
102
+ const commitDate = new Date(dateStr);
103
+
104
+ // Skip commits older than maxAgeDays
105
+ if (commitDate < maxAgeDate) {
106
+ break;
107
+ }
108
+
109
+ // Get files changed in this commit
110
+ const filesOutput = gitExec(rootDir, `git diff-tree --no-commit-id --name-only -r ${hash}`);
111
+ const files = filesOutput ? filesOutput.split("\n").filter(f => f.trim()) : [];
112
+
113
+ commits.push({
114
+ hash,
115
+ date: dateStr,
116
+ message,
117
+ author,
118
+ files
119
+ });
120
+ }
121
+
122
+ return commits;
123
+ }
124
+
125
+ /**
126
+ * Extract changed files from commits
127
+ */
128
+ export function extractChangedFiles(commits: GitCommit[]): RecentFile[] {
129
+ const fileStats: Map<string, { count: number; lastChanged: string }> = new Map();
130
+
131
+ for (const commit of commits) {
132
+ for (const file of commit.files) {
133
+ const existing = fileStats.get(file);
134
+ if (existing) {
135
+ existing.count++;
136
+ if (commit.date > existing.lastChanged) {
137
+ existing.lastChanged = commit.date;
138
+ }
139
+ } else {
140
+ fileStats.set(file, {
141
+ count: 1,
142
+ lastChanged: commit.date
143
+ });
144
+ }
145
+ }
146
+ }
147
+
148
+ // Convert to array and sort by commit count
149
+ const result: RecentFile[] = Array.from(fileStats.entries())
150
+ .map(([path, stats]) => ({
151
+ path,
152
+ commitCount: stats.count,
153
+ lastChanged: stats.lastChanged
154
+ }))
155
+ .sort((a, b) => b.commitCount - a.commitCount);
156
+
157
+ return result.slice(0, DEFAULT_OPTIONS.maxFiles);
158
+ }
159
+
160
+ /**
161
+ * Get list of changed files
162
+ */
163
+ export function getRecentFiles(rootDir: string): string[] {
164
+ const commits = getRecentCommits(rootDir);
165
+ const recentFiles = extractChangedFiles(commits);
166
+ return recentFiles.map(f => f.path);
167
+ }
168
+
169
+ /**
170
+ * Load features from ai/context/features/
171
+ */
172
+ function loadFeatures(aiDir: string): Map<string, string[]> {
173
+ const featuresMap = new Map<string, string[]>();
174
+ const featuresDir = path.join(aiDir, "context", "features");
175
+
176
+ if (!fs.existsSync(featuresDir)) {
177
+ return featuresMap;
178
+ }
179
+
180
+ try {
181
+ const files = fs.readdirSync(featuresDir);
182
+ for (const file of files) {
183
+ if (!file.endsWith(".json")) continue;
184
+
185
+ const featurePath = path.join(featuresDir, file);
186
+ const featureData = readJsonFile(featurePath) as { name: string; files: string[] };
187
+
188
+ if (featureData && featureData.name && featureData.files) {
189
+ // Map each file in the feature to the feature name
190
+ for (const filePath of featureData.files) {
191
+ const existing = featuresMap.get(filePath) || [];
192
+ existing.push(featureData.name);
193
+ featuresMap.set(filePath, existing);
194
+ }
195
+ }
196
+ }
197
+ } catch {
198
+ // Ignore errors reading features
199
+ }
200
+
201
+ return featuresMap;
202
+ }
203
+
204
+ /**
205
+ * Load flows from ai/context/flows/
206
+ */
207
+ function loadFlows(aiDir: string): Map<string, string[]> {
208
+ const flowsMap = new Map<string, string[]>();
209
+ const flowsDir = path.join(aiDir, "context", "flows");
210
+
211
+ if (!fs.existsSync(flowsDir)) {
212
+ return flowsMap;
213
+ }
214
+
215
+ try {
216
+ const files = fs.readdirSync(flowsDir);
217
+ for (const file of files) {
218
+ if (!file.endsWith(".json")) continue;
219
+
220
+ const flowPath = path.join(flowsDir, file);
221
+ const flowData = readJsonFile(flowPath) as { name: string; files: string[] };
222
+
223
+ if (flowData && flowData.name && flowData.files) {
224
+ // Map each file in the flow to the flow name
225
+ for (const filePath of flowData.files) {
226
+ const existing = flowsMap.get(filePath) || [];
227
+ existing.push(flowData.name);
228
+ flowsMap.set(filePath, existing);
229
+ }
230
+ }
231
+ }
232
+ } catch {
233
+ // Ignore errors reading flows
234
+ }
235
+
236
+ return flowsMap;
237
+ }
238
+
239
+ /**
240
+ * Map changed files to features
241
+ */
242
+ export function mapFilesToFeatures(rootDir: string, files: string[]): string[] {
243
+ const aiDir = path.join(rootDir, "ai");
244
+ const featuresMap = loadFeatures(aiDir);
245
+
246
+ const featureSet = new Set<string>();
247
+
248
+ for (const file of files) {
249
+ // Try exact match first
250
+ const directMatch = featuresMap.get(file);
251
+ if (directMatch) {
252
+ directMatch.forEach(f => featureSet.add(f));
253
+ }
254
+
255
+ // Try partial match (file is inside feature directory)
256
+ for (const [featureFile, featureNames] of featuresMap) {
257
+ if (file.startsWith(featureFile) || featureFile.startsWith(file)) {
258
+ featureNames.forEach(f => featureSet.add(f));
259
+ }
260
+ }
261
+ }
262
+
263
+ return Array.from(featureSet);
264
+ }
265
+
266
+ /**
267
+ * Map changed files to flows
268
+ */
269
+ export function mapFilesToFlows(rootDir: string, files: string[]): string[] {
270
+ const aiDir = path.join(rootDir, "ai");
271
+ const flowsMap = loadFlows(aiDir);
272
+
273
+ const flowSet = new Set<string>();
274
+
275
+ for (const file of files) {
276
+ // Try exact match first
277
+ const directMatch = flowsMap.get(file);
278
+ if (directMatch) {
279
+ directMatch.forEach(f => flowSet.add(f));
280
+ }
281
+
282
+ // Try partial match (file is inside flow)
283
+ for (const [flowFile, flowNames] of flowsMap) {
284
+ if (file.startsWith(flowFile) || flowFile.startsWith(file)) {
285
+ flowNames.forEach(f => flowSet.add(f));
286
+ }
287
+ }
288
+ }
289
+
290
+ return Array.from(flowSet);
291
+ }
292
+
293
+ /**
294
+ * Analyze git activity
295
+ */
296
+ export function analyzeGitActivity(rootDir: string, options: GitAnalyzerOptions = {}): GitActivity | null {
297
+ const opts = { ...DEFAULT_OPTIONS, ...options };
298
+
299
+ if (!detectGitRepository(rootDir)) {
300
+ return null;
301
+ }
302
+
303
+ const commits = getRecentCommits(rootDir, opts.commitLimit);
304
+
305
+ if (commits.length === 0) {
306
+ return null;
307
+ }
308
+
309
+ const recentFiles = extractChangedFiles(commits);
310
+ const recentFilePaths = recentFiles.map(f => f.path);
311
+
312
+ const features = mapFilesToFeatures(rootDir, recentFilePaths);
313
+ const flows = mapFilesToFlows(rootDir, recentFilePaths);
314
+
315
+ // Calculate feature/flow commit counts
316
+ const featureCounts: Record<string, number> = {};
317
+ const flowCounts: Record<string, number> = {};
318
+ const fileCounts: Record<string, number> = {};
319
+
320
+ for (const commit of commits) {
321
+ const commitFeatures = mapFilesToFeatures(rootDir, commit.files);
322
+ for (const feature of commitFeatures) {
323
+ featureCounts[feature] = (featureCounts[feature] || 0) + 1;
324
+ }
325
+
326
+ const commitFlows = mapFilesToFlows(rootDir, commit.files);
327
+ for (const flow of commitFlows) {
328
+ flowCounts[flow] = (flowCounts[flow] || 0) + 1;
329
+ }
330
+
331
+ for (const file of commit.files) {
332
+ fileCounts[file] = (fileCounts[file] || 0) + 1;
333
+ }
334
+ }
335
+
336
+ return {
337
+ totalCommits: commits.length,
338
+ dateRange: {
339
+ start: commits[commits.length - 1]?.date || "",
340
+ end: commits[0]?.date || ""
341
+ },
342
+ files: fileCounts,
343
+ features: featureCounts,
344
+ flows: flowCounts
345
+ };
346
+ }
347
+
348
+ /**
349
+ * Generate git context files
350
+ */
351
+ export function generateGitContext(rootDir: string, aiDir?: string): {
352
+ recentFiles: string[];
353
+ recentFeatures: string[];
354
+ recentFlows: string[];
355
+ activity: GitActivity | null;
356
+ } {
357
+ const targetAiDir = aiDir || path.join(rootDir, "ai");
358
+ const gitDir = path.join(targetAiDir, "git");
359
+
360
+ ensureDir(gitDir);
361
+
362
+ const commits = getRecentCommits(rootDir);
363
+ const recentFiles = extractChangedFiles(commits);
364
+ const recentFilePaths = recentFiles.map(f => f.path);
365
+
366
+ const recentFeatures = mapFilesToFeatures(rootDir, recentFilePaths);
367
+ const recentFlows = mapFilesToFlows(rootDir, recentFilePaths);
368
+ const activity = analyzeGitActivity(rootDir);
369
+
370
+ // Write output files
371
+ const recentFilesJson = JSON.stringify(recentFilePaths, null, 2);
372
+ writeFile(path.join(gitDir, "recent-files.json"), recentFilesJson);
373
+
374
+ const recentFeaturesJson = JSON.stringify(recentFeatures, null, 2);
375
+ writeFile(path.join(gitDir, "recent-features.json"), recentFeaturesJson);
376
+
377
+ const recentFlowsJson = JSON.stringify(recentFlows, null, 2);
378
+ writeFile(path.join(gitDir, "recent-flows.json"), recentFlowsJson);
379
+
380
+ if (activity) {
381
+ const activityJson = JSON.stringify(activity, null, 2);
382
+ writeFile(path.join(gitDir, "commit-activity.json"), activityJson);
383
+ }
384
+
385
+ return {
386
+ recentFiles: recentFilePaths,
387
+ recentFeatures,
388
+ recentFlows,
389
+ activity
390
+ };
391
+ }