CodeScope does not understand code. It verifies code.
It transforms source code into verifiable facts, understandable models, and inspectable evidence — enabling AI to validate claims against reality instead of hallucinating.
Version: v0.2.7 | License: Apache 2.0
CodeScope is a Project Truth Engine that answers one question:
"Does the code actually do what you claim?"
Not "what does this code mean", but "does the code actually do what you claim?"
It indexes source code into a structured code graph (call graph + reference graph + module knowledge), then exposes 46 MCP tools that let AI agents locate symbols, trace call paths, verify claims, detect documentation drift, and analyze architecture — all with ~98.9% token savings vs reading raw source files.
| Language | Parser | IR Translator | Verified |
|---|---|---|---|
| Python | ✅ | ✅ | ✅ |
| Go | ✅ | ✅ | ✅ |
| C | ✅ | ✅ | ✅ |
| C++ | ✅ | ✅ | ✅ |
| Rust | ✅ | ✅ | ✅ |
| JavaScript | ✅ | ✅ | ✅ |
| TypeScript | ✅ | ✅ | ✅ |
| Java | ✅ | ✅ | ✅ |
What "Verified" means: the language has an automated test that asserts a real
semantic record (a named call, method declaration, or interface implementation)
is emitted — not merely that the response JSON contains the expected keys. Those
tests live in engine/tests/test_ir_edge_coverage.cpp; before they existed the
per-language E2E harness only checked for the strings "callers" / "callees" /
"total_nodes", so a translator that emitted zero edges still passed.
Known limitations (documented rather than silently dropped):
- Rust: macro invocations (
println!,vec!, userfoo!()) are recorded as calls to the macro name. tree-sitter does not expand macros, so code generated by a macro body is not in the graph, and a macro is matched by name only. - JavaScript:
class A extends Bis not recorded as anInterfaceImpl(JavaScript has noimplementsclause); the extends expression is still visited. TypeScript/TSXimplementsclauses are recorded. - Inference is best-effort: receiver types come from local declarations,
composite literals, and
this/self; dynamically typed receivers stay unknown. - Pathologically deep ASTs are truncated, not walked in full: when a file's
AST nests deeper than
kMaxVisitDepth(250), the deeper subtrees are skipped and the file is reported once on stderr ([module=ir, method=…] AST nesting exceeded kMaxVisitDepth=250). The cap exists because the recursive walk runs on the indexer's 512 KB worker stacks; hand-written code never reaches it, generated code occasionally does. - Some extensions are detected but not parsed:
.kt/.kts,.rb,.scalaand.swiftare recognized (they count as candidate files) but no grammar is vendored for them, so they are skipped and recorded inparse_failuresaslanguage_missing— listed bycodescope parse-failures, never silently dropped, re-attempted on every run, and exempt from theCODESCOPE_FAIL_RETRY_MAXfail-fast skip. Swift'sparser.cis ABI-incompatible with the vendored tree-sitter core, which is why its grammar, visitor and builtin table are absent (seeengine/src/parser/parser.cpp). - A file keeps one identity across the entry points. The walk-based entry
points store the path exactly as passed, while the single-file ones
(
force_index_files, the scheduler's--file-listretry) receivestd::fs::canonicalized paths. Indexing through a symlinked path (macOS/tmp/xis really/private/tmp/x, and the same applies to any symlinked workspace) and then force-indexing something inside it used to store one file under two spellings, duplicating its symbols —find_symbolanswered with the same symbol twice. The stored spelling is now reused both for root-relative forms and, as a second pass, for any absolute row whose ** canonical form equals** the incoming path's (equality, not a guess, so two files can never be merged), and a relative spelling is recognised when its prefix resolves to the project root from the working directory the indexer ran in. Still uncovered, and deliberately left at the old behaviour rather than guessed: Windows paths (the narrowing pattern uses/) and a relative spelling whose index-time working directory is no longer the current one. Rows written by an older build keep their extra spelling — re-indexing deletes entity rows only for the spelling being written, so clear the affectedparse_failuresrows withcodescope reset-failuresand rebuild the database (delete.codescope/codescope.dband re-index) for duplicated symbols.
| Layer | Technology |
|---|---|
| Parser | tree-sitter (unified AST IR, 8 languages) |
| Indexer | C++23 (Clang 17+), SQLite (WAL mode, FTS5) |
| Server | Rust 2024 Edition, MCP Protocol (JSON-RPC 2.0, stdio transport) |
| Graph Storage | SQLite (sole graph store, CSR adjacency for sub-ms call-graph queries) |
| Scheduler | Built-in multi-process parallel indexer (static proportional allocation by default; opt-in shared chunk queue via CODESCOPE_CPU_DYNAMIC=1) |
| Build | CMake 3.30+ (C++), Cargo (Rust) |
graph TB
subgraph "AI Client"
Client["Claude Desktop / Cursor / Any MCP Client"]
end
subgraph "Rust MCP Server"
MCP["MCP Protocol (JSON-RPC 2.0)<br/>46 tools / stdio transport"]
DISPATCH["Tool Dispatch<br/>project_id auto-restore"]
end
subgraph "C++ Core Engine"
PARSER["Parser<br/>tree-sitter → unified IR<br/>8 languages"]
FACTS["Facts Repository<br/>entity / reference / scope / import"]
RESOLVER["Resolver Pipeline<br/>Constraint Chain"]
MODEL["Model Engine<br/>Workflow / Capability<br/>Architecture / Contract"]
INSPECTOR["Inspector<br/>DeadCodeInspector / verify_integrity"]
end
subgraph "SQLite (WAL mode)"
F_STORE["Facts Store<br/>entity / reference / scope / import"]
S_STORE["Semantic Store<br/>resolved_reference / relation"]
M_STORE["Model Store<br/>workflow / capability<br/>architecture / contract"]
E_STORE["Evidence Store<br/>claim / evidence / finding"]
end
Client -->|"MCP stdio"| MCP
MCP --> DISPATCH
DISPATCH -->|"FFI"| PARSER
DISPATCH -->|"FFI"| FACTS
DISPATCH -->|"FFI"| RESOLVER
DISPATCH -->|"FFI"| MODEL
DISPATCH -->|"FFI"| INSPECTOR
PARSER -->|"writes"| F_STORE
F_STORE -->|"reads"| RESOLVER
RESOLVER -->|"writes"| S_STORE
S_STORE -->|"reads"| MODEL
MODEL -->|"writes"| M_STORE
M_STORE -->|"reads"| INSPECTOR
INSPECTOR -->|"writes"| E_STORE
Source Code
|
v
Parser ------------ entity / reference / scope / import
|
v
Resolver ---------- resolved_reference / relation
|
v
Model Engine ------ workflow / capability / architecture / contract
|
v
Inspector --------- evidence / finding
flowchart LR
Q["MCP Client<br/>tool call"] --> Q1["Server receives<br/>project_id auto-restore"]
Q1 --> Q2{"Tool type?"}
Q2 -->|"session auto-index"| Q3["At initialize: spawn worker subprocess<br/>→ memory isolated<br/>→ exits after done<br/>(not a callable tool)"]
Q2 -->|"query tools"| Q4["C++ FFI → SQLite query<br/>entity, relation<br/>code_fts, ..."]
Q2 -->|"get_communities"| Q5["Load full graph<br/>Label Propagation<br/>→ JSON with max_communities limit"]
Q2 -->|"get_hotspots"| Q6["SQL: COUNT(r.id) JOIN<br/>relation edge_type=1<br/>ORDER BY caller_count"]
Q4 --> R["Result JSON<br/>back to MCP Client"]
Q5 --> R
Q6 --> R
flowchart LR
subgraph A["Phase A: Index (scan + full tree-sitter parse)"]
S1["scan_project"]
S2["total_symbols"]
S3["module_tree"]
S4["entry_points"]
S5["full tree-sitter parse → IR records"]
end
subgraph B["Phase B: Graph Finalize (async, seconds)"]
E1["enhance_project"]
E2["buildGraph — NO re-parse"]
E3["call graph / CSR"]
E4["FTS index"]
E5["semantic_fact (v0.3)"]
end
A -->|"trigger (CLI index-parallel path only)"| B
Where the parsing happens: the full tree-sitter parse runs in Phase A, not
Phase B. Phase B (enhance_project) is a lightweight GraphFinalize step —
buildGraph → buildFTSFromGraph → resolveStagedMetrics → semantic_fact
extraction → model build — and never re-parses or re-translates
(engine_queries.cpp).
When Phase B runs automatically: only the CLI path (codescope index-parallel)
triggers it after the merge (server/src/main.rs). The MCP session auto-index
(§5, index_project) triggers the FTS build only, so call enhance_project
explicitly if you need a full graph finalization inside an MCP session.
CodeScope does not index every file in a project. Instead, it applies an 8-layer cascade that strips away noise so you only see what matters: the core source code.
A typical project looks like this (rustc, the Rust compiler):
Total source files: 36,919
tests/ 26,293 ← 71%: test suites
src/tools/*/test/ 3,802 ← 10%: embedded test directories
library/*/test/ 339 ← 1%: library tests
compiler/*/test/ 118 ← <1%: compiler tests
docs/vendor/bench/ 368 ← 1%: documentation, vendored deps, benchmarks
─────────────────────────────────────
Core code indexed: 6,029 ← 16%: the actual source code
Without filtering, CodeScope would waste 84% of its time indexing tests, vendored dependencies, documentation, and build artifacts — files no one needs to analyze.
Layer 1: any-depth skip directories (~150 patterns)
.git, .svn, .hg, node_modules, .venv, target, build, dist,
vendor, vendored, third_party, bin, bench, benchmarks,
test, tests, e2e, docs, examples, samples,
__pycache__, .github, deploy, docker, k8s, ...
→ Catches VCS, build artifacts, dependencies, vendored code and the
test/docs/sample conventions at ANY depth
Layer 2: top-only skip directories (depth ≤ 3, every language)
scripts, hack, migrations, seeds, integration,
locale, locales, i18n, l10n, assets, static, public, media, external
→ These are ordinary business directories outside the web convention
(src/integration/, pkg/scripts/, db/migrations/, app/static/ all
hold first-party code), so skipping them at any depth silently
dropped real symbols — an undetectable false negative. They are
still skipped at the project root and the next two levels, where
the web-frontend convention they target actually lives. The
non-source formats filling those dirs are rejected by Layer 3.
→ For Java: the Layer-1 names (test, docs, samples, ...) are also
top-only, protecting package namespaces (org/.../samples/petclinic)
Layer 3: file suffix skip (always applied)
.md, .txt, .json, .yaml, .toml, .ini, .png, .jpg, .svg,
.pdf, .zip, .tar, .min.js, .d.ts, ...
→ Non-source files, documentation, images, archives
Layer 4: exact filename skip
package-lock.json, yarn.lock, .DS_Store, Thumbs.db,
.env, .env.local, .gitkeep, .gitignore, ...
Layer 5: filename/directory prefix skip
File prefixes: .env.*, docker-compose.* (plus trailing-`~` editor backups)
Directory prefixes: build_*, cmake-build-*, _build, tools-*, tools_*
Layer 6: .gitignore pattern matching
Respects every rule in the project's .gitignore
Layer 7: .codescopeignore (user-defined)
Additional custom ignore patterns per project
Layer 8: file size limit + language detection
• Max file size (default 5 MB = 5242880 bytes, configurable via CODESCOPE_MAX_FILE_SIZE)
• Undetectable language files are silently skipped
Layer numbers are a classification, not the execution order. The skip verdict is a boolean OR, so only the result is observable, but the checks actually run in this order (
FilterPolicy::shouldSkipEntry): path components (Layer 1 / 2) plus Layer 6.gitignoreand Layer 7.codescopeignore→ bundle suffixes → Layer 4 exact filename + Layer 5 prefixes → Layer 3 suffix → the STRICT gate → the user'sCODESCOPE_EXCLUDE_PATHSpatterns last, as an override on top of everything else.
| Project | Raw Source Files | After Filtering | Filtered Out | Time Saved |
|---|---|---|---|---|
| rustc (Rust compiler) | 36,919 | 6,029 | 84% | ~2.5 min |
| CodeScope (self) | 356 | 168 | 53% | ~1 s |
Need to index a specific test file or vendored directory? Use the force_index_files MCP tool — it bypasses all 8 layers:
codescope cli force_index_files '{"paths":["/path/to/test/file.rs"]}'| Platform | Dependencies |
|---|---|
| macOS | Xcode CLT, cmake, Rust (1.85+) |
| Linux | build-essential, cmake, Rust (1.85+) |
| Windows |
MinGW-w64 14.0.0+, Rust x86_64-pc-windows-gnu target, cmake, and the sqlite3 CLI on PATH (the parallel and chunked schedulers merge their worker databases by piping SQL into it). Every graph-query tool (shortest_path, get_neighbors, get_callers/callees, graph_query, subgraph, entry_points, trace_path, hotspots, impact_analysis, ...) works via the built-in SQLite graph-query backend (CSR adjacency, sub-millisecond call-graph lookups). Parallel indexing (index-parallel, including the chunk-level scheduler) now works on Windows as well — the shared state both schedulers map has a Windows backend (CreateFileMappingW/MapViewOfFile) — but stays Beta: the windows-smoke CI job is its acceptance gate and it is dispatch-only. If the sqlite3 CLI is missing, use codescope index (single-process), which does not merge. |
New here?
docs/en/QUICK_START.mdis the 5-minute path — install, index a project, connect an MCP client, run the first queries.
curl -fsSL https://raw.githubusercontent.com/Timwood0x10/CodeScope/main/install.sh | bash
export PATH="$PATH:$HOME/.codescope/bin"macOS code signing issue: If the binary is killed immediately on launch (exit code 137 / SIGKILL), re-sign it locally:
codesign --sign - --force ~/.codescope/bin/codescopeThis happens because the CI-built binary uses ad-hoc signing, which newer macOS versions may reject. Re-signing with your local machine's identity resolves it.
git clone https://github.com/Timwood0x10/CodeScope.git
cd CodeScope
# macOS:
brew install llvm@21 cmake pkg-config
cargo build --release
# Linux (Ubuntu):
sudo apt-get install -y build-essential cmake llvm-dev libclang-dev
cargo build --release# Index a project (spawns an isolated worker subprocess per module)
codescope index-parallel /path/to/your/project
# Quick overview
codescope cli project_overview '{}'
# Start MCP server (for AI clients)
codescopeFor projects with thousands of files, tune the built-in parallel scheduler:
codescope index-parallel /path/to/large/project --workers 8 --parallel 4--workers sets the total parse-worker cores (default 8) and --parallel the
maximum concurrent module workers (default 4).
# Files the indexer could not parse, with the reason and the retry count
codescope parse-failures --limit 50
# Clear them, so those files are attempted again on the next run
codescope reset-failuresBoth accept --db <path> (or CODESCOPE_DB_PATH) and operate on the most
recent project in that database. A file whose only failure is a missing grammar
(e.g. .swift) is retried on every run anyway and needs no reset.
The 46 tools listed here are exactly the ones advertised by tools/list
(server/src/tools/catalog.rs) and dispatched by name (TOOL_HANDLERS in
server/src/tools/mod.rs); the two sets are identical — there are no
advertised-but-unhandled or handled-but-unadvertised tools.
Responses are capped at 1 MiB by the stdio transport. A tool result larger than that is replaced by a JSON-RPC error (
-32000) naming the size and suggesting a narrower query or smaller limits — it is never truncated silently. Bound large result sets with the tools' own arguments (LIMITin agraph_queryDSL string,limit/node_limit/edge_limit,max_findings, …).
index_projectis session-only and NOT callable by name. It is absent fromtools/listand from the tool dispatcher, sotools/call {"name":"index_project"}returns{"error":"Unknown tool: index_project …"}. A worker-subprocess index runs automatically once per MCP session atinitialize(server/src/mcp/server.rs). The CLI/agent equivalent iscodescope index-parallel <dir>;codescope cli index_projectis not a valid invocation. Callenhance_projectexplicitly if you need a full graph finalization inside a session (see §2).
| Tool | Description | Parameters |
|---|---|---|
index_file |
Index a single source file. | {"file_path": "string (required)"} |
force_index_files |
Force-index files/dirs bypassing default skip rules (test/, docs/, node_modules/, .gitignore, etc.). | {"paths": ["string (required)"], "language_filter": "string (optional)"} |
| Tool | Description | Parameters |
|---|---|---|
project_overview |
Primary — comprehensive project overview: languages, modules, symbols, entry points, analysis progress. | {} |
get_graph_stats |
Quick statistics: nodes, edges, files. | {} |
get_module_tree |
Hierarchical module/directory tree. | {} |
get_entry_points |
Find entry points (main/init/setup/run/handler). | {} |
get_routes |
Get registered HTTP routes (Gin/Echo/Chi/net/http). | {} |
get_type_info |
Query type definitions (struct/enum/trait) with reference counts. | {"type_name": "string (optional)"} |
| Tool | Description | Parameters |
|---|---|---|
find_symbol |
Recommended — find symbol by exact name (kind, file, line/col). | {"symbol_name": "string (required)"} |
find_references |
Find all locations referencing a symbol. | {"symbol_name": "string (required)", "file_filter": "string (optional)"} |
explain_symbol |
Get comprehensive symbol info: definition, callers, callees, dependencies. | {"symbol_name": "string (required)"} |
find_definition |
[DEPRECATED — use find_symbol] | {"symbol_name": "string (required)"} |
| Tool | Description | Parameters |
|---|---|---|
find_callers |
Find who calls a function. | {"symbol_name": "string (required)", "file_filter": "string (optional)"} |
find_callees |
Find what a function calls. | {"symbol_name": "string (required)", "file_filter": "string (optional)"} |
find_callers_by_entity |
Find callers of a symbol by its graph entity id. | {"entity_id": "integer (required)"} |
find_callees_by_entity |
Find callees of a symbol by its graph entity id. | {"entity_id": "integer (required)"} |
get_verifier_registry_status |
Inspect the registered verifiers and their health (supported claim types, unsupported list, backend readiness). | {} |
codescope_trace |
Interactive recursive call exploration (depth + direction) or shortest path. | {"function_name": "string (required for exploration mode)", "depth": "integer (default 1, max 5)", "direction": "callers|callees|both", "from": "string (shortest-path mode: required with to)", "to": "string (shortest-path mode: required with from)"} |
trace_flow |
Recursive execution flow tracing (caller→callee chain). | {"function_name": "string (required)", "depth": "integer (default 3, max 10)"} |
shortest_path |
Shortest call path between two functions (BFS). | {"from": "string (required unless from_id)", "to": "string (required unless to_id)", "from_id": "integer (required unless from)", "to_id": "integer (required unless to)"} |
connected_components |
Connected components in the call graph. | {} |
get_communities |
New — detect communities (clusters) in the call graph via deterministic label propagation. Returns {communities:[{id,label,member_count}], total_communities, returned_communities, inter_community_edges, truncated}; members are omitted unless include_members is true. |
{"max_communities": "integer (default 20, max 500)", "include_members": "boolean (default false)", "max_members": "integer (default 10, max 200)"} |
| Tool | Description | Parameters |
|---|---|---|
graph_query |
Cypher-like DSL query: MATCH (Function:main)-[Calls]->(Method), plus optional trailing clauses: LIMIT <n> (honoured; the response then also carries truncated: true) and RETURN <fields> (accepted, no effect today). Any other trailing text is an error. Use LIMIT on large graphs — a broad pattern can exceed the 1 MiB transport cap (see the note above the table). |
{"dsl": "string (required)"} |
get_graph |
Retrieve the complete code graph in paginated pages. | {"node_offset": "integer", "node_limit": "integer (max 50000)", "edge_offset": "integer", "edge_limit": "integer (max 200000)", "node_types": "string", "edge_types": "string"} |
get_subgraph |
Fetch a local region centered on a node (1-hop). | {"node_id": "integer (required)", "radius": "integer", "node_types": "string", "edge_types": "string"} |
get_neighbors |
Fetch direct neighbors (callers + callees) of a graph node. | {"node_id": "integer (required)", "edge_type": "integer (default -1)", "radius": "integer"} |
get_knowledge_graph |
Direct-query a knowledge-layer table. Tables: entity, relation, architecture_edge, module_edge, capability, document, module_summary (see §6). |
{"table": "string (required)", "limit": "integer (default 100, max 1000)"} |
| Tool | Description | Parameters |
|---|---|---|
search |
Recommended — unified search: FTS5 exact/prefix matching complemented by n-gram semantic vector search (restored in v0.2.5; lexical similarity, no external model) when results run short. | {"query": "string (required)", "limit": "integer (default 20, max 100)"} |
search_code |
[DEPRECATED — use search] | {"query": "string (required)", "limit": "integer"} |
| Tool | Description | Parameters |
|---|---|---|
verify_integrity |
Check README-promised features actually exist in code. | {"max_findings": "integer (optional, default 200, max 2000)"} |
verify_claim |
Verify a single claim. claim is a JSON object string, not free text: {"type": "capability_exists | contract_holds | architecture_follows | function_implements", "subject": "symbol/module/contract", "predicate": "optional detail"}. An unknown or missing type is rejected with error_code: claim_type_unsupported instead of being guessed. |
{"claim": "string (required, JSON object)"} |
verify_summary |
Parse natural-language summary and verify each claim. | {"text": "string (required)"} |
verify_review |
Verify code review comment claims. | {"text": "string (required)"} |
verify_reality |
Verify a single AI statement against code evidence. | {"text": "string (required)"} |
The v0.3 Evidence Pipeline transforms indexed code into verifiable evidence and project health snapshots. Flow: Facts → SemanticFacts → Evidence → Verification → ProjectState. Semantic facts are extracted by enhance_project (Step 1.5); evidence is built by applying declarative rule files (engine/src/evidence/rules/*.json) to the semantic_fact table.
| Tool | Description | Parameters |
|---|---|---|
enhance_project |
Run background graph finalization: buildGraph (call graph + CSR), FTS, staged metrics, model build, and v0.3 semantic_fact extraction. It does not re-parse — the full tree-sitter parse happens during indexing (Phase A, §2). Prerequisite for build_evidence to produce non-empty findings. Complexity metrics and n-gram semantic vectors are built during index_project (restored in v0.2.5); n-gram vectors additionally require CODESCOPE_INDEX_MODE=deep. Returns ok:false with failed_step when a finalization step fails. |
{} |
build_evidence |
Build evidence findings by applying the rule set (sync/memory/error/pattern/framework/ffi) to the project's semantic_fact rows. Each rule declares fact needs + a combine mode (Collect / MissingMatch / MissingMatchPerFunction / Count). Returns a JSON array of Evidence objects. Run after enhance_project so semantic facts are populated. |
`{"category": "string (optional, one of: sync |
build_project_state |
Build (or rebuild) and persist the project state snapshot. Runs the full v0.3 Evidence Pipeline (evidence aggregation + state queries) and UPSERTs the result into the project_state table. Returns the snapshot JSON: overall confidence, capability/architecture/workflow/dead_code scores, per-category issue counts, and last_updated timestamp. |
{} |
get_project_state |
Get the previously persisted project state snapshot (without rebuilding). Returns the snapshot_json string for the project, or a JSON error object if no snapshot exists yet (run build_project_state first). Use this for fast reads of the latest health snapshot. |
{} |
| Tool | Description | Parameters |
|---|---|---|
detect_drift |
Scan all declared capabilities & contracts for doc-vs-code drift. | {} |
detect_documentation_drift |
Check README language claims vs actual code entities. | {} |
detect_capability_drift |
Check declared capabilities have implementing entities. | {} |
detect_architecture_drift |
Check call edges for layer violations (Repository→Controller). | {} |
| Tool | Description | Parameters |
|---|---|---|
detect_changes |
Analyze impact of modified files: direct/indirect callers. | {"modified_files": "string (required)"} |
explain_module |
Build module knowledge card: entities, capabilities, integrity score. | {"module_name": "string (required)"} |
| Tool | Description | Parameters |
|---|---|---|
detect_ffi_boundaries |
Detect FFI boundaries (extern/C, JNI, WASM, C ABI). | {} |
count_tokens |
Estimate token count (DeepSeek formula). | {"text": "string (required)"} |
New project → project_overview
Module structure → get_module_tree
Entry points → get_entry_points
Search code → search
Call chain → find_callers / find_callees
Deep dive symbol → explain_symbol
HTTP routes → get_routes
Type info → get_type_info
Verify claim → verify_claim
Enhance project → enhance_project
Build evidence → build_evidence
Project health → build_project_state
Detect drift → detect_documentation_drift
Change impact → detect_changes
CodeScope builds a module-level knowledge graph as a side product of the verification pipeline. It learns structured metadata about how the project is organized, what's important, what's redundant, and what it promises:
| Layer | Table | What it tells you |
|---|---|---|
| Call graph | relation, architecture_edge |
Cross-module call dependencies; drives detect_architecture_drift |
| Module health | module_summary |
Per-module incoming_count / outgoing_count / dead_entities / utilization / role |
| Module dependency | architecture_edge, module_edge |
"Change module A → these modules depend on it" |
| Documented capability | capability + document |
README-extracted capabilities; drives detect_capability_drift / verify_claim |
All knowledge-layer tables are directly queryable via get_knowledge_graph:
Supported tables: entity, relation, architecture_edge, module_edge, capability, document, module_summary.
All benchmarks measured on Apple M3 Max (36 GB RAM, 14 cores), macOS, 2026-08-14, using target/release/codescope (v0.2.6) in worker (serial full index) mode with the pure-SQLite graph backend (no LadybugDB/Kuzu dependency), CODESCOPE_INDEX_MODE=normal. Query latency measured via MCP server mode (engine initialized once, median of 7 runs). Numbers reflect the in-memory fuzzy resolver + ordering fix (edge count 1,249 → 1,189 vs. the pre-fix binary; nodes/files unchanged). Other hardware will produce different results — expect slower performance on less capable machines.
Indicative, single-run figures. These tables are one measurement on one machine, not a regression gate:
benchmarks/run_benchmark.shre-runs the index-time rows, but the latencies and the token-savings table below are recorded by hand and are not reproduced by any script. Treat them as order-of-magnitude indications.
| Project | Language | Files | Nodes | Edges | Index Time | Peak RSS |
|---|---|---|---|---|---|---|
| CodeScope (self) | C++/Rust | 197 | 1,509 | 1,189 | 0.95 s | ~179 MB |
| tinygo | Go | 812 | 21,557 | 4,485 | 1.77 s | ~505 MB |
| rustc (Rust compiler, monorepo) | Rust | 5,575 | 130,410 | 117,284 | 38.94 s | ~4.13 GB |
All graph queries run on the built-in SQLite graph-query backend (CSR adjacency tables), sub-millisecond for typical call-graph lookups. Measured on the CodeScope self-index DB unless noted.
| Query | Measured (median) |
|---|---|
get_graph_stats |
0.08 ms |
find_callers("parse") |
0.16 ms |
find_callees("parse") |
0.17 ms |
graph_query (LIMIT 100) |
88.8 ms |
graph_query (full scan, no LIMIT, self DB) |
88.9 ms |
graph_query (full scan, rustc DB) |
>180 s — always use LIMIT on large graphs |
shortest_path |
0.09 ms (rustc DB: 0.38 ms) |
get_neighbors |
0.06 ms (rustc DB: 2.36 ms) |
get_subgraph |
0.10 ms (rustc DB: 5.68 ms) |
get_module_tree |
0.08 ms |
get_entry_points |
0.21 ms |
get_knowledge_graph |
0.10 ms |
Measured on the self-index DB via MCP server mode (engine initialized once).
| Metric | Value |
|---|---|
Engine init (GraphStore::open) |
3 ms |
| Index throughput (CodeScope self) | ~207 files/s (197 files / 0.95 s) |
Symbol query (find_callers/find_callees, MCP-level) |
0.16–0.17 ms |
graph_query (LIMIT 100) |
88.8 ms |
| 10 queries (total, MCP-level) | ~89.9 ms (dominated by graph_query) |
| Query latency (CLI, includes process start) | 10–17 ms |
| Project | Cross-File CALLS | % of total CALLS |
|---|---|---|
| CodeScope (C++) | 727 | 61.1% |
| tinygo (Go) | 2,210 | 49.3% |
| rustc (Rust) | 70,634 | 60.2% |
codescope discover(File discovery + module statistics, pure Rust, no engine initialization)。
| Project | Time | Languages | Modules |
|---|---|---|---|
| CodeScope (self) | 27 ms | cpp, rust, c | engine 290 / server 20 |
Using code graphs instead of raw source files saves ~98.9% tokens on average:
| Scenario | Raw Source | CodeScope | Savings |
|---|---|---|---|
| Find function definition | ~2,265 tokens | ~21 tokens | 99.1% |
| Trace function callers | ~2,000 tokens | ~18 tokens | 99.1% |
| Project architecture overview | ~1,875 tokens | ~32 tokens | 98.3% |
| USB subsystem overview | ~24,000 tokens | ~250 tokens | 99.0% |
| Scheduler analysis | ~15,000 tokens | ~180 tokens | 98.8% |
The skills/ directory provides shell scripts that wrap common CodeScope queries so you don't need to remember the JSON schema:
cd CodeScope
# Index a project
./skills/index.sh ~/path/to/project
# One-shot full analysis report
./skills/analyze.sh ~/path/to/project
# Graph statistics
./skills/stats.sh
# Module tree
./skills/modules.sh
# Trace call path A → B
./skills/trace.sh func_a func_b
# Top 20 hotspots
./skills/hotspots.sh 20
# Browse architecture dependencies
./skills/knowledge.sh architecture_edge 20Each script calls codescope cli <tool_name> '<json_args>' internally. See skills/skills.md for the full reference.
| Variable | Default | Description |
|---|---|---|
CODESCOPE_DB_PATH |
.codescope/codescope.db |
SQLite database path |
CODESCOPE_INDEX_MODE |
normal |
Parse-pipeline mode: fast / normal / deep. strict is a separate discovery-only mode — see the notes below. |
CODESCOPE_EXCLUDE_PATHS |
(unset) | Comma-separated glob patterns to exclude, matched against the project-relative path and applied last (they override the built-in filters). Escape a literal comma inside a pattern as \, (e.g. a\,b/**); \\ is a literal backslash. |
CODESCOPE_WORKERS |
min(hw,8) |
Total parse-worker cores (kDefaultParseWorkers=8). The in-memory path used for projects of ≤2000 files defaults to 4 instead. |
CODESCOPE_WORKER_TIMEOUT |
300 |
Worker subprocess timeout in seconds |
CODESCOPE_MAX_FILE_SIZE |
5242880 (5 MB) |
Max source file size to index in bytes. Files above it are skipped silently. |
CODESCOPE_FAIL_RETRY_MAX |
1 |
Parse failures before a file is skipped entirely on the automatic index paths (min 1); see parse_failures / codescope reset-failures. Files whose only failure is a missing grammar (language_missing, e.g. .swift) never count and are retried on every run. force_index_files applies no such skip: a forced file is always re-attempted. |
CODESCOPE_MMAP_SIZE |
256 MB | SQLite mmap_size pragma value |
CODESCOPE_MEM_LIMIT_MB |
4096 |
Dynamic-scheduler memory ceiling |
CODESCOPE_DYNAMIC_SCHED |
(unset = static) | Opt-in dynamic CPU scheduling. CODESCOPE_CPU_DYNAMIC is the canonical name and this is the legacy alias; either one works. 1/true/on enables the shared chunk-queue scheduler, 0/false/off disables it, unset stays on the static proportional allocator. |
CODESCOPE_LSP |
(unset) | LSP server command for type enhancement |
strictanddeep:CODESCOPE_INDEX_MODE=strictonly tightens file discovery (a whitelist gate on detected languages); it does not change the parse pipeline.deepadditionally builds the n-gram semantic vectors thatnormalskips.
index-parallelalways parses infastmode. Its per-module and chunk workers are launched withCODESCOPE_INDEX_MODE=fasthard-coded (server/src/scheduler/worker.rs,quarantine.rs), because the merged DB gets its graph pass afterwards. Settingnormal/strict/deeptherefore has no effect oncodescope index-parallel; use the single-processcodescope indexpath when you need a non-fastpipeline.Dynamic scheduling is opt-in, never automatic. The production dispatcher checks the explicit flag only; it does not auto-enable the chunk scheduler for large projects (this is deliberate — see
plan/next/DYNAMIC_SCHED_REDESIGN.md).
Apache 2.0 — see LICENSE.
CodeScope v0.2.7 — Built with Rust 2024 + C++23 + tree-sitter + SQLite.
get_knowledge_graph {"table":"architecture_edge","limit":5} // → {"table":"architecture_edge","rows":[...],"total":3351} get_knowledge_graph {"table":"capability","limit":10} // → {"table":"capability","rows":[...],"total":3}