mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-05 08:06:02 +00:00
enhanced process maps
This commit is contained in:
parent
32c3544d0d
commit
d3dbdb1aff
12 changed files with 855 additions and 75 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "gitnexus-mcp",
|
||||
"version": "0.1.1",
|
||||
"version": "0.2.0",
|
||||
"description": "MCP server for GitNexus code intelligence - connect Cursor, Claude, and other AI agents to your codebase",
|
||||
"author": "Abhigyan Patwari",
|
||||
"license": "MIT",
|
||||
|
|
@ -44,4 +44,4 @@
|
|||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,22 +74,28 @@ function formatContextAsMarkdown(context: CodebaseContext): string {
|
|||
// Usage hints
|
||||
lines.push('## 🛠️ Available Tools');
|
||||
lines.push('');
|
||||
lines.push('- **search**: Semantic search across the codebase');
|
||||
lines.push('- **cypher**: Execute Cypher queries on the knowledge graph');
|
||||
lines.push('- **blastRadius**: Analyze impact of changes to a node');
|
||||
lines.push('- **highlight**: Visualize nodes in the graph');
|
||||
lines.push('- **search**: Semantic + keyword search across codebase');
|
||||
lines.push('- **cypher**: Execute Cypher queries on knowledge graph');
|
||||
lines.push('- **grep**: Regex pattern search in files');
|
||||
lines.push('- **read**: Read file contents');
|
||||
lines.push('- **explore**: Deep dive on symbol, cluster, or process');
|
||||
lines.push('- **overview**: Codebase map (all clusters + processes)');
|
||||
lines.push('- **impact**: Analyze change impact (upstream/downstream)');
|
||||
lines.push('- **highlight**: Visualize nodes in graph');
|
||||
lines.push('');
|
||||
lines.push('## 📝 Graph Schema');
|
||||
lines.push('');
|
||||
lines.push('**Node Types**: File, Folder, Function, Class, Interface, Method');
|
||||
lines.push('**Node Types**: File, Folder, Function, Class, Interface, Method, Community, Process');
|
||||
lines.push('');
|
||||
lines.push('**Relation**: `CodeRelation` with `type` property:');
|
||||
lines.push('- CONTAINS, DEFINES, IMPORTS, CALLS, EXTENDS, IMPLEMENTS');
|
||||
lines.push('- CALLS, IMPORTS, EXTENDS, IMPLEMENTS, CONTAINS, DEFINES');
|
||||
lines.push('- MEMBER_OF (symbol → community), STEP_IN_PROCESS (symbol → process)');
|
||||
lines.push('');
|
||||
lines.push('**Example Cypher Queries**:');
|
||||
lines.push('```cypher');
|
||||
lines.push('MATCH (f:Function) RETURN f.name LIMIT 10');
|
||||
lines.push("MATCH (f:File)-[:CodeRelation {type: 'IMPORTS'}]->(g:File) RETURN f.name, g.name");
|
||||
lines.push("MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community) RETURN c.label, count(s)");
|
||||
lines.push('```');
|
||||
|
||||
return lines.join('\n');
|
||||
|
|
|
|||
|
|
@ -41,19 +41,20 @@ ALWAYS call this first to understand the codebase before searching or querying.`
|
|||
{
|
||||
name: 'search',
|
||||
description: `Hybrid search (keyword + semantic) across the codebase.
|
||||
Returns code nodes with their graph connections.
|
||||
Returns code nodes with their graph connections, grouped by process.
|
||||
|
||||
WHEN TO USE:
|
||||
- Finding implementations ("where is auth handled?")
|
||||
- Understanding code flow ("what calls UserService?")
|
||||
- Locating patterns ("find all API endpoints")
|
||||
|
||||
RETURNS: Array of {name, type, filePath, code, connections[]}`,
|
||||
RETURNS: Array of {name, type, filePath, code, connections[], cluster, processes[]}`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', description: 'Natural language or keyword search query' },
|
||||
limit: { type: 'number', description: 'Max results to return', default: 10 },
|
||||
groupByProcess: { type: 'boolean', description: 'Group results by process', default: true },
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
|
|
@ -63,23 +64,23 @@ RETURNS: Array of {name, type, filePath, code, connections[]}`,
|
|||
description: `Execute Cypher query against the code knowledge graph.
|
||||
|
||||
SCHEMA:
|
||||
- Nodes: File, Function, Class, Interface, Method
|
||||
- Edges: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, CONTAINS
|
||||
- Nodes: File, Folder, Function, Class, Interface, Method, Community, Process
|
||||
- Edges via CodeRelation.type: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, CONTAINS, DEFINES, MEMBER_OF, STEP_IN_PROCESS
|
||||
|
||||
EXAMPLES:
|
||||
• Find callers of a function:
|
||||
MATCH (a)-[:CALLS]->(b:Function {name: "validateUser"}) RETURN a.name, a.filePath
|
||||
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b:Function {name: "validateUser"}) RETURN a.name, a.filePath
|
||||
|
||||
• Find class hierarchy:
|
||||
MATCH (c:Class)-[:EXTENDS*]->(base) WHERE c.name = "AdminUser" RETURN base.name
|
||||
• Find all functions in a community:
|
||||
MATCH (f:Function)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community {label: "Auth"}) RETURN f.name
|
||||
|
||||
• Impact analysis (what depends on X):
|
||||
MATCH (target:Function {name: $name})<-[:CALLS*1..3]-(caller) RETURN DISTINCT caller
|
||||
• Find steps in a process:
|
||||
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {label: "UserLogin"}) RETURN s.name, r.step ORDER BY r.step
|
||||
|
||||
TIPS:
|
||||
- Relationship types are UPPERCASE: CALLS, IMPORTS, EXTENDS
|
||||
- Node labels are PascalCase: Function, Class, Interface
|
||||
- Properties: name, filePath, code, startLine, endLine`,
|
||||
- All relationships use CodeRelation table with 'type' property
|
||||
- Community = functional cluster detected by Leiden algorithm
|
||||
- Process = execution flow trace from entry point to terminal`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
|
@ -133,17 +134,60 @@ RETURNS: {filePath, content, language, lines}`,
|
|||
},
|
||||
},
|
||||
{
|
||||
name: 'blastRadius',
|
||||
name: 'explore',
|
||||
description: `Deep dive on a symbol, cluster, or process.
|
||||
|
||||
TYPE: symbol | cluster | process
|
||||
|
||||
For SYMBOL: Shows cluster membership, process participation, callers/callees
|
||||
For CLUSTER: Shows members, cohesion score, processes touching it
|
||||
For PROCESS: Shows step-by-step trace, clusters traversed, entry/terminal points
|
||||
|
||||
Use after search to understand context of a specific node.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name of symbol, cluster, or process to explore' },
|
||||
type: { type: 'string', description: 'Type: symbol, cluster, or process' },
|
||||
},
|
||||
required: ['name', 'type'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'overview',
|
||||
description: `Get codebase map showing all clusters and processes.
|
||||
|
||||
Returns:
|
||||
- All communities (clusters) with member counts and cohesion scores
|
||||
- All processes with step counts and types (intra/cross-community)
|
||||
- High-level architectural view
|
||||
|
||||
Use to understand overall codebase structure before diving deep.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
showProcesses: { type: 'boolean', description: 'Include process list', default: true },
|
||||
showClusters: { type: 'boolean', description: 'Include cluster list', default: true },
|
||||
limit: { type: 'number', description: 'Max items per category', default: 20 },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'impact',
|
||||
description: `Analyze the impact of changing a code element.
|
||||
Returns all nodes affected by modifying the target, with distance, edge type, and confidence.
|
||||
|
||||
USE BEFORE making changes to understand ripple effects.
|
||||
|
||||
Output format (compact tabular):
|
||||
Type|Name|File:Line|EdgeType|Confidence%
|
||||
Output includes:
|
||||
- Affected processes (with step positions)
|
||||
- Affected clusters (direct/indirect)
|
||||
- Risk assessment (critical/high/medium/low)
|
||||
- Callers/dependents grouped by depth
|
||||
|
||||
EdgeType: CALLS, IMPORTS, EXTENDS, IMPLEMENTS
|
||||
Confidence: 100% = certain, <80% = fuzzy match [fuzzy]
|
||||
Confidence: 100% = certain, <80% = fuzzy match
|
||||
|
||||
Depth groups:
|
||||
- d=1: WILL BREAK (direct callers/importers)
|
||||
|
|
@ -155,7 +199,7 @@ Depth groups:
|
|||
target: { type: 'string', description: 'Name of function, class, or file to analyze' },
|
||||
direction: { type: 'string', description: 'upstream (what depends on this) or downstream (what this depends on)' },
|
||||
maxDepth: { type: 'number', description: 'Max relationship depth (default: 3)', default: 3 },
|
||||
relationTypes: { type: 'array', items: { type: 'string' }, description: 'Filter: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, CONTAINS, DEFINES (default: usage-based)' },
|
||||
relationTypes: { type: 'array', items: { type: 'string' }, description: 'Filter: CALLS, IMPORTS, EXTENDS, IMPLEMENTS (default: usage-based)' },
|
||||
includeTests: { type: 'boolean', description: 'Include test files (default: false)' },
|
||||
minConfidence: { type: 'number', description: 'Minimum confidence 0-1 (default: 0.7)' },
|
||||
},
|
||||
|
|
|
|||
74
gitnexus/docs/FRAMEWORK_SUPPORT.md
Normal file
74
gitnexus/docs/FRAMEWORK_SUPPORT.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# Framework Support for Entry Point Detection
|
||||
|
||||
GitNexus automatically detects frameworks and boosts entry point scores for known patterns.
|
||||
|
||||
## Status Legend
|
||||
- ✅ Supported (path-based detection)
|
||||
- ❌ Not yet supported
|
||||
|
||||
---
|
||||
|
||||
## JavaScript / TypeScript
|
||||
|
||||
| Framework | Status | Detection Pattern | Multiplier |
|
||||
|-----------|--------|-------------------|------------|
|
||||
| Next.js (Pages) | ✅ | `/pages/*.tsx` | 3.0x |
|
||||
| Next.js (App) | ✅ | `/app/*/page.tsx` | 3.0x |
|
||||
| Next.js API | ✅ | `/pages/api/*`, `/app/*/route.ts` | 3.0x |
|
||||
| Express.js | ✅ | `/routes/*` | 2.5x |
|
||||
| React | ✅ | `/components/*.tsx` (PascalCase) | 1.5x |
|
||||
| NestJS | ❌ | TODO: `@Controller` decorator | - |
|
||||
|
||||
## Python
|
||||
|
||||
| Framework | Status | Detection Pattern | Multiplier |
|
||||
|-----------|--------|-------------------|------------|
|
||||
| Django | ✅ | `views.py`, `urls.py` | 3.0x |
|
||||
| FastAPI | ✅ | `/routers/*`, `/endpoints/*` | 2.5x |
|
||||
| Flask | ✅ | `/routes/*` | 2.5x |
|
||||
|
||||
## Java
|
||||
|
||||
| Framework | Status | Detection Pattern | Multiplier |
|
||||
|-----------|--------|-------------------|------------|
|
||||
| Spring Boot | ✅ | `/controller/*`, `*Controller.java` | 3.0x |
|
||||
| JAX-RS | ❌ | TODO: `@Path` annotation | - |
|
||||
|
||||
## C#
|
||||
|
||||
| Framework | Status | Detection Pattern | Multiplier |
|
||||
|-----------|--------|-------------------|------------|
|
||||
| ASP.NET Core | ✅ | `/Controllers/*`, `*Controller.cs` | 3.0x |
|
||||
| Blazor | ✅ | `/Pages/*.razor` | 2.5x |
|
||||
|
||||
## Go
|
||||
|
||||
| Framework | Status | Detection Pattern | Multiplier |
|
||||
|-----------|--------|-------------------|------------|
|
||||
| net/http | ✅ | `/handlers/*`, `main.go` | 2.5-3.0x |
|
||||
| Gin/Echo | ✅ | `/handlers/*`, `/routes/*` | 2.5x |
|
||||
|
||||
## Rust
|
||||
|
||||
| Framework | Status | Detection Pattern | Multiplier |
|
||||
|-----------|--------|-------------------|------------|
|
||||
| Actix/Axum/Rocket | ✅ | `/handlers/*`, `main.rs` | 2.5-3.0x |
|
||||
|
||||
## C / C++
|
||||
|
||||
| Framework | Status | Detection Pattern | Multiplier |
|
||||
|-----------|--------|-------------------|------------|
|
||||
| Generic | ✅ | `main.c`, `main.cpp` | 3.0x |
|
||||
|
||||
---
|
||||
|
||||
## Adding New Framework Support
|
||||
|
||||
1. Edit `framework-detection.ts` → `detectFrameworkFromPath()`
|
||||
2. Add path pattern with appropriate multiplier
|
||||
3. Update this documentation
|
||||
4. Test with a sample project
|
||||
|
||||
## Graceful Fallback
|
||||
|
||||
Unknown frameworks return `null`, resulting in a **1.0x multiplier** (no bonus, no penalty).
|
||||
|
|
@ -38,6 +38,9 @@ export type NodeProperties = {
|
|||
communities?: string[],
|
||||
entryPointId?: string,
|
||||
terminalId?: string,
|
||||
// Entry point scoring (computed by process detection)
|
||||
entryPointScore?: number,
|
||||
entryPointReason?: string,
|
||||
}
|
||||
|
||||
export type RelationshipType =
|
||||
|
|
|
|||
281
gitnexus/src/core/ingestion/entry-point-scoring.ts
Normal file
281
gitnexus/src/core/ingestion/entry-point-scoring.ts
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
/**
|
||||
* Entry Point Scoring
|
||||
*
|
||||
* Calculates entry point scores for process detection based on:
|
||||
* 1. Call ratio (existing algorithm - callees / (callers + 1))
|
||||
* 2. Export status (exported functions get higher priority)
|
||||
* 3. Name patterns (functions matching entry point patterns like handle*, on*, *Controller)
|
||||
* 4. Framework detection (path-based detection for Next.js, Express, Django, etc.)
|
||||
*
|
||||
* This module is language-agnostic - language-specific patterns are defined per language.
|
||||
*/
|
||||
|
||||
import { detectFrameworkFromPath } from './framework-detection';
|
||||
|
||||
// ============================================================================
|
||||
// NAME PATTERNS - All 9 supported languages
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Common entry point naming patterns by language
|
||||
* These patterns indicate functions that are likely feature entry points
|
||||
*/
|
||||
const ENTRY_POINT_PATTERNS: Record<string, RegExp[]> = {
|
||||
// Universal patterns (apply to all languages)
|
||||
'*': [
|
||||
/^(main|init|bootstrap|start|run|setup|configure)$/i,
|
||||
/^handle[A-Z]/, // handleLogin, handleSubmit
|
||||
/^on[A-Z]/, // onClick, onSubmit
|
||||
/Handler$/, // RequestHandler
|
||||
/Controller$/, // UserController
|
||||
/^process[A-Z]/, // processPayment
|
||||
/^execute[A-Z]/, // executeQuery
|
||||
/^perform[A-Z]/, // performAction
|
||||
/^dispatch[A-Z]/, // dispatchEvent
|
||||
/^trigger[A-Z]/, // triggerAction
|
||||
/^fire[A-Z]/, // fireEvent
|
||||
/^emit[A-Z]/, // emitEvent
|
||||
],
|
||||
|
||||
// JavaScript/TypeScript
|
||||
'javascript': [
|
||||
/^use[A-Z]/, // React hooks (useEffect, etc.)
|
||||
],
|
||||
'typescript': [
|
||||
/^use[A-Z]/, // React hooks
|
||||
],
|
||||
|
||||
// Python
|
||||
'python': [
|
||||
/^app$/, // Flask/FastAPI app
|
||||
/^(get|post|put|delete|patch)_/i, // REST conventions
|
||||
/^api_/, // API functions
|
||||
/^view_/, // Django views
|
||||
],
|
||||
|
||||
// Java
|
||||
'java': [
|
||||
/^do[A-Z]/, // doGet, doPost (Servlets)
|
||||
/^create[A-Z]/, // Factory patterns
|
||||
/^build[A-Z]/, // Builder patterns
|
||||
/Service$/, // UserService
|
||||
],
|
||||
|
||||
// C#
|
||||
'csharp': [
|
||||
/^(Get|Post|Put|Delete)/, // ASP.NET conventions
|
||||
/Action$/, // MVC actions
|
||||
/^On[A-Z]/, // Event handlers
|
||||
/Async$/, // Async entry points
|
||||
],
|
||||
|
||||
// Go
|
||||
'go': [
|
||||
/Handler$/, // http.Handler pattern
|
||||
/^Serve/, // ServeHTTP
|
||||
/^New[A-Z]/, // Constructor pattern (returns new instance)
|
||||
/^Make[A-Z]/, // Make functions
|
||||
],
|
||||
|
||||
// Rust
|
||||
'rust': [
|
||||
/^(get|post|put|delete)_handler$/i,
|
||||
/^handle_/, // handle_request
|
||||
/^new$/, // Constructor pattern
|
||||
/^run$/, // run entry point
|
||||
/^spawn/, // Async spawn
|
||||
],
|
||||
|
||||
// C - explicit main() boost (critical for C programs)
|
||||
'c': [
|
||||
/^main$/, // THE entry point
|
||||
/^init_/, // Initialization functions
|
||||
/^start_/, // Start functions
|
||||
/^run_/, // Run functions
|
||||
],
|
||||
|
||||
// C++ - same as C plus class patterns
|
||||
'cpp': [
|
||||
/^main$/, // THE entry point
|
||||
/^init_/,
|
||||
/^Create[A-Z]/, // Factory patterns
|
||||
/^Run$/, // Run methods
|
||||
/^Start$/, // Start methods
|
||||
],
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// UTILITY PATTERNS - Functions that should be penalized
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Patterns that indicate utility/helper functions (NOT entry points)
|
||||
* These get penalized in scoring
|
||||
*/
|
||||
const UTILITY_PATTERNS: RegExp[] = [
|
||||
/^(get|set|is|has|can|should|will|did)[A-Z]/, // Accessors/predicates
|
||||
/^_/, // Private by convention
|
||||
/^(format|parse|validate|convert|transform)/i, // Transformation utilities
|
||||
/^(log|debug|error|warn|info)$/i, // Logging
|
||||
/^(to|from)[A-Z]/, // Conversions
|
||||
/^(encode|decode)/i, // Encoding utilities
|
||||
/^(serialize|deserialize)/i, // Serialization
|
||||
/^(clone|copy|deep)/i, // Cloning utilities
|
||||
/^(merge|extend|assign)/i, // Object utilities
|
||||
/^(filter|map|reduce|sort|find)/i, // Collection utilities (standalone)
|
||||
/Helper$/,
|
||||
/Util$/,
|
||||
/Utils$/,
|
||||
/^utils?$/i,
|
||||
/^helpers?$/i,
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
export interface EntryPointScoreResult {
|
||||
score: number;
|
||||
reasons: string[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN SCORING FUNCTION
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Calculate an entry point score for a function/method
|
||||
*
|
||||
* Higher scores indicate better entry point candidates.
|
||||
* Score = baseScore × exportMultiplier × nameMultiplier
|
||||
*
|
||||
* @param name - Function/method name
|
||||
* @param language - Programming language
|
||||
* @param isExported - Whether the function is exported/public
|
||||
* @param callerCount - Number of functions that call this function
|
||||
* @param calleeCount - Number of functions this function calls
|
||||
* @returns Score and array of reasons explaining the score
|
||||
*/
|
||||
export function calculateEntryPointScore(
|
||||
name: string,
|
||||
language: string,
|
||||
isExported: boolean,
|
||||
callerCount: number,
|
||||
calleeCount: number,
|
||||
filePath: string = '' // Optional for backwards compatibility
|
||||
): EntryPointScoreResult {
|
||||
const reasons: string[] = [];
|
||||
|
||||
// Must have outgoing calls to be an entry point (we need to trace forward)
|
||||
if (calleeCount === 0) {
|
||||
return { score: 0, reasons: ['no-outgoing-calls'] };
|
||||
}
|
||||
|
||||
// Base score: call ratio (existing algorithm)
|
||||
// High ratio = calls many, called by few = likely entry point
|
||||
const baseScore = calleeCount / (callerCount + 1);
|
||||
reasons.push(`base:${baseScore.toFixed(2)}`);
|
||||
|
||||
// Export bonus: exported/public functions are more likely entry points
|
||||
const exportMultiplier = isExported ? 2.0 : 1.0;
|
||||
if (isExported) {
|
||||
reasons.push('exported');
|
||||
}
|
||||
|
||||
// Name pattern scoring
|
||||
let nameMultiplier = 1.0;
|
||||
|
||||
// Check negative patterns first (utilities get penalized)
|
||||
if (UTILITY_PATTERNS.some(p => p.test(name))) {
|
||||
nameMultiplier = 0.3; // Significant penalty
|
||||
reasons.push('utility-pattern');
|
||||
} else {
|
||||
// Check positive patterns
|
||||
const universalPatterns = ENTRY_POINT_PATTERNS['*'] || [];
|
||||
const langPatterns = ENTRY_POINT_PATTERNS[language] || [];
|
||||
const allPatterns = [...universalPatterns, ...langPatterns];
|
||||
|
||||
if (allPatterns.some(p => p.test(name))) {
|
||||
nameMultiplier = 1.5; // Bonus for matching entry point pattern
|
||||
reasons.push('entry-pattern');
|
||||
}
|
||||
}
|
||||
|
||||
// Framework detection bonus (Phase 2)
|
||||
let frameworkMultiplier = 1.0;
|
||||
if (filePath) {
|
||||
const frameworkHint = detectFrameworkFromPath(filePath);
|
||||
if (frameworkHint) {
|
||||
frameworkMultiplier = frameworkHint.entryPointMultiplier;
|
||||
reasons.push(`framework:${frameworkHint.reason}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate final score
|
||||
const finalScore = baseScore * exportMultiplier * nameMultiplier * frameworkMultiplier;
|
||||
|
||||
return {
|
||||
score: finalScore,
|
||||
reasons,
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Check if a file path is a test file (should be excluded from entry points)
|
||||
* Covers common test file patterns across all supported languages
|
||||
*/
|
||||
export function isTestFile(filePath: string): boolean {
|
||||
const p = filePath.toLowerCase().replace(/\\/g, '/');
|
||||
|
||||
return (
|
||||
// JavaScript/TypeScript test patterns
|
||||
p.includes('.test.') ||
|
||||
p.includes('.spec.') ||
|
||||
p.includes('__tests__/') ||
|
||||
p.includes('__mocks__/') ||
|
||||
// Generic test folders
|
||||
p.includes('/test/') ||
|
||||
p.includes('/tests/') ||
|
||||
p.includes('/testing/') ||
|
||||
// Python test patterns
|
||||
p.endsWith('_test.py') ||
|
||||
p.includes('/test_') ||
|
||||
// Go test patterns
|
||||
p.endsWith('_test.go') ||
|
||||
// Java test patterns
|
||||
p.includes('/src/test/') ||
|
||||
// Rust test patterns (inline tests are different, but test files)
|
||||
p.includes('/tests/') ||
|
||||
// C# test patterns
|
||||
p.includes('.tests/') ||
|
||||
p.includes('tests.cs')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file path is likely a utility/helper file
|
||||
* These might still have entry points but should be lower priority
|
||||
*/
|
||||
export function isUtilityFile(filePath: string): boolean {
|
||||
const p = filePath.toLowerCase().replace(/\\/g, '/');
|
||||
|
||||
return (
|
||||
p.includes('/utils/') ||
|
||||
p.includes('/util/') ||
|
||||
p.includes('/helpers/') ||
|
||||
p.includes('/helper/') ||
|
||||
p.includes('/common/') ||
|
||||
p.includes('/shared/') ||
|
||||
p.includes('/lib/') ||
|
||||
p.endsWith('/utils.ts') ||
|
||||
p.endsWith('/utils.js') ||
|
||||
p.endsWith('/helpers.ts') ||
|
||||
p.endsWith('/helpers.js') ||
|
||||
p.endsWith('_utils.py') ||
|
||||
p.endsWith('_helpers.py')
|
||||
);
|
||||
}
|
||||
243
gitnexus/src/core/ingestion/framework-detection.ts
Normal file
243
gitnexus/src/core/ingestion/framework-detection.ts
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
/**
|
||||
* Framework Detection
|
||||
*
|
||||
* Detects frameworks from file path patterns and provides entry point multipliers.
|
||||
* This enables framework-aware entry point scoring.
|
||||
*
|
||||
* DESIGN: Returns null for unknown frameworks, which causes a 1.0 multiplier
|
||||
* (no bonus, no penalty) - same behavior as before this feature.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
export interface FrameworkHint {
|
||||
framework: string;
|
||||
entryPointMultiplier: number;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PATH-BASED FRAMEWORK DETECTION
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Detect framework from file path patterns
|
||||
*
|
||||
* This provides entry point multipliers based on well-known framework conventions.
|
||||
* Returns null if no framework pattern is detected (falls back to 1.0 multiplier).
|
||||
*/
|
||||
export function detectFrameworkFromPath(filePath: string): FrameworkHint | null {
|
||||
// Normalize path separators and ensure leading slash for consistent matching
|
||||
let p = filePath.toLowerCase().replace(/\\/g, '/');
|
||||
if (!p.startsWith('/')) {
|
||||
p = '/' + p; // Add leading slash so patterns like '/app/' match 'app/...'
|
||||
}
|
||||
|
||||
// ========== JAVASCRIPT / TYPESCRIPT FRAMEWORKS ==========
|
||||
|
||||
// Next.js - Pages Router (high confidence)
|
||||
if (p.includes('/pages/') && !p.includes('/_') && !p.includes('/api/')) {
|
||||
if (p.endsWith('.tsx') || p.endsWith('.ts') || p.endsWith('.jsx') || p.endsWith('.js')) {
|
||||
return { framework: 'nextjs-pages', entryPointMultiplier: 3.0, reason: 'nextjs-page' };
|
||||
}
|
||||
}
|
||||
|
||||
// Next.js - App Router (page.tsx files)
|
||||
if (p.includes('/app/') && (
|
||||
p.endsWith('page.tsx') || p.endsWith('page.ts') ||
|
||||
p.endsWith('page.jsx') || p.endsWith('page.js')
|
||||
)) {
|
||||
return { framework: 'nextjs-app', entryPointMultiplier: 3.0, reason: 'nextjs-app-page' };
|
||||
}
|
||||
|
||||
// Next.js - API Routes
|
||||
if (p.includes('/pages/api/') || (p.includes('/app/') && p.includes('/api/') && p.endsWith('route.ts'))) {
|
||||
return { framework: 'nextjs-api', entryPointMultiplier: 3.0, reason: 'nextjs-api-route' };
|
||||
}
|
||||
|
||||
// Next.js - Layout files (moderate - they're entry-ish but not the main entry)
|
||||
if (p.includes('/app/') && (p.endsWith('layout.tsx') || p.endsWith('layout.ts'))) {
|
||||
return { framework: 'nextjs-app', entryPointMultiplier: 2.0, reason: 'nextjs-layout' };
|
||||
}
|
||||
|
||||
// Express / Node.js routes
|
||||
if (p.includes('/routes/') && (p.endsWith('.ts') || p.endsWith('.js'))) {
|
||||
return { framework: 'express', entryPointMultiplier: 2.5, reason: 'routes-folder' };
|
||||
}
|
||||
|
||||
// Generic controllers (MVC pattern)
|
||||
if (p.includes('/controllers/') && (p.endsWith('.ts') || p.endsWith('.js'))) {
|
||||
return { framework: 'mvc', entryPointMultiplier: 2.5, reason: 'controllers-folder' };
|
||||
}
|
||||
|
||||
// Generic handlers
|
||||
if (p.includes('/handlers/') && (p.endsWith('.ts') || p.endsWith('.js'))) {
|
||||
return { framework: 'handlers', entryPointMultiplier: 2.5, reason: 'handlers-folder' };
|
||||
}
|
||||
|
||||
// React components (lower priority - not all are entry points)
|
||||
if ((p.includes('/components/') || p.includes('/views/')) &&
|
||||
(p.endsWith('.tsx') || p.endsWith('.jsx'))) {
|
||||
// Only boost if PascalCase filename (likely a component, not util)
|
||||
const fileName = p.split('/').pop() || '';
|
||||
if (/^[A-Z]/.test(fileName)) {
|
||||
return { framework: 'react', entryPointMultiplier: 1.5, reason: 'react-component' };
|
||||
}
|
||||
}
|
||||
|
||||
// ========== PYTHON FRAMEWORKS ==========
|
||||
|
||||
// Django views (high confidence)
|
||||
if (p.endsWith('views.py')) {
|
||||
return { framework: 'django', entryPointMultiplier: 3.0, reason: 'django-views' };
|
||||
}
|
||||
|
||||
// Django URL configs
|
||||
if (p.endsWith('urls.py')) {
|
||||
return { framework: 'django', entryPointMultiplier: 2.0, reason: 'django-urls' };
|
||||
}
|
||||
|
||||
// FastAPI / Flask routers
|
||||
if ((p.includes('/routers/') || p.includes('/endpoints/') || p.includes('/routes/')) &&
|
||||
p.endsWith('.py')) {
|
||||
return { framework: 'fastapi', entryPointMultiplier: 2.5, reason: 'api-routers' };
|
||||
}
|
||||
|
||||
// Python API folder
|
||||
if (p.includes('/api/') && p.endsWith('.py') && !p.endsWith('__init__.py')) {
|
||||
return { framework: 'python-api', entryPointMultiplier: 2.0, reason: 'api-folder' };
|
||||
}
|
||||
|
||||
// ========== JAVA FRAMEWORKS ==========
|
||||
|
||||
// Spring Boot controllers
|
||||
if ((p.includes('/controller/') || p.includes('/controllers/')) && p.endsWith('.java')) {
|
||||
return { framework: 'spring', entryPointMultiplier: 3.0, reason: 'spring-controller' };
|
||||
}
|
||||
|
||||
// Spring Boot - files ending in Controller.java
|
||||
if (p.endsWith('controller.java')) {
|
||||
return { framework: 'spring', entryPointMultiplier: 3.0, reason: 'spring-controller-file' };
|
||||
}
|
||||
|
||||
// Java service layer (often entry points for business logic)
|
||||
if ((p.includes('/service/') || p.includes('/services/')) && p.endsWith('.java')) {
|
||||
return { framework: 'java-service', entryPointMultiplier: 1.8, reason: 'java-service' };
|
||||
}
|
||||
|
||||
// ========== C# / .NET FRAMEWORKS ==========
|
||||
|
||||
// ASP.NET Controllers
|
||||
if (p.includes('/controllers/') && p.endsWith('.cs')) {
|
||||
return { framework: 'aspnet', entryPointMultiplier: 3.0, reason: 'aspnet-controller' };
|
||||
}
|
||||
|
||||
// ASP.NET - files ending in Controller.cs
|
||||
if (p.endsWith('controller.cs')) {
|
||||
return { framework: 'aspnet', entryPointMultiplier: 3.0, reason: 'aspnet-controller-file' };
|
||||
}
|
||||
|
||||
// Blazor pages
|
||||
if (p.includes('/pages/') && p.endsWith('.razor')) {
|
||||
return { framework: 'blazor', entryPointMultiplier: 2.5, reason: 'blazor-page' };
|
||||
}
|
||||
|
||||
// ========== GO FRAMEWORKS ==========
|
||||
|
||||
// Go handlers
|
||||
if ((p.includes('/handlers/') || p.includes('/handler/')) && p.endsWith('.go')) {
|
||||
return { framework: 'go-http', entryPointMultiplier: 2.5, reason: 'go-handlers' };
|
||||
}
|
||||
|
||||
// Go routes
|
||||
if (p.includes('/routes/') && p.endsWith('.go')) {
|
||||
return { framework: 'go-http', entryPointMultiplier: 2.5, reason: 'go-routes' };
|
||||
}
|
||||
|
||||
// Go controllers
|
||||
if (p.includes('/controllers/') && p.endsWith('.go')) {
|
||||
return { framework: 'go-mvc', entryPointMultiplier: 2.5, reason: 'go-controller' };
|
||||
}
|
||||
|
||||
// Go main.go files (THE entry point)
|
||||
if (p.endsWith('/main.go') || p.endsWith('/cmd/') && p.endsWith('.go')) {
|
||||
return { framework: 'go', entryPointMultiplier: 3.0, reason: 'go-main' };
|
||||
}
|
||||
|
||||
// ========== RUST FRAMEWORKS ==========
|
||||
|
||||
// Rust handlers/routes
|
||||
if ((p.includes('/handlers/') || p.includes('/routes/')) && p.endsWith('.rs')) {
|
||||
return { framework: 'rust-web', entryPointMultiplier: 2.5, reason: 'rust-handlers' };
|
||||
}
|
||||
|
||||
// Rust main.rs (THE entry point)
|
||||
if (p.endsWith('/main.rs')) {
|
||||
return { framework: 'rust', entryPointMultiplier: 3.0, reason: 'rust-main' };
|
||||
}
|
||||
|
||||
// Rust bin folder (executables)
|
||||
if (p.includes('/bin/') && p.endsWith('.rs')) {
|
||||
return { framework: 'rust', entryPointMultiplier: 2.5, reason: 'rust-bin' };
|
||||
}
|
||||
|
||||
// ========== C / C++ ==========
|
||||
|
||||
// C/C++ main files
|
||||
if (p.endsWith('/main.c') || p.endsWith('/main.cpp') || p.endsWith('/main.cc')) {
|
||||
return { framework: 'c-cpp', entryPointMultiplier: 3.0, reason: 'c-main' };
|
||||
}
|
||||
|
||||
// C/C++ src folder entry points (if named specifically)
|
||||
if ((p.includes('/src/') && (p.endsWith('/app.c') || p.endsWith('/app.cpp')))) {
|
||||
return { framework: 'c-cpp', entryPointMultiplier: 2.5, reason: 'c-app' };
|
||||
}
|
||||
|
||||
// ========== GENERIC PATTERNS ==========
|
||||
|
||||
// Any language: index files in API folders
|
||||
if (p.includes('/api/') && (
|
||||
p.endsWith('/index.ts') || p.endsWith('/index.js') ||
|
||||
p.endsWith('/__init__.py')
|
||||
)) {
|
||||
return { framework: 'api', entryPointMultiplier: 1.8, reason: 'api-index' };
|
||||
}
|
||||
|
||||
// No framework detected - return null for graceful fallback (1.0 multiplier)
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FUTURE: AST-BASED PATTERNS (for Phase 3)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Patterns that indicate entry points within code (for future AST-based detection)
|
||||
* These would require parsing decorators/annotations in the code itself.
|
||||
*/
|
||||
export const FRAMEWORK_AST_PATTERNS = {
|
||||
// JavaScript/TypeScript decorators
|
||||
'nestjs': ['@Controller', '@Get', '@Post', '@Put', '@Delete', '@Patch'],
|
||||
'express': ['app.get', 'app.post', 'app.put', 'app.delete', 'router.get', 'router.post'],
|
||||
|
||||
// Python decorators
|
||||
'fastapi': ['@app.get', '@app.post', '@app.put', '@app.delete', '@router.get'],
|
||||
'flask': ['@app.route', '@blueprint.route'],
|
||||
|
||||
// Java annotations
|
||||
'spring': ['@RestController', '@Controller', '@GetMapping', '@PostMapping', '@RequestMapping'],
|
||||
'jaxrs': ['@Path', '@GET', '@POST', '@PUT', '@DELETE'],
|
||||
|
||||
// C# attributes
|
||||
'aspnet': ['[ApiController]', '[HttpGet]', '[HttpPost]', '[Route]'],
|
||||
|
||||
// Go patterns (function signatures)
|
||||
'go-http': ['http.Handler', 'http.HandlerFunc', 'ServeHTTP'],
|
||||
|
||||
// Rust macros
|
||||
'actix': ['#[get', '#[post', '#[put', '#[delete'],
|
||||
'axum': ['Router::new'],
|
||||
'rocket': ['#[get', '#[post'],
|
||||
};
|
||||
|
|
@ -93,18 +93,13 @@ const resolveImportPath = (
|
|||
);
|
||||
if (matchIdx !== -1) {
|
||||
const match = allFileList[matchIdx];
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`📦 Import resolved: ${importPath} → ${match}`);
|
||||
}
|
||||
resolveCache.set(cacheKey, match);
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV && pathParts.length > 0) {
|
||||
console.log(`⚠️ Import unresolved: ${importPath} (tried suffixes from ${pathParts.join('/')})`);
|
||||
}
|
||||
// Unresolved imports (external packages, SDK imports) are expected - don't log
|
||||
resolveCache.set(cacheKey, null);
|
||||
return null;
|
||||
};
|
||||
|
|
@ -156,12 +151,7 @@ export const processImports = async (
|
|||
query = parser.getLanguage().query(queryStr);
|
||||
matches = query.matches(tree.rootNode);
|
||||
|
||||
if (import.meta.env.DEV && language === 'java') {
|
||||
const importMatches = matches.filter(m => m.captures.some(c => c.name === 'import'));
|
||||
if (importMatches.length > 0) {
|
||||
console.log(`📋 Java file ${file.path}: ${importMatches.length} import matches found`);
|
||||
}
|
||||
}
|
||||
// Removed verbose Java import logging
|
||||
} catch (queryError: any) {
|
||||
// Detailed debug logging for query failures
|
||||
console.group(`🔴 Query Error: ${file.path}`);
|
||||
|
|
@ -194,9 +184,7 @@ export const processImports = async (
|
|||
const rawImportPath = sourceNode.text.replace(/['"]/g, '');
|
||||
totalImportsFound++;
|
||||
|
||||
if (import.meta.env.DEV && language === 'java') {
|
||||
console.log(`🔍 Java import found in ${file.path}: ${rawImportPath}`);
|
||||
}
|
||||
// Removed verbose per-import logging
|
||||
|
||||
// Resolve to actual file in the system
|
||||
const resolvedPath = resolveImportPath(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,108 @@ import { getLanguageFromFilename } from './utils';
|
|||
|
||||
export type FileProgressCallback = (current: number, total: number, filePath: string) => void;
|
||||
|
||||
// ============================================================================
|
||||
// EXPORT DETECTION - Language-specific visibility detection
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Check if a symbol (function, class, etc.) is exported/public
|
||||
* Handles all 9 supported languages with explicit logic
|
||||
*
|
||||
* @param node - The AST node for the symbol name
|
||||
* @param name - The symbol name
|
||||
* @param language - The programming language
|
||||
* @returns true if the symbol is exported/public
|
||||
*/
|
||||
const isNodeExported = (node: any, name: string, language: string): boolean => {
|
||||
let current = node;
|
||||
|
||||
switch (language) {
|
||||
// JavaScript/TypeScript: Check for export keyword in ancestors
|
||||
case 'javascript':
|
||||
case 'typescript':
|
||||
while (current) {
|
||||
const type = current.type;
|
||||
if (type === 'export_statement' ||
|
||||
type === 'export_specifier' ||
|
||||
type === 'lexical_declaration' && current.parent?.type === 'export_statement') {
|
||||
return true;
|
||||
}
|
||||
// Also check if text starts with 'export '
|
||||
if (current.text?.startsWith('export ')) {
|
||||
return true;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
|
||||
// Python: Public if no leading underscore (convention)
|
||||
case 'python':
|
||||
return !name.startsWith('_');
|
||||
|
||||
// Java: Check for 'public' modifier
|
||||
// In tree-sitter Java, modifiers are siblings of the name node, not parents
|
||||
case 'java':
|
||||
while (current) {
|
||||
// Check if this node or any sibling is a 'modifiers' node containing 'public'
|
||||
if (current.parent) {
|
||||
const parent = current.parent;
|
||||
// Check all children of the parent for modifiers
|
||||
for (let i = 0; i < parent.childCount; i++) {
|
||||
const child = parent.child(i);
|
||||
if (child?.type === 'modifiers' && child.text?.includes('public')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Also check if the parent's text starts with 'public' (fallback)
|
||||
if (parent.type === 'method_declaration' || parent.type === 'constructor_declaration') {
|
||||
if (parent.text?.trimStart().startsWith('public')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
|
||||
// C#: Check for 'public' modifier in ancestors
|
||||
case 'csharp':
|
||||
while (current) {
|
||||
if (current.type === 'modifier' || current.type === 'modifiers') {
|
||||
if (current.text?.includes('public')) return true;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
|
||||
// Go: Uppercase first letter = exported
|
||||
case 'go':
|
||||
if (name.length === 0) return false;
|
||||
const first = name[0];
|
||||
// Must be uppercase letter (not a number or symbol)
|
||||
return first === first.toUpperCase() && first !== first.toLowerCase();
|
||||
|
||||
// Rust: Check for 'pub' visibility modifier
|
||||
case 'rust':
|
||||
while (current) {
|
||||
if (current.type === 'visibility_modifier') {
|
||||
if (current.text?.includes('pub')) return true;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
|
||||
// C/C++: No native export concept at language level
|
||||
// Entry points will be detected via name patterns (main, etc.)
|
||||
case 'c':
|
||||
case 'cpp':
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const processParsing = async (
|
||||
graph: KnowledgeGraph,
|
||||
files: { path: string; content: string }[],
|
||||
|
|
@ -123,7 +225,8 @@ export const processParsing = async (
|
|||
filePath: file.path,
|
||||
startLine: nameNode.startPosition.row,
|
||||
endLine: nameNode.endPosition.row,
|
||||
language: language
|
||||
language: language,
|
||||
isExported: isNodeExported(nameNode, nodeName, language),
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
import { KnowledgeGraph, GraphNode, GraphRelationship, NodeLabel } from '../graph/types';
|
||||
import { CommunityMembership } from './community-processor';
|
||||
import { calculateEntryPointScore, isTestFile } from './entry-point-scoring';
|
||||
|
||||
// ============================================================================
|
||||
// CONFIGURATION
|
||||
|
|
@ -236,11 +237,12 @@ const buildReverseCallsGraph = (graph: KnowledgeGraph): AdjacencyList => {
|
|||
/**
|
||||
* Find functions/methods that are good entry points for tracing.
|
||||
*
|
||||
* An entry point is a function that:
|
||||
* 1. Has outgoing CALLS (so we can trace forward)
|
||||
* 2. Ranked by having high outgoing/incoming call ratio
|
||||
* Entry points are scored based on:
|
||||
* 1. Call ratio (calls many, called by few)
|
||||
* 2. Export status (exported/public functions rank higher)
|
||||
* 3. Name patterns (handle*, on*, *Controller, etc.)
|
||||
*
|
||||
* We prioritize functions that call many others but are called by few.
|
||||
* Test files are excluded entirely.
|
||||
*/
|
||||
const findEntryPoints = (
|
||||
graph: KnowledgeGraph,
|
||||
|
|
@ -248,39 +250,53 @@ const findEntryPoints = (
|
|||
callsEdges: AdjacencyList
|
||||
): string[] => {
|
||||
const symbolTypes = new Set<NodeLabel>(['Function', 'Method']);
|
||||
const entryPointCandidates: { id: string; score: number; callers: number; callees: number }[] = [];
|
||||
const entryPointCandidates: {
|
||||
id: string;
|
||||
score: number;
|
||||
reasons: string[];
|
||||
}[] = [];
|
||||
|
||||
graph.nodes.forEach(node => {
|
||||
if (symbolTypes.has(node.label)) {
|
||||
const callers = reverseCallsEdges.get(node.id) || [];
|
||||
const callees = callsEdges.get(node.id) || [];
|
||||
|
||||
// Must have at least 1 outgoing call to trace forward
|
||||
if (callees.length > 0) {
|
||||
// Score: ratio of outgoing to incoming calls
|
||||
// Higher ratio = better entry point (calls many, called by few)
|
||||
// Add 1 to denominators to avoid division by zero
|
||||
const score = callees.length / (callers.length + 1);
|
||||
|
||||
entryPointCandidates.push({
|
||||
id: node.id,
|
||||
score,
|
||||
callers: callers.length,
|
||||
callees: callees.length
|
||||
});
|
||||
}
|
||||
if (!symbolTypes.has(node.label)) return;
|
||||
|
||||
const filePath = node.properties.filePath || '';
|
||||
|
||||
// Skip test files entirely
|
||||
if (isTestFile(filePath)) return;
|
||||
|
||||
const callers = reverseCallsEdges.get(node.id) || [];
|
||||
const callees = callsEdges.get(node.id) || [];
|
||||
|
||||
// Must have at least 1 outgoing call to trace forward
|
||||
if (callees.length === 0) return;
|
||||
|
||||
// Calculate entry point score using new scoring system
|
||||
const { score, reasons } = calculateEntryPointScore(
|
||||
node.properties.name,
|
||||
node.properties.language || 'javascript',
|
||||
node.properties.isExported ?? false,
|
||||
callers.length,
|
||||
callees.length,
|
||||
filePath // Pass filePath for framework detection
|
||||
);
|
||||
|
||||
if (score > 0) {
|
||||
entryPointCandidates.push({ id: node.id, score, reasons });
|
||||
}
|
||||
});
|
||||
|
||||
// Sort by score descending and return top candidates
|
||||
const sorted = entryPointCandidates.sort((a, b) => b.score - a.score);
|
||||
|
||||
// DEBUG: Log top candidates
|
||||
if (sorted.length > 0) {
|
||||
console.log(`[Process Debug] Top 5 entry point candidates:`);
|
||||
sorted.slice(0, 5).forEach((c, i) => {
|
||||
// DEBUG: Log top candidates with new scoring details
|
||||
if (sorted.length > 0 && typeof import.meta !== 'undefined' && import.meta.env?.DEV) {
|
||||
console.log(`[Process] Top 10 entry point candidates (new scoring):`);
|
||||
sorted.slice(0, 10).forEach((c, i) => {
|
||||
const node = graph.nodes.find(n => n.id === c.id);
|
||||
console.log(` ${i+1}. ${node?.properties.name} - calls: ${c.callees}, callers: ${c.callers}, score: ${c.score.toFixed(2)}`);
|
||||
const exported = node?.properties.isExported ? '✓' : '✗';
|
||||
const shortPath = node?.properties.filePath?.split('/').slice(-2).join('/') || '';
|
||||
console.log(` ${i+1}. ${node?.properties.name} [exported:${exported}] (${shortPath})`);
|
||||
console.log(` score: ${c.score.toFixed(2)} = [${c.reasons.join(' × ')}]`);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,17 @@ You are an investigator. For each question:
|
|||
Nodes: File, Folder, Function, Class, Interface, Method, Community, Process
|
||||
Relations: \`CodeRelation\` with \`type\` property: CONTAINS, DEFINES, IMPORTS, CALLS, EXTENDS, IMPLEMENTS, MEMBER_OF, STEP_IN_PROCESS
|
||||
|
||||
## 📐 GRAPH SEMANTICS (Important!)
|
||||
**Edge Types:**
|
||||
- \`CALLS\`: Method invocation OR constructor injection. If A receives B as parameter and uses it, A→B is CALLS. This is intentional simplification.
|
||||
- \`IMPORTS\`: File-level import/include statement.
|
||||
- \`EXTENDS/IMPLEMENTS\`: Class inheritance.
|
||||
|
||||
**Process Nodes:**
|
||||
- Process labels use format: "EntryPoint → Terminal" (e.g., "onCreate → showToast")
|
||||
- These are heuristic names from tracing execution flow, NOT application-defined names
|
||||
- Entry points are detected via export status, naming patterns, and framework conventions
|
||||
|
||||
Cypher examples:
|
||||
- \`MATCH (f:Function) RETURN f.name LIMIT 10\`
|
||||
- \`MATCH (f:File)-[:CodeRelation {type: 'IMPORTS'}]->(g:File) RETURN f.name, g.name\`
|
||||
|
|
|
|||
|
|
@ -908,12 +908,23 @@ MATCH (n:Function {id: emb.nodeId}) RETURN n`,
|
|||
: 'Dependencies this RELIES ON';
|
||||
|
||||
// Try to find the target node first
|
||||
const findTargetQuery = `
|
||||
MATCH (n)
|
||||
WHERE n.name = '${target.replace(/'/g, "''")}'
|
||||
RETURN n.id AS id, label(n) AS nodeType, n.filePath AS filePath
|
||||
LIMIT 5
|
||||
`;
|
||||
// If target contains '/', search by filePath; otherwise by name
|
||||
const isPathQuery = target.includes('/');
|
||||
const escapedTarget = target.replace(/'/g, "''");
|
||||
|
||||
const findTargetQuery = isPathQuery
|
||||
? `
|
||||
MATCH (n)
|
||||
WHERE n.filePath IS NOT NULL AND n.filePath CONTAINS '${escapedTarget}'
|
||||
RETURN n.id AS id, label(n) AS nodeType, n.filePath AS filePath
|
||||
LIMIT 10
|
||||
`
|
||||
: `
|
||||
MATCH (n)
|
||||
WHERE n.name = '${escapedTarget}'
|
||||
RETURN n.id AS id, label(n) AS nodeType, n.filePath AS filePath
|
||||
LIMIT 10
|
||||
`;
|
||||
|
||||
let targetResults;
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue