wiring-extract-static (v1)
Status: PRODUCTION. WP-1 (schemas) + WP-2 (runtime + plug-in API + generic fallback) + WP-3 (FastAPI + Express plug-ins) complete. Driven by bob via S023 wiring-ledger pipeline.
Design document: /path/to/project/docs/plans/2026-04-14-wiring-skills-design.md §5.1
What this skill does
Given a target project directory and a run UUID issued by bob, emit per-run static wiring edges. Edges describe how components in the target project call, route to, import from, or persist into each other. Output:
.wiring/runs/<run_id>/manifest.json — conforms to wiring-source-manifest.v1; lists every extractor source that ran, its status, and gaps
.wiring/runs/<run_id>/static.jsonl — one JSON line per edge, each conforms to wiring-source-edge.v1
- A transition request at
.ledger/requests/<uuid>.request.yaml that bob (sole ledger writer, CB4) consumes
The skill itself NEVER writes to the ledger, NEVER invokes LLMs, NEVER touches .wiring/ root (that is bob's single-creator domain), and NEVER writes outside its run-scoped subdirectory.
Invocation
python3 ~/.claude/skills/wiring-extract-static/scripts/run.py \
--project-dir "$PROJECT_DIR" \
--run-id "<uuid>" \
--claim-uuid "<claim_uuid>" \
[--config .ledger/config.yaml]
Exit codes:
0 — full or partial success. Gaps and per-source skipped/failed statuses are recorded in the manifest; downstream reconcile proceeds.
1 — unrecoverable. Disk full, claim revoked, git write-tree fails, target .wiring/ missing.
Lifecycle
- Validate
claim_uuid (first heartbeat immediate; background thread every 60s).
git write-tree → workspace_tree_hash.
- Detect languages + frameworks (project-documentation/context-detection if importable; otherwise filesystem sniff).
- Create
.wiring/runs/<run_id>/ and write initial manifest.json with status: in_progress.
- For each detected language:
- Load plug-ins via
scripts/plugin_loader.py.
- If a framework plug-in matches (
target_framework in detected frameworks), run it against the language's source files.
- Otherwise run the
generic-treesitter fallback for that language.
- Every yielded edge is schema-validated against
wiring-source-edge.v1 before it is appended; malformed edges are dropped and reported as a gap.
- Canonical component naming: every
src_component / dst_component comes from progress/contract-map.yaml via ComponentResolver. Files that do not match any component's source_paths are recorded as unmapped_path:<path> gaps.
- Write
static.jsonl and the terminal manifest.json atomically (tmp + rename).
- Emit a YAML transition request with
target_stage (SCAFFOLDED at first call, later UNIT_TESTED/INTEGRATED per bob's orchestration).
Directory layout
~/.claude/skills/wiring-extract-static/
├── SKILL.md
├── schemas/
│ ├── wiring-source-edge.v1.json # frozen (WP-1)
│ └── wiring-source-manifest.v1.json # frozen (WP-1)
├── scripts/
│ ├── run.py # CLI entry
│ ├── plugin_loader.py # discovers & validates plug-ins
│ ├── component_resolver.py # contract-map-driven component id lookup
│ ├── heartbeat.py # claim heartbeat thread (invokes claims.py)
│ └── scip_invoke.sh # SCIP subprocess wrapper w/ 120s timeout
├── extractors/
│ ├── generic-treesitter/ # fallback: Python ast + JS/TS regex
│ ├── fastapi/ # WP-3 plug-in
│ └── express/ # WP-3 plug-in
├── references/
│ ├── plugin-author-guide.md # frozen plug-in contract
│ ├── edge-schema.md # (future) human-readable schema notes
│ └── scip-notes.md # (future) SCIP gotchas
├── fixtures/
│ ├── fastapi-minimal/ # WP-3 smoke fixture
│ └── express-minimal/ # WP-3 smoke fixture
└── tests/
├── test_schemas.py # 6/6 (WP-1)
├── test_component_resolver.py
├── test_plugin_loader.py
├── test_generic_treesitter.py
├── test_heartbeat.py
├── test_plugin_fastapi.py # WP-3
└── test_plugin_express.py # WP-3
Plug-in author contract
See references/plugin-author-guide.md. Summary:
plugin.json declares id, semver version, target_framework, languages, edge_kinds, is_fallback, description.
extractor.py exposes extract_edges(project_dir, symbols, source_files, workspace_tree_hash, extractor_version, config, resolve_component) -> Iterator[edge dict].
- Every edge MUST pass schema validation.
edge_id MUST come from ~/.claude/skills/wiring-reconcile/scripts/edge_identity.compute_edge_id(...).
- No LLM calls, no network I/O, no writes outside what the loader stages.
Hard rules (for all callers)
- Canonical component naming: NEVER invent component ids. Resolve via contract map, or use
external:<package> for external deps.
edge_id is five-tuple sha256 truncated to 16 hex — one source of truth in wiring-reconcile/scripts/edge_identity.py. No re-implementation.
- Atomic writes (tmp + rename) for every output file.
- Manifest is always written last with terminal statuses per source.
- Exit 0 on partial success (skipped / failed sources recorded); exit 1 only on unrecoverable errors.
- Single-writer discipline for
static.jsonl — only this skill writes it. Agent-asserted edges go in the sibling asserted/<agent_id>.jsonl, owned by the agent.
Tests
Run everything under tests/ with Python 3.12+. Current pass count:
test_schemas.py — 6/6
test_component_resolver.py — 3/3
test_plugin_loader.py — 6/6
test_generic_treesitter.py — 4/4
test_heartbeat.py — 3/3
test_plugin_fastapi.py — fixture-driven smoke
test_plugin_express.py — fixture-driven smoke
Known gaps (v1)
- SCIP indexers (
scip-python, scip-typescript) are optional; when absent the corresponding source is marked skipped and generic-treesitter covers the language with reduced precision (imports + intra-component calls only).
- Generic tree-sitter fallback uses Python
ast + JS regex. Real tree-sitter Python bindings are optional and deferred to v2 when the dependency lands in env-adoption.
- No Go / Java / Ruby — explicit v2 deferral per design §2.2.
1---2name: wiring-extract-static3description: Emit run-scoped static wiring edges from a target project via SCIP (scip-python, scip-typescript) plus framework plug-ins (FastAPI, Express) or a generic fallback (Python ast + JS regex). Writes `.wiring/runs/<run_id>/static.jsonl` and `manifest.json` conforming to `wiring-source-edge.v1` and `wiring-source-manifest.v1`. Single-writer of `static.jsonl`. Deterministic Python, no LLM calls. Invoked by bob with a claim UUID; heartbeats every 60s via `~/.claude/skills/_meta/claims.py`.4---56# wiring-extract-static (v1)78**Status:** PRODUCTION. WP-1 (schemas) + WP-2 (runtime + plug-in API + generic fallback) + WP-3 (FastAPI + Express plug-ins) complete. Driven by bob via S023 wiring-ledger pipeline.910**Design document:** `/path/to/project/docs/plans/2026-04-14-wiring-skills-design.md` §5.11112## What this skill does1314Given a target project directory and a run UUID issued by bob, emit per-run static wiring edges. Edges describe how components in the target project call, route to, import from, or persist into each other. Output:1516- `.wiring/runs/<run_id>/manifest.json` — conforms to `wiring-source-manifest.v1`; lists every extractor source that ran, its status, and gaps17- `.wiring/runs/<run_id>/static.jsonl` — one JSON line per edge, each conforms to `wiring-source-edge.v1`18- A transition request at `.ledger/requests/<uuid>.request.yaml` that bob (sole ledger writer, CB4) consumes1920The skill itself NEVER writes to the ledger, NEVER invokes LLMs, NEVER touches `.wiring/` root (that is bob's single-creator domain), and NEVER writes outside its run-scoped subdirectory.2122## Invocation2324```bash25python3 ~/.claude/skills/wiring-extract-static/scripts/run.py \26 --project-dir "$PROJECT_DIR" \27 --run-id "<uuid>" \28 --claim-uuid "<claim_uuid>" \29 [--config .ledger/config.yaml]30```3132Exit codes:33- `0` — full or partial success. Gaps and per-source skipped/failed statuses are recorded in the manifest; downstream reconcile proceeds.34- `1` — unrecoverable. Disk full, claim revoked, git `write-tree` fails, target `.wiring/` missing.3536## Lifecycle37381. Validate `claim_uuid` (first heartbeat immediate; background thread every 60s).392. `git write-tree` → `workspace_tree_hash`.403. Detect languages + frameworks (project-documentation/context-detection if importable; otherwise filesystem sniff).414. Create `.wiring/runs/<run_id>/` and write initial `manifest.json` with `status: in_progress`.425. For each detected language:43 - Load plug-ins via `scripts/plugin_loader.py`.44 - If a framework plug-in matches (`target_framework` in detected frameworks), run it against the language's source files.45 - Otherwise run the `generic-treesitter` fallback for that language.466. Every yielded edge is schema-validated against `wiring-source-edge.v1` before it is appended; malformed edges are dropped and reported as a gap.477. Canonical component naming: every `src_component` / `dst_component` comes from `progress/contract-map.yaml` via `ComponentResolver`. Files that do not match any component's `source_paths` are recorded as `unmapped_path:<path>` gaps.488. Write `static.jsonl` and the terminal `manifest.json` atomically (tmp + rename).499. Emit a YAML transition request with `target_stage` (`SCAFFOLDED` at first call, later `UNIT_TESTED`/`INTEGRATED` per bob's orchestration).5051## Directory layout5253```54~/.claude/skills/wiring-extract-static/55├── SKILL.md56├── schemas/57│ ├── wiring-source-edge.v1.json # frozen (WP-1)58│ └── wiring-source-manifest.v1.json # frozen (WP-1)59├── scripts/60│ ├── run.py # CLI entry61│ ├── plugin_loader.py # discovers & validates plug-ins62│ ├── component_resolver.py # contract-map-driven component id lookup63│ ├── heartbeat.py # claim heartbeat thread (invokes claims.py)64│ └── scip_invoke.sh # SCIP subprocess wrapper w/ 120s timeout65├── extractors/66│ ├── generic-treesitter/ # fallback: Python ast + JS/TS regex67│ ├── fastapi/ # WP-3 plug-in68│ └── express/ # WP-3 plug-in69├── references/70│ ├── plugin-author-guide.md # frozen plug-in contract71│ ├── edge-schema.md # (future) human-readable schema notes72│ └── scip-notes.md # (future) SCIP gotchas73├── fixtures/74│ ├── fastapi-minimal/ # WP-3 smoke fixture75│ └── express-minimal/ # WP-3 smoke fixture76└── tests/77 ├── test_schemas.py # 6/6 (WP-1)78 ├── test_component_resolver.py79 ├── test_plugin_loader.py80 ├── test_generic_treesitter.py81 ├── test_heartbeat.py82 ├── test_plugin_fastapi.py # WP-383 └── test_plugin_express.py # WP-384```8586## Plug-in author contract8788See `references/plugin-author-guide.md`. Summary:8990- `plugin.json` declares `id`, semver `version`, `target_framework`, `languages`, `edge_kinds`, `is_fallback`, `description`.91- `extractor.py` exposes `extract_edges(project_dir, symbols, source_files, workspace_tree_hash, extractor_version, config, resolve_component) -> Iterator[edge dict]`.92- Every edge MUST pass schema validation. `edge_id` MUST come from `~/.claude/skills/wiring-reconcile/scripts/edge_identity.compute_edge_id(...)`.93- No LLM calls, no network I/O, no writes outside what the loader stages.9495## Hard rules (for all callers)9697- Canonical component naming: NEVER invent component ids. Resolve via contract map, or use `external:<package>` for external deps.98- `edge_id` is five-tuple sha256 truncated to 16 hex — one source of truth in `wiring-reconcile/scripts/edge_identity.py`. No re-implementation.99- Atomic writes (tmp + rename) for every output file.100- Manifest is always written last with terminal statuses per source.101- Exit 0 on partial success (skipped / failed sources recorded); exit 1 only on unrecoverable errors.102- Single-writer discipline for `static.jsonl` — only this skill writes it. Agent-asserted edges go in the sibling `asserted/<agent_id>.jsonl`, owned by the agent.103104## Tests105106Run everything under `tests/` with Python 3.12+. Current pass count:107108- `test_schemas.py` — 6/6109- `test_component_resolver.py` — 3/3110- `test_plugin_loader.py` — 6/6111- `test_generic_treesitter.py` — 4/4112- `test_heartbeat.py` — 3/3113- `test_plugin_fastapi.py` — fixture-driven smoke114- `test_plugin_express.py` — fixture-driven smoke115116## Known gaps (v1)117118- SCIP indexers (`scip-python`, `scip-typescript`) are optional; when absent the corresponding source is marked `skipped` and generic-treesitter covers the language with reduced precision (imports + intra-component calls only).119- Generic tree-sitter fallback uses Python `ast` + JS regex. Real `tree-sitter` Python bindings are optional and deferred to v2 when the dependency lands in env-adoption.120- No Go / Java / Ruby — explicit v2 deferral per design §2.2.