Skip to main content
skill CX Codex official · Google

mantis-structural-index — Claude Code skill: what it does

Builds an index of semantic code units and answers "who calls this function" and "where does the body end". Useful before an agent starts reading an unfamiliar codebase.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add google/mantis --skill mantis-structural-index -a claude-code
Open on GitHub

What mantis-structural-index does

The skill walks every source file under CODE_ROOT, splits the code into independent semantic units and keys each one by its content. The result is a manifest plus a catalog.sqlite catalog, with two scripts in workspace/helpers/: build_structural_index.py builds, query_structural_index.py answers. Queries cover find_callers(symbol) and get_function_boundary(file, line), so call sites and body edges reach the agent as data instead of being guessed from grep output. When no backend fits a partition, it falls back to grep and keeps going.

It needs CODE_ROOT and SNAPSHOT_ID. Both are optional: pass nothing and it reads the current directory, sets the snapshot id to unknown and writes to ./workspace/kb/structural_index/. Rerunning against the same snapshot reuses the existing manifest rather than rebuilding, and unchanged units carry over between snapshots by content key. CODE_ROOT stays read-only.

Until the harness, mantis-plan or mantis-researcher invokes it, the skill returns an empty index. Silently. That is worth checking in your call chain, because otherwise the agent reasons with no structural data at all.

What it helps with

  • find every call site of a function before editing it
  • hand the agent function boundaries instead of a line range
  • rebuild only the units whose content changed
  • index a codebase that has no pinned snapshot
  • attach a prebuilt index in SCIP, Kythe or LSIF format

Example prompts for the agent

  • /mantis-structural-index build the index for the active snapshot
  • build the structural index, then run find_callers(<symbol>) and list the call sites
  • what does this function span? use get_function_boundary(<file>, <line>)

How to install mantis-structural-index

1Skills CLI (Vercel)

npx skills add google/mantis --skill mantis-structural-index -a claude-code

2Manually: put the files in place

~/.claude/skills/mantis-structural-index/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/mantis-structural-index/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about mantis-structural-index

How do I install mantis-structural-index in Claude Code?

Run npx skills add google/mantis --skill mantis-structural-index -a claude-code. The skill file lands at ~/.claude/skills/mantis-structural-index/SKILL.md.

What does mantis-structural-index do?

It turns source code into an index of semantic units and serves it through a bounded query helper: who calls a symbol, and where a function body starts and stops. Storage is a manifest plus catalog.sqlite.

Does the skill work without a snapshot?

Yes. With no --snapshot_root, --snapshot_id or --state_root it runs in MODE-OFF: reads the current directory, writes to ./workspace/kb/structural_index/ and rebuilds on every run.

Source text

SKILL.md · Apache-2.0
---
name: mantis-structural-index
description: >-
  Builds a content-addressed semantic-unit index from source code for
  structural context. Use when a pinned or live codebase is available and
  structural cross-reference data would improve research quality. Don't use for
  findings analysis, patching, or reporting.
---

# Structural Code Index Builder

This is an **optional first-class stage** in the Pass Lifecycle Contract. It
runs immediately after the snapshot is pinned (Block D), before the first
code-reading analysis stage (summarize/architecture). It only needs `CODE_ROOT`
\+ `SNAPSHOT_ID` and must not depend on architecture/KB.

## System Goal

Structural Code Index Builder. Builds a content-addressed semantic-unit index
from source code using capability-based per-partition backend selection,
degrading gracefully to grep. Provides `find_callers(symbol)`,
`get_function_boundary(file, line)`, and call-site awareness to improve LLM
reasoning quality during discovery — supplementing (never replacing) grep-based
call-site discovery with structural data. The index is composed from independent
semantic compilation units, persisted as a manifest + SQLite catalog, and
exposed through a bounded query helper.

## Command Definition

- **Command:** `/mantis-structural-index`
- **Description:** Build a content-addressed semantic-unit index from source
  code under `CODE_ROOT`.
- **Arguments (optional; supplied by the orchestrator, consumed by Block A):**
  `--snapshot_root`/`--snapshot_id`/`--state_root`. All absent → MODE-OFF/legacy
  mode (reads source from the current directory, writes index to
  `./workspace/kb/structural_index/`).

## Input/Output Contract

