# Import graph and boundaries

> manifest.json and ledger.json orchestration, terminal chunk kinds (npm-leaf, oversized-local, external, faced-boundary), facade lifecycle, and quality-gate coverage rules for whole-tree restores.

- Repository: JimLiu/decode-codex
- GitHub: https://github.com/JimLiu/decode-codex
- Human docs: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33
- Complete Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/llms-full.txt

## Source Files

- `.agents/skills/deobfuscate-javascript/SKILL.md`
- `.agents/skills/deobfuscate-javascript/scripts/build-import-graph.ts`
- `.agents/skills/deobfuscate-javascript/scripts/build-symbol-ledger.ts`
- `.agents/skills/deobfuscate-javascript/scripts/ledger.ts`
- `.agents/skills/deobfuscate-javascript/scripts/make-facade.ts`
- `.agents/skills/deobfuscate-javascript/scripts/chunk-classification.ts`
- `.agents/skills/deobfuscate-javascript/scripts/resolve-npm-imports.ts`

---

---
title: "Import graph and boundaries"
description: "manifest.json and ledger.json orchestration, terminal chunk kinds (npm-leaf, oversized-local, external, faced-boundary), facade lifecycle, and quality-gate coverage rules for whole-tree restores."
---

Whole-tree restoration in `deobfuscate-javascript` coordinates work through two on-disk tables under `<target>/.deobfuscate-javascript/_full/`: `manifest.json` (file-level import graph and per-chunk stage flags) and `ledger.json` (per-file symbol checklists and cross-file binding propagation). `build-import-graph.ts` BFS-walks the entry chunk, classifies each dependency as restorable or terminal, and seeds per-chunk workspaces; `build-symbol-ledger.ts` and `ledger.ts` then drive leaf-first, lock-safe fan-out until every reachable `local` chunk reaches `stages.promoted` and `quality-gate.ts <target>` passes.

## Architecture

```mermaid
flowchart TB
  subgraph inputs [Source tree]
    ENTRY[index.html + asset chunks]
  end

  subgraph graph [Graph build]
    BIG[build-import-graph.ts]
    MAN[manifest.json]
    BIG --> MAN
  end

  subgraph symbols [Symbol ledger]
    BSL[build-symbol-ledger.ts]
    LED[ledger.json]
    BSL --> LED
  end

  subgraph staging [_full staging]
    WS[files/basename/original.js]
    LOCKS[locks/basename.stage.lock]
    CP[checkpoints/basename.tsx]
  end

  subgraph public [Public deliverables]
    REST[restored/domain/kebab-file.tsx]
    MAP[IMPORT_MAP.json]
  end

  ENTRY --> BIG
  MAN --> BSL
  MAN --> WS
  LED --> LOCKS
  MAN --> CP
  CP --> REST
  REST --> MAP
  MAN --> MAP
```

| Artifact | Path | Responsibility |
| --- | --- | --- |
| Manifest | `<target>/.deobfuscate-javascript/_full/manifest.json` | Reachable chunk graph, `imports`/`exports` per file, `kind`, `stages`, `organization` |
| Ledger | `<target>/.deobfuscate-javascript/_full/ledger.json` | Per-file `symbols[]`, `crossFileBindings[]`, rename progress |
| Per-chunk workspace | `_full/files/<basename>/` | `original.js`, `symbols.json`, stage outputs, optional `candidate.tsx` |
| Locks | `_full/locks/<basename>.<stage>.lock` | `O_EXCL` agent claims (`ledger.ts claim`) |
| Import map | `<target>/IMPORT_MAP.json` | Hash-basename → public path, exports, boundary metadata |

<Note>
`restored/` is a clean deliverable zone. Mechanical checkpoints under `_full/checkpoints/` are never final output — they must be organized and promoted.
</Note>

## manifest.json schema

`build-import-graph.ts` emits `version: 1` with these top-level fields:

<ResponseField name="entry" type="string">
Entry chunk basename (hash-stripped stem of the discovered app entry).
</ResponseField>

<ResponseField name="rootDir" type="string">
Asset directory used to resolve relative `./chunk-HASH.js` imports.
</ResponseField>

<ResponseField name="targetDir" type="string">
Restore root (for example `restored/`).
</ResponseField>

<ResponseField name="files" type="Record&lt;string, ManifestFile&gt;">
One record per reachable basename. Keys are hash-stripped chunk stems (`app-main`, `clsx`, `rust`).
</ResponseField>

<ResponseField name="edges" type="Array&lt;{from, to}&gt;">
Directed import edges for graph visualization and dependency readiness.
</ResponseField>

