---
name: support-runtime-reachability
description: "Use to verify that every export in the slice diff has a production caller — the runtime-reach gate. Catches orphan exports (code defined but never reached at runtime) before the slice closes."
---

# Support: Runtime Reachability

## Overview

An export with no production caller is wiring debt: code that exists, passes its own tests against mocks, and ships unreached by production. Pattern review can't catch this — each file looks correct in isolation. This skill walks the slice diff, finds the exports it introduced, and proves each one is actually called.

**Core principle:** wiring is reachability. If production never calls it, it doesn't exist.

**Announce at start:** "I'm using the support-runtime-reachability skill to verify slice exports are wired."

## When to Use

- Per-slice gate after `build-tdd` completes — writes the `runtime-reach` gate result on the slice.
- Before invoking `quality-code-review`'s pattern-conformance pass.
- On-demand when investigating "code shipped but doesn't run".

**Do NOT skip when:**
- The slice "feels small" — the most common orphans are tiny (a hook, a route, a handler).
- Tests pass — mocked tests prove behavior under test, not under production wiring.
- A reviewer already approved the diff — pattern review and reachability are different bug classes.

## Scope

Catches **export-level orphans** in TS/JS and Python: a top-level `export` / `def` / `class` with no production call site and no escape-hatch annotation. Other failure classes are someone else's job:

| Failure | Owner |
|---|---|
| No-op stubs (function called, body empty) | `craft-reviewer` Pass 2 |
| Internal binding misses (handler not bound to JSX prop) | human review |
| Method-on-object misses (`store.setUser` never called, where `store` is the export) | human review |
| Behavioral completeness (caller exists, logic incomplete) | behavioral review |
| Transitive runtime unreachability (caller exists, but its calling path is dead) | the `quality-test-execution` phase |

Do not try to fold these into the runtime-reach result.

## I/O Contract

| Field | Value |
|---|---|
| **Requires** | Slice diff file list, manifest path |
| **Produces** | `runtime-reach` gate result (pass / fail with orphan list) |
| **Feeds into** | `quality-code-review` (Pass 2 + reachability), slice-close gate |
| **Updates manifest** | `slice_graph.slices.<id>.gates.runtime-reach.{status, gate-passed}` |

## Process

### Step 0: locate inputs

Discover what the script needs before invoking it. Each input has a fallback if the primary source isn't available.

| Input | How to find |
|---|---|
| Repo root | `git rev-parse --show-toplevel`, or `${CLAUDE_PROJECT_DIR}` if set. |
| Manifest path | The single in-progress manifest under `.forge/work/*/*/manifest.yaml`. If multiple, ask the user which slice graph to validate against. |
| Current slice id | `slice_graph.current_slice` field of that manifest. If the manifest has no `slice_graph` block, surface the gap and stop — this gate has nothing to attach its result to. |
| Slice-parent ref | `slice_graph.slices.<current>.depends_on[0]`'s commit pointer if recorded; otherwise fall back to `origin/main`. If the repo has no `origin/main`, ask the user which ref to diff against. |
| Script path | `<repo-root>/.claude/skills/support-runtime-reachability/scripts/check.mjs`. If missing, the skill isn't installed in this project — surface the gap. |

### Step 1: collect slice diff files

```bash
git -C <repo-root> diff --name-only <slice-parent>...HEAD \
  -- '*.ts' '*.tsx' '*.js' '*.jsx' '*.mjs' '*.cjs' '*.py'
```

Skip deleted files. If the diff is empty (slice has no code changes), the gate passes trivially with `exports_found: 0`.

### Step 2: invoke the check script

```bash
node "<repo-root>/.claude/skills/support-runtime-reachability/scripts/check.mjs" \
  --files <comma-separated-paths-from-step-1> \
  --root "<repo-root>"
```

Output: JSON to stdout, exit 0 on success and exit 2 on internal error. The `exports` array reports per-export production callers, test callers, and any escape-hatch annotation found.

### Step 3: cross-check annotations against the manifest

The script reports what it sees; you decide validity using the slice graph.

**Decision rules:**

| Script reports | Manifest cross-check | Verdict |
|---|---|---|
| `production_callers ≥ 1` | — | reachable |
| `production_callers = 0`, `annotation = null` | — | **orphan** |
| `annotation.kind = "untraceable"`, `value` non-empty | — | annotated, pass |
| `annotation.kind = "untraceable"`, `value` empty/whitespace | — | **malformed annotation** |
| `annotation.kind = "gated-pending"`, slice exists, status ∉ {complete} | — | annotated, pass |
| `annotation.kind = "gated-pending"`, slice missing OR status `complete` | — | **stale annotation** |

