enhanced process maps

This commit is contained in:
abhigyanpatwari 2026-01-28 01:13:10 +05:30
parent 32c3544d0d
commit d3dbdb1aff
12 changed files with 855 additions and 75 deletions

View file

@ -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"
}
}
}

View file

@ -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');

View file

@ -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)' },
},

View 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).

View file

@ -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 =

View 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')
);
}

View 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'],
};

View file

@ -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(

View file

@ -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),
}
};

View file

@ -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(' × ')}]`);
});
}

View file

@ -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\`

View file

@ -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 {