# Stage 2 scripts reference

> Rename and polish script reference: sourcemap-check, wakaru-normalize, extract filters, smart-rename, apply, polish flags (--rename, --fast, --format), and deep-mode import-resolution passes.

- 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/stages/stage-2-restore.md`
- `.agents/skills/deobfuscate-javascript/reference/naming-heuristics.md`
- `.agents/skills/deobfuscate-javascript/reference/bundler-runtimes.md`
- `.agents/skills/deobfuscate-javascript/scripts/extract.ts`
- `.agents/skills/deobfuscate-javascript/scripts/smart-rename.ts`
- `.agents/skills/deobfuscate-javascript/scripts/polish.ts`
- `.agents/skills/deobfuscate-javascript/scripts/format.ts`
- `.agents/skills/deobfuscate-javascript/scripts/wakaru-normalize.ts`

---

---
title: "Stage 2 scripts reference"
description: "Rename and polish script reference: sourcemap-check, wakaru-normalize, extract filters, smart-rename, apply, polish flags (--rename, --fast, --format), and deep-mode import-resolution passes."
---

Stage 2 of the `deobfuscate-javascript` skill lives under `.agents/skills/deobfuscate-javascript/scripts/` and turns deobfuscated or minified JavaScript into readable source through two phases: **Phase A (rename)** recovers identifier meaning, and **Phase B (polish)** undoes bundler and compiler transforms. All scripts run with Bun (`bun scripts/<name>.ts`) and use BSD-style exit codes (`0` success, `1` I/O, `2` parse error, `64` usage).

```mermaid
flowchart TB
  subgraph phaseA [Phase A — Rename]
    SMC[sourcemap-check.ts]
    WN[wakaru-normalize.ts]
    EX[extract.ts]
    SR[smart-rename.ts]
    AP[apply.ts]
    SMC -->|no map| WN --> EX --> SR --> AP
    SMC -->|map found| SMR[Recover via sourcemap]
  end
  subgraph phaseB [Phase B — Polish]
    PO[polish.ts]
    subgraph fast [--fast reading-aid]
      SRC[strip-react-compiler]
      SIM[simplify]
      JSX[jsx-runtime]
      IDF[inline-defaults]
      NEX[normalize-exports]
    end
    subgraph deep [Deep import-resolution tail]
      RSE[react-shim-elim]
      RNI[resolve-npm-imports]
      NCE[npm-cjs-shim-elim]
      DSE[dead-shim-elim]
    end
    AP --> PO
    PO --> fast
    PO --> deep
    PO --> FM[format.ts]
  end
```

<Note>
Run scripts from the skill directory or pass the full path: `bun .agents/skills/deobfuscate-javascript/scripts/<script>.ts`. Per-chunk staging uses `$WS=<target>/.deobfuscate-javascript/<basename>/` — see [Workspace and output](/workspace-and-output).
</Note>

## Exit codes

| Code | Meaning |
| ---- | ------- |
| `0` | Success |
| `1` | I/O error (missing file, write failure) |
| `2` | JavaScript parse error |
| `64` | Usage error (missing arg, unknown flag, unknown polish step) |

`sourcemap-check.ts` exits `1` when no recoverable sourcemap exists — that signals "proceed with rename," not a hard failure.

---

## Phase A — Pre-rename

### sourcemap-check.ts

Always run first. Detects `//# sourceMappingURL=…` (or `//@`) in the last 8 KB of the file, adjacent `.map` files, inline data-URL maps, and HTTP/absolute/relative map paths.

```bash
bun scripts/sourcemap-check.ts <input.js> [--out report.json]
```

<ResponseField name="stdout/stderr" type="string">
Human-readable summary on stderr. Writes JSON report when `--out` is set.
</ResponseField>

<ResponseField name="exit code" type="number">
`0` when a recoverable map is found; `1` when none exists (continue with rename pipeline).
</ResponseField>

Expected stderr signals:

<RequestExample>
```bash
bun scripts/sourcemap-check.ts ref/webview/assets/app-shell.js
```
</RequestExample>