<ResponseField name="unresolved" type="Array&lt;{from, reason, raw?}&gt;">
Imports that could not be classified or resolved (dynamic non-literals, missing siblings).
</ResponseField>

### ManifestFile fields

| Field | Meaning |
| --- | --- |
| `kind` | `local`, `oversized-local`, `npm-leaf`, or `external` |
| `depth` | BFS depth from entry |
| `imports` / `exports` | Parsed ESM edges with specifier detail |
| `stages` | Per-chunk pipeline flags (see below) |
| `organization` | Chosen `domain`, `semanticPath`, `classification`, `recipe` |
| `npmPackage` / `vendorSpecifier` | For `npm-leaf`: package name and bare consumer import |
| `owner` / `claimedAt` | Active agent lock metadata |

### Stage flags (`ManifestStages`)

| Stage field | CLI stage | Meaning |
| --- | --- | --- |
| `extracted` | `extract` | Source parsed; ledger symbols indexed |
| `renamed` | `rename` | Smart rename + apply complete |
| `polished` | `polish` | Reading-aid polish complete |
| `finalized` | `finalize` | Typed semantic deliverable drafted |
| `organized` | `organize` | Public domain + path decided |
| `promoted` | `promote` | Deliverable copied to `restored/<domain>/` |
| `skipped` | — | Terminal node; not restored (`npm-leaf`, `external`, `oversized-local`) |
| `faced` | — | Boundary facade installed; satisfied at every stage |

Deep whole-tree completion requires every reachable `local` chunk to reach `promoted` (and `finalized` in deep mode), plus a passing target-level `quality-gate.ts` run.

## ledger.json orchestration

`build-symbol-ledger.ts` walks manifest `kind: local` files, extracts program-scope symbols, and builds `crossFileBindings` that link each consumer import alias to the producer's exported binding and `restoredName`.

### LedgerSymbol lifecycle

| `status` | Meaning |
| --- | --- |
| `pending` | Awaiting rename decision |
| `claimed` | Agent holds the rename lock |
| `done` | `restoredName` assigned |

`ledger.ts propagate-cross-file` pushes producer `restoredName` values into downstream consumer bindings after `mark-done --renames`.

### Dependency-ready scheduling

`ledger.ts frontier` and `ledger.ts next` select work using leaf-first rules:

1. Only `kind: local` files without `stages.faced`.
2. Skip stages already marked done or actively locked.
3. All outgoing `local` dependencies must be at-or-past the target stage, or `faced` (facades satisfy every stage).
4. Prefer deeper depth, more pending symbols, fewer unready deps.

<Steps>
<Step title="Build the graph">

```bash
TARGET=restored
bun .agents/skills/deobfuscate-javascript/scripts/build-import-graph.ts \
  --target "$TARGET" \
  --root ref/webview/assets
```

Auto-discovers the entry from `index.html` when the positional is omitted. Merges into an existing manifest unless `--rebuild` is passed.

</Step>

<Step title="Initialize the ledger">

```bash
bun .agents/skills/deobfuscate-javascript/scripts/build-symbol-ledger.ts \
  --target "$TARGET"
```

Flips `stages.extracted` on indexed local files and writes `ledger.json`.

</Step>

<Step title="Drain the frontier">

```bash
bun .agents/skills/deobfuscate-javascript/scripts/ledger.ts frontier --target "$TARGET"
bun .agents/skills/deobfuscate-javascript/scripts/ledger.ts claim <basename> rename --owner agent-1 --target "$TARGET"
# … run stage scripts …
bun .agents/skills/deobfuscate-javascript/scripts/ledger.ts mark-done <basename> rename --renames renames.json --target "$TARGET"
bun .agents/skills/deobfuscate-javascript/scripts/ledger.ts propagate-cross-file --target "$TARGET"
```

Repeat until `ledger.ts frontier` is empty for all stages through `promote`.

</Step>

<Step title="Verify completion">

```bash
bun .agents/skills/deobfuscate-javascript/scripts/ledger.ts status --target "$TARGET"
bun .agents/skills/deobfuscate-javascript/scripts/quality-gate.ts "$TARGET" --check-format
```

</Step>
</Steps>

### ledger.ts subcommands

| Subcommand | Purpose |
| --- | --- |
| `status` | Files, per-stage counts, symbol totals, promote frontier, blocked candidates |
| `next` / `frontier` | Single best candidate vs full restorable batch |
| `claim` / `release` | Acquire or release `(basename, stage)` lock (exit `75` if held) |
| `mark-done` | Flip stage flag; optionally apply `renames.json` |
| `mark-faced` | Mark boundary chunk as terminal facade |
| `set-organization` | Record `organization` and flip `organized` |
| `propagate-cross-file` | Refresh cross-file binding restored names |
| `reset` | Roll back a stage and pending symbols |

