Skip to content

Commit 3da89f9

Browse files
committed
feat: add react and nextjs analyzers
Upstream the React and Next.js analyzer work from issue #2 and the aolin480 fork through the existing registry surfaces with focused tests and docs updates.
1 parent 41c252a commit 3da89f9

File tree

18 files changed

+1539
-26
lines changed

18 files changed

+1539
-26
lines changed

CONTRIBUTING.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ See [README.md](./README.md) for configuration with Claude Desktop, VS Code, Cur
2222
src/
2323
analyzers/
2424
angular/ # Angular-specific analysis
25+
nextjs/ # Next.js routes, metadata, and client/server detection
26+
react/ # React components, hooks, and context patterns
2527
generic/ # Fallback for non-Angular files
2628
core/
2729
indexer.ts # Scans files, creates chunks
@@ -34,8 +36,6 @@ src/
3436

3537
## What Would Help
3638

37-
**React analyzer** - Biggest gap right now. Look at `src/analyzers/angular/index.ts` for the pattern. Needs to detect components, hooks, context usage, etc.
38-
3939
**Vue analyzer** - Same deal. Detect components, composables, Pinia stores.
4040

4141
**Better search ranking** - The hybrid search in `src/core/search.ts` could use tuning. Currently uses RRF to combine semantic and keyword scores.
@@ -44,9 +44,9 @@ src/
4444

4545
## Adding a Framework Analyzer
4646

47-
1. Create `src/analyzers/react/index.ts`
47+
1. Create `src/analyzers/<framework>/index.ts`
4848
2. Implement `FrameworkAnalyzer` interface
49-
3. Register in `src/index.ts`
49+
3. Register in `src/index.ts`, `src/cli.ts`, and `src/lib.ts`
5050

5151
The interface is straightforward:
5252

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ If you get `selection_required`, retry with one of the paths from `availableProj
145145

146146
## Language Support
147147

148-
10 languages with full symbol extraction via Tree-sitter: TypeScript, JavaScript, Python, Java, Kotlin, C, C++, C#, Go, Rust. 30+ languages with indexing and retrieval coverage, including PHP, Ruby, Swift, Scala, Shell, and config formats. Angular has a dedicated analyzer; everything else uses the Generic analyzer with AST-aligned chunking when a grammar is available.
148+
10 languages with full symbol extraction via Tree-sitter: TypeScript, JavaScript, Python, Java, Kotlin, C, C++, C#, Go, Rust. 30+ languages with indexing and retrieval coverage, including PHP, Ruby, Swift, Scala, Shell, and config formats. Angular, React, and Next.js have dedicated analyzers; everything else uses the Generic analyzer with AST-aligned chunking when a grammar is available.
149149

150150
## Configuration
151151

@@ -156,6 +156,9 @@ If you get `selection_required`, retry with one of the paths from `availableProj
156156
| `CODEBASE_ROOT` || Bootstrap root for CLI and single-project MCP clients |
157157
| `CODEBASE_CONTEXT_DEBUG` || Set to `1` for verbose logging |
158158
| `EMBEDDING_MODEL` | `Xenova/bge-small-en-v1.5` | Local embedding model override |
159+
| `CODEBASE_CONTEXT_HTTP` || Set to `1` to start in HTTP mode (same as `--http` flag) |
160+
| `CODEBASE_CONTEXT_PORT` | `3100` | HTTP server port override (same as `--port`; ignored in stdio mode) |
161+
| `CODEBASE_CONTEXT_CONFIG_PATH` | `~/.codebase-context/config.json` | Override the server config file path |
159162

160163
## Performance
161164

docs/capabilities.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ Impact is 2-hop transitive: direct importers (hop 1) and their importers (hop 2)
269269
## Analyzers
270270