<ResponseExample>
```text
✓ sourcemap detected
  sourceMappingURL = app-shell.js.map
  map file         = ref/webview/assets/app-shell.js.map
  47 original source(s):
    src/App.tsx
    ...

Recover from sourcemap instead of renaming — preserves original variable names, comments, and file structure.
```
</ResponseExample>

When a map is found, stop the rename pipeline and recover originals via `npx source-map-explorer` or manual `.map` decoding.

### wakaru-normalize.ts

Mechanical pre-rename normalizer wrapping `@wakaru/cli@1.5.0`. Recovers ES6 classes, async/await, optional chaining, destructuring, TS enums, and template literals (~66 rules polish does not cover). **Not** a deobfuscator — run Stage 1 first on obfuscated input.

```bash
bun scripts/wakaru-normalize.ts <input> -o <output> \
  [--level minimal|standard|aggressive] \
  [--unpack[=auto|strict]] [--dce] [--source-map <file>]
```

<ParamField body="--level" type="string" default="standard">
`minimal` for fidelity-critical code; `aggressive` only with verification.
</ParamField>

<ParamField body="--unpack" type="string">
Split a single bundle into modules. **Never** use on an already-split chunk tree in deep/full mode.
</ParamField>

<ParamField body="--dce" type="boolean" default="false">
Dead-code elimination — off by default (can drop side effects).
</ParamField>

<Warning>
Wakaru rewrites the AST and invalidates byte offsets. From this point forward, **extract and rename from `$WS/normalized.js`**, not `original.js`. Keep `--source` pointing at the original bundle for provenance headers.
</Warning>

Graceful degradation: when `wakaru` or `npx` is unavailable, the script copies input to output unchanged, logs a one-line stderr note, and exits `0`. Skip wakaru when a usable sourcemap exists.

---

## extract.ts

Walks the AST with Babel, collects every binding, sorts **largest-scope-first**, and emits a JSON array for agent naming or `smart-rename` planning.

```bash
bun scripts/extract.ts <input.js|-> [--out symbols.json] [--context-size 500] \
  [--top N] [--min-refs N] [--scope-kind Program|FunctionDeclaration|...] \
  [--kind param,let,const,var,hoisted,...] \
  [--name <regex>] [--only-cryptic] [--no-context] [--max-same-scope N] [--compact]
```

### Symbol entry schema

Each array element contains:

| Field | Type | Description |
| ----- | ---- | ----------- |
| `id` | string | Rename key: `<name>@<declStart>` (byte offset) |
| `name` | string | Current identifier |
| `kind` | string | `var`, `let`, `const`, `param`, `hoisted`, `module`, `local`, `unknown` |
| `scopeKind` | string | AST type of enclosing scope (e.g. `Program`, `FunctionDeclaration`) |
| `scopeSize` | number | Scope span in bytes — primary sort key |
| `referenceCount` | number | Reference count (`1` often means scratch) |
| `constant` | boolean | Whether binding is constant |
| `sameScopeBindings` | string[] | Other names in the same scope |
| `context` | string | Source snippet around the declaration (default 500 chars) |

### Filter flags

| Flag | Effect |
| ---- | ------ |
| `--top N` | Keep first N symbols after sort (highest scope impact) |
| `--min-refs N` | Drop bindings with fewer than N references |
| `--scope-kind X` | Keep only bindings in scope of AST type X |
| `--kind a,b,c` | Comma-separated binding kinds |
| `--name <regex>` | Keep only names matching regex |
| `--only-cryptic` | Keep `_0x…`, single/double-letter, letter+digit names |
| `--no-context` | Empty `context` — largest size reduction |
| `--max-same-scope N` | Cap `sameScopeBindings` length |
| `--compact` | Shorthand for `--no-context --max-same-scope 10` — planning extracts only |

**Sizing guidance:** a 1 MB bundle with ~10k symbols and full context can produce ~50 MB JSON. For large files, use:

```bash
bun scripts/extract.ts input.js --out symbols.json \
  --only-cryptic --min-refs 3 --top 200 --max-same-scope 5 --context-size 300
```

If a `sourceMappingURL` comment is detected, extract warns on stderr to run `sourcemap-check.ts` first.

---

## smart-rename.ts