<ParamField body="--target" type="string">
Restore root whose `_full/` subtree holds manifest and ledger.
</ParamField>

<ParamField body="--owner" type="string">
Agent identifier for lock files (default `agent-<pid>`).
</ParamField>

<ParamField body="--steal" type="boolean">
Reclaim locks older than 30 minutes.
</ParamField>

## Terminal chunk kinds

During BFS, `classifyTarget()` and `classifyVendorDataChunk()` mark dependencies that the restoration loop must not descend into. Terminal nodes appear in `manifest.files` for cross-file context but receive no ledger entry, no `_full/files/<basename>/` workspace (except `oversized-local`, which is recorded but not staged), and no rename work.

```text
import edge from consumer
        │
        ├─► local          → enqueue BFS, stage workspace, ledger symbols
        ├─► npm-leaf       → terminal; rewrite consumer imports to bare specifier
        ├─► external       → terminal; bare npm import already
        ├─► oversized-local→ terminal when --max-lines N exceeded; no BFS descent
        └─► faced (stage)  → terminal after mark-faced; facade satisfies deps
```

### npm-leaf

Triggered when:

- The stripped basename matches `CHUNK_NAME_REGISTRY` in `resolve-npm-imports.ts` (for example `clsx`, `react`, `jsx-runtime`, `tslib.es6`, `marked.esm`), or
- `--treat-as-npm` names the basename, or
- `classifyVendorDataChunk()` detects bundled vendor **data** (Shiki grammars, Shiki themes, `3dmol`).

Behavior:

- Manifest: `kind: npm-leaf`, `stages.skipped: true`.
- Vendor data also sets `vendorSpecifier` (for example `@shikijs/langs/rust`) so `promote-organized.ts` rewrites `./rust-HASH.js` to the bare package import.
- Consumers: `resolve-npm-imports.ts` (via `polish.ts` in deep mode) replaces hash-chunk imports with `import … from "package"`.
- The leaf chunk file itself is never restored into `restored/`.

### external

Bare specifiers (`react`, `lodash`) that are not relative chunk paths. Recorded as terminal with `stages.skipped`. Treated like `npm-leaf` for consumer import rewriting.

### oversized-local

Only when `--max-lines N` is a **positive** integer and a non-entry local file exceeds the cap.

| Default | `--max-lines 0` | No cap; every local sibling is restorable (deep default) |
| Quick mode | `--max-lines 5000` | Large siblings become boundaries |
| Override | `--include foo,bar` | Force-restore named basenames despite cap |

Oversized files:

- Parse imports/exports (cross-file bindings still link).
- Do **not** BFS into downstream edges.
- Do **not** receive a workspace copy.
- Start with `stages.skipped: true`.

<Warning>
Deep/full restoration cannot finish with reachable `oversized-local` chunks. `quality-gate.ts` emits `full-restoration-oversized-local`. Use `--max-lines 0` (default) or `--include` for explicit deep restore.
</Warning>

### faced-boundary

Not a `FileKind` — it is a `stages.faced` flag on a `local` (or formerly local) chunk that was boundary-faced instead of fully restored.

Use for genuinely **third-party vendor/runtime** chunks (`app-scope`, `src` (Zod), `vscode-api`, `statsig`, large npm re-export walls) imported by many feature chunks via cryptic aliases.

<Warning>
`ledger.ts mark-faced` refuses basenames that `isLikelyAppChunk()` identifies as app/feature code (`app-main`, `app-shell`, `*-page`, `*-panel`, etc.) unless `--force` is passed. App chunks must be recursively restored, not faced.
</Warning>

After facing:

- Consumers treat the dependency as satisfied at every stage.
- The chunk itself is not promotable (`quality-gate.ts` skips faced entries in promote-drain checks).
- Deep restore remains **incomplete** until the runtime is deep-restored or explicitly scoped out.

## Facade lifecycle

`make-facade.ts` generates boundary artifacts consumed through `restored/boundaries/` and recorded in `IMPORT_MAP.json` under `dependencyBoundaryFacades`. `classifyBoundary()` splits boundaries into two exit paths based on the IMPORT_MAP `vendor` field.

