@optave/codegraph 2.2.3-dev.44e8146 → 2.3.1-dev.1aeea34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -15
- package/package.json +7 -6
- package/src/builder.js +66 -0
- package/src/cli.js +116 -13
- package/src/cochange.js +498 -0
- package/src/config.js +6 -0
- package/src/db.js +40 -0
- package/src/embedder.js +61 -2
- package/src/export.js +158 -13
- package/src/extractors/helpers.js +2 -1
- package/src/extractors/javascript.js +294 -78
- package/src/index.js +13 -0
- package/src/mcp.js +62 -1
- package/src/parser.js +39 -2
- package/src/queries.js +158 -9
- package/src/registry.js +9 -1
- package/src/structure.js +94 -0
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ cd your-project
|
|
|
55
55
|
codegraph build
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
That's it. No config files, no Docker, no JVM, no API keys, no accounts. The graph is ready to query. Add `codegraph mcp` to your AI agent's config and it has full access to your dependency graph through
|
|
58
|
+
That's it. No config files, no Docker, no JVM, no API keys, no accounts. The graph is ready to query. Add `codegraph mcp` to your AI agent's config and it has full access to your dependency graph through 19 MCP tools.
|
|
59
59
|
|
|
60
60
|
### Why it matters
|
|
61
61
|
|
|
@@ -78,7 +78,9 @@ That's it. No config files, no Docker, no JVM, no API keys, no accounts. The gra
|
|
|
78
78
|
| Semantic search | **Yes** | — | **Yes** | **Yes** | — | **Yes** | — | — |
|
|
79
79
|
| MCP / AI agent support | **Yes** | — | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | — |
|
|
80
80
|
| Git diff impact | **Yes** | — | — | — | — | **Yes** | — | **Yes** |
|
|
81
|
+
| Git co-change analysis | **Yes** | — | — | — | — | — | **Yes** | **Yes** |
|
|
81
82
|
| Watch mode | **Yes** | — | **Yes** | — | — | — | — | — |
|
|
83
|
+
| Dead code / role classification | **Yes** | — | **Yes** | — | — | — | — | **Yes** |
|
|
82
84
|
| Cycle detection | **Yes** | — | **Yes** | — | — | — | — | **Yes** |
|
|
83
85
|
| Incremental rebuilds | **O(changed)** | — | O(n) Merkle | — | — | — | — | — |
|
|
84
86
|
| Zero config | **Yes** | — | **Yes** | — | — | — | — | — |
|
|
@@ -94,9 +96,10 @@ That's it. No config files, no Docker, no JVM, no API keys, no accounts. The gra
|
|
|
94
96
|
| **⚡** | **Always-fresh graph** | Three-tier change detection: journal (O(changed)) → mtime+size (O(n) stats) → hash (O(changed) reads). Sub-second rebuilds even on large codebases |
|
|
95
97
|
| **🔓** | **Zero-cost core, LLM-enhanced when you want** | Full graph analysis with no API keys, no accounts, no cost. Optionally bring your own LLM provider — your code only goes where you choose |
|
|
96
98
|
| **🔬** | **Function-level, not just files** | Traces `handleAuth()` → `validateToken()` → `decryptJWT()` and shows 14 callers across 9 files break if `decryptJWT` changes |
|
|
97
|
-
|
|
|
99
|
+
| **🏷️** | **Role classification** | Every symbol auto-tagged as `entry`/`core`/`utility`/`adapter`/`dead`/`leaf` — agents instantly know what they're looking at |
|
|
100
|
+
| **🤖** | **Built for AI agents** | 19-tool [MCP server](https://modelcontextprotocol.io/) — AI assistants query your graph directly. Single-repo by default |
|
|
98
101
|
| **🌐** | **Multi-language, one CLI** | JS/TS + Python + Go + Rust + Java + C# + PHP + Ruby + HCL in a single graph |
|
|
99
|
-
| **💥** | **Git diff impact** | `codegraph diff-impact` shows changed functions, their callers, and full blast radius —
|
|
102
|
+
| **💥** | **Git diff impact** | `codegraph diff-impact` shows changed functions, their callers, and full blast radius — enriched with historically coupled files from git co-change analysis. Ships with a GitHub Actions workflow |
|
|
100
103
|
| **🧠** | **Semantic search** | Local embeddings by default, LLM-powered when opted in — multi-query with RRF ranking via `"auth; token; JWT"` |
|
|
101
104
|
|
|
102
105
|
---
|
|
@@ -141,10 +144,10 @@ After modifying code:
|
|
|
141
144
|
Or connect directly via MCP:
|
|
142
145
|
|
|
143
146
|
```bash
|
|
144
|
-
codegraph mcp #
|
|
147
|
+
codegraph mcp # 19-tool MCP server — AI queries the graph directly
|
|
145
148
|
```
|
|
146
149
|
|
|
147
|
-
Full agent setup: [AI Agent Guide](docs/ai-agent-guide.md) · [CLAUDE.md template](docs/ai-agent-guide.md#claudemd-template)
|
|
150
|
+
Full agent setup: [AI Agent Guide](docs/guides/ai-agent-guide.md) · [CLAUDE.md template](docs/guides/ai-agent-guide.md#claudemd-template)
|
|
148
151
|
|
|
149
152
|
---
|
|
150
153
|
|
|
@@ -159,13 +162,15 @@ Full agent setup: [AI Agent Guide](docs/ai-agent-guide.md) · [CLAUDE.md t
|
|
|
159
162
|
| 🎯 | **Deep context** | `context` gives AI agents source, deps, callers, signature, and tests for a function in one call; `explain` gives structural summaries of files or functions |
|
|
160
163
|
| 📍 | **Fast lookup** | `where` shows exactly where a symbol is defined and used — minimal, fast |
|
|
161
164
|
| 📊 | **Diff impact** | Parse `git diff`, find overlapping functions, trace their callers |
|
|
165
|
+
| 🔗 | **Co-change analysis** | Analyze git history for files that always change together — surfaces hidden coupling the static graph can't see; enriches `diff-impact` with historically coupled files |
|
|
162
166
|
| 🗺️ | **Module map** | Bird's-eye view of your most-connected files |
|
|
163
167
|
| 🏗️ | **Structure & hotspots** | Directory cohesion scores, fan-in/fan-out hotspot detection, module boundaries |
|
|
168
|
+
| 🏷️ | **Node role classification** | Every symbol auto-tagged as `entry`/`core`/`utility`/`adapter`/`dead`/`leaf` based on connectivity patterns — agents instantly know architectural role |
|
|
164
169
|
| 🔄 | **Cycle detection** | Find circular dependencies at file or function level |
|
|
165
170
|
| 📤 | **Export** | DOT (Graphviz), Mermaid, and JSON graph export |
|
|
166
171
|
| 🧠 | **Semantic search** | Embeddings-powered natural language search with multi-query RRF ranking |
|
|
167
172
|
| 👀 | **Watch mode** | Incrementally update the graph as files change |
|
|
168
|
-
| 🤖 | **MCP server** |
|
|
173
|
+
| 🤖 | **MCP server** | 19-tool MCP server for AI assistants; single-repo by default, opt-in multi-repo |
|
|
169
174
|
| ⚡ | **Always fresh** | Three-tier incremental detection — sub-second rebuilds even on large codebases |
|
|
170
175
|
|
|
171
176
|
## 📦 Commands
|
|
@@ -189,6 +194,9 @@ codegraph map -n 50 --no-tests # Top 50, excluding test files
|
|
|
189
194
|
codegraph where <name> # Where is a symbol defined and used?
|
|
190
195
|
codegraph where --file src/db.js # List symbols, imports, exports for a file
|
|
191
196
|
codegraph stats # Graph health: nodes, edges, languages, quality score
|
|
197
|
+
codegraph roles # Node role classification (entry, core, utility, adapter, dead, leaf)
|
|
198
|
+
codegraph roles --role dead -T # Find dead code (unreferenced, non-exported symbols)
|
|
199
|
+
codegraph roles --role core --file src/ # Core symbols in src/
|
|
192
200
|
```
|
|
193
201
|
|
|
194
202
|
### Deep Context (AI-Optimized)
|
|
@@ -213,6 +221,22 @@ codegraph diff-impact HEAD~3 # Impact vs a specific ref
|
|
|
213
221
|
codegraph diff-impact main --format mermaid -T # Mermaid flowchart of blast radius
|
|
214
222
|
```
|
|
215
223
|
|
|
224
|
+
### Co-Change Analysis
|
|
225
|
+
|
|
226
|
+
Analyze git history to find files that always change together — surfaces hidden coupling the static graph can't see. Requires a git repository.
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
codegraph co-change --analyze # Scan git history and populate co-change data
|
|
230
|
+
codegraph co-change src/queries.js # Show co-change partners for a file
|
|
231
|
+
codegraph co-change # Show top co-changing file pairs globally
|
|
232
|
+
codegraph co-change --since 6m # Limit to last 6 months of history
|
|
233
|
+
codegraph co-change --min-jaccard 0.5 # Only show strong coupling (Jaccard >= 0.5)
|
|
234
|
+
codegraph co-change --min-support 5 # Minimum co-commit count
|
|
235
|
+
codegraph co-change --full # Include all details
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Co-change data also enriches `diff-impact` — historically coupled files appear in a `historicallyCoupled` section alongside the static dependency analysis.
|
|
239
|
+
|
|
216
240
|
### Structure & Hotspots
|
|
217
241
|
|
|
218
242
|
```bash
|
|
@@ -373,22 +397,36 @@ Codegraph also extracts symbols from common callback patterns: Commander `.comma
|
|
|
373
397
|
|
|
374
398
|
## 📊 Performance
|
|
375
399
|
|
|
376
|
-
Self-measured on every release via CI ([
|
|
400
|
+
Self-measured on every release via CI ([build benchmarks](generated/BUILD-BENCHMARKS.md) | [embedding benchmarks](generated/EMBEDDING-BENCHMARKS.md)):
|
|
377
401
|
|
|
378
402
|
| Metric | Latest |
|
|
379
403
|
|---|---|
|
|
380
404
|
| Build speed (native) | **1.9 ms/file** |
|
|
381
405
|
| Build speed (WASM) | **6.6 ms/file** |
|
|
382
|
-
| Query time | **
|
|
406
|
+
| Query time | **2ms** |
|
|
383
407
|
| ~50,000 files (est.) | **~95.0s build** |
|
|
384
408
|
|
|
385
409
|
Metrics are normalized per file for cross-version comparability. Times above are for a full initial build — incremental rebuilds only re-parse changed files.
|
|
386
410
|
|
|
411
|
+
### Lightweight Footprint
|
|
412
|
+
|
|
413
|
+
<a href="https://www.npmjs.com/package/@optave/codegraph"><img src="https://img.shields.io/npm/unpacked-size/@optave/codegraph?style=flat-square&label=unpacked%20size" alt="npm unpacked size" /></a>
|
|
414
|
+
|
|
415
|
+
Only **3 runtime dependencies** — everything else is optional or a devDependency:
|
|
416
|
+
|
|
417
|
+
| Dependency | What it does | | |
|
|
418
|
+
|---|---|---|---|
|
|
419
|
+
| [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) | Fast, synchronous SQLite driver |  |  |
|
|
420
|
+
| [commander](https://github.com/tj/commander.js) | CLI argument parsing |  |  |
|
|
421
|
+
| [web-tree-sitter](https://github.com/tree-sitter/tree-sitter) | WASM tree-sitter bindings |  |  |
|
|
422
|
+
|
|
423
|
+
Optional: `@huggingface/transformers` (semantic search), `@modelcontextprotocol/sdk` (MCP server) — lazy-loaded only when needed.
|
|
424
|
+
|
|
387
425
|
## 🤖 AI Agent Integration
|
|
388
426
|
|
|
389
427
|
### MCP Server
|
|
390
428
|
|
|
391
|
-
Codegraph includes a built-in [Model Context Protocol](https://modelcontextprotocol.io/) server with
|
|
429
|
+
Codegraph includes a built-in [Model Context Protocol](https://modelcontextprotocol.io/) server with 19 tools, so AI assistants can query your dependency graph directly:
|
|
392
430
|
|
|
393
431
|
```bash
|
|
394
432
|
codegraph mcp # Single-repo mode (default) — only local project
|
|
@@ -402,7 +440,7 @@ codegraph mcp --repos a,b # Restrict to specific repos (implies --multi-rep
|
|
|
402
440
|
|
|
403
441
|
### CLAUDE.md / Agent Instructions
|
|
404
442
|
|
|
405
|
-
Add this to your project's `CLAUDE.md` to help AI agents use codegraph (full template in the [AI Agent Guide](docs/ai-agent-guide.md#claudemd-template)):
|
|
443
|
+
Add this to your project's `CLAUDE.md` to help AI agents use codegraph (full template in the [AI Agent Guide](docs/guides/ai-agent-guide.md#claudemd-template)):
|
|
406
444
|
|
|
407
445
|
```markdown
|
|
408
446
|
## Code Navigation
|
|
@@ -460,7 +498,7 @@ Use `--kind function` to cut noise. Use `--file <pattern>` to scope.
|
|
|
460
498
|
|
|
461
499
|
## 📋 Recommended Practices
|
|
462
500
|
|
|
463
|
-
See **[docs/recommended-practices.md](docs/recommended-practices.md)** for integration guides:
|
|
501
|
+
See **[docs/guides/recommended-practices.md](docs/guides/recommended-practices.md)** for integration guides:
|
|
464
502
|
|
|
465
503
|
- **Git hooks** — auto-rebuild on commit, impact checks on push, commit message enrichment
|
|
466
504
|
- **CI/CD** — PR impact comments, threshold gates, graph caching
|
|
@@ -468,7 +506,7 @@ See **[docs/recommended-practices.md](docs/recommended-practices.md)** for integ
|
|
|
468
506
|
- **Developer workflow** — watch mode, explore-before-you-edit, semantic search
|
|
469
507
|
- **Secure credentials** — `apiKeyCommand` with 1Password, Bitwarden, Vault, macOS Keychain, `pass`
|
|
470
508
|
|
|
471
|
-
For AI-specific integration, see the **[AI Agent Guide](docs/ai-agent-guide.md)** — a comprehensive reference covering the 6-step agent workflow, complete command-to-MCP mapping, Claude Code hooks, and token-saving patterns.
|
|
509
|
+
For AI-specific integration, see the **[AI Agent Guide](docs/guides/ai-agent-guide.md)** — a comprehensive reference covering the 6-step agent workflow, complete command-to-MCP mapping, Claude Code hooks, and token-saving patterns.
|
|
472
510
|
|
|
473
511
|
## 🔁 CI / GitHub Actions
|
|
474
512
|
|
|
@@ -575,6 +613,8 @@ const { results: fused } = await multiSearchData(
|
|
|
575
613
|
| Incremental rebuilds | **O(changed)** | — | O(n) Merkle | — | — | — |
|
|
576
614
|
| MCP / AI agent support | **Yes** | — | **Yes** | **Yes** | **Yes** | **Yes** |
|
|
577
615
|
| Git diff impact | **Yes** | — | — | — | — | **Yes** |
|
|
616
|
+
| Git co-change analysis | **Yes** | — | — | — | — | — |
|
|
617
|
+
| Dead code / role classification | **Yes** | — | **Yes** | — | — | — |
|
|
578
618
|
| Semantic search | **Yes** | — | **Yes** | **Yes** | — | **Yes** |
|
|
579
619
|
| Watch mode | **Yes** | — | **Yes** | — | — | — |
|
|
580
620
|
| Zero config, no Docker/JVM | **Yes** | — | **Yes** | — | — | — |
|
|
@@ -583,7 +623,7 @@ const { results: fused } = await multiSearchData(
|
|
|
583
623
|
|
|
584
624
|
## 🗺️ Roadmap
|
|
585
625
|
|
|
586
|
-
See **[ROADMAP.md](ROADMAP.md)** for the full development roadmap and **[STABILITY.md](STABILITY.md)** for the stability policy and versioning guarantees. Current plan:
|
|
626
|
+
See **[ROADMAP.md](docs/roadmap/ROADMAP.md)** for the full development roadmap and **[STABILITY.md](STABILITY.md)** for the stability policy and versioning guarantees. Current plan:
|
|
587
627
|
|
|
588
628
|
1. ~~**Rust Core**~~ — **Complete** (v1.3.0) — native tree-sitter parsing via napi-rs, parallel multi-core parsing, incremental re-parsing, import resolution & cycle detection in Rust
|
|
589
629
|
2. ~~**Foundation Hardening**~~ — **Complete** (v1.4.0) — parser registry, 12-tool MCP server with multi-repo support, test coverage 62%→75%, `apiKeyCommand` secret resolution, global repo registry
|
|
@@ -592,7 +632,7 @@ See **[ROADMAP.md](ROADMAP.md)** for the full development roadmap and **[STABILI
|
|
|
592
632
|
5. **Natural Language Queries** — `codegraph ask` command, conversational sessions
|
|
593
633
|
6. **Expanded Language Support** — 8 new languages (12 → 20)
|
|
594
634
|
7. **GitHub Integration & CI** — reusable GitHub Action, PR review, SARIF output
|
|
595
|
-
8. **Visualization & Advanced** — web UI,
|
|
635
|
+
8. **Visualization & Advanced** — web UI, monorepo support, agentic search
|
|
596
636
|
|
|
597
637
|
## 🤝 Contributing
|
|
598
638
|
|
|
@@ -605,7 +645,7 @@ npm install
|
|
|
605
645
|
npm test
|
|
606
646
|
```
|
|
607
647
|
|
|
608
|
-
Looking to add a new language? Check out **[Adding a New Language](docs/adding-a-language.md)**.
|
|
648
|
+
Looking to add a new language? Check out **[Adding a New Language](docs/guides/adding-a-language.md)**.
|
|
609
649
|
|
|
610
650
|
## 📄 License
|
|
611
651
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optave/codegraph",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1-dev.1aeea34",
|
|
4
4
|
"description": "Local code graph CLI — parse codebases with tree-sitter, build dependency graphs, query them",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
9
|
"import": "./src/index.js"
|
|
10
|
-
}
|
|
10
|
+
},
|
|
11
|
+
"./package.json": "./package.json"
|
|
11
12
|
},
|
|
12
13
|
"bin": {
|
|
13
14
|
"codegraph": "./src/cli.js"
|
|
@@ -61,10 +62,10 @@
|
|
|
61
62
|
"optionalDependencies": {
|
|
62
63
|
"@huggingface/transformers": "^3.8.1",
|
|
63
64
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
64
|
-
"@optave/codegraph-darwin-arm64": "2.
|
|
65
|
-
"@optave/codegraph-darwin-x64": "2.
|
|
66
|
-
"@optave/codegraph-linux-x64-gnu": "2.
|
|
67
|
-
"@optave/codegraph-win32-x64-msvc": "2.
|
|
65
|
+
"@optave/codegraph-darwin-arm64": "2.3.1-dev.1aeea34",
|
|
66
|
+
"@optave/codegraph-darwin-x64": "2.3.1-dev.1aeea34",
|
|
67
|
+
"@optave/codegraph-linux-x64-gnu": "2.3.1-dev.1aeea34",
|
|
68
|
+
"@optave/codegraph-win32-x64-msvc": "2.3.1-dev.1aeea34"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
71
|
"@biomejs/biome": "^2.4.4",
|
package/src/builder.js
CHANGED
|
@@ -827,6 +827,59 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
827
827
|
}
|
|
828
828
|
}
|
|
829
829
|
|
|
830
|
+
// For incremental builds, buildStructure needs ALL files (not just changed ones)
|
|
831
|
+
// because it clears and rebuilds all contains edges and directory metrics.
|
|
832
|
+
// Load unchanged files from the DB so structure data stays complete.
|
|
833
|
+
if (!isFullBuild) {
|
|
834
|
+
const existingFiles = db.prepare("SELECT DISTINCT file FROM nodes WHERE kind = 'file'").all();
|
|
835
|
+
const defsByFile = db.prepare(
|
|
836
|
+
"SELECT name, kind, line FROM nodes WHERE file = ? AND kind != 'file' AND kind != 'directory'",
|
|
837
|
+
);
|
|
838
|
+
// Count imports per file — buildStructure only uses imports.length for metrics
|
|
839
|
+
const importCountByFile = db.prepare(
|
|
840
|
+
`SELECT COUNT(DISTINCT n2.file) AS cnt FROM edges e
|
|
841
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
842
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
843
|
+
WHERE n1.file = ? AND e.kind = 'imports'`,
|
|
844
|
+
);
|
|
845
|
+
const lineCountByFile = db.prepare(
|
|
846
|
+
`SELECT n.name AS file, m.line_count
|
|
847
|
+
FROM node_metrics m JOIN nodes n ON m.node_id = n.id
|
|
848
|
+
WHERE n.kind = 'file'`,
|
|
849
|
+
);
|
|
850
|
+
const cachedLineCounts = new Map();
|
|
851
|
+
for (const row of lineCountByFile.all()) {
|
|
852
|
+
cachedLineCounts.set(row.file, row.line_count);
|
|
853
|
+
}
|
|
854
|
+
let loadedFromDb = 0;
|
|
855
|
+
for (const { file: relPath } of existingFiles) {
|
|
856
|
+
if (!fileSymbols.has(relPath)) {
|
|
857
|
+
const importCount = importCountByFile.get(relPath)?.cnt || 0;
|
|
858
|
+
fileSymbols.set(relPath, {
|
|
859
|
+
definitions: defsByFile.all(relPath),
|
|
860
|
+
imports: new Array(importCount),
|
|
861
|
+
exports: [],
|
|
862
|
+
});
|
|
863
|
+
loadedFromDb++;
|
|
864
|
+
}
|
|
865
|
+
if (!lineCountMap.has(relPath)) {
|
|
866
|
+
const cached = cachedLineCounts.get(relPath);
|
|
867
|
+
if (cached != null) {
|
|
868
|
+
lineCountMap.set(relPath, cached);
|
|
869
|
+
} else {
|
|
870
|
+
const absPath = path.join(rootDir, relPath);
|
|
871
|
+
try {
|
|
872
|
+
const content = fs.readFileSync(absPath, 'utf-8');
|
|
873
|
+
lineCountMap.set(relPath, content.split('\n').length);
|
|
874
|
+
} catch {
|
|
875
|
+
lineCountMap.set(relPath, 0);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
debug(`Structure: ${fileSymbols.size} files (${loadedFromDb} loaded from DB)`);
|
|
881
|
+
}
|
|
882
|
+
|
|
830
883
|
// Build directory structure, containment edges, and metrics
|
|
831
884
|
const relDirs = new Set();
|
|
832
885
|
for (const absDir of discoveredDirs) {
|
|
@@ -839,6 +892,19 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
839
892
|
debug(`Structure analysis failed: ${err.message}`);
|
|
840
893
|
}
|
|
841
894
|
|
|
895
|
+
// Classify node roles (entry, core, utility, adapter, dead, leaf)
|
|
896
|
+
try {
|
|
897
|
+
const { classifyNodeRoles } = await import('./structure.js');
|
|
898
|
+
const roleSummary = classifyNodeRoles(db);
|
|
899
|
+
debug(
|
|
900
|
+
`Roles: ${Object.entries(roleSummary)
|
|
901
|
+
.map(([r, c]) => `${r}=${c}`)
|
|
902
|
+
.join(', ')}`,
|
|
903
|
+
);
|
|
904
|
+
} catch (err) {
|
|
905
|
+
debug(`Role classification failed: ${err.message}`);
|
|
906
|
+
}
|
|
907
|
+
|
|
842
908
|
const nodeCount = db.prepare('SELECT COUNT(*) as c FROM nodes').get().c;
|
|
843
909
|
info(`Graph built: ${nodeCount} nodes, ${edgeCount} edges`);
|
|
844
910
|
info(`Stored in ${dbPath}`);
|
package/src/cli.js
CHANGED
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import Database from 'better-sqlite3';
|
|
6
5
|
import { Command } from 'commander';
|
|
7
6
|
import { buildGraph } from './builder.js';
|
|
8
7
|
import { loadConfig } from './config.js';
|
|
9
8
|
import { findCycles, formatCycles } from './cycles.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { openReadonlyOrFail } from './db.js';
|
|
10
|
+
import {
|
|
11
|
+
buildEmbeddings,
|
|
12
|
+
DEFAULT_MODEL,
|
|
13
|
+
EMBEDDING_STRATEGIES,
|
|
14
|
+
MODELS,
|
|
15
|
+
search,
|
|
16
|
+
} from './embedder.js';
|
|
12
17
|
import { exportDOT, exportJSON, exportMermaid } from './export.js';
|
|
13
18
|
import { setVerbose } from './logger.js';
|
|
14
19
|
import {
|
|
@@ -22,7 +27,9 @@ import {
|
|
|
22
27
|
impactAnalysis,
|
|
23
28
|
moduleMap,
|
|
24
29
|
queryName,
|
|
30
|
+
roles,
|
|
25
31
|
stats,
|
|
32
|
+
VALID_ROLES,
|
|
26
33
|
where,
|
|
27
34
|
} from './queries.js';
|
|
28
35
|
import {
|
|
@@ -273,13 +280,15 @@ program
|
|
|
273
280
|
.option('-T, --no-tests', 'Exclude test/spec files')
|
|
274
281
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
275
282
|
.option('--min-confidence <score>', 'Minimum edge confidence threshold (default: 0.5)', '0.5')
|
|
283
|
+
.option('--direction <dir>', 'Flowchart direction for Mermaid: TB, LR, RL, BT', 'LR')
|
|
276
284
|
.option('-o, --output <file>', 'Write to file instead of stdout')
|
|
277
285
|
.action((opts) => {
|
|
278
|
-
const db =
|
|
286
|
+
const db = openReadonlyOrFail(opts.db);
|
|
279
287
|
const exportOpts = {
|
|
280
288
|
fileLevel: !opts.functions,
|
|
281
289
|
noTests: resolveNoTests(opts),
|
|
282
290
|
minConfidence: parseFloat(opts.minConfidence),
|
|
291
|
+
direction: opts.direction,
|
|
283
292
|
};
|
|
284
293
|
|
|
285
294
|
let output;
|
|
@@ -314,7 +323,7 @@ program
|
|
|
314
323
|
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
315
324
|
.option('-j, --json', 'Output as JSON')
|
|
316
325
|
.action((opts) => {
|
|
317
|
-
const db =
|
|
326
|
+
const db = openReadonlyOrFail(opts.db);
|
|
318
327
|
const cycles = findCycles(db, { fileLevel: !opts.functions, noTests: resolveNoTests(opts) });
|
|
319
328
|
db.close();
|
|
320
329
|
|
|
@@ -396,8 +405,15 @@ registry
|
|
|
396
405
|
.command('prune')
|
|
397
406
|
.description('Remove stale registry entries (missing directories or idle beyond TTL)')
|
|
398
407
|
.option('--ttl <days>', 'Days of inactivity before pruning (default: 30)', '30')
|
|
408
|
+
.option('--exclude <names>', 'Comma-separated repo names to preserve from pruning')
|
|
399
409
|
.action((opts) => {
|
|
400
|
-
const
|
|
410
|
+
const excludeNames = opts.exclude
|
|
411
|
+
? opts.exclude
|
|
412
|
+
.split(',')
|
|
413
|
+
.map((s) => s.trim())
|
|
414
|
+
.filter((s) => s.length > 0)
|
|
415
|
+
: [];
|
|
416
|
+
const pruned = pruneRegistry(undefined, parseInt(opts.ttl, 10), excludeNames);
|
|
401
417
|
if (pruned.length === 0) {
|
|
402
418
|
console.log('No stale entries found.');
|
|
403
419
|
} else {
|
|
@@ -415,12 +431,13 @@ program
|
|
|
415
431
|
.command('models')
|
|
416
432
|
.description('List available embedding models')
|
|
417
433
|
.action(() => {
|
|
434
|
+
const defaultModel = config.embeddings?.model || DEFAULT_MODEL;
|
|
418
435
|
console.log('\nAvailable embedding models:\n');
|
|
419
|
-
for (const [key,
|
|
420
|
-
const def = key ===
|
|
421
|
-
const ctx =
|
|
436
|
+
for (const [key, cfg] of Object.entries(MODELS)) {
|
|
437
|
+
const def = key === defaultModel ? ' (default)' : '';
|
|
438
|
+
const ctx = cfg.contextWindow ? `${cfg.contextWindow} ctx` : '';
|
|
422
439
|
console.log(
|
|
423
|
-
` ${key.padEnd(12)} ${String(
|
|
440
|
+
` ${key.padEnd(12)} ${String(cfg.dim).padStart(4)}d ${ctx.padEnd(9)} ${cfg.desc}${def}`,
|
|
424
441
|
);
|
|
425
442
|
}
|
|
426
443
|
console.log('\nUsage: codegraph embed --model <name> --strategy <structured|source>');
|
|
@@ -434,8 +451,7 @@ program
|
|
|
434
451
|
)
|
|
435
452
|
.option(
|
|
436
453
|
'-m, --model <name>',
|
|
437
|
-
'Embedding model
|
|
438
|
-
'minilm',
|
|
454
|
+
'Embedding model (default from config or minilm). Run `codegraph models` for details',
|
|
439
455
|
)
|
|
440
456
|
.option(
|
|
441
457
|
'-s, --strategy <name>',
|
|
@@ -450,7 +466,8 @@ program
|
|
|
450
466
|
process.exit(1);
|
|
451
467
|
}
|
|
452
468
|
const root = path.resolve(dir || '.');
|
|
453
|
-
|
|
469
|
+
const model = opts.model || config.embeddings?.model || DEFAULT_MODEL;
|
|
470
|
+
await buildEmbeddings(root, model, undefined, { strategy: opts.strategy });
|
|
454
471
|
});
|
|
455
472
|
|
|
456
473
|
program
|
|
@@ -465,6 +482,7 @@ program
|
|
|
465
482
|
.option('-k, --kind <kind>', 'Filter by kind: function, method, class')
|
|
466
483
|
.option('--file <pattern>', 'Filter by file path pattern')
|
|
467
484
|
.option('--rrf-k <number>', 'RRF k parameter for multi-query ranking', '60')
|
|
485
|
+
.option('-j, --json', 'Output as JSON')
|
|
468
486
|
.action(async (query, opts) => {
|
|
469
487
|
await search(query, opts.db, {
|
|
470
488
|
limit: parseInt(opts.limit, 10),
|
|
@@ -474,6 +492,7 @@ program
|
|
|
474
492
|
kind: opts.kind,
|
|
475
493
|
filePattern: opts.file,
|
|
476
494
|
rrfK: parseInt(opts.rrfK, 10),
|
|
495
|
+
json: opts.json,
|
|
477
496
|
});
|
|
478
497
|
});
|
|
479
498
|
|
|
@@ -530,6 +549,90 @@ program
|
|
|
530
549
|
}
|
|
531
550
|
});
|
|
532
551
|
|
|
552
|
+
program
|
|
553
|
+
.command('roles')
|
|
554
|
+
.description('Show node role classification: entry, core, utility, adapter, dead, leaf')
|
|
555
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
556
|
+
.option('--role <role>', `Filter by role (${VALID_ROLES.join(', ')})`)
|
|
557
|
+
.option('-f, --file <path>', 'Scope to a specific file (partial match)')
|
|
558
|
+
.option('-T, --no-tests', 'Exclude test/spec files')
|
|
559
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
560
|
+
.option('-j, --json', 'Output as JSON')
|
|
561
|
+
.action((opts) => {
|
|
562
|
+
if (opts.role && !VALID_ROLES.includes(opts.role)) {
|
|
563
|
+
console.error(`Invalid role "${opts.role}". Valid roles: ${VALID_ROLES.join(', ')}`);
|
|
564
|
+
process.exit(1);
|
|
565
|
+
}
|
|
566
|
+
roles(opts.db, {
|
|
567
|
+
role: opts.role,
|
|
568
|
+
file: opts.file,
|
|
569
|
+
noTests: resolveNoTests(opts),
|
|
570
|
+
json: opts.json,
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
program
|
|
575
|
+
.command('co-change [file]')
|
|
576
|
+
.description(
|
|
577
|
+
'Analyze git history for files that change together. Use --analyze to scan, or query existing data.',
|
|
578
|
+
)
|
|
579
|
+
.option('--analyze', 'Scan git history and populate co-change data')
|
|
580
|
+
.option('--since <date>', 'Git date for history window (default: "1 year ago")')
|
|
581
|
+
.option('--min-support <n>', 'Minimum co-occurrence count (default: 3)')
|
|
582
|
+
.option('--min-jaccard <n>', 'Minimum Jaccard similarity 0-1 (default: 0.3)')
|
|
583
|
+
.option('--full', 'Force full re-scan (ignore incremental state)')
|
|
584
|
+
.option('-n, --limit <n>', 'Max results', '20')
|
|
585
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
586
|
+
.option('-T, --no-tests', 'Exclude test/spec files')
|
|
587
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
588
|
+
.option('-j, --json', 'Output as JSON')
|
|
589
|
+
.action(async (file, opts) => {
|
|
590
|
+
const { analyzeCoChanges, coChangeData, coChangeTopData, formatCoChange, formatCoChangeTop } =
|
|
591
|
+
await import('./cochange.js');
|
|
592
|
+
|
|
593
|
+
if (opts.analyze) {
|
|
594
|
+
const result = analyzeCoChanges(opts.db, {
|
|
595
|
+
since: opts.since || config.coChange?.since,
|
|
596
|
+
minSupport: opts.minSupport ? parseInt(opts.minSupport, 10) : config.coChange?.minSupport,
|
|
597
|
+
maxFilesPerCommit: config.coChange?.maxFilesPerCommit,
|
|
598
|
+
full: opts.full,
|
|
599
|
+
});
|
|
600
|
+
if (opts.json) {
|
|
601
|
+
console.log(JSON.stringify(result, null, 2));
|
|
602
|
+
} else if (result.error) {
|
|
603
|
+
console.error(result.error);
|
|
604
|
+
process.exit(1);
|
|
605
|
+
} else {
|
|
606
|
+
console.log(
|
|
607
|
+
`\nCo-change analysis complete: ${result.pairsFound} pairs from ${result.commitsScanned} commits (since: ${result.since})\n`,
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const queryOpts = {
|
|
614
|
+
limit: parseInt(opts.limit, 10),
|
|
615
|
+
minJaccard: opts.minJaccard ? parseFloat(opts.minJaccard) : config.coChange?.minJaccard,
|
|
616
|
+
noTests: resolveNoTests(opts),
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
if (file) {
|
|
620
|
+
const data = coChangeData(file, opts.db, queryOpts);
|
|
621
|
+
if (opts.json) {
|
|
622
|
+
console.log(JSON.stringify(data, null, 2));
|
|
623
|
+
} else {
|
|
624
|
+
console.log(formatCoChange(data));
|
|
625
|
+
}
|
|
626
|
+
} else {
|
|
627
|
+
const data = coChangeTopData(opts.db, queryOpts);
|
|
628
|
+
if (opts.json) {
|
|
629
|
+
console.log(JSON.stringify(data, null, 2));
|
|
630
|
+
} else {
|
|
631
|
+
console.log(formatCoChangeTop(data));
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
|
|
533
636
|
program
|
|
534
637
|
.command('watch [dir]')
|
|
535
638
|
.description('Watch project for file changes and incrementally update the graph')
|