gitnexus 1.3.3 → 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.
Files changed (45) hide show
  1. package/dist/cli/ai-context.js +23 -52
  2. package/dist/cli/analyze.js +4 -1
  3. package/dist/cli/index.js +1 -0
  4. package/dist/cli/mcp.js +11 -22
  5. package/dist/cli/serve.d.ts +1 -0
  6. package/dist/cli/serve.js +2 -1
  7. package/dist/cli/setup.js +2 -2
  8. package/dist/cli/wiki.js +6 -2
  9. package/dist/config/supported-languages.d.ts +2 -1
  10. package/dist/config/supported-languages.js +1 -1
  11. package/dist/core/embeddings/embedder.js +40 -1
  12. package/dist/core/graph/types.d.ts +2 -0
  13. package/dist/core/ingestion/entry-point-scoring.js +26 -1
  14. package/dist/core/ingestion/framework-detection.d.ts +12 -4
  15. package/dist/core/ingestion/framework-detection.js +105 -5
  16. package/dist/core/ingestion/import-processor.js +77 -0
  17. package/dist/core/ingestion/parsing-processor.js +50 -8
  18. package/dist/core/ingestion/process-processor.js +7 -1
  19. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -0
  20. package/dist/core/ingestion/tree-sitter-queries.js +361 -282
  21. package/dist/core/ingestion/utils.js +6 -0
  22. package/dist/core/ingestion/workers/parse-worker.d.ts +3 -0
  23. package/dist/core/ingestion/workers/parse-worker.js +191 -0
  24. package/dist/core/kuzu/csv-generator.js +4 -2
  25. package/dist/core/kuzu/kuzu-adapter.d.ts +9 -0
  26. package/dist/core/kuzu/kuzu-adapter.js +68 -9
  27. package/dist/core/kuzu/schema.d.ts +6 -6
  28. package/dist/core/kuzu/schema.js +8 -0
  29. package/dist/core/tree-sitter/parser-loader.js +2 -0
  30. package/dist/core/wiki/generator.js +2 -2
  31. package/dist/mcp/local/local-backend.js +25 -13
  32. package/dist/mcp/server.d.ts +9 -0
  33. package/dist/mcp/server.js +13 -2
  34. package/dist/mcp/staleness.js +2 -2
  35. package/dist/server/api.d.ts +7 -5
  36. package/dist/server/api.js +145 -127
  37. package/dist/server/mcp-http.d.ts +13 -0
  38. package/dist/server/mcp-http.js +100 -0
  39. package/package.json +2 -1
  40. package/skills/gitnexus-cli.md +82 -0
  41. package/skills/{debugging.md → gitnexus-debugging.md} +12 -8
  42. package/skills/{exploring.md → gitnexus-exploring.md} +10 -7
  43. package/skills/gitnexus-guide.md +64 -0
  44. package/skills/{impact-analysis.md → gitnexus-impact-analysis.md} +14 -11
  45. package/skills/{refactoring.md → gitnexus-refactoring.md} +15 -7
@@ -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
- ## Tools Reference
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: 'Navigate unfamiliar code using GitNexus knowledge graph',
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: 'debugging',
146
- description: 'Trace bugs through call chains using knowledge graph',
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: 'impact-analysis',
150
- description: 'Analyze blast radius before making code changes',
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: 'refactoring',
154
- description: 'Plan safe refactors using blast radius and dependency mapping',
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: gitnexus-${skill.name}
143
+ name: ${skill.name}
173
144
  description: ${skill.description}
174
145
  ---
175
146
 
@@ -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
- import { runEmbeddingPipeline } from '../core/embeddings/embedding-pipeline.js';
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
- // Load all registered repos
22
- const entries = await listRegisteredRepos({ validate: true });
23
- if (entries.length === 0) {
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
- const ok = await backend.init();
37
- if (!ok) {
38
- console.error('GitNexus: Failed to initialize backend from registry.');
39
- process.exit(1);
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
- const repoNames = (await backend.listRepos()).map(r => r.name);
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
  };
@@ -1,3 +1,4 @@
1
1
  export declare const serveCommand: (options?: {
2
2
  port?: string;
3
+ host?: string;
3
4
  }) => Promise<void>;
package/dist/cli/serve.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createServer } from '../server/api.js';
2
2
  export const serveCommand = async (options) => {
3
3
  const port = Number(options?.port ?? 4747);
4
- await createServer(port);
4
+ const host = options?.host ?? '127.0.0.1';
5
+ await createServer(port, host);
5
6
  };
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, `gitnexus-${skillName}`);
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 = execSync(`gh gist create "${htmlPath}" --desc "Repository Wiki — generated by GitNexus" --public`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
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];
@@ -7,5 +7,6 @@ export declare enum SupportedLanguages {
7
7
  CPlusPlus = "cpp",
8
8
  CSharp = "csharp",
9
9
  Go = "go",
10
- Rust = "rust"
10
+ Rust = "rust",
11
+ PHP = "php"
11
12
  }
@@ -9,7 +9,7 @@ export var SupportedLanguages;
9
9
  SupportedLanguages["CSharp"] = "csharp";
10
10
  SupportedLanguages["Go"] = "go";
11
11
  SupportedLanguages["Rust"] = "rust";
12
- // PHP = 'php',
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 {
@@ -6,6 +6,8 @@ export type NodeProperties = {
6
6
  endLine?: number;
7
7
  language?: string;
8
8
  isExported?: boolean;
9
+ astFrameworkMultiplier?: number;
10
+ astFrameworkReason?: string;
9
11
  heuristicLabel?: string;
10
12
  cohesion?: number;
11
13
  symbolCount?: number;
@@ -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
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Framework Detection
3
3
  *
4
- * Detects frameworks from file path patterns and provides entry point multipliers.
5
- * This enables framework-aware entry point scoring.
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 (for future AST-based detection)
24
- * These would require parsing decorators/annotations in the code itself.
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 file path patterns and provides entry point multipliers.
5
- * This enables framework-aware entry point scoring.
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
- // FUTURE: AST-BASED PATTERNS (for Phase 3)
210
+ // AST-BASED FRAMEWORK DETECTION
160
211
  // ============================================================================
161
212
  /**
162
- * Patterns that indicate entry points within code (for future AST-based detection)
163
- * These would require parsing decorators/annotations in the code itself.
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
+ }