-
Notifications
You must be signed in to change notification settings - Fork 1
Architecture
High-level architecture, written from the codebase at tag v0.1.4 (2026-08-31). Every file reference below was read from the tree, not from memory. If code and wiki disagree, the code wins.
cie turns a codebase into a queryable graph: symbols, files, call edges (receiver-resolved), test edges, tasks/QA records and PRD hierarchy live in ONE store — so questions grep cannot answer ("what breaks, and which tests catch it?") become single tool calls. Three front-ends expose the same 135-tool surface over one envelope; two storage backends serve it; one policy layer enforces what each caller may do.
┌───────────────────────────────────────────┐
front-ends │ cie-mcp (stdio/HTTP MCP) │ cie CLI │ HTTP │
│ routes.py (tool-mount) │
├───────────────────────────────────────────┤
policy │ cie/tool_policy.py — server-side, │
│ WRITE_TOOLS never registered to callers │
│ that didn't opt in │
├───────────────────────────────────────────┤
service │ cie/tools/__init__.py — ToolService, │
│ ~135 methods, one envelope for all │
├───────────────────────────────────────────┤
engine │ cie/query.py — QueryEngine over either: │
│ • Neo4jRepository (team/multi-project) │
│ • EmbeddedRepository (SQLite, zero- │
│ config) + tasks.db + hierarchy.db │
├───────────────────────────────────────────┤
ingestion │ cie/extract.py (tree-sitter, 9 languages)│
│ cie/callgraph.py (receiver-resolved calls)│
│ cie/testlink.py (TESTS edges, 4 heuristics)│
└───────────────────────────────────────────┘
-
Pass 1, structural (
cie/extract.py::extract_many): tree-sitter per language (_LANG_LOADERS, extract.py:72 — Python, JS/JSX, TS/TSX, Java, Go, Rust, C/C++ (+headers), C#). Emits FILE/FUNC/ METHOD/CLASS nodes with signatures, docstrings, decorators, imports — pluscontains/definesedges. -
Pass 2, call resolution (
cie/callgraph.py::resolve_call_edges): call sites resolved through an import map to receiver-resolved edges —a.B().m()links toB.m, not to whatevermgrep finds. Confidence-tagged (EXTRACTED/INFERRED); unresolved bases synthesizeexternal::stub nodes so inheritance queries degrade honestly. -
Pass 3, tests (
cie/testlink.py::resolve_test_edges): TESTS edges from test symbols to what they cover, four heuristics — naming convention, naming+call upgrade,@patchdotted paths, and direct calls (added 2026-08-31 after dogfooding measured 1 TESTS edge in cie's own 308-test suite; on cie itself: 1 → 562). Test-glob files andconftest.pyare never targets; still honestly not exhaustive (a test that only reaches its target via a helper gets no edge — thetest_maphint says so).
-
Embedded (zero-config):
EmbeddedRepository+ task/hierarchy SQLite files under<root>/.cie/— built bycie.factory.build_tool_service_embedded(factory.py:168). Whatcie indexwrites and the demo serves. -
Neo4j (team/multi-project):
Neo4jRepository+ shared driver, per---projectnamespaces inside one graph (nodeprojectproperty + query filters, neo4j_repository.py:451) — built bybuild_tool_service/build_tool_service_from_config. -
One selection rule, every front-end
(
cie.config.resolve_backend): explicit--backend›CIE_BACKENDenv › the--embeddedalias › auto (serve<root>/.cie/graph.dbwhen it exists, else Neo4j). The MCP server states the resolved choice on stderr at startup — never silent; stdout stays byte-clean (it is the JSON-RPC channel). Selection is orthogonal to configuration:NEO4J_*/ legacyCIE_NEO4J_*/--neo4j-*configure Neo4j however it was selected.
ToolService (cie/tools/init.py) is the only thing front-ends
talk to: ~135 methods — search, skeleton, callers/callees, impact
(affected_by), tests (test_map), traceability, freshness, tasks,
coverage, plus write tools (files/tasks/results). Every method
returns the same envelope (ok/tool/results/truncated/total/hint/ elapsed_ms), so an agent gets machine-checkable answers and honest
empty-hints instead of silence.
cie/tool_policy.py (AgentType :40, permits :110,
WRITE_TOOLS :18): policies are enforced server-side — tools a
caller may not use are never registered, not "registered then
denied". --policy readonly (what cie init registers for clients)
shows the 85 read tools; full opts into writes.
-
cie-mcp(stdio MCP for Claude Code/Cursor/Codex; also streamable-http/sse for browser clients). Startup resolves backend- policy; the same ToolService behind both transports.
-
cieCLI (click): index/load/reindex/watch/export-html + query commands through the same backend-selection rule (_selected_backend, cli.py:101). -
HTTP tool-mount (
cie/routes.py): POST/tools/{tool}per request — Neo4j mode today, per-project service cache (routes.py:127).
No provider key or network is ever required: optional deps
(mcp, neo4j, embeddings, watchdog) are checked at call time and
return unavailable[OPTIONAL_BACKEND_MISSING:...] envelopes, not
tracebacks (pinned by tests/test_optional_dependency_envelope.py).
The embeddings fallback is env-gated on explicit CIE_EMBED_DSN
- key — a bare provider key never turns the network on.
- Start:
cie/cli.py(the commands) →cie/factory.py(the two construction paths) →cie/tools/__init__.py(the surface). - The semantic layer (
cie/graphrag.py,cie/embed.py) layers lexical+dense+graph search over the same store — see How-to: enable semantic search. - In-repo evidence (benchmarks, the demo's uncut casts, production log): docs/demo and docs/benchmarks.md.
-
MCP (
cie.mcp_server/cie-mcp): real Model Context Protocol over stdio (orsse/streamable-http), built with the officialmcpSDK. Each tool's JSON Schema comes from SDK introspection of the bound method — one source of truth. Denied-by-policy tools are never registered, not merely refused. Policies:forge/orchestrator(read+write),miner/inspector(read-only). -
HTTP (
cie.routes.py):routermounted into the host FastAPI app (not a separate process).POST /tools/{tool}(kwargs in body),GET /tools,GET /health,GET /schema-version, plus dedicatedPOST /tasks,GET /tasks/{name},GET /tasks/pending,POST /hierarchy,POST /telemetry/otlp, etc. Read-only by default, enforced server-side through the sameToolPolicythe MCP path uses: write tools and mutating legacy REST routes (POST /tasks,POST /code/reload,POST /sync/event,POST /telemetry/otlp, …) are 403 (forbiddenenvelope kind) unlessCIE_HTTP_POLICY=orchestrator(orCIE_HTTP_ALLOW_WRITE=1; alsominerfor read-only-by-name). Mutating requests carrying a cross-originOriginare rejected even when writes are allowed (the CSRF-to-localhost vector), unless the origin is listed inCIE_HTTP_ALLOWED_ORIGINS.GET /toolsdiscovery is filtered to match — a read-only caller can't even see a tool it can't call. -
CLI (
cie.cli, 47 commands,cie indexincluded): human Rich tables by default; every command honors--json(group-level, before the subcommand) emitting the same SPEC §0 envelope as the HTTP surface, so an agent can drive cie entirely over JSON. Commands mirror the tools above (search,node,neighbors,community,communities,god,stats,search-symbol,view-file,callers,callees,skeleton,failing-context,affected-by,blame,run,reindex,watch,tasks:*,hierarchy:*,coverage:*,validate:*,schema-version,schema:dump, …).
If code and this wiki disagree, the code wins — then this wiki gets a PR. Evidence lives in the repo, not here.
Start
How-tos
- Install & serve to your MCP client
- Choose your storage backend
- Run the Neo4j team mode
- Index & keep it fresh
- Ask impact questions
- Task & QA layer
- Semantic search
- Snapshot or serve HTTP
- Policies & the write boundary
- Troubleshoot
Reference
- Extraction pipeline
- Data model
- Tool reference
- Benchmarks
- Security & determinism
- Two tiers · Project layout · Docs index
Contribute