npm.io
0.2.0 • Published 1 week agoCLI

arcscope

Licence
MIT
Version
0.2.0
Deps
5
Size
3.5 MB
Vulns
0
Weekly
0

arcscope

A fully-local MCP server that gives an AI coding agent precise views of an unfamiliar codebase — fast symbol navigation, a compiler-exact call graph and flow surface, and a repo-declared architecture vocabulary answered live against current code — so it stops re-deriving structure with grep, and stops missing the this.service.foo() calls grep can't see.

Status: 0.1.0. Three tiers — tree-sitter breadth, an agent-authored knowledge layer, and a compiler-exact TypeScript precise tier — work end-to-end. TS / JS / TSX.

npm i -D arcscope
npx arcscope init        # index once, write MCP config, update .gitignore
# reload Cursor (Settings → MCP) or restart Claude Code

init writes the same offline node … serve entry to .cursor/mcp.json (Cursor) and .mcp.json (Claude Code). When arcscope is installed under node_modules, the path is project-relative so you can commit .cursor/mcp.json for the team.

Everything runs on your machine: no network at query time or at server spawn, no telemetry, no embeddings. Breadth comes from tree-sitter (WASM grammars bundled offline); precision comes from the TypeScript compiler running locally. Every result carries a precision tier so the agent never mistakes a heuristic for a compiler-accurate answer.

How it's being used

npx arcscope stats

One local command — no network, no telemetry — that shows both halves:

  • How much — reads .arcscope/usage.jsonl (one line per tool call, written as the server runs): totals, a breakdown by tool, and the most-queried symbols/concepts/entry points, plus your committed concepts and their drift baselines.

  • Is it useful (vs grep) — the server only sees its own calls, so stats also reads the newest Claude Code session transcript for this repo, replays every search/navigation call in order (arcscope vs grep vs ToolSearch ), and reports arcscope's share of arcscope + grep. No transcript yet → it says so and shows the usage half only.

    Reads Claude Code transcripts only (~/.claude/projects); Cursor sessions aren't included. The share is a whole-session volume ratio — a quick "is the agent reaching for it?" gut check, not a formal adoption gate.

Tools

tool answers
find_def where a symbol is defined, with its signature
find_refs who references it — compiler-exact for methods when a tsconfig governs the file (member access obj.method() resolved); import-resolved otherwise
dep_graph the file/module dependency graph: hubs, a file's neighborhood, or circular dependencies
call_graph the method-resolved outgoing call graph from a function — follows this.x.foo() through the type checker
flow the complete surface of a flow before you change it: the call closure + a structural edge-case checklist
arch_list the repo's named architecture concepts (committed + agent-asserted)
arch_query resolve one concept to its live code locations, with drift + conformance
arch_assert record a concept — or a reviewed flow — so a later session inherits it, re-verified live
arch_candidates find a concept's likely missing members by structural (AST-shape) similarity

The precise tier — compiler-exact, still local

tree-sitter can't tell you who calls this.documentService.clone() — resolving a method call needs the receiver's type. arcscope's precise tier runs the TypeScript compiler + language service locally (no network) to answer that exactly:

  • find_refs on a method finds its member-access call sites (obj.method()), resolved through the type checker — the call sites grep and import-resolution miss.
  • call_graph traces the outgoing closure from an entry point with method dispatch resolved to the concrete implementation (DI-injected services, inheritance).
  • flow maps the complete surface of a flow before you touch it: every function it transitively calls, annotated with each function's edge cases (branches, error handling, async). So you catch every case a change must handle.
flow cloneUser
# Flow surface from `cloneUser` (apps/.../clone-user.service.ts:44) — 38 functions, precision tier: typescript:
# cloneUser  apps/.../clone-user.service.ts:44  (+5 lib)  {2 branch}
# ├─ loadRecord  apps/.../record.repository.ts:183  (+2 lib)  {2 branch, 3 await}
# │  └─ …
# └─ performClone  apps/.../clone-user.service.ts:62  (+3 lib)  {1 err, 1 await}
#    └─ cloneRecord  libs/.../record-clone.ts:32  (+1 lib)
#       └─ cloneItem  libs/.../record-clone.ts:104
#          └─ reindexAfterClone  libs/.../reindex.ts:41
# … (38 functions; tree abbreviated)
# Edge-case surface: 6 decision points (if/switch/ternary) · 1 error-handling site (try/throw) · 7 async boundaries (await).
# Each {…} tag marks where behaviour forks, fails, or awaits — verify your change handles each before you write it.

