ai-first-cli 1.3.5 → 1.3.8
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 +186 -0
- package/README.es.md +68 -0
- package/README.md +53 -15
- package/ai/graph/knowledge-graph.json +1 -1
- package/ai-context/index-state.json +86 -2
- package/dist/analyzers/architecture.d.ts.map +1 -1
- package/dist/analyzers/architecture.js +72 -5
- package/dist/analyzers/architecture.js.map +1 -1
- package/dist/analyzers/entrypoints.d.ts.map +1 -1
- package/dist/analyzers/entrypoints.js +253 -0
- package/dist/analyzers/entrypoints.js.map +1 -1
- package/dist/analyzers/symbols.d.ts.map +1 -1
- package/dist/analyzers/symbols.js +47 -2
- package/dist/analyzers/symbols.js.map +1 -1
- package/dist/analyzers/techStack.d.ts.map +1 -1
- package/dist/analyzers/techStack.js +86 -0
- package/dist/analyzers/techStack.js.map +1 -1
- package/dist/commands/ai-first.d.ts.map +1 -1
- package/dist/commands/ai-first.js +78 -4
- package/dist/commands/ai-first.js.map +1 -1
- package/dist/config/configLoader.d.ts +6 -0
- package/dist/config/configLoader.d.ts.map +1 -0
- package/dist/config/configLoader.js +232 -0
- package/dist/config/configLoader.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +101 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/core/content/contentProcessor.d.ts +4 -0
- package/dist/core/content/contentProcessor.d.ts.map +1 -0
- package/dist/core/content/contentProcessor.js +235 -0
- package/dist/core/content/contentProcessor.js.map +1 -0
- package/dist/core/content/index.d.ts +3 -0
- package/dist/core/content/index.d.ts.map +1 -0
- package/dist/core/content/index.js +2 -0
- package/dist/core/content/index.js.map +1 -0
- package/dist/core/content/types.d.ts +32 -0
- package/dist/core/content/types.d.ts.map +1 -0
- package/dist/core/content/types.js +2 -0
- package/dist/core/content/types.js.map +1 -0
- package/dist/core/gitAnalyzer.d.ts +14 -0
- package/dist/core/gitAnalyzer.d.ts.map +1 -1
- package/dist/core/gitAnalyzer.js +98 -0
- package/dist/core/gitAnalyzer.js.map +1 -1
- package/dist/core/multiRepo/index.d.ts +3 -0
- package/dist/core/multiRepo/index.d.ts.map +1 -0
- package/dist/core/multiRepo/index.js +2 -0
- package/dist/core/multiRepo/index.js.map +1 -0
- package/dist/core/multiRepo/multiRepoScanner.d.ts +18 -0
- package/dist/core/multiRepo/multiRepoScanner.d.ts.map +1 -0
- package/dist/core/multiRepo/multiRepoScanner.js +131 -0
- package/dist/core/multiRepo/multiRepoScanner.js.map +1 -0
- package/dist/core/rag/index.d.ts +3 -0
- package/dist/core/rag/index.d.ts.map +1 -0
- package/dist/core/rag/index.js +2 -0
- package/dist/core/rag/index.js.map +1 -0
- package/dist/core/rag/vectorIndex.d.ts +28 -0
- package/dist/core/rag/vectorIndex.d.ts.map +1 -0
- package/dist/core/rag/vectorIndex.js +71 -0
- package/dist/core/rag/vectorIndex.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +154 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/utils/fileUtils.d.ts.map +1 -1
- package/dist/utils/fileUtils.js +5 -0
- package/dist/utils/fileUtils.js.map +1 -1
- package/docs/planning/evaluator-v1.0.0/README.md +112 -0
- package/docs/planning/evaluator-v1.0.0/improvements_plan_2026-03-28.md +237 -0
- package/package.json +13 -3
- package/src/analyzers/architecture.ts +75 -6
- package/src/analyzers/entrypoints.ts +285 -0
- package/src/analyzers/symbols.ts +52 -2
- package/src/analyzers/techStack.ts +90 -0
- package/src/commands/ai-first.ts +83 -4
- package/src/config/configLoader.ts +274 -0
- package/src/config/index.ts +27 -0
- package/src/config/types.ts +117 -0
- package/src/core/content/contentProcessor.ts +292 -0
- package/src/core/content/index.ts +9 -0
- package/src/core/content/types.ts +35 -0
- package/src/core/gitAnalyzer.ts +130 -0
- package/src/core/multiRepo/index.ts +2 -0
- package/src/core/multiRepo/multiRepoScanner.ts +177 -0
- package/src/core/rag/index.ts +2 -0
- package/src/core/rag/vectorIndex.ts +105 -0
- package/src/mcp/index.ts +1 -0
- package/src/mcp/server.ts +179 -0
- package/src/utils/fileUtils.ts +5 -0
- package/tests/entrypoints-languages.test.ts +373 -0
- package/tests/framework-detection.test.ts +296 -0
- package/tests/v1.3.8-integration.test.ts +361 -0
- package/BETA_EVALUATION_REPORT.md +0 -151
- package/ai-context/context/flows/App.json +0 -17
- package/ai-context/context/flows/DashboardPage.json +0 -14
- package/ai-context/context/flows/LoginPage.json +0 -14
- package/ai-context/context/flows/admin.json +0 -10
- package/ai-context/context/flows/androidresources.json +0 -11
- package/ai-context/context/flows/authController.json +0 -14
- package/ai-context/context/flows/entrypoints.json +0 -9
- package/ai-context/context/flows/fastapiAdapter.json +0 -14
- package/ai-context/context/flows/fastapiadapter.json +0 -11
- package/ai-context/context/flows/index.json +0 -19
- package/ai-context/context/flows/indexer.json +0 -9
- package/ai-context/context/flows/indexstate.json +0 -9
- package/ai-context/context/flows/init.json +0 -22
- package/ai-context/context/flows/main.json +0 -18
- package/ai-context/context/flows/mainactivity.json +0 -9
- package/ai-context/context/flows/models.json +0 -15
- package/ai-context/context/flows/posts.json +0 -15
- package/ai-context/context/flows/repoMapper.json +0 -20
- package/ai-context/context/flows/repomapper.json +0 -11
- package/ai-context/context/flows/serializers.json +0 -10
- package/ai-context-evaluation-report-1774223059505.md +0 -206
- package/dist/scripts/ai-context-evaluator.js +0 -367
- package/quick-evaluation-report-1774396002305.md +0 -64
- package/quick-evaluator.ts +0 -200
- package/scripts/ai-context-evaluator.ts +0 -440
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { detectTechStack } from "../src/analyzers/techStack.js";
|
|
3
|
+
import { analyzeArchitecture } from "../src/analyzers/architecture.js";
|
|
4
|
+
import { FileInfo } from "../src/core/repoScanner.js";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import os from "os";
|
|
8
|
+
|
|
9
|
+
function createTempProjectDir(files: Record<string, string>): string {
|
|
10
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-framework-test-"));
|
|
11
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
12
|
+
const fullPath = path.join(tempDir, filePath);
|
|
13
|
+
const dir = path.dirname(fullPath);
|
|
14
|
+
if (!fs.existsSync(dir)) {
|
|
15
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
fs.writeFileSync(fullPath, content);
|
|
18
|
+
}
|
|
19
|
+
return tempDir;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createFileInfo(relativePath: string, name: string, extension: string): FileInfo {
|
|
23
|
+
return {
|
|
24
|
+
path: path.join("/tmp", relativePath),
|
|
25
|
+
relativePath,
|
|
26
|
+
name,
|
|
27
|
+
extension,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("Framework Detection - NestJS", () => {
|
|
32
|
+
it("should detect NestJS from @nestjs/common in dependencies", () => {
|
|
33
|
+
const tempDir = createTempProjectDir({
|
|
34
|
+
"package.json": JSON.stringify({
|
|
35
|
+
name: "nestjs-app",
|
|
36
|
+
dependencies: {
|
|
37
|
+
"@nestjs/common": "^10.0.0",
|
|
38
|
+
"@nestjs/core": "^10.0.0",
|
|
39
|
+
"@nestjs/platform-express": "^10.0.0",
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
"src/main.ts": "// main file",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const files: FileInfo[] = [
|
|
46
|
+
createFileInfo("package.json", "package.json", ""),
|
|
47
|
+
createFileInfo("src/main.ts", "main.ts", "ts"),
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const techStack = detectTechStack(files, tempDir);
|
|
51
|
+
|
|
52
|
+
expect(techStack.frameworks).toContain("NestJS");
|
|
53
|
+
|
|
54
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should detect NestJS from @nestjs/* packages in devDependencies", () => {
|
|
58
|
+
const tempDir = createTempProjectDir({
|
|
59
|
+
"package.json": JSON.stringify({
|
|
60
|
+
name: "nestjs-app",
|
|
61
|
+
devDependencies: {
|
|
62
|
+
"@nestjs/testing": "^10.0.0",
|
|
63
|
+
"@nestjs/cli": "^10.0.0",
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const files: FileInfo[] = [createFileInfo("package.json", "package.json", "")];
|
|
69
|
+
|
|
70
|
+
const techStack = detectTechStack(files, tempDir);
|
|
71
|
+
|
|
72
|
+
expect(techStack.frameworks).toContain("NestJS");
|
|
73
|
+
|
|
74
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should not detect NestJS when only 'nest' is present without @nestjs/*", () => {
|
|
78
|
+
const tempDir = createTempProjectDir({
|
|
79
|
+
"package.json": JSON.stringify({
|
|
80
|
+
name: "other-app",
|
|
81
|
+
dependencies: {
|
|
82
|
+
"nestjs-stuff": "1.0.0",
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const files: FileInfo[] = [createFileInfo("package.json", "package.json", "")];
|
|
88
|
+
|
|
89
|
+
const techStack = detectTechStack(files, tempDir);
|
|
90
|
+
|
|
91
|
+
// Should not detect NestJS from "nestjs-stuff" package
|
|
92
|
+
expect(techStack.frameworks).not.toContain("NestJS");
|
|
93
|
+
|
|
94
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("Framework Detection - Spring Boot", () => {
|
|
99
|
+
it("should detect Spring Boot from pom.xml", () => {
|
|
100
|
+
const tempDir = createTempProjectDir({
|
|
101
|
+
"pom.xml": `<?xml version="1.0" encoding="UTF-8"?>
|
|
102
|
+
<project>
|
|
103
|
+
<parent>
|
|
104
|
+
<groupId>org.springframework.boot</groupId>
|
|
105
|
+
<artifactId>spring-boot-starter-parent</artifactId>
|
|
106
|
+
<version>3.0.0</version>
|
|
107
|
+
</parent>
|
|
108
|
+
<dependencies>
|
|
109
|
+
<dependency>
|
|
110
|
+
<groupId>org.springframework.boot</groupId>
|
|
111
|
+
<artifactId>spring-boot-starter-web</artifactId>
|
|
112
|
+
</dependency>
|
|
113
|
+
</dependencies>
|
|
114
|
+
</project>`,
|
|
115
|
+
"src/main/java/com/example/Application.java": "package com.example;",
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const files: FileInfo[] = [
|
|
119
|
+
createFileInfo("pom.xml", "pom.xml", ""),
|
|
120
|
+
createFileInfo("src/main/java/com/example/Application.java", "Application.java", "java"),
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
const techStack = detectTechStack(files, tempDir);
|
|
124
|
+
|
|
125
|
+
expect(techStack.frameworks).toContain("Spring Boot");
|
|
126
|
+
|
|
127
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should detect Spring Boot from build.gradle", () => {
|
|
131
|
+
const tempDir = createTempProjectDir({
|
|
132
|
+
"build.gradle": `plugins {
|
|
133
|
+
id 'java'
|
|
134
|
+
id 'org.springframework.boot' version '3.0.0'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
dependencies {
|
|
138
|
+
implementation 'org.springframework.boot:spring-boot-starter-web'
|
|
139
|
+
}`,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const files: FileInfo[] = [
|
|
143
|
+
createFileInfo("build.gradle", "build.gradle", ""),
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const techStack = detectTechStack(files, tempDir);
|
|
147
|
+
|
|
148
|
+
expect(techStack.frameworks).toContain("Spring Boot");
|
|
149
|
+
|
|
150
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should detect Spring Boot from build.gradle.kts", () => {
|
|
154
|
+
const tempDir = createTempProjectDir({
|
|
155
|
+
"build.gradle.kts": `plugins {
|
|
156
|
+
java
|
|
157
|
+
id("org.springframework.boot") version "3.0.0"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
dependencies {
|
|
161
|
+
implementation("org.springframework.boot:spring-boot-starter-web")
|
|
162
|
+
}`,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const files: FileInfo[] = [
|
|
166
|
+
createFileInfo("build.gradle.kts", "build.gradle.kts", ""),
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
const techStack = detectTechStack(files, tempDir);
|
|
170
|
+
|
|
171
|
+
expect(techStack.frameworks).toContain("Spring Boot");
|
|
172
|
+
|
|
173
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should detect Spring Boot from spring-boot in pom.xml", () => {
|
|
177
|
+
const tempDir = createTempProjectDir({
|
|
178
|
+
"pom.xml": `<?xml version="1.0" encoding="UTF-8"?>
|
|
179
|
+
<project>
|
|
180
|
+
<dependencies>
|
|
181
|
+
<dependency>
|
|
182
|
+
<groupId>org.springframework.boot</groupId>
|
|
183
|
+
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
|
184
|
+
</dependency>
|
|
185
|
+
</dependencies>
|
|
186
|
+
</project>`,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const files: FileInfo[] = [createFileInfo("pom.xml", "pom.xml", "")];
|
|
190
|
+
|
|
191
|
+
const techStack = detectTechStack(files, tempDir);
|
|
192
|
+
|
|
193
|
+
expect(techStack.frameworks).toContain("Spring Boot");
|
|
194
|
+
|
|
195
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("Architecture Detection - Microservices vs API Server", () => {
|
|
200
|
+
it("should detect API Server for single services directory", () => {
|
|
201
|
+
const files: FileInfo[] = [
|
|
202
|
+
createFileInfo("src/controllers/user.js", "user.js", "js"),
|
|
203
|
+
createFileInfo("src/services/userService.js", "userService.js", "js"),
|
|
204
|
+
createFileInfo("package.json", "package.json", ""),
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
const architecture = analyzeArchitecture(files, "/tmp");
|
|
208
|
+
|
|
209
|
+
// Should detect API Server, not Microservices
|
|
210
|
+
expect(architecture.description).not.toContain("Microservices");
|
|
211
|
+
expect(architecture.description).toContain("API Server");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should detect Microservices for multiple service directories", () => {
|
|
215
|
+
const files: FileInfo[] = [
|
|
216
|
+
createFileInfo("services/user/src/index.js", "index.js", "js"),
|
|
217
|
+
createFileInfo("services/order/src/index.js", "index.js", "js"),
|
|
218
|
+
createFileInfo("services/payment/src/index.js", "index.js", "js"),
|
|
219
|
+
createFileInfo("package.json", "package.json", ""),
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
const architecture = analyzeArchitecture(files, "/tmp");
|
|
223
|
+
|
|
224
|
+
// Should detect Microservices when there are multiple service directories
|
|
225
|
+
expect(architecture.description).toContain("Microservices");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should detect API Server for single api directory", () => {
|
|
229
|
+
const files: FileInfo[] = [
|
|
230
|
+
createFileInfo("api/routes.js", "routes.js", "js"),
|
|
231
|
+
createFileInfo("api/controllers.js", "controllers.js", "js"),
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
const architecture = analyzeArchitecture(files, "/tmp");
|
|
235
|
+
|
|
236
|
+
// Should detect API Server, not Microservices
|
|
237
|
+
expect(architecture.description).not.toContain("Microservices");
|
|
238
|
+
expect(architecture.description).toContain("API Server");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("should detect API Server for nested api directories", () => {
|
|
242
|
+
const files: FileInfo[] = [
|
|
243
|
+
createFileInfo("api/v1/routes.js", "routes.js", "js"),
|
|
244
|
+
createFileInfo("api/v2/routes.js", "routes.js", "js"),
|
|
245
|
+
createFileInfo("api/v3/routes.js", "routes.js", "js"),
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
const architecture = analyzeArchitecture(files, "/tmp");
|
|
249
|
+
|
|
250
|
+
// Should detect API Server for nested api directories, not Microservices
|
|
251
|
+
expect(architecture.description).not.toContain("Microservices");
|
|
252
|
+
expect(architecture.description).toContain("API Server");
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe("Framework Detection - Integration Tests", () => {
|
|
257
|
+
it("should detect NestJS in nestjs-backend test project", () => {
|
|
258
|
+
const testProjectPath = path.join(process.cwd(), "test-projects/nestjs-backend");
|
|
259
|
+
|
|
260
|
+
const files: FileInfo[] = [
|
|
261
|
+
createFileInfo("package.json", "package.json", ""),
|
|
262
|
+
createFileInfo("src/main.ts", "main.ts", "ts"),
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
const techStack = detectTechStack(files, testProjectPath);
|
|
266
|
+
|
|
267
|
+
expect(techStack.frameworks).toContain("NestJS");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("should detect Spring Boot in spring-boot-app test project", () => {
|
|
271
|
+
const testProjectPath = path.join(process.cwd(), "test-projects/spring-boot-app");
|
|
272
|
+
|
|
273
|
+
const files: FileInfo[] = [
|
|
274
|
+
createFileInfo("pom.xml", "pom.xml", ""),
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
const techStack = detectTechStack(files, testProjectPath);
|
|
278
|
+
|
|
279
|
+
expect(techStack.frameworks).toContain("Spring Boot");
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("should detect API Server for express-api test project", () => {
|
|
283
|
+
const testProjectPath = path.join(process.cwd(), "test-projects/express-api");
|
|
284
|
+
|
|
285
|
+
const files: FileInfo[] = [
|
|
286
|
+
createFileInfo("src/routes/index.js", "index.js", "js"),
|
|
287
|
+
createFileInfo("src/services/dataService.js", "dataService.js", "js"),
|
|
288
|
+
];
|
|
289
|
+
|
|
290
|
+
const architecture = analyzeArchitecture(files, testProjectPath);
|
|
291
|
+
|
|
292
|
+
// Should detect API Server, not Microservices
|
|
293
|
+
expect(architecture.description).not.toContain("Microservices");
|
|
294
|
+
expect(architecture.description).toContain("API Server");
|
|
295
|
+
});
|
|
296
|
+
});
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { loadConfig, getPreset, listPresets } from '../src/config/configLoader.js';
|
|
6
|
+
import { processContent, classifyFile } from '../src/core/content/contentProcessor.js';
|
|
7
|
+
import { getGitBlame, formatGitBlame } from '../src/core/gitAnalyzer.js';
|
|
8
|
+
import { createVectorIndex, semanticSearch } from '../src/core/rag/vectorIndex.js';
|
|
9
|
+
import { scanMultiRepo, generateMultiRepoReport } from '../src/core/multiRepo/multiRepoScanner.js';
|
|
10
|
+
import { detectTechStack } from '../src/analyzers/techStack.js';
|
|
11
|
+
import type { FileInfo } from '../src/core/repoScanner.js';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
|
|
16
|
+
describe('v1.3.8 Features Integration Tests', () => {
|
|
17
|
+
const testDir = path.join(__dirname, 'test-temp-v138');
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
if (!fs.existsSync(testDir)) {
|
|
21
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
if (fs.existsSync(testDir)) {
|
|
27
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('Config System', () => {
|
|
32
|
+
it('should load default config when file does not exist', () => {
|
|
33
|
+
const result = loadConfig({ configPath: path.join(testDir, 'non-existent.json') });
|
|
34
|
+
expect(result.config).toBeDefined();
|
|
35
|
+
expect(result.source).toBe('default');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should load config from file', () => {
|
|
39
|
+
const configPath = path.join(testDir, 'ai-first.config.json');
|
|
40
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
41
|
+
version: '1.0',
|
|
42
|
+
output: { directory: 'custom-ai' }
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
const result = loadConfig({ configPath });
|
|
46
|
+
expect(result.source).toBe('file');
|
|
47
|
+
expect(result.config.output?.directory).toBe('custom-ai');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should apply preset configuration', () => {
|
|
51
|
+
const result = loadConfig({
|
|
52
|
+
configPath: path.join(testDir, 'non-existent.json'),
|
|
53
|
+
preset: 'quick'
|
|
54
|
+
});
|
|
55
|
+
expect(result.source).toBe('preset');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should override config with overrides', () => {
|
|
59
|
+
const configPath = path.join(testDir, 'ai-first.config.json');
|
|
60
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
61
|
+
version: '1.0',
|
|
62
|
+
output: { directory: 'from-file', formats: ['md'], prettyPrint: false }
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
const result = loadConfig({
|
|
66
|
+
configPath,
|
|
67
|
+
overrides: {
|
|
68
|
+
output: { directory: 'override', formats: ['md'], prettyPrint: true }
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(result.config.output?.directory).toBe('override');
|
|
73
|
+
expect(result.config.output?.prettyPrint).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should list all 4 builtin presets', () => {
|
|
77
|
+
const presets = listPresets();
|
|
78
|
+
expect(presets.length).toBeGreaterThanOrEqual(4);
|
|
79
|
+
expect(presets.some(p => p.name === 'full')).toBe(true);
|
|
80
|
+
expect(presets.some(p => p.name === 'quick')).toBe(true);
|
|
81
|
+
expect(presets.some(p => p.name === 'api')).toBe(true);
|
|
82
|
+
expect(presets.some(p => p.name === 'docs')).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should get preset by name', () => {
|
|
86
|
+
const preset = getPreset('quick');
|
|
87
|
+
expect(preset).toBeDefined();
|
|
88
|
+
expect(preset?.name).toBe('quick');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('Content Processing', () => {
|
|
93
|
+
it('should compress code with signatures mode', () => {
|
|
94
|
+
const code = `
|
|
95
|
+
/**
|
|
96
|
+
* Test function
|
|
97
|
+
*/
|
|
98
|
+
export function test(x: number): number {
|
|
99
|
+
const result = x * 2;
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
103
|
+
const result = processContent(code, { detailLevel: 'signatures' });
|
|
104
|
+
expect(result.compressionRatio).toBeGreaterThan(0);
|
|
105
|
+
expect(result.tokens).toBeLessThan(code.length / 4);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should not compress with full mode', () => {
|
|
109
|
+
const code = 'function test() { return 42; }';
|
|
110
|
+
const result = processContent(code, { detailLevel: 'full' });
|
|
111
|
+
expect(result.compressionRatio).toBe(0);
|
|
112
|
+
expect(result.processedLength).toBe(code.length);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should extract signatures from TypeScript', () => {
|
|
116
|
+
const code = `
|
|
117
|
+
export function calculateTotal(items: CartItem[]): number {
|
|
118
|
+
return items.reduce((sum, item) => sum + item.price, 0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface CartItem {
|
|
122
|
+
price: number;
|
|
123
|
+
quantity: number;
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
const result = processContent(code, { detailLevel: 'signatures' });
|
|
127
|
+
expect(result.content).toContain('calculateTotal');
|
|
128
|
+
expect(result.content).toContain('interface CartItem');
|
|
129
|
+
expect(result.compressionRatio).toBeGreaterThan(40);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should classify files correctly', () => {
|
|
133
|
+
expect(classifyFile('test.spec.js', ['src/**/*'], [], [], ['**/*.spec.js'])).toBe('exclude');
|
|
134
|
+
expect(classifyFile('node_modules/lodash/index.js', ['src/**/*'], ['node_modules/**/*'], [], [])).toBe('compress');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('Git Blame', () => {
|
|
139
|
+
it('should return blame info for a file', () => {
|
|
140
|
+
const result = getGitBlame('.', 'package.json');
|
|
141
|
+
expect(result.filePath).toBe('package.json');
|
|
142
|
+
expect(result.lines).toBeDefined();
|
|
143
|
+
expect(result.lines.length).toBeGreaterThan(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should return authors map', () => {
|
|
147
|
+
const result = getGitBlame('.', 'package.json');
|
|
148
|
+
expect(result.authors).toBeInstanceOf(Map);
|
|
149
|
+
expect(result.authors.size).toBeGreaterThan(0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should format blame as inline', () => {
|
|
153
|
+
const blame = getGitBlame('.', 'package.json');
|
|
154
|
+
const formatted = formatGitBlame(blame, 'inline');
|
|
155
|
+
expect(formatted).toContain('[');
|
|
156
|
+
expect(formatted).toContain(']');
|
|
157
|
+
expect(formatted.split('\n').length).toBeGreaterThan(1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should format blame as block', () => {
|
|
161
|
+
const blame = getGitBlame('.', 'package.json');
|
|
162
|
+
const formatted = formatGitBlame(blame, 'block');
|
|
163
|
+
expect(formatted).toContain('//');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('RAG Vector Search', () => {
|
|
168
|
+
it('should create vector index', () => {
|
|
169
|
+
const indexPath = path.join(testDir, 'vector-index.json');
|
|
170
|
+
const index = createVectorIndex(indexPath);
|
|
171
|
+
expect(index).toBeDefined();
|
|
172
|
+
expect(typeof index.addDocument).toBe('function');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should add and search documents', () => {
|
|
176
|
+
const indexPath = path.join(testDir, 'vector-index.json');
|
|
177
|
+
const index = createVectorIndex(indexPath);
|
|
178
|
+
|
|
179
|
+
index.addDocument({
|
|
180
|
+
id: '1',
|
|
181
|
+
content: 'function authenticateUser() { }',
|
|
182
|
+
embedding: Array(100).fill(0).map((_, i) => Math.sin(i * 0.1)),
|
|
183
|
+
metadata: { filePath: 'auth.js', type: 'function' }
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
index.addDocument({
|
|
187
|
+
id: '2',
|
|
188
|
+
content: 'function processPayment() { }',
|
|
189
|
+
embedding: Array(100).fill(0).map((_, i) => Math.cos(i * 0.1)),
|
|
190
|
+
metadata: { filePath: 'payment.js', type: 'function' }
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const results = semanticSearch(index, 'user authentication', 2);
|
|
194
|
+
expect(results).toHaveLength(2);
|
|
195
|
+
expect(results[0].score).toBeGreaterThan(0);
|
|
196
|
+
expect(results[0].document).toBeDefined();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should save and load index', () => {
|
|
200
|
+
const indexPath = path.join(testDir, 'vector-index.json');
|
|
201
|
+
const index = createVectorIndex(indexPath);
|
|
202
|
+
|
|
203
|
+
index.addDocument({
|
|
204
|
+
id: '1',
|
|
205
|
+
content: 'test',
|
|
206
|
+
embedding: Array(100).fill(0.5),
|
|
207
|
+
metadata: { filePath: 'test.js' }
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
index.save();
|
|
211
|
+
expect(fs.existsSync(indexPath)).toBe(true);
|
|
212
|
+
|
|
213
|
+
const index2 = createVectorIndex(indexPath);
|
|
214
|
+
const results = semanticSearch(index2, 'test', 1);
|
|
215
|
+
expect(results).toHaveLength(1);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('Multi-Repository', () => {
|
|
220
|
+
it('should scan current repository', () => {
|
|
221
|
+
const context = scanMultiRepo({ repositories: ['.'] });
|
|
222
|
+
expect(context.repositories).toHaveLength(1);
|
|
223
|
+
expect(context.totalFiles).toBeGreaterThan(0);
|
|
224
|
+
expect(context.repositories[0].files.length).toBeGreaterThan(0);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should generate markdown report', () => {
|
|
228
|
+
const context = scanMultiRepo({ repositories: ['.'] });
|
|
229
|
+
const report = generateMultiRepoReport(context);
|
|
230
|
+
expect(report).toContain('# Multi-Repository Context');
|
|
231
|
+
expect(report).toContain('## Summary');
|
|
232
|
+
expect(report).toContain('## Repositories');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should detect multiple repositories', () => {
|
|
236
|
+
const repo1 = path.join(testDir, 'repo1');
|
|
237
|
+
const repo2 = path.join(testDir, 'repo2');
|
|
238
|
+
fs.mkdirSync(repo1, { recursive: true });
|
|
239
|
+
fs.mkdirSync(repo2, { recursive: true });
|
|
240
|
+
fs.writeFileSync(path.join(repo1, 'file1.js'), '');
|
|
241
|
+
fs.writeFileSync(path.join(repo2, 'file2.js'), '');
|
|
242
|
+
|
|
243
|
+
const context = scanMultiRepo({ repositories: [repo1, repo2] });
|
|
244
|
+
expect(context.repositories).toHaveLength(2);
|
|
245
|
+
expect(context.totalFiles).toBe(2);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('Django Detection (v1.3.8)', () => {
|
|
250
|
+
const DJANGO_PROJECT = path.join(__dirname, '..', 'test-projects', 'django-app');
|
|
251
|
+
|
|
252
|
+
it('should detect Django framework from Python project', () => {
|
|
253
|
+
const files: FileInfo[] = [
|
|
254
|
+
{ path: 'blog/models.py', relativePath: 'blog/models.py', name: 'models.py', extension: 'py' },
|
|
255
|
+
{ path: 'blog/views.py', relativePath: 'blog/views.py', name: 'views.py', extension: 'py' },
|
|
256
|
+
{ path: 'users/models.py', relativePath: 'users/models.py', name: 'models.py', extension: 'py' }
|
|
257
|
+
];
|
|
258
|
+
const techStack = detectTechStack(files, DJANGO_PROJECT);
|
|
259
|
+
expect(techStack.frameworks).toContain('Django');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should detect Django from requirements.txt', () => {
|
|
263
|
+
const techStackPath = path.join(DJANGO_PROJECT, 'ai-context', 'tech_stack.md');
|
|
264
|
+
expect(fs.existsSync(techStackPath)).toBe(true);
|
|
265
|
+
|
|
266
|
+
const content = fs.readFileSync(techStackPath, 'utf-8').toLowerCase();
|
|
267
|
+
expect(content).toContain('django');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should include Django in tech_stack.md frameworks section', () => {
|
|
271
|
+
const techStackPath = path.join(DJANGO_PROJECT, 'ai-context', 'tech_stack.md');
|
|
272
|
+
const content = fs.readFileSync(techStackPath, 'utf-8');
|
|
273
|
+
expect(content).toMatch(/## Frameworks\n[\s\S]*- Django/);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should generate ai_context.md with Django context', () => {
|
|
277
|
+
const aiContextPath = path.join(DJANGO_PROJECT, 'ai-context', 'ai_context.md');
|
|
278
|
+
expect(fs.existsSync(aiContextPath)).toBe(true);
|
|
279
|
+
|
|
280
|
+
const content = fs.readFileSync(aiContextPath, 'utf-8').toLowerCase();
|
|
281
|
+
expect(content).toContain('django');
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// MCP Integration tests removed - MCP command does not exist yet
|
|
286
|
+
// TODO(julian): Add MCP tests when the feature is implemented
|
|
287
|
+
|
|
288
|
+
describe('Config Presets (v1.3.8)', () => {
|
|
289
|
+
it('should have preset available: full', () => {
|
|
290
|
+
const preset = getPreset('full');
|
|
291
|
+
expect(preset).toBeDefined();
|
|
292
|
+
expect(preset?.name).toBe('full');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should have preset available: quick', () => {
|
|
296
|
+
const preset = getPreset('quick');
|
|
297
|
+
expect(preset).toBeDefined();
|
|
298
|
+
expect(preset?.name).toBe('quick');
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('should have preset available: api', () => {
|
|
302
|
+
const preset = getPreset('api');
|
|
303
|
+
expect(preset).toBeDefined();
|
|
304
|
+
expect(preset?.name).toBe('api');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should have preset available: docs', () => {
|
|
308
|
+
const preset = getPreset('docs');
|
|
309
|
+
expect(preset).toBeDefined();
|
|
310
|
+
expect(preset?.name).toBe('docs');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should list at least 4 presets', () => {
|
|
314
|
+
const presets = listPresets();
|
|
315
|
+
expect(presets.length).toBeGreaterThanOrEqual(4);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should apply preset to config', () => {
|
|
319
|
+
const result = loadConfig({
|
|
320
|
+
configPath: path.join(testDir, 'non-existent.json'),
|
|
321
|
+
preset: 'full'
|
|
322
|
+
});
|
|
323
|
+
expect(result.source).toBe('preset');
|
|
324
|
+
expect(result.config).toBeDefined();
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('Git Blame Integration (v1.3.8)', () => {
|
|
329
|
+
it('should return blame info for a file', () => {
|
|
330
|
+
const result = getGitBlame('.', 'package.json');
|
|
331
|
+
expect(result.filePath).toBe('package.json');
|
|
332
|
+
expect(result.lines).toBeDefined();
|
|
333
|
+
expect(result.lines.length).toBeGreaterThan(0);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should return authors with contributions', () => {
|
|
337
|
+
const result = getGitBlame('.', 'package.json');
|
|
338
|
+
expect(result.authors).toBeInstanceOf(Map);
|
|
339
|
+
expect(result.authors.size).toBeGreaterThan(0);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should format blame as inline', () => {
|
|
343
|
+
const blame = getGitBlame('.', 'package.json');
|
|
344
|
+
const formatted = formatGitBlame(blame, 'inline');
|
|
345
|
+
expect(formatted).toContain('[');
|
|
346
|
+
expect(formatted).toContain(']');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should format blame as block', () => {
|
|
350
|
+
const blame = getGitBlame('.', 'package.json');
|
|
351
|
+
const formatted = formatGitBlame(blame, 'block');
|
|
352
|
+
expect(formatted).toContain('//');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should include blame info in summary', () => {
|
|
356
|
+
const blame = getGitBlame('.', 'package.json');
|
|
357
|
+
const formatted = formatGitBlame(blame, 'inline');
|
|
358
|
+
expect(formatted).toContain('\n');
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
});
|