```mermaid
stateDiagram-v2
  [*] --> Classify: large boundary chunk detected
  Classify --> VendorNpm: vendor != runtime
  Classify --> VendorRuntime: vendor == runtime or host bridge

  VendorNpm --> ReexportShim: make-facade --reexport specifier
  ReexportShim --> Done: real @types resolve

  VendorRuntime --> TypedFacade: make-facade (default any exports)
  VendorRuntime --> Passthrough: make-facade --passthrough refPath
  TypedFacade --> markFaced: ledger.ts mark-faced
  Passthrough --> markFaced
  markFaced --> OpenBoundary: consumers unblocked, restore incomplete

  OpenBoundary --> DeepRestore: explicit scope
  DeepRestore --> Organize: set-organization real domain
  Organize --> Promote: promote-organized.ts
  Promote --> Done: moved out of boundaries/
```

### make-facade modes

| Mode | Flag | Output | Terminal state |
| --- | --- | --- | --- |
| Typed facade | (default) | `export declare const X: any` per export token | Open boundary; `mark-faced` |
| Re-export shim | `--reexport <specifier>` | `export … from "<specifier>"` | **Done** for stock npm packages |
| Passthrough | `--passthrough <ref-relpath>` | `@ts-nocheck` re-export of original ref chunk | Runnable interim; still open |

<ParamField body="--name-map" type="string">
JSON map of public consumer alias → real export name in the target module. Required when consumers import cryptic aliases (`appScopeC`).
</ParamField>

<ParamField body="--export-star" type="boolean">
Emit `export * from "<specifier>"` for re-export shims.
</ParamField>

`isKnownTerminalBoundaryChunk()` recognizes permanent vendor boundaries (for example `app-scope`, `@pierre/*`, CodeMirror, ProseMirror, xterm) so `quality-gate.ts` does not treat them as incomplete app features.

### Boundary completion rules

1. **vendor-npm** — Convert to `make-facade.ts --reexport <specifier>`. A remaining `any`-facade triggers `full-restoration-npm-boundary-not-resolved`.
2. **vendor-runtime** — Typed facade or passthrough is scaffolding only. Completion requires deep restore, `set-organization` into a real domain (`host/`, `contexts/`, `utils/`), and `promote-organized.ts` out of `boundaries/`.
3. **`restored/boundaries/`** — Transit directory, not a resting place for finished deliverables.

## build-import-graph.ts flags

<ParamField body="--target" type="string" required>
Restore root directory.
</ParamField>

<ParamField body="--root" type="string">
Asset directory for sibling resolution. Required for entry auto-discovery.
</ParamField>

<ParamField body="--out" type="string">
Manifest output path (default `<target>/.deobfuscate-javascript/_full/manifest.json`).
</ParamField>

<ParamField body="--max-lines" type="number">
Line-count cap for `oversized-local` classification. Default `0` disables the cap.
</ParamField>

<ParamField body="--include" type="string">
Comma-separated basenames to force-restore despite `--max-lines`.
</ParamField>

<ParamField body="--treat-as-npm" type="string">
Extra basenames to classify as `npm-leaf`.
</ParamField>

<ParamField body="--rebuild" type="boolean">
Ignore prior manifest stage merges.
</ParamField>

<ParamField body="--discover" type="boolean">
Auto-discover entry from `index.html` even when a positional is given.
</ParamField>

<ParamField body="--no-entry-check" type="boolean">
Silence the vendor-leaf entry advisory from `check-entry.ts`.
</ParamField>

On build, local files get `_full/files/<basename>/original.js` copied once. Prior `stages`, `organization`, and lock metadata merge forward unless `--rebuild`.

## Quality-gate coverage (whole-tree)

Running `quality-gate.ts <target-dir>` on a directory with `_full/manifest.json` invokes `analyzeFullRestorationCoverage()`, which cross-checks the manifest, `IMPORT_MAP.json`, and on-disk public files.

### Anti-stall checks (no IMPORT_MAP required)

| Code | Condition |
| --- | --- |
| `full-restoration-checkpoints-not-drained` | `_full/checkpoints/` has files not yet `promoted` |
| `full-restoration-organize-incomplete` | `finalized` but not `promoted` |
| `full-restoration-public-file-in-hash-dir` | Public file lives under a hash-basename directory |

Suppress with `--allow-organize-incomplete` for in-progress runs.

### Reachable-chunk coverage (requires IMPORT_MAP.json)