271271
- **Angular**: signals, standalone components, control flow syntax, lifecycle hooks, DI patterns, component metadata
272+
- **React**: function/class components, custom hooks, context usage, memoization, Suspense, ecosystem signal extraction
273+
- **Next.js**: App Router and Pages Router detection, route/API classification, route paths, `"use client"`, metadata exports
272274
- **Generic**: 30+ have indexing/retrieval coverage including PHP, Ruby, Swift, Scala, Shell, config/markup., 10 languages have full symbol extraction (Tree-sitter: TypeScript, JavaScript, Python, Java, Kotlin, C, C++, C#, Go, Rust).
273275

274276
Notes:
@@ -291,6 +293,6 @@ Reproducible evaluation is shipped as a CLI entrypoint backed by shared scoring/
291293

292294
- **Symbol refs are not a call-graph.** `get_symbol_references` counts identifier-node occurrences in the AST (comments/strings excluded via Tree-sitter). It does not distinguish call sites from type annotations, variable assignments, or imports. Full call-site-specific analysis (`call_expression` nodes only) is a roadmap item.
293295
- **Impact is 2-hop max.** `computeImpactCandidates` walks direct importers then their importers. Full BFS reachability is on the roadmap.
294-
- **Angular is the only framework with a rich dedicated analyzer.** All other languages go through the Generic analyzer (30+ languages, chunking + import graph, no framework-specific signal extraction).
296+
- **Angular, React, and Next.js have dedicated analyzers.** All other languages go through the Generic analyzer (30+ languages, chunking + import graph, no framework-specific signal extraction).
295297
- **Default embedding model is `bge-small-en-v1.5` (512-token context).** Granite (8192 context) is opt-in via `EMBEDDING_MODEL`. OpenAI is opt-in via `EMBEDDING_PROVIDER=openai` — sends code externally.
296298
- **Patterns are file-level frequency counts.** Not semantic clustering. Rising/Declining trend is derived from git commit recency for files using each pattern, not from usage semantics.

package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@
1818
"import": "./dist/analyzers/angular/index.js",
1919
"types": "./dist/analyzers/angular/index.d.ts"
2020
},
21+
"./analyzers/react": {
22+
"import": "./dist/analyzers/react/index.js",
23+
"types": "./dist/analyzers/react/index.d.ts"
24+
},
25+
"./analyzers/nextjs": {
26+
"import": "./dist/analyzers/nextjs/index.js",
27+
"types": "./dist/analyzers/nextjs/index.d.ts"
28+
},
2129
"./analyzers/generic": {
2230
"import": "./dist/analyzers/generic/index.js",
2331
"types": "./dist/analyzers/generic/index.d.ts"

src/analyzers/angular/index.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
KEYWORD_INDEX_FILENAME
2626
} from '../../constants/codebase-context.js';
2727
import { registerComplementaryPatterns } from '../../patterns/semantics.js';
28+
import { isFileNotFoundError } from '../shared/metadata.js';
2829

2930
interface AngularInput {
3031
name: string;
@@ -911,7 +912,7 @@ export class AngularAnalyzer implements FrameworkAnalyzer {
911912
...packageJson.devDependencies
912913
};
913914

914-
const angularVersion = allDeps['@angular/core']?.replace(/[\^~]/, '') || 'unknown';
915+
const angularCoreVersion = allDeps['@angular/core'];
915916

916917
// Detect state management
917918
const stateManagement: string[] = [];
@@ -931,15 +932,17 @@ export class AngularAnalyzer implements FrameworkAnalyzer {
931932
if (allDeps['karma']) testingFrameworks.push('Karma');
932933
if (allDeps['jest']) testingFrameworks.push('Jest');
933934

934-
metadata.framework = {
935-
name: 'Angular',
936-
version: angularVersion,
937-
type: 'angular',
938-
variant: 'unknown', // Will be determined during analysis
939-
stateManagement,
940-
uiLibraries,
941-
testingFrameworks
942-
};
935+
if (angularCoreVersion) {
936+
metadata.framework = {
937+
name: 'Angular',
938+
version: angularCoreVersion.replace(/[\^~]/, ''),
939+
type: 'angular',
940+
variant: 'unknown', // Will be determined during analysis
941+
stateManagement,
942+
uiLibraries,
943+
testingFrameworks
944+
};
945+
}
943946

944947
// Convert dependencies
945948
metadata.dependencies = Object.entries(allDeps).map(([name, version]) => ({
@@ -948,7 +951,9 @@ export class AngularAnalyzer implements FrameworkAnalyzer {
948951
category: this.categorizeDependency(name)
949952
}));
950953
} catch (error) {
951-
console.warn('Failed to read Angular project metadata:', error);
954+
if (!isFileNotFoundError(error)) {
955+
console.warn('Failed to read Angular project metadata:', error);
956+
}
952957
}
953958

954959
// Calculate statistics from existing index if available
@@ -966,8 +971,6 @@ export class AngularAnalyzer implements FrameworkAnalyzer {
966971
const chunks =
967972
parsedObj && Array.isArray(parsedObj.chunks) ? (parsedObj.chunks as IndexChunk[]) : null;
968973
if (Array.isArray(chunks) && chunks.length > 0) {
969-
console.error(`Loading statistics from ${indexPath}: ${chunks.length} chunks`);
970-
971974
metadata.statistics.totalFiles = new Set(chunks.map((c) => c.filePath)).size;
972975
metadata.statistics.totalLines = chunks.reduce(
973976
(sum, c) => sum + ((c.endLine ?? 0) - (c.startLine ?? 0) + 1),
@@ -1005,8 +1008,9 @@ export class AngularAnalyzer implements FrameworkAnalyzer {
10051008
metadata.architecture.layers = layerCounts;
10061009
}
10071010
} catch (error) {
1008-
// Index doesn't exist yet, keep statistics at 0
1009-
console.warn('Failed to calculate statistics from index:', error);
1011+
if (!isFileNotFoundError(error)) {
1012+
console.warn('Failed to calculate statistics from index:', error);
1013+
}
10101014
}
10111015

10121016
return metadata;

0 commit comments

Comments
 (0)