[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6992QB4DVMDR2AGT6BPSE3N":3,"skill-same:01M6992QB4DVMDR2AGT6BPSE3N":73},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":49,"commitSha":50,"skillDirectory":51,"forksCount":52,"repositoryHtmlUrl":53,"repositoryHomepageUrl":54,"ownerHtmlUrl":55,"ownerBlogUrl":56,"ownerDescription":57,"updatedAt":58,"install":59,"resources":72},"01M6992QB4DVMDR2AGT6BPSE3N","mantis-structural-index",null,"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.","google\u002Fmantis","google","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F1342004?v=4","01M698VYEFDM0VJBZDYYJ2T83S","Google","01M6990SDGGFEAP21TJNS7HEG9",2778,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fmantis\u002Fblob\u002Fmain\u002Fskills\u002Fmantis-structural-index\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"index","structural","mantis structural index","quality","when","for","unit","live","reference","research quality","use","context","improve","patching","analysis","data","reporting","content","code","research","2026-11-10 10:12:30",false,"---\nname: mantis-structural-index\ndescription: >-\n  Builds a content-addressed semantic-unit index from source code for\n  structural context. Use when a pinned or live codebase is available and\n  structural cross-reference data would improve research quality. Don't use for\n  findings analysis, patching, or reporting.\n---\n\n# Structural Code Index Builder\n\nThis is an **optional first-class stage** in the Pass Lifecycle Contract. It\nruns immediately after the snapshot is pinned (Block D), before the first\ncode-reading analysis stage (summarize\u002Farchitecture). It only needs `CODE_ROOT`\n\\+ `SNAPSHOT_ID` and must not depend on architecture\u002FKB.\n\n## System Goal\n\nStructural Code Index Builder. Builds a content-addressed semantic-unit index\nfrom source code using capability-based per-partition backend selection,\ndegrading gracefully to grep. Provides `find_callers(symbol)`,\n`get_function_boundary(file, line)`, and call-site awareness to improve LLM\nreasoning quality during discovery — supplementing (never replacing) grep-based\ncall-site discovery with structural data. The index is composed from independent\nsemantic compilation units, persisted as a manifest + SQLite catalog, and\nexposed through a bounded query helper.\n\n## Command Definition\n\n- **Command:** `\u002Fmantis-structural-index`\n- **Description:** Build a content-addressed semantic-unit index from source\n  code under `CODE_ROOT`.\n- **Arguments (optional; supplied by the orchestrator, consumed by Block A):**\n  `--snapshot_root`\u002F`--snapshot_id`\u002F`--state_root`. All absent → MODE-OFF\u002Flegacy\n  mode (reads source from the current directory, writes index to\n  `.\u002Fworkspace\u002Fkb\u002Fstructural_index\u002F`).\n\n## Input\u002FOutput Contract\n\n- **Reads**:\n  - `workspace\u002F.mantis_state.json` (to read `active_snapshot` for provenance\n    checking and snapshot-aware rebuild logic).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fmanifest.json` (to check `snapshot_id` for\n    reuse-on-match idempotency — primary check).\n  - `workspace\u002Fkb\u002Fstructural_index.jsonl` (backward-compat provenance check if\n    manifest absent).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Funits\u002F` (content-addressed cache for\n    incremental unit reuse).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fnative\u002F` and sidecar `provenance.json` files\n    (prebuilt index attachments and metadata manifests).\n  - CODE_ROOT source files (via generated helper script — the script parses all\n    source files under `CODE_ROOT`).\n- **Writes**:\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fmanifest.json` (STATE-RELATIVE — atomic\n    commit point, written LAST).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fcatalog.sqlite` (STATE-RELATIVE —\n    query-optimized serving store).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Funits\u002F` (STATE-RELATIVE — content-addressed\n    immutable unit outputs).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fshards\u002F` (STATE-RELATIVE — partitioned\n    serving data for large corpora).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fnative\u002F` (STATE-RELATIVE — prebuilt index\n    attachments: SCIP, Kythe, LSIF).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Ftmp\u002F` (STATE-RELATIVE — temporary objects\n    during build).\n  - `workspace\u002Fkb\u002Fstructural_index.jsonl` (STATE-RELATIVE — compatibility\n    pointer; full export below threshold).\n  - `workspace\u002Fhelpers\u002Fbuild_structural_index.py` (STATE-RELATIVE — the builder\n    helper script).\n  - `workspace\u002Fhelpers\u002Fquery_structural_index.py` (STATE-RELATIVE — the query\n    helper script).\n- **Preconditions**:\n  - Source files must exist under `CODE_ROOT`. If `CODE_ROOT` is not resolved\n    (MODE-OFF and no readable `active_snapshot`), build against the current\n    directory with `snapshot_id` set to `\"unknown\"`. Do NOT skip — this is the\n    standalone-efficiency case.\n- **Inert until wired:** This skill returns an empty index until a caller\n  invokes it (the harness, `mantis-plan`, or `mantis-researcher`). It never\n  fails — it simply returns an empty index if tools are unavailable or source\n  cannot be parsed.\n- **Idempotency Guarantee**:\n  - Read-only on `CODE_ROOT`. Writes only to STATE-RELATIVE paths. Re-running\n    with the same `CODE_ROOT` and `SNAPSHOT_ID` reuses the existing index\n    (manifest `snapshot_id` match) rather than rebuilding — except in MODE-OFF,\n    where it always rebuilds. Individual semantic units are reused across\n    snapshots when their content-addressed cache keys match (incremental\n    rebuild).\n\n## Instructions\n\n### Step 0: Locator Resolution (run first)\n\n```\nLOCATOR RESOLUTION (before reading ANY target code or artifact):\n0. ROLE: If this skill NEVER reads target source (report, calibrate, reflect),\n   you are a FINDINGS-ONLY stage: skip steps 2-6; still read active_snapshot from\n   state for provenance\u002Fannotation; NEVER stop merely because a code root is unset.\n1. Determine CODE_ROOT, in this priority order:\n   a. If --target_root is passed on THIS invocation, CODE_ROOT = --target_root.\n      It is AUTHORITATIVE and OVERRIDES SNAPSHOT_ROOT and the state fallback\n      (used when a caller hands you a prepared tree, e.g. a patched shadow).\n   b. Else if --snapshot_root (or SNAPSHOT_ROOT) is passed, use it.\n   c. Else read state_root\u002Fworkspace\u002F.mantis_state.json (state_root from\n      --state_root if passed, else .\u002Fworkspace\u002F... relative to the current dir)\n      -> active_snapshot.root \u002F .snapshot_id \u002F .snapshot_pinned.\n   d. Else (no arg AND no readable active_snapshot): CODE_ROOT = current directory,\n      treat snapshot_pinned = false (MODE-OFF). Do NOT stop.\n2. SENTINEL CHECK (only if snapshot_pinned is true AND you did NOT take path 1a):\n   verify CODE_ROOT\u002F.mantis_snapshot_id exists and equals SNAPSHOT_ID. If missing\n   or different -> STOP \"snapshot sentinel mismatch\". (A --target_root tree (1a) is\n   deliberately mutated and is sentinel-EXEMPT.)\n3. PATH FIELDS:\n   - SNAPSHOT-RELATIVE (read under CODE_ROOT): code_paths entries; plan target_files\n     that are file paths. Strip ONLY a trailing \":\u003Cdigits>\". A code_paths entry\n     containing \":\u002F\u002F\" is a URL\u002Fendpoint, NOT a file read. A code_paths entry that is\n     NOT of the form \u003Cexisting-path>:\u003Cinteger> is a non-source LOCATOR\n     (symbol\u002Foffset\u002Fendpoint): only check that the artifact\u002Fsymbol exists; skip ALL\n     line-range and line-existence logic.\n   - STATE-RELATIVE (read\u002Fwrite under state_root\u002Fworkspace, NEVER prefix CODE_ROOT):\n     kb_references, repro_file_path, reattack_file_path, helper scripts, report\n     files, and all state\u002Ffindings JSON.\n4. Never WRITE under CODE_ROOT when snapshot_pinned is true. Any command that\n   compiles, generates, or writes artifacts MUST run in a PRIVATE SHADOW copy\n   (mktemp -d from CODE_ROOT), never with cwd=CODE_ROOT. Read-only inspection may\n   cd into CODE_ROOT.\n5. VCS-METADATA CARVE-OUT: history-log extraction and any VCS diff\u002Fblame command\n   run in the LIVE repository root (which still has .git\u002F.hg\u002F.repo), NOT CODE_ROOT\n   (the snapshot copy strips VCS metadata). Do NOT stop merely because CODE_ROOT\n   lacks .git\u002F.hg\u002F.repo.\n6. Every shell command uses ABSOLUTE paths and sets its own working directory on\n   that call. Do NOT assume the working directory persists between calls.\n```\n\nThis is a CODE-READING stage — it reads target source files under CODE_ROOT via\nthe helper script. Block A step 0's findings-only skip does NOT apply.\n\n### Step 1: Idempotency \u002F Freshness Check (MANDATORY FIRST)\n\n**MANDATORY FIRST STEP:** Before writing any build scripts, probing backends, or\nextracting symbols, check for MODE-OFF or an existing manifest:\n\n1. **MODE-OFF check (FIRST):** In MODE-OFF (`SNAPSHOT_ID` is `\"unknown\"` or\n   absent) → **always rebuild**. Do NOT reuse a previous `\"unknown\"` index,\n   because the live tree is mutable and `\"unknown\"` is a constant (not a\n   freshness signal). Skip directly to Step 2 (individual units whose content\n   has not changed may still hit the content-addressed cache).\n2. If `workspace\u002Fkb\u002Fstructural_index\u002Fmanifest.json` exists, read its\n   `snapshot_id` field.\n3. If `snapshot_id` matches the current `SNAPSHOT_ID` (and `SNAPSHOT_ID` is NOT\n   `\"unknown\"`) → **reuse the index immediately and STOP**. Do NOT invoke Step\n   2, probe backends, or write build scripts. This bounds cost across\n   retries\u002Fcrash-resume.\n4. If `manifest.json` is absent but `workspace\u002Fkb\u002Fstructural_index.jsonl` exists\n   (backward compat), read its provenance header (`_provenance`, `snapshot_id`\n   keys). If `snapshot_id` matches (and is NOT `\"unknown\"`) → **reuse and\n   STOP**. Otherwise proceed to rebuild.\n5. If `SNAPSHOT_ID` differs → proceed to rebuild (Steps 2–5). For incremental\n   reuse: before rebuilding a semantic unit, check `units\u002F` for a\n   content-addressed cache hit (see Content-Addressed Cache Key below). A cache\n   hit reuses the unit output without re-extraction.\n\n### Step 2: Select Backend per Partition\n\nPartition the codebase into semantic units (see Per-Language Semantic Units\nbelow). For each unit, select the most precise backend available in this\nenvironment using a **capability-based, per-partition decision** — not a single\nglobal ladder. The following are **examples, not an exhaustive enum**. The\nhelper probes each tier per partition and selects the highest available:\n\n1. **Snapshot-matched SCIP \u002F LSIF \u002F Kythe \u002F clangd-static \u002F remote index**\n   (precision: `semantic`) — if a pre-built index matching the current\n   `snapshot_id` or `root_fingerprint` is available. Most precise: full\n   type-aware cross-reference, call hierarchy, and hover\u002Fsignature data.\n2. **Compiler \u002F typechecker-backed extractor** (precision: `typecheck`) — if\n   `compile_commands.json`, build context, or typechecker is available.\n   Type-accurate symbol resolution and call edges.\n3. **Language-aware AST extraction** (precision: `ast`) — if `tree-sitter`,\n   `ast-grep`, or a language-specific parser is available. Full AST parsing:\n   function boundaries, call expressions, signatures.\n4. **Symbol-only extraction** (precision: `symbol-only`) — if `ctags` or\n   equivalent is on `PATH`. Symbol table only (function definitions, locations —\n   no call graph). Call-site extraction uses a lightweight regex pass within\n   known function boundaries.\n5. **Heuristic fallback** (precision: `heuristic`) — Python stdlib regex pass\n   over source files. Identifies function definitions and call patterns using\n   language-agnostic heuristics. Less precise but zero-dependency.\n6. **Coverage-only manifest + lexical fallback** (precision: `coverage-only`) —\n   grep; no structural index is written. Manifest records `status: \"empty\"`.\n   Consumers fall back to grep-based discovery (today's behavior byte-for-byte).\n\n**Native Index Probing & Resolution Rules**:\n\n- **Probe instruction**: Before evaluating per-partition backends, probe\n  `workspace\u002Fkb\u002Fstructural_index\u002Fnative\u002F` and subdirectories\n  `native\u002F{scip,lsif,kythe}\u002F` for prebuilt index files.\n- **Snapshot-declaration convention**: Because native formats (SCIP, LSIF,\n  Kythe) do not embed snapshot identity directly in their binary payload,\n  prebuilt indexes MUST declare their target snapshot using a sidecar\n  `provenance.json` manifest located at\n  `workspace\u002Fkb\u002Fstructural_index\u002Fnative\u002Fprovenance.json` or\n  `native\u002F\u003Ckind>\u002Fprovenance.json`. The manifest contains an array of\n  attachments:\n  `[{\"kind\": \"scip|lsif|kythe\", \"path\": \"...\", \"snapshot_id\": \"...\", \"root_fingerprint\": \"...\", \"language\": \"...\", \"indexer\": \"...\", \"precision\": \"semantic\", \"files\": [...]}]`.\n  A native index is matched if its declared `snapshot_id` equals `SNAPSHOT_ID`\n  (when `SNAPSHOT_ID != \"unknown\"`) or its `root_fingerprint` matches the\n  workspace's calculated root fingerprint. If the `files` array is absent or\n  empty, treat the native index as covering no individual files directly (record\n  in `manifest.native_indexes` but do not update `coverage` rows; fall through\n  to lower tiers for all files).\n- **Record-and-Defer Ingestion Rule**: Parsing raw binary native indexes (e.g.\n  SCIP protobuf) in pure Python without dependencies is costly and complex.\n  Option A builder scripts MUST detect matching prebuilt native indexes, record\n  their entries in `manifest.native_indexes`, and set the `coverage` table\n  `backend` (e.g., `\"scip\"` or `\"scip-clangd\"`) for all files listed in the\n  provenance manifest. If `catalog.sqlite` is NOT populated with symbols from\n  the native index (raw binary deferred to harness\u002FMCP readers), set\n  `coverage.status = \"deferred\"` and `precision = \"deferred\"` (with\n  `indexed_files = 0`). This prevents the query helper from claiming an\n  un-ingested partition is \"authoritative empty\" at `semantic` precision,\n  ensuring consumers run the mandatory grep fallback. When `catalog.sqlite` IS\n  populated (e.g., via Option B pre-ingestion or `scip-to-sqlite`), set\n  `precision = \"semantic\"` and `status = \"indexed\"`.\n\n**LSP is NOT equivalent to SCIP \u002F LSIF.** LSP is an interactive protocol whose\nworkspace state may be partial or mutable. Use it only when the server can\ndemonstrate snapshot identity AND complete workspace coverage. A running\nlanguage server does not automatically qualify as a `semantic` backend.\n\n**SCIP merging.** SCIP explicitly permits merging complementary information from\nindexers with different precision levels. Its format records indexer version and\nper-document language metadata. The builder MAY merge results from multiple\nindexers (e.g., a SCIP index for Go + tree-sitter for Python) within a single\ncatalog.\n\nThe determinism lives in a runtime-generated versioned helper\n(`build_structural_index.py`, `# MANTIS_HELPER_VERSION = 5`, grep-and-regenerate\non reuse) that probes and selects backends per partition. No shipped binaries;\nair-gapped-safe.\n\n**Deterministic partial coverage.** On very large source trees, a full rebuild\ncan dominate stage-0 wall-clock. Replace discovery-order truncation with a\ndeterministic priority queue:\n\n1. **Explicit target files and symbols** (from `plan.json`, if available).\n2. **Changed units and known reverse dependencies** (from Block E diff +\n   reverse-dependency edges in the existing catalog).\n3. **Containing packages\u002Fmodules and direct imports** of tier 1+2 units.\n4. **Remaining units in normalized path order**.\n\nApply deterministic `max_units` (default: 10000) or `max_source_bytes` (default:\n500MB) bounds to that queue. Persist deferred units in the manifest so another\ninvocation can resume. Statuses: `complete`, `partial`, `empty`, `failed`.\n\nA consumer should only interpret \"no callers\" strongly when the relevant\npartition is complete, snapshot-matched, covered, and semantically precise.\nOtherwise the answer is \"no indexed callers\", followed by the existing lexical\nfallback. The index is HINT-only, so partial coverage is safe (grep remains\nauthoritative).\n\n### Step 3: Write and Run Helper Scripts\n\nTwo runtime-generated helpers are used. Both follow the grep-and-regenerate\npattern: before reuse, grep the first line for the version marker; if absent or\na different integer, REGENERATE.\n\n#### Builder: `build_structural_index.py`\n\n1. Write the builder to `workspace\u002Fhelpers\u002Fbuild_structural_index.py`. The FIRST\n   LINE MUST be exactly `# MANTIS_HELPER_VERSION = 5`. Before reusing an\n   existing helper, grep its first lines for `MANTIS_HELPER_VERSION = 5`; if\n   that marker is absent or a different integer, REGENERATE the helper.\n2. The builder partitions the codebase into semantic units (see Per-Language\n   Semantic Units), computes content-addressed cache keys, checks `units\u002F` for\n   reuse, selects a backend per partition (Step 2), extracts symbols + edges,\n   and writes results to `catalog.sqlite`.\n3. The builder must extract:\n   - **Symbols**: `symbol_id`, `name`, `qualified_name`, `namespace`,\n     `language`, `file_path`, `start_line`, `end_line`, `kind`, `signature`,\n     `backend`, `precision`.\n   - **Call edges**: `caller_id`, `callee_id`, `callee_name`, `file_path`,\n     `line`, `edge_kind`.\n   - **Function boundaries**: `symbol_id`, `file_path`, `start_line`,\n     `end_line`, `signature`, `language`.\n   - **Coverage**: `file_path`, `indexed`, `backend`, `precision`,\n     `unit_cache_key`, `status`.\n4. The builder writes the manifest LAST (Step 4) and the compatibility pointer.\n5. The builder MUST use ABSOLUTE paths and set its own working directory (Block\n   A step 6). It MUST NOT write anything under `CODE_ROOT` when\n   `snapshot_pinned` is true (Block A step 4). Backends that produce sidecar\n   files (ctags `tags`, cscope `cscope.out`, clangd cache) MUST be redirected to\n   STATE-RELATIVE paths: `ctags -f \u003Cstate>\u002Fhelpers\u002Ftags`,\n   `cscope -f \u003Cstate>\u002Fhelpers\u002Fcscope.out`,\n   `CLANGD_INDEX_STORAGE=\u003Cstate>\u002Fhelpers\u002F`. Read-only LSP\u002FSCIP queries to a\n   running server need no redirect.\n\n#### Query helper: `query_structural_index.py`\n\n1. Write the query helper to `workspace\u002Fhelpers\u002Fquery_structural_index.py`. The\n   FIRST LINE MUST be exactly `# MANTIS_HELPER_VERSION = 5`. Before reusing an\n   existing helper, grep its first lines for `MANTIS_HELPER_VERSION = 5`; if\n   absent or a different integer, REGENERATE.\n2. The query helper provides bounded, paginated operations against\n   `catalog.sqlite` (or a remote endpoint — identical API). It IS the\n   consumption contract (see Query Interface below).\n3. If `catalog.sqlite` is absent but `structural_index.jsonl` exists, the query\n   helper falls back to linear scanning of the JSONL file (slower but\n   functional). If both are absent, it returns empty results with\n   `coverage.partition_status = \"empty\"`.\n\n### Step 4: Write Manifest and Commit\n\n1. Write the manifest to `workspace\u002Fkb\u002Fstructural_index\u002Ftmp\u002Fmanifest.json`\n   first. Then atomically rename it to\n   `workspace\u002Fkb\u002Fstructural_index\u002Fmanifest.json`. This is the atomic commit\n   point — the manifest is written LAST, after all units, catalog, and coverage\n   data are written.\n2. Write a compatibility pointer to `workspace\u002Fkb\u002Fstructural_index.jsonl`\n   (STATE-RELATIVE — NEVER under `CODE_ROOT`). Below a configurable threshold\n   (default: 10K records), emit a complete JSONL export (provenance header + all\n   records). Above the threshold, emit only a provenance header with\n   `compat_pointer.full_export = false` and `compat_pointer.symbol_count` set.\n   Large consumers MUST use the query interface.\n3. The manifest `provider.backend_versions` field records which backend was used\n   per language (e.g.,\n   `{\"go\": {\"backend_name\": \"scip-clangd\", \"precision\": \"semantic\"}, \"python\": {\"backend_name\": \"tree-sitter\", \"precision\": \"ast\"}}`),\n   so consumers know the precision level.\n4. Interrupted builds leave unreferenced temp objects in `tmp\u002F` without\n   corrupting the last published index. On resume, check `manifest.json` status\n   and `snapshot_id` (Step 1).\n\n### Step 5: Return Results \u002F Notify Caller\n\n1. Return the path to `manifest.json`, `catalog.sqlite`, and the query helper.\n   Include a summary (unit count, symbol count, call-edge count, backends used,\n   coverage status).\n2. If the index is empty (no tools available or no source files found), notify\n   the caller: \"Structural index is empty — structural context unavailable.\"\n3. Do not notify the user directly — this skill is invoked as a sub-agent by the\n   harness, planner, or researcher.\n\n## Index Schema and On-Disk Contract\n\n### On-Disk Layout\n\n```\nworkspace\u002Fkb\u002Fstructural_index\u002F\n├── manifest.json          # Atomic commit point — written LAST\n├── catalog.sqlite         # Query-optimized serving store (both directions indexed)\n├── units\u002F                 # Content-addressed immutable unit outputs\n│   └── ab\u002Fcd\u002Fabcdef...    # sha256 prefix sharding (2+2 hex dirs)\n├── shards\u002F                # Partitioned serving data (large corpora)\n│   └── shard_0000.sqlite\n├── native\u002F                # Prebuilt index attachments (SCIP, Kythe, LSIF)\n│   ├── provenance.json    # Prebuilt index provenance manifest\n│   ├── scip\u002F\n│   └── kythe\u002F\n└── tmp\u002F                   # Temporary objects during build\n\nworkspace\u002Fhelpers\u002F\n├── build_structural_index.py    # Builder (MANTIS_HELPER_VERSION = 5)\n└── query_structural_index.py    # Query helper (MANTIS_HELPER_VERSION = 5)\n\nworkspace\u002Fkb\u002Fstructural_index.jsonl  # Compatibility pointer\n```\n\n### Manifest Schema (`manifest.json`)\n\n```json\n{\n  \"schema_version\": 1,\n  \"snapshot_id\": \"\u003CSNAPSHOT_ID or 'unknown'>\",\n  \"root_fingerprint\": \"\u003Csha256 of sorted (path, content_sha256) for all source files>\",\n  \"status\": \"complete|partial|empty|failed\",\n  \"provider\": {\n    \"kind\": \"local-build|baseline+overlay|remote\",\n    \"catalog\": \"catalog.sqlite\",\n    \"backend_versions\": {\n      \"cpp\": {\"backend_name\": \"tree-sitter\", \"backend_version\": \"0.20.8\", \"precision\": \"ast\"},\n      \"go\": {\"backend_name\": \"scip-clangd\", \"backend_version\": \"0.2.3\", \"precision\": \"semantic\"}\n    }\n  },\n  \"units\": {\"total\": 0, \"reused\": 0, \"rebuild\": 0, \"failed\": 0},\n  \"coverage\": {\"total_files\": 0, \"indexed_files\": 0, \"failed_files\": 0, \"deferred_files\": 0},\n  \"shards\": [{\"id\": \"\", \"path\": \"\", \"checksum\": \"\", \"partition_key\": \"\", \"symbol_count\": 0, \"edge_count\": 0}],\n  \"deferred_units\": [{\"unit_id\": \"\", \"language\": \"\", \"files\": [], \"priority\": 4, \"reason\": \"\"}],\n  \"native_indexes\": [{\"kind\": \"scip\", \"path\": \"\", \"snapshot_id\": \"\", \"root_fingerprint\": \"\", \"language\": \"\", \"indexer\": \"\", \"precision\": \"\"}],\n  \"baseline\": {\"source\": \"ci|local|none\", \"snapshot_id\": \"\", \"manifest_path\": \"\"},\n  \"overlay\": {\"units_added\": 0, \"units_modified\": 0, \"files\": []},\n  \"compat_pointer\": {\"path\": \"structural_index.jsonl\", \"full_export\": true, \"symbol_count\": 0},\n  \"created_at\": \"\u003CISO 8601>\",\n  \"build_duration_ms\": 0\n}\n```\n\n**Atomic commit**: The manifest is written LAST (atomic rename from `tmp\u002F`).\nInterrupted builds leave unreferenced temp objects without corrupting the last\npublished index.\n\n### SQLite Catalog Schema (`catalog.sqlite`)\n\n```sql\nCREATE TABLE IF NOT EXISTS schema_meta (\n    key TEXT PRIMARY KEY, value TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS symbols (\n    symbol_id       TEXT PRIMARY KEY,\n    name            TEXT NOT NULL,\n    qualified_name  TEXT NOT NULL,\n    namespace       TEXT,\n    language        TEXT NOT NULL,\n    file_path       TEXT NOT NULL,\n    start_line      INTEGER NOT NULL,\n    end_line        INTEGER,\n    kind            TEXT NOT NULL,\n    signature       TEXT,\n    backend         TEXT NOT NULL,\n    precision       TEXT NOT NULL CHECK (precision IN ('semantic','typecheck','ast','symbol-only','heuristic','deferred','coverage-only')),\n    corpus          TEXT DEFAULT 'default',\n    partition_key   TEXT,\n    unit_cache_key  TEXT,\n    source_layer    TEXT NOT NULL DEFAULT 'baseline'\n);\n\nCREATE TABLE IF NOT EXISTS call_edges (\n    edge_id     INTEGER PRIMARY KEY AUTOINCREMENT,\n    caller_id   TEXT NOT NULL,\n    callee_id   TEXT,\n    callee_name TEXT NOT NULL,\n    file_path   TEXT NOT NULL,\n    line        INTEGER NOT NULL,\n    edge_kind   TEXT NOT NULL CHECK (edge_kind IN ('direct','indirect','virtual','macro','unresolved')),\n    corpus      TEXT DEFAULT 'default',\n    partition_key TEXT,\n    source_layer TEXT NOT NULL DEFAULT 'baseline',\n    FOREIGN KEY (caller_id) REFERENCES symbols(symbol_id),\n    FOREIGN KEY (callee_id) REFERENCES symbols(symbol_id)\n);\n\nCREATE TABLE IF NOT EXISTS function_boundaries (\n    symbol_id   TEXT PRIMARY KEY,\n    file_path   TEXT NOT NULL,\n    start_line  INTEGER NOT NULL,\n    end_line    INTEGER NOT NULL,\n    signature   TEXT,\n    language    TEXT NOT NULL,\n    FOREIGN KEY (symbol_id) REFERENCES symbols(symbol_id)\n);\n\nCREATE TABLE IF NOT EXISTS coverage (\n    file_path   TEXT NOT NULL,\n    indexed     INTEGER NOT NULL DEFAULT 0,\n    backend     TEXT,\n    precision   TEXT,\n    unit_cache_key TEXT,\n    status      TEXT NOT NULL DEFAULT 'pending',\n    corpus      TEXT DEFAULT 'default',\n    partition_key TEXT,\n    PRIMARY KEY (file_path, corpus)\n);\n\nCREATE TABLE IF NOT EXISTS unit_cache (\n    cache_key       TEXT PRIMARY KEY,\n    unit_id         TEXT NOT NULL,\n    language        TEXT NOT NULL,\n    extractor_name  TEXT NOT NULL,\n    extractor_version TEXT NOT NULL,\n    file_count      INTEGER NOT NULL,\n    symbol_count    INTEGER NOT NULL,\n    edge_count      INTEGER NOT NULL,\n    source_bytes    INTEGER NOT NULL,\n    created_at      TEXT NOT NULL,\n    snapshot_id     TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS deferred_units (\n    unit_id     TEXT PRIMARY KEY,\n    language    TEXT NOT NULL,\n    files       TEXT NOT NULL,\n    priority    INTEGER NOT NULL,\n    reason      TEXT NOT NULL,\n    cache_key   TEXT,\n    created_at  TEXT NOT NULL\n);\n\n-- Indexes: both directions!\nCREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);\nCREATE INDEX IF NOT EXISTS idx_symbols_qualified ON symbols(qualified_name);\nCREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path, start_line);\nCREATE INDEX IF NOT EXISTS idx_symbols_lang ON symbols(language);\nCREATE INDEX IF NOT EXISTS idx_edges_caller ON call_edges(caller_id);\nCREATE INDEX IF NOT EXISTS idx_edges_callee ON call_edges(callee_id);\nCREATE INDEX IF NOT EXISTS idx_edges_callee_name ON call_edges(callee_name);\nCREATE INDEX IF NOT EXISTS idx_edges_file ON call_edges(file_path, line);\nCREATE INDEX IF NOT EXISTS idx_boundaries_file ON function_boundaries(file_path, start_line, end_line);\nCREATE INDEX IF NOT EXISTS idx_coverage_status ON coverage(status);\nCREATE INDEX IF NOT EXISTS idx_deferred_priority ON deferred_units(priority);\n```\n\n**Partitioning**: When symbols exceed 500K or edges exceed 2M, partition into\nshard SQLite files under `shards\u002F` by `lang:{language}:bucket:{NN}` (stable\nhash). Each shard has the same schema. The query helper routes queries to the\ncorrect shard(s) based on the manifest.\n\n### Content-Addressed Cache Key\n\n```python\ncache_key = sha256(\n    schema_version +\n    extractor_name@version +\n    language +\n    compile_context_digest +\n    ordered_input_content_digests +\n    dependency_or_interface_digest\n)\n```\n\n**Critical**: `snapshot_id` is NOT in the cache key. It goes in provenance only.\nThis permits reuse across commits, branches, and snapshots with identical units.\n\n### Canonical Symbol IDs\n\n**Native IDs** (when semantic backend is available):\n\n- SCIP: `scip:{symbol}`\n- Kythe: `kythe:{uri}`\n- clangd: `clangd:{usr}`\n\n**Fallback IDs** (when no semantic backend):\n\n```\nfallback:{language}:{file_path}:{sha256(qualified_name|start_line|signature)[:16]}\n```\n\nThis distinguishes namespaces, overloads, methods, local functions, and\nduplicate names across languages or repositories.\n\n### Edge Kinds\n\nEdge kinds are kept explicitly separate — never silently merged:\n\n- `direct` — statically resolved call to a known symbol\n- `indirect` — function pointer, closure, callback\n- `virtual` — virtual method dispatch (runtime-resolved)\n- `macro` — call introduced by macro expansion\n- `unresolved` — callee name found but no symbol_id resolved\n\n### Per-Language Semantic Units\n\n| Language    | Unit Type        | Compile Context             | Dependency Digest                           |\n| ----------- | ---------------- | --------------------------- | ------------------------------------------- |\n| C\u002FC++       | Compilation unit | compile_commands.json entry | sha256 of transitive header interfaces      |\n| Go          | Package          | go.mod + build tags         | sha256 of imported packages' exported API   |\n| Rust        | Crate            | Cargo.toml + features       | sha256 of extern crate signatures           |\n| Java\u002FKotlin | Compiler batch   | module path + classpath     | sha256 of imported class signatures         |\n| TypeScript  | Project          | tsconfig.json               | sha256 of imported module type declarations |\n| Fallback    | Individual file  | None                        | Empty string                                |\n\n**Fallback Granularity Rule:** For the fallback tier (regex, AST parser, or\nlightweight crawler without a language build system), **each source file MUST be\nits own independent semantic unit**. Never bundle multiple source files into a\nsingle fallback unit. Per-file unit isolation is essential for content-addressed\ncache efficiency — when one file changes in a future snapshot, only that single\nfile's unit invalidates while all unchanged files hit the cache\n(`reused = N-1`).\n\n### Baseline + Delta Overlay\n\n- **Static baseline**: Built once in CI for the complete snapshot.\n- **Incremental cache**: Immutable unit outputs reused across snapshots.\n- **Delta overlay**: Changed\u002Ftarget files indexed locally, layered over\n  baseline.\n- **Remote serving**: Optional when global index is too costly locally.\n\nAt query time, the query helper merges baseline + overlay results. Overlay\nsymbols supersede baseline symbols for the same file. The manifest records\n`provider.kind = \"baseline+overlay\"` with baseline manifest reference and\noverlay unit count.\n\n### Compatibility JSONL Export\n\nThe old `structural_index.jsonl` format is retained as a compatibility export\nonly — it is NOT the query contract. Below the threshold (default 10K records),\na complete JSONL export is emitted. Above the threshold, only a provenance\npointer is written. Large consumers MUST use the query interface.\n\nLine 1 — provenance header:\n\n```json\n{\"_provenance\": true, \"snapshot_id\": \"abc123\", \"tool\": \"tree-sitter\"}\n```\n\nLines 2+ — structural records (one per line, `_type` discriminator):\n\n```json\n{\"_type\": \"function\", \"key\": \"src\u002Fparser.c:parse_input\", \"start_line\": 45, \"end_line\": 120, \"signature\": \"int parse_input(char *buf, size_t len)\", \"calls\": [\"malloc\", \"validate_input\", \"memcpy\"]}\n{\"_type\": \"call_edge\", \"caller\": \"parse_input\", \"callee\": \"malloc\", \"file\": \"src\u002Fparser.c\", \"line\": 78}\n{\"_type\": \"call_edge\", \"caller\": \"main\", \"callee\": \"parse_input\", \"file\": \"src\u002Fmain.c\", \"line\": 203}\n```\n\n**Provenance header fields:**\n\n| Field         | Type   | Description                                                                         |\n| ------------- | ------ | ----------------------------------------------------------------------------------- |\n| `_provenance` | bool   | Always `true` — marks this as the provenance header line                            |\n| `snapshot_id` | string | `SNAPSHOT_ID` the index was built against                                           |\n| `tool`        | string | Backend used (e.g. `\"lsp-clangd\"`, `\"tree-sitter\"`, `\"ctags\"`, `\"regex\"`, `\"grep\"`) |\n\n**Record types:**\n\n| `_type`     | Description                                     | Key fields                                            |\n| ----------- | ----------------------------------------------- | ----------------------------------------------------- |\n| `function`  | Function definition with boundary and signature | `key`, `start_line`, `end_line`, `signature`, `calls` |\n| `call_edge` | A call from caller to callee at file:line       | `caller`, `callee`, `file`, `line`                    |\n\n## Snapshot Safety\n\n1. **Build from `CODE_ROOT`, not live tree (when pinned).** When the snapshot is\n   pinned, the structural index is built from the pinned `CODE_ROOT`, ensuring\n   it reflects the exact bytes the pipeline is analyzing.\n2. **Reuse-on-match.** If `manifest.json` already carries the current\n   `SNAPSHOT_ID` (and `SNAPSHOT_ID` is NOT `\"unknown\"` — in MODE-OFF, always\n   rebuild), reuse it — do not rebuild. Rebuild when `SNAPSHOT_ID` differs or is\n   `\"unknown\"`. Individual units may still be reused from the content-addressed\n   cache. This bounds cost across retries\u002Fcrash-resume.\n3. **Manifest atomicity.** The manifest is written LAST via atomic rename from\n   `tmp\u002F`. Interrupted builds leave unreferenced temp objects without corrupting\n   the last published index. On resume, check `manifest.json` status and\n   `snapshot_id`.\n4. **STALE flag in HALT mode.** When `snapshot_pinned` is false (HALT), the\n   index may be built from the unpinned `CODE_ROOT` but is marked as potentially\n   stale. Consumers treat structural hints as advisory.\n5. **MODE-OFF: build, do not skip.** When `active_snapshot` is absent\n   (MODE-OFF), build against the current directory (cwd) with provenance\n   `snapshot_id` set to `\"unknown\"`. This is the standalone-efficiency case —\n   the index is still useful for the researcher\u002Fplanner even without snapshot\n   pinning. Do NOT return an empty index merely because the snapshot is absent.\n\n## Consumption Contract\n\nThese are runtime instructions for callers — they define how consumers use the\nstructural index. The structural index is a HINT-only enhancement; skills that\ndo not use it behave exactly as they do today.\n\n### Query Interface\n\n**The query helper IS the contract.** All consumers use\n`query_structural_index.py` (or a compatible remote endpoint). The JSONL file is\nNOT the query contract — it is a compatibility export only.\n\n**Operations**:\n\n1. `resolve_symbol(name, language?, file?, namespace?)` →\n   `{results, total, ambiguous, coverage}`\n\n   - Returns ALL matches — no silent selection among ambiguous symbols.\n   - Caller MUST disambiguate before calling `find_callers` \u002F `find_callees`.\n\n2. `find_callers(symbol_id, limit=100, offset=0)` →\n   `{results, total, has_more, coverage}`\n\n   - Bounded, paginated caller lookup.\n   - Each result carries `precision`, `backend`, `edge_kind`.\n   - Empty results carry `coverage.partition_status`.\n\n3. `find_callees(symbol_id, limit=100, offset=0)` →\n   `{results, total, has_more, coverage}`\n\n   - Same shape as `find_callers`.\n\n4. `get_function_boundary(file, line)` →\n   `{symbol_id, start_line, end_line, signature, precision, backend}`\n\n5. `get_coverage(file?)` →\n   `{total_files, indexed_files, failed, deferred, partition_status, backends_used}`\n\n**Key properties**:\n\n- Bounded results and pagination (prevents loading entire index into memory).\n- Explicit name resolution before graph traversal (no silent selection among\n  ambiguous symbols).\n- Precision and backend attached to every result.\n- Coverage attached to empty results.\n- Identical operations for local and remote providers (remote via\n  `MANTIS_STRUCTURAL_INDEX_URL` env var or manifest `provider.kind = \"remote\"`).\n\n**Coverage on empty results**:\n\n| `partition_status` | Meaning                       | Consumer action                                          |\n| ------------------ | ----------------------------- | -------------------------------------------------------- |\n| `complete`         | All files indexed             | \"No indexed callers\" (still run grep per HINT-only rule) |\n| `partial`          | Some files deferred or failed | \"Not fully indexed\" — MUST run grep fallback             |\n| `empty`            | No backend available          | \"Not indexed\" — MUST run grep fallback                   |\n| `failed`           | Backend attempted but failed  | \"Index failed\" — MUST run grep fallback                  |\n\n### mantis-plan\n\n- Use the structural index query helper for function-level dependency fan-out.\n  When planning investigations, call `resolve_symbol()` then `find_callers()` to\n  identify all functions that call into a target — this broadens the audit set\n  beyond single-file analysis.\n- The structural index decides ORDER of investigations (which functions to audit\n  first based on call-graph centrality), never MEMBERSHIP. It may broaden the\n  audit set (safe over-reporting), but must never REMOVE or drop a file; the\n  planner's existing logic remains the membership floor.\n\n### mantis-researcher\n\n- **Wave 1 (Rapid Triage):** Run a repo-wide grep for the function name to build\n  the exhaustive set of candidate call-sites — this is the mandatory floor. Then\n  use the structural index query helper (`resolve_symbol` then `find_callers`)\n  to RANK and prioritize which call-sites to audit first (the index\n  distinguishes actual calls from comments\u002Fstrings\u002Fvariable names). Audit the\n  union of both result sets — the structural index may miss macro-based calls,\n  function pointers, and dynamic dispatch, so grep remains the floor.\n- **Wave 2 (Deep Audit):** Use `get_function_boundary(file, line)` to start with\n  the enclosing function, expanding to callers\u002Fcallees\u002Ffile as needed for\n  cross-function context — this saves context while preserving coverage.\n- If the structural index is absent or empty, fall back to grep-based discovery\n  (today's behavior). The structural index is a coverage HINT only — it improves\n  audit quality but is never required.\n\n## Safety\n\nNon-negotiable invariants:\n\n1. **Agnostic.** Nothing is ever required; no-tool \u002F parse-fail \u002F not-invoked →\n   empty index → grep fallback = today's behavior byte-for-byte. Optional in the\n   Pass Lifecycle Contract; a non-conformant harness simply skips it.\n2. **HINT-only \u002F union \u002F never MEMBERSHIP.** Consumers audit the union with\n   grep; a symbol the index misses must still be reachable by the exhaustive\n   sweep. The structural index decides ORDER, never MEMBERSHIP. The query\n   contract enforces this: `find_callers` returns HINTs, never authoritative\n   membership — consumers MUST union with grep.\n3. **No verdicts, touches no findings.** Cannot violate INV-1 and cannot itself\n   drop a finding.\n4. **Narrow scope: source cross-reference only.** Binary\u002Fbuild-derived\n   reachability (\"is it compiled into production\") is explicitly out of scope —\n   a dev customization, not part of this skill, because absence-from-a-build can\n   hide a real finding (INV-2). Do not fold build-derived reachability into this\n   skill.\n\nA reference blueprint is available at\n[mantis-pipeline-adapter\u002Freferences\u002Fmantis-structural-index.md](..\u002Fmantis-pipeline-adapter\u002Freferences\u002Fmantis-structural-index.md).\nIt is a stub that points to this SKILL.md as the single source of truth — do not\nduplicate spec content there.\n","# Structural Code Index Builder\n\nThis is an **optional first-class stage** in the Pass Lifecycle Contract. It\nruns immediately after the snapshot is pinned (Block D), before the first\ncode-reading analysis stage (summarize\u002Farchitecture). It only needs `CODE_ROOT`\n\\+ `SNAPSHOT_ID` and must not depend on architecture\u002FKB.\n\n## System Goal\n\nStructural Code Index Builder. Builds a content-addressed semantic-unit index\nfrom source code using capability-based per-partition backend selection,\ndegrading gracefully to grep. Provides `find_callers(symbol)`,\n`get_function_boundary(file, line)`, and call-site awareness to improve LLM\nreasoning quality during discovery — supplementing (never replacing) grep-based\ncall-site discovery with structural data. The index is composed from independent\nsemantic compilation units, persisted as a manifest + SQLite catalog, and\nexposed through a bounded query helper.\n\n## Command Definition\n\n- **Command:** `\u002Fmantis-structural-index`\n- **Description:** Build a content-addressed semantic-unit index from source\n  code under `CODE_ROOT`.\n- **Arguments (optional; supplied by the orchestrator, consumed by Block A):**\n  `--snapshot_root`\u002F`--snapshot_id`\u002F`--state_root`. All absent → MODE-OFF\u002Flegacy\n  mode (reads source from the current directory, writes index to\n  `.\u002Fworkspace\u002Fkb\u002Fstructural_index\u002F`).\n\n## Input\u002FOutput Contract\n\n- **Reads**:\n  - `workspace\u002F.mantis_state.json` (to read `active_snapshot` for provenance\n    checking and snapshot-aware rebuild logic).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fmanifest.json` (to check `snapshot_id` for\n    reuse-on-match idempotency — primary check).\n  - `workspace\u002Fkb\u002Fstructural_index.jsonl` (backward-compat provenance check if\n    manifest absent).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Funits\u002F` (content-addressed cache for\n    incremental unit reuse).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fnative\u002F` and sidecar `provenance.json` files\n    (prebuilt index attachments and metadata manifests).\n  - CODE_ROOT source files (via generated helper script — the script parses all\n    source files under `CODE_ROOT`).\n- **Writes**:\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fmanifest.json` (STATE-RELATIVE — atomic\n    commit point, written LAST).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fcatalog.sqlite` (STATE-RELATIVE —\n    query-optimized serving store).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Funits\u002F` (STATE-RELATIVE — content-addressed\n    immutable unit outputs).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fshards\u002F` (STATE-RELATIVE — partitioned\n    serving data for large corpora).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Fnative\u002F` (STATE-RELATIVE — prebuilt index\n    attachments: SCIP, Kythe, LSIF).\n  - `workspace\u002Fkb\u002Fstructural_index\u002Ftmp\u002F` (STATE-RELATIVE — temporary objects\n    during build).\n  - `workspace\u002Fkb\u002Fstructural_index.jsonl` (STATE-RELATIVE — compatibility\n    pointer; full export below threshold).\n  - `workspace\u002Fhelpers\u002Fbuild_structural_index.py` (STATE-RELATIVE — the builder\n    helper script).\n  - `workspace\u002Fhelpers\u002Fquery_structural_index.py` (STATE-RELATIVE — the query\n    helper script).\n- **Preconditions**:\n  - Source files must exist under `CODE_ROOT`. If `CODE_ROOT` is not resolved\n    (MODE-OFF and no readable `active_snapshot`), build against the current\n    directory with `snapshot_id` set to `\"unknown\"`. Do NOT skip — this is the\n    standalone-efficiency case.\n- **Inert until wired:** This skill returns an empty index until a caller\n  invokes it (the harness, `mantis-plan`, or `mantis-researcher`). It never\n  fails — it simply returns an empty index if tools are unavailable or source\n  cannot be parsed.\n- **Idempotency Guarantee**:\n  - Read-only on `CODE_ROOT`. Writes only to STATE-RELATIVE paths. Re-running\n    with the same `CODE_ROOT` and `SNAPSHOT_ID` reuses the existing index\n    (manifest `snapshot_id` match) rather than rebuilding — except in MODE-OFF,\n    where it always rebuilds. Individual semantic units are reused across\n    snapshots when their content-addressed cache keys match (incremental\n    rebuild).\n\n## Instructions\n\n### Step 0: Locator Resolution (run first)\n\n```\nLOCATOR RESOLUTION (before reading ANY target code or artifact):\n0. ROLE: If this skill NEVER reads target source (report, calibrate, reflect),\n   you are a FINDINGS-ONLY stage: skip steps 2-6; still read active_snapshot from\n   state for provenance\u002Fannotation; NEVER stop merely because a code root is unset.\n1. Determine CODE_ROOT, in this priority order:\n   a. If --target_root is passed on THIS invocation, CODE_ROOT = --target_root.\n      It is AUTHORITATIVE and OVERRIDES SNAPSHOT_ROOT and the state fallback\n      (used when a caller hands you a prepared tree, e.g. a patched shadow).\n   b. Else if --snapshot_root (or SNAPSHOT_ROOT) is passed, use it.\n   c. Else read state_root\u002Fworkspace\u002F.mantis_state.json (state_root from\n      --state_root if passed, else .\u002Fworkspace\u002F... relative to the current dir)\n      -> active_snapshot.root \u002F .snapshot_id \u002F .snapshot_pinned.\n   d. Else (no arg AND no readable active_snapshot): CODE_ROOT = current directory,\n      treat snapshot_pinned = false (MODE-OFF). Do NOT stop.\n2. SENTINEL CHECK (only if snapshot_pinned is true AND you did NOT take path 1a):\n   verify CODE_ROOT\u002F.mantis_snapshot_id exists and equals SNAPSHOT_ID. If missing\n   or different -> STOP \"snapshot sentinel mismatch\". (A --target_root tree (1a) is\n   deliberately mutated and is sentinel-EXEMPT.)\n3. PATH FIELDS:\n   - SNAPSHOT-RELATIVE (read under CODE_ROOT): code_paths entries; plan target_files\n     that are file paths. Strip ONLY a trailing \":\u003Cdigits>\". A code_paths entry\n     containing \":\u002F\u002F\" is a URL\u002Fendpoint, NOT a file read. A code_paths entry that is\n     NOT of the form \u003Cexisting-path>:\u003Cinteger> is a non-source LOCATOR\n     (symbol\u002Foffset\u002Fendpoint): only check that the artifact\u002Fsymbol exists; skip ALL\n     line-range and line-existence logic.\n   - STATE-RELATIVE (read\u002Fwrite under state_root\u002Fworkspace, NEVER prefix CODE_ROOT):\n     kb_references, repro_file_path, reattack_file_path, helper scripts, report\n     files, and all state\u002Ffindings JSON.\n4. Never WRITE under CODE_ROOT when snapshot_pinned is true. Any command that\n   compiles, generates, or writes artifacts MUST run in a PRIVATE SHADOW copy\n   (mktemp -d from CODE_ROOT), never with cwd=CODE_ROOT. Read-only inspection may\n   cd into CODE_ROOT.\n5. VCS-METADATA CARVE-OUT: history-log extraction and any VCS diff\u002Fblame command\n   run in the LIVE repository root (which still has .git\u002F.hg\u002F.repo), NOT CODE_ROOT\n   (the snapshot copy strips VCS metadata). Do NOT stop merely because CODE_ROOT\n   lacks .git\u002F.hg\u002F.repo.\n6. Every shell command uses ABSOLUTE paths and sets its own working directory on\n   that call. Do NOT assume the working directory persists between calls.\n```\n\nThis is a CODE-READING stage — it reads target source files under CODE_ROOT via\nthe helper script. Block A step 0's findings-only skip does NOT apply.\n\n### Step 1: Idempotency \u002F Freshness Check (MANDATORY FIRST)\n\n**MANDATORY FIRST STEP:** Before writing any build scripts, probing backends, or\nextracting symbols, check for MODE-OFF or an existing manifest:\n\n1. **MODE-OFF check (FIRST):** In MODE-OFF (`SNAPSHOT_ID` is `\"unknown\"` or\n   absent) → **always rebuild**. Do NOT reuse a previous `\"unknown\"` index,\n   because the live tree is mutable and `\"unknown\"` is a constant (not a\n   freshness signal). Skip directly to Step 2 (individual units whose content\n   has not changed may still hit the content-addressed cache).\n2. If `workspace\u002Fkb\u002Fstructural_index\u002Fmanifest.json` exists, read its\n   `snapshot_id` field.\n3. If `snapshot_id` matches the current `SNAPSHOT_ID` (and `SNAPSHOT_ID` is NOT\n   `\"unknown\"`) → **reuse the index immediately and STOP**. Do NOT invoke Step\n   2, probe backends, or write build scripts. This bounds cost across\n   retries\u002Fcrash-resume.\n4. If `manifest.json` is absent but `workspace\u002Fkb\u002Fstructural_index.jsonl` exists\n   (backward compat), read its provenance header (`_provenance`, `snapshot_id`\n   keys). If `snapshot_id` matches (and is NOT `\"unknown\"`) → **reuse and\n   STOP**. Otherwise proceed to rebuild.\n5. If `SNAPSHOT_ID` differs → proceed to rebuild (Steps 2–5). For incremental\n   reuse: before rebuilding a semantic unit, check `units\u002F` for a\n   content-addressed cache hit (see Content-Addressed Cache Key below). A cache\n   hit reuses the unit output without re-extraction.\n\n### Step 2: Select Backend per Partition\n\nPartition the codebase into semantic units (see Per-Language Semantic Units\nbelow). For each unit, select the most precise backend available in this\nenvironment using a **capability-based, per-partition decision** — not a single\nglobal ladder. The following are **examples, not an exhaustive enum**. The\nhelper probes each tier per partition and selects the highest available:\n\n1. **Snapshot-matched SCIP \u002F LSIF \u002F Kythe \u002F clangd-static \u002F remote index**\n   (precision: `semantic`) — if a pre-built index matching the current\n   `snapshot_id` or `root_fingerprint` is available. Most precise: full\n   type-aware cross-reference, call hierarchy, and hover\u002Fsignature data.\n2. **Compiler \u002F typechecker-backed extractor** (precision: `typecheck`) — if\n   `compile_commands.json`, build context, or typechecker is available.\n   Type-accurate symbol resolution and call edges.\n3. **Language-aware AST extraction** (precision: `ast`) — if `tree-sitter`,\n   `ast-grep`, or a language-specific parser is available. Full AST parsing:\n   function boundaries, call expressions, signatures.\n4. **Symbol-only extraction** (precision: `symbol-only`) — if `ctags` or\n   equivalent is on `PATH`. Symbol table only (function definitions, locations —\n   no call graph). Call-site extraction uses a lightweight regex pass within\n   known function boundaries.\n5. **Heuristic fallback** (precision: `heuristic`) — Python stdlib regex pass\n   over source files. Identifies function definitions and call patterns using\n   language-agnostic heuristics. Less precise but zero-dependency.\n6. **Coverage-only manifest + lexical fallback** (precision: `coverage-only`) —\n   grep; no structural index is written. Manifest records `status: \"empty\"`.\n   Consumers fall back to grep-based discovery (today's behavior byte-for-byte).\n\n**Native Index Probing & Resolution Rules**:\n\n- **Probe instruction**: Before evaluating per-partition backends, probe\n  `workspace\u002Fkb\u002Fstructural_index\u002Fnative\u002F` and subdirectories\n  `native\u002F{scip,lsif,kythe}\u002F` for prebuilt index files.\n- **Snapshot-declaration convention**: Because native formats (SCIP, LSIF,\n  Kythe) do not embed snapshot identity directly in their binary payload,\n  prebuilt indexes MUST declare their target snapshot using a sidecar\n  `provenance.json` manifest located at\n  `workspace\u002Fkb\u002Fstructural_index\u002Fnative\u002Fprovenance.json` or\n  `native\u002F\u003Ckind>\u002Fprovenance.json`. The manifest contains an array of\n  attachments:\n  `[{\"kind\": \"scip|lsif|kythe\", \"path\": \"...\", \"snapshot_id\": \"...\", \"root_fingerprint\": \"...\", \"language\": \"...\", \"indexer\": \"...\", \"precision\": \"semantic\", \"files\": [...]}]`.\n  A native index is matched if its declared `snapshot_id` equals `SNAPSHOT_ID`\n  (when `SNAPSHOT_ID != \"unknown\"`) or its `root_fingerprint` matches the\n  workspace's calculated root fingerprint. If the `files` array is absent or\n  empty, treat the native index as covering no individual files directly (record\n  in `manifest.native_indexes` but do not update `coverage` rows; fall through\n  to lower tiers for all files).\n- **Record-and-Defer Ingestion Rule**: Parsing raw binary native indexes (e.g.\n  SCIP protobuf) in pure Python without dependencies is costly and complex.\n  Option A builder scripts MUST detect matching prebuilt native indexes, record\n  their entries in `manifest.native_indexes`, and set the `coverage` table\n  `backend` (e.g., `\"scip\"` or `\"scip-clangd\"`) for all files listed in the\n  provenance manifest. If `catalog.sqlite` is NOT populated with symbols from\n  the native index (raw binary deferred to harness\u002FMCP readers), set\n  `coverage.status = \"deferred\"` and `precision = \"deferred\"` (with\n  `indexed_files = 0`). This prevents the query helper from claiming an\n  un-ingested partition is \"authoritative empty\" at `semantic` precision,\n  ensuring consumers run the mandatory grep fallback. When `catalog.sqlite` IS\n  populated (e.g., via Option B pre-ingestion or `scip-to-sqlite`), set\n  `precision = \"semantic\"` and `status = \"indexed\"`.\n\n**LSP is NOT equivalent to SCIP \u002F LSIF.** LSP is an interactive protocol whose\nworkspace state may be partial or mutable. Use it only when the server can\ndemonstrate snapshot identity AND complete workspace coverage. A running\nlanguage server does not automatically qualify as a `semantic` backend.\n\n**SCIP merging.** SCIP explicitly permits merging complementary information from\nindexers with different precision levels. Its format records indexer version and\nper-document language metadata. The builder MAY merge results from multiple\nindexers (e.g., a SCIP index for Go + tree-sitter for Python) within a single\ncatalog.\n\nThe determinism lives in a runtime-generated versioned helper\n(`build_structural_index.py`, `# MANTIS_HELPER_VERSION = 5`, grep-and-regenerate\non reuse) that probes and selects backends per partition. No shipped binaries;\nair-gapped-safe.\n\n**Deterministic partial coverage.** On very large source trees, a full rebuild\ncan dominate stage-0 wall-clock. Replace discovery-order truncation with a\ndeterministic priority queue:\n\n1. **Explicit target files and symbols** (from `plan.json`, if available).\n2. **Changed units and known reverse dependencies** (from Block E diff +\n   reverse-dependency edges in the existing catalog).\n3. **Containing packages\u002Fmodules and direct imports** of tier 1+2 units.\n4. **Remaining units in normalized path order**.\n\nApply deterministic `max_units` (default: 10000) or `max_source_bytes` (default:\n500MB) bounds to that queue. Persist deferred units in the manifest so another\ninvocation can resume. Statuses: `complete`, `partial`, `empty`, `failed`.\n\nA consumer should only interpret \"no callers\" strongly when the relevant\npartition is complete, snapshot-matched, covered, and semantically precise.\nOtherwise the answer is \"no indexed callers\", followed by the existing lexical\nfallback. The index is HINT-only, so partial coverage is safe (grep remains\nauthoritative).\n\n### Step 3: Write and Run Helper Scripts\n\nTwo runtime-generated helpers are used. Both follow the grep-and-regenerate\npattern: before reuse, grep the first line for the version marker; if absent or\na different integer, REGENERATE.\n\n#### Builder: `build_structural_index.py`\n\n1. Write the builder to `workspace\u002Fhelpers\u002Fbuild_structural_index.py`. The FIRST\n   LINE MUST be exactly `# MANTIS_HELPER_VERSION = 5`. Before reusing an\n   existing helper, grep its first lines for `MANTIS_HELPER_VERSION = 5`; if\n   that marker is absent or a different integer, REGENERATE the helper.\n2. The builder partitions the codebase into semantic units (see Per-Language\n   Semantic Units), computes content-addressed cache keys, checks `units\u002F` for\n   reuse, selects a backend per partition (Step 2), extracts symbols + edges,\n   and writes results to `catalog.sqlite`.\n3. The builder must extract:\n   - **Symbols**: `symbol_id`, `name`, `qualified_name`, `namespace`,\n     `language`, `file_path`, `start_line`, `end_line`, `kind`, `signature`,\n     `backend`, `precision`.\n   - **Call edges**: `caller_id`, `callee_id`, `callee_name`, `file_path`,\n     `line`, `edge_kind`.\n   - **Function boundaries**: `symbol_id`, `file_path`, `start_line`,\n     `end_line`, `signature`, `language`.\n   - **Coverage**: `file_path`, `indexed`, `backend`, `precision`,\n     `unit_cache_key`, `status`.\n4. The builder writes the manifest LAST (Step 4) and the compatibility pointer.\n5. The builder MUST use ABSOLUTE paths and set its own working directory (Block\n   A step 6). It MUST NOT write anything under `CODE_ROOT` when\n   `snapshot_pinned` is true (Block A step 4). Backends that produce sidecar\n   files (ctags `tags`, cscope `cscope.out`, clangd cache) MUST be redirected to\n   STATE-RELATIVE paths: `ctags -f \u003Cstate>\u002Fhelpers\u002Ftags`,\n   `cscope -f \u003Cstate>\u002Fhelpers\u002Fcscope.out`,\n   `CLANGD_INDEX_STORAGE=\u003Cstate>\u002Fhelpers\u002F`. Read-only LSP\u002FSCIP queries to a\n   running server need no redirect.\n\n#### Query helper: `query_structural_index.py`\n\n1. Write the query helper to `workspace\u002Fhelpers\u002Fquery_structural_index.py`. The\n   FIRST LINE MUST be exactly `# MANTIS_HELPER_VERSION = 5`. Before reusing an\n   existing helper, grep its first lines for `MANTIS_HELPER_VERSION = 5`; if\n   absent or a different integer, REGENERATE.\n2. The query helper provides bounded, paginated operations against\n   `catalog.sqlite` (or a remote endpoint — identical API). It IS the\n   consumption contract (see Query Interface below).\n3. If `catalog.sqlite` is absent but `structural_index.jsonl` exists, the query\n   helper falls back to linear scanning of the JSONL file (slower but\n   functional). If both are absent, it returns empty results with\n   `coverage.partition_status = \"empty\"`.\n\n### Step 4: Write Manifest and Commit\n\n1. Write the manifest to `workspace\u002Fkb\u002Fstructural_index\u002Ftmp\u002Fmanifest.json`\n   first. Then atomically rename it to\n   `workspace\u002Fkb\u002Fstructural_index\u002Fmanifest.json`. This is the atomic commit\n   point — the manifest is written LAST, after all units, catalog, and coverage\n   data are written.\n2. Write a compatibility pointer to `workspace\u002Fkb\u002Fstructural_index.jsonl`\n   (STATE-RELATIVE — NEVER under `CODE_ROOT`). Below a configurable threshold\n   (default: 10K records), emit a complete JSONL export (provenance header + all\n   records). Above the threshold, emit only a provenance header with\n   `compat_pointer.full_export = false` and `compat_pointer.symbol_count` set.\n   Large consumers MUST use the query interface.\n3. The manifest `provider.backend_versions` field records which backend was used\n   per language (e.g.,\n   `{\"go\": {\"backend_name\": \"scip-clangd\", \"precision\": \"semantic\"}, \"python\": {\"backend_name\": \"tree-sitter\", \"precision\": \"ast\"}}`),\n   so consumers know the precision level.\n4. Interrupted builds leave unreferenced temp objects in `tmp\u002F` without\n   corrupting the last published index. On resume, check `manifest.json` status\n   and `snapshot_id` (Step 1).\n\n### Step 5: Return Results \u002F Notify Caller\n\n1. Return the path to `manifest.json`, `catalog.sqlite`, and the query helper.\n   Include a summary (unit count, symbol count, call-edge count, backends used,\n   coverage status).\n2. If the index is empty (no tools available or no source files found), notify\n   the caller: \"Structural index is empty — structural context unavailable.\"\n3. Do not notify the user directly — this skill is invoked as a sub-agent by the\n   harness, planner, or researcher.\n\n## Index Schema and On-Disk Contract\n\n### On-Disk Layout\n\n```\nworkspace\u002Fkb\u002Fstructural_index\u002F\n├── manifest.json          # Atomic commit point — written LAST\n├── catalog.sqlite         # Query-optimized serving store (both directions indexed)\n├── units\u002F                 # Content-addressed immutable unit outputs\n│   └── ab\u002Fcd\u002Fabcdef...    # sha256 prefix sharding (2+2 hex dirs)\n├── shards\u002F                # Partitioned serving data (large corpora)\n│   └── shard_0000.sqlite\n├── native\u002F                # Prebuilt index attachments (SCIP, Kythe, LSIF)\n│   ├── provenance.json    # Prebuilt index provenance manifest\n│   ├── scip\u002F\n│   └── kythe\u002F\n└── tmp\u002F                   # Temporary objects during build\n\nworkspace\u002Fhelpers\u002F\n├── build_structural_index.py    # Builder (MANTIS_HELPER_VERSION = 5)\n└── query_structural_index.py    # Query helper (MANTIS_HELPER_VERSION = 5)\n\nworkspace\u002Fkb\u002Fstructural_index.jsonl  # Compatibility pointer\n```\n\n### Manifest Schema (`manifest.json`)\n\n```json\n{\n  \"schema_version\": 1,\n  \"snapshot_id\": \"\u003CSNAPSHOT_ID or 'unknown'>\",\n  \"root_fingerprint\": \"\u003Csha256 of sorted (path, content_sha256) for all source files>\",\n  \"status\": \"complete|partial|empty|failed\",\n  \"provider\": {\n    \"kind\": \"local-build|baseline+overlay|remote\",\n    \"catalog\": \"catalog.sqlite\",\n    \"backend_versions\": {\n      \"cpp\": {\"backend_name\": \"tree-sitter\", \"backend_version\": \"0.20.8\", \"precision\": \"ast\"},\n      \"go\": {\"backend_name\": \"scip-clangd\", \"backend_version\": \"0.2.3\", \"precision\": \"semantic\"}\n    }\n  },\n  \"units\": {\"total\": 0, \"reused\": 0, \"rebuild\": 0, \"failed\": 0},\n  \"coverage\": {\"total_files\": 0, \"indexed_files\": 0, \"failed_files\": 0, \"deferred_files\": 0},\n  \"shards\": [{\"id\": \"\", \"path\": \"\", \"checksum\": \"\", \"partition_key\": \"\", \"symbol_count\": 0, \"edge_count\": 0}],\n  \"deferred_units\": [{\"unit_id\": \"\", \"language\": \"\", \"files\": [], \"priority\": 4, \"reason\": \"\"}],\n  \"native_indexes\": [{\"kind\": \"scip\", \"path\": \"\", \"snapshot_id\": \"\", \"root_fingerprint\": \"\", \"language\": \"\", \"indexer\": \"\", \"precision\": \"\"}],\n  \"baseline\": {\"source\": \"ci|local|none\", \"snapshot_id\": \"\", \"manifest_path\": \"\"},\n  \"overlay\": {\"units_added\": 0, \"units_modified\": 0, \"files\": []},\n  \"compat_pointer\": {\"path\": \"structural_index.jsonl\", \"full_export\": true, \"symbol_count\": 0},\n  \"created_at\": \"\u003CISO 8601>\",\n  \"build_duration_ms\": 0\n}\n```\n\n**Atomic commit**: The manifest is written LAST (atomic rename from `tmp\u002F`).\nInterrupted builds leave unreferenced temp objects without corrupting the last\npublished index.\n\n### SQLite Catalog Schema (`catalog.sqlite`)\n\n```sql\nCREATE TABLE IF NOT EXISTS schema_meta (\n    key TEXT PRIMARY KEY, value TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS symbols (\n    symbol_id       TEXT PRIMARY KEY,\n    name            TEXT NOT NULL,\n    qualified_name  TEXT NOT NULL,\n    namespace       TEXT,\n    language        TEXT NOT NULL,\n    file_path       TEXT NOT NULL,\n    start_line      INTEGER NOT NULL,\n    end_line        INTEGER,\n    kind            TEXT NOT NULL,\n    signature       TEXT,\n    backend         TEXT NOT NULL,\n    precision       TEXT NOT NULL CHECK (precision IN ('semantic','typecheck','ast','symbol-only','heuristic','deferred','coverage-only')),\n    corpus          TEXT DEFAULT 'default',\n    partition_key   TEXT,\n    unit_cache_key  TEXT,\n    source_layer    TEXT NOT NULL DEFAULT 'baseline'\n);\n\nCREATE TABLE IF NOT EXISTS call_edges (\n    edge_id     INTEGER PRIMARY KEY AUTOINCREMENT,\n    caller_id   TEXT NOT NULL,\n    callee_id   TEXT,\n    callee_name TEXT NOT NULL,\n    file_path   TEXT NOT NULL,\n    line        INTEGER NOT NULL,\n    edge_kind   TEXT NOT NULL CHECK (edge_kind IN ('direct','indirect','virtual','macro','unresolved')),\n    corpus      TEXT DEFAULT 'default',\n    partition_key TEXT,\n    source_layer TEXT NOT NULL DEFAULT 'baseline',\n    FOREIGN KEY (caller_id) REFERENCES symbols(symbol_id),\n    FOREIGN KEY (callee_id) REFERENCES symbols(symbol_id)\n);\n\nCREATE TABLE IF NOT EXISTS function_boundaries (\n    symbol_id   TEXT PRIMARY KEY,\n    file_path   TEXT NOT NULL,\n    start_line  INTEGER NOT NULL,\n    end_line    INTEGER NOT NULL,\n    signature   TEXT,\n    language    TEXT NOT NULL,\n    FOREIGN KEY (symbol_id) REFERENCES symbols(symbol_id)\n);\n\nCREATE TABLE IF NOT EXISTS coverage (\n    file_path   TEXT NOT NULL,\n    indexed     INTEGER NOT NULL DEFAULT 0,\n    backend     TEXT,\n    precision   TEXT,\n    unit_cache_key TEXT,\n    status      TEXT NOT NULL DEFAULT 'pending',\n    corpus      TEXT DEFAULT 'default',\n    partition_key TEXT,\n    PRIMARY KEY (file_path, corpus)\n);\n\nCREATE TABLE IF NOT EXISTS unit_cache (\n    cache_key       TEXT PRIMARY KEY,\n    unit_id         TEXT NOT NULL,\n    language        TEXT NOT NULL,\n    extractor_name  TEXT NOT NULL,\n    extractor_version TEXT NOT NULL,\n    file_count      INTEGER NOT NULL,\n    symbol_count    INTEGER NOT NULL,\n    edge_count      INTEGER NOT NULL,\n    source_bytes    INTEGER NOT NULL,\n    created_at      TEXT NOT NULL,\n    snapshot_id     TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS deferred_units (\n    unit_id     TEXT PRIMARY KEY,\n    language    TEXT NOT NULL,\n    files       TEXT NOT NULL,\n    priority    INTEGER NOT NULL,\n    reason      TEXT NOT NULL,\n    cache_key   TEXT,\n    created_at  TEXT NOT NULL\n);\n\n-- Indexes: both directions!\nCREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);\nCREATE INDEX IF NOT EXISTS idx_symbols_qualified ON symbols(qualified_name);\nCREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path, start_line);\nCREATE INDEX IF NOT EXISTS idx_symbols_lang ON symbols(language);\nCREATE INDEX IF NOT EXISTS idx_edges_caller ON call_edges(caller_id);\nCREATE INDEX IF NOT EXISTS idx_edges_callee ON call_edges(callee_id);\nCREATE INDEX IF NOT EXISTS idx_edges_callee_name ON call_edges(callee_name);\nCREATE INDEX IF NOT EXISTS idx_edges_file ON call_edges(file_path, line);\nCREATE INDEX IF NOT EXISTS idx_boundaries_file ON function_boundaries(file_path, start_line, end_line);\nCREATE INDEX IF NOT EXISTS idx_coverage_status ON coverage(status);\nCREATE INDEX IF NOT EXISTS idx_deferred_priority ON deferred_units(priority);\n```\n\n**Partitioning**: When symbols exceed 500K or edges exceed 2M, partition into\nshard SQLite files under `shards\u002F` by `lang:{language}:bucket:{NN}` (stable\nhash). Each shard has the same schema. The query helper routes queries to the\ncorrect shard(s) based on the manifest.\n\n### Content-Addressed Cache Key\n\n```python\ncache_key = sha256(\n    schema_version +\n    extractor_name@version +\n    language +\n    compile_context_digest +\n    ordered_input_content_digests +\n    dependency_or_interface_digest\n)\n```\n\n**Critical**: `snapshot_id` is NOT in the cache key. It goes in provenance only.\nThis permits reuse across commits, branches, and snapshots with identical units.\n\n### Canonical Symbol IDs\n\n**Native IDs** (when semantic backend is available):\n\n- SCIP: `scip:{symbol}`\n- Kythe: `kythe:{uri}`\n- clangd: `clangd:{usr}`\n\n**Fallback IDs** (when no semantic backend):\n\n```\nfallback:{language}:{file_path}:{sha256(qualified_name|start_line|signature)[:16]}\n```\n\nThis distinguishes namespaces, overloads, methods, local functions, and\nduplicate names across languages or repositories.\n\n### Edge Kinds\n\nEdge kinds are kept explicitly separate — never silently merged:\n\n- `direct` — statically resolved call to a known symbol\n- `indirect` — function pointer, closure, callback\n- `virtual` — virtual method dispatch (runtime-resolved)\n- `macro` — call introduced by macro expansion\n- `unresolved` — callee name found but no symbol_id resolved\n\n### Per-Language Semantic Units\n\n| Language    | Unit Type        | Compile Context             | Dependency Digest                           |\n| ----------- | ---------------- | --------------------------- | ------------------------------------------- |\n| C\u002FC++       | Compilation unit | compile_commands.json entry | sha256 of transitive header interfaces      |\n| Go          | Package          | go.mod + build tags         | sha256 of imported packages' exported API   |\n| Rust        | Crate            | Cargo.toml + features       | sha256 of extern crate signatures           |\n| Java\u002FKotlin | Compiler batch   | module path + classpath     | sha256 of imported class signatures         |\n| TypeScript  | Project          | tsconfig.json               | sha256 of imported module type declarations |\n| Fallback    | Individual file  | None                        | Empty string                                |\n\n**Fallback Granularity Rule:** For the fallback tier (regex, AST parser, or\nlightweight crawler without a language build system), **each source file MUST be\nits own independent semantic unit**. Never bundle multiple source files into a\nsingle fallback unit. Per-file unit isolation is essential for content-addressed\ncache efficiency — when one file changes in a future snapshot, only that single\nfile's unit invalidates while all unchanged files hit the cache\n(`reused = N-1`).\n\n### Baseline + Delta Overlay\n\n- **Static baseline**: Built once in CI for the complete snapshot.\n- **Incremental cache**: Immutable unit outputs reused across snapshots.\n- **Delta overlay**: Changed\u002Ftarget files indexed locally, layered over\n  baseline.\n- **Remote serving**: Optional when global index is too costly locally.\n\nAt query time, the query helper merges baseline + overlay results. Overlay\nsymbols supersede baseline symbols for the same file. The manifest records\n`provider.kind = \"baseline+overlay\"` with baseline manifest reference and\noverlay unit count.\n\n### Compatibility JSONL Export\n\nThe old `structural_index.jsonl` format is retained as a compatibility export\nonly — it is NOT the query contract. Below the threshold (default 10K records),\na complete JSONL export is emitted. Above the threshold, only a provenance\npointer is written. Large consumers MUST use the query interface.\n\nLine 1 — provenance header:\n\n```json\n{\"_provenance\": true, \"snapshot_id\": \"abc123\", \"tool\": \"tree-sitter\"}\n```\n\nLines 2+ — structural records (one per line, `_type` discriminator):\n\n```json\n{\"_type\": \"function\", \"key\": \"src\u002Fparser.c:parse_input\", \"start_line\": 45, \"end_line\": 120, \"signature\": \"int parse_input(char *buf, size_t len)\", \"calls\": [\"malloc\", \"validate_input\", \"memcpy\"]}\n{\"_type\": \"call_edge\", \"caller\": \"parse_input\", \"callee\": \"malloc\", \"file\": \"src\u002Fparser.c\", \"line\": 78}\n{\"_type\": \"call_edge\", \"caller\": \"main\", \"callee\": \"parse_input\", \"file\": \"src\u002Fmain.c\", \"line\": 203}\n```\n\n**Provenance header fields:**\n\n| Field         | Type   | Description                                                                         |\n| ------------- | ------ | ----------------------------------------------------------------------------------- |\n| `_provenance` | bool   | Always `true` — marks this as the provenance header line                            |\n| `snapshot_id` | string | `SNAPSHOT_ID` the index was built against                                           |\n| `tool`        | string | Backend used (e.g. `\"lsp-clangd\"`, `\"tree-sitter\"`, `\"ctags\"`, `\"regex\"`, `\"grep\"`) |\n\n**Record types:**\n\n| `_type`     | Description                                     | Key fields                                            |\n| ----------- | ----------------------------------------------- | ----------------------------------------------------- |\n| `function`  | Function definition with boundary and signature | `key`, `start_line`, `end_line`, `signature`, `calls` |\n| `call_edge` | A call from caller to callee at file:line       | `caller`, `callee`, `file`, `line`                    |\n\n## Snapshot Safety\n\n1. **Build from `CODE_ROOT`, not live tree (when pinned).** When the snapshot is\n   pinned, the structural index is built from the pinned `CODE_ROOT`, ensuring\n   it reflects the exact bytes the pipeline is analyzing.\n2. **Reuse-on-match.** If `manifest.json` already carries the current\n   `SNAPSHOT_ID` (and `SNAPSHOT_ID` is NOT `\"unknown\"` — in MODE-OFF, always\n   rebuild), reuse it — do not rebuild. Rebuild when `SNAPSHOT_ID` differs or is\n   `\"unknown\"`. Individual units may still be reused from the content-addressed\n   cache. This bounds cost across retries\u002Fcrash-resume.\n3. **Manifest atomicity.** The manifest is written LAST via atomic rename from\n   `tmp\u002F`. Interrupted builds leave unreferenced temp objects without corrupting\n   the last published index. On resume, check `manifest.json` status and\n   `snapshot_id`.\n4. **STALE flag in HALT mode.** When `snapshot_pinned` is false (HALT), the\n   index may be built from the unpinned `CODE_ROOT` but is marked as potentially\n   stale. Consumers treat structural hints as advisory.\n5. **MODE-OFF: build, do not skip.** When `active_snapshot` is absent\n   (MODE-OFF), build against the current directory (cwd) with provenance\n   `snapshot_id` set to `\"unknown\"`. This is the standalone-efficiency case —\n   the index is still useful for the researcher\u002Fplanner even without snapshot\n   pinning. Do NOT return an empty index merely because the snapshot is absent.\n\n## Consumption Contract\n\nThese are runtime instructions for callers — they define how consumers use the\nstructural index. The structural index is a HINT-only enhancement; skills that\ndo not use it behave exactly as they do today.\n\n### Query Interface\n\n**The query helper IS the contract.** All consumers use\n`query_structural_index.py` (or a compatible remote endpoint). The JSONL file is\nNOT the query contract — it is a compatibility export only.\n\n**Operations**:\n\n1. `resolve_symbol(name, language?, file?, namespace?)` →\n   `{results, total, ambiguous, coverage}`\n\n   - Returns ALL matches — no silent selection among ambiguous symbols.\n   - Caller MUST disambiguate before calling `find_callers` \u002F `find_callees`.\n\n2. `find_callers(symbol_id, limit=100, offset=0)` →\n   `{results, total, has_more, coverage}`\n\n   - Bounded, paginated caller lookup.\n   - Each result carries `precision`, `backend`, `edge_kind`.\n   - Empty results carry `coverage.partition_status`.\n\n3. `find_callees(symbol_id, limit=100, offset=0)` →\n   `{results, total, has_more, coverage}`\n\n   - Same shape as `find_callers`.\n\n4. `get_function_boundary(file, line)` →\n   `{symbol_id, start_line, end_line, signature, precision, backend}`\n\n5. `get_coverage(file?)` →\n   `{total_files, indexed_files, failed, deferred, partition_status, backends_used}`\n\n**Key properties**:\n\n- Bounded results and pagination (prevents loading entire index into memory).\n- Explicit name resolution before graph traversal (no silent selection among\n  ambiguous symbols).\n- Precision and backend attached to every result.\n- Coverage attached to empty results.\n- Identical operations for local and remote providers (remote via\n  `MANTIS_STRUCTURAL_INDEX_URL` env var or manifest `provider.kind = \"remote\"`).\n\n**Coverage on empty results**:\n\n| `partition_status` | Meaning                       | Consumer action                                          |\n| ------------------ | ----------------------------- | -------------------------------------------------------- |\n| `complete`         | All files indexed             | \"No indexed callers\" (still run grep per HINT-only rule) |\n| `partial`          | Some files deferred or failed | \"Not fully indexed\" — MUST run grep fallback             |\n| `empty`            | No backend available          | \"Not indexed\" — MUST run grep fallback                   |\n| `failed`           | Backend attempted but failed  | \"Index failed\" — MUST run grep fallback                  |\n\n### mantis-plan\n\n- Use the structural index query helper for function-level dependency fan-out.\n  When planning investigations, call `resolve_symbol()` then `find_callers()` to\n  identify all functions that call into a target — this broadens the audit set\n  beyond single-file analysis.\n- The structural index decides ORDER of investigations (which functions to audit\n  first based on call-graph centrality), never MEMBERSHIP. It may broaden the\n  audit set (safe over-reporting), but must never REMOVE or drop a file; the\n  planner's existing logic remains the membership floor.\n\n### mantis-researcher\n\n- **Wave 1 (Rapid Triage):** Run a repo-wide grep for the function name to build\n  the exhaustive set of candidate call-sites — this is the mandatory floor. Then\n  use the structural index query helper (`resolve_symbol` then `find_callers`)\n  to RANK and prioritize which call-sites to audit first (the index\n  distinguishes actual calls from comments\u002Fstrings\u002Fvariable names). Audit the\n  union of both result sets — the structural index may miss macro-based calls,\n  function pointers, and dynamic dispatch, so grep remains the floor.\n- **Wave 2 (Deep Audit):** Use `get_function_boundary(file, line)` to start with\n  the enclosing function, expanding to callers\u002Fcallees\u002Ffile as needed for\n  cross-function context — this saves context while preserving coverage.\n- If the structural index is absent or empty, fall back to grep-based discovery\n  (today's behavior). The structural index is a coverage HINT only — it improves\n  audit quality but is never required.\n\n## Safety\n\nNon-negotiable invariants:\n\n1. **Agnostic.** Nothing is ever required; no-tool \u002F parse-fail \u002F not-invoked →\n   empty index → grep fallback = today's behavior byte-for-byte. Optional in the\n   Pass Lifecycle Contract; a non-conformant harness simply skips it.\n2. **HINT-only \u002F union \u002F never MEMBERSHIP.** Consumers audit the union with\n   grep; a symbol the index misses must still be reachable by the exhaustive\n   sweep. The structural index decides ORDER, never MEMBERSHIP. The query\n   contract enforces this: `find_callers` returns HINTs, never authoritative\n   membership — consumers MUST union with grep.\n3. **No verdicts, touches no findings.** Cannot violate INV-1 and cannot itself\n   drop a finding.\n4. **Narrow scope: source cross-reference only.** Binary\u002Fbuild-derived\n   reachability (\"is it compiled into production\") is explicitly out of scope —\n   a dev customization, not part of this skill, because absence-from-a-build can\n   hide a real finding (INV-2). Do not fold build-derived reachability into this\n   skill.\n\nA reference blueprint is available at\n[mantis-pipeline-adapter\u002Freferences\u002Fmantis-structural-index.md](..\u002Fmantis-pipeline-adapter\u002Freferences\u002Fmantis-structural-index.md).\nIt is a stub that points to this SKILL.md as the single source of truth — do not\nduplicate spec content there.\n",{"name":5,"description":7},"main","f3162c039ecd9c9d5d8bc404dd1373c7ca3152f7","skills\u002Fmantis-structural-index",237,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fmantis","https:\u002F\u002Fcloud.google.com\u002F","https:\u002F\u002Fgithub.com\u002Fgoogle","https:\u002F\u002Fopensource.google\u002F","Google ❤️ Open Source","2026-11-15 00:33:31",{"source":8,"skillName":5,"commands":60,"prompt":70,"zipAvailable":71},[61,64,67],{"tool":62,"command":63},"npx","npx skills add google\u002Fmantis --skill mantis-structural-index",{"tool":65,"command":66},"bunx","bunx skills add google\u002Fmantis --skill mantis-structural-index",{"tool":68,"command":69},"pnpm","pnpm dlx skills add google\u002Fmantis --skill mantis-structural-index","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fmantis\u002Fblob\u002Fmain\u002Fskills\u002Fmantis-structural-index\u002FSKILL.md",true,[],[74,100,127,138,153,168],{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":44},"01M6992XJHHV0R5N8E392EHRW3","mantis-threat-model","Synthesizes trust boundaries, attack surfaces, and attacker profiles into a living threat model. Use as Stage B of the Knowledge Base generation process, reading architecture and entity definitions from the KB. Don't use for analyzing source code or extracting raw learnings from JSONL files.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fmantis\u002Fblob\u002Fmain\u002Fskills\u002Fmantis-threat-model\u002FSKILL.md",[],[81,82,83,84,85,42,86,87,88,89,90,91,92,93,94,95,96,97,98,99],"mantis threat model","threat model","model","threat","architecture","analyzing","knowledge","kb","files","surfaces","trust","learnings","knowledge base","as","extracting","reading","jsonl","source","source code",{"skillNo":101,"name":102,"nameZh":6,"description":103,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":111,"publishedAt":44},"01M6992TR1BPA5HVF13QRM7EP0","mantis-summarize","Pre-processes the repository by generating security-focused summaries (mantis-summary.md) for each directory to make planning and research more efficient. Use when starting a review campaign to map the codebase before threat modeling and planning. Don't use for executing code reviews, writing test scripts, or patching code.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fmantis\u002Fblob\u002Fmain\u002Fskills\u002Fmantis-summarize\u002FSKILL.md",[106],{"categoryNo":107,"categoryName":108,"taxonomyType":109,"taxonomyTypeDesc":110},"01JBSEEDCAT000000000000021","资料检索与研究","SCENARIO","使用场景",[112,113,114,115,116,117,118,42,119,120,37,84,121,122,123,124,125,126,29,28],"summarize","mantis summarize","writing","directory","review","security","threat modeling","planning","repository","campaign","map","modeling","scripts","make","reviews",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":133,"publishedAt":44},"01M6992KRP2XYWEMV12P5M4GPE","mantis-review","Independently reviews findings and filters out false positives. Use when consolidated findings need validation against the actual source code. Don't use for reproducing crashes or patching code.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fmantis\u002Fblob\u002Fmain\u002Fskills\u002Fmantis-review\u002FSKILL.md",[],[116,134,42,135,37,34,136,137,126,29,98,99,28],"mantis review","validation","filters","false",{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":144,"publishedAt":44},"01M6992GKVGJ2NXEJWYFVCN01Q","mantis-researcher","Audits production source code files based on the strategy in workspace\u002Fplan.json. Use when a review plan exists and you need to perform static analysis and deep-dive reviews of targeted files. Don't use for planning, deduplicating, or writing patches.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fmantis\u002Fblob\u002Fmain\u002Fskills\u002Fmantis-researcher\u002FSKILL.md",[],[145,146,114,116,147,42,119,38,148,149,34,89,150,151,126,29,98,152,99,28],"mantis researcher","researcher","static analysis","plan","strategy","deep","you","static",{"skillNo":154,"name":155,"nameZh":6,"description":156,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":157,"categories":158,"keywords":159,"publishedAt":44},"01M6992D5WXENF3TW68GM5WPHC","mantis-reproduce","Generates and runs crash reproducers to verify security flaws. Use when viable findings exist and you need to write and execute a script or payload to verify the crash. Don't use for code auditing or patching.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fmantis\u002Fblob\u002Fmain\u002Fskills\u002Fmantis-reproduce\u002FSKILL.md",[],[160,161,117,42,37,162,163,164,34,165,166,167,151,29,28],"mantis reproduce","reproduce","verify","write","execute","crash","payload","script",{"skillNo":169,"name":170,"nameZh":6,"description":171,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":172,"categories":173,"keywords":177,"publishedAt":44},"01M69929WWZT1MCG427PSHY7ZA","mantis-report","Generates a human-readable security review packet compiled from confirmed findings and exploit chains. Use at the end of a review cycle to produce stakeholder-facing documentation. Don't use for auditing code or verifying patches directly.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fmantis\u002Fblob\u002Fmain\u002Fskills\u002Fmantis-report\u002FSKILL.md",[174],{"categoryNo":175,"categoryName":176,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000034","行政与办公人员",[178,179,116,117,180,181,42,182,34,183,184,185,186,187,188,29,189],"mantis report","report","security review","documentation","produce","exploit","cycle","review cycle","human","end","at","review packet"]