- **Reads**:
  - `workspace/.mantis_state.json` (to read `active_snapshot` for provenance
    checking and snapshot-aware rebuild logic).
  - `workspace/kb/structural_index/manifest.json` (to check `snapshot_id` for
    reuse-on-match idempotency — primary check).
  - `workspace/kb/structural_index.jsonl` (backward-compat provenance check if
    manifest absent).
  - `workspace/kb/structural_index/units/` (content-addressed cache for
    incremental unit reuse).
  - `workspace/kb/structural_index/native/` and sidecar `provenance.json` files
    (prebuilt index attachments and metadata manifests).
  - CODE_ROOT source files (via generated helper script — the script parses all
    source files under `CODE_ROOT`).
- **Writes**:
  - `workspace/kb/structural_index/manifest.json` (STATE-RELATIVE — atomic
    commit point, written LAST).
  - `workspace/kb/structural_index/catalog.sqlite` (STATE-RELATIVE —
    query-optimized serving store).
  - `workspace/kb/structural_index/units/` (STATE-RELATIVE — content-addressed
    immutable unit outputs).
  - `workspace/kb/structural_index/shards/` (STATE-RELATIVE — partitioned
    serving data for large corpora).
  - `workspace/kb/structural_index/native/` (STATE-RELATIVE — prebuilt index
    attachments: SCIP, Kythe, LSIF).
  - `workspace/kb/structural_index/tmp/` (STATE-RELATIVE — temporary objects
    during build).
  - `workspace/kb/structural_index.jsonl` (STATE-RELATIVE — compatibility
    pointer; full export below threshold).
  - `workspace/helpers/build_structural_index.py` (STATE-RELATIVE — the builder
    helper script).
  - `workspace/helpers/query_structural_index.py` (STATE-RELATIVE — the query
    helper script).
- **Preconditions**:
  - Source files must exist under `CODE_ROOT`. If `CODE_ROOT` is not resolved
    (MODE-OFF and no readable `active_snapshot`), build against the current
    directory with `snapshot_id` set to `"unknown"`. Do NOT skip — this is the
    standalone-efficiency case.
- **Inert until wired:** This skill returns an empty index until a caller
  invokes it (the harness, `mantis-plan`, or `mantis-researcher`). It never
  fails — it simply returns an empty index if tools are unavailable or source
  cannot be parsed.
- **Idempotency Guarantee**:
  - Read-only on `CODE_ROOT`. Writes only to STATE-RELATIVE paths. Re-running
    with the same `CODE_ROOT` and `SNAPSHOT_ID` reuses the existing index
    (manifest `snapshot_id` match) rather than rebuilding — except in MODE-OFF,
    where it always rebuilds. Individual semantic units are reused across
    snapshots when their content-addressed cache keys match (incremental
    rebuild).

## Instructions

### Step 0: Locator Resolution (run first)

```
LOCATOR RESOLUTION (before reading ANY target code or artifact):
0. ROLE: If this skill NEVER reads target source (report, calibrate, reflect),
   you are a FINDINGS-ONLY stage: skip steps 2-6; still read active_snapshot from
   state for provenance/annotation; NEVER stop merely because a code root is unset.
1. Determine CODE_ROOT, in this priority order:
   a. If --target_root is passed on THIS invocation, CODE_ROOT = --target_root.
      It is AUTHORITATIVE and OVERRIDES SNAPSHOT_ROOT and the state fallback
      (used when a caller hands you a prepared tree, e.g. a patched shadow).
   b. Else if --snapshot_root (or SNAPSHOT_ROOT) is passed, use it.
   c. Else read state_root/workspace/.mantis_state.json (state_root from
      --state_root if passed, else ./workspace/... relative to the current dir)
      -> active_snapshot.root / .snapshot_id / .snapshot_pinned.
   d. Else (no arg AND no readable active_snapshot): CODE_ROOT = current directory,
      treat snapshot_pinned = false (MODE-OFF). Do NOT stop.
2. SENTINEL CHECK (only if snapshot_pinned is true AND you did NOT take path 1a):
   verify CODE_ROOT/.mantis_snapshot_id exists and equals SNAPSHOT_ID. If missing
   or different -> STOP "snapshot sentinel mismatch". (A --target_root tree (1a) is
   deliberately mutated and is sentinel-EXEMPT.)
3. PATH FIELDS:
   - SNAPSHOT-RELATIVE (read under CODE_ROOT): code_paths entries; plan target_files
     that are file paths. Strip ONLY a trailing ":<digits>". A code_paths entry
     containing "://" is a URL/endpoint, NOT a file read. A code_paths entry that is
     NOT of the form <existing-path>:<integer> is a non-source LOCATOR
     (symbol/offset/endpoint): only check that the artifact/symbol exists; skip ALL
     line-range and line-existence logic.
   - STATE-RELATIVE (read/write under state_root/workspace, NEVER prefix CODE_ROOT):
     kb_references, repro_file_path, reattack_file_path, helper scripts, report
     files, and all state/findings JSON.
4. Never WRITE under CODE_ROOT when snapshot_pinned is true. Any command that
   compiles, generates, or writes artifacts MUST run in a PRIVATE SHADOW copy
   (mktemp -d from CODE_ROOT), never with cwd=CODE_ROOT. Read-only inspection may
   cd into CODE_ROOT.
5. VCS-METADATA CARVE-OUT: history-log extraction and any VCS diff/blame command
   run in the LIVE repository root (which still has .git/.hg/.repo), NOT CODE_ROOT
   (the snapshot copy strips VCS metadata). Do NOT stop merely because CODE_ROOT
   lacks .git/.hg/.repo.
6. Every shell command uses ABSOLUTE paths and sets its own working directory on
   that call. Do NOT assume the working directory persists between calls.
```