Deterministic mechanical renamer covering ~80% of boring cases in React/Vite/Rollup bundles: component `props`, `forwardRef` `ref`, event handlers, `.map`/`.reduce`/`.sort` iteratees, promise handlers, hook returns (`useIntl` → `intl`), destructured prop aliases, and `clsx` className params.

```bash
bun scripts/smart-rename.ts <input.js|-> [--out renames.json] [--merge existing.json]
```

Output is id-keyed JSON compatible with `apply.ts`:

```json
{
  "e@1234": "props",
  "t@2056": "event",
  "n@3401": "item",
  "k@3404": "index"
}
```

<ParamField body="--merge" type="string">
Existing rename map. Manual entries win on collision; smart-rename fills the remainder.
</ParamField>

Stderr reports counts by reason (`props`, `event`, `iteratee`, `hook-return`, etc.). Full heuristic spec: see naming heuristics in the skill reference.

---

## apply.ts

Applies an id-keyed rename map with scope-aware, collision-safe renaming. Processes largest scopes first; prefixes `_` on reserved words and scope collisions.

```bash
bun scripts/apply.ts <input.js|-> <renames.json> [--out output.js]
```

<ParamField body="renames.json" type="object" required>
Map of `"<name>@<offset>"` → new identifier. Omit or map-to-self for already-meaningful names.
</ParamField>

<ResponseField name="stderr" type="string">
`renamed N symbol(s)` plus optional `skipped` (already same after normalization) and `ignored` (ids with no matching binding).
</ResponseField>

Output is reformatted via `@babel/generator`. Unicode identifiers round-trip as-is. `-` reads stdin; without `--out`, writes code to stdout.

### Multi-pass rename strategy

Do not stop at Program scope. Work outward:

| Pass | Filter | Target |
| ---- | ------ | ------ |
| 1 — Program | `--scope-kind Program` | Exports, top-level helpers, module constants |
| 2 — Function bodies | `--kind let,const,var,hoisted --only-cryptic --min-refs 2` | Hook results, JSX intermediates |
| 3 — Params | `--kind param --only-cryptic --min-refs 1` | Props, events, iteratees (mostly `smart-rename`) |
| 4 — Nested (optional) | `--only-cryptic --min-refs 3` | Deep callbacks, IIFEs |

**Default flow:** one combined extract → write `manual.json` for domain nouns only → `smart-rename --merge manual.json` → one `apply`. **Thorough flow:** re-extract between passes from each pass output so context reflects prior renames.

Verify with `quality-gate.ts --allow-flat` and lexical/binding density sweeps before Phase B.

---

## polish.ts — orchestrator

One-shot Phase B runner. Optionally chains Phase A mechanical rename first.

```bash
bun scripts/polish.ts <input.js|-> [--out output.js] [--report report.json] \
  [--rename] [--fast] [--skip step1,step2] [--stop-after step] \
  [--no-inline] [--max-passes 10] [--prefer local|exported] \
  [--source <original-path>] [--description <one-line summary>] [--format]
```

### Key flags

<ParamField body="--rename" type="boolean" default="false">
Runs `smart-rename` + `apply` before the polish chain. Default one-shot: `polish.ts <file> --rename --fast --source <path> --out draft.tsx --format`.
</ParamField>

<ParamField body="--fast" type="boolean" default="false">
Readable-tier profile. Skips the import-resolution tail (`react-shim-elim`, `resolve-npm-imports`, `npm-cjs-shim-elim`, `dead-shim-elim`). Output will not resolve against `node_modules`.
</ParamField>

<ParamField body="--format" type="boolean" default="false">
Chains `format.ts` (Prettier) on `--out`. Requires `--out` — cannot format stdout.
</ParamField>

<ParamField body="--source" type="string">
Prepends `// Restored from <original-path>` header. Use the original bundle path, not workspace intermediates.
</ParamField>

<ParamField body="--description" type="string">
Adds a second summary header line. Write after reading the polished output.
</ParamField>

<ParamField body="--prefer" type="string" default="local">
`local` or `exported` — passed to `normalize-exports`. Default `local` because Phase A usually picks readable local names.
</ParamField>

<ParamField body="--skip / --stop-after" type="string">
Comma-separated step names. Valid steps listed below.
</ParamField>