### Step 4: write the gate result

| Outcome | Manifest write |
|---|---|
| All exports reachable or validly annotated | `gates.runtime-reach: { status: complete, gate-passed: true }` |
| Any orphan / stale / malformed | `gates.runtime-reach: { status: complete, gate-passed: false }` |

### Step 5: report

If pass: one line — `runtime-reach: <N> exports checked, all wired.`

If fail: structured report:

```markdown
## runtime-reach: FAILED (<N> findings)

### Orphan exports
1. `src/handlers/cases.ts:3` exports `casesRouter` (const)
   Fix: wire it (e.g. `app.use('/cases', casesRouter)`), annotate `// gated-pending: <slice-id>`, or delete.

### Stale gated-pending
2. `src/future.ts:5` exports `futureHandler` annotated `// gated-pending: phase-2-wiring`,
   but slice `phase-2-wiring` is `complete`. Wire it now.

### Malformed untraceable
3. `src/lazy.ts:2` exports `lazy` annotated `// untraceable:` with no rationale.
```

The user fixes; re-run.

## Annotations

Place on the line immediately preceding the export, or trailing on the same line:

```ts
// gated-pending: <slice-id>
export function futureHandler() { ... }

export const lateMount = ...; // gated-pending: phase-2-wiring
```

```python
# gated-pending: phase-2-wiring
def future_handler():
    pass
```

| Annotation | Use when |
|---|---|
| `// gated-pending: <slice-id>` | Export is intentionally orphan in this slice; another (named, non-complete) slice will wire it. |
| `// untraceable: <rationale>` | Export is consumed via mechanism the gate can't see: framework auto-discovery (Next.js routes), decorator mounts (NestJS, Spring), reflection-based loaders, defensive exports invoked only by upstream failures. |

Use line comments (`//` for TS/JS, `#` for Python). Block comments and annotations separated from the export by blank lines, JSDoc, or decorators are not parsed — place the annotation immediately above.

## When to Suspect a Finding

When the script's verdict disagrees with what you can verify by reading the code, the cause is usually one of these. Each row's "signal" is something you can observe in the diff or a quick grep — no script-internals knowledge required.

| Signal in the code | Likely cause | Fix |
|---|---|---|
| Script reports orphan, but you can grep a usage in a file that imports via `@/...`, `~/...`, or a non-relative path other than a known npm package | Path-aliased import — script can't resolve the alias to the export's file | Annotate `// untraceable: path-alias-consumer` |
| Script reports orphan, but the only usage is inside an `import('./X')` call | Dynamic import not detected | Annotate `// untraceable: dynamic-import` |
| Script reports orphan on a Next.js route, NestJS controller, Spring-annotated handler, or similar framework-mounted file | Framework auto-discovery — no explicit caller exists | Annotate `// untraceable: framework-mounted` |
| Script reports orphan, but you can grep `export * from '<path-resolving-to-this-file>'` in a re-export module | Wildcard re-export not followed | Convert to a named re-export (`export { name } from ...`), or annotate |
| Two source files in the diff both export the same identifier name | Same-name across modules — caller could be credited to the wrong export | Read the script output's `production_callers[].file` to confirm which export the call resolves to; restructure if needed |
| Script reports a caller, but the caller file both imports `name` AND declares its own `const name = ...` / `function name(...)` | Local shadow — the call resolves to the local declaration, not the import | Rename the local; the script's name-based fallback can't distinguish shadows |

The script's other constraints: regex (not AST); ESM only (no CommonJS `module.exports`); commented-out callers don't count (line comments are stripped).

## Red Flags

**Never:**
- Add a no-op caller (`useless();`) to satisfy reachability — that's gaming, not wiring.
- Annotate every orphan as `untraceable` to silence the gate. High annotation density signals architecture problems, not gate problems.
- Suppress findings to make the slice look clean.

**Always:**
- Resolve orphans before closing the slice.
- Re-run the script after every fix attempt.
- Annotate genuine framework-mounted exports so the next reviewer doesn't relitigate.

## Integration

| Caller | When |
|---|---|
| `quality-code-review` | After Pass 1 safety and Pass 2 craft, before Codex adversarial cross-check. |
| `/feature` and `/refactor` slice-close | Per-slice gate. |
| Ad-hoc | When a reviewer suspects orphan code. |

| Pairs with | For |
|---|---|
| `craft-reviewer` | Stubs the reachability gate accepts as called-but-empty. |
| `verify-manifest` | Validates the slice graph this skill cross-references. |
