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,105 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export interface VectorDocument {
|
|
5
|
+
id: string;
|
|
6
|
+
content: string;
|
|
7
|
+
embedding: number[];
|
|
8
|
+
metadata: {
|
|
9
|
+
filePath: string;
|
|
10
|
+
language?: string;
|
|
11
|
+
type?: 'function' | 'class' | 'interface' | 'variable';
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SearchResult {
|
|
16
|
+
document: VectorDocument;
|
|
17
|
+
score: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class VectorIndex {
|
|
21
|
+
private documents: Map<string, VectorDocument> = new Map();
|
|
22
|
+
private indexPath: string;
|
|
23
|
+
|
|
24
|
+
constructor(indexPath: string) {
|
|
25
|
+
this.indexPath = indexPath;
|
|
26
|
+
this.load();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private load(): void {
|
|
30
|
+
if (fs.existsSync(this.indexPath)) {
|
|
31
|
+
try {
|
|
32
|
+
const data = JSON.parse(fs.readFileSync(this.indexPath, 'utf-8'));
|
|
33
|
+
this.documents = new Map(Object.entries(data));
|
|
34
|
+
} catch {
|
|
35
|
+
this.documents = new Map();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
save(): void {
|
|
41
|
+
const data = Object.fromEntries(this.documents);
|
|
42
|
+
fs.writeFileSync(this.indexPath, JSON.stringify(data, null, 2));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
addDocument(doc: VectorDocument): void {
|
|
46
|
+
this.documents.set(doc.id, doc);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
search(query: string, topK: number = 5): SearchResult[] {
|
|
50
|
+
const queryEmbedding = this.simpleEmbedding(query);
|
|
51
|
+
const results: SearchResult[] = [];
|
|
52
|
+
|
|
53
|
+
for (const doc of this.documents.values()) {
|
|
54
|
+
const score = this.cosineSimilarity(queryEmbedding, doc.embedding);
|
|
55
|
+
results.push({ document: doc, score });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return results
|
|
59
|
+
.sort((a, b) => b.score - a.score)
|
|
60
|
+
.slice(0, topK);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private simpleEmbedding(text: string): number[] {
|
|
64
|
+
const words = text.toLowerCase().split(/\s+/);
|
|
65
|
+
const embedding: number[] = new Array(100).fill(0);
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < words.length && i < 100; i++) {
|
|
68
|
+
let hash = 0;
|
|
69
|
+
for (const char of words[i]) {
|
|
70
|
+
hash = ((hash << 5) - hash) + char.charCodeAt(0);
|
|
71
|
+
hash = hash & hash;
|
|
72
|
+
}
|
|
73
|
+
embedding[i] = Math.sin(hash) * 0.5 + 0.5;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return embedding;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private cosineSimilarity(a: number[], b: number[]): number {
|
|
80
|
+
let dotProduct = 0;
|
|
81
|
+
let normA = 0;
|
|
82
|
+
let normB = 0;
|
|
83
|
+
|
|
84
|
+
for (let i = 0; i < a.length; i++) {
|
|
85
|
+
dotProduct += a[i] * b[i];
|
|
86
|
+
normA += a[i] * a[i];
|
|
87
|
+
normB += b[i] * b[i];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (normA === 0 || normB === 0) return 0;
|
|
91
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function createVectorIndex(indexPath: string): VectorIndex {
|
|
96
|
+
return new VectorIndex(indexPath);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function semanticSearch(
|
|
100
|
+
index: VectorIndex,
|
|
101
|
+
query: string,
|
|
102
|
+
topK: number = 5
|
|
103
|
+
): SearchResult[] {
|
|
104
|
+
return index.search(query, topK);
|
|
105
|
+
}
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { startMCPServer, startMCP } from './server.js';
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import {
|
|
4
|
+
CallToolRequestSchema,
|
|
5
|
+
ListToolsRequestSchema,
|
|
6
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
import { runAIFirst } from '../commands/ai-first.js';
|
|
8
|
+
import { generateIndex } from '../core/indexer.js';
|
|
9
|
+
import { buildKnowledgeGraph } from '../core/knowledgeGraphBuilder.js';
|
|
10
|
+
import { analyzeArchitecture } from '../analyzers/architecture.js';
|
|
11
|
+
|
|
12
|
+
interface MCPServerOptions {
|
|
13
|
+
rootDir?: string;
|
|
14
|
+
aiDir?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function startMCPServer(options: MCPServerOptions = {}): void {
|
|
18
|
+
const rootDir = options.rootDir || process.cwd();
|
|
19
|
+
const aiDir = options.aiDir || `${rootDir}/ai-context`;
|
|
20
|
+
|
|
21
|
+
const server = new Server(
|
|
22
|
+
{
|
|
23
|
+
name: 'ai-first-cli',
|
|
24
|
+
version: '1.4.0',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
capabilities: {
|
|
28
|
+
tools: {},
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
34
|
+
return {
|
|
35
|
+
tools: [
|
|
36
|
+
{
|
|
37
|
+
name: 'generate_context',
|
|
38
|
+
description: 'Generate AI context for the repository or a specific module',
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
module: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'Optional module path to generate context for (e.g., "src/auth")',
|
|
45
|
+
},
|
|
46
|
+
preset: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
enum: ['full', 'quick', 'api', 'docs'],
|
|
49
|
+
description: 'Preset to use for context generation',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'query_symbols',
|
|
56
|
+
description: 'Query symbols (functions, classes, interfaces) in the indexed repository',
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
query: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
description: 'Search query for symbols',
|
|
63
|
+
},
|
|
64
|
+
type: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
enum: ['function', 'class', 'interface', 'variable', 'all'],
|
|
67
|
+
description: 'Type of symbol to search for',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ['query'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'get_architecture',
|
|
75
|
+
description: 'Get the architecture analysis of the project',
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
format: {
|
|
80
|
+
type: 'string',
|
|
81
|
+
enum: ['summary', 'detailed'],
|
|
82
|
+
description: 'Level of detail for the architecture report',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
92
|
+
const { name, arguments: args } = request.params;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
switch (name) {
|
|
96
|
+
case 'generate_context': {
|
|
97
|
+
const result = await runAIFirst({
|
|
98
|
+
rootDir,
|
|
99
|
+
outputDir: aiDir,
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: JSON.stringify({
|
|
106
|
+
success: result.success,
|
|
107
|
+
filesCreated: result.filesCreated,
|
|
108
|
+
message: result.success
|
|
109
|
+
? `Generated context in ${aiDir}`
|
|
110
|
+
: `Error: ${result.error}`,
|
|
111
|
+
}, null, 2),
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
case 'query_symbols': {
|
|
118
|
+
const query = args?.query as string;
|
|
119
|
+
const symbolType = (args?.type as string) || 'all';
|
|
120
|
+
|
|
121
|
+
const index = generateIndex(rootDir, aiDir);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
content: [
|
|
125
|
+
{
|
|
126
|
+
type: 'text',
|
|
127
|
+
text: JSON.stringify({
|
|
128
|
+
query,
|
|
129
|
+
type: symbolType,
|
|
130
|
+
results: [],
|
|
131
|
+
message: `Symbol query executed for "${query}"`,
|
|
132
|
+
}, null, 2),
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
case 'get_architecture': {
|
|
139
|
+
const format = (args?.format as string) || 'summary';
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: 'text',
|
|
145
|
+
text: JSON.stringify({
|
|
146
|
+
format,
|
|
147
|
+
rootDir,
|
|
148
|
+
message: 'Architecture analysis available',
|
|
149
|
+
}, null, 2),
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
default:
|
|
156
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: 'text',
|
|
163
|
+
text: JSON.stringify({
|
|
164
|
+
error: error instanceof Error ? error.message : String(error),
|
|
165
|
+
}, null, 2),
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
isError: true,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const transport = new StdioServerTransport();
|
|
174
|
+
server.connect(transport);
|
|
175
|
+
|
|
176
|
+
console.error('AI-First MCP Server running on stdio');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export { startMCPServer as startMCP };
|
package/src/utils/fileUtils.ts
CHANGED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { discoverEntrypoints, Entrypoint } from "../src/analyzers/entrypoints.js";
|
|
3
|
+
import { FileInfo } from "../src/core/repoScanner.js";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import os from "os";
|
|
7
|
+
|
|
8
|
+
function createTempProjectDir(files: Record<string, string>): string {
|
|
9
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-first-entrypoints-test-"));
|
|
10
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
11
|
+
const fullPath = path.join(tempDir, filePath);
|
|
12
|
+
const dir = path.dirname(fullPath);
|
|
13
|
+
if (!fs.existsSync(dir)) {
|
|
14
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
fs.writeFileSync(fullPath, content);
|
|
17
|
+
}
|
|
18
|
+
return tempDir;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createFileInfo(relativePath: string, name: string, extension: string): FileInfo {
|
|
22
|
+
return {
|
|
23
|
+
path: path.join("/tmp", relativePath),
|
|
24
|
+
relativePath,
|
|
25
|
+
name,
|
|
26
|
+
extension,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("Entrypoints - Go Language", () => {
|
|
31
|
+
it("should detect main.go with func main()", () => {
|
|
32
|
+
const tempDir = createTempProjectDir({
|
|
33
|
+
"main.go": `package main
|
|
34
|
+
|
|
35
|
+
import "net/http"
|
|
36
|
+
|
|
37
|
+
func main() {
|
|
38
|
+
http.ListenAndServe(":8080", nil)
|
|
39
|
+
}`,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const files: FileInfo[] = [createFileInfo("main.go", "main.go", "go")];
|
|
43
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
44
|
+
|
|
45
|
+
const mainEntry = entrypoints.find(ep => ep.name === "main.go");
|
|
46
|
+
expect(mainEntry).toBeDefined();
|
|
47
|
+
expect(mainEntry?.type).toBe("server");
|
|
48
|
+
expect(mainEntry?.path).toBe("main.go");
|
|
49
|
+
|
|
50
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should detect HTTP handlers in Go", () => {
|
|
54
|
+
const tempDir = createTempProjectDir({
|
|
55
|
+
"main.go": `package main
|
|
56
|
+
|
|
57
|
+
import "net/http"
|
|
58
|
+
|
|
59
|
+
func main() {
|
|
60
|
+
http.HandleFunc("/users", handleUsers)
|
|
61
|
+
http.HandleFunc("/posts", handlePosts)
|
|
62
|
+
http.ListenAndServe(":3000", nil)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func handleUsers(w http.ResponseWriter, r *http.Request) {}
|
|
66
|
+
func handlePosts(w http.ResponseWriter, r *http.Request) {}`,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const files: FileInfo[] = [createFileInfo("main.go", "main.go", "go")];
|
|
70
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
71
|
+
|
|
72
|
+
const mainEntry = entrypoints.find(ep => ep.name === "main.go");
|
|
73
|
+
expect(mainEntry).toBeDefined();
|
|
74
|
+
expect(mainEntry?.description).toContain("/users");
|
|
75
|
+
expect(mainEntry?.description).toContain("/posts");
|
|
76
|
+
expect(mainEntry?.description).toContain(":3000");
|
|
77
|
+
|
|
78
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should detect go.mod file", () => {
|
|
82
|
+
const tempDir = createTempProjectDir({
|
|
83
|
+
"main.go": "package main\n\nfunc main() {}",
|
|
84
|
+
"go.mod": "module github.com/example/app\n\ngo 1.21",
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const files: FileInfo[] = [
|
|
88
|
+
createFileInfo("main.go", "main.go", "go"),
|
|
89
|
+
createFileInfo("go.mod", "go.mod", ""),
|
|
90
|
+
];
|
|
91
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
92
|
+
|
|
93
|
+
const modEntry = entrypoints.find(ep => ep.name === "go.mod");
|
|
94
|
+
expect(modEntry).toBeDefined();
|
|
95
|
+
expect(modEntry?.type).toBe("config");
|
|
96
|
+
expect(modEntry?.description).toContain("github.com/example/app");
|
|
97
|
+
|
|
98
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should detect structs in Go modules", () => {
|
|
102
|
+
const tempDir = createTempProjectDir({
|
|
103
|
+
"models.go": `package models
|
|
104
|
+
|
|
105
|
+
type User struct {
|
|
106
|
+
ID int
|
|
107
|
+
Name string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
type Post struct {
|
|
111
|
+
Title string
|
|
112
|
+
Body string
|
|
113
|
+
}`,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const files: FileInfo[] = [createFileInfo("models.go", "models.go", "go")];
|
|
117
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
118
|
+
|
|
119
|
+
const modelEntry = entrypoints.find(ep => ep.name === "models.go");
|
|
120
|
+
expect(modelEntry).toBeDefined();
|
|
121
|
+
expect(modelEntry?.type).toBe("library");
|
|
122
|
+
expect(modelEntry?.description).toContain("User");
|
|
123
|
+
expect(modelEntry?.description).toContain("Post");
|
|
124
|
+
|
|
125
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("Entrypoints - Rust Language", () => {
|
|
130
|
+
it("should detect main.rs with fn main()", () => {
|
|
131
|
+
const tempDir = createTempProjectDir({
|
|
132
|
+
"src/main.rs": `fn main() {
|
|
133
|
+
println!("Hello, world!");
|
|
134
|
+
}`,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const files: FileInfo[] = [createFileInfo("src/main.rs", "main.rs", "rs")];
|
|
138
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
139
|
+
|
|
140
|
+
const mainEntry = entrypoints.find(ep => ep.name === "main.rs");
|
|
141
|
+
expect(mainEntry).toBeDefined();
|
|
142
|
+
expect(mainEntry?.type).toBe("cli");
|
|
143
|
+
expect(mainEntry?.path).toBe("src/main.rs");
|
|
144
|
+
|
|
145
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should detect structs and implementations in main.rs", () => {
|
|
149
|
+
const tempDir = createTempProjectDir({
|
|
150
|
+
"src/main.rs": `struct Config {
|
|
151
|
+
name: String,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
impl Config {
|
|
155
|
+
fn new() -> Self {
|
|
156
|
+
Config { name: "app".to_string() }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fn main() {}`,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const files: FileInfo[] = [createFileInfo("src/main.rs", "main.rs", "rs")];
|
|
164
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
165
|
+
|
|
166
|
+
const mainEntry = entrypoints.find(ep => ep.name === "main.rs");
|
|
167
|
+
expect(mainEntry).toBeDefined();
|
|
168
|
+
expect(mainEntry?.description).toContain("Config");
|
|
169
|
+
expect(mainEntry?.description).toContain("implementations");
|
|
170
|
+
|
|
171
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should detect lib.rs as library", () => {
|
|
175
|
+
const tempDir = createTempProjectDir({
|
|
176
|
+
"src/lib.rs": `pub fn public_function() {}
|
|
177
|
+
|
|
178
|
+
pub fn another_public_fn() {}`,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const files: FileInfo[] = [createFileInfo("src/lib.rs", "lib.rs", "rs")];
|
|
182
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
183
|
+
|
|
184
|
+
const libEntry = entrypoints.find(ep => ep.name === "lib.rs");
|
|
185
|
+
expect(libEntry).toBeDefined();
|
|
186
|
+
expect(libEntry?.type).toBe("library");
|
|
187
|
+
expect(libEntry?.description).toContain("public_function");
|
|
188
|
+
|
|
189
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should detect Cargo.toml with project info", () => {
|
|
193
|
+
const tempDir = createTempProjectDir({
|
|
194
|
+
"src/main.rs": "fn main() {}",
|
|
195
|
+
"Cargo.toml": `[package]
|
|
196
|
+
name = "my-app"
|
|
197
|
+
version = "1.0.0"
|
|
198
|
+
edition = "2021"`,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const files: FileInfo[] = [
|
|
202
|
+
createFileInfo("src/main.rs", "main.rs", "rs"),
|
|
203
|
+
createFileInfo("Cargo.toml", "Cargo.toml", ""),
|
|
204
|
+
];
|
|
205
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
206
|
+
|
|
207
|
+
const cargoEntry = entrypoints.find(ep => ep.name === "Cargo.toml");
|
|
208
|
+
expect(cargoEntry).toBeDefined();
|
|
209
|
+
expect(cargoEntry?.type).toBe("config");
|
|
210
|
+
expect(cargoEntry?.description).toContain("my-app");
|
|
211
|
+
expect(cargoEntry?.description).toContain("v1.0.0");
|
|
212
|
+
|
|
213
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("Entrypoints - PHP Language", () => {
|
|
218
|
+
it("should detect index.php", () => {
|
|
219
|
+
const tempDir = createTempProjectDir({
|
|
220
|
+
"index.php": `<?php
|
|
221
|
+
echo "Hello, World!";`,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const files: FileInfo[] = [createFileInfo("index.php", "index.php", "php")];
|
|
225
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
226
|
+
|
|
227
|
+
const indexEntry = entrypoints.find(ep => ep.name === "index.php");
|
|
228
|
+
expect(indexEntry).toBeDefined();
|
|
229
|
+
expect(indexEntry?.type).toBe("api");
|
|
230
|
+
expect(indexEntry?.path).toBe("index.php");
|
|
231
|
+
|
|
232
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should detect public/index.php as server", () => {
|
|
236
|
+
const tempDir = createTempProjectDir({
|
|
237
|
+
"public/index.php": `<?php
|
|
238
|
+
require_once __DIR__ . '/../vendor/autoload.php';`,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const files: FileInfo[] = [createFileInfo("public/index.php", "index.php", "php")];
|
|
242
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
243
|
+
|
|
244
|
+
const indexEntry = entrypoints.find(ep => ep.name === "index.php");
|
|
245
|
+
expect(indexEntry).toBeDefined();
|
|
246
|
+
expect(indexEntry?.type).toBe("server");
|
|
247
|
+
|
|
248
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should detect PHP classes and routes", () => {
|
|
252
|
+
const tempDir = createTempProjectDir({
|
|
253
|
+
"index.php": `<?php
|
|
254
|
+
|
|
255
|
+
class UserController {
|
|
256
|
+
public function index() {}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
class Router {
|
|
260
|
+
public function add() {}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
$router = new Router();
|
|
264
|
+
$router->add('GET', '/users', function() {});`,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const files: FileInfo[] = [createFileInfo("index.php", "index.php", "php")];
|
|
268
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
269
|
+
|
|
270
|
+
const indexEntry = entrypoints.find(ep => ep.name === "index.php");
|
|
271
|
+
expect(indexEntry).toBeDefined();
|
|
272
|
+
expect(indexEntry?.description).toContain("UserController");
|
|
273
|
+
expect(indexEntry?.description).toContain("Router");
|
|
274
|
+
expect(indexEntry?.description).toContain("/users");
|
|
275
|
+
|
|
276
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("should detect composer.json with Laravel", () => {
|
|
280
|
+
const tempDir = createTempProjectDir({
|
|
281
|
+
"index.php": "<?php echo 'Hello';",
|
|
282
|
+
"composer.json": JSON.stringify({
|
|
283
|
+
name: "example/laravel-app",
|
|
284
|
+
description: "My Laravel application",
|
|
285
|
+
require: {
|
|
286
|
+
"laravel/framework": "^10.0",
|
|
287
|
+
},
|
|
288
|
+
}),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const files: FileInfo[] = [
|
|
292
|
+
createFileInfo("index.php", "index.php", "php"),
|
|
293
|
+
createFileInfo("composer.json", "composer.json", ""),
|
|
294
|
+
];
|
|
295
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
296
|
+
|
|
297
|
+
const composerEntry = entrypoints.find(ep => ep.name === "composer.json");
|
|
298
|
+
expect(composerEntry).toBeDefined();
|
|
299
|
+
expect(composerEntry?.type).toBe("config");
|
|
300
|
+
expect(composerEntry?.description).toContain("example/laravel-app");
|
|
301
|
+
expect(composerEntry?.description).toContain("Laravel");
|
|
302
|
+
|
|
303
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("should detect composer.json with Symfony", () => {
|
|
307
|
+
const tempDir = createTempProjectDir({
|
|
308
|
+
"index.php": "<?php echo 'Hello';",
|
|
309
|
+
"composer.json": JSON.stringify({
|
|
310
|
+
name: "example/symfony-app",
|
|
311
|
+
require: {
|
|
312
|
+
"symfony/framework-bundle": "^6.0",
|
|
313
|
+
},
|
|
314
|
+
}),
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const files: FileInfo[] = [
|
|
318
|
+
createFileInfo("index.php", "index.php", "php"),
|
|
319
|
+
createFileInfo("composer.json", "composer.json", ""),
|
|
320
|
+
];
|
|
321
|
+
const entrypoints = discoverEntrypoints(files, tempDir);
|
|
322
|
+
|
|
323
|
+
const composerEntry = entrypoints.find(ep => ep.name === "composer.json");
|
|
324
|
+
expect(composerEntry).toBeDefined();
|
|
325
|
+
expect(composerEntry?.description).toContain("Symfony");
|
|
326
|
+
|
|
327
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe("Entrypoints - Integration with Test Projects", () => {
|
|
332
|
+
it("should detect Go microservice entrypoints", () => {
|
|
333
|
+
const testProjectPath = path.join(process.cwd(), "test-projects/go-microservice");
|
|
334
|
+
|
|
335
|
+
const files: FileInfo[] = [
|
|
336
|
+
createFileInfo("main.go", "main.go", "go"),
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
const entrypoints = discoverEntrypoints(files, testProjectPath);
|
|
340
|
+
|
|
341
|
+
const mainEntry = entrypoints.find(ep => ep.name === "main.go");
|
|
342
|
+
expect(mainEntry).toBeDefined();
|
|
343
|
+
expect(mainEntry?.type).toBe("server");
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("should detect Rust CLI entrypoints", () => {
|
|
347
|
+
const testProjectPath = path.join(process.cwd(), "test-projects/rust-cli");
|
|
348
|
+
|
|
349
|
+
const files: FileInfo[] = [
|
|
350
|
+
createFileInfo("src/main.rs", "main.rs", "rs"),
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
const entrypoints = discoverEntrypoints(files, testProjectPath);
|
|
354
|
+
|
|
355
|
+
const mainEntry = entrypoints.find(ep => ep.name === "main.rs");
|
|
356
|
+
expect(mainEntry).toBeDefined();
|
|
357
|
+
expect(mainEntry?.type).toBe("cli");
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("should detect PHP vanilla entrypoints", () => {
|
|
361
|
+
const testProjectPath = path.join(process.cwd(), "test-projects/php-vanilla");
|
|
362
|
+
|
|
363
|
+
const files: FileInfo[] = [
|
|
364
|
+
createFileInfo("index.php", "index.php", "php"),
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
const entrypoints = discoverEntrypoints(files, testProjectPath);
|
|
368
|
+
|
|
369
|
+
const indexEntry = entrypoints.find(ep => ep.name === "index.php");
|
|
370
|
+
expect(indexEntry).toBeDefined();
|
|
371
|
+
expect(indexEntry?.type).toBe("api");
|
|
372
|
+
});
|
|
373
|
+
});
|