### Polish step order

| Step | Profile | Purpose |
| ---- | ------- | ------- |
| `strip-react-compiler` | Both | Remove React Compiler `cache[N]` memoization scaffolding |
| `simplify` | Both | `(0, fn)(args)` → `fn(args)`, backtick → string, shorthand restore, bool-obj collapse |
| `jsx-runtime` | Both | `jsx`/`jsxs`/`jsxDEV` calls → JSX syntax |
| `inline-defaults` | Both | Merge `let A,B; ({A,B}=expr)` → destructure; inline `??`/`=== undefined` defaults |
| `normalize-exports` | Both | Collapse `var X=expr; export {X as Y}` → `export const Y=expr` |
| `react-shim-elim` | Deep only | Collapse Rollup `toESM(loadReact())` shims |
| `resolve-npm-imports` | Deep only | Rewrite vendored chunk imports to bare npm specifiers |
| `npm-cjs-shim-elim` | Deep only | Replace `toESModule(defaultImport())` namespace vars |
| `dead-shim-elim` | Deep only | Drop dead lazy-getter vars and orphaned imports |

Each step runs in a per-step try/catch — errors are logged but do not abort the chain. `--report` writes JSON with per-step stats.

<Info>
When wakaru ran first, several polish passes largely no-op (`jsx-runtime` ≈ wakaru `un_jsx`, simplify `(0,fn)` ≈ `un_indirect_call`). That is expected. Still run Phase B: `strip-react-compiler` has no wakaru equivalent.
</Info>

---

## Phase B — Individual scripts

Run standalone or via `polish.ts`. All accept `<input.js|->` and optional `--out`.

### Reading-aid subset (--fast)

```bash
# strip-react-compiler — run first
bun scripts/strip-react-compiler.ts <input.js|-> [--out output.js]

# simplify — shares Stage 1 script; valuable passes on renamed output
bun scripts/simplify.ts <input.js|-> [--out output.js] [--max-passes 10] [--no-inline]

# jsx-runtime
bun scripts/jsx-runtime.ts <input.js|-> [--out output.js]

# inline-defaults
bun scripts/inline-defaults.ts <input.js|-> [--out output.js]

# normalize-exports
bun scripts/normalize-exports.ts <input.js|-> [--out output.js] [--prefer exported|local]
```

### Deep-mode import-resolution tail

Drop `--fast` from `polish.ts` to run these. They make imports resolve against `node_modules` — a compilability concern, not a readability one.

```bash
# Collapse React namespace shims (Rollup/Rolldown CJS interop)
bun scripts/react-shim-elim.ts <input.tsx|-> [--out output.tsx] [--format]

# Rewrite vendored chunk paths to bare npm specifiers
bun scripts/resolve-npm-imports.ts <input.js|-> [--out output.js] \
  [--no-chunk-lookup] [--no-alias-lookup]

# Collapse npm CJS interop namespace vars after resolve
bun scripts/npm-cjs-shim-elim.ts <input.js|-> [--out output.js]

# Drop dead lazy-getter shim vars (run last)
bun scripts/dead-shim-elim.ts <input.js|-> [--out output.js]
```

`resolve-npm-imports.ts` uses two strategies in order:

1. **Chunk-name lookup** — strips hash suffix from `../clsx-DDuZWq6Y.js`, looks up in `CHUNK_NAME_REGISTRY`.
2. **Alias fallback** — matches local bindings against `ALIAS_REGISTRY` (React APIs, jsx helpers, clsx, tslib, etc.).

Never resolves already-bare specifiers. Leaves specifiers alone on rename collision.

---

## format.ts

Final Prettier pass. Mandatory for hand-off — `@babel/generator` JSX spreads attributes across too many lines.

```bash
bun scripts/format.ts <file-or-dir> [--check] [--glob '**/*.tsx']
```

<ParamField body="--check" type="boolean" default="false">
Dry-run — fail with diff if anything would change.
</ParamField>

<ParamField body="--glob" type="string">
Limit files when target is a directory.
</ParamField>