This is a CODE-READING stage — it reads target source files under CODE_ROOT via
the helper script. Block A step 0's findings-only skip does NOT apply.

### Step 1: Idempotency / Freshness Check (MANDATORY FIRST)

**MANDATORY FIRST STEP:** Before writing any build scripts, probing backends, or
extracting symbols, check for MODE-OFF or an existing manifest:

1. **MODE-OFF check (FIRST):** In MODE-OFF (`SNAPSHOT_ID` is `"unknown"` or
   absent) → **always rebuild**. Do NOT reuse a previous `"unknown"` index,
   because the live tree is mutable and `"unknown"` is a constant (not a
   freshness signal). Skip directly to Step 2 (individual units whose content
   has not changed may still hit the content-addressed cache).
2. If `workspace/kb/structural_index/manifest.json` exists, read its
   `snapshot_id` field.
3. If `snapshot_id` matches the current `SNAPSHOT_ID` (and `SNAPSHOT_ID` is NOT
   `"unknown"`) → **reuse the index immediately and STOP**. Do NOT invoke Step
   2, probe backends, or write build scripts. This bounds cost across
   retries/crash-resume.
4. If `manifest.json` is absent but `workspace/kb/structural_index.jsonl` exists
   (backward compat), read its provenance header (`_provenance`, `snapshot_id`
   keys). If `snapshot_id` matches (and is NOT `"unknown"`) → **reuse and
   STOP**. Otherwise proceed to rebuild.
5. If `SNAPSHOT_ID` differs → proceed to rebuild (Steps 2–5). For incremental
   reuse: before rebuilding a semantic unit, check `units/` for a
   content-addressed cache hit (see Content-Addressed Cache Key below). A cache
   hit reuses the unit output without re-extraction.

### Step 2: Select Backend per Partition

Partition the codebase into semantic units (see Per-Language Semantic Units
below). For each unit, select the most precise backend available in this
environment using a **capability-based, per-partition decision** — not a single
global ladder. The following are **examples, not an exhaustive enum**. The
helper probes each tier per partition and selects the highest available:

1. **Snapshot-matched SCIP / LSIF / Kythe / clangd-static / remote index**
   (precision: `semantic`) — if a pre-built index matching the current
   `snapshot_id` or `root_fingerprint` is available. Most precise: full
   type-aware cross-reference, call hierarchy, and hover/signature data.
2. **Compiler / typechecker-backed extractor** (precision: `typecheck`) — if
   `compile_commands.json`, build context, or typechecker is available.
   Type-accurate symbol resolution and call edges.
3. **Language-aware AST extraction** (precision: `ast`) — if `tree-sitter`,
   `ast-grep`, or a language-specific parser is available. Full AST parsing:
   function boundaries, call expressions, signatures.
4. **Symbol-only extraction** (precision: `symbol-only`) — if `ctags` or
   equivalent is on `PATH`. Symbol table only (function definitions, locations —
   no call graph). Call-site extraction uses a lightweight regex pass within
   known function boundaries.
