gitnexus 1.3.2 → 1.3.4
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/dist/cli/ai-context.js +23 -52
- package/dist/cli/analyze.js +4 -1
- package/dist/cli/index.js +1 -0
- package/dist/cli/mcp.js +11 -22
- package/dist/cli/serve.d.ts +1 -0
- package/dist/cli/serve.js +2 -1
- package/dist/cli/setup.js +2 -2
- package/dist/cli/wiki.js +6 -2
- package/dist/config/supported-languages.d.ts +2 -1
- package/dist/config/supported-languages.js +1 -1
- package/dist/core/embeddings/embedder.js +40 -1
- package/dist/core/graph/types.d.ts +2 -0
- package/dist/core/ingestion/entry-point-scoring.js +26 -1
- package/dist/core/ingestion/filesystem-walker.js +3 -3
- package/dist/core/ingestion/framework-detection.d.ts +12 -4
- package/dist/core/ingestion/framework-detection.js +105 -5
- package/dist/core/ingestion/import-processor.js +77 -0
- package/dist/core/ingestion/parsing-processor.js +51 -9
- package/dist/core/ingestion/process-processor.js +7 -1
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -0
- package/dist/core/ingestion/tree-sitter-queries.js +361 -282
- package/dist/core/ingestion/utils.js +6 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +3 -0
- package/dist/core/ingestion/workers/parse-worker.js +192 -1
- package/dist/core/kuzu/csv-generator.js +4 -2
- package/dist/core/kuzu/kuzu-adapter.d.ts +9 -0
- package/dist/core/kuzu/kuzu-adapter.js +68 -9
- package/dist/core/kuzu/schema.d.ts +6 -6
- package/dist/core/kuzu/schema.js +8 -0
- package/dist/core/tree-sitter/parser-loader.js +2 -0
- package/dist/core/wiki/generator.js +2 -2
- package/dist/mcp/local/local-backend.js +25 -13
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.js +13 -2
- package/dist/mcp/staleness.js +2 -2
- package/dist/server/api.d.ts +7 -5
- package/dist/server/api.js +145 -127
- package/dist/server/mcp-http.d.ts +13 -0
- package/dist/server/mcp-http.js +100 -0
- package/package.json +2 -1
- package/skills/gitnexus-cli.md +82 -0
- package/skills/{debugging.md → gitnexus-debugging.md} +12 -8
- package/skills/{exploring.md → gitnexus-exploring.md} +10 -7
- package/skills/gitnexus-guide.md +64 -0
- package/skills/{impact-analysis.md → gitnexus-impact-analysis.md} +14 -11
- package/skills/{refactoring.md → gitnexus-refactoring.md} +15 -7
package/dist/cli/ai-context.js
CHANGED
|
@@ -29,12 +29,8 @@ function generateGitNexusContent(projectName, stats) {
|
|
|
29
29
|
|
|
30
30
|
This project is indexed by GitNexus as **${projectName}** (${stats.nodes || 0} symbols, ${stats.edges || 0} relationships, ${stats.processes || 0} execution flows).
|
|
31
31
|
|
|
32
|
-
GitNexus provides a knowledge graph over this codebase — call chains, blast radius, execution flows, and semantic search.
|
|
33
|
-
|
|
34
32
|
## Always Start Here
|
|
35
33
|
|
|
36
|
-
For any task involving code understanding, debugging, impact analysis, or refactoring, you must:
|
|
37
|
-
|
|
38
34
|
1. **Read \`gitnexus://repo/{name}/context\`** — codebase overview + check index freshness
|
|
39
35
|
2. **Match your task to a skill below** and **read that skill file**
|
|
40
36
|
3. **Follow the skill's workflow and checklist**
|
|
@@ -45,45 +41,12 @@ For any task involving code understanding, debugging, impact analysis, or refact
|
|
|
45
41
|
|
|
46
42
|
| Task | Read this skill file |
|
|
47
43
|
|------|---------------------|
|
|
48
|
-
| Understand architecture / "How does X work?" | \`.claude/skills/gitnexus/exploring/SKILL.md\` |
|
|
49
|
-
| Blast radius / "What breaks if I change X?" | \`.claude/skills/gitnexus/impact-analysis/SKILL.md\` |
|
|
50
|
-
| Trace bugs / "Why is X failing?" | \`.claude/skills/gitnexus/debugging/SKILL.md\` |
|
|
51
|
-
| Rename / extract / split / refactor | \`.claude/skills/gitnexus/refactoring/SKILL.md\` |
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
| Tool | What it gives you |
|
|
56
|
-
|------|-------------------|
|
|
57
|
-
| \`query\` | Process-grouped code intelligence — execution flows related to a concept |
|
|
58
|
-
| \`context\` | 360-degree symbol view — categorized refs, processes it participates in |
|
|
59
|
-
| \`impact\` | Symbol blast radius — what breaks at depth 1/2/3 with confidence |
|
|
60
|
-
| \`detect_changes\` | Git-diff impact — what do your current changes affect |
|
|
61
|
-
| \`rename\` | Multi-file coordinated rename with confidence-tagged edits |
|
|
62
|
-
| \`cypher\` | Raw graph queries (read \`gitnexus://repo/{name}/schema\` first) |
|
|
63
|
-
| \`list_repos\` | Discover indexed repos |
|
|
64
|
-
|
|
65
|
-
## Resources Reference
|
|
66
|
-
|
|
67
|
-
Lightweight reads (~100-500 tokens) for navigation:
|
|
68
|
-
|
|
69
|
-
| Resource | Content |
|
|
70
|
-
|----------|---------|
|
|
71
|
-
| \`gitnexus://repo/{name}/context\` | Stats, staleness check |
|
|
72
|
-
| \`gitnexus://repo/{name}/clusters\` | All functional areas with cohesion scores |
|
|
73
|
-
| \`gitnexus://repo/{name}/cluster/{clusterName}\` | Area members |
|
|
74
|
-
| \`gitnexus://repo/{name}/processes\` | All execution flows |
|
|
75
|
-
| \`gitnexus://repo/{name}/process/{processName}\` | Step-by-step trace |
|
|
76
|
-
| \`gitnexus://repo/{name}/schema\` | Graph schema for Cypher |
|
|
77
|
-
|
|
78
|
-
## Graph Schema
|
|
79
|
-
|
|
80
|
-
**Nodes:** File, Function, Class, Interface, Method, Community, Process
|
|
81
|
-
**Edges (via CodeRelation.type):** CALLS, IMPORTS, EXTENDS, IMPLEMENTS, DEFINES, MEMBER_OF, STEP_IN_PROCESS
|
|
82
|
-
|
|
83
|
-
\`\`\`cypher
|
|
84
|
-
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"})
|
|
85
|
-
RETURN caller.name, caller.filePath
|
|
86
|
-
\`\`\`
|
|
44
|
+
| Understand architecture / "How does X work?" | \`.claude/skills/gitnexus/gitnexus-exploring/SKILL.md\` |
|
|
45
|
+
| Blast radius / "What breaks if I change X?" | \`.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md\` |
|
|
46
|
+
| Trace bugs / "Why is X failing?" | \`.claude/skills/gitnexus/gitnexus-debugging/SKILL.md\` |
|
|
47
|
+
| Rename / extract / split / refactor | \`.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md\` |
|
|
48
|
+
| Tools, resources, schema reference | \`.claude/skills/gitnexus/gitnexus-guide/SKILL.md\` |
|
|
49
|
+
| Index, status, clean, wiki CLI commands | \`.claude/skills/gitnexus/gitnexus-cli/SKILL.md\` |
|
|
87
50
|
|
|
88
51
|
${GITNEXUS_END_MARKER}`;
|
|
89
52
|
}
|
|
@@ -138,20 +101,28 @@ async function installSkills(repoPath) {
|
|
|
138
101
|
// Skill definitions bundled with the package
|
|
139
102
|
const skills = [
|
|
140
103
|
{
|
|
141
|
-
name: 'exploring',
|
|
142
|
-
description: '
|
|
104
|
+
name: 'gitnexus-exploring',
|
|
105
|
+
description: 'Use when the user asks how code works, wants to understand architecture, trace execution flows, or explore unfamiliar parts of the codebase. Examples: "How does X work?", "What calls this function?", "Show me the auth flow"',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'gitnexus-debugging',
|
|
109
|
+
description: 'Use when the user is debugging a bug, tracing an error, or asking why something fails. Examples: "Why is X failing?", "Where does this error come from?", "Trace this bug"',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'gitnexus-impact-analysis',
|
|
113
|
+
description: 'Use when the user wants to know what will break if they change something, or needs safety analysis before editing code. Examples: "Is it safe to change X?", "What depends on this?", "What will break?"',
|
|
143
114
|
},
|
|
144
115
|
{
|
|
145
|
-
name: '
|
|
146
|
-
description: '
|
|
116
|
+
name: 'gitnexus-refactoring',
|
|
117
|
+
description: 'Use when the user wants to rename, extract, split, move, or restructure code safely. Examples: "Rename this function", "Extract this into a module", "Refactor this class", "Move this to a separate file"',
|
|
147
118
|
},
|
|
148
119
|
{
|
|
149
|
-
name: '
|
|
150
|
-
description: '
|
|
120
|
+
name: 'gitnexus-guide',
|
|
121
|
+
description: 'Use when the user asks about GitNexus itself — available tools, how to query the knowledge graph, MCP resources, graph schema, or workflow reference. Examples: "What GitNexus tools are available?", "How do I use GitNexus?"',
|
|
151
122
|
},
|
|
152
123
|
{
|
|
153
|
-
name: '
|
|
154
|
-
description: '
|
|
124
|
+
name: 'gitnexus-cli',
|
|
125
|
+
description: 'Use when the user needs to run GitNexus CLI commands like analyze/index a repo, check status, clean the index, generate a wiki, or list indexed repos. Examples: "Index this repo", "Reanalyze the codebase", "Generate a wiki"',
|
|
155
126
|
},
|
|
156
127
|
];
|
|
157
128
|
for (const skill of skills) {
|
|
@@ -169,7 +140,7 @@ async function installSkills(repoPath) {
|
|
|
169
140
|
catch {
|
|
170
141
|
// Fallback: generate minimal skill content
|
|
171
142
|
skillContent = `---
|
|
172
|
-
name:
|
|
143
|
+
name: ${skill.name}
|
|
173
144
|
description: ${skill.description}
|
|
174
145
|
---
|
|
175
146
|
|
package/dist/cli/analyze.js
CHANGED
|
@@ -9,7 +9,9 @@ import v8 from 'v8';
|
|
|
9
9
|
import cliProgress from 'cli-progress';
|
|
10
10
|
import { runPipelineFromRepo } from '../core/ingestion/pipeline.js';
|
|
11
11
|
import { initKuzu, loadGraphToKuzu, getKuzuStats, executeQuery, executeWithReusedStatement, closeKuzu, createFTSIndex, loadCachedEmbeddings } from '../core/kuzu/kuzu-adapter.js';
|
|
12
|
-
|
|
12
|
+
// Embedding imports are lazy (dynamic import) so onnxruntime-node is never
|
|
13
|
+
// loaded when embeddings are not requested. This avoids crashes on Node
|
|
14
|
+
// versions whose ABI is not yet supported by the native binary (#89).
|
|
13
15
|
// disposeEmbedder intentionally not called — ONNX Runtime segfaults on cleanup (see #38)
|
|
14
16
|
import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath } from '../storage/repo-manager.js';
|
|
15
17
|
import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
|
|
@@ -231,6 +233,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
231
233
|
if (!embeddingSkipped) {
|
|
232
234
|
updateBar(90, 'Loading embedding model...');
|
|
233
235
|
const t0Emb = Date.now();
|
|
236
|
+
const { runEmbeddingPipeline } = await import('../core/embeddings/embedding-pipeline.js');
|
|
234
237
|
await runEmbeddingPipeline(executeQuery, executeWithReusedStatement, (progress) => {
|
|
235
238
|
const scaled = 90 + Math.round((progress.percent / 100) * 8);
|
|
236
239
|
const label = progress.phase === 'loading-model' ? 'Loading embedding model...' : `Embedding ${progress.nodesProcessed || 0}/${progress.totalNodes || '?'}`;
|
package/dist/cli/index.js
CHANGED
|
@@ -50,6 +50,7 @@ program
|
|
|
50
50
|
.command('serve')
|
|
51
51
|
.description('Start local HTTP server for web UI connection')
|
|
52
52
|
.option('-p, --port <port>', 'Port number', '4747')
|
|
53
|
+
.option('--host <host>', 'Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)')
|
|
53
54
|
.action(serveCommand);
|
|
54
55
|
program
|
|
55
56
|
.command('mcp')
|
package/dist/cli/mcp.js
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { startMCPServer } from '../mcp/server.js';
|
|
9
9
|
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
10
|
-
import { listRegisteredRepos } from '../storage/repo-manager.js';
|
|
11
10
|
export const mcpCommand = async () => {
|
|
12
11
|
// Prevent unhandled errors from crashing the MCP server process.
|
|
13
12
|
// KuzuDB lock conflicts and transient errors should degrade gracefully.
|
|
@@ -18,28 +17,18 @@ export const mcpCommand = async () => {
|
|
|
18
17
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
19
18
|
console.error(`GitNexus MCP: unhandled rejection — ${msg}`);
|
|
20
19
|
});
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
console.error('');
|
|
25
|
-
console.error(' GitNexus: No indexed repositories found.');
|
|
26
|
-
console.error('');
|
|
27
|
-
console.error(' To get started:');
|
|
28
|
-
console.error(' 1. cd into a git repository');
|
|
29
|
-
console.error(' 2. Run: gitnexus analyze');
|
|
30
|
-
console.error(' 3. Restart your editor');
|
|
31
|
-
console.error('');
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
// Initialize multi-repo backend from registry
|
|
20
|
+
// Initialize multi-repo backend from registry.
|
|
21
|
+
// The server starts even with 0 repos — tools call refreshRepos() lazily,
|
|
22
|
+
// so repos indexed after the server starts are discovered automatically.
|
|
35
23
|
const backend = new LocalBackend();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
await backend.init();
|
|
25
|
+
const repos = await backend.listRepos();
|
|
26
|
+
if (repos.length === 0) {
|
|
27
|
+
console.error('GitNexus: No indexed repos yet. Run `gitnexus analyze` in a git repo — the server will pick it up automatically.');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.error(`GitNexus: MCP server starting with ${repos.length} repo(s): ${repos.map(r => r.name).join(', ')}`);
|
|
40
31
|
}
|
|
41
|
-
|
|
42
|
-
console.error(`GitNexus: MCP server starting with ${repoNames.length} repo(s): ${repoNames.join(', ')}`);
|
|
43
|
-
// Start MCP server (serves all repos)
|
|
32
|
+
// Start MCP server (serves all repos, discovers new ones lazily)
|
|
44
33
|
await startMCPServer(backend);
|
|
45
34
|
};
|
package/dist/cli/serve.d.ts
CHANGED
package/dist/cli/serve.js
CHANGED
package/dist/cli/setup.js
CHANGED
|
@@ -198,7 +198,7 @@ async function setupOpenCode(result) {
|
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
// ─── Skill Installation ───────────────────────────────────────────
|
|
201
|
-
const SKILL_NAMES = ['exploring', 'debugging', 'impact-analysis', 'refactoring'];
|
|
201
|
+
const SKILL_NAMES = ['gitnexus-exploring', 'gitnexus-debugging', 'gitnexus-impact-analysis', 'gitnexus-refactoring', 'gitnexus-guide', 'gitnexus-cli'];
|
|
202
202
|
/**
|
|
203
203
|
* Install GitNexus skills to a target directory.
|
|
204
204
|
* Each skill is installed as {targetDir}/gitnexus-{skillName}/SKILL.md
|
|
@@ -212,7 +212,7 @@ async function installSkillsTo(targetDir) {
|
|
|
212
212
|
const installed = [];
|
|
213
213
|
const skillsRoot = path.join(__dirname, '..', '..', 'skills');
|
|
214
214
|
for (const skillName of SKILL_NAMES) {
|
|
215
|
-
const skillDir = path.join(targetDir,
|
|
215
|
+
const skillDir = path.join(targetDir, skillName);
|
|
216
216
|
try {
|
|
217
217
|
// Try directory-based skill first (skills/{name}/SKILL.md)
|
|
218
218
|
const dirSource = path.join(skillsRoot, skillName);
|
package/dist/cli/wiki.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import readline from 'readline';
|
|
9
|
-
import { execSync } from 'child_process';
|
|
9
|
+
import { execSync, execFileSync } from 'child_process';
|
|
10
10
|
import cliProgress from 'cli-progress';
|
|
11
11
|
import { getGitRoot, isGitRepo } from '../storage/git.js';
|
|
12
12
|
import { getStoragePaths, loadMeta, loadCLIConfig, saveCLIConfig } from '../storage/repo-manager.js';
|
|
@@ -299,7 +299,11 @@ function hasGhCLI() {
|
|
|
299
299
|
}
|
|
300
300
|
function publishGist(htmlPath) {
|
|
301
301
|
try {
|
|
302
|
-
const output =
|
|
302
|
+
const output = execFileSync('gh', [
|
|
303
|
+
'gist', 'create', htmlPath,
|
|
304
|
+
'--desc', 'Repository Wiki — generated by GitNexus',
|
|
305
|
+
'--public',
|
|
306
|
+
], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
303
307
|
// gh gist create prints the gist URL as the last line
|
|
304
308
|
const lines = output.split('\n');
|
|
305
309
|
const gistUrl = lines.find(l => l.includes('gist.github.com')) || lines[lines.length - 1];
|
|
@@ -9,7 +9,7 @@ export var SupportedLanguages;
|
|
|
9
9
|
SupportedLanguages["CSharp"] = "csharp";
|
|
10
10
|
SupportedLanguages["Go"] = "go";
|
|
11
11
|
SupportedLanguages["Rust"] = "rust";
|
|
12
|
-
|
|
12
|
+
SupportedLanguages["PHP"] = "php";
|
|
13
13
|
// Ruby = 'ruby',
|
|
14
14
|
// Swift = 'swift',
|
|
15
15
|
})(SupportedLanguages || (SupportedLanguages = {}));
|
|
@@ -13,7 +13,44 @@ if (!process.env.ORT_LOG_LEVEL) {
|
|
|
13
13
|
process.env.ORT_LOG_LEVEL = '3';
|
|
14
14
|
}
|
|
15
15
|
import { pipeline, env } from '@huggingface/transformers';
|
|
16
|
+
import { existsSync } from 'fs';
|
|
17
|
+
import { execFileSync } from 'child_process';
|
|
18
|
+
import { join } from 'path';
|
|
16
19
|
import { DEFAULT_EMBEDDING_CONFIG } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* Check whether CUDA libraries are actually available on this system.
|
|
22
|
+
* ONNX Runtime's native layer crashes (uncatchable) if we attempt CUDA
|
|
23
|
+
* without the required shared libraries, so we probe first.
|
|
24
|
+
*
|
|
25
|
+
* Checks the dynamic linker cache (ldconfig) which covers all architectures
|
|
26
|
+
* and install paths, then falls back to CUDA_PATH / LD_LIBRARY_PATH env vars.
|
|
27
|
+
*/
|
|
28
|
+
function isCudaAvailable() {
|
|
29
|
+
// Primary: query the dynamic linker cache — covers all architectures,
|
|
30
|
+
// distro layouts, and custom install paths registered with ldconfig
|
|
31
|
+
try {
|
|
32
|
+
const out = execFileSync('ldconfig', ['-p'], { timeout: 3000, encoding: 'utf-8' });
|
|
33
|
+
if (out.includes('libcublasLt.so.12'))
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// ldconfig not available (e.g. non-standard container)
|
|
38
|
+
}
|
|
39
|
+
// Fallback: check CUDA_PATH and LD_LIBRARY_PATH for environments where
|
|
40
|
+
// ldconfig doesn't know about the CUDA install (conda, manual /opt/cuda, etc.)
|
|
41
|
+
for (const envVar of ['CUDA_PATH', 'LD_LIBRARY_PATH']) {
|
|
42
|
+
const val = process.env[envVar];
|
|
43
|
+
if (!val)
|
|
44
|
+
continue;
|
|
45
|
+
for (const dir of val.split(':').filter(Boolean)) {
|
|
46
|
+
if (existsSync(join(dir, 'lib64', 'libcublasLt.so.12')) ||
|
|
47
|
+
existsSync(join(dir, 'lib', 'libcublasLt.so.12')) ||
|
|
48
|
+
existsSync(join(dir, 'libcublasLt.so.12')))
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
17
54
|
// Module-level state for singleton pattern
|
|
18
55
|
let embedderInstance = null;
|
|
19
56
|
let isInitializing = false;
|
|
@@ -45,8 +82,10 @@ export const initEmbedder = async (onProgress, config = {}, forceDevice) => {
|
|
|
45
82
|
const finalConfig = { ...DEFAULT_EMBEDDING_CONFIG, ...config };
|
|
46
83
|
// On Windows, use DirectML for GPU acceleration (via DirectX12)
|
|
47
84
|
// CUDA is only available on Linux x64 with onnxruntime-node
|
|
85
|
+
// Probe for CUDA first — ONNX Runtime crashes (uncatchable native error)
|
|
86
|
+
// if we attempt CUDA without the required shared libraries
|
|
48
87
|
const isWindows = process.platform === 'win32';
|
|
49
|
-
const gpuDevice = isWindows ? 'dml' : 'cuda';
|
|
88
|
+
const gpuDevice = isWindows ? 'dml' : (isCudaAvailable() ? 'cuda' : 'cpu');
|
|
50
89
|
let requestedDevice = forceDevice || (finalConfig.device === 'auto' ? gpuDevice : finalConfig.device);
|
|
51
90
|
initPromise = (async () => {
|
|
52
91
|
try {
|
|
@@ -91,6 +91,26 @@ const ENTRY_POINT_PATTERNS = {
|
|
|
91
91
|
/^Run$/, // Run methods
|
|
92
92
|
/^Start$/, // Start methods
|
|
93
93
|
],
|
|
94
|
+
// PHP / Laravel
|
|
95
|
+
'php': [
|
|
96
|
+
/Controller$/, // UserController (class name convention)
|
|
97
|
+
/^handle$/, // Job::handle(), Listener::handle()
|
|
98
|
+
/^execute$/, // Command::execute()
|
|
99
|
+
/^boot$/, // ServiceProvider::boot()
|
|
100
|
+
/^register$/, // ServiceProvider::register()
|
|
101
|
+
/^__invoke$/, // Invokable controllers/actions
|
|
102
|
+
/^(index|show|store|update|destroy|create|edit)$/, // RESTful resource methods
|
|
103
|
+
/^(get|post|put|delete|patch)[A-Z]/, // Explicit HTTP method actions
|
|
104
|
+
/^run$/, // Command/Job run()
|
|
105
|
+
/^fire$/, // Event fire()
|
|
106
|
+
/^dispatch$/, // Dispatchable jobs
|
|
107
|
+
/Service$/, // UserService (Service layer)
|
|
108
|
+
/Repository$/, // UserRepository (Repository pattern)
|
|
109
|
+
/^find$/, // Repository::find()
|
|
110
|
+
/^findAll$/, // Repository::findAll()
|
|
111
|
+
/^save$/, // Repository::save()
|
|
112
|
+
/^delete$/, // Repository::delete()
|
|
113
|
+
],
|
|
94
114
|
};
|
|
95
115
|
// ============================================================================
|
|
96
116
|
// UTILITY PATTERNS - Functions that should be penalized
|
|
@@ -211,7 +231,12 @@ export function isTestFile(filePath) {
|
|
|
211
231
|
p.includes('/tests/') ||
|
|
212
232
|
// C# test patterns
|
|
213
233
|
p.includes('.tests/') ||
|
|
214
|
-
p.includes('tests.cs')
|
|
234
|
+
p.includes('tests.cs') ||
|
|
235
|
+
// PHP/Laravel test patterns
|
|
236
|
+
p.endsWith('test.php') ||
|
|
237
|
+
p.endsWith('spec.php') ||
|
|
238
|
+
p.includes('/tests/feature/') ||
|
|
239
|
+
p.includes('/tests/unit/'));
|
|
215
240
|
}
|
|
216
241
|
/**
|
|
217
242
|
* Check if a file path is likely a utility/helper file
|
|
@@ -3,8 +3,8 @@ import path from 'path';
|
|
|
3
3
|
import { glob } from 'glob';
|
|
4
4
|
import { shouldIgnorePath } from '../../config/ignore-service.js';
|
|
5
5
|
const READ_CONCURRENCY = 32;
|
|
6
|
-
/** Skip files larger than
|
|
7
|
-
const MAX_FILE_SIZE =
|
|
6
|
+
/** Skip files larger than 512KB — they're usually generated/vendored and crash tree-sitter */
|
|
7
|
+
const MAX_FILE_SIZE = 512 * 1024;
|
|
8
8
|
/**
|
|
9
9
|
* Phase 1: Scan repository — stat files to get paths + sizes, no content loaded.
|
|
10
10
|
* Memory: ~10MB for 100K files vs ~1GB+ with content.
|
|
@@ -42,7 +42,7 @@ export const walkRepositoryPaths = async (repoPath, onProgress) => {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
if (skippedLarge > 0) {
|
|
45
|
-
console.warn(` Skipped ${skippedLarge} files
|
|
45
|
+
console.warn(` Skipped ${skippedLarge} large files (>${MAX_FILE_SIZE / 1024}KB, likely generated/vendored)`);
|
|
46
46
|
}
|
|
47
47
|
return entries;
|
|
48
48
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Framework Detection
|
|
3
3
|
*
|
|
4
|
-
* Detects frameworks from
|
|
5
|
-
*
|
|
4
|
+
* Detects frameworks from:
|
|
5
|
+
* 1) file path patterns
|
|
6
|
+
* 2) AST definition text (decorators/annotations/attributes)
|
|
7
|
+
* and provides entry point multipliers for process scoring.
|
|
6
8
|
*
|
|
7
9
|
* DESIGN: Returns null for unknown frameworks, which causes a 1.0 multiplier
|
|
8
10
|
* (no bonus, no penalty) - same behavior as before this feature.
|
|
@@ -20,8 +22,8 @@ export interface FrameworkHint {
|
|
|
20
22
|
*/
|
|
21
23
|
export declare function detectFrameworkFromPath(filePath: string): FrameworkHint | null;
|
|
22
24
|
/**
|
|
23
|
-
* Patterns that indicate entry points within code
|
|
24
|
-
* These
|
|
25
|
+
* Patterns that indicate framework entry points within code definitions.
|
|
26
|
+
* These are matched against AST node text (class/method/function declaration text).
|
|
25
27
|
*/
|
|
26
28
|
export declare const FRAMEWORK_AST_PATTERNS: {
|
|
27
29
|
nestjs: string[];
|
|
@@ -32,7 +34,13 @@ export declare const FRAMEWORK_AST_PATTERNS: {
|
|
|
32
34
|
jaxrs: string[];
|
|
33
35
|
aspnet: string[];
|
|
34
36
|
'go-http': string[];
|
|
37
|
+
laravel: string[];
|
|
35
38
|
actix: string[];
|
|
36
39
|
axum: string[];
|
|
37
40
|
rocket: string[];
|
|
38
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Detect framework entry points from AST definition text (decorators/annotations/attributes).
|
|
44
|
+
* Returns null if no known pattern is found.
|
|
45
|
+
*/
|
|
46
|
+
export declare function detectFrameworkFromAST(language: string, definitionText: string): FrameworkHint | null;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Framework Detection
|
|
3
3
|
*
|
|
4
|
-
* Detects frameworks from
|
|
5
|
-
*
|
|
4
|
+
* Detects frameworks from:
|
|
5
|
+
* 1) file path patterns
|
|
6
|
+
* 2) AST definition text (decorators/annotations/attributes)
|
|
7
|
+
* and provides entry point multipliers for process scoring.
|
|
6
8
|
*
|
|
7
9
|
* DESIGN: Returns null for unknown frameworks, which causes a 1.0 multiplier
|
|
8
10
|
* (no bonus, no penalty) - same behavior as before this feature.
|
|
@@ -146,6 +148,55 @@ export function detectFrameworkFromPath(filePath) {
|
|
|
146
148
|
if ((p.includes('/src/') && (p.endsWith('/app.c') || p.endsWith('/app.cpp')))) {
|
|
147
149
|
return { framework: 'c-cpp', entryPointMultiplier: 2.5, reason: 'c-app' };
|
|
148
150
|
}
|
|
151
|
+
// ========== PHP / LARAVEL FRAMEWORKS ==========
|
|
152
|
+
// Laravel routes (highest - these ARE the entry point definitions)
|
|
153
|
+
if (p.includes('/routes/') && p.endsWith('.php')) {
|
|
154
|
+
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-routes' };
|
|
155
|
+
}
|
|
156
|
+
// Laravel controllers (very high - receive HTTP requests)
|
|
157
|
+
if ((p.includes('/http/controllers/') || p.includes('/controllers/')) && p.endsWith('.php')) {
|
|
158
|
+
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller' };
|
|
159
|
+
}
|
|
160
|
+
// Laravel controller by file name convention
|
|
161
|
+
if (p.endsWith('controller.php')) {
|
|
162
|
+
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller-file' };
|
|
163
|
+
}
|
|
164
|
+
// Laravel console commands
|
|
165
|
+
if ((p.includes('/console/commands/') || p.includes('/commands/')) && p.endsWith('.php')) {
|
|
166
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-command' };
|
|
167
|
+
}
|
|
168
|
+
// Laravel jobs (queue entry points)
|
|
169
|
+
if (p.includes('/jobs/') && p.endsWith('.php')) {
|
|
170
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-job' };
|
|
171
|
+
}
|
|
172
|
+
// Laravel listeners (event-driven entry points)
|
|
173
|
+
if (p.includes('/listeners/') && p.endsWith('.php')) {
|
|
174
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-listener' };
|
|
175
|
+
}
|
|
176
|
+
// Laravel middleware
|
|
177
|
+
if (p.includes('/http/middleware/') && p.endsWith('.php')) {
|
|
178
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-middleware' };
|
|
179
|
+
}
|
|
180
|
+
// Laravel service providers
|
|
181
|
+
if (p.includes('/providers/') && p.endsWith('.php')) {
|
|
182
|
+
return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-provider' };
|
|
183
|
+
}
|
|
184
|
+
// Laravel policies
|
|
185
|
+
if (p.includes('/policies/') && p.endsWith('.php')) {
|
|
186
|
+
return { framework: 'laravel', entryPointMultiplier: 2.0, reason: 'laravel-policy' };
|
|
187
|
+
}
|
|
188
|
+
// Laravel models (important but not entry points per se)
|
|
189
|
+
if (p.includes('/models/') && p.endsWith('.php')) {
|
|
190
|
+
return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-model' };
|
|
191
|
+
}
|
|
192
|
+
// Laravel services (Service Repository pattern)
|
|
193
|
+
if (p.includes('/services/') && p.endsWith('.php')) {
|
|
194
|
+
return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-service' };
|
|
195
|
+
}
|
|
196
|
+
// Laravel repositories (Service Repository pattern)
|
|
197
|
+
if (p.includes('/repositories/') && p.endsWith('.php')) {
|
|
198
|
+
return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-repository' };
|
|
199
|
+
}
|
|
149
200
|
// ========== GENERIC PATTERNS ==========
|
|
150
201
|
// Any language: index files in API folders
|
|
151
202
|
if (p.includes('/api/') && (p.endsWith('/index.ts') || p.endsWith('/index.js') ||
|
|
@@ -156,11 +207,11 @@ export function detectFrameworkFromPath(filePath) {
|
|
|
156
207
|
return null;
|
|
157
208
|
}
|
|
158
209
|
// ============================================================================
|
|
159
|
-
//
|
|
210
|
+
// AST-BASED FRAMEWORK DETECTION
|
|
160
211
|
// ============================================================================
|
|
161
212
|
/**
|
|
162
|
-
* Patterns that indicate entry points within code
|
|
163
|
-
* These
|
|
213
|
+
* Patterns that indicate framework entry points within code definitions.
|
|
214
|
+
* These are matched against AST node text (class/method/function declaration text).
|
|
164
215
|
*/
|
|
165
216
|
export const FRAMEWORK_AST_PATTERNS = {
|
|
166
217
|
// JavaScript/TypeScript decorators
|
|
@@ -176,8 +227,57 @@ export const FRAMEWORK_AST_PATTERNS = {
|
|
|
176
227
|
'aspnet': ['[ApiController]', '[HttpGet]', '[HttpPost]', '[Route]'],
|
|
177
228
|
// Go patterns (function signatures)
|
|
178
229
|
'go-http': ['http.Handler', 'http.HandlerFunc', 'ServeHTTP'],
|
|
230
|
+
// PHP/Laravel
|
|
231
|
+
'laravel': ['Route::get', 'Route::post', 'Route::put', 'Route::delete',
|
|
232
|
+
'Route::resource', 'Route::apiResource', '#[Route('],
|
|
179
233
|
// Rust macros
|
|
180
234
|
'actix': ['#[get', '#[post', '#[put', '#[delete'],
|
|
181
235
|
'axum': ['Router::new'],
|
|
182
236
|
'rocket': ['#[get', '#[post'],
|
|
183
237
|
};
|
|
238
|
+
const AST_FRAMEWORK_PATTERNS_BY_LANGUAGE = {
|
|
239
|
+
javascript: [
|
|
240
|
+
{ framework: 'nestjs', entryPointMultiplier: 3.2, reason: 'nestjs-decorator', patterns: FRAMEWORK_AST_PATTERNS.nestjs },
|
|
241
|
+
],
|
|
242
|
+
typescript: [
|
|
243
|
+
{ framework: 'nestjs', entryPointMultiplier: 3.2, reason: 'nestjs-decorator', patterns: FRAMEWORK_AST_PATTERNS.nestjs },
|
|
244
|
+
],
|
|
245
|
+
python: [
|
|
246
|
+
{ framework: 'fastapi', entryPointMultiplier: 3.0, reason: 'fastapi-decorator', patterns: FRAMEWORK_AST_PATTERNS.fastapi },
|
|
247
|
+
{ framework: 'flask', entryPointMultiplier: 2.8, reason: 'flask-decorator', patterns: FRAMEWORK_AST_PATTERNS.flask },
|
|
248
|
+
],
|
|
249
|
+
java: [
|
|
250
|
+
{ framework: 'spring', entryPointMultiplier: 3.2, reason: 'spring-annotation', patterns: FRAMEWORK_AST_PATTERNS.spring },
|
|
251
|
+
{ framework: 'jaxrs', entryPointMultiplier: 3.0, reason: 'jaxrs-annotation', patterns: FRAMEWORK_AST_PATTERNS.jaxrs },
|
|
252
|
+
],
|
|
253
|
+
csharp: [
|
|
254
|
+
{ framework: 'aspnet', entryPointMultiplier: 3.2, reason: 'aspnet-attribute', patterns: FRAMEWORK_AST_PATTERNS.aspnet },
|
|
255
|
+
],
|
|
256
|
+
php: [
|
|
257
|
+
{ framework: 'laravel', entryPointMultiplier: 3.0, reason: 'php-route-attribute', patterns: FRAMEWORK_AST_PATTERNS.laravel },
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Detect framework entry points from AST definition text (decorators/annotations/attributes).
|
|
262
|
+
* Returns null if no known pattern is found.
|
|
263
|
+
*/
|
|
264
|
+
export function detectFrameworkFromAST(language, definitionText) {
|
|
265
|
+
if (!language || !definitionText)
|
|
266
|
+
return null;
|
|
267
|
+
const configs = AST_FRAMEWORK_PATTERNS_BY_LANGUAGE[language.toLowerCase()];
|
|
268
|
+
if (!configs || configs.length === 0)
|
|
269
|
+
return null;
|
|
270
|
+
const normalized = definitionText.toLowerCase();
|
|
271
|
+
for (const cfg of configs) {
|
|
272
|
+
for (const pattern of cfg.patterns) {
|
|
273
|
+
if (normalized.includes(pattern.toLowerCase())) {
|
|
274
|
+
return {
|
|
275
|
+
framework: cfg.framework,
|
|
276
|
+
entryPointMultiplier: cfg.entryPointMultiplier,
|
|
277
|
+
reason: cfg.reason,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|