Runner resolution: `prettier` on PATH → `bunx prettier` → `npx prettier`. Uses `--ignore-path .prettierignore` only (not `.gitignore`) so gitignored `restored/` trees still format. Run **after** multi-export splits — splits overwrite Prettier output.

`polish.ts --format` delegates here when `--out` is set.

---

## Common recipes

<Tabs>
<Tab title="Readable one-shot">

```bash
WS=restored/.deobfuscate-javascript/my-chunk
mkdir -p "$WS" && cp input.js "$WS/original.js"

bun scripts/sourcemap-check.ts "$WS/original.js" || true
bun scripts/wakaru-normalize.ts "$WS/original.js" -o "$WS/normalized.js"
bun scripts/polish.ts "$WS/normalized.js" \
  --rename --fast \
  --source ref/webview/assets/my-chunk.js \
  --out "$WS/draft.tsx" --format
```

Hand-name residue in the draft, then promote to `restored/`.

</Tab>
<Tab title="Manual rename + polish">

```bash
bun scripts/extract.ts "$WS/normalized.js" --out "$WS/symbols.json" \
  --only-cryptic --min-refs 1 --context-size 400
# Write $WS/manual.json with domain nouns only
bun scripts/smart-rename.ts "$WS/normalized.js" --merge "$WS/manual.json" --out "$WS/renames.json"
bun scripts/apply.ts "$WS/normalized.js" "$WS/renames.json" --out "$WS/renamed.js"
bun scripts/polish.ts "$WS/renamed.js" --fast --out "$WS/polished.tsx" --format
```

</Tab>
<Tab title="Deep mode (full import resolution)">

```bash
bun scripts/polish.ts "$WS/renamed.js" \
  --source ref/webview/assets/my-chunk.js \
  --out "$WS/polished.tsx" --format
# No --fast — runs react-shim-elim → resolve-npm-imports → npm-cjs-shim-elim → dead-shim-elim
```

</Tab>
</Tabs>

### Piped composition

```bash
cat bundle.js | bun scripts/apply.ts - renames.json | bun scripts/polish.ts - --fast --out draft.tsx
```

---

## What Stage 2 does not do

Phase B and rename scripts do **not** add TypeScript annotations, invent semantic component APIs, choose `.js` vs `.tsx` extensions, or split multi-export bundles. Those are [Stage 3](/restoration-pipeline) concerns (`semantic-finalize.ts`, `plan-split.ts`).

<AccordionGroup>
<Accordion title="Program-scope-only rename anti-pattern">
Pass 1 with `--scope-kind Program` alone leaves function bodies full of `let k = useIntl()`. Run Pass 2/3 or `smart-rename` until `quality-gate.ts` stops reporting `cryptic-params` / `cryptic-bindings`.
</Accordion>
<Accordion title="Checkpoint vs deliverable">
`$WS/draft.tsx` and gate-failing `renamed.js` are valid workspace checkpoints, not deliverables. Promote only after density sweeps pass.
</Accordion>
<Accordion title="Sourcemap precedence">
If `sourcemap-check.ts` finds a recoverable map, renaming is wasted work. Stage 1 also invalidates sourcemaps — check before Stage 1 on mapped bundles.
</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Restoration pipeline" href="/restoration-pipeline">
Stage 1 → Stage 2 → Stage 3 flow, readable vs deep depth, and the restoration contract.
</Card>
<Card title="Deobfuscate a single file" href="/deobfuscate-single-file">
Readable-tier workflow for isolated chunks without import-graph orchestration.
</Card>
<Card title="Stage 1 scripts reference" href="/stage-1-scripts">
Deobfuscation scripts that must run before Stage 2 on obfuscated input.
</Card>
<Card title="Pipeline caveats" href="/pipeline-caveats">
Ordering rules, wakaru byte-offset traps, polish tier differences, and recovery steps.
</Card>
<Card title="Quality bar and anti-patterns" href="/quality-bar-and-anti-patterns">
Readable and deep-tier completion criteria, naming anti-patterns, and quality-gate failure modes.
</Card>
<Card title="External tools and dependencies" href="/external-tools-and-dependencies">
Bun, Babel, wakaru, Prettier, webcrack, and graceful degradation when binaries are absent.
</Card>
</CardGroup>