The architecture vocabulary — the differentiator

Where other tools keep project knowledge as static prose that silently rots, arcscope binds each named concept to an executable locator recomputed on every query. The knowledge is agent-authored, not hand-edited: as the agent works out a concept, it records it with arch_assert into a committed .arcscope/assertions.yaml — a binding of locators, never a frozen list — so a later session inherits it. One concept looks like:

concepts:
  repository-tokens:
    title: Repository-token pattern (I{Name}Repository)
    locators:
      - { kind: symbol, query: "interface I*Repository", in: "libs/data-access/**" }
      - { kind: symbol, query: "const *_REPOSITORY = InjectionToken", in: "libs/**/tokens/**" }
      - { kind: path,   glob: "apps/**/firestore-*.repository.ts" }

arch_query repository-tokens resolves this against the current tree and flags drift when the resolved set diverges from its accepted baseline. Locators come in three kinds — symbol (a tree-sitter query), path (a glob), and import (every file importing a module specifier) — and resolve through arcscope's own engine; a committed assertion can never run a shell command.

Three things keep an agent-written assertion honest:

  • a must invariant — a rule every member must satisfy, re-checked live (conformance); a violation surfaces on every query, not when it ships.
  • arch_candidates — finds the members a binding missed, by AST-shape similarity (name-independent, so it catches a hand-copied re-implementation that shares no name or import). The "fixed it twice" antidote.
  • a flow conceptarch_assert a reviewed flow by its entry point; arch_query recomputes the whole flow live (precise tier) and drifts when a function enters or leaves it.

Nothing is stored as a bare fact: every assertion is re-verified against live code on read, so it can't silently rot.

Drift in practice

The first arch_query records a baseline; later queries compare against it:

arch_query repository-tokens
# Concept `repository-tokens` — Repository-token pattern (7 locations, fresh (baseline captured)):
#  … later, someone adds apps/audit/firestore-audit.repository.ts …
arch_query repository-tokens
# Concept `repository-tokens` — Repository-token pattern (8 locations, DRIFTED):
#   ⚠ DRIFT vs baseline: 1 added, 0 removed, 0 changed.
#     + apps/audit/firestore-audit.repository.ts
arch_query repository-tokens reaccept:true     # accept the new shape → fresh again

How it works

One local Node process speaking MCP over stdio, in tiers:

  • Engine (tree-sitter) — web-tree-sitter parses each file (lazy WASM grammars) into symbols + import edges. The always-on breadth substrate.
  • Graph — a derived view over those import edges (dependency graph, cycles), grouped by directory + import-clustering — never a build tool's project model (nx.json, BUILD files).
  • Knowledge — the live, agent-authored architecture vocabulary above.
  • Precise tier (TypeScript) — a local ts.Program + language service, built lazily per tsconfig and cached, powering the call graph, flow surface, and compiler-exact method references.

Precision tiers & limits

Two tiers, labeled on every result:

  • tree-sitter — fast, broad (multi-language breadth), structural. The always-on substrate; complete for imported-symbol references.
  • typescript — compiler-exact, via the local TypeScript compiler + language service. Resolves method dispatch, DI, inheritance, overloads.

arcscope never presents a tree-sitter heuristic as compiler-exact. Honest residual limits (the precise tier counts these at the boundary, never hides them):

  • any-typed / higher-order callbacks and runtime-wired providers (InjectionToken, useFactory) may resolve to the declared member rather than the concrete impl.
  • the precise tier is per-tsconfig — a single flow rarely spans module systems, but cross-project closures aren't unioned. It builds a TS program on first use (seconds), cached after.
  • TS / JS / TSX only; module grouping is general-first (directory + import clustering), never a build tool's project model.

Keywords