| Code | Condition |
| --- | --- |
| `missing-import-map` | Manifest exists but no `IMPORT_MAP.json` |
| `full-restoration-oversized-local` | Any reachable `oversized-local` chunk |
| `full-restoration-missing-public-output` | Local chunk has no IMPORT_MAP entry |
| `full-restoration-missing-public-target` | Entry lacks `restored` / facade / boundary target |
| `full-restoration-public-target-not-found` | Mapped target path missing on disk |
| `full-restoration-app-feature-boundary` | App chunk marked boundary/facade |
| `full-restoration-npm-boundary-not-resolved` | vendor-npm still an `any`-facade |
| `full-restoration-mechanical-app-feature` | App chunk status `mechanical-readable-restored` |
| `full-restoration-app-feature-not-accepted` | No `finalized` or Stage 3 acceptance record |

Plus per-file inspections on app-feature targets: `@ts-nocheck`, generated facades, empty placeholders, mechanical naming patterns.

### Completion definition

A whole-tree deep restore is done **iff** all three hold:

1. `quality-gate.ts <target-dir>` exits `0`
2. Every reachable `local` chunk has `stages.promoted` (deep mode also expects `stages.finalized`)
3. `ledger.ts frontier --stage promote --target <dir>` is empty

<Check>
Do not substitute a `boundaries/` directory grep or `IMPORT_MAP.status === "done"` scan for the target-level gate — `analyzeFullRestorationCoverage()` is the authoritative whole-tree audit.
</Check>

<RequestExample>

```bash
# After organize → promote and Stage 3 acceptance
bun .agents/skills/deobfuscate-javascript/scripts/quality-gate.ts restored/ \
  --check-format
```

</RequestExample>

<ResponseExample>

```text
quality-gate: PASS restored/IMPORT_MAP.json
```

On failure, issues print as `[code] message` with JSON `detail` arrays listing offending basenames or paths.

</ResponseExample>

## CHUNK_NAME_REGISTRY and import resolution

`resolve-npm-imports.ts` maps hash-stripped basenames to npm packages via `CHUNK_NAME_REGISTRY` and falls back to `ALIAS_REGISTRY` for local-binding names (`useState` → `react`, `_React` → `react` default). Alias rules take precedence over chunk-name rules when both could apply (for example `jsx-runtime-HASH.js` re-exporting both React default and `react/jsx-runtime` named exports).

Precedence for a single import specifier:

1. **Alias rule** — local binding name matches `ALIAS_REGISTRY`
2. **Default-export chunk rule** — chunk has `defaultName` and specifier is not namespace
3. **Named-only chunk** — requires alias match; otherwise left for agent decision

Unmatched specifiers stay on the original `./chunk-HASH.js` path until Stage 3 or manual resolution.

## Troubleshooting

| Symptom | Likely cause | Recovery |
| --- | --- | --- |
| Tiny graph (handful of files) | Wrong entry (vendor leaf) | Re-run `check-entry.ts --discover`; pick `app-main-*` or `index.html` script root |
| `frontier` empty but work remains | Upstream deps not done/faced | `ledger.ts status`; complete producer stages or face genuine vendor boundaries |
| `mark-faced` refused | App/feature basename | Restore the chunk; use `--force` only after confirming vendor/runtime content |
| Checkpoints full, `restored/` empty | Skipped organize → promote | `plan-organize.ts --apply` then `promote-organized.ts` |
| `full-restoration-oversized-local` | Positive `--max-lines` used | Rebuild graph with `--max-lines 0` or `--include` |
| Consumers stuck on cryptic import | npm-leaf not rewritten | Run `resolve-npm-imports.ts` / deep `polish.ts`; extend `CHUNK_NAME_REGISTRY` if needed |

<Info>
For in-progress restores, inspect existing `IMPORT_MAP.json`, `manifest.json`, and `ledger.json` before rebuilding the graph. See the delta-restore workflow when only a scoped chunk needs replacement.
</Info>

## Related pages

<CardGroup>
<Card title="Full tree restoration" href="/full-tree-restoration">
End-to-end deep workflow: entry discovery through promote and quality-gate.
</Card>
<Card title="Workspace and output" href="/workspace-and-output">
Staging layout, promote bar, semantic domains, and IMPORT_MAP.json contract.
</Card>
<Card title="Delta restore and resume" href="/delta-and-resume">
Continue an in-progress restore without rebuilding the reachable graph.
</Card>
<Card title="Orchestration scripts" href="/orchestration-scripts">
CLI reference for build-import-graph, ledger, auto-restore-full, plan-organize, and quality-gate.
</Card>
<Card title="Quality bar and anti-patterns" href="/quality-bar-and-anti-patterns">
Readable vs deep completion criteria and gate failure modes.
</Card>
<Card title="Codex project profile" href="/codex-project-profile">
Default entry, chunk root, and vendor boundary fingerprints for this repository.
</Card>
</CardGroup>