5. **Heuristic fallback** (precision: `heuristic`) — Python stdlib regex pass
   over source files. Identifies function definitions and call patterns using
   language-agnostic heuristics. Less precise but zero-dependency.
6. **Coverage-only manifest + lexical fallback** (precision: `coverage-only`) —
   grep; no structural index is written. Manifest records `status: "empty"`.
   Consumers fall back to grep-based discovery (today's behavior byte-for-byte).

**Native Index Probing & Resolution Rules**:

- **Probe instruction**: Before evaluating per-partition backends, probe
  `workspace/kb/structural_index/native/` and subdirectories
  `native/{scip,lsif,kythe}/` for prebuilt index files.
- **Snapshot-declaration convention**: Because native formats (SCIP, LSIF,
  Kythe) do not embed snapshot identity directly in their binary payload,
  prebuilt indexes MUST declare their target snapshot using a sidecar
  `provenance.json` manifest located at
  `workspace/kb/structural_index/native/provenance.json` or
  `native/<kind>/provenance.json`. The manifest contains an array of
  attachments:
  `[{"kind": "scip|lsif|kythe", "path": "...", "snapshot_id": "...", "root_fingerprint": "...", "language": "...", "indexer": "...", "precision": "semantic", "files": [...]}]`.
  A native index is matched if its declared `snapshot_id` equals `SNAPSHOT_ID`
  (when `SNAPSHOT_ID != "unknown"`) or its `root_fingerprint` matches the
  workspace's calculated root fingerprint. If the `files` array is absent or
  empty, treat the native index as covering no individual files directly (record
  in `manifest.native_indexes` but do not update `coverage` rows; fall through
  to lower tiers for all files).
- **Record-and-Defer Ingestion Rule**: Parsing raw binary native indexes (e.g.
  SCIP protobuf) in pure Python without dependencies is costly and complex.
  Option A builder scripts MUST detect matching prebuilt native indexes, record
  their entries in `manifest.native_indexes`, and set the `coverage` table
  `backend` (e.g., `"scip"` or `"scip-clangd"`) for all files listed in the
  provenance manifest. If `catalog.sqlite` is NOT populated with symbols from
  the native index (raw binary deferred to harness/MCP readers), set
  `coverage.status = "deferred"` and `precision = "deferred"` (with
  `indexed_files = 0`). This prevents the query helper from claiming an
  un-ingested partition is "authoritative empty" at `semantic` precision,
  ensuring consumers run the mandatory grep fallback. When `catalog.sqlite` IS
  populated (e.g., via Option B pre-ingestion or `scip-to-sqlite`), set
  `precision = "semantic"` and `status = "indexed"`.

**LSP is NOT equivalent to SCIP / LSIF.** LSP is an interactive protocol whose
workspace state may be partial or mutable. Use it only when the server can
demonstrate snapshot identity AND complete workspace coverage. A running
language server does not automatically qualify as a `semantic` backend.

**SCIP merging.** SCIP explicitly permits merging complementary information from
indexers with different precision levels. Its format records indexer version and
per-document language metadata. The builder MAY merge results from multiple
indexers (e.g., a SCIP index for Go + tree-sitter for Python) within a single
catalog.

The determinism lives in a runtime-generated versioned helper
(`build_structural_index.py`, `# MANTIS_HELPER_VERSION = 5`, grep-and-regenerate
on reuse) that probes and selects backends per partition. No shipped binaries;
air-gapped-safe.

**Deterministic partial coverage.** On very large source trees, a full rebuild
can dominate stage-0 wall-clock. Replace discovery-order truncation with a
deterministic priority queue:

1. **Explicit target files and symbols** (from `plan.json`, if available).
2. **Changed units and known reverse dependencies** (from Block E diff +
   reverse-dependency edges in the existing catalog).
3. **Containing packages/modules and direct imports** of tier 1+2 units.
4. **Remaining units in normalized path order**.

Apply deterministic `max_units` (default: 10000) or `max_source_bytes` (default:
500MB) bounds to that queue. Persist deferred units in the manifest so another
invocation can resume. Statuses: `complete`, `partial`, `empty`, `failed`.

A consumer should only interpret "no callers" strongly when the relevant
partition is complete, snapshot-matched, covered, and semantically precise.
Otherwise the answer is "no indexed callers", followed by the existing lexical
fallback. The index is HINT-only, so partial coverage is safe (grep remains
authoritative).

### Step 3: Write and Run Helper Scripts

Two runtime-generated helpers are used. Both follow the grep-and-regenerate
pattern: before reuse, grep the first line for the version marker; if absent or
a different integer, REGENERATE.

#### Builder: `build_structural_index.py`

1. Write the builder to `workspace/helpers/build_structural_index.py`. The FIRST
   LINE MUST be exactly `# MANTIS_HELPER_VERSION = 5`. Before reusing an
   existing helper, grep its first lines for `MANTIS_HELPER_VERSION = 5`; if
   that marker is absent or a different integer, REGENERATE the helper.
2. The builder partitions the codebase into semantic units (see Per-Language
   Semantic Units), computes content-addressed cache keys, checks `units/` for
   reuse, selects a backend per partition (Step 2), extracts symbols + edges,
   and writes results to `catalog.sqlite`.
3. The builder must extract:
   - **Symbols**: `symbol_id`, `name`, `qualified_name`, `namespace`,
     `language`, `file_path`, `start_line`, `end_line`, `kind`, `signature`,
     `backend`, `precision`.
   - **Call edges**: `caller_id`, `callee_id`, `callee_name`, `file_path`,
     `line`, `edge_kind`.
   - **Function boundaries**: `symbol_id`, `file_path`, `start_line`,
     `end_line`, `signature`, `language`.
   - **Coverage**: `file_path`, `indexed`, `backend`, `precision`,
     `unit_cache_key`, `status`.
4. The builder writes the manifest LAST (Step 4) and the compatibility pointer.
5. The builder MUST use ABSOLUTE paths and set its own working directory (Block
   A step 6). It MUST NOT write anything under `CODE_ROOT` when
   `snapshot_pinned` is true (Block A step 4). Backends that produce sidecar
   files (ctags `tags`, cscope `cscope.out`, clangd cache) MUST be redirected to
   STATE-RELATIVE paths: `ctags -f <state>/helpers/tags`,
   `cscope -f <state>/helpers/cscope.out`,
   `CLANGD_INDEX_STORAGE=<state>/helpers/`. Read-only LSP/SCIP queries to a
   running server need no redirect.

#### Query helper: `query_structural_index.py`

1. Write the query helper to `workspace/helpers/query_structural_index.py`. The
   FIRST LINE MUST be exactly `# MANTIS_HELPER_VERSION = 5`. Before reusing an
   existing helper, grep its first lines for `MANTIS_HELPER_VERSION = 5`; if
   absent or a different integer, REGENERATE.
2. The query helper provides bounded, paginated operations against
   `catalog.sqlite` (or a remote endpoint — identical API). It IS the
   consumption contract (see Query Interface below).
3. If `catalog.sqlite` is absent but `structural_index.jsonl` exists, the query
   helper falls back to linear scanning of the JSONL file (slower but
   functional). If both are absent, it returns empty results with
   `coverage.partition_status = "empty"`.

### Step 4: Write Manifest and Commit

1. Write the manifest to `workspace/kb/structural_index/tmp/manifest.json`
   first. Then atomically rename it to
   `workspace/kb/structural_index/manifest.json`. This is the atomic commit
   point — the manifest is written LAST, after all units, catalog, and coverage
   data are written.
2. Write a compatibility pointer to `workspace/kb/structural_index.jsonl`
   (STATE-RELATIVE — NEVER under `CODE_ROOT`). Below a configurable threshold
   (default: 10K records), emit a complete JSONL export (provenance header + all
   records). Above the threshold, emit only a provenance header with
   `compat_pointer.full_export = false` and `compat_pointer.symbol_count` set.
   Large consumers MUST use the query interface.
3. The manifest `provider.backend_versions` field records which backend was used
   per language (e.g.,
   `{"go": {"backend_name": "scip-clangd", "precision": "semantic"}, "python": {"backend_name": "tree-sitter", "precision": "ast"}}`),
   so consumers know the precision level.
4. Interrupted builds leave unreferenced temp objects in `tmp/` without
   corrupting the last published index. On resume, check `manifest.json` status
   and `snapshot_id` (Step 1).

### Step 5: Return Results / Notify Caller

1. Return the path to `manifest.json`, `catalog.sqlite`, and the query helper.
   Include a summary (unit count, symbol count, call-edge count, backends used,
   coverage status).
2. If the index is empty (no tools available or no source files found), notify
   the caller: "Structural index is empty — structural context unavailable."
3. Do not notify the user directly — this skill is invoked as a sub-agent by the
   harness, planner, or researcher.

## Index Schema and On-Disk Contract

### On-Disk Layout

```
workspace/kb/structural_index/
├── manifest.json          # Atomic commit point — written LAST
├── catalog.sqlite         # Query-optimized serving store (both directions indexed)
├── units/                 # Content-addressed immutable unit outputs
│   └── ab/cd/abcdef...    # sha256 prefix sharding (2+2 hex dirs)
├── shards/                # Partitioned serving data (large corpora)
│   └── shard_0000.sqlite
├── native/                # Prebuilt index attachments (SCIP, Kythe, LSIF)
│   ├── provenance.json    # Prebuilt index provenance manifest
│   ├── scip/
│   └── kythe/
└── tmp/                   # Temporary objects during build

workspace/helpers/
├── build_structural_index.py    # Builder (MANTIS_HELPER_VERSION = 5)
└── query_structural_index.py    # Query helper (MANTIS_HELPER_VERSION = 5)

workspace/kb/structural_index.jsonl  # Compatibility pointer
```

### Manifest Schema (`manifest.json`)

```json
{
  "schema_version": 1,
  "snapshot_id": "<SNAPSHOT_ID or 'unknown'>",
  "root_fingerprint": "<sha256 of sorted (path, content_sha256) for all source files>",
  "status": "complete|partial|empty|failed",
  "provider": {
    "kind": "local-build|baseline+overlay|remote",
    "catalog": "catalog.sqlite",
    "backend_versions": {
      "cpp": {"backend_name": "tree-sitter", "backend_version": "0.20.8", "precision": "ast"},
      "go": {"backend_name": "scip-clangd", "backend_version": "0.2.3", "precision": "semantic"}
    }
  },
  "units": {"total": 0, "reused": 0, "rebuild": 0, "failed": 0},
  "coverage": {"total_files": 0, "indexed_files": 0, "failed_files": 0, "deferred_files": 0},
  "shards": [{"id": "", "path": "", "checksum": "", "partition_key": "", "symbol_count": 0, "edge_count": 0}],
  "deferred_units": [{"unit_id": "", "language": "", "files": [], "priority": 4, "reason": ""}],
  "native_indexes": [{"kind": "scip", "path": "", "snapshot_id": "", "root_fingerprint": "", "language": "", "indexer": "", "precision": ""}],
  "baseline": {"source": "ci|local|none", "snapshot_id": "", "manifest_path": ""},
  "overlay": {"units_added": 0, "units_modified": 0, "files": []},
  "compat_pointer": {"path": "structural_index.jsonl", "full_export": true, "symbol_count": 0},
  "created_at": "<ISO 8601>",
  "build_duration_ms": 0
}
```

**Atomic commit**: The manifest is written LAST (atomic rename from `tmp/`).
Interrupted builds leave unreferenced temp objects without corrupting the last
published index.

### SQLite Catalog Schema (`catalog.sqlite`)

```sql
CREATE TABLE IF NOT EXISTS schema_meta (
    key TEXT PRIMARY KEY, value TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS symbols (
    symbol_id       TEXT PRIMARY KEY,
    name            TEXT NOT NULL,
    qualified_name  TEXT NOT NULL,
    namespace       TEXT,
    language        TEXT NOT NULL,
    file_path       TEXT NOT NULL,
    start_line      INTEGER NOT NULL,
    end_line        INTEGER,
    kind            TEXT NOT NULL,
    signature       TEXT,
    backend         TEXT NOT NULL,
    precision       TEXT NOT NULL CHECK (precision IN ('semantic','typecheck','ast','symbol-only','heuristic','deferred','coverage-only')),
    corpus          TEXT DEFAULT 'default',
    partition_key   TEXT,
    unit_cache_key  TEXT,
    source_layer    TEXT NOT NULL DEFAULT 'baseline'
);

CREATE TABLE IF NOT EXISTS call_edges (
    edge_id     INTEGER PRIMARY KEY AUTOINCREMENT,
    caller_id   TEXT NOT NULL,
    callee_id   TEXT,
    callee_name TEXT NOT NULL,
    file_path   TEXT NOT NULL,
    line        INTEGER NOT NULL,
    edge_kind   TEXT NOT NULL CHECK (edge_kind IN ('direct','indirect','virtual','macro','unresolved')),
    corpus      TEXT DEFAULT 'default',
    partition_key TEXT,
    source_layer TEXT NOT NULL DEFAULT 'baseline',
    FOREIGN KEY (caller_id) REFERENCES symbols(symbol_id),
    FOREIGN KEY (callee_id) REFERENCES symbols(symbol_id)
);

CREATE TABLE IF NOT EXISTS function_boundaries (
    symbol_id   TEXT PRIMARY KEY,
    file_path   TEXT NOT NULL,
    start_line  INTEGER NOT NULL,
    end_line    INTEGER NOT NULL,
    signature   TEXT,
    language    TEXT NOT NULL,
    FOREIGN KEY (symbol_id) REFERENCES symbols(symbol_id)
);

CREATE TABLE IF NOT EXISTS coverage (
    file_path   TEXT NOT NULL,
    indexed     INTEGER NOT NULL DEFAULT 0,
    backend     TEXT,
    precision   TEXT,
    unit_cache_key TEXT,
    status      TEXT NOT NULL DEFAULT 'pending',
    corpus      TEXT DEFAULT 'default',
    partition_key TEXT,
    PRIMARY KEY (file_path, corpus)
);

CREATE TABLE IF NOT EXISTS unit_cache (
    cache_key       TEXT PRIMARY KEY,
    unit_id         TEXT NOT NULL,
    language        TEXT NOT NULL,
    extractor_name  TEXT NOT NULL,
    extractor_version TEXT NOT NULL,
    file_count      INTEGER NOT NULL,
    symbol_count    INTEGER NOT NULL,
    edge_count      INTEGER NOT NULL,
    source_bytes    INTEGER NOT NULL,
    created_at      TEXT NOT NULL,
    snapshot_id     TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS deferred_units (
    unit_id     TEXT PRIMARY KEY,
    language    TEXT NOT NULL,
    files       TEXT NOT NULL,
    priority    INTEGER NOT NULL,
    reason      TEXT NOT NULL,
    cache_key   TEXT,
    created_at  TEXT NOT NULL
);

-- Indexes: both directions!
CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
CREATE INDEX IF NOT EXISTS idx_symbols_qualified ON symbols(qualified_name);
CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path, start_line);
CREATE INDEX IF NOT EXISTS idx_symbols_lang ON symbols(language);
CREATE INDEX IF NOT EXISTS idx_edges_caller ON call_edges(caller_id);
CREATE INDEX IF NOT EXISTS idx_edges_callee ON call_edges(callee_id);
CREATE INDEX IF NOT EXISTS idx_edges_callee_name ON call_edges(callee_name);
CREATE INDEX IF NOT EXISTS idx_edges_file ON call_edges(file_path, line);
CREATE INDEX IF NOT EXISTS idx_boundaries_file ON function_boundaries(file_path, start_line, end_line);
CREATE INDEX IF NOT EXISTS idx_coverage_status ON coverage(status);
CREATE INDEX IF NOT EXISTS idx_deferred_priority ON deferred_units(priority);
```

**Partitioning**: When symbols exceed 500K or edges exceed 2M, partition into
shard SQLite files under `shards/` by `lang:{language}:bucket:{NN}` (stable
hash). Each shard has the same schema. The query helper routes queries to the
correct shard(s) based on the manifest.

### Content-Addressed Cache Key

```python
cache_key = sha256(
    schema_version +
    extractor_name@version +
    language +
    compile_context_digest +
    ordered_input_content_digests +
    dependency_or_interface_digest
)
```

**Critical**: `snapshot_id` is NOT in the cache key. It goes in provenance only.
This permits reuse across commits, branches, and snapshots with identical units.

### Canonical Symbol IDs

**Native IDs** (when semantic backend is available):

- SCIP: `scip:{symbol}`
- Kythe: `kythe:{uri}`
- clangd: `clangd:{usr}`

**Fallback IDs** (when no semantic backend):

```
fallback:{language}:{file_path}:{sha256(qualified_name|start_line|signature)[:16]}
```

This distinguishes namespaces, overloads, methods, local functions, and
duplicate names across languages or repositories.

### Edge Kinds

Edge kinds are kept explicitly separate — never silently merged:

- `direct` — statically resolved call to a known symbol
- `indirect` — function pointer, closure, callback
- `virtual` — virtual method dispatch (runtime-resolved)
- `macro` — call introduced by macro expansion
- `unresolved` — callee name found but no symbol_id resolved

### Per-Language Semantic Units

| Language    | Unit Type        | Compile Context             | Dependency Digest                           |
| ----------- | ---------------- | --------------------------- | ------------------------------------------- |
| C/C++       | Compilation unit | compile_commands.json entry | sha256 of transitive header interfaces      |
| Go          | Package          | go.mod + build tags         | sha256 of imported packages' exported API   |
| Rust        | Crate            | Cargo.toml + features       | sha256 of extern crate signatures           |
| Java/Kotlin | Compiler batch   | module path + classpath     | sha256 of imported class signatures         |
| TypeScript  | Project          | tsconfig.json               | sha256 of imported module type declarations |
| Fallback    | Individual file  | None                        | Empty string                                |

**Fallback Granularity Rule:** For the fallback tier (regex, AST parser, or
lightweight crawler without a language build system), **each source file MUST be
its own independent semantic unit**. Never bundle multiple source files into a
single fallback unit. Per-file unit isolation is essential for content-addressed
cache efficiency — when one file changes in a future snapshot, only that single
file's unit invalidates while all unchanged files hit the cache
(`reused = N-1`).

### Baseline + Delta Overlay

- **Static baseline**: Built once in CI for the complete snapshot.
- **Incremental cache**: Immutable unit outputs reused across snapshots.
- **Delta overlay**: Changed/target files indexed locally, layered over
  baseline.
- **Remote serving**: Optional when global index is too costly locally.

At query time, the query helper merges baseline + overlay results. Overlay
symbols supersede baseline symbols for the same file. The manifest records
`provider.kind = "baseline+overlay"` with baseline manifest reference and
overlay unit count.

### Compatibility JSONL Export

The old `structural_index.jsonl` format is retained as a compatibility export
only — it is NOT the query contract. Below the threshold (default 10K records),
a complete JSONL export is emitted. Above the threshold, only a provenance
pointer is written. Large consumers MUST use the query interface.

Line 1 — provenance header:

```json
{"_provenance": true, "snapshot_id": "abc123", "tool": "tree-sitter"}
```

Lines 2+ — structural records (one per line, `_type` discriminator):

```json
{"_type": "function", "key": "src/parser.c:parse_input", "start_line": 45, "end_line": 120, "signature": "int parse_input(char *buf, size_t len)", "calls": ["malloc", "validate_input", "memcpy"]}
{"_type": "call_edge", "caller": "parse_input", "callee": "malloc", "file": "src/parser.c", "line": 78}
{"_type": "call_edge", "caller": "main", "callee": "parse_input", "file": "src/main.c", "line": 203}
```

**Provenance header fields:**

| Field         | Type   | Description                                                                         |
| ------------- | ------ | ----------------------------------------------------------------------------------- |
| `_provenance` | bool   | Always `true` — marks this as the provenance header line                            |
| `snapshot_id` |...

© google · Apache-2.0 · google/mantis

Similar skills

All skills for Agent setup: memory, context, orchestration →

From the same set19

mantis-plan Generates a targeted security review plan based on your threat model and historical findings. Run at the start of a review campaign to map code boundaries and prioritize what to check. CX Codex +1 1,600installs mantis-summarize Builds a security-focused codebase map with mantis-summary.md files in each directory. Reduces token overhead during planning and research. CX Codex +1 1,600installs mantis-architecture Turns codebase analysis and collected learnings into an interlinked Markdown knowledge base. Use it at the start of an architecture and vulnerability review loop. CX Codex +1 1,600installs mantis-review Reviews findings against the actual source code and drops false positives. Useful once a scan has produced a list of bugs that needs cleaning up. CX Codex +1 1,600installs mantis-meta-agent A campaign supervisor for Mantis security review: it keeps the loop running pass after pass and holds state and snapshots. Built for reviews that run long unattended. CX Codex +1 1,500installs mantis-reflect Extracts insights, failures, and false assumptions from Mantis agent transcripts, appending them to workspace/learnings.jsonl. CX Codex +1 1,500installs mantis-chain Stacks separate validated findings into multi-step exploit chains, so a handful of low-severity bugs can add up to one serious scenario. CX Codex +1 1,500installs mantis-pipeline-adapter A consulting skill that walks you through building your own deterministic harness for running Mantis skills. CX Codex +1 1,500installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor