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,159 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
detectAdapter,
|
|
4
|
+
detectAllAdapters,
|
|
5
|
+
getAdapter,
|
|
6
|
+
listAdapters
|
|
7
|
+
} from "../src/core/adapters/index.js";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import os from "os";
|
|
11
|
+
|
|
12
|
+
function createTempProjectDir(files: Record<string, string>): string {
|
|
13
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-adapter-test-"));
|
|
14
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
15
|
+
const fullPath = path.join(tempDir, filePath);
|
|
16
|
+
const dir = path.dirname(fullPath);
|
|
17
|
+
if (!fs.existsSync(dir)) {
|
|
18
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
fs.writeFileSync(fullPath, content);
|
|
21
|
+
}
|
|
22
|
+
return tempDir;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("Adapter System", () => {
|
|
26
|
+
describe("detectAdapter", () => {
|
|
27
|
+
it("should detect JavaScript project with package.json", () => {
|
|
28
|
+
const projectDir = createTempProjectDir({
|
|
29
|
+
"package.json": '{"name": "test"}',
|
|
30
|
+
"src/index.js": "// code"
|
|
31
|
+
});
|
|
32
|
+
const adapter = detectAdapter(projectDir);
|
|
33
|
+
expect(adapter.name).toBe("javascript");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should detect TypeScript project with tsconfig.json", () => {
|
|
37
|
+
const projectDir = createTempProjectDir({
|
|
38
|
+
"package.json": '{"name": "test"}',
|
|
39
|
+
"tsconfig.json": '{}'
|
|
40
|
+
});
|
|
41
|
+
const adapter = detectAdapter(projectDir);
|
|
42
|
+
expect(adapter.name).toBe("javascript");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should detect Django project with manage.py", () => {
|
|
46
|
+
const projectDir = createTempProjectDir({
|
|
47
|
+
"manage.py": "# django",
|
|
48
|
+
"settings.py": "DEBUG=True"
|
|
49
|
+
});
|
|
50
|
+
const adapter = detectAdapter(projectDir);
|
|
51
|
+
expect(adapter.name).toBe("django");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should detect Flask project with app.py", () => {
|
|
55
|
+
const projectDir = createTempProjectDir({
|
|
56
|
+
"app.py": "from flask import Flask"
|
|
57
|
+
});
|
|
58
|
+
const adapter = detectAdapter(projectDir);
|
|
59
|
+
expect(adapter.name).toBe("flask");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should detect Rails project with Gemfile", () => {
|
|
63
|
+
const projectDir = createTempProjectDir({
|
|
64
|
+
"Gemfile": "source 'https://rubygems.org'",
|
|
65
|
+
"app/controllers/application_controller.rb": "class ApplicationController"
|
|
66
|
+
});
|
|
67
|
+
const adapter = detectAdapter(projectDir);
|
|
68
|
+
expect(adapter.name).toBe("rails");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should detect Salesforce project with sfdx-project.json", () => {
|
|
72
|
+
const projectDir = createTempProjectDir({
|
|
73
|
+
"sfdx-project.json": '{"packageDirectories": []}',
|
|
74
|
+
"force-app/main/default/classes/Test.cls": "public class Test {}"
|
|
75
|
+
});
|
|
76
|
+
const adapter = detectAdapter(projectDir);
|
|
77
|
+
expect(adapter.name).toBe("salesforce");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should detect .NET project with .csproj", () => {
|
|
81
|
+
const projectDir = createTempProjectDir({
|
|
82
|
+
"test.csproj": "<Project></Project>"
|
|
83
|
+
});
|
|
84
|
+
const adapter = detectAdapter(projectDir);
|
|
85
|
+
expect(["dotnet", "aspnetcore", "blazor"]).toContain(adapter.name);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should detect ASP.NET Core with Startup.cs and Program.cs", () => {
|
|
89
|
+
const projectDir = createTempProjectDir({
|
|
90
|
+
"test.csproj": "<Project></Project>",
|
|
91
|
+
"Startup.cs": "public class Startup {}",
|
|
92
|
+
"Program.cs": "CreateHostBuilder(args);"
|
|
93
|
+
});
|
|
94
|
+
const adapter = detectAdapter(projectDir);
|
|
95
|
+
expect(adapter.name).toBe("aspnetcore");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should return default adapter for unknown project", () => {
|
|
99
|
+
const projectDir = createTempProjectDir({
|
|
100
|
+
"README.md": "# Test"
|
|
101
|
+
});
|
|
102
|
+
const adapter = detectAdapter(projectDir);
|
|
103
|
+
expect(adapter.name).toBe("default");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("detectAllAdapters", () => {
|
|
108
|
+
it("should return adapters with confidence scores", () => {
|
|
109
|
+
const projectDir = createTempProjectDir({
|
|
110
|
+
"package.json": '{"name": "test"}'
|
|
111
|
+
});
|
|
112
|
+
const results = detectAllAdapters(projectDir);
|
|
113
|
+
expect(results.length).toBeGreaterThan(0);
|
|
114
|
+
expect(results[0].confidence).toBeGreaterThan(0);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("getAdapter", () => {
|
|
119
|
+
it("should return adapter by name", () => {
|
|
120
|
+
const adapter = getAdapter("javascript");
|
|
121
|
+
expect(adapter?.name).toBe("javascript");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should return undefined for unknown adapter", () => {
|
|
125
|
+
const adapter = getAdapter("unknown");
|
|
126
|
+
expect(adapter).toBeUndefined();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("listAdapters", () => {
|
|
131
|
+
it("should list all available adapters", () => {
|
|
132
|
+
const adapters = listAdapters();
|
|
133
|
+
expect(adapters.length).toBeGreaterThan(0);
|
|
134
|
+
expect(adapters.find(a => a.name === "javascript")).toBeDefined();
|
|
135
|
+
expect(adapters.find(a => a.name === "python")).toBeDefined();
|
|
136
|
+
expect(adapters.find(a => a.name === "rails")).toBeDefined();
|
|
137
|
+
expect(adapters.find(a => a.name === "salesforce")).toBeDefined();
|
|
138
|
+
expect(adapters.find(a => a.name === "dotnet")).toBeDefined();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("Adapter Configuration", () => {
|
|
143
|
+
it("javascript adapter should have correct feature roots", () => {
|
|
144
|
+
const adapter = getAdapter("javascript");
|
|
145
|
+
expect(adapter?.featureRoots).toContain("src");
|
|
146
|
+
expect(adapter?.featureRoots).toContain("app");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("django adapter should detect views", () => {
|
|
150
|
+
const adapter = getAdapter("django");
|
|
151
|
+
expect(adapter?.entrypointPatterns).toContain("view");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("salesforce adapter should detect LWC", () => {
|
|
155
|
+
const adapter = getAdapter("salesforce");
|
|
156
|
+
expect(adapter?.entrypointPatterns).toContain("LWC");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
detectGitRepository,
|
|
4
|
+
getRecentCommits,
|
|
5
|
+
extractChangedFiles,
|
|
6
|
+
mapFilesToFeatures,
|
|
7
|
+
mapFilesToFlows,
|
|
8
|
+
analyzeGitActivity,
|
|
9
|
+
generateGitContext
|
|
10
|
+
} from "../src/core/gitAnalyzer.js";
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import os from "os";
|
|
14
|
+
|
|
15
|
+
describe("Git Analyzer", () => {
|
|
16
|
+
const testRepoRoot = process.cwd();
|
|
17
|
+
|
|
18
|
+
describe("detectGitRepository", () => {
|
|
19
|
+
it("should detect current repository as git repo", () => {
|
|
20
|
+
expect(detectGitRepository(testRepoRoot)).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should detect non-git directory", () => {
|
|
24
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-git-test-"));
|
|
25
|
+
expect(detectGitRepository(tempDir)).toBe(false);
|
|
26
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("getRecentCommits", () => {
|
|
31
|
+
it("should return commits for git repository", () => {
|
|
32
|
+
const commits = getRecentCommits(testRepoRoot);
|
|
33
|
+
expect(commits.length).toBeGreaterThan(0);
|
|
34
|
+
expect(commits[0]).toHaveProperty("hash");
|
|
35
|
+
expect(commits[0]).toHaveProperty("date");
|
|
36
|
+
expect(commits[0]).toHaveProperty("message");
|
|
37
|
+
expect(commits[0]).toHaveProperty("author");
|
|
38
|
+
expect(commits[0]).toHaveProperty("files");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should return empty array for non-git directory", () => {
|
|
42
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-git-test-"));
|
|
43
|
+
const commits = getRecentCommits(tempDir);
|
|
44
|
+
expect(commits).toEqual([]);
|
|
45
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("extractChangedFiles", () => {
|
|
50
|
+
it("should extract and count changed files", () => {
|
|
51
|
+
const commits = getRecentCommits(testRepoRoot);
|
|
52
|
+
const files = extractChangedFiles(commits);
|
|
53
|
+
|
|
54
|
+
expect(files.length).toBeGreaterThan(0);
|
|
55
|
+
expect(files[0]).toHaveProperty("path");
|
|
56
|
+
expect(files[0]).toHaveProperty("commitCount");
|
|
57
|
+
expect(files[0].commitCount).toBeGreaterThan(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should sort by commit count descending", () => {
|
|
61
|
+
const commits = getRecentCommits(testRepoRoot);
|
|
62
|
+
const files = extractChangedFiles(commits);
|
|
63
|
+
|
|
64
|
+
for (let i = 1; i < files.length; i++) {
|
|
65
|
+
expect(files[i - 1].commitCount).toBeGreaterThanOrEqual(files[i].commitCount);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("mapFilesToFeatures", () => {
|
|
71
|
+
it("should return empty array when no features exist", () => {
|
|
72
|
+
const features = mapFilesToFeatures(testRepoRoot, ["src/test.ts"]);
|
|
73
|
+
expect(Array.isArray(features)).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("mapFilesToFlows", () => {
|
|
78
|
+
it("should return empty array when no flows exist", () => {
|
|
79
|
+
const flows = mapFilesToFlows(testRepoRoot, ["src/test.ts"]);
|
|
80
|
+
expect(Array.isArray(flows)).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("analyzeGitActivity", () => {
|
|
85
|
+
it("should analyze git activity", () => {
|
|
86
|
+
const activity = analyzeGitActivity(testRepoRoot);
|
|
87
|
+
|
|
88
|
+
if (activity) {
|
|
89
|
+
expect(activity).toHaveProperty("totalCommits");
|
|
90
|
+
expect(activity).toHaveProperty("dateRange");
|
|
91
|
+
expect(activity).toHaveProperty("files");
|
|
92
|
+
expect(activity).toHaveProperty("features");
|
|
93
|
+
expect(activity).toHaveProperty("flows");
|
|
94
|
+
expect(activity.totalCommits).toBeGreaterThan(0);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should return null for non-git directory", () => {
|
|
99
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-git-test-"));
|
|
100
|
+
const activity = analyzeGitActivity(tempDir);
|
|
101
|
+
expect(activity).toBeNull();
|
|
102
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("generateGitContext", () => {
|
|
107
|
+
it("should generate output files even for non-git directory", () => {
|
|
108
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-git-test-"));
|
|
109
|
+
const aiDir = path.join(tempDir, "ai");
|
|
110
|
+
|
|
111
|
+
// Create ai directory structure
|
|
112
|
+
fs.mkdirSync(path.join(aiDir, "context", "features"), { recursive: true });
|
|
113
|
+
fs.mkdirSync(path.join(aiDir, "context", "flows"), { recursive: true });
|
|
114
|
+
|
|
115
|
+
const result = generateGitContext(tempDir, aiDir);
|
|
116
|
+
|
|
117
|
+
expect(result).toHaveProperty("recentFiles");
|
|
118
|
+
expect(result).toHaveProperty("recentFeatures");
|
|
119
|
+
expect(result).toHaveProperty("recentFlows");
|
|
120
|
+
expect(result).toHaveProperty("activity");
|
|
121
|
+
expect(result.recentFiles).toEqual([]);
|
|
122
|
+
expect(result.activity).toBeNull();
|
|
123
|
+
|
|
124
|
+
// Output files should still be created
|
|
125
|
+
expect(fs.existsSync(path.join(aiDir, "git", "recent-files.json"))).toBe(true);
|
|
126
|
+
expect(fs.existsSync(path.join(aiDir, "git", "recent-features.json"))).toBe(true);
|
|
127
|
+
expect(fs.existsSync(path.join(aiDir, "git", "recent-flows.json"))).toBe(true);
|
|
128
|
+
|
|
129
|
+
// Cleanup
|
|
130
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
detectChangedFiles,
|
|
4
|
+
updateFeatures,
|
|
5
|
+
updateFlows,
|
|
6
|
+
updateKnowledgeGraph,
|
|
7
|
+
runIncrementalUpdate,
|
|
8
|
+
ChangedFile
|
|
9
|
+
} from "../src/core/incrementalAnalyzer.js";
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import os from "os";
|
|
13
|
+
|
|
14
|
+
describe("Incremental Analyzer", () => {
|
|
15
|
+
const testRoot = process.cwd();
|
|
16
|
+
|
|
17
|
+
describe("detectChangedFiles", () => {
|
|
18
|
+
it("should detect changes in git repository", () => {
|
|
19
|
+
const changes = detectChangedFiles(testRoot);
|
|
20
|
+
expect(Array.isArray(changes)).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should return empty array for non-git with no state", () => {
|
|
24
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-incr-test-"));
|
|
25
|
+
const changes = detectChangedFiles(tempDir, false);
|
|
26
|
+
expect(changes).toEqual([]);
|
|
27
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("runIncrementalUpdate", () => {
|
|
32
|
+
it("should return error for non-existent ai directory", () => {
|
|
33
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-incr-test-"));
|
|
34
|
+
const result = runIncrementalUpdate(tempDir);
|
|
35
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
36
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should handle changes gracefully", () => {
|
|
40
|
+
const aiDir = path.join(testRoot, "ai");
|
|
41
|
+
const result = runIncrementalUpdate(testRoot, aiDir);
|
|
42
|
+
expect(result).toHaveProperty("changedFiles");
|
|
43
|
+
expect(result).toHaveProperty("updatedSymbols");
|
|
44
|
+
expect(result).toHaveProperty("graphUpdated");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("updateKnowledgeGraph", () => {
|
|
49
|
+
it("should update knowledge graph", () => {
|
|
50
|
+
const aiDir = path.join(testRoot, "ai");
|
|
51
|
+
const updated = updateKnowledgeGraph(testRoot, aiDir);
|
|
52
|
+
expect(typeof updated).toBe("boolean");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("updateFeatures", () => {
|
|
57
|
+
it("should return empty array when no features dir", () => {
|
|
58
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-incr-test-"));
|
|
59
|
+
const aiDir = path.join(tempDir, "ai");
|
|
60
|
+
fs.mkdirSync(path.join(aiDir, "context"), { recursive: true });
|
|
61
|
+
|
|
62
|
+
const changes: ChangedFile[] = [];
|
|
63
|
+
const result = updateFeatures(tempDir, changes, aiDir);
|
|
64
|
+
expect(result).toEqual([]);
|
|
65
|
+
|
|
66
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("updateFlows", () => {
|
|
71
|
+
it("should return empty array when no flows dir", () => {
|
|
72
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-incr-test-"));
|
|
73
|
+
const aiDir = path.join(tempDir, "ai");
|
|
74
|
+
fs.mkdirSync(path.join(aiDir, "context"), { recursive: true });
|
|
75
|
+
|
|
76
|
+
const changes: ChangedFile[] = [];
|
|
77
|
+
const result = updateFlows(tempDir, changes, aiDir);
|
|
78
|
+
expect(result).toEqual([]);
|
|
79
|
+
|
|
80
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createNodes,
|
|
4
|
+
createEdges,
|
|
5
|
+
buildKnowledgeGraph,
|
|
6
|
+
loadKnowledgeGraph,
|
|
7
|
+
getNodesByType,
|
|
8
|
+
getEdgesByType,
|
|
9
|
+
getNeighbors
|
|
10
|
+
} from "../src/core/knowledgeGraphBuilder.js";
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import os from "os";
|
|
14
|
+
|
|
15
|
+
describe("Knowledge Graph Builder", () => {
|
|
16
|
+
const testRoot = process.cwd();
|
|
17
|
+
const testAiDir = path.join(testRoot, "ai");
|
|
18
|
+
|
|
19
|
+
describe("createNodes", () => {
|
|
20
|
+
it("should create nodes from ai directory", () => {
|
|
21
|
+
const nodes = createNodes(testAiDir);
|
|
22
|
+
expect(nodes.length).toBeGreaterThan(0);
|
|
23
|
+
expect(nodes[0]).toHaveProperty("id");
|
|
24
|
+
expect(nodes[0]).toHaveProperty("type");
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("createEdges", () => {
|
|
29
|
+
it("should create edges from ai directory", () => {
|
|
30
|
+
const edges = createEdges(testAiDir);
|
|
31
|
+
expect(edges.length).toBeGreaterThan(0);
|
|
32
|
+
expect(edges[0]).toHaveProperty("from");
|
|
33
|
+
expect(edges[0]).toHaveProperty("to");
|
|
34
|
+
expect(edges[0]).toHaveProperty("type");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should create valid edge types", () => {
|
|
38
|
+
const edges = createEdges(testAiDir);
|
|
39
|
+
const validTypes = ["contains", "implements", "declares", "references", "modifies"];
|
|
40
|
+
for (const edge of edges) {
|
|
41
|
+
expect(validTypes).toContain(edge.type);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("buildKnowledgeGraph", () => {
|
|
47
|
+
it("should build and save knowledge graph with data", () => {
|
|
48
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-graph-test-"));
|
|
49
|
+
const aiDir = path.join(tempDir, "ai");
|
|
50
|
+
fs.mkdirSync(path.join(aiDir, "graph"), { recursive: true });
|
|
51
|
+
fs.mkdirSync(path.join(aiDir, "git"), { recursive: true });
|
|
52
|
+
|
|
53
|
+
fs.writeFileSync(path.join(aiDir, "git", "commit-activity.json"), JSON.stringify({
|
|
54
|
+
totalCommits: 10,
|
|
55
|
+
dateRange: { start: "2026-01-01", end: "2026-03-11" },
|
|
56
|
+
files: { "src/test.ts": 5, "src/index.ts": 3 }
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
fs.writeFileSync(path.join(aiDir, "files.json"), JSON.stringify(["src/test.ts", "src/index.ts"]));
|
|
60
|
+
|
|
61
|
+
const graph = buildKnowledgeGraph(tempDir, aiDir);
|
|
62
|
+
|
|
63
|
+
expect(graph.nodes.length).toBeGreaterThan(0);
|
|
64
|
+
expect(graph.edges.length).toBeGreaterThan(0);
|
|
65
|
+
expect(graph.metadata).toHaveProperty("generated");
|
|
66
|
+
expect(graph.metadata).toHaveProperty("sources");
|
|
67
|
+
expect(graph.metadata).toHaveProperty("nodeCount");
|
|
68
|
+
expect(graph.metadata).toHaveProperty("edgeCount");
|
|
69
|
+
|
|
70
|
+
expect(fs.existsSync(path.join(aiDir, "graph", "knowledge-graph.json"))).toBe(true);
|
|
71
|
+
|
|
72
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("loadKnowledgeGraph", () => {
|
|
77
|
+
it("should load existing knowledge graph", () => {
|
|
78
|
+
const graph = loadKnowledgeGraph(testAiDir);
|
|
79
|
+
if (graph) {
|
|
80
|
+
expect(graph.nodes).toBeDefined();
|
|
81
|
+
expect(graph.edges).toBeDefined();
|
|
82
|
+
expect(graph.metadata).toBeDefined();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should return null for non-existent graph", () => {
|
|
87
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-graph-test-"));
|
|
88
|
+
const aiDir = path.join(tempDir, "ai");
|
|
89
|
+
|
|
90
|
+
const graph = loadKnowledgeGraph(aiDir);
|
|
91
|
+
expect(graph).toBeNull();
|
|
92
|
+
|
|
93
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("getNodesByType", () => {
|
|
98
|
+
it("should filter nodes by type", () => {
|
|
99
|
+
const graph = loadKnowledgeGraph(testAiDir);
|
|
100
|
+
if (graph) {
|
|
101
|
+
const commitNodes = getNodesByType(graph, "commit");
|
|
102
|
+
for (const node of commitNodes) {
|
|
103
|
+
expect(node.type).toBe("commit");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("getEdgesByType", () => {
|
|
110
|
+
it("should filter edges by type", () => {
|
|
111
|
+
const graph = loadKnowledgeGraph(testAiDir);
|
|
112
|
+
if (graph) {
|
|
113
|
+
const modifiesEdges = getEdgesByType(graph, "modifies");
|
|
114
|
+
for (const edge of modifiesEdges) {
|
|
115
|
+
expect(edge.type).toBe("modifies");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("getNeighbors", () => {
|
|
122
|
+
it("should find neighboring nodes", () => {
|
|
123
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-graph-test-"));
|
|
124
|
+
const aiDir = path.join(tempDir, "ai");
|
|
125
|
+
fs.mkdirSync(path.join(aiDir, "graph"), { recursive: true });
|
|
126
|
+
fs.mkdirSync(path.join(aiDir, "git"), { recursive: true });
|
|
127
|
+
|
|
128
|
+
fs.writeFileSync(path.join(aiDir, "git", "commit-activity.json"), JSON.stringify({
|
|
129
|
+
totalCommits: 10,
|
|
130
|
+
dateRange: { start: "2026-01-01", end: "2026-03-11" },
|
|
131
|
+
files: { "src/test.ts": 5 }
|
|
132
|
+
}));
|
|
133
|
+
fs.writeFileSync(path.join(aiDir, "files.json"), JSON.stringify(["src/test.ts"]));
|
|
134
|
+
|
|
135
|
+
const graph = buildKnowledgeGraph(tempDir, aiDir);
|
|
136
|
+
|
|
137
|
+
if (graph.nodes.length > 0) {
|
|
138
|
+
const firstNode = graph.nodes[0].id;
|
|
139
|
+
const neighbors = getNeighbors(graph, firstNode);
|
|
140
|
+
expect(Array.isArray(neighbors)).toBe(true);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|