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.
- package/CHANGELOG.md +78 -0
- package/README.es.md +137 -1
- package/README.md +136 -4
- package/ai/ai_context.md +2 -2
- package/ai/architecture.md +3 -3
- package/ai/cache.json +85 -57
- package/ai/ccp/jira-123/context.json +7 -0
- package/ai/context/repo.json +56 -0
- package/ai/context/utils.json +7 -0
- package/ai/dependencies.json +51 -1026
- package/ai/files.json +195 -3
- package/ai/git/commit-activity.json +8646 -0
- package/ai/git/recent-features.json +1 -0
- package/ai/git/recent-files.json +52 -0
- package/ai/git/recent-flows.json +1 -0
- package/ai/graph/knowledge-graph.json +43643 -0
- package/ai/graph/module-graph.json +4 -0
- package/ai/graph/symbol-graph.json +3307 -879
- package/ai/graph/symbol-references.json +119 -32
- package/ai/index-state.json +843 -188
- package/ai/index.db +0 -0
- package/ai/modules.json +4 -0
- package/ai/repo-map.json +81 -17
- package/ai/repo_map.json +81 -17
- package/ai/repo_map.md +21 -7
- package/ai/summary.md +5 -5
- package/ai/symbols.json +1 -20287
- package/dist/analyzers/androidResources.d.ts +23 -0
- package/dist/analyzers/androidResources.d.ts.map +1 -0
- package/dist/analyzers/androidResources.js +93 -0
- package/dist/analyzers/androidResources.js.map +1 -0
- package/dist/analyzers/dependencies.d.ts.map +1 -1
- package/dist/analyzers/dependencies.js +37 -0
- package/dist/analyzers/dependencies.js.map +1 -1
- package/dist/analyzers/entrypoints.d.ts.map +1 -1
- package/dist/analyzers/entrypoints.js +71 -1
- package/dist/analyzers/entrypoints.js.map +1 -1
- package/dist/analyzers/gradleModules.d.ts +22 -0
- package/dist/analyzers/gradleModules.d.ts.map +1 -0
- package/dist/analyzers/gradleModules.js +75 -0
- package/dist/analyzers/gradleModules.js.map +1 -0
- package/dist/analyzers/techStack.d.ts +7 -0
- package/dist/analyzers/techStack.d.ts.map +1 -1
- package/dist/analyzers/techStack.js +44 -1
- package/dist/analyzers/techStack.js.map +1 -1
- package/dist/commands/ai-first.d.ts.map +1 -1
- package/dist/commands/ai-first.js +311 -1
- package/dist/commands/ai-first.js.map +1 -1
- package/dist/core/adapters/adapterRegistry.d.ts +39 -0
- package/dist/core/adapters/adapterRegistry.d.ts.map +1 -0
- package/dist/core/adapters/adapterRegistry.js +155 -0
- package/dist/core/adapters/adapterRegistry.js.map +1 -0
- package/dist/core/adapters/baseAdapter.d.ts +49 -0
- package/dist/core/adapters/baseAdapter.d.ts.map +1 -0
- package/dist/core/adapters/baseAdapter.js +28 -0
- package/dist/core/adapters/baseAdapter.js.map +1 -0
- package/dist/core/adapters/community/fastapiAdapter.d.ts +7 -0
- package/dist/core/adapters/community/fastapiAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/fastapiAdapter.js +40 -0
- package/dist/core/adapters/community/fastapiAdapter.js.map +1 -0
- package/dist/core/adapters/community/index.d.ts +11 -0
- package/dist/core/adapters/community/index.d.ts.map +1 -0
- package/dist/core/adapters/community/index.js +11 -0
- package/dist/core/adapters/community/index.js.map +1 -0
- package/dist/core/adapters/community/laravelAdapter.d.ts +7 -0
- package/dist/core/adapters/community/laravelAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/laravelAdapter.js +47 -0
- package/dist/core/adapters/community/laravelAdapter.js.map +1 -0
- package/dist/core/adapters/community/nestjsAdapter.d.ts +7 -0
- package/dist/core/adapters/community/nestjsAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/nestjsAdapter.js +48 -0
- package/dist/core/adapters/community/nestjsAdapter.js.map +1 -0
- package/dist/core/adapters/community/phoenixAdapter.d.ts +7 -0
- package/dist/core/adapters/community/phoenixAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/phoenixAdapter.js +45 -0
- package/dist/core/adapters/community/phoenixAdapter.js.map +1 -0
- package/dist/core/adapters/community/springBootAdapter.d.ts +7 -0
- package/dist/core/adapters/community/springBootAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/springBootAdapter.js +44 -0
- package/dist/core/adapters/community/springBootAdapter.js.map +1 -0
- package/dist/core/adapters/dotnetAdapter.d.ts +20 -0
- package/dist/core/adapters/dotnetAdapter.d.ts.map +1 -0
- package/dist/core/adapters/dotnetAdapter.js +86 -0
- package/dist/core/adapters/dotnetAdapter.js.map +1 -0
- package/dist/core/adapters/index.d.ts +18 -0
- package/dist/core/adapters/index.d.ts.map +1 -0
- package/dist/core/adapters/index.js +19 -0
- package/dist/core/adapters/index.js.map +1 -0
- package/dist/core/adapters/javascriptAdapter.d.ts +11 -0
- package/dist/core/adapters/javascriptAdapter.d.ts.map +1 -0
- package/dist/core/adapters/javascriptAdapter.js +47 -0
- package/dist/core/adapters/javascriptAdapter.js.map +1 -0
- package/dist/core/adapters/pythonAdapter.d.ts +20 -0
- package/dist/core/adapters/pythonAdapter.d.ts.map +1 -0
- package/dist/core/adapters/pythonAdapter.js +99 -0
- package/dist/core/adapters/pythonAdapter.js.map +1 -0
- package/dist/core/adapters/railsAdapter.d.ts +10 -0
- package/dist/core/adapters/railsAdapter.d.ts.map +1 -0
- package/dist/core/adapters/railsAdapter.js +52 -0
- package/dist/core/adapters/railsAdapter.js.map +1 -0
- package/dist/core/adapters/salesforceAdapter.d.ts +16 -0
- package/dist/core/adapters/salesforceAdapter.d.ts.map +1 -0
- package/dist/core/adapters/salesforceAdapter.js +64 -0
- package/dist/core/adapters/salesforceAdapter.js.map +1 -0
- package/dist/core/adapters/sdk.d.ts +83 -0
- package/dist/core/adapters/sdk.d.ts.map +1 -0
- package/dist/core/adapters/sdk.js +114 -0
- package/dist/core/adapters/sdk.js.map +1 -0
- package/dist/core/ccp.d.ts +37 -0
- package/dist/core/ccp.d.ts.map +1 -0
- package/dist/core/ccp.js +184 -0
- package/dist/core/ccp.js.map +1 -0
- package/dist/core/gitAnalyzer.d.ts +74 -0
- package/dist/core/gitAnalyzer.d.ts.map +1 -0
- package/dist/core/gitAnalyzer.js +298 -0
- package/dist/core/gitAnalyzer.js.map +1 -0
- package/dist/core/incrementalAnalyzer.d.ts +28 -0
- package/dist/core/incrementalAnalyzer.d.ts.map +1 -0
- package/dist/core/incrementalAnalyzer.js +343 -0
- package/dist/core/incrementalAnalyzer.js.map +1 -0
- package/dist/core/knowledgeGraphBuilder.d.ts +31 -0
- package/dist/core/knowledgeGraphBuilder.d.ts.map +1 -0
- package/dist/core/knowledgeGraphBuilder.js +197 -0
- package/dist/core/knowledgeGraphBuilder.js.map +1 -0
- package/dist/core/lazyAnalyzer.d.ts +57 -0
- package/dist/core/lazyAnalyzer.d.ts.map +1 -0
- package/dist/core/lazyAnalyzer.js +204 -0
- package/dist/core/lazyAnalyzer.js.map +1 -0
- package/dist/core/schema.d.ts +57 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +131 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/semanticContexts.d.ts +40 -0
- package/dist/core/semanticContexts.d.ts.map +1 -0
- package/dist/core/semanticContexts.js +454 -0
- package/dist/core/semanticContexts.js.map +1 -0
- package/docs/es/guide/adapters.md +143 -0
- package/docs/es/guide/ai-repository-schema.md +119 -0
- package/docs/es/guide/features.md +67 -0
- package/docs/es/guide/flows.md +134 -0
- package/docs/es/guide/git-intelligence.md +170 -0
- package/docs/es/guide/incremental-analysis.md +131 -0
- package/docs/es/guide/knowledge-graph.md +135 -0
- package/docs/es/guide/lazy-indexing.md +144 -0
- package/docs/es/guide/performance.md +125 -0
- package/docs/guide/adapters.md +225 -0
- package/docs/guide/ai-repository-schema.md +119 -0
- package/docs/guide/architecture.md +69 -1
- package/docs/guide/flows.md +134 -0
- package/docs/guide/git-intelligence.md +170 -0
- package/docs/guide/incremental-analysis.md +131 -0
- package/docs/guide/knowledge-graph.md +135 -0
- package/docs/guide/lazy-indexing.md +144 -0
- package/docs/guide/performance.md +125 -0
- package/package.json +5 -2
- package/src/analyzers/androidResources.ts +113 -0
- package/src/analyzers/dependencies.ts +41 -0
- package/src/analyzers/entrypoints.ts +80 -1
- package/src/analyzers/gradleModules.ts +100 -0
- package/src/analyzers/techStack.ts +56 -0
- package/src/commands/ai-first.ts +342 -1
- package/src/core/adapters/adapterRegistry.ts +187 -0
- package/src/core/adapters/baseAdapter.ts +82 -0
- package/src/core/adapters/community/fastapiAdapter.ts +50 -0
- package/src/core/adapters/community/index.ts +11 -0
- package/src/core/adapters/community/laravelAdapter.ts +56 -0
- package/src/core/adapters/community/nestjsAdapter.ts +57 -0
- package/src/core/adapters/community/phoenixAdapter.ts +54 -0
- package/src/core/adapters/community/springBootAdapter.ts +53 -0
- package/src/core/adapters/dotnetAdapter.ts +104 -0
- package/src/core/adapters/index.ts +24 -0
- package/src/core/adapters/javascriptAdapter.ts +56 -0
- package/src/core/adapters/pythonAdapter.ts +118 -0
- package/src/core/adapters/railsAdapter.ts +65 -0
- package/src/core/adapters/salesforceAdapter.ts +76 -0
- package/src/core/adapters/sdk.ts +172 -0
- package/src/core/ccp.ts +240 -0
- package/src/core/gitAnalyzer.ts +391 -0
- package/src/core/incrementalAnalyzer.ts +382 -0
- package/src/core/knowledgeGraphBuilder.ts +181 -0
- package/src/core/lazyAnalyzer.ts +261 -0
- package/src/core/schema.ts +157 -0
- package/src/core/semanticContexts.ts +575 -0
- package/tests/adapters.test.ts +159 -0
- package/tests/gitAnalyzer.test.ts +133 -0
- package/tests/incrementalAnalyzer.test.ts +83 -0
- package/tests/knowledgeGraph.test.ts +146 -0
- package/tests/lazyAnalyzer.test.ts +230 -0
- package/tests/schema.test.ts +203 -0
- package/tests/semanticContexts.test.ts +435 -0
- package/ai/context/analyzers.Symbol.json +0 -19
- 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
|
+
}
|