# decode-codex Documentation

> Reference for extracting the installed Codex.app bundle into ./ref and reverse-engineering its minified JavaScript into readable, typed source under ./restored using two agent skills and bundled Bun/Node scripts.

## Context Links

- [Agent index](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/llms.txt)
- [Human interactive docs](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33)
- [GitHub repository](https://github.com/JimLiu/decode-codex)

## Repository Metadata

- Repository: JimLiu/decode-codex

- Generated: 2026-06-27T21:23:39.455Z
- Updated: 2026-06-28T00:40:55.317Z
- Runtime: Grok CLI
- Format: Documentation
- Pages: 20

## Page Index

- 01. [Overview](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/01-overview.md) - What decode-codex exposes: two agent skills (codex-app-ref-refresh and deobfuscate-javascript), the ./ref and ./restored artifact roots, runtime prerequisites, and the shortest end-to-end path from installed Codex.app to readable source.
- 02. [Installation](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/02-installation.md) - Prerequisites and first-time setup: macOS Codex.app at /Applications/Codex.app, Node.js for asar extraction, Bun for deobfuscation scripts, and bun install inside the deobfuscate-javascript skill directory.
- 03. [Quickstart](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/03-quickstart.md) - First successful run: refresh ./ref from Codex.app, invoke deobfuscate-javascript on ref/webview/assets, and verify formatted output under restored/ with expected log lines and directory layout.
- 04. [Restoration pipeline](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/04-restoration-pipeline.md) - Stage 1 deobfuscation, Stage 2 rename and polish, and Stage 3 semantic finalize; readable vs deep depth; whole-tree vs single-file scope; and the restoration contract that defines done.
- 05. [Workspace and output conventions](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/05-workspace-and-output-conventions.md) - Per-chunk staging under restored/.deobfuscate-javascript/, the promote bar for deliverables, semantic-domain subfolders, kebab-case filenames, provenance headers, and the shared restored/IMPORT_MAP.json contract.
- 06. [Import graph and boundaries](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/06-import-graph-and-boundaries.md) - 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.
- 07. [Codex project profile](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/07-codex-project-profile.md) - Layout of the extracted openai-codex-electron bundle: ref/webview/index.html entry discovery, ref/webview/assets chunk root, npm-leaf and Pierre vendor boundaries, and default deep-restore command frame for this repository.
- 08. [Refresh Codex reference source](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/08-refresh-codex-reference-source.md) - Run codex-app-ref-refresh to delete and recreate ./ref from Codex.app Contents/Resources/app.asar, format extracted JS/CSS with Prettier, and verify completion signals before deobfuscation.
- 09. [Deobfuscate a single file](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/09-deobfuscate-a-single-file.md) - Readable-tier workflow for lone minified snippets or isolated chunks: sourcemap-check, wakaru-normalize, extract, smart-rename, polish --fast, format, and promote a single deliverable without import-graph orchestration.
- 10. [Full tree restoration](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/10-full-tree-restoration.md) - Deep default workflow for index.html plus asset trees: entry discovery, build-import-graph, build-symbol-ledger, auto-restore-full checkpoints, plan-organize, promote-organized, and quality-gate over the whole target.
- 11. [Handle obfuscated input](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/11-handle-obfuscated-input.md) - Stage 1 workflow for packed, encoded, or Obfuscator.IO input: detect, unpack, string-array, decode-strings, simplify, control-flow-report, and critical ordering before Stage 2 rename.
- 12. [Delta restore and resume](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/12-delta-restore-and-resume.md) - Continue an in-progress restoration: inspect manifest.json, ledger.json, and IMPORT_MAP.json; run delta/boundary replacement for a scoped chunk; and avoid rebuilding the whole reachable graph.
- 13. [Refresh script reference](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/13-refresh-script-reference.md) - refresh-codex-ref.mjs flags (--dry-run, --skip-format), CODEX_APP_ASAR override, safety guards on ./ref replacement, Prettier verification passes, and expected stdout completion lines.
- 14. [Stage 1 scripts reference](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/14-stage-1-scripts-reference.md) - CLI signatures and behavior for detect, unpack, string-array, decode-strings, simplify, control-flow-report, and the deobfuscate.ts orchestrator with --skip and --stop-after controls.
- 15. [Stage 2 scripts reference](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/15-stage-2-scripts-reference.md) - 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.
- 16. [Orchestration scripts reference](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/16-orchestration-scripts-reference.md) - Full-restoration CLI reference: check-entry exit codes, build-import-graph flags, ledger subcommands, auto-restore-full, plan-organize, promote-organized, quality-gate checks, and semantic-finalize recipes.
- 17. [External tools and dependencies](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/17-external-tools-and-dependencies.md) - Bun and Babel package dependencies, subprocess tools (@electron/asar, prettier, @wakaru/cli, webcrack, source-map-explorer), BSD-style script exit codes, and graceful degradation when binaries are absent.
- 18. [Quality bar and anti-patterns](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/18-quality-bar-and-anti-patterns.md) - Readable and deep-tier completion criteria, naming anti-patterns (program-scope-only rename, mechanical fallback names, checkpoint promotion), Stage 3 acceptance categories E1–E4, and quality-gate failure modes.
- 19. [Pipeline caveats](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/19-pipeline-caveats.md) - Ordering rules, eval safety in unpack, sourcemap precedence, polish tier differences, Prettier gitignore traps, wakaru caveats, vendor-leaf entry misidentification, and other documented failure modes with recovery steps.
- 20. [Maintaining the skill](https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/20-maintaining-the-skill.md) - Self-improvement protocol for deobfuscate-javascript: when to update CHUNK_NAME_REGISTRY, fix scripts with tests, append caveats or codex-ref notes, commit discipline, and legal constraints on extracted Codex source.

## Source File Index

- `.agents/skills/codex-app-ref-refresh/agents/openai.yaml`
- `.agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs`
- `.agents/skills/codex-app-ref-refresh/SKILL.md`
- `.agents/skills/deobfuscate-javascript/package.json`
- `.agents/skills/deobfuscate-javascript/reference/bundler-runtimes.md`
- `.agents/skills/deobfuscate-javascript/reference/caveats.md`
- `.agents/skills/deobfuscate-javascript/reference/codex-ref.md`
- `.agents/skills/deobfuscate-javascript/reference/examples.md`
- `.agents/skills/deobfuscate-javascript/reference/naming-heuristics.md`
- `.agents/skills/deobfuscate-javascript/scripts/acceptance-checklist.md`
- `.agents/skills/deobfuscate-javascript/scripts/apply.ts`
- `.agents/skills/deobfuscate-javascript/scripts/auto-restore-full.ts`
- `.agents/skills/deobfuscate-javascript/scripts/build-import-graph.ts`
- `.agents/skills/deobfuscate-javascript/scripts/build-symbol-ledger.ts`
- `.agents/skills/deobfuscate-javascript/scripts/check-entry.ts`
- `.agents/skills/deobfuscate-javascript/scripts/chunk-classification.ts`
- `.agents/skills/deobfuscate-javascript/scripts/control-flow-report.ts`
- `.agents/skills/deobfuscate-javascript/scripts/decode-strings.ts`
- `.agents/skills/deobfuscate-javascript/scripts/deobfuscate.ts`
- `.agents/skills/deobfuscate-javascript/scripts/detect.ts`
- `.agents/skills/deobfuscate-javascript/scripts/extract.ts`
- `.agents/skills/deobfuscate-javascript/scripts/format.ts`
- `.agents/skills/deobfuscate-javascript/scripts/ledger.ts`
- `.agents/skills/deobfuscate-javascript/scripts/make-facade.ts`
- `.agents/skills/deobfuscate-javascript/scripts/plan-organize.ts`
- `.agents/skills/deobfuscate-javascript/scripts/polish.ts`
- `.agents/skills/deobfuscate-javascript/scripts/prepare-stage-e-review.ts`
- `.agents/skills/deobfuscate-javascript/scripts/promote-final.ts`
- `.agents/skills/deobfuscate-javascript/scripts/promote-organized.ts`
- `.agents/skills/deobfuscate-javascript/scripts/quality-gate.test.ts`
- `.agents/skills/deobfuscate-javascript/scripts/quality-gate.ts`
- `.agents/skills/deobfuscate-javascript/scripts/resolve-npm-imports.ts`
- `.agents/skills/deobfuscate-javascript/scripts/semantic-finalize.ts`
- `.agents/skills/deobfuscate-javascript/scripts/simplify.ts`
- `.agents/skills/deobfuscate-javascript/scripts/smart-rename.ts`
- `.agents/skills/deobfuscate-javascript/scripts/sourcemap-check.ts`
- `.agents/skills/deobfuscate-javascript/scripts/string-array.ts`
- `.agents/skills/deobfuscate-javascript/scripts/unpack.ts`
- `.agents/skills/deobfuscate-javascript/scripts/verify-stage-e-results.ts`
- `.agents/skills/deobfuscate-javascript/scripts/wakaru-normalize.ts`
- `.agents/skills/deobfuscate-javascript/SKILL.md`
- `.agents/skills/deobfuscate-javascript/stages/stage-1-deobfuscate.md`
- `.agents/skills/deobfuscate-javascript/stages/stage-2-restore.md`
- `.agents/skills/deobfuscate-javascript/stages/stage-3-finalize.md`
- `.agents/skills/deobfuscate-javascript/stages/workspace.md`
- `.agents/skills/deobfuscate-javascript/tsconfig.json`
- `.agents/skills/deobfuscate-javascript/workflows/full-obfuscation.md`
- `.agents/skills/deobfuscate-javascript/workflows/full-restoration.md`
- `.agents/skills/deobfuscate-javascript/workflows/huge-single-file.md`
- `.agents/skills/deobfuscate-javascript/workflows/react-vite.md`
- `.agents/skills/deobfuscate-javascript/workflows/small-minified.md`
- `.agents/skills/deobfuscate-javascript/workflows/webpack-bundle.md`
- `.gitignore`
- `README.md`
- `restored/app-shell/thread-app-shell-chrome-entry.ts`
- `restored/app-shell/thread-handoff-operations/index.ts`
- `restored/browser/browser-use-settings/index.tsx`
- `restored/composer/composer-footer-branch-switcher/branch-switcher.tsx`
- `restored/composer/project-selector/project-selector.tsx`
- `restored/runtime/vite-browser-external.ts`
- `restored/ui/tooltip-b/index.tsx`

---

## 01. Overview

> What decode-codex exposes: two agent skills (codex-app-ref-refresh and deobfuscate-javascript), the ./ref and ./restored artifact roots, runtime prerequisites, and the shortest end-to-end path from installed Codex.app to readable source.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/01-overview.md
- Generated: 2026-06-27T21:17:38.135Z

### Source Files

- `README.md`
- `.agents/skills/codex-app-ref-refresh/SKILL.md`
- `.agents/skills/deobfuscate-javascript/SKILL.md`
- `.gitignore`
- `.agents/skills/codex-app-ref-refresh/agents/openai.yaml`

---
title: "Overview"
description: "What decode-codex exposes: two agent skills (codex-app-ref-refresh and deobfuscate-javascript), the ./ref and ./restored artifact roots, runtime prerequisites, and the shortest end-to-end path from installed Codex.app to readable source."
---

decode-codex is a skill-pack repository that turns the installed macOS **Codex.app** bundle into readable source. It ships two agent skills under `.agents/skills/` — `codex-app-ref-refresh` and `deobfuscate-javascript` — that write generated artifacts into `./ref` (extracted app code) and `./restored` (deobfuscated deliverables). Neither directory is version-controlled; both are regenerated by running the skills.

## What this repository exposes

| Surface | Location | Role |
| ------- | -------- | ---- |
| **codex-app-ref-refresh** | `.agents/skills/codex-app-ref-refresh/` | Extracts `Codex.app/Contents/Resources/app.asar` into `./ref` and formats extracted `.js`/`.css` with Prettier |
| **deobfuscate-javascript** | `.agents/skills/deobfuscate-javascript/` | Reverse-engineers bundled JS from `./ref` into readable, well-named source under `./restored/` |
| **Refresh script** | `.agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs` | Direct CLI entry when no agent harness is available |
| **Deobfuscation scripts** | `.agents/skills/deobfuscate-javascript/scripts/*.ts` | Bun-run TypeScript tools orchestrated by the agent during restoration |

The typical workflow is linear: **refresh `./ref` from the app → deobfuscate `./ref` into `./restored`**.

```mermaid
flowchart LR
  subgraph source["Installed app"]
    ASAR["/Applications/Codex.app<br/>Contents/Resources/app.asar"]
  end

  subgraph skill1["codex-app-ref-refresh"]
    EXTRACT["@electron/asar extract"]
    PRETTY["Prettier format"]
  end

  subgraph refRoot["./ref (generated)"]
    HTML["ref/webview/index.html"]
    ASSETS["ref/webview/assets/"]
  end

  subgraph skill2["deobfuscate-javascript"]
    STAGE["Stage 1–3 pipeline"]
    STAGE_WS["restored/.deobfuscate-javascript/"]
  end

  subgraph restoredRoot["./restored (generated)"]
    SEM["Semantic subfolders"]
    MAP["IMPORT_MAP.json"]
  end

  ASAR --> EXTRACT --> refRoot
  EXTRACT --> PRETTY
  ASSETS --> STAGE --> STAGE_WS
  STAGE_WS -->|"organize → promote"| SEM
  STAGE --> MAP
```

## Artifact roots

### `./ref` — extracted Codex app source

`codex-app-ref-refresh` deletes and recreates `./ref` on every run. The bundled web UI lives at:

- Entry: `ref/webview/index.html`
- Chunks: `ref/webview/assets/`

The refresh script refuses to run unless the target resolves to `./ref` under the current working directory, and it skips `ref/node_modules` during Prettier passes.

### `./restored` — readable deliverables

`deobfuscate-javascript` writes finalized files into `./restored/` with:

- Semantic-domain subfolders (`app-shell/`, `composer/`, `utils/`, `icons/`, …)
- kebab-case filenames and PascalCase React component identifiers
- Provenance headers (`// Restored from <path>`)
- A shared `restored/IMPORT_MAP.json` mapping original chunk basenames to public paths

Batch and script output never lands directly in `./restored`. Mechanical checkpoints stage under `restored/.deobfuscate-javascript/` (for example `_full/checkpoints/` or per-chunk workspaces) and promote only after the agent organizes them.

<Note>
Both `ref/` and `restored/` are listed in `.gitignore`. Regenerate them with the skills rather than editing by hand or committing them.
</Note>

## Runtime prerequisites

| Requirement | Used by | Purpose |
| ----------- | ------- | ------- |
| **macOS** with **Codex.app** at `/Applications/Codex.app` | `codex-app-ref-refresh` | Source `app.asar` for extraction |
| **Node.js** | `codex-app-ref-refresh` | Runs `refresh-codex-ref.mjs`; invokes `npx @electron/asar` and `npx prettier` |
| **[Bun](https://bun.sh)** | `deobfuscate-javascript` | Runs TypeScript restoration scripts |
| **Agent harness** (optional) | Both skills | Any agent that reads `.agents/skills/` — Claude Code, Codex, Grok, or similar. Skills are portable file-based instructions, not tied to a specific model provider. |

First-time setup for deobfuscation scripts:

```bash
cd .agents/skills/deobfuscate-javascript
bun install
```

## Shortest end-to-end path

<Steps>
<Step title="Install prerequisites">

Confirm Codex.app is installed at `/Applications/Codex.app`, Node.js and Bun are available, and deobfuscation script dependencies are installed (`bun install` inside `.agents/skills/deobfuscate-javascript`).

</Step>

<Step title="Refresh ./ref from Codex.app">

From the repository root:

```bash
node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs
```

Or ask your agent:

> Use **codex-app-ref-refresh** to refresh `./ref` from the installed Codex app.

Verify completion signals in stdout:

```
Formatting N JS/CSS file(s) with Prettier, ignoring git/prettier ignore files...
Prettier verification complete.
Codex app ref refresh complete.
```

Spot-check that files under `ref/webview/assets/` are multi-line (Prettier-formatted), not single minified lines.

</Step>

<Step title="Deobfuscate ./ref into ./restored">

After `./ref` exists, invoke the deobfuscation skill:

> Use **deobfuscate-javascript** to restore `./ref`.

For this repository, the default bundled-code root is `ref/webview/assets`. The skill auto-discovers the app entry from `ref/webview/index.html` and restores the reachable import tree.

</Step>

<Step title="Verify restored output">

Confirm deliverables under `restored/` with semantic folder layout, kebab-case filenames, and an updated `restored/IMPORT_MAP.json`. For a whole-tree deep restore, `quality-gate.ts` over the target directory must pass before the restore is complete.

</Step>
</Steps>

## Restoration depth at a glance

The deobfuscation skill operates on two independent axes: **scope** (single file vs whole import tree) and **depth** (readable vs deep).

| Axis | Default | Output |
| ---- | ------- | ------ |
| **Scope** | Whole tree from `index.html` | Every reachable project-local chunk under `ref/webview/assets/` |
| **Depth** | Deep for whole-tree restores | Typed `.tsx`, npm-import resolution, import-graph orchestration, acceptance review |
| **Readable opt-out** | Triggered by "quick" / "readable" / "快速", or a lone pasted snippet | Well-named untyped JS/TSX — naming quality is the hard bar |

The per-chunk pipeline runs three stages:

1. **Stage 1 — Deobfuscation** (only if input is obfuscated): unpack, decode strings, simplify control flow.
2. **Stage 2 — Restore to readable**: `smart-rename` + agent hand-naming, then reading-aid polish (`polish.ts --fast`).
3. **Stage 3 — Finalize** (deep mode only): semantic typed rewrite, directory organization, acceptance review.

<Info>
For the Codex project profile in this repo, load `reference/codex-ref.md` inside the deobfuscate skill before choosing a workflow. Vendor npm chunks and bundled data (Shiki grammars, 3Dmol) are detected as terminal `npm-leaf` nodes and left as bare npm imports rather than restored into `./restored/`.
</Info>

## Invoke skills with or without an agent

Skills live under `.agents/skills/` and are provider-neutral: any agent harness that reads skill files can invoke them by name. You can also run bundled scripts directly.

**Via agent** (recommended for deobfuscation — the agent performs semantic renaming):

```
Use codex-app-ref-refresh to refresh ./ref from the installed Codex.app asar and format extracted JS/CSS.
```

```
Use deobfuscate-javascript to restore ./ref.
```

**Via CLI** (refresh only; deobfuscation requires agent orchestration for naming):

<ParamField body="--dry-run" type="flag">
Print resolved paths without deleting or extracting `./ref`.
</ParamField>

<ParamField body="--skip-format" type="flag">
Extract only; skip Prettier formatting. Treat as incomplete unless intentional.
</ParamField>

<ParamField body="CODEX_APP_ASAR" type="string">
Override the default `/Applications/Codex.app/Contents/Resources/app.asar` path.
</ParamField>

## Repository layout

:::files
decode-codex/
├─ .agents/skills/
│  ├─ codex-app-ref-refresh/      # Skill 1: Codex.app → ./ref
│  │  ├─ SKILL.md
│  │  ├─ agents/openai.yaml
│  │  └─ scripts/refresh-codex-ref.mjs
│  └─ deobfuscate-javascript/     # Skill 2: ./ref → ./restored
│     ├─ SKILL.md
│     ├─ scripts/*.ts
│     ├─ workflows/
│     ├─ stages/
│     └─ reference/codex-ref.md
├─ ref/                           # Generated: extracted Codex app (gitignored)
└─ restored/                      # Generated: readable output (gitignored)
:::

## Legal and ethical constraints

<Warning>
This project is for **personal study and interoperability research** of software you have installed. Extracted code is © OpenAI. Respect the Codex app's license and terms of service. Do not redistribute extracted or restored code.
</Warning>

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Prerequisites and first-time setup: Codex.app, Node.js, Bun, and `bun install` in the deobfuscate skill directory.
</Card>
<Card title="Quickstart" href="/quickstart">
First successful run from refresh through deobfuscation with expected log lines and directory layout.
</Card>
<Card title="Restoration pipeline" href="/restoration-pipeline">
Stage 1–3 workflow, readable vs deep depth, whole-tree vs single-file scope, and the restoration contract.
</Card>
<Card title="Refresh Codex reference" href="/refresh-codex-ref">
Run `codex-app-ref-refresh`, verify Prettier completion, and prepare `./ref` for deobfuscation.
</Card>
<Card title="Codex project profile" href="/codex-project-profile">
Layout of the extracted `openai-codex-electron` bundle and default restore targets for this repository.
</Card>
</CardGroup>

---

## 02. Installation

> Prerequisites and first-time setup: macOS Codex.app at /Applications/Codex.app, Node.js for asar extraction, Bun for deobfuscation scripts, and bun install inside the deobfuscate-javascript skill directory.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/02-installation.md
- Generated: 2026-06-27T21:18:13.402Z

### Source Files

- `README.md`
- `.agents/skills/deobfuscate-javascript/package.json`
- `.agents/skills/deobfuscate-javascript/tsconfig.json`
- `.agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs`
- `.agents/skills/codex-app-ref-refresh/SKILL.md`

---
title: "Installation"
description: "Prerequisites and first-time setup: macOS Codex.app at /Applications/Codex.app, Node.js for asar extraction, Bun for deobfuscation scripts, and bun install inside the deobfuscate-javascript skill directory."
---

decode-codex is a macOS-only workflow: extract the installed Codex desktop app into `./ref` with Node.js, then run TypeScript restoration scripts under `.agents/skills/deobfuscate-javascript/` with Bun. First-time setup installs Bun skill dependencies once; the refresh skill pulls `@electron/asar` and Prettier on demand via `npx`.

## Platform requirements

| Requirement | Role |
| ----------- | ---- |
| **macOS** | Codex ships as a macOS desktop app; extraction reads its `app.asar` bundle. |
| **[Codex desktop app](https://chatgpt.com/codex)** at `/Applications/Codex.app` | Source for `Contents/Resources/app.asar`. Override with `CODEX_APP_ASAR` when the bundle lives elsewhere. |
| **Node.js** | Runs `refresh-codex-ref.mjs` and shells out to `npx @electron/asar` and `npx prettier`. |
| **[Bun](https://bun.sh)** | Executes the deobfuscate-javascript TypeScript scripts (`bun scripts/*.ts`). |
| **Agent harness** (optional) | Claude Code, Codex, or any agent that reads `.agents/skills/`. Scripts can also be run by hand. |

<Warning>
Extracted and restored Codex source is for personal study and interoperability research. Do not redistribute extracted or restored code.
</Warning>

## What installs where

The repository has no root `package.json`. Dependencies are scoped to the deobfuscate skill; the refresh skill uses on-demand `npx` tools.

:::files
decode-codex/
├─ .agents/skills/
│  ├─ codex-app-ref-refresh/     # Node only — no bun install
│  │  └─ scripts/refresh-codex-ref.mjs
│  └─ deobfuscate-javascript/     # Bun + Babel — run bun install here
│     ├─ package.json
│     ├─ scripts/*.ts
│     └─ node_modules/            # created by bun install
├─ ref/                           # generated by refresh (gitignored)
└─ restored/                      # generated by deobfuscation (gitignored)
:::

| Location | Installed by | Contents |
| -------- | ------------ | -------- |
| `.agents/skills/deobfuscate-javascript/node_modules/` | `bun install` | `@babel/generator`, `@babel/parser`, `@babel/traverse`, `@babel/types`, and dev types |
| `ref/node_modules/` | Codex `app.asar` extraction | Bundled Electron app dependencies (not managed by this repo) |
| Ephemeral `npx` cache | Refresh and script wrappers | `@electron/asar`, `prettier`, `@wakaru/cli`, `webcrack`, `source-map-explorer` as invoked |

## Runtime prerequisites

### Node.js (refresh skill)

The refresh script is a plain Node ESM module. It requires:

- `node` on `PATH`
- `npx` for `@electron/asar extract` and Prettier formatting

Default asar path:

```
/Applications/Codex.app/Contents/Resources/app.asar
```

<ParamField body="CODEX_APP_ASAR" type="string">
Override the source asar path when Codex.app is not at the default location.
</ParamField>

### Bun (deobfuscate-javascript skill)

All pipeline scripts under `.agents/skills/deobfuscate-javascript/scripts/` run with `bun`. The skill's `tsconfig.json` targets `esnext`, uses `moduleResolution: "bundler"`, and includes only `scripts/**/*.ts`.

`package.json` declares Babel as runtime dependencies and `bun-types` as a dev dependency. External binaries (`prettier`, `@wakaru/cli`, `webcrack`) are **not** listed in `package.json`; wrappers invoke them via `npx`/`bunx` and degrade gracefully when absent.

## First-time setup

<Steps>
<Step title="Clone the repository">

```bash
git clone https://github.com/JimLiu/decode-codex.git
cd decode-codex
```

Work from the repository root for all subsequent steps. The refresh script requires `./ref` to resolve under the current working directory.

</Step>

<Step title="Install Codex.app on macOS">

Install the [Codex desktop app](https://chatgpt.com/codex) so it lands at `/Applications/Codex.app`.

Verify the asar bundle exists:

```bash
test -f /Applications/Codex.app/Contents/Resources/app.asar && echo "asar found"
```

If Codex is installed elsewhere, note the path for `CODEX_APP_ASAR`.

</Step>

<Step title="Install Node.js and Bun">

Install Node.js (any recent LTS or current release with `npx` support) and [Bun](https://bun.sh).

Verify both runtimes:

```bash
node --version
bun --version
```

</Step>

<Step title="Install deobfuscate-javascript dependencies">

From the repository root:

```bash
cd .agents/skills/deobfuscate-javascript
bun install
cd ../../..
```

This installs the Babel packages the scripts import. Re-run after pulling skill dependency changes.

Optional sanity check:

```bash
cd .agents/skills/deobfuscate-javascript
bun test
```

</Step>

<Step title="Verify refresh prerequisites (dry run)">

From the repository root, confirm paths resolve without modifying `./ref`:

```bash
node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs --dry-run
```

<RequestExample>

```bash
node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs --dry-run
```

</RequestExample>

<ResponseExample>

```text
Workspace: /path/to/decode-codex
Source asar: /Applications/Codex.app/Contents/Resources/app.asar
Target ref: /path/to/decode-codex/ref
Dry run only; no files changed.
```

</ResponseExample>

</Step>

<Step title="Point your agent at the repository (optional)">

Skills live under `.agents/skills/`. Configure your agent harness to read that directory, then invoke by skill name:

- `codex-app-ref-refresh` — sync `./ref` from Codex.app
- `deobfuscate-javascript` — restore `./ref` into `./restored/`

The codex-app-ref-refresh skill also ships an OpenAI agent interface at `.agents/skills/codex-app-ref-refresh/agents/openai.yaml` with display name **Codex App Ref Refresh**.

</Step>
</Steps>

## Dependency tiers

| Tier | Tools | Install method | Required for |
| ---- | ----- | -------------- | ------------ |
| **Core** | Node.js, Bun, Codex.app | OS / manual install | Any end-to-end run |
| **Skill-local** | Babel packages | `bun install` in deobfuscate-javascript | All `bun scripts/*.ts` commands |
| **On-demand** | `@electron/asar`, `prettier` | `npx -y` from refresh script | `./ref` extraction and formatting |
| **Optional** | `@wakaru/cli@1.5.0`, `webcrack`, `source-map-explorer` | `npx` from script wrappers | Better normalization and bundle pre-split; auto-skipped when unavailable |

<Tip>
You do not need to pre-install `@electron/asar` or Prettier. The refresh script and format wrappers fetch them via `npx` on first use.
</Tip>

## Agent vs manual execution

| Mode | Refresh | Deobfuscate |
| ---- | ------- | ----------- |
| **Agent** | Ask: *Use codex-app-ref-refresh to refresh `./ref` from the installed Codex app.* | Ask: *Use deobfuscate-javascript to restore `./ref`.* |
| **Manual** | `node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs` | `bun .agents/skills/deobfuscate-javascript/scripts/<script>.ts …` (agent normally orchestrates) |

Manual deobfuscation is possible but the skill expects an agent for semantic renaming in Stage 2 and acceptance review in deep mode.

## Verification checklist

After setup, confirm:

| Check | Expected signal |
| ----- | --------------- |
| Bun deps installed | `.agents/skills/deobfuscate-javascript/node_modules/@babel/parser` exists |
| Dry run succeeds | Prints `Workspace`, `Source asar`, `Target ref`, and `Dry run only; no files changed.` |
| Asar reachable | No `Error: asar file not found` from refresh |
| Refresh cwd guard | Running refresh outside the repo root fails with `ref directory must resolve to ./ref under the current working directory` |

A full extraction run (not just dry-run) additionally logs Prettier formatting lines and ends with `Codex app ref refresh complete.` — see [Refresh Codex reference source](/refresh-codex-ref).

## Troubleshooting

<AccordionGroup>
<Accordion title="Error: asar file not found">

Codex.app is missing or not at the default path. Install Codex or set the override:

```bash
CODEX_APP_ASAR=/path/to/app.asar node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs --dry-run
```

</Accordion>

<Accordion title="ref directory must resolve to ./ref under the current working directory">

The refresh script only replaces `./ref` relative to the current working directory. `cd` to the decode-codex repository root before running it.

</Accordion>

<Accordion title="bun: command not found or script import errors">

Install Bun and run `bun install` inside `.agents/skills/deobfuscate-javascript`. Stage 2 docs also gate on `cd <skill-dir> && bun install` before creating per-chunk workspaces.

</Accordion>

<Accordion title="npx @electron/asar or prettier failed to start">

Confirm Node.js and network access for first-time `npx -y` downloads. Corporate proxies may block registry access.

</Accordion>

<Accordion title="Prettier skips ref/ files during manual checks">

`ref/` is gitignored. Prettier 3 honors `.gitignore` by default. The refresh script passes `--ignore-path /dev/null` to bypass ignore files. Use the same flag for manual verification.

</Accordion>
</AccordionGroup>

## What is not part of installation

- **No `bun install` at the repository root** — only inside the deobfuscate-javascript skill directory.
- **No install step for codex-app-ref-refresh** — it is a single `.mjs` script plus skill metadata.
- **No committed `ref/` or `restored/`** — both are generated artifacts listed in `.gitignore`.
- **No API keys or model-provider setup** — the deobfuscate skill uses the host agent for renaming, not an external LLM API.

## Next

<CardGroup>
<Card title="Quickstart" href="/quickstart">
Run the first successful refresh and deobfuscation pass; verify `./ref` and `./restored/` output.
</Card>
<Card title="Overview" href="/overview">
Two skills, artifact roots, and the shortest path from Codex.app to readable source.
</Card>
<Card title="External tools and dependencies" href="/external-tools-and-dependencies">
Full list of Bun, Babel, and subprocess tools with graceful-degradation behavior.
</Card>
<Card title="Refresh Codex reference source" href="/refresh-codex-ref">
Operational guide for running codex-app-ref-refresh after installation.
</Card>
</CardGroup>

---

## 03. Quickstart

> First successful run: refresh ./ref from Codex.app, invoke deobfuscate-javascript on ref/webview/assets, and verify formatted output under restored/ with expected log lines and directory layout.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/03-quickstart.md
- Generated: 2026-06-27T21:18:14.399Z

### Source Files

- `README.md`
- `.agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs`
- `.agents/skills/deobfuscate-javascript/reference/codex-ref.md`
- `.agents/skills/deobfuscate-javascript/scripts/check-entry.ts`
- `restored/app-shell/thread-app-shell-chrome-entry.ts`
- `restored/composer/project-selector/project-selector.tsx`

---
title: "Quickstart"
description: "First successful run: refresh ./ref from Codex.app, invoke deobfuscate-javascript on ref/webview/assets, and verify formatted output under restored/ with expected log lines and directory layout."
---

decode-codex runs two agent skills in sequence: `codex-app-ref-refresh` extracts and formats the installed Codex.app bundle into `./ref`, then `deobfuscate-javascript` reads `ref/webview/assets` and promotes readable TypeScript into `./restored/`. Both `./ref` and `./restored/` are generated artifacts — regenerate them with the skills rather than editing by hand.

## Prerequisites

| Requirement | Role |
| ----------- | ---- |
| macOS with Codex.app at `/Applications/Codex.app` | Source `app.asar` for `./ref` |
| Node.js | Runs `refresh-codex-ref.mjs` and `npx @electron/asar` / Prettier |
| Bun | Runs deobfuscation TypeScript scripts |
| Agent harness (optional) | Invokes skills by name; scripts also run directly |

First-time setup for deobfuscation script dependencies:

```bash
cd .agents/skills/deobfuscate-javascript
bun install
```

<Note>
Run all commands from the repository root — the directory that owns `./ref` and `./restored/`. See [Installation](/installation) for full prerequisite detail.
</Note>

## Pipeline overview

```mermaid
sequenceDiagram
  participant App as Codex.app
  participant Refresh as codex-app-ref-refresh
  participant Ref as ./ref
  participant Deob as deobfuscate-javascript
  participant Stage as restored/.deobfuscate-javascript/
  participant Out as ./restored/

  App->>Refresh: app.asar extract
  Refresh->>Ref: sync + Prettier format
  Ref->>Deob: ref/webview/assets chunks
  Deob->>Stage: mechanical checkpoints
  Deob->>Out: organize → promote deliverables
```

Mechanical script and batch output lands in `restored/.deobfuscate-javascript/` first. Only organized, gated files promote into the public `restored/` tree.

<Steps>
<Step title="Refresh ./ref from Codex.app">

<Tabs>
<Tab title="Agent">

From the repo root, ask your agent:

> Use **codex-app-ref-refresh** to refresh `./ref` from the installed Codex app.

</Tab>
<Tab title="CLI">

```bash
node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs
```

</Tab>
</Tabs>

The script deletes `./ref`, extracts the asar, then formats every `.js` and `.css` file with Prettier (skipping `ref/node_modules`).

**Refresh completion signals** — treat the run as incomplete unless you see:

| Log line | Meaning |
| -------- | ------- |
| `Formatting N JS/CSS file(s) with Prettier, ignoring git/prettier ignore files...` | Format pass started |
| `Prettier verification complete.` | JS verification passed |
| `Codex app ref refresh complete.` | Refresh finished |

If you see `Prettier verification found N file(s) needing another pass; reformatting...`, that is acceptable when followed by `Prettier verification complete.`

**Spot-check after refresh:**

- `ref/webview/index.html` exists and references hashed chunks under `ref/webview/assets/`
- `ref/package.json` has `"name": "openai-codex-electron"`
- A sample JS file under `ref/` is multi-line (Prettier-formatted), not a single minified line

<Warning>
The refresh script intentionally wipes `./ref`. It refuses to run unless the target resolves to `./ref` under the current working directory.
</Warning>

</Step>

<Step title="Discover the app entry">

Before building the import graph, confirm the entry is the app bootstrap, not a transitive vendor leaf:

```bash
SKILL_DIR=.agents/skills/deobfuscate-javascript
bun "$SKILL_DIR/scripts/check-entry.ts" --discover --root ref/webview/assets
```

**Expected stderr summary:**

```
✓ discovered entry
  index.html        = ref/webview/index.html
  → index-XXXX.js (script, out-degree N)
  ...
```

The command prints the resolved entry path on stdout (for example `ref/webview/assets/app-main-XXXX.js`). Exit codes: `0` = ok, `3` = suspicious vendor/transitive leaf, `1` = I/O or discovery failure.

If exit code is `3`, switch to the `index.html` `<script>` root or a high-fan-out `app-main-*.js` chunk before continuing.

</Step>

<Step title="Run deobfuscate-javascript on ref/webview/assets">

<Tabs>
<Tab title="Agent (recommended)">

After `./ref` exists, ask your agent:

> Use **deobfuscate-javascript** to restore `./ref`.

Equivalent triggers: *"deobfuscate `ref/webview/assets`"*, *"反混淆 `./ref`"*.

For a whole-tree restore (the default when `index.html` plus an asset tree exist), depth defaults to **deep**: typed `.tsx` output, import-graph orchestration, and every reachable project-local chunk promoted. Say **"quick"**, **"readable"**, or **"快速"** to downgrade to untyped readable output.

</Tab>
<Tab title="CLI bootstrap">

Bootstrap the import graph and mechanical checkpoints:

```bash
SKILL_DIR=.agents/skills/deobfuscate-javascript
ENTRY=$(bun "$SKILL_DIR/scripts/check-entry.ts" --discover --root ref/webview/assets)
TARGET=restored
FULL="$TARGET/.deobfuscate-javascript/_full"

bun "$SKILL_DIR/scripts/sourcemap-check.ts" "$ENTRY" || true
bun "$SKILL_DIR/scripts/build-import-graph.ts" "$ENTRY" \
  --target "$TARGET" \
  --root ref/webview/assets \
  --max-lines 0
bun "$SKILL_DIR/scripts/build-symbol-ledger.ts" \
  --target "$TARGET" \
  --manifest "$FULL/manifest.json" \
  --out "$FULL/ledger.json"
bun "$SKILL_DIR/scripts/auto-restore-full.ts" --target "$TARGET" --format
```

`auto-restore-full.ts` writes checkpoints under `restored/.deobfuscate-javascript/_full/checkpoints/` — these are not final deliverables. The agent (or you) must run `plan-organize.ts` and `promote-organized.ts` to drain checkpoints into `restored/`. See [Full tree restoration](/full-tree-restoration) for the complete orchestration loop.

</Tab>
</Tabs>

**Deobfuscation log signals:**

| Source | Log line | Meaning |
| ------ | -------- | ------- |
| `sourcemap-check.ts` | `✓ sourcemap detected` | Stop and recover from the map instead of renaming |
| `sourcemap-check.ts` | `✗ no sourcemap` | Continue with the rename pipeline |
| `check-entry.ts` | `✓ entry looks ok` | Entry passes sanity check |
| `auto-restore-full.ts` | `checkpoints built` | Mechanical checkpoints written |
| `auto-restore-full.ts` | `auto-restore: wrote N file(s) and .../auto-restore-report.json` | Batch checkpoint report saved |
| `quality-gate.ts` | `quality-gate: PASS <path>` | Deliverable passes the gate |

</Step>

<Step title="Verify restored/ output">

Confirm the public deliverable tree, not just staging checkpoints.

**Directory layout** — promoted files live under semantic-domain subfolders with kebab-case names:

:::files
decode-codex/
├─ ref/                              # extracted Codex.app (Skill 1)
│  └─ webview/
│     ├─ index.html
│     └─ assets/*.js
└─ restored/                         # readable output (Skill 2)
   ├─ IMPORT_MAP.json               # shared chunk → public path map
   ├─ app-shell/
   ├─ composer/
   ├─ browser/
   ├─ ui/
   ├─ utils/
   ├─ icons/
   └─ .deobfuscate-javascript/       # staging only (gitignored working area)
      └─ _full/
         ├─ manifest.json
         ├─ ledger.json
         └─ checkpoints/
:::

**File-level checks** — open any promoted file and confirm:

- First line is a provenance header: `// Restored from ref/webview/assets/<chunk>.js`
- Identifiers are semantic (not single-letter or `buttonValue3`-style fallbacks)
- React components use PascalCase exports in kebab-case filenames (`project-selector.tsx` exports `ComposerProjectSelector`)
- Code is Prettier-formatted (wrapped lines, readable JSX)

**Gate verification** on a promoted file or the whole target:

```bash
bun .agents/skills/deobfuscate-javascript/scripts/quality-gate.ts restored/composer/project-selector/project-selector.tsx
# or, after full promotion:
bun .agents/skills/deobfuscate-javascript/scripts/quality-gate.ts restored --check-format
```

**Shared import map** — `restored/IMPORT_MAP.json` maps hashed chunk basenames to public paths and export aliases. One map at the restore root; do not create per-entry maps.

</Step>
</Steps>

## Agent vs CLI responsibilities

| Surface | Refresh `./ref` | Deobfuscate whole tree |
| ------- | --------------- | ---------------------- |
| Agent skill | Fully automated | Orchestrates graph, rename, organize, promote, acceptance |
| Direct CLI | `refresh-codex-ref.mjs` is self-contained | Scripts handle mechanical work; semantic rewrite and promotion need agent judgment |

Skill packs are portable file or repository sources — any agent harness that reads `.agents/skills/` can invoke them without a specific model provider.

## Troubleshooting

<AccordionGroup>
<Accordion title="asar file not found">

Confirm Codex.app is installed at `/Applications/Codex.app`. Override the source path:

```bash
CODEX_APP_ASAR=/path/to/app.asar node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs
```

</Accordion>

<Accordion title="Skipping Prettier formatting">

If refresh logs `Skipping Prettier formatting.`, the extraction is raw and unreadable. Re-run without `--skip-format` unless you explicitly want unformatted output.

</Accordion>

<Accordion title="restored/ is empty but checkpoints exist">

Mechanical checkpoints under `restored/.deobfuscate-javascript/_full/checkpoints/` are not deliverables. Run `plan-organize.ts` then `promote-organized.ts` until files appear under semantic subfolders. `quality-gate.ts` fails with `full-restoration-checkpoints-not-drained` while this stall persists.

</Accordion>

<Accordion title="Only a handful of files restored">

You may have pointed at a vendor leaf instead of the app entry. Re-run `check-entry.ts --discover`; exit code `3` means the chosen chunk is a transitive dependency, not the bootstrap. Use the `index.html` script root or `app-main-*.js`.

</Accordion>

<Accordion title="Resuming instead of rebuilding">

If `restored/IMPORT_MAP.json` and `restored/.deobfuscate-javascript/_full/manifest.json` already exist, inspect them before starting fresh. See [Delta restore and resume](/delta-and-resume).

</Accordion>
</AccordionGroup>

<Warning>
Extracted and restored Codex source is for personal study and interoperability research. Do not redistribute. See the legal note in the repository README.
</Warning>

## Next

<CardGroup>
<Card title="Installation" href="/installation">
Prerequisites and first-time `bun install` for deobfuscation scripts.
</Card>
<Card title="Refresh Codex reference source" href="/refresh-codex-ref">
Detailed refresh workflow, flags, and Prettier verification behavior.
</Card>
<Card title="Full tree restoration" href="/full-tree-restoration">
Deep default workflow: import graph, ledger, organize, promote, and quality-gate.
</Card>
<Card title="Workspace and output conventions" href="/workspace-and-output">
Staging rules, promote bar, provenance headers, and `IMPORT_MAP.json` contract.
</Card>
<Card title="Codex project profile" href="/codex-project-profile">
Layout of the extracted `openai-codex-electron` bundle and default command frame.
</Card>
</CardGroup>

---

## 04. Restoration pipeline

> Stage 1 deobfuscation, Stage 2 rename and polish, and Stage 3 semantic finalize; readable vs deep depth; whole-tree vs single-file scope; and the restoration contract that defines done.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/04-restoration-pipeline.md
- Generated: 2026-06-27T21:17:45.599Z

### Source Files

- `.agents/skills/deobfuscate-javascript/SKILL.md`
- `.agents/skills/deobfuscate-javascript/stages/stage-1-deobfuscate.md`
- `.agents/skills/deobfuscate-javascript/stages/stage-2-restore.md`
- `.agents/skills/deobfuscate-javascript/stages/stage-3-finalize.md`
- `.agents/skills/deobfuscate-javascript/scripts/deobfuscate.ts`
- `.agents/skills/deobfuscate-javascript/scripts/polish.ts`

---
title: "Restoration pipeline"
description: "Stage 1 deobfuscation, Stage 2 rename and polish, and Stage 3 semantic finalize; readable vs deep depth; whole-tree vs single-file scope; and the restoration contract that defines done."
---

The `deobfuscate-javascript` skill turns minified or obfuscated JavaScript into human-maintainable source through three ordered stages, two independent axes (scope and depth), and a staging → organize → promote discipline that keeps `restored/` deliverable-only. Stage 1 and Stage 2 produce the readable restore; Stage 3 is the deep-tier add-on for typed `.tsx` output and acceptance review.

## Two axes: scope and depth

Scope and depth are chosen independently. Scope follows input shape; depth follows user intent.

| Axis | Option | Trigger | Pipeline shape |
|------|--------|---------|----------------|
| **Scope** | Whole tree | `index.html` + sibling asset tree | Auto-discover entry → BFS every reachable project-local chunk via `manifest.json` / `ledger.json` |
| **Scope** | Single file | Lone snippet, no `index.html`/tree | Per-chunk `$WS/` workspace only; no import-graph orchestration |
| **Depth** | Deep (whole-tree default) | "deep", "full", "typed", "production", "完整", "深度", or restore-the-whole-tree | Stage 3 typed rewrite + acceptance review + full npm-import resolution + drain every chunk to `promoted` |
| **Depth** | Readable (quick opt-out) | "quick", "readable", "快速", lone pasted snippet | Meaningful names + reading-aid polish (`polish.ts --fast`); untyped output acceptable |

<Note>
Deep is the completion bar for whole-tree restores, not an upsell. A whole-tree restore is incomplete while any reachable project-local chunk lacks `stages.promoted` or `quality-gate.ts <target>` fails.
</Note>

## Pipeline overview

```mermaid
flowchart TB
  subgraph preflight["Preflight"]
    SM[sourcemap-check]
    CE[check-entry / discover]
  end

  subgraph stage1["Stage 1 — Deobfuscation (obfuscated only)"]
    D1[detect]
    D2[unpack]
    D3[string-array]
    D4[decode-strings]
    D5[simplify]
    D6[control-flow-report]
    DO[deobfuscate.ts orchestrator]
  end

  subgraph stage2["Stage 2 — Restore to readable"]
    WN[wakaru-normalize]
    RN[Rename: extract → smart-rename → apply]
    PO[Polish: strip-react-compiler → simplify → jsx-runtime → …]
    FM[format.ts]
  end

  subgraph stage3["Stage 3 — Finalize (deep only)"]
    SR[Semantic rewrite D0–D7]
    PO2[plan-organize → promote-organized]
    AR[Acceptance review E1–E4]
    QG[quality-gate.ts target audit]
  end

  SM --> CE
  CE -->|obfuscated| stage1
  CE -->|minified| WN
  stage1 --> WN
  WN --> RN --> PO --> FM
  FM -->|readable| PROMOTE[organize → promote → restored/]
  FM -->|deep| SR --> PO2 --> AR --> QG
  PROMOTE -->|deep continues| SR
```

### Critical ordering rules

Byte-rewriting passes invalidate `extract.ts` offsets and `renames.json` keys. The enforced order is:

1. **Stage 1 before Stage 2** on obfuscated input (skip Stage 1 on purely minified input).
2. **wakaru-normalize after Stage 1**, before any `extract` / `apply` — rename from `normalized.js`, never `original.js`.
3. **sourcemap-check first** — a usable `.map` beats any rename pipeline.

<Warning>
Running `simplify` before `string-array` splits rotation IIFEs and breaks string-array matching. Running `extract` before Stage 1 or wakaru produces stale symbol IDs.
</Warning>

## Stage 1 — Deobfuscation

Stage 1 unwinds obfuscator transforms with pure Babel passes — no LLM dependency. It handles Packer/AAEncode/URLEncode packing, Obfuscator.IO string arrays, hex/unicode/base64 escapes, dead code, and opaque predicates.

| Step | Script | Purpose |
|------|--------|---------|
| detect | `detect.ts` | Classify techniques; emit `recommendation` |
| unpack | `unpack.ts` | Unwrap layered packers (uses `new Function`; `--no-eval` to refuse) |
| string-array | `string-array.ts` | Inline `_0x` array lookups; remove rotation IIFE |
| decode-strings | `decode-strings.ts` | Resolve `String.fromCharCode`, `atob`, `\xNN`, `\uNNNN` |
| simplify | `simplify.ts` | Constant folding, dead-code removal, literal inlining |
| control-flow-report | `control-flow-report.ts` | Report-only: `while(true){switch}` flatteners |

The `deobfuscate.ts` orchestrator runs these in order, re-running `detect` after `unpack`, with per-step try/catch so one failure does not abort the rest.

<ParamField body="--skip" type="string">
Comma-separated step names to skip. Valid: `detect`, `unpack`, `string-array`, `decode-strings`, `simplify`, `control-flow-report`.
</ParamField>

<ParamField body="--stop-after" type="string">
Run through the named step, then stop.
</ParamField>

<ParamField body="--no-eval" type="boolean">
Refuse eval-based unpack (exits 0, input unchanged, `evalRefused: true`).
</ParamField>

Control-flow flattening detected by `control-flow-report` requires agent-driven manual rewrite — automatic CFG reconstruction is unreliable.

## Stage 2 — Restore to readable

Stage 2 has two phases: **rename** (where readability is won) and **polish** (undo bundler/compiler transforms).

### Phase A — Rename

<Steps>
<Step title="Check sourcemap">
Run `sourcemap-check.ts`. If a map exists, recover via `source-map-explorer` instead of renaming.
</Step>
<Step title="Archive and normalize">
Create per-chunk workspace `$WS=<target>/.deobfuscate-javascript/<basename>/`, copy `original.js`. Run `wakaru-normalize.ts` (readable tier: default-on; auto-skips when `@wakaru/cli` absent).
</Step>
<Step title="Extract and name">
`extract.ts` emits symbols sorted largest-scope-first. Run `smart-rename.ts` first for mechanical cases (~80%); hand-name the residue. Apply with `apply.ts`.
</Step>
<Step title="Verify density">
Run lexical and binding sweeps. Any single-letter reference count > 50 means Pass 2 (function bodies) was skipped. `quality-gate.ts --allow-flat` catches unfinished renames.
</Step>
</Steps>

The default one-shot combines mechanical rename + reading-aid polish:

```bash
bun scripts/polish.ts "$WS/normalized.js" \
  --rename --fast \
  --source ref/webview/assets/button-bq66r8jD.js \
  --out "$WS/draft.tsx" \
  --format
```

<Warning>
Program-scope-only rename is the top failure mode: top-level exports look fine while function bodies remain `let k = useIntl(), [A,M] = useState(false)`. Keep renaming outward until single-letter density is low.
</Warning>

### Phase B — Polish

`polish.ts` runs a chain of idempotent transforms. Two profiles exist:

| Profile | Flag | Steps included | Purpose |
|---------|------|----------------|---------|
| Reading-aid subset | `--fast` (readable default) | `strip-react-compiler`, `simplify`, `jsx-runtime`, `inline-defaults`, `normalize-exports` | How code *reads* |
| Import-resolution tail | no `--fast` (deep only) | + `react-shim-elim`, `resolve-npm-imports`, `npm-cjs-shim-elim`, `dead-shim-elim` | Imports resolve against `node_modules` |

When `--rename` is set, `polish.ts` runs `smart-rename` + `apply` before the polish chain. `--source` prepends `// Restored from <path>`; `--description` adds a second summary line.

```47:61:.agents/skills/deobfuscate-javascript/scripts/polish.ts
/**
 * The `--fast` (default readable-tier) profile skips the import-resolution /
 * shim-elimination tail. Those passes make the output resolve against
 * `node_modules` (a compilability concern); they do not improve how the code
 * *reads*. Skipping them keeps the readability passes — strip-react-compiler,
 * simplify, jsx-runtime, inline-defaults, normalize-exports — and is the right
 * default when "readable" matters more than "compiles". Drop `--fast` (deep
 * mode) to get resolvable npm imports.
 */
export const FAST_SKIP_STEPS: PolishStep[] = [
  "react-shim-elim",
  "resolve-npm-imports",
  "npm-cjs-shim-elim",
  "dead-shim-elim",
];
```

A well-named Stage 2 file with semantic filename and provenance header is a valid **readable-tier deliverable**. Types and import resolution are Stage 3 work.

## Stage 3 — Finalize (deep mode only)

Stage 3 turns mechanical checkpoints into idiomatic typed TypeScript. It is not required for readable-tier completion.

### Phase A — Semantic rewrite (D0–D7)

| Step | Action |
|------|--------|
| D0 | `quality-gate.ts` pre-filter — cryptic density, fallback names, bundler residue, flat multi-export files |
| D0.5 | Semantic kebab-case public filenames (component identifiers stay PascalCase) |
| D1 | Two-line provenance header |
| D2 | Import paths: bare npm specifiers; semantic paths between finalized siblings |
| D3–D4 | Delete dead runtime stubs; strip dangling sourcemap comments |
| D5 | TypeScript types via hand-edit or `semantic-finalize.ts --recipe icon|button` |
| D6 | `format.ts` directory pass after splits |
| D7 | `quality-gate.ts` exit 0 before acceptance review |

For whole-tree restores, drive Phase A through `plan-organize.ts` → `promote-organized.ts` rather than hand-walking hundreds of checkpoints.

### Phase B — Acceptance review (E1–E4)

After script gates pass, the host agent reads every delivered file end-to-end:

| Category | Readable tier | Deep tier |
|----------|---------------|-----------|
| E1 Naming | Hard bar (both tiers) | Hard bar |
| E2 Readability (Props, forwardRef, IconProps) | Optional | Required |
| E3 Formatting | Optional | Required |
| E4 Structure/imports | Optional | Required |

<Check>
No TODO-header fallback exists in deep mode. Every public file must pass E1–E4 before completion is declared.
</Check>

The review loop: pre-filter with `quality-gate.ts` → read against checklist → repair `NEEDS_FIX` files → re-read changed files only → repeat until clean.

## Scope: whole tree vs single file

### Whole-tree restore (default scope)

When input is an app (`index.html` + asset tree):

1. Auto-discover entry: `check-entry.ts --discover --root <assets-dir>` or omit positional to `build-import-graph.ts`.
2. Build `manifest.json` + `ledger.json` under `restored/.deobfuscate-javascript/_full/`.
3. Iterate `ledger.ts frontier` — extract → rename → polish per chunk with `O_EXCL` locks.
4. Batch checkpoint via `auto-restore-full.ts` (writes to `_full/checkpoints/`, never `restored/`).
5. Organize → promote: `plan-organize.ts` → `promote-organized.ts`.
6. Stage 3 acceptance + `quality-gate.ts <target-dir>`.

Terminal chunk kinds stop BFS but are recorded in the manifest:

| Kind | Treatment |
|------|-----------|
| `npm-leaf` | Consumer imports rewritten to bare specifier; chunk not restored |
| `external` | Same as npm-leaf |
| `oversized-local` | Only with explicit `--max-lines N` (quick mode, not deep) |
| `faced-boundary` | `make-facade.ts` scaffold; open boundary until deep-restored |

Project/feature chunks (`app-shell-*`, pages, panels, hooks) are never faced — they must be recursively restored.

### Single-file restore (fallback scope)

When input is a lone snippet or isolated chunk:

- Same per-file pipeline: Stage 1 (if obfuscated) → wakaru → rename → `polish.ts --rename --fast` → format.
- No `manifest.json` / `ledger.json` orchestration.
- Promote from `$WS/` into `restored/` after organizing the draft.

## Workspace and staging discipline

All intermediates live under the target's hidden workspace; `restored/` is deliverable-only.

:::files
restored/
├── button.tsx                          # promoted deliverable (semantic kebab name)
├── app-shell/                          # semantic-domain subfolder
├── IMPORT_MAP.json                     # one shared map for the whole restore
└── .deobfuscate-javascript/
    ├── button-bq66r8jD/                # per-chunk $WS
    │   ├── original.js
    │   ├── normalized.js
    │   ├── renames.json
    │   └── draft.tsx
    └── _full/                          # whole-tree coordination
        ├── manifest.json
        ├── ledger.json
        ├── checkpoints/                # mechanical batch output (not deliverables)
        └── files/<basename>/           # same layout as single-chunk $WS
:::

<Warning>
Batch/script output (`auto-restore-full.ts`, hash-basename `.tsx`, `--write-target-checkpoints`) is a mechanical checkpoint, never a deliverable. Promote only after the promotion bar is met.
</Warning>

Promotion bar (every tier): semantic names throughout, no fallback names (`buttonValue3`, `contextParam14`), kebab-case filenames, semantic-domain directories, prettier-formatted output. Deep tier adds complete types (`Props` interfaces on exported components).

## Restoration contract

### Readable tier — done when

1. Entry discovered (whole tree) or single file scoped.
2. `sourcemap-check` run; Stage 1 only if obfuscated.
3. wakaru-normalize → rename (`smart-rename` + hand-name residue) → `polish.ts --rename --fast` → `format`.
4. Draft organized and promoted into `restored/` with provenance header and `IMPORT_MAP.json` update.
5. Naming quality bar met: meaningful identifiers, no generated fallback names.

Types, npm-import resolution, and the E2–E4 acceptance loop are optional. An optional naming-only self-review against E1 is available.

### Deep tier — done when

Everything in the readable contract, plus:

1. `manifest.json` + `ledger.json` built for whole-tree scope.
2. Mechanical checkpoint produced (`auto-restore-full.ts` or per-chunk Stage 2).
3. Host-agent semantic rewrite: typed `.tsx`, semantic paths, resolved npm imports.
4. `quality-gate.ts` pre-filter passes (scripts prove not catastrophically broken, not semantically complete).
5. Acceptance review LOOP: every delivered file passes E1–E4.
6. Full-target audit: `quality-gate.ts <target-dir>` exits 0.

**Whole-tree completion proof** — all three must hold:

```
quality-gate.ts <target-dir>  →  exit 0
every reachable local chunk   →  stages.promoted (+ stages.finalized in deep)
ledger.ts frontier --stage promote  →  empty
```

<Info>
Do not infer completion from `IMPORT_MAP.status === "done"`, a `boundaries/` grep, or checkpoint counts alone. The target-level `quality-gate.ts` audit is the proof.
</Info>

### Delta restore

When `IMPORT_MAP.json` and `_full/manifest.json` already exist, prefer delta restore over rebuilding the whole graph: reuse manifest/ledger/map, restore only the scoped chunk, replace the mapped boundary/public file, validate changed paths only.

## Routing decision table

| Input signal | Route |
|--------------|-------|
| Usable `.map` | Recover from sourcemap; skip rename pipeline |
| Obfuscated (`_0x`, Packer, hex walls) | Stage 1 first, then Stage 2 |
| `index.html` + asset tree | Whole-tree restore at deep depth by default |
| Lone snippet / no tree | Single-file readable workflow |
| Existing restore + scoped chunk | Delta/boundary replacement |

## Default one-shot commands

<CodeGroup>

```bash title="Stage 1 orchestrator"
bun scripts/deobfuscate.ts input.js --out deobfuscated.js --report report.json
```

```bash title="Readable-tier Stage 2 one-shot"
bun scripts/polish.ts "$WS/normalized.js" \
  --rename --fast \
  --source ref/webview/assets/chunk-HASH.js \
  --out "$WS/draft.tsx" \
  --format
```

```bash title="Deep-tier polish (full chain)"
bun scripts/polish.ts "$WS/renamed.js" \
  --source ref/webview/assets/chunk-HASH.js \
  --out "$WS/polished.tsx" \
  --format
```

```bash title="Icon recipe (Stage 3)"
bun scripts/semantic-finalize.ts "$WS/polished.tsx" \
  --recipe icon \
  --source "$INPUT" \
  --out restored/icons/download-icon.tsx
```

</CodeGroup>

## Related pages

<CardGroup>
<Card title="Handle obfuscated input" href="/obfuscated-input">
Stage 1 detect → unpack → string-array ordering, eval safety, and decoder-indirection recovery.
</Card>
<Card title="Deobfuscate a single file" href="/deobfuscate-single-file">
Readable-tier workflow for isolated chunks without import-graph orchestration.
</Card>
<Card title="Full tree restoration" href="/full-tree-restoration">
Deep default workflow: entry discovery, manifest/ledger loop, organize → promote.
</Card>
<Card title="Workspace and output conventions" href="/workspace-and-output">
Per-chunk staging, promotion bar, kebab filenames, and `IMPORT_MAP.json`.
</Card>
<Card title="Quality bar and anti-patterns" href="/quality-bar-and-anti-patterns">
Naming anti-patterns, E1–E4 acceptance categories, and quality-gate failure modes.
</Card>
<Card title="Pipeline caveats" href="/pipeline-caveats">
Ordering traps, sourcemap precedence, wakaru guards, and recovery steps.
</Card>
</CardGroup>

---

## 05. Workspace and output conventions

> Per-chunk staging under restored/.deobfuscate-javascript/, the promote bar for deliverables, semantic-domain subfolders, kebab-case filenames, provenance headers, and the shared restored/IMPORT_MAP.json contract.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/05-workspace-and-output-conventions.md
- Generated: 2026-06-27T21:18:53.605Z

### Source Files

- `.agents/skills/deobfuscate-javascript/SKILL.md`
- `.agents/skills/deobfuscate-javascript/stages/workspace.md`
- `.agents/skills/deobfuscate-javascript/scripts/promote-organized.ts`
- `.agents/skills/deobfuscate-javascript/scripts/quality-gate.ts`
- `restored/ui/tooltip-b/index.tsx`
- `restored/browser/browser-use-settings/index.tsx`

---
title: Workspace and output conventions
description: Per-chunk staging under restored/.deobfuscate-javascript/, the promote bar for deliverables, semantic-domain subfolders, kebab-case filenames, provenance headers, and the shared restored/IMPORT_MAP.json contract.
---

decode-codex separates **source** from **deliverables**. Extracted Codex bundle files live under `./ref`; human-readable restored code lives under `./restored`. Between those two roots, every restoration run follows one hard rule: **stage → organize → promote**. Batch and script output never lands directly in `restored/` — it goes into the hidden staging tree first, passes the promotion bar, and only then becomes a public file.

## Two artifact roots

| Root | Role | Typical contents |
|------|------|------------------|
| `./ref` | Pristine extracted source from Codex.app | `ref/webview/assets/*.js`, formatted but still minified/hash-named |
| `./restored` | Clean deliverable zone | Semantic-domain folders (`ui/`, `browser/`, `composer/`, …), one shared `IMPORT_MAP.json` |

There is **one shared restore root** — `restored/` — regardless of which entry chunk triggered the restore. Do not create per-entry folders such as `restored/app-main/`.

## Staging workspace layout

All intermediate artifacts live under a single hidden parent at the target root:

:::files
restored/
├── ui/tooltip-b/index.tsx              # promoted deliverable (public)
├── browser/browser-use-settings/       # promoted deliverable (public)
├── IMPORT_MAP.json                     # shared chunk → path registry
└── .deobfuscate-javascript/            # hidden staging (gitignore this)
    ├── spinner-D37df5tU/               # per-chunk workspace ($WS)
    │   ├── original.js
    │   ├── symbols.json
    │   ├── renames.json
    │   ├── renamed.js
    │   ├── polished.tsx
    │   └── polish-report.json
    └── _full/                          # whole-tree coordination workspace
        ├── manifest.json
        ├── ledger.json
        ├── organize-plan.json
        ├── checkpoints/
        │   └── <basename>.tsx          # mechanical checkpoints — never deliverables
        ├── locks/
        │   └── <basename>.<stage>.lock
        └── files/
            └── <basename>/             # same layout as per-chunk $WS
                ├── original.js
                ├── candidate.tsx       # agent rewrite (preferred at promote)
                └── polished.tsx
:::

### Per-chunk workspace (`$WS`)

For a single chunk or per-file step inside a tree restore, use:

```bash
INPUT=ref/webview/assets/spinner-D37df5tU.js
TARGET=restored
WS="$TARGET/.deobfuscate-javascript/$(basename "$INPUT" .js)"

mkdir -p "$WS"
cp "$INPUT" "$WS/original.js"
```

<ParamField body="basename" type="string" required>
  Input filename minus directory and extension, **with the hash suffix kept**. For `spinner-D37df5tU.js`, the workspace is `restored/.deobfuscate-javascript/spinner-D37df5tU/`.
</ParamField>

Run every Stage 1 and Stage 2 script against `$WS/original.js` and write outputs back into `$WS/`. The host-agent's final typed rewrite (Stage 3) lands in `restored/` only after promotion — never inside the workspace.

**Do not:**
- Use `/tmp/` (not resumable, collides across parallel runs)
- Use the skill's own `archive/` directory (shared installation, not per-target)
- Put final deliverables in the workspace
- Use the deprecated flat `.deobfuscate-javascript-<basename>` layout

Add `.deobfuscate-javascript/` to `.gitignore` if you want intermediates off version control; they regenerate from `original.js` + `renames.json`.

### Whole-tree workspace (`_full/`)

Full-tree restoration adds a shared coordination layer under `restored/.deobfuscate-javascript/_full/`:

```bash
TARGET=restored
FULL="$TARGET/.deobfuscate-javascript/_full"

mkdir -p "$FULL/files" "$FULL/locks"
bun scripts/build-import-graph.ts --target "$TARGET" \
  --root ref/webview/assets --out "$FULL/manifest.json"
bun scripts/build-symbol-ledger.ts --target "$TARGET" --out "$FULL/ledger.json"
```

`_full/files/<basename>/` mirrors the per-chunk `$WS` layout so existing scripts (`extract.ts`, `apply.ts`, `polish.ts`, …) work unchanged. `manifest.json` and `ledger.json` are the coordination tables. `checkpoints/` holds mechanical batch output from `auto-restore-full.ts` — checkpoints are inputs to organize/promote, not deliverables.

The public import map is **not** inside `_full/`. It lives at `restored/IMPORT_MAP.json` and is shared across entries and sessions.

npm-leaf chunks (`clsx-XXXX.js`, `react-XXXX.js`, …) are recorded in `manifest.files` with `kind: "npm-leaf"` but **never** get a `files/<basename>/` workspace.

## Staging → organize → promote

```mermaid
flowchart LR
  ref["ref/webview/assets/*.js"] --> stage[".deobfuscate-javascript/<br/>checkpoints + $WS"]
  stage --> organize["plan-organize.ts<br/>manifest.organization"]
  organize --> promote["promote-organized.ts<br/>quality gate"]
  promote --> restored["restored/<domain>/"]
  promote --> map["restored/IMPORT_MAP.json"]
```

Anything a batch or script emits — `auto-restore-full.ts` checkpoints, hash-basename `.tsx` files, one-shot `polish.ts` sweeps — is a **mechanical checkpoint**, not a deliverable. It must stay under `.deobfuscate-javascript/` until promoted.

<Steps>
<Step title="Build checkpoints in staging">

Run Stage 1 (if obfuscated) → wakaru-normalize → Stage 2 rename/polish → format. For whole trees, `auto-restore-full.ts` writes flat checkpoints to `_full/checkpoints/`. Verify checkpoints exist; verify `restored/` does **not** contain hash-basename files.

</Step>
<Step title="Organize semantic paths">

```bash
bun scripts/plan-organize.ts --target restored
# Review _full/organize-plan.json; fix needs-review rows
bun scripts/plan-organize.ts --target restored --apply
```

`plan-organize.ts` proposes `{ domain, semanticPath, recipe, classification }` per chunk using project-agnostic shape heuristics (icons → `icons/`, buttons → `ui/button.tsx`, single-export → `utils/<kebab>.ts`, vendor/runtime → `boundaries/`). App-feature chunks without a clear shape are flagged `needs-review` for manual domain assignment via `ledger.ts set-organization` or an optional `--domain-map`.

</Step>
<Step title="Promote passing deliverables">

```bash
bun scripts/promote-organized.ts --target restored --dry-run
bun scripts/promote-organized.ts --target restored
```

`promote-organized.ts` drains the promote frontier (producers before consumers). Per chunk it builds the typed deliverable, writes it at the final semantic path, runs `quality-gate.ts`, and on pass records the chunk in `IMPORT_MAP.json` with `status: "done"`. Gate failure rolls back the file and continues — one bad chunk never blocks the batch.

</Step>
<Step title="Audit the whole target">

```bash
bun scripts/quality-gate.ts restored
```

The target-level gate checks manifest coverage, import-map completeness, and anti-stall conditions (checkpoints not drained, files still in hash-named directories).

</Step>
</Steps>

## Promotion bar

A file may enter `restored/` only after it meets the promotion bar. The bar applies in **every tier**; deep mode adds typing requirements on top.

| Criterion | Readable tier | Deep tier (default for whole tree) |
|-----------|---------------|-------------------------------------|
| Semantic identifiers | Required — no mechanical fallbacks (`buttonValue3`, `contextParam14`, …) | Required |
| Filename / directory naming | kebab-case, hash-free | Same |
| Directory structure | Semantic-domain subfolders | Same |
| Formatting | Prettier-formatted (automatic at promote) | Same |
| TypeScript types | Optional (`--tier readable`) | Required — `Props` on exported components, typed public params |

`promote-organized.ts` runs `format.ts` on every deliverable before gating, so promoted files are never raw `@babel/generator` output.

Mechanical checkpoints — even if they parse — **fail** promotion when they retain fallback names, bundler residue, or hash basenames in public paths. The quality gate detects the common stall pattern where `_full/checkpoints/` is full but `restored/` is empty (`full-restoration-checkpoints-not-drained`).

## Semantic-domain subfolders

Public files group by product domain, not by source chunk hash. Original chunk identity is preserved in provenance headers and `IMPORT_MAP.json`, not in directory names.

Common domains in this repository:

| Domain | Typical contents |
|--------|------------------|
| `app-shell/` | Shell chrome, handoff, background processes |
| `browser/` | In-app browser pages and settings |
| `composer/` | Composer UI, mentions, branch switcher |
| `ui/` | Shared controls (`button.tsx`, `tooltip-b/`, dropdowns) |
| `icons/` | SVG icon modules |
| `utils/` | Single-export utilities and small helpers |
| `boundaries/` | Vendor/runtime facades in transit (not a resting place) |
| `vendor/` | Bundled third-party diagram/runtime modules |

`plan-organize.ts` auto-assigns shape-detectable chunks. App features without a clear heuristic need manual review — assign a domain with `ledger.ts set-organization` before `--apply`.

**Anti-pattern:** promoted files living inside directories still named after hash basenames (e.g. `restored/button-bq66r8jD/button.tsx`). The gate flags this as `full-restoration-public-file-in-hash-dir`.

### Example: split feature folder

`tooltip-B-u9JAuV` promotes to a domain-local barrel:

```
restored/ui/tooltip-b/
├── index.tsx      # entry — exports Tooltip, TooltipProvider, …
├── content.tsx
├── geometry.ts
├── provider.tsx
├── refs.ts
└── types.ts
```

`IMPORT_MAP.json` records the mapping:

```json
"tooltip-B-u9JAuV": {
  "restored": "ui/tooltip-b/index.tsx",
  "exports": {
    "n": "TooltipProvider",
    "r": "TooltipShortcut",
    "t": "Tooltip"
  },
  "status": "done"
}
```

### Example: app-feature page

`browser-use-settings-Ct3jD7gG` promotes under the `browser/` domain with a provenance header and semantic imports:

```tsx
// Restored from ref/webview/assets/browser-use-settings-Ct3jD7gG.js
// Browser Use settings page, section rows, permission controls, and public chunk exports.

import { Button } from "../../ui/button";
import { SettingsContentLayout } from "../../ui/settings-content-layout";
```

Multiple source chunks may map to the same semantic path when they are alternate builds of the same module — `IMPORT_MAP.json` tracks each basename independently.

## Kebab-case filenames

Public file and directory names must be **kebab-case** (lowercase with dashes). React component and type identifiers stay **PascalCase** because JSX requires it.

| Layer | Convention | Example |
|-------|------------|---------|
| Filename | kebab-case | `button.tsx`, `browser-use-settings/` |
| Export name | PascalCase / camelCase | `Button`, `useBrowserUseSettings` |
| Source chunk | hash-suffixed basename | `button-bq66r8jD.js` (staging only) |

`quality-gate.ts` enforces this via the `non-kebab-filename` issue. Allowances: `index`, `types`, `*.d.ts`, `*.facade.ts`, and all-lowercase dotted basenames.

## Provenance headers

Every promoted deliverable starts with a two-line provenance block. `promote-organized.ts` injects this via `ensureProvenanceHeader()`:

```
// Restored from ref/webview/assets/<basename>.js
// <short description of what the module exports>
```

<ParamField body="line 1" type="string" required>
  Must match `^// Restored from ref/webview/assets/[^/]+\.js`. The gate's `hasRestorationProvenanceHeader` check expects the repo-relative `ref/webview/assets/` path, not an absolute filesystem path.
</ParamField>

<ParamField body="line 2" type="string">
  Optional summary (≤ 80 chars) naming the component or utility and hinting at its API shape. Omitted for `icon`/`button` recipe outputs that only need line 1.
</ParamField>

Generated barrel files (`index.ts`, `types.ts`) are exempt from the provenance requirement. Duplicate provenance headers (`duplicate-provenance-headers`) also fail the gate.

Public files must **not** import hidden checkpoint paths under `.deobfuscate-javascript/` (`unfinalized-checkpoint-imports`). At promote time, `promote-organized.ts` rewrites imports of already-promoted producers to their semantic relative paths and redirects npm-leaf targets to bare specifiers.

## `restored/IMPORT_MAP.json` contract

One shared import map at the restore root maps every source chunk basename to its public deliverable. Reuse and append on delta restores; never create per-chunk, per-session, or per-entry maps.

### Schema (current)

```json
{
  "chunks": {
    "<basename>": {
      "restored": "domain/semantic-path.tsx",
      "exports": { "<sourceExport>": "<publicName>" },
      "status": "done"
    }
  }
}
```

<ResponseField name="restored" type="string">
  Repo-relative path to the public entry file, relative to the restore root. For directory layouts, this is the `index.tsx` or primary entry.
</ResponseField>

<ResponseField name="exports" type="object">
  Maps minified source export keys (e.g. `"t"`, `"n"`) to semantic public names (e.g. `"Tooltip"`, `"TooltipProvider"`). Used by `promote-organized.ts` to rewrite consumer imports.
</ResponseField>

<ResponseField name="status" type="string">
  `"done"` when the chunk is promoted and gate-passed. Other values (`mechanical-readable-restored`, boundary markers) indicate incomplete work — do not treat status alone as proof of a complete restore.
</ResponseField>

The gate also understands legacy sections (`boundaries`, `appScope`, `vscodeApi`, `src`, `statsig`, `publicOutputs`) for backward compatibility with older restore runs.

### Lifecycle

1. **Absent** — early in a restore, before any promotion. Anti-stall checks still fire from `manifest.json` + checkpoint presence.
2. **Growing** — `promote-organized.ts` atomically upserts `chunks[basename]` after each gate pass, setting `restored`, `exports`, and `status: "done"`.
3. **Complete** — every reachable local chunk has a `chunks` entry with `status: "done"` and an on-disk file at `restored/<restored>`. Confirmed by `quality-gate.ts restored` exiting 0.

On delta restores, check whether the requested chunk already maps to a public file before rebuilding the whole graph. Reuse the existing target, manifest, ledger, and import map.

## What belongs where

<AccordionGroup>
<Accordion title="restored/ — public deliverables only">

Typed `.tsx`/`.ts` files in semantic-domain folders, `IMPORT_MAP.json`, and optionally `boundaries/` facades in transit. No hash-basename files, no mechanical checkpoints, no `.deobfuscate-javascript/` imports from public files.

</Accordion>
<Accordion title=".deobfuscate-javascript/ — intermediates and coordination">

Per-chunk workspaces, `_full/manifest.json`, `_full/ledger.json`, `_full/checkpoints/`, organize plans, lockfiles, and agent candidate rewrites. Safe to gitignore; regenerable from source.

</Accordion>
<Accordion title="ref/ — read-only source reference">

Extracted Codex bundle. Do not treat unrestored ref chunks as permanent dependencies in deliverables (except deliberate `--passthrough` stopgaps marked `@ts-nocheck`).

</Accordion>
</AccordionGroup>

## Quick reference commands

<CodeGroup>
```bash title="Per-chunk workspace"
INPUT=ref/webview/assets/spinner-D37df5tU.js
TARGET=restored
WS="$TARGET/.deobfuscate-javascript/$(basename "$INPUT" .js)"
mkdir -p "$WS" && cp "$INPUT" "$WS/original.js"
```

```bash title="Organize + promote"
bun scripts/plan-organize.ts --target restored --apply
bun scripts/promote-organized.ts --target restored
```

```bash title="Gate a single file"
bun scripts/quality-gate.ts restored/ui/button.tsx
```

```bash title="Gate the whole target"
bun scripts/quality-gate.ts restored
```
</CodeGroup>

<ParamField body="--tier" type="readable | deep">
  `promote-organized.ts --tier deep` (default) enforces TypeScript types at promote. `--tier readable` allows untyped deliverables for quick passes.
</ParamField>

<ParamField body="--allow-organize-incomplete" type="boolean">
  Pass to `quality-gate.ts` during in-progress runs to suppress checkpoint-not-drained checks. Do not use when declaring a restore complete.
</ParamField>

## Related pages

<Card href="/restoration-pipeline" title="Restoration pipeline" icon="layers">
  Stage 1–3 flow, readable vs deep depth, and the restoration contract that defines done.
</Card>

<Card href="/full-tree-restoration" title="Full tree restoration" icon="git-branch">
  Entry discovery, import-graph orchestration, and the organize → promote loop in practice.
</Card>

<Card href="/import-graph-and-boundaries" title="Import graph and boundaries" icon="share-2">
  manifest.json and ledger.json orchestration, terminal chunk kinds, and quality-gate coverage rules.
</Card>

<Card href="/quality-bar-and-anti-patterns" title="Quality bar and anti-patterns" icon="shield-check">
  Promotion failures, naming anti-patterns, and gate issue codes explained.
</Card>

<Card href="/codex-project-profile" title="Codex project profile" icon="box">
  Default domain layout for the openai-codex-electron bundle and semantic rewrite playbook.
</Card>

---

## 06. 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.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/06-import-graph-and-boundaries.md
- Generated: 2026-06-27T21:18:54.328Z

### 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>

---

## 07. Codex project profile

> Layout of the extracted openai-codex-electron bundle: ref/webview/index.html entry discovery, ref/webview/assets chunk root, npm-leaf and Pierre vendor boundaries, and default deep-restore command frame for this repository.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/07-codex-project-profile.md
- Generated: 2026-06-27T21:19:49.806Z

### Source Files

- `.agents/skills/deobfuscate-javascript/reference/codex-ref.md`
- `.agents/skills/deobfuscate-javascript/scripts/check-entry.ts`
- `.agents/skills/deobfuscate-javascript/scripts/build-import-graph.ts`
- `restored/app-shell/thread-handoff-operations/index.ts`
- `restored/composer/composer-footer-branch-switcher/branch-switcher.tsx`
- `restored/runtime/vite-browser-external.ts`

---
title: "Codex project profile"
description: "Layout of the extracted openai-codex-electron bundle: ref/webview/index.html entry discovery, ref/webview/assets chunk root, npm-leaf and Pierre vendor boundaries, and default deep-restore command frame for this repository."
---

The extracted Codex desktop bundle under `./ref` is the `openai-codex-electron` package: the browser UI entry is `ref/webview/index.html`, bundled JavaScript lives in `ref/webview/assets/`, and deep restoration auto-discovers the app entry from that HTML before walking the import graph into `./restored/`. Chunk content hashes rotate every Codex.app build, so entry and chunk basenames must be resolved at runtime — never hard-coded from an older snapshot.

## Bundle scope map

After [refreshing `./ref`](/refresh-codex-ref) from `/Applications/Codex.app/Contents/Resources/app.asar`, the layout relevant to restoration looks like this:

:::files
decode-codex/
├─ ref/
│  ├─ package.json              # identifies openai-codex-electron; dependency oracle
│  ├─ node_modules/**           # extracted deps — API shape only, not restore targets
│  ├─ native-menu-locales/**     # JSON locale data
│  └─ webview/
│     ├─ index.html              # <script> + modulepreload roots
│     └─ assets/
│        ├─ *.js                 # Vite/Rollup content-hashed chunks (restore input)
│        ├─ *.css, *.wasm, fonts, images, animation/data files
│        └─ (no per-build stable names — hashes change each release)
└─ restored/                     # semantic deliverables (generated by deobfuscation)
   ├─ IMPORT_MAP.json            # chunk basename → public path
   ├─ .deobfuscate-javascript/_full/
   │  ├─ manifest.json           # import graph + per-chunk stages
   │  └─ ledger.json             # symbol-level rename state
   └─ <domain>/                  # app-shell, composer, browser, utils, vendor, …
:::

<ParamField body="ref/package.json" type="file">
Names the extracted app as `openai-codex-electron`. Use it to confirm npm package identity when classifying boundary chunks.
</ParamField>

<ParamField body="ref/webview/index.html" type="file">
Declares the browser bootstrap: `<script src="…">` roots and `<link rel="modulepreload" href="…">` dependencies. Read this before choosing a restoration entry.
</ParamField>

<ParamField body="ref/webview/assets/*.js" type="glob">
The bundled code root. Every whole-tree restore starts here. CSS, WASM, fonts, and static assets in the same directory are preserved or referenced — not rewritten to TS/TSX unless a JS chunk wraps them.
</ParamField>

<ParamField body="ref/node_modules/**" type="glob">
Extracted dependency and native-module source. Consult for API shape and fork detection; do not run the restoration pipeline against it.
</ParamField>

<Note>
`./ref` is generated and git-ignored. This repository may ship a populated `./restored/` snapshot (for example `restored/.deobfuscate-javascript/_full/manifest.json` records entry `index-CG92uawu` with high-fan-out neighbor `app-main-lwTO-JL9`) even when `./ref` is absent. Refresh `./ref` before starting a new restore against your installed Codex version.
</Note>

```mermaid
flowchart TB
  subgraph source ["Extracted bundle (./ref)"]
    PKG["ref/package.json<br/>openai-codex-electron"]
    HTML["ref/webview/index.html"]
    ASSETS["ref/webview/assets/*.js"]
    NM["ref/node_modules/**"]
  end

  subgraph discovery ["Entry discovery"]
    CE["check-entry.ts --discover"]
    BIG["app-main-* high fan-out"]
  end

  subgraph graph ["Import graph orchestration"]
    M["manifest.json"]
    L["ledger.json"]
  end

  subgraph output ["Public deliverables (./restored)"]
    MAP["IMPORT_MAP.json"]
    DOM["Semantic domains<br/>app-shell, composer, utils, vendor, …"]
  end

  PKG --> ASSETS
  HTML --> CE
  CE --> ASSETS
  CE --> BIG
  ASSETS --> M
  M --> L
  L --> DOM
  DOM --> MAP
  NM -.->|"fork / API checks only"| ASSETS
```

## Entry discovery from index.html

Whole-tree restoration treats `ref/webview/index.html` as authoritative. `check-entry.ts --discover` parses `<script src>` and `<link rel="modulepreload" href>` basenames, resolves them under `ref/webview/assets`, and scores each candidate before printing the chosen entry path on stdout.

<Steps>
<Step title="Discover the entry">
```bash
SKILL_DIR=.agents/skills/deobfuscate-javascript
ENTRY=$(bun "$SKILL_DIR/scripts/check-entry.ts" --discover --root ref/webview/assets)
echo "$ENTRY"
```
</Step>
<Step title="Interpret exit codes">
<ResponseField name="0" type="exit code">Entry looks reasonable — proceed.</ResponseField>
<ResponseField name="3" type="exit code">Suspicious vendor/transitive leaf — do not build the graph from this file.</ResponseField>
<ResponseField name="1" type="exit code">I/O failure or no entry discovered.</ResponseField>
<ResponseField name="64" type="exit code">Usage error (missing `--root`).</ResponseField>
</Step>
<Step title="Verify fan-out signals">
A real app entry has high **local out-degree** (many sibling chunk imports) and low **in-degree** (few siblings import it). A vendor leaf shows the inverse: imported by many siblings, imports few locals. The `<script>` root in `index.html` is the bootstrap; `app-main-*` is the usual deep-restore target because it fans out into most feature code even when it is not the script tag itself.
</Step>
</Steps>

Selection order inside `discoverEntry`:

1. Prefer a non-suspicious `<script>` root with the highest local out-degree.
2. Fall back to the best non-suspicious, non-vendored candidate (script or preload).
3. If every root looks vendored or suspicious, pick the highest fan-out basename and flag the risk in the discovery reason string.

`build-import-graph.ts` performs the same auto-discovery when the positional entry is omitted or `--discover` is passed. It warns on suspicious entries but does not block — run `check-entry.ts` first or heed the warning.

<Warning>
Pointing the graph at a transitive vendor leaf (for example a `vscode-languageserver-types` or lodash utility chunk imported by dozens of siblings) produces a tiny dependency closure that looks complete but is the wrong subtree. Exit code `3` from `check-entry.ts` is the guardrail.
</Warning>

## Chunk root and file kinds

`build-import-graph.ts` walks every reachable `./xxx-HASH.js` import from the entry and classifies each node in `manifest.json`:

| Kind | Behavior | Restore action |
| --- | --- | --- |
| `local` | Project-local sibling chunk | BFS continues; staged under `_full/files/<basename>/` |
| `oversized-local` | Local chunk over `--max-lines N` cap (partial mode only) | Recorded but not descended into unless `--include` or `--max-lines 0` |
| `npm-leaf` | Known npm package or bundled vendor data (Shiki grammar/theme, 3Dmol, etc.) | Terminal node; consumers rewritten to bare specifiers |
| `external` | Bare specifier like `"react"` | Terminal node |

For Codex deep restores, pass `--max-lines 0` so no local chunk is treated as an oversized boundary. A positive `--max-lines` is a quick/partial mode only.

Known npm chunks are detected via `CHUNK_NAME_REGISTRY` in `resolve-npm-imports.ts` (basename stems like `clsx`, `react`, `jsx-runtime`, `tslib.es6`, `marked.esm`, `floating-ui.react-dom`, `core.esm`, `statsig`, …) plus `classifyVendorDataChunk` for grammar/theme/data payloads. Example from a live manifest: a shared chunk named `app-initial~app-main~…` is classified `npm-leaf` with `npmPackage: "@shikijs/langs"` and skipped — it never lands in `restored/utils/`.

## npm-leaf and bare-import boundaries

Stock npm packages bundled under project-local basenames (`isEqual-*`, `lib-*`, `single-value-*`, `chunk-XXXX-*`) should become **bare re-export shims**, not `any`-facades. The model file is a `vendor/highlight-js-core.ts`-style re-export recorded in `IMPORT_MAP.json`.

| Typical chunk signal | npm specifier | Deliverable pattern |
| --- | --- | --- |
| `isEqual-*` | `lodash` | `vendor/lodash.ts` star/named re-export |
| `chunk-LFPYN7LY-*` | `react-router` | bare re-export shim |
| `lib-BWT6A3Q0` | `react-intl` | `useIntl` / `FormattedMessage` surface |
| `single-value-*` | `framer-motion` | bare re-export shim |
| `pkg-*` | `@segment/analytics-next` | analytics SDK boundary |
| `dist-*`, `Combination-*` | `@radix-ui/react-*` | per-primitive shim (**may be forked**) |
| `src-*` | `zod` | verify stock Zod vs Codex fork |

Extend `CHUNK_NAME_REGISTRY` or pass `--treat-as-npm name,name,…` when the bundler uses a basename the registry does not yet cover. Record the package in `IMPORT_MAP.json` so `classifyBoundary()` and `quality-gate.ts` treat the chunk as resolved vendor npm, not an open `any`-facade.

Runtime/host boundaries that are genuine app infrastructure (`app-scope`, `vscode-api`, `rpc`, `host-config`) may receive typed `any`-facades via `make-facade.ts` and `ledger.ts mark-faced` so consumers compile while the runtime is restored later. Facades are open boundaries — a deep restore is not complete while they stand in for project-local chunks.

## Pierre vendor boundaries

Codex bundles `@pierre/trees` (file tree, Preact-based) and `@pierre/diffs` (diff views, Shiki-backed) inline. These are **vendored package boundaries**, not Codex feature code — but Codex **forked** them, so a clean bare swap is often infeasible.

Recognize the Pierre family by fingerprint, not basename:

| Signal | Examples |
| --- | --- |
| CSS custom properties | `--trees-row-height`, `--trees-file-icon-color-rust`, `--diffs-addition-base`, `--diffs-line-bg` |
| DOM attributes | `data-file-tree-virtualized-root`, `data-diffs-header`, `data-diff-type`, `data-diff-span` |
| Theme names | `pierre-light`, `pierre-dark`, `pierre-dark-soft` |
| Renderer cluster basenames | `file-tree-search-input-*`, `shiki-highlight-provider-gate-*`, `file-diff-*`, `diff-unified-*`, `parsePatchFiles-*`, highlight `worker-*` |

Treatment order:

1. **Clean leaves → bare import.** The highlight worker (Oniguruma WASM, no Codex logic) maps to `@pierre/diffs/worker`. A self-contained `parsePatchFiles-*` chunk may map to `@pierre/diffs` if the public surface reconciles.
2. **Forked / entangled engine → keep wrapper, boundary-ize.** Do not delete forked files or pretend they are upstream. Mark vendored (`quality-gate.ts --vendored`), keep thin Codex wrappers, lift Codex-local inputs to props/config. Example mapping: `file-diff-*` → `vendor/pierre-diffs/file-diff.ts` exporting `FileDiff`.
3. **Themes** (`pierre-*`) → `@pierre/theme` data (low priority).

<Warning>
**Basename traps — these are Codex code, not Pierre:**

`diff-stats-*`, `diff-view-mode-*`, `use-diff-annotations-*`, `parse-diff-*` (size-guard wrapper), `grammars/diffGrammar.ts`, `treeView-*` / `treemap-*` (Mermaid/d3), `worktree-*` / `pending-worktree-*`, and genuine consumers (`review-*`, `workspace-directory-tree-*`, `editor-diff-page-*`) stay in restored feature domains.
</Warning>

This restore workspace has no vendored `node_modules` under `restored/`. Externalizing Pierre packages means ambient `declare module "…"` stubs plus `IMPORT_MAP.json` reclassification — not installing packages.

## Default deep-restore command frame

For a main app restore or continuation against this repository:

```bash
SKILL_DIR=.agents/skills/deobfuscate-javascript
ENTRY=$(bun "$SKILL_DIR/scripts/check-entry.ts" --discover --root ref/webview/assets)
TARGET=restored
FULL="$TARGET/.deobfuscate-javascript/_full"

bun "$SKILL_DIR/scripts/sourcemap-check.ts" "$ENTRY" || true
bun "$SKILL_DIR/scripts/build-import-graph.ts" "$ENTRY" \
  --target "$TARGET" \
  --root ref/webview/assets \
  --max-lines 0
bun "$SKILL_DIR/scripts/build-symbol-ledger.ts" \
  --target "$TARGET" \
  --manifest "$FULL/manifest.json" \
  --out "$FULL/ledger.json"
bun "$SKILL_DIR/scripts/auto-restore-full.ts" --target "$TARGET" --format
```

Omit the positional entry and pass `--discover` to `build-import-graph.ts` when you want the script to resolve the entry itself:

```bash
bun "$SKILL_DIR/scripts/build-import-graph.ts" \
  --target restored \
  --root ref/webview/assets \
  --max-lines 0
```

After checkpoints exist, run organization and promotion (`plan-organize.ts`, `promote-organized.ts`), Stage 3 acceptance review in deep mode, then:

```bash
bun "$SKILL_DIR/scripts/quality-gate.ts" restored
```

<Check>
Completion requires `quality-gate.ts` exit 0, every reachable local chunk at `stages.promoted` (plus `stages.finalized` in deep mode), and an empty `ledger.ts frontier --stage promote` — not merely populated checkpoints under `_full/`.
</Check>

## Resume before rebuilding

Inspect existing restoration state before re-running graph or auto-restore scripts:

```bash
find restored -maxdepth 3 \( -name README.md -o -name 'IMPORT_MAP.json' \)
find restored \( -path '*/.deobfuscate-javascript/_full/manifest.json' \
  -o -path '*/.deobfuscate-javascript/_full/ledger.json' \)
```

If `restored/.deobfuscate-javascript/_full/manifest.json` and `ledger.json` exist, resume from them unless the user asked to rebuild or the entry hash changed after a Codex.app refresh. Reuse the single shared `restored/IMPORT_MAP.json` at the restore root.

## Semantic output conventions

Public files under `restored/` follow product-domain folders (kebab-case, hash-free names). Provenance stays in file headers and `IMPORT_MAP.json`:

```typescript
// Restored from ref/webview/assets/composer-footer-branch-switcher-GaN7fzcq.js
```

Representative domains in the current snapshot: `app-shell/` (thread handoff operations), `composer/` (footer branch switcher), `browser/`, `threads/`, `runtime/` (including `vite-browser-external.ts` for `__vite-browser-external-*` chunks), `utils/`, `vendor/`, and `features/`.

Feature chunks named after product concepts (`composer-*`, `thread-*`, `app-shell-*`, `settings-*`, `mcp-*`, `*-page-*`, `*-panel-*`) deserve Stage 3 semantic splitting and typed TS/TSX surfaces. Syntax grammars, themes, locales, Mermaid, KaTeX, PDF, and protobuf runtime chunks should become compact facades or data modules — prove vendor/data identity by fingerprint before boundary-izing an app feature.

## Related pages

<CardGroup>
<Card title="Refresh Codex reference source" href="/refresh-codex-ref">
Extract and format `./ref` from the installed Codex.app before any deobfuscation run.
</Card>
<Card title="Full tree restoration" href="/full-tree-restoration">
End-to-end deep workflow: graph build, ledger, auto-restore, organize, promote, and quality gate.
</Card>
<Card title="Import graph and boundaries" href="/import-graph-and-boundaries">
manifest.json and ledger.json orchestration, terminal chunk kinds, and facade lifecycle.
</Card>
<Card title="Workspace and output conventions" href="/workspace-and-output">
Staging under `.deobfuscate-javascript/`, promote bar, semantic domains, and `IMPORT_MAP.json`.
</Card>
<Card title="Pipeline caveats" href="/pipeline-caveats">
Vendor-leaf entry misidentification, Pierre basename traps, and recovery steps.
</Card>
</CardGroup>

---

## 08. Refresh Codex reference source

> Run codex-app-ref-refresh to delete and recreate ./ref from Codex.app Contents/Resources/app.asar, format extracted JS/CSS with Prettier, and verify completion signals before deobfuscation.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/08-refresh-codex-reference-source.md
- Generated: 2026-06-27T21:19:05.432Z

### Source Files

- `.agents/skills/codex-app-ref-refresh/SKILL.md`
- `.agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs`
- `.agents/skills/codex-app-ref-refresh/agents/openai.yaml`
- `README.md`

---
title: "Refresh Codex reference source"
description: "Run codex-app-ref-refresh to delete and recreate ./ref from Codex.app Contents/Resources/app.asar, format extracted JS/CSS with Prettier, and verify completion signals before deobfuscation."
---

The `codex-app-ref-refresh` skill runs `refresh-codex-ref.mjs` from the repository root: it deletes `./ref`, extracts `/Applications/Codex.app/Contents/Resources/app.asar` with `@electron/asar`, formats every extracted `.js` and `.css` file with Prettier (skipping `ref/node_modules`), and verifies JavaScript formatting before reporting success. A complete refresh is the mandatory first step before invoking `deobfuscate-javascript` on `ref/webview/assets`.

<Info>
`ref/` and `restored/` are gitignored generated artifacts. Regenerate `ref/` with this skill rather than editing it by hand.
</Info>

## Role in the pipeline

decode-codex exposes two skills that chain together:

| Skill | Input | Output |
| ----- | ----- | ------ |
| `codex-app-ref-refresh` | Installed `Codex.app` (`app.asar`) | `./ref/` |
| `deobfuscate-javascript` | `./ref/webview/assets` | `./restored/` |

The refresh step keeps the reference tree aligned with the Codex desktop app version you have installed. Prettier formatting is not optional decoration — asar extraction yields single-line minified bundles that are impractical for analysis or deobfuscation until formatted.

```mermaid
sequenceDiagram
    participant Agent as Agent or shell
    participant Script as refresh-codex-ref.mjs
    participant Asar as @electron/asar
    participant Prettier as Prettier (npx)
    participant Ref as ./ref

    Agent->>Script: node .../refresh-codex-ref.mjs
    Script->>Script: assertSafeRefDir(./ref)
    Script->>Ref: rmSync ./ref
    Script->>Asar: extract app.asar → ref/
    Asar-->>Ref: openai-codex-electron bundle
    Script->>Prettier: --write *.js, *.css
    Script->>Prettier: --list-different (JS only, up to 3 passes)
    Prettier-->>Script: Prettier verification complete.
    Script-->>Agent: Codex app ref refresh complete.
```

## Prerequisites

<Warning>
Run from the repository root — the directory that should own `./ref`. The script intentionally replaces `./ref` and refuses unsafe targets.
</Warning>

| Requirement | Purpose |
| ------------- | ------- |
| macOS with `Codex.app` at `/Applications/Codex.app` | Default source for `app.asar` |
| Node.js | Runs the refresh script and `npx` subprocesses |
| Network (first run) | `npx -y @electron/asar` and `npx -y prettier` download on demand |

Override the asar location when Codex is installed elsewhere:

```bash
CODEX_APP_ASAR=/path/to/app.asar node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs
```

## Run the refresh

<Tabs>
  <Tab title="Agent">

Ask your agent from the repo root:

> Use **codex-app-ref-refresh** to refresh `./ref` from the installed Codex app.

The skill's default prompt matches this intent: refresh `./ref` from the installed `Codex.app` asar and format extracted JS/CSS.

  </Tab>
  <Tab title="Script">

```bash
# from the repository root
node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs
```

  </Tab>
</Tabs>

<Steps>
  <Step title="Confirm working directory">

Verify `pwd` resolves to the decode-codex repository root. The script only accepts a target named `ref` that resolves to `./ref` under the current working directory.

  </Step>
  <Step title="Run sync and format">

The script executes three phases in order:

1. **Sync** — removes existing `./ref`, then extracts the asar into a fresh `ref/` directory.
2. **Format** — walks `ref/` recursively, collects `.js` and `.css` files (skipping any `node_modules` subtree), runs Prettier `--write`, then verifies JavaScript with up to three `--list-different` passes.
3. **Report** — prints the formatted file count and a completion line.

  </Step>
  <Step title="Verify completion signals">

Confirm stdout includes the expected lines before starting deobfuscation:

- `Formatting N JS/CSS file(s) with Prettier, ignoring git/prettier ignore files...`
- `Prettier verification complete.`
- `Codex app ref refresh complete.`

If you see `Prettier verification found N file(s) needing another pass; reformatting...`, that is acceptable when followed by `Prettier verification complete.`

If you see `Skipping Prettier formatting.`, the refresh is incomplete unless you intentionally passed `--skip-format`.

Spot-check a known file under `ref/webview/assets/` — it should be multi-line formatted, not a single minified line.

  </Step>
</Steps>

## CLI flags and environment

<ParamField body="--dry-run" type="boolean">
Print resolved `Workspace`, `Source asar`, and `Target ref` paths without deleting or extracting anything. Exits after `Dry run only; no files changed.`
</ParamField>

<ParamField body="--skip-format" type="boolean">
Extract only; skip the Prettier write and verification phases. Logs `Skipping Prettier formatting.` and exits. Use only when you explicitly want raw, unformatted extraction.
</ParamField>

<ParamField body="CODEX_APP_ASAR" type="string">
Override the default asar path (`/Applications/Codex.app/Contents/Resources/app.asar`). Resolved with `path.resolve` before extraction.
</ParamField>

Unknown arguments cause a non-zero exit with `unknown argument(s): ...`. Missing or invalid asar files fail with `asar file not found: ...`.

## What the script does internally

### Safety guards

Before any file operations, `assertSafeRefDir` enforces:

- Basename must be exactly `ref`
- Relative path from cwd must be `ref` (not an absolute or nested path)
- Target must not be filesystem root, home directory, or the cwd itself

These guards prevent accidental deletion of the wrong directory when chaining `rm` and extract manually.

### Prettier behavior

The refresh script deliberately bypasses `.gitignore` and `.prettierignore` by passing `--ignore-path /dev/null`. This matters because `ref/` is gitignored — a plain `prettier --write ref/` would silently skip every file under Prettier 3's default ignore rules.

| Phase | Files | Gate |
| ----- | ----- | ---- |
| `--write` | All `.js` and `.css` under `ref/` (except `node_modules`) | Required |
| `--list-different` verification | JavaScript only | Hard gate — fails after 3 passes if files remain |
| CSS | Formatted but not verified | Bundled Tailwind CSS can be non-idempotent under Prettier |

Files are batched in groups up to ~50 KB of path length to avoid command-line limits.

### Subprocess tools

| Tool | Invocation | On failure |
| ---- | ------------ | ---------- |
| `@electron/asar` | `npx -y @electron/asar extract <asar> <refDir>` | Non-zero exit aborts |
| `prettier` | `npx -y prettier --write` / `--list-different` | Non-zero exit aborts (verification allows exit 1 for differing files) |

Prefer the bundled script over manually chaining `rm`, `npx @electron/asar`, and `prettier` — the script applies consistent safety checks and `node_modules` exclusion.

## Output layout

After a successful refresh, the extracted bundle mirrors the Codex Electron app structure:

:::files
decode-codex/
└─ ref/                              # generated — do not commit
   ├─ package.json                   # name: openai-codex-electron
   ├─ node_modules/                  # extracted deps (format skipped)
   ├─ native-menu-locales/           # JSON locale data
   └─ webview/
      ├─ index.html                  # browser entry + modulepreload roots
      └─ assets/
         ├─ *.js                     # bundled chunks (deobfuscation input)
         ├─ *.css                    # stylesheets
         └─ WASM, fonts, images      # non-JS assets
:::

The deobfuscation skill reads `ref/webview/index.html` for entry discovery and treats `ref/webview/assets/*.js` as the primary restoration target. Chunk hashes rotate every Codex build — always resolve the entry from the freshly extracted `index.html` rather than hard-coding a basename.

<Check>
A refresh is ready for deobfuscation when formatting verification passed and `ref/webview/index.html` exists with script tags pointing at hashed chunks under `ref/webview/assets/`.
</Check>

## Troubleshooting

<AccordionGroup>
  <Accordion title="asar file not found">

Confirm Codex.app is installed. Check the default path:

```bash
ls -la /Applications/Codex.app/Contents/Resources/app.asar
```

Or set `CODEX_APP_ASAR` to your bundle location and re-run.

  </Accordion>

  <Accordion title="ref directory is unsafe to replace">

You are not in the intended repository root, or cwd resolves incorrectly. `cd` to the decode-codex root where `./ref` should live and retry.

  </Accordion>

  <Accordion title="Prettier verification failed after 3 passes">

Some JavaScript files did not stabilize under Prettier. Inspect the stderr output for the failing paths. Re-run the full refresh; if the problem persists, check whether a specific file triggers a Prettier parser error.

  </Accordion>

  <Accordion title="Formatting appears skipped despite no --skip-format">

Confirm stdout shows `Prettier verification complete.` — not `Skipping Prettier formatting.`. For manual verification against gitignored `ref/`, always include `--ignore-path /dev/null`; otherwise Prettier may report success while skipping all files.

  </Accordion>

  <Accordion title="Command failed to start: npx">

Node.js is missing or not on `PATH`. Install Node.js and verify with `node --version`.

  </Accordion>
</AccordionGroup>

## Dry-run inspection

Use `--dry-run` to confirm paths before wiping `./ref`:

<RequestExample>

```bash
node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs --dry-run
```

</RequestExample>

<ResponseExample>

```text
Workspace: /path/to/decode-codex
Source asar: /Applications/Codex.app/Contents/Resources/app.asar
Target ref: /path/to/decode-codex/ref
Dry run only; no files changed.
```

</ResponseExample>

## Legal note

Extracted Codex source is © OpenAI. This project is for personal study and interoperability research of software you have installed. Do not redistribute extracted or restored code.

## Next

<CardGroup>
  <Card title="Quickstart" href="/quickstart">
    First end-to-end run: refresh `./ref`, then deobfuscate `ref/webview/assets` into `./restored/`.
  </Card>
  <Card title="Codex project profile" href="/codex-project-profile">
    Layout of the extracted bundle: entry discovery, chunk roots, and default deep-restore command frame.
  </Card>
  <Card title="Refresh script reference" href="/refresh-script-reference">
    Full CLI reference for `refresh-codex-ref.mjs`: flags, safety guards, verification passes, and stdout contract.
  </Card>
  <Card title="Pipeline caveats" href="/pipeline-caveats">
    Prettier gitignore traps and other failure modes that affect both refresh and restoration.
  </Card>
</CardGroup>

---

## 09. Deobfuscate a single file

> Readable-tier workflow for lone minified snippets or isolated chunks: sourcemap-check, wakaru-normalize, extract, smart-rename, polish --fast, format, and promote a single deliverable without import-graph orchestration.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/09-deobfuscate-a-single-file.md
- Generated: 2026-06-27T21:20:08.677Z

### Source Files

- `.agents/skills/deobfuscate-javascript/workflows/small-minified.md`
- `.agents/skills/deobfuscate-javascript/workflows/react-vite.md`
- `.agents/skills/deobfuscate-javascript/scripts/sourcemap-check.ts`
- `.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/apply.ts`

---
title: "Deobfuscate a single file"
description: "Readable-tier workflow for lone minified snippets or isolated chunks: sourcemap-check, wakaru-normalize, extract, smart-rename, polish --fast, format, and promote a single deliverable without import-graph orchestration."
---

The `deobfuscate-javascript` skill routes lone pasted snippets and isolated chunks through `workflows/small-minified.md`: a per-chunk workspace under `<target-dir>/.deobfuscate-javascript/<basename>/`, Stage 2 rename and polish only (Stage 1 when `detect.ts` reports obfuscation), and a single semantic deliverable in `<target-dir>/` — no `manifest.json`, `ledger.json`, or `build-import-graph.ts` orchestration.

<Info>
Whole-tree restores (`index.html` plus sibling asset chunks) default to deep mode and import-graph coordination. Use this page only when the input is one file or chunk with no app entry tree.
</Info>

## Scope and completion bar

| Axis | Single-file default |
| --- | --- |
| Scope | One input file; no recursive sibling-chunk restore |
| Depth | Readable tier — meaningful names and reading-aid polish |
| Hard bar | Naming quality throughout (no `buttonValue3`, `contextParam14`) |
| Optional | Stage 3 typed `.tsx`, npm-import resolution, acceptance review (deep mode) |

A valid readable deliverable is a semantic kebab-case public filename (for example `spinner.tsx` exporting `Spinner`, not `spinner-D37df5tU.tsx`) with the `// Restored from <path>` provenance header intact. Types, full npm-import resolution, and the Stage 3 reviewer loop are continuations, not corrections.

## Prerequisites

<Steps>
<Step title="Install skill dependencies">

From the skill directory:

```bash
cd .agents/skills/deobfuscate-javascript
bun install
```

</Step>

<Step title="Choose target and workspace paths">

```bash
INPUT=path/to/chunk.js          # the file to restore
TARGET=restored                 # deliverable root
WS="$TARGET/.deobfuscate-javascript/$(basename "$INPUT" .js)"

mkdir -p "$WS"
cp "$INPUT" "$WS/original.js"
```

Keep the hash suffix in `<basename>` when the source chunk has one (`spinner-D37df5tU` for `spinner-D37df5tU.js`). Intermediates stay in `$WS/`; the final `.tsx` lands directly in `$TARGET/`.

</Step>
</Steps>

## Pipeline overview

```mermaid
flowchart TD
  subgraph preflight [Preflight]
    SM[sourcemap-check.ts]
    DT[detect.ts]
  end
  subgraph stage1 [Stage 1 — only if obfuscated]
    S1[deobfuscate.ts]
  end
  subgraph stage2 [Stage 2 — readable tier]
    WK[wakaru-normalize.ts]
    PL[polish.ts --rename --fast --format]
    HN[Hand-name residue]
  end
  subgraph deliver [Deliver]
    PR[Promote to TARGET/]
  end
  SM -->|no usable .map| DT
  SM -->|map found| MAP[Recover via sourcemap — stop rename pipeline]
  DT -->|obfuscated| S1
  DT -->|minified only| WK
  S1 --> WK
  WK --> PL
  PL --> HN
  HN --> PR
```

Byte-rewriting steps (`wakaru-normalize`, Stage 1) invalidate symbol offsets. After normalization, run extract, rename, and polish against `$WS/normalized.js`, not `$WS/original.js`. Keep `--source` pointed at the original input path for the provenance header.

## Step 0 — Preflight checks

### Sourcemap check (always first)

```bash
bun scripts/sourcemap-check.ts "$WS/original.js"
```

<ResponseExample>

```text
✓ sourcemap detected
  sourceMappingURL = chunk.js.map
  map file         = /path/to/chunk.js.map
  3 original source(s):
    src/Button.tsx
    ...

Adjacent chunk.js.map found with 3 original source(s). Recover from sourcemap instead of renaming — preserves original variable names, comments, and file structure.
```

</ResponseExample>

When `mapFound` is true, recover originals with `source-map-explorer` or the `.map` `sourcesContent` — that path beats any rename pipeline. Skip `wakaru-normalize` when a usable map exists.

<ParamField body="--out" type="string">
Write a JSON report instead of stderr-only summary.
</ParamField>

Exit codes: `0` when a map is found; `1` when no map is detected (proceed with rename pipeline).

### Obfuscation detect

```bash
bun scripts/detect.ts "$WS/original.js"
```

If the input is packed, encoded, or Obfuscator.IO-shaped (`_0x` arrays, Packer wrappers, hex walls), run Stage 1 first via the obfuscated-input workflow, then return to this pipeline. Pure minification skips Stage 1.

## Step 1 — Wakaru normalization

```bash
bun scripts/wakaru-normalize.ts "$WS/original.js" \
  -o "$WS/normalized.js" --level standard
```

Wakaru recovers ES6 classes, async/await, optional chaining, destructuring, TS enums, and similar transpiler output that polish alone does not restore. The wrapper auto-skips (passthrough copy, exit `0`) when `@wakaru/cli` is unavailable — `normalized.js` always exists for the next step.

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

<Warning>
Wakaru is not a deobfuscator and not a semantic renamer. Run Stage 1 before wakaru on obfuscated input.
</Warning>

## Step 2 — One-shot rename and polish

The readable-tier default combines mechanical rename, reading-aid polish, and formatting in one command:

```bash
bun scripts/polish.ts "$WS/normalized.js" --rename --fast \
  --source "$INPUT" --out "$WS/draft.tsx" --format
```

<ParamField body="--rename" type="boolean">
Runs `smart-rename.ts` then `apply.ts` as a pre-step before polish passes.
</ParamField>

<ParamField body="--fast" type="boolean">
Readable-tier profile. Skips import-resolution tail: `react-shim-elim`, `resolve-npm-imports`, `npm-cjs-shim-elim`, `dead-shim-elim`. Drop `--fast` for deep-mode compilable imports.
</ParamField>

<ParamField body="--source" type="string" required>
Original input path for the `// Restored from <path>` provenance header. Polish input is normalized output; provenance still references the pristine chunk.
</ParamField>

<ParamField body="--format" type="boolean">
Runs `format.ts` on `--out` (requires `--out`; uses `.prettierignore` only so gitignored `restored/` trees still format).
</ParamField>

### What `--fast` polish runs

| Step | Purpose |
| --- | --- |
| `strip-react-compiler` | Remove React Compiler `cache[N]` memoization |
| `simplify` | Fold constants, unwrap `(0, fn)(...)`, collapse templates |
| `jsx-runtime` | Convert `jsx`/`jsxs`/`Fragment` calls to JSX syntax |
| `inline-defaults` | Inline destructure defaults |
| `normalize-exports` | Collapse single-use export aliases |

### What `smart-rename` covers mechanically

`smart-rename.ts` applies deterministic heuristics before hand-naming:

- React component `props` and DOM `event` parameters
- `.map`/`.reduce`/`.forEach` callback parameters (`item`, `index`, `accumulator`)
- Promise `.then`/`.catch` handlers (`value`, `error`)
- Hook return aliases (`useIntl()` → `intl`, `useRouter()` → `router`)
- Destructure prop aliases (`{ foo: e }` → `foo`)
- `clsx`/`classNames`/`cn` call results → `className`

Cryptic names match `/^_?[a-zA-Z]{1,2}$/` — single or double letters and short letter-digit forms.

## Step 3 — Hand-name the residue

Read `$WS/draft.tsx` and name what `smart-rename` could not reach. This is the only hard bar at readable depth:

- Program-scope domain nouns — top-level exports and the concepts they model
- Lookup-table object keys — `{0:"sm",1:"md"}` → `buttonSizeClassNames`
- JSX component aliases — lowercase `<r>` that is really `<Spinner>`

Continue into function bodies until single-letter density is low. Refuse mechanical fallback names (`buttonValue3`, `contextParam14`).

### Second rename pass

When residue remains, re-extract on the draft and apply a focused `renames.json`:

```bash
bun scripts/extract.ts "$WS/draft.tsx" \
  --out "$WS/symbols.json" --only-cryptic --min-refs 2
# Write $WS/renames.json — keys are "name@declStart" ids from extract
bun scripts/apply.ts "$WS/draft.tsx" "$WS/renames.json" --out "$WS/draft.tsx"
bun scripts/format.ts "$WS/draft.tsx"
```

<ParamField body="--only-cryptic" type="boolean">
Keep `_0x…`, single/double-letter, and letter+digit bindings only.
</ParamField>

<ParamField body="--min-refs" type="number">
Drop bindings referenced fewer than N times (scratch locals).
</ParamField>

`apply.ts` renames scope-aware: each `name@offset` id maps to one binding. Collisions get `_`-prefixed safe names; reserved words are normalized via `toIdentifier()`.

## Step 4 — Promote the deliverable

Copy or write the finished file to `<target-dir>/` with a semantic kebab filename:

```bash
cp "$WS/draft.tsx" "$TARGET/spinner.tsx"
```

<Check>
Deliverable checklist: meaningful names throughout, semantic kebab filename (no hash suffix), provenance header present, Prettier-formatted output.
</Check>

Do not leave the public file inside `.deobfuscate-javascript/`. That directory holds intermediates only (`original.js`, `symbols.json`, `renames.json`, `draft.tsx`).

:::files
restored/
├── spinner.tsx                              # final deliverable
└── .deobfuscate-javascript/
    └── spinner-D37df5tU/                    # workspace (gitignore-friendly)
        ├── original.js
        ├── normalized.js
        ├── symbols.json
        ├── renames.json
        └── draft.tsx
:::

## Sub-cases (branch only when applicable)

<AccordionGroup>
<Accordion title="React / Vite / Rollup component chunk">

No separate recipe. `--fast` polish already runs `jsx-runtime` recovery — `jsxRuntime.jsx("svg", …)` becomes JSX. See the react-vite workflow note: single components use this same path.

</Accordion>

<Accordion title="Obfuscated input (Packer, _0x arrays, encoded strings)">

Run Stage 1 (`detect` → `deobfuscate.ts`) before wakaru and polish. Stage 1 rewrites invalidate pre-deobfuscation `renames.json` ids.

</Accordion>

<Accordion title="≥ 500 KB or > 1000 cryptic symbols">

Use `plan.ts` batching — one rename pass will not fit context budgets. Filter large extracts: `--only-cryptic --min-refs 3 --top 200 --compact`.

</Accordion>

<Accordion title="Webpack id:(e,t,n)=>{} bundle">

Pre-split with `webcrack`, then restore each module through this single-file flow.

</Accordion>

<Accordion title="≥ 3 exports or registry object">

Optional deep-tier step: split into a directory via multi-export-bundle workflow. Readable tier may leave the file flat.

</Accordion>
</AccordionGroup>

## Troubleshooting

| Symptom | Likely cause | Recovery |
| --- | --- | --- |
| `extract.ts` warns about `sourceMappingURL` | Map comment present but not checked | Re-run `sourcemap-check.ts`; prefer map recovery |
| `polish[format]: prettier failed` | Prettier missing or path issue | Install prettier; ensure `--out` is set with `--format` |
| Names still cryptic in function bodies | Stopped after program-scope only | Hand-name bodies; run second extract/apply pass |
| `renamed 0 symbol(s) (N id(s)… had no matching binding)` | Stale `renames.json` after byte rewrite | Re-extract from current draft; rebuild ids |
| JSX still `jsx()` calls | Non-standard runtime binding names | Inspect polish report; check `jsx-runtime` step stats |
| Wakaru stderr `skipped` | `@wakaru/cli` unavailable | Passthrough is valid; pipeline continues on `original.js` bytes in `normalized.js` |

<Note>
Optional E1 naming self-review (meaningful identifiers, no fallback names, low single-letter density) adds confidence at readable depth. The full Stage 3 acceptance review (E1–E4) is deep mode only.
</Note>

## Readable vs deep for a single file

| Concern | Readable (`--fast`) | Deep (drop `--fast`) |
| --- | --- | --- |
| Naming | Required | Required |
| JSX recovery | Yes | Yes |
| npm import resolution | Skipped | `resolve-npm-imports` runs |
| Shim elimination | Skipped | `react-shim-elim`, `npm-cjs-shim-elim`, `dead-shim-elim` |
| TypeScript types | Optional | Stage 3 rewrite |
| Acceptance review | Optional E1 | Full E1–E4 loop |

## Related pages

<CardGroup>
<Card title="Restoration pipeline" href="/restoration-pipeline">
Stage 1, Stage 2, and Stage 3 boundaries; readable vs deep depth; single-file vs whole-tree scope.
</Card>
<Card title="Workspace and output" href="/workspace-and-output">
Per-chunk `$WS` staging, promote bar, kebab filenames, and provenance headers.
</Card>
<Card title="Handle obfuscated input" href="/obfuscated-input">
Stage 1 ordering when `detect.ts` reports packed or encoded input.
</Card>
<Card title="Stage 2 scripts reference" href="/stage-2-scripts">
CLI signatures for `sourcemap-check`, `wakaru-normalize`, `extract`, `smart-rename`, `apply`, and `polish` flags.
</Card>
<Card title="Quality bar and anti-patterns" href="/quality-bar-and-anti-patterns">
Naming anti-patterns, completion criteria, and when to refuse mechanical fallback names.
</Card>
<Card title="Full tree restoration" href="/full-tree-restoration">
Default path when input is `index.html` plus an asset tree instead of one chunk.
</Card>
</CardGroup>

---

## 10. Full tree restoration

> Deep default workflow for index.html plus asset trees: entry discovery, build-import-graph, build-symbol-ledger, auto-restore-full checkpoints, plan-organize, promote-organized, and quality-gate over the whole target.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/10-full-tree-restoration.md
- Generated: 2026-06-27T21:20:12.221Z

### Source Files

- `.agents/skills/deobfuscate-javascript/workflows/full-restoration.md`
- `.agents/skills/deobfuscate-javascript/reference/codex-ref.md`
- `.agents/skills/deobfuscate-javascript/scripts/auto-restore-full.ts`
- `.agents/skills/deobfuscate-javascript/scripts/plan-organize.ts`
- `.agents/skills/deobfuscate-javascript/scripts/promote-organized.ts`
- `.agents/skills/deobfuscate-javascript/scripts/semantic-finalize.ts`
- `.agents/skills/deobfuscate-javascript/scripts/acceptance-checklist.md`

---
title: "Full tree restoration"
description: "Deep default workflow for index.html plus asset trees: entry discovery, build-import-graph, build-symbol-ledger, auto-restore-full checkpoints, plan-organize, promote-organized, and quality-gate over the whole target."
---

Full tree restoration is the default orchestration path when the input is an `index.html` plus a Vite/Rollup-style content-hashed asset tree. The workflow coordinates Stages 1–3 across every reachable project-local chunk through two on-disk tables — `manifest.json` (file-level dependency graph and per-file stage status) and `ledger.json` (symbol-level checklist with cross-file binding propagation) — then drains mechanical checkpoints from `restored/.deobfuscate-javascript/_full/` into the public `restored/` tree via organize → promote, and proves completion with a whole-target `quality-gate.ts` run.

<Note>
Whole-tree restores default to **deep** depth: typed `.tsx` deliverables, Stage 3 acceptance review, and every reachable local chunk at `stages.promoted`. Readable depth is an explicit opt-out (`quick`, `readable`, `快速`) or applies to lone snippets with no asset tree.
</Note>

## When to use this workflow

Use full tree restoration when:

- An `index.html` references a sibling-chunk asset directory (many `import` / `export … from "./xxx-HASH.js"` edges).
- You want every reachable project-local chunk restored, not a single isolated file.
- No recoverable sourcemap exists (`sourcemap-check.ts` did not find a usable `.map`).

Fall back to single-file workflows when the input is a lone pasted snippet or has no surrounding tree. For the Codex.app bundle under `./ref`, read the [Codex project profile](/codex-project-profile) first — it defines entry discovery, boundary classification, and the default command frame.

## Architecture

```mermaid
flowchart TB
  subgraph input [Input]
    HTML[index.html]
    ASSETS[assets/*.js chunks]
  end

  subgraph discovery [Entry discovery]
    CE[check-entry.ts --discover]
    SM[sourcemap-check.ts]
  end

  subgraph graph [Graph + ledger]
    BIG[build-import-graph.ts]
    BSL[build-symbol-ledger.ts]
    MAN[(manifest.json)]
    LED[(ledger.json)]
  end

  subgraph staging [Staging _full/]
    WS[files/basename/original.js]
    CP[checkpoints/basename.tsx]
    PLAN[organize-plan.json]
  end

  subgraph drain [Organize → promote]
    PO[plan-organize.ts]
    PR[promote-organized.ts]
    MAP[(IMPORT_MAP.json)]
  end

  subgraph deliverable [Public restored/]
    DOM[domain/semantic-path.tsx]
  end

  subgraph proof [Completion proof]
    QG[quality-gate.ts]
    ACC[Stage 3 acceptance review]
  end

  HTML --> CE
  ASSETS --> CE
  CE --> SM
  SM --> BIG
  BIG --> MAN
  MAN --> BSL
  BSL --> LED
  BIG --> WS
  ARF[auto-restore-full.ts] --> CP
  CP --> PO
  PO --> PLAN
  PLAN --> PR
  PR --> DOM
  PR --> MAP
  DOM --> ACC
  ACC --> QG
  MAN --> QG
  MAP --> QG
```

| Artifact | Location | Role |
| --- | --- | --- |
| `manifest.json` | `restored/.deobfuscate-javascript/_full/` | File-level graph: imports, exports, chunk kind, per-file `stages` |
| `ledger.json` | `_full/` | Symbol checklist + `crossFileBindings` for name propagation |
| `organize-plan.json` | `_full/` | Proposed domain + semantic public path per chunk |
| `auto-restore-report.json` | `_full/` | Per-chunk rename stats and `fallbackRenameRatio` triage signal |
| `IMPORT_MAP.json` | `restored/` | Shared chunk → public path mapping (one map per restore root) |
| Checkpoints | `_full/checkpoints/<basename>.tsx` | Mechanical Stage 2 output — never a deliverable |

## Prerequisites

- Bun installed; dependencies installed in `.agents/skills/deobfuscate-javascript/`.
- For Codex.app restores: `./ref` refreshed from `/Applications/Codex.app` (see [Refresh Codex reference source](/refresh-codex-ref)).
- `TARGET=restored` as the shared restore root (no per-entry subfolder).

<Warning>
Batch and script output never lands directly in `restored/`. Checkpoints live under `_full/checkpoints/` until `promote-organized.ts` passes the quality gate and copies typed deliverables to semantic paths.
</Warning>

## Pipeline overview

| Step | Script | Output |
| --- | --- | --- |
| 0 | `sourcemap-check.ts` | Bail out if a recoverable sourcemap exists |
| 0.5 | `check-entry.ts --discover` | Confirmed app entry (not a vendor leaf) |
| 1 | `build-import-graph.ts` | `_full/manifest.json` |
| 1.5 | `make-facade.ts` + `ledger.ts mark-faced` (optional) | Typed boundary facades for huge vendor/runtime chunks |
| 2 | `build-symbol-ledger.ts` | `_full/ledger.json` + per-file `symbols.json` |
| 3 | `auto-restore-full.ts` or manual `ledger.ts` loop | `_full/checkpoints/*.tsx` |
| 4 | `plan-organize.ts` → `promote-organized.ts` | Public files under `restored/<domain>/` |
| 5 | Stage 3 acceptance review | Every delivered file passes B1–B4 checklist |
| 6 | `quality-gate.ts <target>` | Exit 0 = completion proof |

## Run the workflow

<Steps>
<Step title="Check for sourcemap and discover entry">

```bash
SKILL_DIR=.agents/skills/deobfuscate-javascript
TARGET=restored
FULL="$TARGET/.deobfuscate-javascript/_full"

# Bail out if a sourcemap is recoverable
bun "$SKILL_DIR/scripts/sourcemap-check.ts" ref/webview/assets/app-main-*.js || true

# Auto-discover entry from index.html (hash rotates every build)
ENTRY=$(bun "$SKILL_DIR/scripts/check-entry.ts" --discover --root ref/webview/assets)
echo "Entry: $ENTRY"
```

<ParamField body="--discover" type="flag">
Reads `index.html` script/preload roots and picks the best entry candidate. Prints the resolved file path to stdout.
</ParamField>

<ResponseField name="exit code" type="number">
`0` — entry looks valid · `3` — suspicious vendor/transitive leaf (wrong subtree) · `1` — I/O or nothing discovered · `64` — usage error
</ResponseField>

Exit `3` means you pointed at a transitive dependency (high in-degree, low out-degree), not the application bootstrap. Switch to the `index.html` `<script>` root or a high-fan-out `app-main-*` chunk.

</Step>

<Step title="Build import graph and symbol ledger">

```bash
mkdir -p "$FULL/files" "$FULL/locks"

bun "$SKILL_DIR/scripts/build-import-graph.ts" \
  --target "$TARGET" \
  --root ref/webview/assets \
  --max-lines 0

bun "$SKILL_DIR/scripts/build-symbol-ledger.ts" \
  --target "$TARGET" \
  --manifest "$FULL/manifest.json" \
  --out "$FULL/ledger.json"
```

<ParamField body="--max-lines" type="number" default="0">
`0` (default for deep restores) restores every reachable project-local sibling. A positive value enables quick/targeted mode: files exceeding the cap become `oversized-local` and are skipped.
</ParamField>

Read the console summary before proceeding. In a deep/full task, `oversized-local` must be `0`.

Chunk kinds recorded in the manifest:

| Kind | Behavior |
| --- | --- |
| `local` | Project-local chunk — gets a `_full/files/<basename>/` workspace and is restored |
| `npm-leaf` | Known npm package or bundled vendor data (Shiki grammars/themes) — terminal node, consumers get bare specifiers |
| `oversized-local` | Only with `--max-lines N > 0` — boundary recorded, BFS stops |
| `external` | Bare specifier not seen as a chunk — terminal node |

</Step>

<Step title="Optionally facade vendor/runtime boundaries">

For enormous third-party runtime chunks (Zod `src-*`, React scope layer, `vscode-api` bridge) that block dozens of feature chunks:

```bash
bun "$SKILL_DIR/scripts/make-facade.ts" ref/webview/assets/src-C7fSIbpz.js \
  --provenance "ref/webview/assets/src-C7fSIbpz.js" \
  --out "$TARGET/boundaries/zod.facade.ts"
bun "$SKILL_DIR/scripts/ledger.ts" mark-faced src-C7fSIbpz --target "$TARGET"
```

<Warning>
Facing applies to genuine vendor/runtime chunks only. `mark-faced` refuses app-entry basenames (`app-shell-*`, `app-main-*`, pages, panels) unless `--force` is passed. A facade is an open boundary — the deep restore stays incomplete while one stands in for a project-local chunk.
</Warning>

</Step>

<Step title="Build mechanical checkpoints">

For large trees, use the batch checkpoint executor:

```bash
bun "$SKILL_DIR/scripts/auto-restore-full.ts" --target "$TARGET" --format
```

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

<ParamField body="--format" type="flag">
Run `format.ts` on the target after checkpoints are written.
</ParamField>

<ParamField body="--resume" type="flag">
Skip chunks whose `auto-polished.tsx` and checkpoint already exist.
</ParamField>

<ParamField body="--write-target-checkpoints" type="flag">
Legacy/debug: also write hash-named `.tsx` files into `restored/`. These are still not deliverables.
</ParamField>

This writes `_full/checkpoints/<basename>.tsx` plus per-file `auto-renames.json`, `auto-renamed.js`, and `auto-polished.tsx` under `_full/files/<basename>/`, and emits `_full/auto-restore-report.json`. Every checkpoint has `needsAgentRewrite: true` — it is an accelerator, not a substitute for Stage 3 semantic rewrite.

For manual iteration instead of batch mode, use `ledger.ts frontier`, `claim`, Stage 2 scripts (`apply.ts`, `polish.ts`), `mark-done`, and `propagate-cross-file` per file.

</Step>

<Step title="Plan organization">

```bash
bun "$SKILL_DIR/scripts/plan-organize.ts" --target "$TARGET"
```

Review `_full/organize-plan.json`. For each `needs-review` app-feature chunk, assign a domain and semantic kebab path:

```bash
bun "$SKILL_DIR/scripts/ledger.ts" set-organization composer-footer-DyRbFsKV \
  --domain composer --semantic-path composer/composer-footer.tsx \
  --recipe manual --classification app-feature --target "$TARGET"
```

Then apply approved entries:

```bash
bun "$SKILL_DIR/scripts/plan-organize.ts" --target "$TARGET" --apply
```

<ParamField body="--domain-map" type="string">
Optional JSON file mapping basename prefixes to domains: `{ "composer-": "composer", "thread-": "threads" }`.
</ParamField>

The planner uses project-agnostic shape heuristics: icon-shaped → `icons/`, button-shaped → `ui/`, single-export → `utils/`, known vendor → `boundaries/`. Everything else is `app-feature` with `status: "needs-review"`. Colliding public paths auto-downgrade to `needs-review`.

For deep mode `manual`/`split` chunks, drop a hand-cleaned typed candidate at `_full/files/<basename>/candidate.tsx` before promoting. Icon and button recipe chunks are typed automatically by `semantic-finalize.ts` at promote time.

</Step>

<Step title="Drain the promote frontier">

```bash
bun "$SKILL_DIR/scripts/promote-organized.ts" --target "$TARGET" --dry-run
bun "$SKILL_DIR/scripts/promote-organized.ts" --target "$TARGET"
```

<ParamField body="--tier" type="string" default="deep">
`deep` enforces typed `Props` interfaces and parameter types. `readable` relaxes the typing gate.
</ParamField>

<ParamField body="--dry-run" type="flag">
Preview every move, import-map change, and gate verdict with no writes.
</ParamField>

For each organized chunk whose local dependencies are already promoted, `promote-organized.ts`:

1. Builds the typed deliverable (semantic-finalize recipe or agent `candidate.tsx`).
2. Writes at the final semantic path so relative imports to promoted siblings resolve.
3. Runs `analyzeSource` (same quality gate as `promote-final.ts`).
4. On pass: upserts `restored/IMPORT_MAP.json`, rewrites imports, sets `stages.promoted`.
5. On fail: rolls back the file, records issues, continues (one bad chunk never blocks the batch).

Track progress with `ledger.ts status --target "$TARGET"` — it prints `completion: <promoted>/<total> promoted (pct%) · <organized> organized · <ready> ready-to-promote` plus a blocked list of chunks needing `candidate.tsx`.

</Step>

<Step title="Acceptance review and final gate">

Run Stage 3 acceptance review: read every delivered file end-to-end against the four-category checklist (B1 naming, B2 readability, B3 formatting, B4 structure/imports). `NEEDS_FIX` means rewrite the candidate and re-promote.

Then run the whole-target completion proof:

```bash
bun "$SKILL_DIR/scripts/quality-gate.ts" restored --check-format
```

Pass `--allow-organize-incomplete` only for intermediate mid-drain runs.

</Step>
</Steps>

## Codex.app default command frame

For `./ref/webview/assets` in this repository:

```bash
SKILL_DIR=.agents/skills/deobfuscate-javascript
ENTRY=$(bun "$SKILL_DIR/scripts/check-entry.ts" --discover --root ref/webview/assets)
TARGET=restored

bun "$SKILL_DIR/scripts/sourcemap-check.ts" "$ENTRY" || true
bun "$SKILL_DIR/scripts/build-import-graph.ts" "$ENTRY" \
  --target "$TARGET" \
  --root ref/webview/assets \
  --max-lines 0
bun "$SKILL_DIR/scripts/build-symbol-ledger.ts" \
  --target "$TARGET" \
  --manifest "$TARGET/.deobfuscate-javascript/_full/manifest.json" \
  --out "$TARGET/.deobfuscate-javascript/_full/ledger.json"
bun "$SKILL_DIR/scripts/auto-restore-full.ts" --target "$TARGET" --format
```

Always resolve the hashed entry from `ref/webview/index.html` or by globbing `app-main-*.js` — chunk hashes rotate every build.

## Completion definition

The 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 requires `stages.finalized` or explicit Stage 3 acceptance record).
3. `ledger.ts frontier --stage promote --target <dir>` is empty.

<Check>
`IMPORT_MAP.status === "done"` alone is not completion evidence. A boundary-only audit or a count of placeholder files under `boundaries/` does not substitute for the whole-target gate.
</Check>

Common `quality-gate.ts` failure codes for stalled restores:

| Code | Meaning |
| --- | --- |
| `full-restoration-checkpoints-not-drained` | Checkpoints exist but chunks were never promoted into `restored/` |
| `full-restoration-organize-incomplete` | Chunks reached finalize but lack `stages.promoted` |
| `full-restoration-public-file-in-hash-dir` | Deliverable still in a hash-named directory |
| `full-restoration-npm-boundary-not-resolved` | Third-party npm chunk left as an `any`-facade instead of bare re-export |
| `full-restoration-mechanical-app-feature` | App chunk still a mechanical-readable checkpoint |
| `full-restoration-app-feature-not-accepted` | Missing `stages.finalized` or Stage 3 acceptance record |

## Resume before rebuilding

Inspect existing state before re-running graph or auto-restore scripts:

```bash
find restored -maxdepth 3 \( -name README.md -o -name 'IMPORT_MAP.json' \)
find restored \( -path '*/.deobfuscate-javascript/_full/manifest.json' \
  -o -path '*/.deobfuscate-javascript/_full/ledger.json' \)
```

If `manifest.json` and `ledger.json` exist and the entry hash has not changed, resume from them. Reuse the single shared `restored/IMPORT_MAP.json`. Re-running `build-import-graph.ts` is idempotent — existing `kind`, `npmPackage`, and `stages` fields are preserved.

## Parallelism and locking

- One lockfile per `(basename, stage)` at `_full/locks/<basename>.<stage>.lock`.
- Stages: `extract`, `rename`, `polish`, `finalize`, `promote`.
- `ledger.ts claim` uses `O_EXCL` create — a second claim fails with exit `75`.
- `mark-done` releases the lock; use `claim --steal` only for locks older than 30 minutes.
- Fan multiple agents across `ledger.ts frontier` rows (deepest first) or the promote frontier — each claims a different chunk.

## Troubleshooting

<AccordionGroup>
<Accordion title="Got only a handful of files — wrong entry">
`check-entry.ts` exit `3` or `build-import-graph.ts` warning about suspicious entry. You built the graph from a vendor leaf (`main-*`, `isEqual-*`) instead of the app bootstrap. Re-run with `--discover` or target `app-main-*.js`.
</Accordion>

<Accordion title="restored/ is empty after auto-restore-full">
Expected. Checkpoints are mechanical drafts in `_full/checkpoints/`. Run `plan-organize.ts --apply` then `promote-organized.ts` to drain them.
</Accordion>

<Accordion title="Promote gate rejects manual chunks">
Drop a typed `candidate.tsx` at `_full/files/<basename>/candidate.tsx` with semantic names, `Props` interfaces, and no mechanical fallback names (`buttonValue3`, `ImportedBinding1`). Re-run promote.
</Accordion>

<Accordion title="High fallbackRenameRatio in organize plan">
Chunks with 62–91% fallback renames in `auto-restore-report.json` need hand-cleaning before promote. Triage these first.
</Accordion>

<Accordion title="Vendored package classified as local">
Add the basename to `CHUNK_NAME_REGISTRY` in `resolve-npm-imports.ts`, or pass `--treat-as-npm <basename>` to `build-import-graph.ts`.
</Accordion>
</AccordionGroup>

## Output layout

:::files
restored/
├── IMPORT_MAP.json              # shared chunk → public path map
├── app-shell/
│   └── app-shell.tsx
├── composer/
│   └── composer-footer.tsx
├── icons/
│   └── download-icon.tsx
├── ui/
│   └── button.tsx
├── boundaries/
│   └── zod.facade.ts            # vendor/runtime facades only
└── .deobfuscate-javascript/
    └── _full/
        ├── manifest.json
        ├── ledger.json
        ├── organize-plan.json
        ├── auto-restore-report.json
        ├── checkpoints/
        │   └── app-shell-JLpboL12.tsx
        ├── files/
        │   └── app-shell-JLpboL12/
        │       ├── original.js
        │       ├── symbols.json
        │       ├── auto-polished.tsx
        │       └── candidate.tsx    # deep-mode hand rewrite
        └── locks/
:::

Public files use kebab-case paths in semantic-domain folders. React component identifiers stay PascalCase. Original chunk identity lives in provenance headers (`// Restored from ref/webview/assets/<basename>.js`), not in directory names.

## Related pages

<CardGroup>
<Card title="Restoration pipeline" href="/restoration-pipeline">
Stage 1–3 detail, readable vs deep depth, and the restoration contract that defines done.
</Card>
<Card title="Import graph and boundaries" href="/import-graph-and-boundaries">
manifest.json and ledger.json orchestration, terminal chunk kinds, facade lifecycle, and quality-gate coverage rules.
</Card>
<Card title="Workspace and output" href="/workspace-and-output">
Staging conventions, promote bar, semantic-domain subfolders, and IMPORT_MAP.json contract.
</Card>
<Card title="Orchestration scripts reference" href="/orchestration-scripts">
CLI signatures for check-entry, build-import-graph, ledger subcommands, auto-restore-full, plan-organize, promote-organized, and quality-gate.
</Card>
<Card title="Delta restore and resume" href="/delta-and-resume">
Continue an in-progress restoration without rebuilding the whole reachable graph.
</Card>
<Card title="Quality bar and anti-patterns" href="/quality-bar-and-anti-patterns">
Deep-tier completion criteria, naming anti-patterns, and quality-gate failure modes.
</Card>
</CardGroup>

---

## 11. Handle obfuscated input

> Stage 1 workflow for packed, encoded, or Obfuscator.IO input: detect, unpack, string-array, decode-strings, simplify, control-flow-report, and critical ordering before Stage 2 rename.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/11-handle-obfuscated-input.md
- Generated: 2026-06-27T21:19:58.474Z

### Source Files

- `.agents/skills/deobfuscate-javascript/workflows/full-obfuscation.md`
- `.agents/skills/deobfuscate-javascript/stages/stage-1-deobfuscate.md`
- `.agents/skills/deobfuscate-javascript/scripts/detect.ts`
- `.agents/skills/deobfuscate-javascript/scripts/unpack.ts`
- `.agents/skills/deobfuscate-javascript/scripts/string-array.ts`
- `.agents/skills/deobfuscate-javascript/scripts/decode-strings.ts`
- `.agents/skills/deobfuscate-javascript/scripts/deobfuscate.ts`

---
title: "Handle obfuscated input"
description: "Stage 1 workflow for packed, encoded, or Obfuscator.IO input: detect, unpack, string-array, decode-strings, simplify, control-flow-report, and critical ordering before Stage 2 rename."
---

Stage 1 in `.agents/skills/deobfuscate-javascript/scripts/` unwinds obfuscator transformations with pure Babel passes—no LLM dependency. The `deobfuscate.ts` orchestrator runs `detect` → `unpack` → `string-array` → `decode-strings` → `simplify` → `control-flow-report` in a fixed order, re-running `detect` after `unpack` when unpacking reveals hidden techniques. Output is syntactically normal JavaScript with cryptic names intact; Stage 2 rename and polish must not start until Stage 1 completes because `extract.ts` byte offsets and `renames.json` ids are invalid across Stage 1 rewrites.

<Note>
Run `sourcemap-check.ts` before committing to Stage 1. If a usable `.map` exists, sourcemap recovery is strictly better than Stage 1 + Stage 2.
</Note>

## When to run Stage 1

| Input signal | Action |
|---|---|
| `detect.ts` reports techniques with confidence ≥ 0.5 | Run Stage 1 (one-shot `deobfuscate.ts` or step-by-step scripts) |
| `detect.ts` returns empty `techniques` | Skip Stage 1; go straight to Stage 2 (`wakaru-normalize` → `extract` → rename) |
| Packer / AAEncode / Obfuscator.IO `_0x` arrays / hex escapes / opaque predicates / control-flow flattening | Stage 1 required |
| Pure minification (short identifiers, single-line uglify, no obfuscation techniques) | Skip Stage 1 |

`detect.ts` recognizes: Packer, AAEncode, URL-encoded payloads, Obfuscator.IO `_0x` identifiers, string-array declarations and rotation IIFEs, hex and unicode escapes, `String.fromCharCode`, `atob`, control-flow flattening (`while(true){switch(...)}` and split-string dispatch), opaque predicates, dead-code injection, webpack signatures, and single-line uglify.

## Pipeline architecture

```mermaid
sequenceDiagram
  participant Input as original.js
  participant Detect as detect.ts
  participant Unpack as unpack.ts
  participant SA as string-array.ts
  participant DS as decode-strings.ts
  participant Sim as simplify.ts
  participant CFR as control-flow-report.ts
  participant Out as stageA.js

  Input->>Detect: techniques + recommendation
  Detect->>Unpack: packed layers
  Unpack->>Detect: detect-after-unpack (if changed)
  Unpack->>SA: AST with _0x arrays
  SA->>DS: inlined string literals
  DS->>Sim: decoded literals + normalized escapes
  Sim->>CFR: folded constants, removed dead code
  CFR-->>Out: mutated code + read-only report JSON
```

Mutating steps rewrite the AST. `control-flow-report` is read-only—it emits JSON for manual unflattening and does not change the file.

## Workspace setup

Stage 1 intermediates live under a per-chunk workspace nested in the target directory:

```bash
INPUT=ref/webview/assets/sample-Ab12Cd34.js
TARGET=restored
SKILL=.agents/skills/deobfuscate-javascript
WS="$TARGET/.deobfuscate-javascript/$(basename "$INPUT" .js)"

mkdir -p "$WS"
cp "$INPUT" "$WS/original.js"
```

Typical Stage 1 artifacts in `$WS/`:

| File | Produced by |
|---|---|
| `original.js` | Archived pristine input |
| `stageA.js` | `deobfuscate.ts --out` |
| `stageA.json` | `deobfuscate.ts --report` |

## Standard run

<Steps>
<Step title="Detect obfuscation">

```bash
cd "$SKILL"
bun scripts/detect.ts "$WS/original.js"
```

Read the JSON `recommendation` field. If `techniques` is empty, skip Stage 1 and continue with [Deobfuscate a single file](/deobfuscate-single-file).

</Step>
<Step title="Run the orchestrator">

```bash
bun scripts/deobfuscate.ts "$WS/original.js" \
  --out "$WS/stageA.js" \
  --report "$WS/stageA.json"
```

Stderr logs per-step summaries. The report JSON records `originalSize`, `finalSize`, `reduction`, and a `steps` array with per-step stats or errors.

</Step>
<Step title="Verify output">

Confirm in `$WS/stageA.json`:

- `string-array`: `referencesReplaced > 0` when Obfuscator.IO arrays were present
- `decode-strings`: `fromCharCode` / `atob` counts or `escapesNormalized: true` when encodings were present
- `simplify`: `deadIfsRemoved > 0` or `constantsFolded > 0` when dead code or opaque predicates were present
- `control-flow-report`: `flatteners` length is 0, or plan a manual rewrite (see below)

Read `$WS/stageA.js`—no `_0x` identifiers, no hex escape walls, no `eval(function(p,a,c,k,e,d)`.

</Step>
<Step title="Continue to Stage 2">

Feed `$WS/stageA.js` (not `original.js`) into Stage 2: `wakaru-normalize` → `extract` → `smart-rename` → `apply` → `polish`. See [Restoration pipeline](/restoration-pipeline) and [Deobfuscate a single file](/deobfuscate-single-file).

</Step>
</Steps>

## Step reference

### detect

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

<ResponseField name="techniques" type="array">
Each entry: `{ name, confidence, evidence }`. Sorted by confidence descending.
</ResponseField>

<ResponseField name="recommendation" type="string">
Ordered script list or guidance to skip Stage 1 when no obfuscation is found.
</ResponseField>

### unpack

Unpacks layered wrappers: Dean Edwards Packer → AAEncode → URLEncode, up to `--max-iterations` (default 5).

```bash
bun scripts/unpack.ts <input.js|-> [--out output.js] [--max-iterations 5] [--no-eval]
```

| Layer | Detection | Eval required |
|---|---|---|
| Packer | `eval(function(p,a,c,k,e,d){...})` signature | Yes (`new Function` for arg parsing) |
| AAEncode | Emoji glyphs (`゜-゜`, `ω゜`, `(ﾟДﾟ)`) | Yes (`new Function`) |
| URLEncode | >10 `%XX` sequences | No (`decodeURIComponent`) |

<Warning>
`unpack.ts` executes input via `new Function`. Sandboxed JS cannot read your filesystem, but the payload still runs. On untrusted input, gate with `--no-eval` first to confirm a Packer wrapper without executing it.
</Warning>

<ParamField body="--no-eval" type="boolean">
Refuses Packer and AAEncode evaluation. Exits 0 with input unchanged and `evalRefused: true` in the result. URLEncode still runs.
</ParamField>

### string-array

Inlines Obfuscator.IO `var _0x123 = ['log','hi',…]` arrays, applies rotation IIFE correction, replaces indexed refs, and removes dead array declarations.

```bash
bun scripts/string-array.ts <input.js|-> [--out output.js]
```

<ResponseField name="decoderIndirection" type="boolean">
`true` when arrays were collected but zero references were replaced—access is wrapped behind a decoder function. Run `simplify.ts` first (inlines small constant functions), then re-run `string-array`.
</ResponseField>

### decode-strings

Literal-only decoder: `String.fromCharCode(72,101,…)` → `"He"` (all-numeric args), `atob("…")` with string-literal arg, and `\xNN` / `\uNNNN` escapes normalized to characters. Variables and non-literal args are left alone.

```bash
bun scripts/decode-strings.ts <input.js|-> [--out output.js]
```

### simplify

Loops to a fixed point (default `--max-passes 10`). Per pass: constant folding, dead-code removal, logical short-circuit, identity ops, sequence expansion, computed-to-dot, and scope-aware literal inlining.

```bash
bun scripts/simplify.ts <input.js|-> [--out output.js] [--max-passes 10] [--no-inline]
```

<ParamField body="--no-inline" type="boolean">
Skips `var k = 5; …k` → `…5` literal inlining. Constant folding and dead-code removal still run. Use when you want binding names preserved for Stage 2's renamer.
</ParamField>

### control-flow-report

Read-only analysis. Does not mutate code.

```bash
bun scripts/control-flow-report.ts <input.js|-> [--out report.json]
```

Detects:

| Pattern kind | Shape | Action |
|---|---|---|
| `while-switch-flattening` | `while(true){switch(state){case 0:…}}` | Manual rewrite using `dispatchVariable`, `caseLabels`, `containingFunction` |
| `split-string-dispatch` | `"0\|1\|2".split("\|")` | Find corresponding switch and inline in dispatch order |
| `opaque-predicate` | `if(1===2)`, `if(!![])`, `if(void 0)` missed by simplify | Apply `rewriteHint` (replace with then/else branch) |

Automatic CFG reconstruction is unreliable. The report lays out the dispatch graph; you trace state transitions and inline cases in execution order, then re-run `simplify.ts` on the rewritten code.

## Orchestrator flags

```bash
bun scripts/deobfuscate.ts <input.js|-> \
  [--out output.js] [--report report.json] \
  [--skip step1,step2] [--stop-after step] \
  [--no-eval] [--no-inline] \
  [--max-iterations 5] [--max-passes 10]
```

Valid step names for `--skip` and `--stop-after`:

`detect`, `unpack`, `string-array`, `decode-strings`, `simplify`, `control-flow-report`

| Behavior | Detail |
|---|---|
| Per-step try/catch | One failing AST pass does not abort the rest; errors appear in `--report` JSON |
| Re-detect after unpack | When `unpack` changes the code and `detect` is not skipped, runs `detect-after-unpack` |
| `--stop-after control-flow-report` | Useful to inspect flatteners before continuing to Stage 2 |

## Critical ordering

Out-of-order runs silently no-op or mangle output:

```
unpack  →  string-array  →  decode-strings  →  simplify  →  control-flow-report
                                                              ↓
                                                    Stage 2 (wakaru → extract → rename)
```

| Rule | Reason |
|---|---|
| `unpack` first | Packed input is one giant `CallExpression`; AST passes cannot parse it |
| `string-array` before `decode-strings` | Inlining first limits decode work to used literals only |
| `simplify` after `string-array` + `decode-strings` | Folding needs resolved literals; running simplify first splits rotation IIFEs that `string-array` won't match |
| `control-flow-report` last | Opaque predicates fold out in simplify first; report captures remaining CFG |
| Stage 1 before Stage 2 | `extract.ts` offsets and `renames.json` ids are stale after Stage 1 rewrites |
| Stage 1 → wakaru → extract | wakaru is byte-rewriting; it runs after Stage 1 but before any extract/apply |

## Untrusted and Packer-wrapped input

<CodeGroup>

```bash title="Gate with --no-eval"
cd "$SKILL"
bun scripts/unpack.ts "$WS/original.js" --no-eval
# Visually verify the packer wrapper looks legit
```

```bash title="Allow eval after inspection"
bun scripts/deobfuscate.ts "$WS/original.js" --out "$WS/clean.js"
```

</CodeGroup>

With `--no-eval`, stderr shows `eval refused` and the result includes `evalRefused: true`. Without it, stderr warns before each `new Function` evaluation.

## Decoder indirection recovery

When `string-array` reports `DECODER-INDIRECTION` or `decoderIndirection: true` in the orchestrator report:

<Steps>
<Step title="Run simplify on current code">

```bash
bun scripts/simplify.ts "$WS/stageA-partial.js" --out "$WS/after-simplify.js"
```

</Step>
<Step title="Re-run string-array">

```bash
bun scripts/string-array.ts "$WS/after-simplify.js" --out "$WS/after-string-array.js"
```

</Step>
<Step title="Continue the pipeline">

Resume with `decode-strings` → `simplify` → `control-flow-report`, or re-run `deobfuscate.ts` from the simplify output.

</Step>
</Steps>

If references are still zero after simplify, the rotation pattern may be non-standard—manual decoder rewrite is required.

## Troubleshooting

<AccordionGroup>
<Accordion title="unpack says no packer detected but eval wrapper is visible">

The detector requires the exact `(p, a, c, k, e, d|r)` parameter signature. Custom or renamed parameters won't match. Rename wrapper params to the standard signature, or execute the inner body manually in a JS REPL.

</Accordion>
<Accordion title="string-array replaced 0 references">

Check for `decoderIndirection`. Run `simplify.ts` first, then re-run `string-array`. See [Pipeline caveats](/pipeline-caveats).

</Accordion>
<Accordion title="control-flow-report shows flatteners">

Read each `flatteners[i]` entry: note `dispatchVariable`, `caseLabels`, `containingFunction`, and `loc`. Open source at `loc.start..loc.end`, trace `case → next-state → next case`, inline reachable statements, skip unreachable cases. Re-run `simplify.ts` afterward.

</Accordion>
<Accordion title="Orchestrator step errored but kept going">

By design. Read `--report` JSON for the error. Fix input (often a syntax error from a prior step), or `--skip` the failing step and continue.

</Accordion>
<Accordion title="simplify inlined too aggressively">

Pass `--no-inline` to preserve binding names for Stage 2 while keeping constant folding and dead-code removal.

</Accordion>
</AccordionGroup>

## Expected verification signals

After a successful Stage 1 run on Obfuscator.IO composite input:

- `$WS/stageA.js` contains readable string literals (`"hello world"`, `"Hi"`)
- No `_0xc0de` identifiers, no `fromCharCode` calls, no `if (false)` or `if (5 > 3)` dead branches
- `finalSize < originalSize` in the report
- Stderr ends with `deobfuscate: done — <original> → <final> bytes`

## Related pages

<CardGroup>
<Card title="Stage 1 scripts reference" href="/stage-1-scripts">
CLI signatures, flags, and per-script behavior for every Stage 1 script.
</Card>
<Card title="Restoration pipeline" href="/restoration-pipeline">
Where Stage 1 fits between sourcemap check, Stage 2 rename, and Stage 3 finalize.
</Card>
<Card title="Pipeline caveats" href="/pipeline-caveats">
Ordering rules, eval safety, decoder indirection, and sourcemap invalidation recovery.
</Card>
<Card title="Deobfuscate a single file" href="/deobfuscate-single-file">
Continue from `$WS/stageA.js` through wakaru, extract, rename, and polish.
</Card>
</CardGroup>

---

## 12. Delta restore and resume

> Continue an in-progress restoration: inspect manifest.json, ledger.json, and IMPORT_MAP.json; run delta/boundary replacement for a scoped chunk; and avoid rebuilding the whole reachable graph.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/12-delta-restore-and-resume.md
- Generated: 2026-06-27T21:21:09.542Z

### Source Files

- `.agents/skills/deobfuscate-javascript/SKILL.md`
- `.agents/skills/deobfuscate-javascript/reference/codex-ref.md`
- `.agents/skills/deobfuscate-javascript/scripts/ledger.ts`
- `.agents/skills/deobfuscate-javascript/scripts/promote-final.ts`
- `.agents/skills/deobfuscate-javascript/scripts/prepare-stage-e-review.ts`
- `.agents/skills/deobfuscate-javascript/scripts/verify-stage-e-results.ts`

---
title: Delta restore and resume
description: Continue an in-progress restoration by inspecting manifest.json, ledger.json, and IMPORT_MAP.json, running scoped delta or boundary replacement, and avoiding a full reachable-graph rebuild.
---

Most whole-tree restores span multiple sessions. When `restored/` already has a shared import map and a `_full/` coordination layer, the right move is usually to **resume or delta-replace one chunk** — not to rerun `build-import-graph.ts` and drain the entire graph from scratch.

## When to resume vs rebuild

Resume or delta-restore when all of the following hold:

- `TARGET/.deobfuscate-javascript/_full/manifest.json` and `ledger.json` exist.
- `TARGET/IMPORT_MAP.json` exists at the restore root (one shared map, never per-entry).
- The user wants to finish, fix, or replace a **scoped chunk** — not restart the whole tree.

Rebuild the graph only when the user explicitly asks, or when the entry hash changed after a Codex.app refresh and the prior graph no longer matches the ref tree.

<Steps>
<Step title="Inspect existing state">

Before any graph or auto-restore script, check what is already on disk:

```bash
find restored -maxdepth 3 \( -name README.md -o -name 'IMPORT_MAP.json' \)
find restored \( -path '*/.deobfuscate-javascript/_full/manifest.json' \
  -o -path '*/.deobfuscate-javascript/_full/ledger.json' \)
```

If both `_full` tables exist, treat them as authoritative. Reuse `restored/IMPORT_MAP.json`; append to it as the project grows — never create a parallel map or per-entry folder.

</Step>
<Step title="Decide delta vs full-tree">

**Step 0.6 routing:** when `IMPORT_MAP.json` plus `_full/manifest.json` are present, check whether the requested chunk already maps to a public file or a typed boundary facade.

| Situation | Action |
| --- | --- |
| Chunk maps to `restored/<domain>/…` or `boundaries/…` | Delta restore — replace that scoped deliverable |
| Chunk is missing from the map or still a facade placeholder | Restore the chunk; pull in dependencies only if they are missing or explicitly requested |
| User says "rebuild", "start over", or entry hash rotated | Re-run graph build; use `--rebuild` only when discarding prior stage progress is intentional |

"Complete" for a delta task means **complete replacement for that scoped chunk**, not whole-tree coverage — unless the user asked for a full reachable-graph rebuild.

</Step>
</Steps>

## The three coordination files

### `manifest.json` — file-level graph and stage status

Lives at `TARGET/.deobfuscate-javascript/_full/manifest.json`. Records every reachable chunk, import/export edges, file `kind` (`local`, `npm-leaf`, `oversized-local`, `external`), and per-file stage flags:

`extracted` → `renamed` → `polished` → `finalized` → `organized` → `promoted`, plus `faced` for terminal boundary chunks.

Re-running `build-import-graph.ts` without `--rebuild` is **idempotent**: existing `kind`, `npmPackage`, `stages`, and `organization` are preserved; only `imports`/`exports` refresh. That lets you update the graph after ref changes without losing progress.

### `ledger.json` — symbol-level checklist

Lives at `TARGET/.deobfuscate-javascript/_full/ledger.json`. Tracks per-binding rename status and the `crossFileBindings` table that links consumer aliases to producer restored names. Use it to see what is still `pending`, what was committed, and whether downstream consumers already absorbed a producer rename.

Key resume commands:

```bash
SKILL_DIR=.agents/skills/deobfuscate-javascript
TARGET=restored
FULL="$TARGET/.deobfuscate-javascript/_full"

bun "$SKILL_DIR/scripts/ledger.ts" status --target "$TARGET"
bun "$SKILL_DIR/scripts/ledger.ts" frontier --target "$TARGET"
bun "$SKILL_DIR/scripts/ledger.ts" frontier --stage promote --target "$TARGET"
```

`frontier` lists every file restorable **right now** (local deps done or `faced`), deepest first. For a cold restart, pick up from the frontier instead of replaying finished work.

### `IMPORT_MAP.json` — public path contract

Lives at `TARGET/IMPORT_MAP.json` (restore root, not inside `_full/`). Maps hash basenames to public deliverables:

<ResponseField name="chunks" type="object">
Per-basename entries. Typical fields:

- `restored` — public path relative to target (e.g. `composer/composer-panel.tsx`)
- `status` — `done` when promoted; `boundary` while still a facade
- `exports` — alias map from bundle names to semantic export names
- `vendor` — npm family marker for boundary classification
- `boundary` — true while the chunk is still a typed facade, not a real restore
</ResponseField>

<ResponseField name="dependencyBoundaryFacades" type="object">
Maps facade export names to bundle aliases for chunks given `make-facade.ts` treatment. Consumers import the documented `boundaries/…` path until the boundary is explicitly restored and moved out.
</ResponseField>

`plan-organize.ts` **reuses existing `IMPORT_MAP` mappings** on stable re-runs and delta restores — the first priority when proposing domain and semantic path. That prevents reorganize passes from fighting prior public layout.

## Delta restore contract

When the target already has coordination state, follow this contract instead of launching a new whole-tree restore:

<Steps>
<Step title="Reuse coordination artifacts">

Do not create a parallel restore root. Keep `TARGET`, `_full/manifest.json`, `_full/ledger.json`, and `IMPORT_MAP.json` as the single source of truth.

</Step>
<Step title="Restore the scoped chunk in its workspace">

Work inside the existing per-chunk workspace:

```
TARGET/.deobfuscate-javascript/_full/files/<basename>/
├── original.js
├── symbols.json
├── renames.json
├── renamed.js
├── polished.tsx
└── candidate.tsx          # deep mode: hand-cleaned deliverable input
```

Run Stages 1–2 (and Stage 3 rewrite for deep mode) only for the requested basename. Restore additional chunks **only** when they are missing from `IMPORT_MAP.json` or the user explicitly scoped them.

</Step>
<Step title="Rewrite imports from the shared map">

Derive import paths from `IMPORT_MAP.json` — do not hand-invent semantic paths.

```bash
IMPORT_MAP="restored/IMPORT_MAP.json"
bun .agents/skills/deobfuscate-javascript/scripts/semantic-finalize.ts \
  --rewrite-imports <file-or-dir> \
  --import-map "$IMPORT_MAP"
```

Rules:

- Finalized local deps → semantic public paths from `chunks[basename].restored`
- npm deps → bare specifiers (`"clsx"`, not `"./clsx-XXXX.js"`)
- Unresolved runtime/vendor boundaries → documented `boundaries/…` facade path until explicitly restored

</Step>
<Step title="Replace the boundary or public file">

Swap the mapped deliverable with the new semantic candidate:

1. Write the candidate at `_full/files/<basename>/candidate.tsx` (deep) or polish output (readable).
2. Promote through the gate — either the orchestrator or the one-off primitive:

```bash
# Whole-tree promote frontier (preferred when organized)
bun .agents/skills/deobfuscate-javascript/scripts/promote-organized.ts \
  --target "$TARGET"

# One-off delta (single file or directory)
bun .agents/skills/deobfuscate-javascript/scripts/promote-final.ts \
  "$FULL/files/<basename>/candidate" \
  "restored/<domain>/<semantic-kebab>.tsx"
```

3. Update the import-map entry: set `restored`, `exports`, `status: "done"`, and **remove `boundary`** when a facade is no longer a facade.

For boundary-to-real-module upgrades, deep-restore the chunk, `ledger.ts set-organization` it into its real domain (`host/`, `contexts/`, `utils/`, …), and promote it **out of** `boundaries/`.

</Step>
<Step title="Validate the delta">

Validation scope is the **changed public file set**, not the whole tree — unless the deliverable changed the entire public tree or the user asked for all-tree proof.

```bash
# Per-changed-path gate
bun .agents/skills/deobfuscate-javascript/scripts/quality-gate.ts \
  restored/<domain>/<changed-file>.tsx

# Target TypeScript check when tsconfig exists
# (run from restore root if configured)

# Format
bun .agents/skills/deobfuscate-javascript/scripts/format.ts <changed-paths>
```

Stage 3 acceptance (deep mode) applies to changed files only. If `prepare-stage-e-review.ts` emits the whole tree, either prepare a small changed-file packet manually or review only the changed batch and report scope accurately. Do not present a partial delta review as all-tree acceptance proof.

`verify-stage-e-results.ts` enforces verdict coverage only when reviewer verdict files exist under `_stage-e/results/`.

</Step>
</Steps>

## Boundary replacement lifecycle

Delta work often means upgrading a **temporary facade** to a real module.

| Prior state | Delta outcome |
| --- | --- |
| `boundaries/*.ts` with `export declare const … : any` | Deep-restore chunk → promote to semantic domain → clear `boundary` in map |
| npm package chunk left as `any`-facade | `make-facade.ts --reexport <specifier>` → bare re-export shim (finished deliverable) |
| `status: "boundary"` / `faced` in manifest | Consumers already compile; replacing the facade unblocks `quality-gate` npm-boundary checks |

`ledger.ts mark-faced` marks vendor/runtime boundaries so consumers stop waiting. It **refuses app/feature basenames** unless `--force` — project chunks must be restored, not faced.

A boundary in `boundaries/` is scaffolding in transit, not a resting place. Delta completion for kind-2 runtime boundaries means the chunk is restored and promoted out of `boundaries/`.

## Resume patterns without rebuilding the graph

### Cold restart mid whole-tree restore

```bash
bun scripts/ledger.ts status --target restored
bun scripts/ledger.ts frontier --target restored
# claim → run stage scripts → mark-done → propagate-cross-file
bun scripts/promote-organized.ts --target restored   # skips already-promoted
```

State lives on disk. Nothing is in memory. `promote-organized.ts` skips `stages.promoted` chunks and re-derives the promote frontier.

### Re-extract one file

When a single chunk's source changed or rename went sideways:

```bash
rm restored/.deobfuscate-javascript/_full/files/<basename>/symbols.json
bun scripts/build-symbol-ledger.ts --target restored \
  --rebuild --only <basename>
```

Already-done downstream consumers stay done; committed `producerRestoredName` values persist.

### Roll back one stage

```bash
bun scripts/ledger.ts reset <basename> --stage rename --target restored
```

Use sparingly — `propagate-cross-file` results in downstream ledgers are not auto-undone.

### Refresh graph edges without losing progress

```bash
bun scripts/build-import-graph.ts "$ENTRY" \
  --target restored --root ref/webview/assets
# no --rebuild → preserves stages and organization
```

Pass `--rebuild` only when intentionally discarding prior `stages`/`owner` fields.

## Anti-patterns

| Mistake | Why it fails | Cure |
| --- | --- | --- |
| Rerun `auto-restore-full.ts` over the whole tree for one chunk fix | Wastes work; may overwrite hand-cleaned candidates | Scope to `_full/files/<basename>/` and delta-promote |
| Declare delta done from `IMPORT_MAP.status === "done"` alone | Map status does not prove naming, typing, or acceptance | `quality-gate.ts` on changed paths + scoped Stage 3 |
| Hand-invent import paths | Breaks cross-chunk consistency | `semantic-finalize.ts --import-map` |
| Copy checkpoint straight into `restored/` | Bypasses gate and import rewrite | `promote-organized.ts` or `promote-final.ts` |
| Present partial Stage E review as whole-tree proof | Misstates completion scope | Review only changed files; say so explicitly |
| Rebuild graph when only one boundary needs replacement | Rebuilds entire reachable component | Delta contract above |

Whole-tree completion still requires `quality-gate.ts <target-dir>` exit 0, every reachable local chunk `stages.promoted` (deep: + `stages.finalized`), and an empty `ledger.ts frontier --stage promote`. A successful delta does not automatically satisfy those unless the user scoped whole-tree validation.

## Quick reference

```bash
SKILL_DIR=.agents/skills/deobfuscate-javascript
TARGET=restored
FULL="$TARGET/.deobfuscate-javascript/_full"
IMPORT_MAP="$TARGET/IMPORT_MAP.json"

# 1. Inspect
bun "$SKILL_DIR/scripts/ledger.ts" status --target "$TARGET"
jq '.chunks["<basename>"]' "$IMPORT_MAP"

# 2. Scoped restore (example)
BASENAME=composer-panel-Ab12Cd34
WS="$FULL/files/$BASENAME"
# … Stage 1/2/3 in $WS …

# 3. Rewrite imports + promote
bun "$SKILL_DIR/scripts/semantic-finalize.ts" \
  --rewrite-imports "$WS/candidate.tsx" --import-map "$IMPORT_MAP"
bun "$SKILL_DIR/scripts/promote-final.ts" \
  "$WS/candidate.tsx" "restored/composer/composer-panel.tsx"

# 4. Validate delta
bun "$SKILL_DIR/scripts/quality-gate.ts" restored/composer/composer-panel.tsx
```

<ParamField body="--target" type="string" required>
Restore root (e.g. `restored`). All ledger and promote scripts resolve `_full/` relative to this directory.
</ParamField>

<ParamField body="--rebuild" type="boolean">
On `build-import-graph.ts` and `build-symbol-ledger.ts`. Discards prior stage progress. Default off — preserve for resume.
</ParamField>

<ParamField body="--only" type="string">
On `build-symbol-ledger.ts`. Limit ledger regeneration to one basename during single-file recovery.
</ParamField>

## Related pages

<Card href="/import-graph-and-boundaries" title="Import graph and boundaries" icon="book">
manifest.json and ledger.json orchestration, terminal chunk kinds, facade lifecycle, and quality-gate coverage rules.
</Card>

<Card href="/workspace-and-output" title="Workspace and output conventions" icon="book">
Per-chunk staging under `_full/files/`, the promote bar, semantic-domain layout, and the shared `IMPORT_MAP.json` contract.
</Card>

<Card href="/full-tree-restoration" title="Full tree restoration" icon="book">
Deep default workflow when no coordination state exists yet — graph build, ledger init, auto-restore, organize, and promote.
</Card>

<Card href="/orchestration-scripts" title="Orchestration scripts reference" icon="book">
CLI signatures for `ledger.ts`, `plan-organize.ts`, `promote-organized.ts`, `promote-final.ts`, and `quality-gate.ts`.
</Card>

<Card href="/quality-bar-and-anti-patterns" title="Quality bar and anti-patterns" icon="book">
Readable and deep-tier completion criteria, Stage 3 acceptance categories, and gate failure modes.
</Card>

---

## 13. Refresh script reference

> refresh-codex-ref.mjs flags (--dry-run, --skip-format), CODEX_APP_ASAR override, safety guards on ./ref replacement, Prettier verification passes, and expected stdout completion lines.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/13-refresh-script-reference.md
- Generated: 2026-06-27T21:21:01.431Z

### Source Files

- `.agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs`
- `.agents/skills/codex-app-ref-refresh/SKILL.md`
- `README.md`

---
title: "Refresh script reference"
description: "refresh-codex-ref.mjs flags (--dry-run, --skip-format), CODEX_APP_ASAR override, safety guards on ./ref replacement, Prettier verification passes, and expected stdout completion lines."
---

`refresh-codex-ref.mjs` is the bundled Node entry point for the `codex-app-ref-refresh` skill. From the repository root it deletes `./ref`, extracts the Codex.app `app.asar` with `@electron/asar`, formats every extracted `.js` and `.css` file with Prettier (skipping `ref/node_modules`), and verifies JavaScript formatting with up to three `--list-different` passes before printing a completion line.

## Invocation

<CodeGroup>

```bash title="Default refresh"
node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs
```

```bash title="Dry-run path inspection"
node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs --dry-run
```

```bash title="Custom asar location"
CODEX_APP_ASAR=/path/to/app.asar node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs
```

</CodeGroup>

Run from the directory that should own `./ref`. The script resolves `ref` as `path.join(process.cwd(), "ref")` and refuses unsafe targets before any deletion.

<ParamField body="--dry-run" type="flag">
Prints resolved `Workspace`, `Source asar`, and `Target ref` paths, then exits with `Dry run only; no files changed.` Does not delete `./ref`, extract the asar, or invoke Prettier.
</ParamField>

<ParamField body="--skip-format" type="flag">
Extracts the asar and logs `Skipping Prettier formatting.` before exit. Use only when raw, unformatted extraction is intentional — minified asar output is unreadable for deobfuscation without the default format step.
</ParamField>

<ParamField body="--help" type="flag">
Aliases: `-h`. Prints usage (flags and `CODEX_APP_ASAR`) and exits 0.
</ParamField>

<ParamField body="CODEX_APP_ASAR" type="string">
Overrides the default source asar path `/Applications/Codex.app/Contents/Resources/app.asar`. The value is passed through `path.resolve()` before existence checks and extraction.
</ParamField>

Unknown positional flags or arguments cause a non-zero exit: `Error: unknown argument(s): <names>`.

## Pipeline phases

```mermaid
sequenceDiagram
  participant CLI as refresh-codex-ref.mjs
  participant FS as ./ref
  participant ASAR as npx @electron/asar
  participant P as npx prettier

  CLI->>CLI: assertSafeRefDir(./ref)
  CLI->>CLI: log Workspace / Source asar / Target ref
  alt --dry-run
    CLI-->>CLI: exit 0 (no changes)
  else live run
    CLI->>FS: rmSync ./ref (recursive)
    CLI->>FS: mkdirSync ./ref
    CLI->>ASAR: extract asarPath → refDir
    alt --skip-format
      CLI-->>CLI: exit 0 (skip Prettier)
    else default
      CLI->>CLI: walkFormatTargets (skip node_modules)
      CLI->>P: --write --ignore-path /dev/null (JS + CSS)
      loop up to 3 verification passes
        CLI->>P: --list-different (JS only)
        alt files still differ
          CLI->>P: --write on differing JS files
        end
      end
      CLI-->>CLI: Codex app ref refresh complete.
    end
  end
```

| Phase | Action | Subprocess | Skipped when |
| ----- | ------ | ---------- | ------------ |
| Guard | `assertSafeRefDir` on resolved `./ref` | — | Never |
| Sync | `rmSync` + `mkdirSync` + asar extract | `npx -y @electron/asar extract` | `--dry-run` |
| Format | Prettier write over all `.js`/`.css` under `ref` | `npx -y prettier --write` | `--dry-run`, `--skip-format` |
| Verify | `--list-different` loop on JS files only | `npx -y prettier --list-different` | `--dry-run`, `--skip-format`, or zero JS files |
| Report | Completion or skip-format line | — | `--dry-run` only |

## Safety guards on `./ref` replacement

`assertSafeRefDir` runs before path logging and before `--dry-run` short-circuits deletion. All checks must pass or the script exits with `Error: …`.

| Guard | Condition | Error message prefix |
| ----- | --------- | -------------------- |
| Not a system anchor | `refDir` must not equal filesystem root, user home (`homedir()`), or `cwd` itself | `ref directory is unsafe to replace` |
| Basename | Directory name must be exactly `ref` | `ref directory must be named "ref"` |
| Relative path | `path.relative(cwd, refDir)` must equal `ref` | `ref directory must resolve to ./ref under the current working directory` |

<Warning>
The guard validates the *shape* of the target (`./ref` under cwd), not whether cwd is this repository. Running from `/tmp` would replace `/tmp/ref`. Always `cd` to the intended workspace first.
</Warning>

After guards pass, a live run unconditionally executes `rmSync(refDir, { recursive: true, force: true })`. There is no backup or prompt.

## Prettier formatting and verification

### Walk and scope

`walkFormatTargets` recursively collects files ending in `.js` or `.css` under `refDir`. Any directory named `node_modules` is skipped entirely — extracted vendor trees under `ref/node_modules` are never formatted.

### Ignore-path bypass

Both write and verify invocations pass `--ignore-path /dev/null` (`PRETTIER_IGNORE_PATH`). This bypasses `.gitignore` and `.prettierignore`. In this repository, `ref/` is gitignored, so a plain `prettier --write ref/` would silently skip files; the script forces formatting anyway.

### Write pass

`formatWithPrettier` batches file paths so the combined path-string length stays under 50,000 characters per `npx` invocation, then runs:

```
npx -y prettier --write --ignore-path /dev/null <batch...>
```

All discovered JS and CSS files are formatted in the initial write pass.

### Verification passes (JS only)

`verifyWithPrettier` runs only on `.js` files (`jsFiles` filter). CSS is formatted but not used as a hard verification gate — bundled Tailwind CSS can be non-idempotent under Prettier.

| Pass behavior | Detail |
| ------------- | ------ |
| Max passes | 3 (`maxVerificationPasses`) |
| Check command | `prettier --list-different --ignore-path /dev/null` |
| Allowed exit codes | `0` (all match) and `1` (some differ) |
| Retry | On pass 1–2, differing files are reformatted and the loop continues |
| Failure | On pass 3 with remaining diffs: `Prettier verification failed: N file(s) still need formatting.` |
| Zero JS files | Logs `Prettier verification complete.` immediately |

Intermediate retry stdout:

```
Prettier verification found N file(s) needing another pass; reformatting...
```

Acceptable only when followed by `Prettier verification complete.`

## Expected stdout and stderr

### Startup (all modes except `--help`)

<RequestExample>

```bash
node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs --dry-run
```

</RequestExample>

<ResponseExample>

```text
Workspace: /path/to/decode-codex
Source asar: /Applications/Codex.app/Contents/Resources/app.asar
Target ref: /path/to/decode-codex/ref
Dry run only; no files changed.
```

</ResponseExample>

With `CODEX_APP_ASAR=/custom/path/app.asar`, the `Source asar:` line reflects the resolved override.

### Successful full refresh

<ResponseExample>

```text
Workspace: /path/to/decode-codex
Source asar: /Applications/Codex.app/Contents/Resources/app.asar
Target ref: /path/to/decode-codex/ref
Removing existing ./ref...
Extracting Codex app asar...
Formatting 842 JS/CSS file(s) with Prettier, ignoring git/prettier ignore files...
Prettier verification complete.
Codex app ref refresh complete.
```

</ResponseExample>

The file count `N` varies with the installed Codex.app version. A retry pass may appear between the formatting line and `Prettier verification complete.`

### `--skip-format` completion

```text
Skipping Prettier formatting.
```

Treat this as an incomplete refresh for deobfuscation unless extraction-only was intentional.

### Error lines

| Trigger | Example stderr |
| ------- | -------------- |
| Missing asar | `Error: asar file not found: /Applications/Codex.app/Contents/Resources/app.asar` |
| Unsafe ref path | `Error: ref directory is unsafe to replace: /Users/you` |
| Wrong ref name/location | `Error: ref directory must be named "ref": …` or `… must resolve to ./ref …` |
| Unknown flag | `Error: unknown argument(s): --bogus` |
| Subprocess failure | `Error: npx exited with status 1` |
| Verification exhausted | `Error: Prettier verification failed: 3 file(s) still need formatting.` |

All errors are prefixed with `Error:` and exit non-zero via `process.exit(1)`.

## Verification checklist

<Steps>

<Step title="Confirm cwd">
Ensure `Target ref:` in startup output ends with your workspace `/ref`, not another directory's `./ref`.
</Step>

<Step title="Read completion signals">
A complete default run must include, in order after extraction:
`Formatting N JS/CSS file(s)…` → `Prettier verification complete.` → `Codex app ref refresh complete.`
</Step>

<Step title="Spot-check formatted output">
Open a known file under `ref/webview/assets/` — it should be multi-line Prettier output, not a single minified line.
</Step>

<Step title="Manual Prettier check (optional)">
If re-verifying by hand, pass `--ignore-path /dev/null`; otherwise gitignored `ref/` files may be skipped. Prefer strict checks on JS files only.
</Step>

</Steps>

<Check>
`Prettier verification found N file(s) needing another pass; reformatting…` is normal when followed by `Prettier verification complete.`
</Check>

## Troubleshooting

<AccordionGroup>

<Accordion title="Prettier reports success but ref files look minified">
You likely ran plain `prettier` without `--ignore-path /dev/null`. Because `ref/` is gitignored, Prettier 3 defaults skip those files. Re-run the bundled script or add the ignore-path flag manually.
</Accordion>

<Accordion title="Verification fails after three passes">
Inspect the files listed in the failure message. Persistent diffs usually indicate Prettier parser options incompatible with a specific bundle chunk. Fix or exclude manually, then re-run; the script does not support per-file overrides.
</Accordion>

<Accordion title="asar file not found">
Install Codex.app at the default path or set `CODEX_APP_ASAR` to the actual `Contents/Resources/app.asar` inside your bundle.
</Accordion>

<Accordion title="npx failed to start or exited non-zero">
Confirm Node.js and network access for `npx -y` downloads of `@electron/asar` and `prettier`. Subprocess stderr is inherited (`stdio: "inherit"` on extract/format; captured on list-different with stderr forwarded).
</Accordion>

</AccordionGroup>

## Related pages

<CardGroup>

<Card title="Refresh Codex reference source" href="/refresh-codex-ref">
Workflow-oriented guide: when to refresh, agent invocation, and pre-deobfuscation checks.
</Card>

<Card title="External tools and dependencies" href="/external-tools-and-dependencies">
`@electron/asar`, Prettier via `npx`, subprocess exit codes, and offline behavior.
</Card>

<Card title="Pipeline caveats" href="/pipeline-caveats">
Prettier `.gitignore` traps, non-idempotent CSS, and recovery steps shared with the deobfuscation pipeline.
</Card>

<Card title="Installation" href="/installation">
macOS Codex.app path, Node.js prerequisite, and first-time setup before running the script.
</Card>

</CardGroup>

---

## 14. Stage 1 scripts reference

> CLI signatures and behavior for detect, unpack, string-array, decode-strings, simplify, control-flow-report, and the deobfuscate.ts orchestrator with --skip and --stop-after controls.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/14-stage-1-scripts-reference.md
- Generated: 2026-06-27T21:21:12.639Z

### Source Files

- `.agents/skills/deobfuscate-javascript/stages/stage-1-deobfuscate.md`
- `.agents/skills/deobfuscate-javascript/scripts/detect.ts`
- `.agents/skills/deobfuscate-javascript/scripts/unpack.ts`
- `.agents/skills/deobfuscate-javascript/scripts/string-array.ts`
- `.agents/skills/deobfuscate-javascript/scripts/decode-strings.ts`
- `.agents/skills/deobfuscate-javascript/scripts/simplify.ts`
- `.agents/skills/deobfuscate-javascript/scripts/deobfuscate.ts`

---
title: "Stage 1 scripts reference"
description: "CLI signatures and behavior for detect, unpack, string-array, decode-strings, simplify, control-flow-report, and the deobfuscate.ts orchestrator with --skip and --stop-after controls."
---

Stage 1 deobfuscation lives under `.agents/skills/deobfuscate-javascript/scripts/`. Seven Bun entry points unwind packers, Obfuscator.IO string arrays, encoded literals, dead code, and opaque predicates using pure Babel transforms—no LLM dependency. Run them on obfuscated input before Stage 2; on purely minified (not obfuscated) input, skip Stage 1 entirely.

<Info>
All Stage 1 scripts accept `-` as the input path to read from stdin. Transformed code or JSON reports go to stdout unless `--out` is set. Progress summaries and warnings go to stderr. A successful run that makes no changes still exits `0`.
</Info>

## Prerequisites

Run from the skill directory after `bun install`:

```bash
cd .agents/skills/deobfuscate-javascript
```

Every script is invoked as `bun scripts/<name>.ts`.

## Pipeline order

Stage 1 steps are order-sensitive. Each pass expects the AST shape produced by the previous one.

```mermaid
sequenceDiagram
  participant CLI as deobfuscate.ts
  participant D as detect
  participant U as unpack
  participant SA as string-array
  participant DS as decode-strings
  participant S as simplify
  participant CF as control-flow-report

  CLI->>D: detect (regex, read-only)
  CLI->>U: unpack (packer → aaencode → urlencoded)
  Note over CLI,D: detect-after-unpack if unpack changed code
  CLI->>SA: inline _0x... arrays
  CLI->>DS: decode literals
  CLI->>S: fold / dead-code / inline
  CLI->>CF: CFG report (read-only)
```

| Step | Mutates source | Mechanism |
|------|----------------|-----------|
| `detect` | No | Regex heuristics |
| `unpack` | Yes | String replacement + optional `new Function` |
| `string-array` | Yes | Babel AST |
| `decode-strings` | Yes | Babel AST |
| `simplify` | Yes | Babel AST (multi-pass) |
| `control-flow-report` | No | Babel AST analysis |

<Warning>
Run Stage 1 before Stage 2. `extract.ts` byte offsets and pre-deobfuscation `renames.json` ids become stale after Stage 1 rewrites.
</Warning>

## Shared exit codes

| Code | Meaning |
|------|---------|
| `0` | Success (including no-op runs) |
| `1` | I/O failure (read/write) |
| `2` | Parse error (AST-based scripts) |
| `64` | Usage error (missing input, invalid flags, unknown step names) |

## `detect.ts`

Scans source with regex heuristics and emits a JSON report. Does not parse or mutate code.

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

<ParamField body="input" type="positional string" required>
Path to a `.js` file, or `-` for stdin.
</ParamField>

<ParamField body="--out / -o" type="string">
Write JSON report to this path instead of stdout.
</ParamField>

<ResponseField name="techniques" type="array">
Each entry: `{ name, confidence, evidence }`. Sorted by `confidence` descending.
</ResponseField>

<ResponseField name="recommendation" type="string">
Ordered script list to run next, or guidance to skip Stage 1 and go to Stage B when no obfuscation is found.
</ResponseField>

**Detected technique names:** `packer`, `aaencode`, `urlencoded`, `string-array`, `string-array-rotation`, `hex-encoding`, `unicode-encoding`, `from-char-code`, `base64-decoding`, `control-flow-flattening`, `opaque-predicates`, `dead-code-injection`, `obfuscator-io`, `webpack`, `single-line-uglify`.

<RequestExample>
```bash
bun scripts/detect.ts fixtures/composite.min.js
```
</RequestExample>

<ResponseExample>
```json
{
  "input": "fixtures/composite.min.js",
  "size": 842,
  "techniques": [
    { "name": "string-array", "confidence": 0.9, "evidence": "_0x... array declaration with 12 indexed access(es)" }
  ],
  "recommendation": "Run, in order: scripts/string-array.ts → scripts/decode-strings.ts → scripts/simplify.ts. Or run scripts/deobfuscate.ts to do all at once."
}
```
</ResponseExample>

Stderr prints a one-line summary, e.g. `detect: 4 technique(s) — string-array(90%), from-char-code(85%), …`.

## `unpack.ts`

Iteratively unpacks layered encodings. Per iteration, tries **Packer → AAEncode → URLEncode** until no layer matches or `--max-iterations` is reached.

```bash
bun scripts/unpack.ts <input.js|-> [--out output.js] [--max-iterations 5] [--no-eval]
```

<ParamField body="--max-iterations" type="integer" default="5">
Maximum unpack loop iterations. Must be a positive integer.
</ParamField>

<ParamField body="--no-eval" type="boolean" default="false">
Refuse Dean Edwards Packer and AAEncode unpacking. Input is left unchanged; `evalRefused` is set. URLEncode (`decodeURIComponent`) is unaffected.
</ParamField>

**Eval safety:** Packer arg-list parsing and AAEncode decoding use `new Function(...)`. The sandbox cannot read your filesystem, but it does execute input bytecode. `--no-eval` refuses these paths and logs a warning to stderr.

<RequestExample>
```bash
bun scripts/unpack.ts packed.js --out unpacked.js --no-eval
```
</RequestExample>

Stderr examples:

- `unpack: 1 step(s) — packer(412→89) → unpacked.js`
- `unpack: no packer/aaencode/urlencode detected — unchanged`
- `unpack: no changes (eval refused)`

## `string-array.ts`

Inlines Obfuscator.IO `var _0x… = ['a','b',…]` arrays: applies rotation IIFEs, replaces indexed member access with string literals, removes dead array declarations.

```bash
bun scripts/string-array.ts <input.js|-> [--out output.js]
```

<Warning>
If string access is wrapped behind a decoder function (e.g. `_0xabc('0x1')`), the script collects arrays but replaces zero references (`decoderIndirection: true`). Run `simplify.ts` first to inline small decoder functions, then re-run `string-array.ts`.
</Warning>

Stderr reports counts: arrays collected, refs replaced, arrays removed, rotations applied. On decoder indirection, stderr includes a `WARNING` line.

## `decode-strings.ts`

Literal-only decoder pass on the AST.

```bash
bun scripts/decode-strings.ts <input.js|-> [--out output.js]
```

| Transform | Constraint |
|-----------|------------|
| `String.fromCharCode(72, 101, …)` | All arguments must be integer numeric literals in `0…0xffff` |
| `atob("…")` | Single string-literal argument; invalid base64 is skipped |
| `\xNN` / `\uNNNN` escapes | Normalized in string literals via Babel regeneration |

Variables and non-literal arguments are left untouched.

Stderr: `decode-strings: 2 fromCharCode, 1 atob, escapes normalized` or `decode-strings: nothing to decode`.

## `simplify.ts`

Multi-pass Babel simplifier that loops to a fixed point (up to `--max-passes`).

```bash
bun scripts/simplify.ts <input.js|-> [--out output.js] [--max-passes 10] [--no-inline]
```

<ParamField body="--max-passes" type="integer" default="10">
Maximum simplification passes. Must be a positive integer.
</ParamField>

<ParamField body="--no-inline" type="boolean" default="false">
Skip scope-aware constant inlining (`var k = 5` → inline `5` at references). Use when preserving short names for Stage 2 rename.
</ParamField>

**Per-pass transforms:** constant folding (binary, comparison, unary), dead `if`/`?:` removal, logical short-circuit, identity ops (`x+0`→`x`), sequence-in-statement expansion, computed-to-dot (`obj["foo"]`→`obj.foo`), template-literal collapse, and (unless `--no-inline`) provably-constant variable inlining.

Stderr prints pass count and per-category totals: folded, dead-if, inlined, computed→dot, seq, identity, etc.

## `control-flow-report.ts`

Read-only analysis. **Does not mutate source.** Emits JSON describing patterns that survived `simplify.ts` and require manual rewrite.

```bash
bun scripts/control-flow-report.ts <input.js|-> [--out report.json]
```

<ResponseField name="flatteners" type="array">
`while(true){switch(…)}` patterns with `dispatchVariable`, `caseCount`, `caseLabels`, `containingFunction`, and `rewriteHint`.
</ResponseField>

<ResponseField name="splitDispatches" type="array">
`"0|1|2".split("|")` dispatch arrays with `states`, `splitOn`, and `rewriteHint`.
</ResponseField>

<ResponseField name="opaquePredicates" type="array">
Residual opaque tests (`literal-vs-literal`, `not-not-array`, `not-not-object`, `void-zero`) with `alwaysTruthy` and `rewriteHint`.
</ResponseField>

Automatic CFG reconstruction is intentionally not attempted. Trace `dispatchVariable` transitions and inline cases in execution order by hand.

## `deobfuscate.ts` orchestrator

Runs the full Stage 1 pipeline in one invocation.

```bash
bun scripts/deobfuscate.ts <input.js|-> [--out output.js] [--report report.json] \
  [--skip step1,step2] [--stop-after step] [--no-eval] [--no-inline] \
  [--max-iterations 5] [--max-passes 10]
```

### Step names

Valid values for `--skip` and `--stop-after`:

`detect`, `unpack`, `string-array`, `decode-strings`, `simplify`, `control-flow-report`

<ParamField body="--skip" type="comma-separated string">
Comma-separated step names to omit. Unknown names exit `64`.
</ParamField>

<ParamField body="--stop-after" type="string">
Run through this step (inclusive), then halt. Unknown names exit `64`.
</ParamField>

<ParamField body="--report / -r" type="string">
Write a JSON run report with `input`, `originalSize`, `finalSize`, `reduction` (percentage string), and `steps` array.
</ParamField>

<ParamField body="--no-eval" type="boolean" default="false">
Forwarded to `unpack`.
</ParamField>

<ParamField body="--no-inline" type="boolean" default="false">
Forwarded to `simplify`.
</ParamField>

<ParamField body="--max-iterations" type="integer" default="5">
Forwarded to `unpack`.
</ParamField>

<ParamField body="--max-passes" type="integer" default="10">
Forwarded to `simplify`.
</ParamField>

### Orchestrator behavior

- Steps run in fixed order: detect → unpack → string-array → decode-strings → simplify → control-flow-report.
- After `unpack` changes the source, a bonus `detect-after-unpack` step re-scans (unless `detect` is in `--skip`).
- Each step is wrapped in try/catch; one failing step records `{ step, error }` in the report and the pipeline continues.
- `detect` is regex-based and succeeds even on syntactically invalid JS; later AST steps may record parse errors.
- Re-running on already-deobfuscated output is idempotent (output unchanged).

<RequestExample>
```bash
bun scripts/deobfuscate.ts obfuscated.js \
  --out clean.js \
  --report stage1-report.json \
  --stop-after simplify
```
</RequestExample>

<ResponseExample>
```text
deobfuscate: detect: 4 technique(s)
deobfuscate: unpack: 0 iter
deobfuscate: string-array: 1 arrays collected, 8 refs replaced, 1 removed
deobfuscate: decode-strings: 2 fromCharCode
deobfuscate: simplify: 3 pass(es), 12 folded, 2 dead, 4 inlined
deobfuscate: done — 842 → 312 bytes
```
</ResponseExample>

### `--skip` and `--stop-after` examples

<CodeGroup>
```bash title="Skip unpack for pre-unpacked input"
bun scripts/deobfuscate.ts already-unpacked.js --skip unpack
```

```bash title="Diagnostic: detect only"
bun scripts/deobfuscate.ts suspect.js --stop-after detect
```

```bash title="Partial pipeline through string-array"
bun scripts/deobfuscate.ts obfuscated.js --stop-after string-array --out partial.js
```

```bash title="Skip simplify to preserve names for Stage 2"
bun scripts/deobfuscate.ts obfuscated.js --skip simplify --out literals-only.js
```
</CodeGroup>

## Ordering rules

<Steps>
<Step title="Unpack first">
Packed input is one giant `CallExpression`; AST passes have nothing to parse until `unpack.ts` expands it.
</Step>
<Step title="String-array before decode-strings">
Inlining arrays first limits `decode-strings` to literals that are actually referenced.
</Step>
<Step title="Simplify after string-array and decode-strings">
Running `simplify` first can split rotation IIFEs so `string-array`'s matcher no longer recognizes them.
</Step>
<Step title="Control-flow-report last">
`simplify` folds most opaque predicates; the report surfaces what remains for manual CFG rewrite.
</Step>
<Step title="Stage 1 before Stage 2">
Full order: **Stage 1 → wakaru-normalize → extract**. Wakaru is also byte-rewriting and must finish before any `extract`/`apply`.
</Step>
</Steps>

## Troubleshooting

| Symptom | Likely cause | Recovery |
|---------|--------------|----------|
| `string-array: WARNING — … decoder indirection` | Decoder function wraps array access | Run `simplify.ts`, then re-run `string-array.ts` |
| `unpack: no changes (eval refused)` | `--no-eval` blocked Packer/AAEncode | Remove `--no-eval` if input is trusted, or unpack manually |
| `parse error` (exit `2`) | Syntax too broken for Babel | Fix syntax upstream; `detect` still works for heuristics |
| `control-flow-report` lists flatteners | CFG obfuscation survived simplify | Follow per-item `rewriteHint`; rewrite by hand |
| Pipeline step shows `ERROR` in `--report` | That step threw; later steps still ran | Inspect `steps[].error` in the report JSON |

<AccordionGroup>
<Accordion title="When to skip Stage 1 entirely">
If `detect` returns an empty `techniques` array and the file is only minified (short identifiers, no packers or `_0x` arrays), skip Stage 1 and proceed directly to Stage 2 (`wakaru-normalize` → `extract`).
</Accordion>

<Accordion title="Library imports">
Each script exports its core function (`detectReport`, `unpack`, `transformStringArrays`, `decodeStrings`, `simplify`, `analyzeControlFlow`, `deobfuscate`) for programmatic use and test fixtures under `scripts/*.test.ts`.
</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Handle obfuscated input" href="/obfuscated-input">
Workflow guide for packed, encoded, and Obfuscator.IO input with ordering rationale.
</Card>
<Card title="Restoration pipeline" href="/restoration-pipeline">
Stage 1, Stage 2, and Stage 3 depth tiers and the restoration contract.
</Card>
<Card title="Pipeline caveats" href="/pipeline-caveats">
Eval safety, decoder indirection, sourcemap invalidation, and other failure modes.
</Card>
<Card title="External tools and dependencies" href="/external-tools-and-dependencies">
Bun, Babel packages, BSD exit codes, and subprocess tool requirements.
</Card>
<Card title="Stage 2 scripts reference" href="/stage-2-scripts">
Rename and polish scripts that follow Stage 1.
</Card>
</CardGroup>

---

## 15. 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.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/15-stage-2-scripts-reference.md
- Generated: 2026-06-27T21:21:21.392Z

### 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>

---

## 16. Orchestration scripts reference

> Full-restoration CLI reference: check-entry exit codes, build-import-graph flags, ledger subcommands, auto-restore-full, plan-organize, promote-organized, quality-gate checks, and semantic-finalize recipes.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/16-orchestration-scripts-reference.md
- Generated: 2026-06-27T21:22:35.567Z

### Source Files

- `.agents/skills/deobfuscate-javascript/workflows/full-restoration.md`
- `.agents/skills/deobfuscate-javascript/scripts/check-entry.ts`
- `.agents/skills/deobfuscate-javascript/scripts/build-import-graph.ts`
- `.agents/skills/deobfuscate-javascript/scripts/ledger.ts`
- `.agents/skills/deobfuscate-javascript/scripts/auto-restore-full.ts`
- `.agents/skills/deobfuscate-javascript/scripts/quality-gate.ts`
- `.agents/skills/deobfuscate-javascript/scripts/semantic-finalize.ts`
- `.agents/skills/deobfuscate-javascript/scripts/acceptance-checklist.md`

---
title: "Orchestration scripts reference"
description: "Full-restoration CLI reference: check-entry exit codes, build-import-graph flags, ledger subcommands, auto-restore-full, plan-organize, promote-organized, quality-gate checks, and semantic-finalize recipes."
---

The deobfuscate-javascript skill coordinates whole-tree restoration through Bun scripts under `.agents/skills/deobfuscate-javascript/scripts/`. They maintain `manifest.json` (file-level import graph) and `ledger.json` (symbol-level checklist) under `<target>/.deobfuscate-javascript/_full/`, then drain mechanical checkpoints into semantic deliverables under `restored/`. All scripts share BSD-style exit codes: `0` success, `1` I/O or parse failure, `64` usage error, `75` lock held (`ledger.ts claim`).

```text
check-entry → build-import-graph → build-symbol-ledger
     │              │                      │
     └──────────────┴──────────────────────┘
                         │
              ledger.ts (iterate: claim → rename → mark-done)
                         │
              auto-restore-full (batch checkpoints)
                         │
         plan-organize → promote-organized → quality-gate
                         │
              semantic-finalize (icon/button recipes at promote time)
```

<Info>
Run scripts from the skill directory with `bun <skill-dir>/scripts/<script>.ts`. Set `TARGET=restored` and `FULL="$TARGET/.deobfuscate-javascript/_full"` for the examples below.
</Info>

## Shared paths and artifacts

| Path | Role |
|------|------|
| `<target>/.deobfuscate-javascript/_full/manifest.json` | Import graph, per-chunk `kind`, `stages`, `organization` |
| `<target>/.deobfuscate-javascript/_full/ledger.json` | Symbol statuses, `crossFileBindings` |
| `<target>/.deobfuscate-javascript/_full/files/<basename>/` | Per-chunk workspace (`original.js`, `renames.json`, `candidate.tsx`) |
| `<target>/.deobfuscate-javascript/_full/checkpoints/` | Flat `.tsx` checkpoints from `auto-restore-full.ts` |
| `<target>/.deobfuscate-javascript/_full/organize-plan.json` | Proposed domain + semantic paths from `plan-organize.ts` |
| `<target>/IMPORT_MAP.json` | Public chunk registry updated by `promote-organized.ts` |

## check-entry.ts

Sanity-checks the restoration entry before graph construction. Prevents restoring from a transitive vendor leaf (high in-degree, low out-degree) and declaring a tiny subtree complete.

<CodeGroup>

```bash title="Check a specific entry"
bun scripts/check-entry.ts ref/webview/assets/app-shell-JLpboL12.js \
  --root ref/webview/assets
```

```bash title="Auto-discover from index.html"
ENTRY=$(bun scripts/check-entry.ts --discover --root ref/webview/assets)
```

</CodeGroup>

### Signals

| Signal | Meaning |
|--------|---------|
| `isRoot` | Entry referenced by `index.html` `<script>` or `modulepreload` |
| `localOutDegree` | Count of local sibling imports (high for real app entries) |
| `inDegree` | How many siblings import this chunk (high for vendor leaves) |
| `looksVendored` | Basename or content matches known vendor patterns |
| `suspicious` | Not in `index.html`, `inDegree ≥ 5`, `localOutDegree ≤ 8` |

### Exit codes

| Code | Meaning |
|------|---------|
| `0` | Entry looks reasonable |
| `3` | Suspicious — likely transitive dependency, not app entry |
| `1` | I/O error or discovery found no resolvable entry |
| `64` | Usage error |

<ParamField body="--root" type="string" required>
Assets directory containing sibling chunks. Required for `--discover`.
</ParamField>

<ParamField body="--index" type="string">
Override path to `index.html` when auto-discovery should not use default candidates.
</ParamField>

<ParamField body="--discover" type="boolean">
Read `index.html`, score script/preload roots, print chosen entry path to stdout.
</ParamField>

<ParamField body="--out" type="string">
Write JSON report (`EntryReport` or `DiscoverEntryResult`) to file.
</ParamField>

<Warning>
`build-import-graph.ts` runs the same advisory check but does not block. Run `check-entry.ts` first, or heed its warning, before sinking effort into the wrong subtree. Exit `3` means switch to the `index.html` script root or a high-fan-out `app-main-*` chunk.
</Warning>

## build-import-graph.ts

BFS-walks the entry's ESM import graph, classifies each chunk, copies `local` sources into `_full/files/<basename>/original.js`, and writes `manifest.json`.

<CodeGroup>

```bash title="Deep/full restore (default)"
bun scripts/build-import-graph.ts \
  --target "$TARGET" \
  --root ref/webview/assets \
  --out "$FULL/manifest.json"
```

```bash title="Pin explicit entry"
bun scripts/build-import-graph.ts "$ENTRY" \
  --target "$TARGET" --root ref/webview/assets
```

```bash title="Quick/targeted (skip huge siblings)"
bun scripts/build-import-graph.ts "$ENTRY" \
  --target "$TARGET" --root ref/webview/assets --max-lines 5000
```

</CodeGroup>

### Chunk kinds

| Kind | Behavior |
|------|----------|
| `local` | Restorable project chunk; BFS continues, workspace staged |
| `oversized-local` | Recorded boundary only when `--max-lines N > 0` exceeded; not restored |
| `npm-leaf` | Terminal node from `CHUNK_NAME_REGISTRY` or bundled vendor data; `stages.skipped` |
| `external` | Bare specifier like `"react"`; terminal |

### Flags

<ParamField body="--target" type="string" required>
Restore root; manifest lands under `<target>/.deobfuscate-javascript/_full/`.
</ParamField>

<ParamField body="--root" type="string">
Sibling-chunk directory. When positional entry is omitted, enables auto-discovery from `index.html`.
</ParamField>

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

<ParamField body="--max-lines" type="number" default="0">
`0` disables line cap (deep default). Positive value enables quick mode: files exceeding cap become `oversized-local`. Entry is always exempt.
</ParamField>

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

<ParamField body="--treat-as-npm" type="string">
Comma-separated basenames to classify as `npm-leaf` regardless of registry.
</ParamField>

<ParamField body="--rebuild" type="boolean" default="false">
Discard prior manifest `stages`/`owner` and start fresh.
</ParamField>

<ParamField body="--discover" type="boolean" default="false">
Auto-discover entry from `index.html` (requires `--root`).
</ParamField>

<ParamField body="--no-entry-check" type="boolean" default="false">
Silence the suspicious-entry advisory when a positional entry is provided.
</ParamField>

<ParamField body="--index" type="string">
Override `index.html` path for discovery and entry check.
</ParamField>

### Console summary

After the run, stderr reports `X local · Y oversized-local (skipped) · Z npm-leaf · E edges`. In a deep/full task, `Y` must be `0`; rebuild with `--max-lines 0` if non-zero.

Re-running is idempotent: existing `kind`, `npmPackage`, and `stages` are preserved; only `imports`/`exports` refresh.

## build-symbol-ledger.ts

Initializes `ledger.json` and per-file `symbols.json` for every `kind: local` chunk. Run once after graph build, before the rename loop or `auto-restore-full.ts`.

```bash
bun scripts/build-symbol-ledger.ts \
  --target "$TARGET" \
  --manifest "$FULL/manifest.json" \
  --out "$FULL/ledger.json"
```

<ParamField body="--rebuild" type="boolean" default="false">
Regenerate all symbols. Without `--rebuild`, only `pending` symbols refresh.
</ParamField>

<ParamField body="--only" type="string">
Comma-separated basenames to restrict extraction to.
</ParamField>

## ledger.ts

Coordinates parallel-safe, resumable work across the import tree. Lockfiles live at `_full/locks/<basename>.<stage>.lock` with `O_EXCL` create semantics.

### Stages

| Stage | Manifest field | Typical work |
|-------|----------------|--------------|
| `extract` | `extracted` | Symbol extraction (usually via `build-symbol-ledger.ts`) |
| `rename` | `renamed` | `apply.ts` + `renames.json` |
| `polish` | `polished` | `polish.ts` |
| `finalize` | `finalized` | Hand-cleaned deliverable |
| `organize` | `organized` | Domain + semantic path decided |
| `promote` | `promoted` | Public file written to `restored/` |

### Subcommands

| Subcommand | Purpose |
|------------|---------|
| `status` | One-line summary plus completion overview (`promoted/total`, blocked list) |
| `next [--stage rename] [--skip list]` | Single best `(basename, stage)` suggestion |
| `frontier [--stage rename] [--skip list]` | All restorable files now, deepest first |
| `claim <basename> <stage> --owner <id>` | Acquire lock; exit `75` if held |
| `release <basename> <stage>` | Release lock (required only on cancellation) |
| `mark-done <basename> <stage> [--renames file]` | Flip stage; apply renames to ledger; auto-release lock |
| `mark-faced <basename> [--force]` | Mark vendor boundary as satisfied via facade |
| `set-organization <basename> --domain <d> --semantic-path <p>` | Override one chunk's public path |
| `propagate-cross-file [--from <basename>] [--auto-fill]` | Push producer names to consumer bindings |
| `reset <basename> [--stage rename]` | Roll stage back to pending |

<RequestExample>

```bash
# Fan-out batch: list every file ready to rename
bun scripts/ledger.ts frontier --stage rename --target "$TARGET"

# Claim and work one file
bun scripts/ledger.ts claim AnimatePresence-BMR_rf2Q rename \
  --target "$TARGET" --owner agent-1

# After apply.ts + polish.ts
bun scripts/ledger.ts mark-done AnimatePresence-BMR_rf2Q rename \
  --target "$TARGET" --renames "$FULL/files/AnimatePresence-BMR_rf2Q/renames.json"

bun scripts/ledger.ts propagate-cross-file --target "$TARGET" \
  --from AnimatePresence-BMR_rf2Q
```

</RequestExample>

### Locking rules

- One lock per `(basename, stage)`; `claim` uses `O_EXCL` — second claim exits `75`.
- `mark-done` deletes the lockfile automatically.
- Stale locks older than 30 minutes can be reclaimed with `claim --steal`.
- Different stages of the same file can be in flight, but in practice rename → polish → finalize is sequential per file.

### mark-faced

Marks a vendor/runtime boundary chunk as `stages.faced` so consumers treat it as a satisfied dependency. Refuses app/feature basenames (`app-shell-*`, `app-main-*`, pages, panels) unless `--force`. A facade is an open boundary — the deep restore remains incomplete until reported in README/IMPORT_MAP.

## auto-restore-full.ts

Batch checkpoint executor for large trees. After `build-symbol-ledger.ts`, runs extract → smart-rename → apply → polish for every `kind: local` file and writes flat `.tsx` checkpoints to `_full/checkpoints/`.

```bash
bun scripts/auto-restore-full.ts --target "$TARGET" --format
```

### Flags

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

<ParamField body="--manifest" type="string">
Override manifest path (default: `_full/manifest.json`).
</ParamField>

<ParamField body="--ledger" type="string">
Override ledger path (default: `_full/ledger.json`).
</ParamField>

<ParamField body="--checkpoint-dir" type="string">
Checkpoint output directory (default: `_full/checkpoints/`).
</ParamField>

<ParamField body="--resume" type="boolean" default="false">
Skip chunks whose `auto-polished.tsx` and checkpoint already exist.
</ParamField>

<ParamField body="--format" type="boolean" default="false">
Run Prettier on the target after checkpoints are written.
</ParamField>

<ParamField body="--write-target-checkpoints" type="boolean" default="false">
Legacy/debug: also write checkpoints directly under `<target>/`. These are not deliverables.
</ParamField>

### Per-chunk outputs

For each local chunk, the script writes under `_full/files/<basename>/`:

- `auto-renames.json` — rename map
- `auto-renamed.js` — applied renames
- `auto-polished.tsx` — polished checkpoint

Plus `_full/checkpoints/<basename>.tsx` and `_full/auto-restore-report.json` with per-file `fallbackRenameRatio` and `needsAgentRewrite: true`.

<Warning>
Checkpoints are accelerators, not deliverables. The host agent must rewrite into semantic public files, pass `quality-gate.ts`, and complete Stage 3 acceptance before declaring done.
</Warning>

## plan-organize.ts

Proposes a semantic domain and public path for every local chunk, writing an editable `_full/organize-plan.json`.

```bash
# Generate plan
bun scripts/plan-organize.ts --target "$TARGET"

# After review, write approved entries into manifest
bun scripts/plan-organize.ts --target "$TARGET" --apply
```

### Plan entry fields

| Field | Values |
|-------|--------|
| `domain` | First path segment (`icons`, `utils`, `boundaries`, `composer`, …) |
| `semanticPath` | Public deliverable relative to target (`icons/download-icon.tsx`) |
| `recipe` | `icon`, `button`, `split`, `manual` |
| `classification` | `app-feature`, `icon`, `single-util`, `vendor-runtime`, `boundary`, … |
| `status` | `approved`, `needs-review` |
| `fallbackRenameRatio` | Surfaces checkpoints with heavy mechanical fallback names |

Heuristics auto-approve shape-known chunks (icons, buttons, single-utils). `app-feature` chunks default to `needs-review` with no domain — the planner cannot know your domain layout. Colliding public paths auto-downgrade to `needs-review`.

### Flags

<ParamField body="--apply" type="boolean" default="false">
Write `status: "approved"` rows into `manifest.organization` and set `stages.organized`.
</ParamField>

<ParamField body="--rebuild" type="boolean" default="false">
With `--apply`, overwrite already-organized chunks.
</ParamField>

<ParamField body="--only" type="string">
Comma-separated basenames to restrict planning.
</ParamField>

<ParamField body="--domain-map" type="string">
JSON file `{ "<basename-prefix>": "<domain>" }` for auto-assigning app chunks.
</ParamField>

<ParamField body="--out" type="string">
Plan output path (default: `_full/organize-plan.json`).
</ParamField>

### Override one chunk

```bash
bun scripts/ledger.ts set-organization composer-footer-DyRbFsKV \
  --domain composer --semantic-path composer/composer-footer.tsx \
  --recipe manual --classification app-feature --target "$TARGET"
```

## promote-organized.ts

Drains the promote frontier: builds typed deliverables, gates them, copies to semantic paths, updates `IMPORT_MAP.json`, rewrites imports, and sets `stages.promoted`. On gate failure, rolls back and continues — one bad chunk never blocks the batch.

```bash
bun scripts/promote-organized.ts --target "$TARGET" --dry-run
bun scripts/promote-organized.ts --target "$TARGET"
```

### Flags

<ParamField body="--dry-run" type="boolean" default="false">
Preview every move and gate verdict; write nothing.
</ParamField>

<ParamField body="--tier" type="string" default="deep">
`deep` enforces TypeScript types on candidates; `readable` allows untyped `manual` chunks with semantic names.
</ParamField>

<ParamField body="--only" type="string">
Comma-separated basenames to restrict promotion.
</ParamField>

<ParamField body="--limit" type="number">
Promote at most N chunks this run.
</ParamField>

<ParamField body="--owner" type="string">
Lock owner identifier (default: `agent-<pid>`).
</ParamField>

### Deliverable sources

| Recipe | Source at promote time |
|--------|------------------------|
| `icon`, `button` | `semantic-finalize.ts` runs automatically on checkpoint |
| `manual`, `split` | Hand-cleaned `_full/files/<basename>/candidate.tsx` required in deep mode |

Blocked chunks appear in `ledger.ts status` output: organized chunks in the promote frontier with no `candidate.tsx`.

## semantic-finalize.ts

Recipe-driven Stage 3 transform for icon and button checkpoints. Called by `promote-organized.ts` for `icon`/`button` recipe chunks; also usable standalone.

<CodeGroup>

```bash title="Finalize a checkpoint"
bun scripts/semantic-finalize.ts checkpoint.tsx \
  --out icons/download-icon.tsx \
  --recipe icon --source ref/webview/assets/download-XXXX.js --format
```

```bash title="Rewrite imports from import map"
bun scripts/semantic-finalize.ts --rewrite-imports restored/ \
  --import-map restored/IMPORT_MAP.json
```

</CodeGroup>

### Recipes

| Recipe | Detection | Output |
|--------|-----------|--------|
| `auto` | SVG exports → `icon`; `Button` pattern → `button`; else error | — |
| `icon` | Exported SVG components | Single file or directory with `types.ts`, per-icon files, `index.ts` barrel |
| `button` | Radius/color/size class tables | Typed `Button` with `Props`, `forwardRef`, `displayName`, `as const` lookup tables |

Icon output uses `IconProps = SVGProps<SVGSVGElement>`, kebab-case filenames, PascalCase component names. Button output extracts variant tables into named `*ClassNames` records with `keyof typeof` unions.

<ParamField body="--recipe" type="string" default="auto">
`auto`, `icon`, or `button`.
</ParamField>

<ParamField body="--source" type="string">
Original chunk path for provenance header and export-alias inference.
</ParamField>

<ParamField body="--basename" type="string">
Chunk basename when provenance header is absent.
</ParamField>

<ParamField body="--format" type="boolean" default="false">
Run Prettier on written files.
</ParamField>

## quality-gate.ts

Executable pre-filter and completion proof. Analyzes source mechanically; when the input is a target directory with `_full/manifest.json`, also runs full-restoration coverage checks.

```bash
# Per-file or mid-drain (intermediate)
bun scripts/quality-gate.ts restored/ --allow-organize-incomplete

# Completion proof (whole target)
bun scripts/quality-gate.ts restored/ --check-format
```

Exit `0` only when every analyzed file and coverage check passes.

### Per-file thresholds (defaults)

| Option | Default | Fails when |
|--------|---------|------------|
| `--max-cryptic-params` | `20` | Too many `*ParamN` / short param names |
| `--max-cryptic-bindings` | `80` | Too many cryptic identifiers |
| `--max-short-ref-count` | `50` | Excessive one-letter references |
| `--max-flat-lines` | `1000` | Flat file exceeds line cap (unless `--allow-flat`) |
| `--max-flat-exports` | `2` | Flat file has too many exports (unless `--allow-flat`) |

### Relaxation flags

<ParamField body="--allow-mechanical-names" type="boolean" default="false">
Permit generated fallback names (`ImportedBinding1`, `callbackValue1`, …).
</ParamField>

<ParamField body="--allow-untyped" type="boolean" default="false">
Skip TypeScript typing requirements on exported components.
</ParamField>

<ParamField body="--allow-missing-provenance" type="boolean" default="false">
Skip provenance header requirement.
</ParamField>

<ParamField body="--vendored" type="boolean" default="false">
Relax naming/typing on intentional boundary facades.
</ParamField>

<ParamField body="--allow-organize-incomplete" type="boolean" default="false">
Suppress drain stall checks during in-progress promotion.
</ParamField>

<ParamField body="--check-format" type="boolean" default="false">
Run Prettier `--check` on all files under the target.
</ParamField>

<ParamField body="--json" type="boolean" default="false">
Emit structured issue reports to stdout.
</ParamField>

### Full-restoration coverage issue codes

When scanning a target directory with an existing manifest, these codes indicate incomplete or invalid whole-tree restores:

| Code | Meaning |
|------|---------|
| `full-restoration-checkpoints-not-drained` | Checkpoints exist but chunks not `promoted`; `restored/` empty |
| `full-restoration-organize-incomplete` | Chunks `finalized` but never promoted |
| `full-restoration-public-file-in-hash-dir` | Deliverable still under hash-named directory |
| `full-restoration-npm-boundary-not-resolved` | Third-party npm chunk left as `any`-facade |
| `full-restoration-app-feature-facade` | App feature chunk promoted as boundary/facade |
| `full-restoration-mechanical-app-feature` | App chunk still mechanical-readable |
| `full-restoration-app-feature-not-accepted` | Missing Stage 3 acceptance / `stages.finalized` |
| `full-restoration-oversized-local` | Reachable chunk marked `oversized-local` |
| `full-restoration-app-feature-ts-nocheck` | `@ts-nocheck` in app deliverable |

Mechanical checks catch bundler residue (`jsx-runtime`, React Compiler cache, `toESM` shims), generated fallback names, and missing JSX intrinsics. Stage 3 acceptance review (categories B1–B4 in `acceptance-checklist.md`) is the semantic layer the script cannot judge.

## Completion contract

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

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

While checkpoints sit in `_full/checkpoints/` and `restored/` is empty, the gate fails with `full-restoration-checkpoints-not-drained` — mechanical checkpoint completion is not a reachable done state.

<Steps>

<Step title="Build graph and ledger">
Run `check-entry.ts`, then `build-import-graph.ts` and `build-symbol-ledger.ts`.
</Step>

<Step title="Produce checkpoints">
Run `auto-restore-full.ts --format` or iterate manually via `ledger.ts`.
</Step>

<Step title="Organize and promote">
Run `plan-organize.ts`, review/override, `--apply`, then `promote-organized.ts` until the promote frontier is empty.
</Step>

<Step title="Accept and audit">
Complete Stage 3 acceptance review per `acceptance-checklist.md`, then `quality-gate.ts` over the whole target with `--check-format`.
</Step>

</Steps>

## Related pages

<CardGroup>

<Card title="Full tree restoration" href="/full-tree-restoration">
End-to-end workflow tying these scripts together from entry discovery through completion proof.
</Card>

<Card title="Import graph and boundaries" href="/import-graph-and-boundaries">
Manifest and ledger schemas, terminal chunk kinds, facade lifecycle, and coverage rules.
</Card>

<Card title="Workspace and output" href="/workspace-and-output">
Staging layout, promote bar, semantic domains, and IMPORT_MAP.json contract.
</Card>

<Card title="Quality bar and anti-patterns" href="/quality-bar-and-anti-patterns">
Readable vs deep completion criteria, naming anti-patterns, and gate failure modes.
</Card>

<Card title="Stage 2 scripts reference" href="/stage-2-scripts">
Per-chunk rename and polish scripts invoked inside the ledger loop and auto-restore batch.
</Card>

</CardGroup>

---

## 17. External tools and dependencies

> Bun and Babel package dependencies, subprocess tools (@electron/asar, prettier, @wakaru/cli, webcrack, source-map-explorer), BSD-style script exit codes, and graceful degradation when binaries are absent.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/17-external-tools-and-dependencies.md
- Generated: 2026-06-27T21:22:34.342Z

### Source Files

- `.agents/skills/deobfuscate-javascript/package.json`
- `.agents/skills/deobfuscate-javascript/SKILL.md`
- `.agents/skills/deobfuscate-javascript/scripts/wakaru-normalize.ts`
- `.agents/skills/deobfuscate-javascript/scripts/format.ts`
- `.agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs`
- `.agents/skills/deobfuscate-javascript/scripts/sourcemap-check.ts`

---
title: "External tools and dependencies"
description: "Bun and Babel package dependencies, subprocess tools (@electron/asar, prettier, @wakaru/cli, webcrack, source-map-explorer), BSD-style script exit codes, and graceful degradation when binaries are absent."
---

decode-codex splits toolchain ownership across two agent skills: **codex-app-ref-refresh** runs on **Node.js** and shells out to **`npx`** for `@electron/asar` and Prettier; **deobfuscate-javascript** runs on **Bun** with in-process **Babel** libraries and thin wrappers that probe optional subprocess CLIs (`@wakaru/cli`, Prettier) before falling back to passthrough behavior so the core rename/polish path never hard-depends on network-fetched binaries.

## Runtime stack

| Layer | Skill | Runtime | Installed deps |
| ----- | ----- | ------- | -------------- |
| Refresh | `codex-app-ref-refresh` | Node.js (`node refresh-codex-ref.mjs`) | None — tools fetched on demand via `npx -y` |
| Deobfuscate | `deobfuscate-javascript` | Bun (`bun scripts/<name>.ts`) | Babel packages in skill `package.json` |

<Note>
Run `bun install` once inside `.agents/skills/deobfuscate-javascript/` before invoking deobfuscation scripts. The refresh skill has no `package.json`; it only needs Node.js and network access for `npx`.
</Note>

### Bun and Babel (`deobfuscate-javascript/package.json`)

All mechanical AST work — parse, traverse, generate, type-aware transforms in Stage 1–3 — runs in-process via Babel. These are the only runtime dependencies declared in the skill manifest:

| Package | Role |
| ------- | ---- |
| `@babel/parser` | Parse minified/obfuscated JS into ASTs |
| `@babel/traverse` | Walk and rewrite bindings, imports, JSX-runtime calls |
| `@babel/generator` | Emit readable code between pipeline steps |
| `@babel/types` | AST node builders and guards |

Dev dependencies (`bun-types`, `@types/babel__*`) support TypeScript scripts and `bun test`. **External CLIs are intentionally absent from `package.json`** — they are invoked as subprocesses through wrapper scripts or direct `npx` calls.

## Subprocess tools

External binaries are **not** `bun install` targets. The skill adopts them via `npx`, a global install on `PATH`, or a dedicated wrapper that pins versions and degrades when unavailable.

```text
  codex-app-ref-refresh                    deobfuscate-javascript
  ─────────────────────                    ──────────────────────
  Node.js                                  Bun + Babel (in-process)
       │                                        │
       ├─ npx @electron/asar extract            ├─ scripts/format.ts ──► prettier (PATH → bunx → npx)
       └─ npx prettier --write                  ├─ scripts/wakaru-normalize.ts ──► wakaru / npx @wakaru/cli@1.5.0
                                                ├─ npx webcrack (direct, webpack pre-split)
                                                └─ npx source-map-explorer (direct, sourcemap recovery)
```

### Tool reference

| Tool | Invoked by | When | Version / resolution |
| ---- | ---------- | ---- | -------------------- |
| `@electron/asar` | `refresh-codex-ref.mjs` | Extract `Codex.app/Contents/Resources/app.asar` into `./ref` | `npx -y @electron/asar extract <asar> <refDir>` — unpinned, fetched on demand |
| Prettier | `refresh-codex-ref.mjs`, `scripts/format.ts`, `quality-gate.ts --check-format` | Format extracted JS/CSS; format deliverables; optional gate check | Refresh: `npx -y prettier`. Deobfuscate: `prettier` on `PATH` → `bunx prettier` → `npx prettier` |
| `@wakaru/cli` | `scripts/wakaru-normalize.ts` | Pre-rename mechanical normalization (classes, async/await, optional chaining, destructuring, enums) | Pinned `@wakaru/cli@1.5.0`; global `wakaru` preferred over `npx` |
| `webcrack` | Agent / workflow docs (`npx webcrack`) | Pre-split webpack/browserify bundles ≥ 500 KB or `id:(e,t,n)=>{}` module maps | Direct `npx webcrack <file> -o <out-dir>/` — no wrapper |
| `source-map-explorer` | Agent after `sourcemap-check.ts` | Recover original sources when a usable `.map` exists — higher fidelity than rename | Direct `npx source-map-explorer <input.js>` — no wrapper |

<Warning>
`webcrack` and `source-map-explorer` have no graceful-degradation wrappers. If `npx` cannot fetch or launch them, the agent must fall back manually (feed the original file into `extract.ts`, or hand-decode `sourcesContent`).
</Warning>

### `@electron/asar` and Prettier (refresh skill)

`refresh-codex-ref.mjs` chains three steps: delete `./ref`, extract with `@electron/asar`, then format every `.js`/`.css` under `ref` (skipping `ref/node_modules`). Prettier runs with `--ignore-path /dev/null` so gitignored `ref/` is still formatted — the same trap that affects restore deliverables under gitignored `restored/`.

<ParamField body="CODEX_APP_ASAR" type="string">
Override the default asar path `/Applications/Codex.app/Contents/Resources/app.asar`.
</ParamField>

<ParamField body="--dry-run" type="boolean">
Print resolved workspace, asar, and target paths without deleting or extracting.
</ParamField>

<ParamField body="--skip-format" type="boolean">
Extract only; skip Prettier. Use only when the user explicitly wants raw minified output.
</ParamField>

The refresh script uses a simple **0 / 1** exit model: `0` on success (including dry-run), `1` on any error (missing asar, unsafe `./ref` path, subprocess failure, Prettier verification failure after three passes). It does not use BSD usage code `64`.

### Prettier (`scripts/format.ts`)

`format.ts` is the stable entry point for deliverable formatting. It wraps Prettier with gitignore-aware defaults:

- Directories default to glob `**/*.{ts,tsx,js,jsx,mjs,cjs}`.
- `--ignore-path .prettierignore` — **not** `.gitignore` — so gitignored `restored/` files are actually formatted.
- Runner resolution: `prettier` on `PATH` (offline-safe) → `bunx prettier` → `npx prettier`.

<ParamField body="--check" type="boolean">
Run `prettier --check` instead of `--write`; non-zero exit when formatting would change files.
</ParamField>

<ParamField body="--glob" type="string">
Limit which files under a directory Prettier touches.
</ParamField>

Unlike wakaru, `format.ts` does **not** passthrough on failure — a missing target exits `1`, and Prettier errors propagate the subprocess exit code.

### `@wakaru/cli` (`scripts/wakaru-normalize.ts`)

Wakaru is a Rust transpiler/minifier decompiler. The wrapper pins `WAKARU_VERSION = "1.5.0"` and resolves the runner as:

1. Global `wakaru` on `PATH`
2. `npx --yes @wakaru/cli@1.5.0`

<ParamField body="--level" type="string" default="standard">
`minimal` | `standard` | `aggressive` — aggressiveness of mechanical recovery.
</ParamField>

<ParamField body="--unpack" type="string">
`auto` | `strict` or bare `--unpack` — split a **single** scope-hoisted bundle into modules. Never use on an already-split chunk tree.
</ParamField>

<ParamField body="--dce" type="boolean" default={false}>
Dead-code elimination — opt-in only; can drop side-effecting code.
</ParamField>

<ParamField body="--source-map" type="string" short="m">
Pass a sourcemap for identifier recovery and import dedup.
</ParamField>

Readable tier runs wakaru **default-on** after sourcemap-check and Stage 1 (if obfuscated), before `extract.ts`. Skip entirely when a usable `.map` exists — sourcemap recovery wins. Deep/full mode runs wakaru per chunk body only, never with `--unpack` on a split tree.

### `webcrack` (direct `npx`)

Use when input is one giant webpack-shaped bundle. Output lands under a workspace directory (e.g. `$WS/webcracked/modules/`); each module then follows the single-file restore flow. webcrack is webpack-shaped — Rollup/esbuild/Vite flat ESM usually passes through unchanged; use [multi-export-bundle](/page-multi-export-bundle) or wakaru `--unpack` for those shapes instead.

### `source-map-explorer` (direct `npx`)

`sourcemap-check.ts` is always Step 0. When it reports `✓ sourcemap detected`, stop the rename pipeline and recover originals:

```bash
npx source-map-explorer ref/webview/assets/some-chunk-XXXX.js
```

This preserves original variable names, comments, and file structure that Stage 1 + Stage 2 cannot reconstruct. Running wakaru or Stage 1 **after** committing to sourcemap recovery invalidates byte offsets the map indexes.

## BSD-style exit codes

All `deobfuscate-javascript` scripts share a common exit vocabulary so shell pipelines and orchestrators can branch predictably:

| Code | Meaning | Typical scripts |
| ---- | ------- | ----------------- |
| `0` | Success | All scripts |
| `1` | I/O error — missing input, write failure, gate failure | `extract.ts`, `format.ts`, `quality-gate.ts`, `sourcemap-check.ts` (no map found) |
| `2` | JavaScript parse error | `extract.ts`, `smart-rename.ts`, `wakaru-normalize.ts` (wakaru parse failure) |
| `64` | Usage error — missing arg, unknown flag, bad option value | CLI entrypoints across `scripts/*.ts` |

### Domain-specific extensions

| Code | Meaning | Script |
| ---- | ------- | ------ |
| `1` (informational) | No recoverable sourcemap — proceed with rename pipeline | `sourcemap-check.ts` |
| `3` | Suspicious entry — likely a transitive vendor leaf, not the app root | `check-entry.ts` |
| `75` | Lock contention — another agent holds the file-stage lock | `ledger.ts` (`EX_LOCKED`) |

<Info>
`sourcemap-check.ts` exiting `1` when no map is found is **not** a hard failure — it signals "continue with extract/rename." `check-entry.ts` exiting `3` **is** actionable: switch to the `index.html` script root or a high fan-out `app-main-*` chunk before building the import graph.
</Info>

### Wakaru exit mapping

| Condition | Exit | Output file |
| --------- | ---- | ----------- |
| Binary unavailable (`which` finds neither `wakaru` nor `npx`) | `0`, `skipped: true` | Passthrough copy of input |
| Runner spawn fails (ENOENT, offline `npx` fetch) | `0`, `skipped: true` | Passthrough copy |
| Wakaru runs but parse fails | `2` | Passthrough copy (pipeline may continue) |
| Success | `0` | Wakaru-transformed output |

Stderr carries the skip note: `wakaru unavailable — skipping mechanical normalization, continuing with original`.

## Graceful degradation

The skill distinguishes **hard dependencies** (Bun + Babel — pipeline stops without them) from **optional accelerators** (external CLIs — pipeline continues with reduced fidelity).

```mermaid
sequenceDiagram
  participant Agent
  participant WakaruWrap as wakaru-normalize.ts
  participant Wakaru as @wakaru/cli
  participant Next as extract.ts

  Agent->>WakaruWrap: normalize input → normalized.js
  alt binary on PATH or npx reachable
    WakaruWrap->>Wakaru: spawn with pinned @1.5.0
    Wakaru-->>WakaruWrap: transformed JS
    WakaruWrap-->>Agent: exit 0, skipped=false
  else unavailable or spawn failure
    WakaruWrap->>WakaruWrap: copyFileSync passthrough
    WakaruWrap-->>Agent: exit 0, skipped=true + stderr note
  else wakaru parse error
    WakaruWrap->>WakaruWrap: passthrough copy
    WakaruWrap-->>Agent: exit 2 + stderr
  end
  Agent->>Next: always reads normalized.js
```

| Surface | Missing tool behavior | Pipeline impact |
| ------- | --------------------- | --------------- |
| `wakaru-normalize.ts` | Passthrough copy, exit `0`, stderr note | Rename starts from less-normalized input; more hand-fixes |
| `format.ts` | Tries `PATH` → `bunx` → `npx`; fails if all unreachable | Promotion/gate may report unformatted deliverables |
| `quality-gate.ts --check-format` | Soft-skip — advisory stderr, no `unformatted` issues | Gate passes format dimension when Prettier cannot run |
| `unpack.ts --no-eval` | Exit `0`, input unchanged, `evalRefused: true` | Packed layers remain; agent must unpack manually |
| `refresh-codex-ref.mjs` | Hard fail exit `1` if `npx` or asar missing | `./ref` not created — fix Node/network before deobfuscation |

<Check>
Every wrapper that degrades still **writes the expected output path** (`normalized.js`, formatted deliverable target) so the next pipeline step never blocks on a missing file. Wakaru and unpack follow this contract; refresh does not — it fails fast because unreadable minified `ref/` blocks all downstream work.
</Check>

### Adopting new tools

When extending the skill:

- **JS/Babel library** → add to `package.json`, run `bun install`, import in scripts.
- **External binary** (Rust/Go CLI) → do **not** add to `package.json`; create a thin wrapper modeled on `format.ts` or `wakaru-normalize.ts`: probe `PATH`, fall back to pinned `npx`, degrade gracefully, document under external tools.

## First-time setup

<Steps>
<Step title="Install runtimes">
Install **Node.js** (refresh skill) and **Bun** (deobfuscate skill). macOS with Codex.app at `/Applications/Codex.app` is required for the default asar path.
</Step>

<Step title="Install Babel deps">
```bash
cd .agents/skills/deobfuscate-javascript
bun install
```
</Step>

<Step title="Optional: prefetch external CLIs">
For offline runs, install globals or ensure `npx` cache is warm:
```bash
npm install -g prettier @wakaru/cli@1.5.0
# webcrack and source-map-explorer: npx on demand
```
</Step>

<Step title="Verify toolchain">
```bash
node .agents/skills/codex-app-ref-refresh/scripts/refresh-codex-ref.mjs --dry-run
bun .agents/skills/deobfuscate-javascript/scripts/wakaru-normalize.ts --help 2>&1 | head -1
bun .agents/skills/deobfuscate-javascript/scripts/format.ts --help 2>&1 | head -1
```
</Step>
</Steps>

## Troubleshooting

<AccordionGroup>
<Accordion title="wakaru skipped but I expected normalization">
Check stderr for `wakaru unavailable` or `could not be launched`. Install `@wakaru/cli@1.5.0` globally or ensure `npx` has network access. The pipeline still produces `normalized.js` as a copy of the input — compare file hashes to confirm skip vs transform.
</Accordion>

<Accordion title="Prettier says all files use Prettier code style but nothing changed">
Prettier 3 honors `.gitignore` by default. On gitignored `ref/` or `restored/`, use `scripts/format.ts` (pins `--ignore-path .prettierignore`) or pass `--ignore-path /dev/null` when invoking Prettier directly — the refresh script already does this.
</Accordion>

<Accordion title="check-entry exits 3">
The discovered or supplied entry is a transitive vendor leaf (small local fan-out, imported by many siblings). Re-run discovery from `index.html` or pick a high fan-out `app-main-*` / `app-shell-*` chunk before `build-import-graph.ts`.
</Accordion>

<Accordion title="quality-gate --check-format silently passes unformatted files">
When Prettier is unreachable, the gate soft-skips with `[quality-gate] prettier --check could not run …; skipping format check`. Install Prettier and re-run with `--check-format`, or run `bun scripts/format.ts <target>` first.
</Accordion>

<Accordion title="webcrack left the bundle unsplit">
Input is likely Rollup/esbuild/Vite ESM, not webpack module-id shape. Feed the original file into `extract.ts` directly, or try wakaru `--unpack` on a **single** bundle only.
</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Prerequisites and first-time `bun install` inside the deobfuscate skill directory.
</Card>
<Card title="Refresh script reference" href="/refresh-script-reference">
`refresh-codex-ref.mjs` flags, `CODEX_APP_ASAR`, Prettier verification passes, and completion log lines.
</Card>
<Card title="Stage 2 scripts" href="/stage-2-scripts">
`sourcemap-check`, `wakaru-normalize`, `format`, and `polish` CLI signatures and ordering.
</Card>
<Card title="Pipeline caveats" href="/pipeline-caveats">
Prettier gitignore traps, wakaru caveats, sourcemap precedence, and recovery steps.
</Card>
</CardGroup>

---

## 18. Quality bar and anti-patterns

> Readable and deep-tier completion criteria, naming anti-patterns (program-scope-only rename, mechanical fallback names, checkpoint promotion), Stage 3 acceptance categories E1–E4, and quality-gate failure modes.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/18-quality-bar-and-anti-patterns.md
- Generated: 2026-06-27T21:22:56.934Z

### Source Files

- `.agents/skills/deobfuscate-javascript/SKILL.md`
- `.agents/skills/deobfuscate-javascript/scripts/acceptance-checklist.md`
- `.agents/skills/deobfuscate-javascript/stages/stage-3-finalize.md`
- `.agents/skills/deobfuscate-javascript/scripts/quality-gate.ts`
- `.agents/skills/deobfuscate-javascript/reference/naming-heuristics.md`

---
title: "Quality bar and anti-patterns"
description: "Readable and deep-tier completion criteria, naming anti-patterns (program-scope-only rename, mechanical fallback names, checkpoint promotion), Stage 3 acceptance categories E1–E4, and quality-gate failure modes."
---

`scripts/quality-gate.ts` enforces threshold-based checks on every promoted file under `restored/`, and `scripts/acceptance-checklist.md` defines the four-category semantic bar (E1–E4) that Stage 3 acceptance review applies after the script gate passes. Together they define when a deobfuscation restore is actually done — not merely formatted or syntactically valid.

## Two completion tiers

Restoration depth is independent of scope (single file vs whole import tree). Both tiers require the **staging → organize → promote** discipline: batch/script output never lands directly in `restored/`.

| Tier | Trigger | Hard bar | Optional / deep-only |
|------|---------|----------|----------------------|
| **Readable** (default for lone snippets; opt-out for whole trees) | User asks "quick" / "readable" / "快速", or a pasted snippet with no `index.html` tree | Meaningful identifiers throughout; no mechanical fallback names; semantic kebab filenames; provenance header; prettier-clean structure | TypeScript types, `forwardRef`, npm-import resolution tail, Stage 3 acceptance LOOP |
| **Deep** (default for whole-tree restores) | User asks "deep" / "full" / "typed" / "restore the code", or any `index.html` + asset tree without an explicit quick pass | Everything in readable tier **plus** typed `.tsx` surfaces, import-graph drain to `stages.promoted`, Stage 3 semantic rewrite, E1–E4 acceptance review, target-level `quality-gate.ts` over the whole `restored/` tree | — |

<Note>
Deep is a completion bar, not an upsell. A whole-tree restore is **done** only when every reachable project-local chunk reaches `stages.promoted` (deep: also `stages.finalized`) and `quality-gate.ts <target-dir>` exits 0.
</Note>

### Readable-tier deliverable shape

A valid readable deliverable:

- Lives in `restored/<semantic-domain>/` with kebab-case filenames (`button.tsx` exports `Button`)
- Starts with `// Restored from ref/webview/assets/<chunk>.js`
- Uses meaningful names — no `buttonValue3`, `contextParam14`, or other generated fallbacks
- May ship untyped (`function Button(props)` with no `Props` interface is acceptable)
- Passes `quality-gate.ts` with `--allow-untyped` when typing checks are intentionally relaxed

### Deep-tier deliverable shape

A valid deep deliverable adds:

- `Props` interfaces (or type aliases) on exported React components
- `forwardRef` + `displayName` on DOM-wrapping components
- `as const` lookup tables with `keyof typeof` unions
- Bare-specifier npm imports (`import clsx from "clsx"`)
- Semantic local imports between finalized siblings
- Stage 3 acceptance review pass on every public file (E1–E4)
- `manifest.stages.finalized = true` on accepted app-feature chunks

## Anti-patterns to refuse

These produce output that looks acceptable on a skim but fails on a real read. Refuse them before declaring done.

### Both tiers — naming and staging

<Warning>
The anti-patterns below are hard failures in **every** tier. They concern readability and staging discipline, not TypeScript compilation.
</Warning>

#### Program-scope-only rename

**Symptom:** Top-level exports have meaningful names (`AppShellTabContent`, `RightPanelTabs`) but function bodies remain `let k = useIntl(), [A, M] = useState(false), N = !h`.

**Cause:** Renaming stopped at Pass 1 (`--scope-kind Program`). Function-body bindings live in `FunctionDeclaration` / `ArrowFunctionExpression` scopes that the Program filter drops.

**Cure:** Continue through Pass 2 (function-body `let`/`const`/`var`) and Pass 3 (params). Run `smart-rename.ts` first for mechanical cases, then hand-name domain nouns, lookup tables, and JSX intermediates. See [Stage 2 scripts](/stage-2-scripts) (Step 2.5 — don't stop at program scope).

```text
Pass 1 (Program)     → exports, registries, top-level helpers
Pass 2 (bodies)      → hook returns, JSX intermediates, destructured props
Pass 3 (params)      → props, event, item/index — mostly smart-rename.ts
```

#### Mechanical fallback names

**Symptom:** Identifiers like `ImportedBinding1`, `callbackValue1`, `localValue1`, `buttonValue3`, `buttonParam1`, `contextParam14`, `DistO`, `restoredHelper1`, or `elementNode1`.

**Cause:** `auto-restore-full.ts` or the renamer exhausted evidence and emitted placeholder names that look grammatical but carry no domain meaning.

**Cure:** Replace with semantic names from producer/consumer usage, JSX shape, call graph, and export context. Never ship new placeholders. `quality-gate.ts` matches these via `MECHANICAL_NAME_RE` and fails with `mechanical-names`.

`smart-rename.ts` handles the ~80% of cases that are mechanically derivable (React `props`, event handlers, `.map` iteratees, hook returns). What remains is judgment work — see the heuristics spec in `reference/naming-heuristics.md`.

#### Checkpoint promotion

Two related failure modes violate the staging contract:

**Mechanical checkpoint in `restored/`**

- **Symptom:** `auto-restore-full.ts` output, hash-basename files (`button-bq66r8jD.tsx`), or `--write-target-checkpoints` files sitting directly in `restored/`
- **Cause:** Skipping organize → promote; copying raw script output into the deliverable root
- **Cure:** Keep all batch output in `restored/.deobfuscate-javascript/_full/checkpoints/`. Promote only after the promotion bar is met

**Checkpoints built, `restored/` empty**

- **Symptom:** `_full/checkpoints/` is full but `restored/` has no promoted semantic tree
- **Cause:** Batch executor ran; organize → promote loop was skipped
- **Cure:** `plan-organize.ts` → review/`--apply` → `promote-organized.ts` until `ledger.ts frontier --stage promote` is empty

```mermaid
stateDiagram-v2
    [*] --> Checkpoint: auto-restore-full / polish
    Checkpoint --> Organized: plan-organize.ts
    Organized --> Promoted: promote-organized.ts + quality-gate
    Promoted --> Finalized: Stage 3 rewrite + E1–E4 review
    Finalized --> [*]: quality-gate target-dir exit 0
    Checkpoint --> AntiPattern: copy to restored/ directly
    AntiPattern --> [*]: FAIL
```

### Deep tier only

These are typing, structure, and coverage requirements. A well-named untyped file is a valid readable deliverable; they apply only when deep/typed/production output was requested.

| Anti-pattern | Symptom | Cure |
|--------------|---------|------|
| Checkpoint shipped as deep completion | `$WS/polished.tsx` or `_full/checkpoints/<basename>.tsx` copied to public target without semantic rewrite | Host-agent rewrite → D0 gate → E1–E4 review |
| Multi-export bundle left flat | >1000 lines, registry-object export, single `<bundle>.tsx` | Split into a semantic directory (deep only; see `/restoration-pipeline`) |
| Deep request downgraded | `build-import-graph.ts --max-lines N` skips reachable chunks | Default `--max-lines 0`; report partial restore explicitly |
| App chunk faced instead of restored | Feature chunk left as `boundaries/*.ts` facade | Recurse import graph; drain `ledger.ts frontier` |
| Completion inferred from narrow audit | `IMPORT_MAP.status === "done"` while app chunks remain mechanical | Run `quality-gate.ts <target-dir>` over whole tree |
| Script gate passed, no acceptance review | `promote-final.ts` exit 0 but no end-to-end read | Host-agent E1–E4 self-review (always runnable) |

## Stage 3 acceptance categories (E1–E4)

After `quality-gate.ts` passes, deep mode requires an end-to-end read of every delivered file. The host agent applies `scripts/acceptance-checklist.md` directly — no sub-agent authorization required.

<Info>
**E1 (Naming)** is the shared bar in both tiers. **E2–E4** are deep-mode criteria. The checklist file uses B1–B4 labels internally; they map 1:1 to E1–E4 in `stages/stage-3-finalize.md`.
</Info>

### E1 — Naming

Failures report as `NEEDS_FIX:naming`.

- No identifiers matching `*Param\d+`, `*Value\d+`, `paramN`, `valueN`, `callbackValueN`, `localValueN`, `ImportedBindingN`, `restoredHelperN`, `elementNodeN`, `hookValueN`, `RestoredComponentN`, or `DistA` / `DistO` / `DistT`
- Lookup tables named for their role (`buttonColorClassNames`, not `buttonValue3` or `colorMap`)
- Parameters with call-site-appropriate names (`props`, `event`, `disabled`, `className`)
- React components in `PascalCase` ending in a noun phrase; hooks start with `use`
- Sibling imports use semantic names from the producer (not `import { t as ot }`)
- Public filenames and directories are semantic kebab-case without hash suffixes

### E2 — Readability (idiomatic TSX)

Failures report as `NEEDS_FIX:readability`.

- Every exported React component has a `Props` interface or `type Props =`
- DOM-wrapping components use `forwardRef<…>(…)` with `displayName`
- Pure SVG icons use `SVGProps<SVGSVGElement>` / `IconProps`, named `*Icon`, default-exported when single
- Lookup tables use `as const`; key unions exposed via `keyof typeof`
- No bundler residue: no `jsxRuntime.jsx(…)`, `(0, fn)(…)`, React Compiler `cache[N]`, `__toESM` / `loadJsxRuntime`, trailing `//# sourceMappingURL=`
- Provenance header present

### E3 — Formatting

Failures report as `NEEDS_FIX:formatting`.

- `bunx prettier --check <file>` exits 0
- User-defined JSX component tags are capitalized
- Long ternaries and nested call pyramids collapsed or extracted

### E4 — Other (structure, imports, exports)

Failures report as `NEEDS_FIX:other`.

- Vendored npm deps use bare specifiers
- Finalized local sibling imports use semantic public paths
- `default export` where the file has a single primary component
- Re-export aliases collapsed (`export const Foo = expr` preferred)
- Dead helpers and unused imports removed

### Acceptance LOOP

<Steps>
<Step title="Pre-filter">

Run `bun scripts/quality-gate.ts <target-dir>`. Exit 0 means review-ready. Non-zero → fix script-detectable failures via the D0 remediation list before spending the semantic read.

</Step>
<Step title="Review">

Read each file end-to-end against E1–E4. For large trees, `prepare-stage-e-review.ts` writes batch packets under the hidden workspace.

</Step>
<Step title="Repair">

Apply the cheapest fix per finding shape: `semantic-finalize.ts --recipe icon|button` for recipe gaps; hand-edit for missing types; `ledger.ts mark-done` + `apply.ts` for mechanical fallback names; `prettier --write` for formatting.

</Step>
<Step title="Re-read changed files only">

Do not re-review files that already passed. Repeat until every file returns `VERDICT: PASS`.

</Step>
</Steps>

There is no "ship with TODO" success path in deep mode. A TODO header may live in a checkpoint, but public completion requires every file to pass E1–E4.

## Quality-gate failure modes

`quality-gate.ts` runs per-file analysis plus full-restoration coverage checks when `_full/manifest.json` exists. Default thresholds:

<ParamField body="maxCrypticParams" type="number" default="20">
Cryptic parameter count before `cryptic-params` failure.
</ParamField>

<ParamField body="maxCrypticBindings" type="number" default="80">
Total cryptic binding count before `cryptic-bindings` failure.
</ParamField>

<ParamField body="maxShortRefCount" type="number" default="50">
Per-identifier short-name reference density before `short-identifier-density` failure.
</ParamField>

<ParamField body="maxFlatLines" type="number" default="1000">
Line count triggering `split-required` on multi-export bundles.
</ParamField>

### Per-file issue codes

| Code | Meaning | Typical fix |
|------|---------|-------------|
| `cryptic-params` | >20 cryptic params | Pass 3 rename + `smart-rename.ts` |
| `cryptic-bindings` | >80 cryptic bindings | Pass 2/3 rename |
| `short-identifier-density` | One-letter refs exceed threshold | Continue function-body rename |
| `mechanical-names` | Generated fallback names remain | Semantic rename from usage context |
| `mechanical-import-bindings` | Facade import aliases like `DistO` | Import semantic facade aliases |
| `bundle-residue` | `jsx-runtime`, `toESModule`, `sourceMappingURL`, React Compiler cache | Re-run `polish.ts` or hand-strip |
| `invalid-jsx-tags` | Lowercase tags that are not HTML intrinsics | Recover PascalCase component name |
| `unbound-jsx-tags` | JSX component referenced without local binding | Fix import or declare component |
| `unbound-identifiers` | Identifier referenced without binding | Fix import or scope |
| `untyped-component-props` | Exported component lacks `Props` type (deep only) | Add interface or run button recipe |
| `untyped-public-function-params` | Exported function params lack types (deep only) | Add parameter annotations |
| `public-cryptic-names` | Exported symbol still cryptic | Rename public exports |
| `non-kebab-filename` | Public file not kebab-case | Rename to `button.tsx` etc. |
| `split-required` | Flat file should be a directory | Split into semantic directory (`/restoration-pipeline`) |
| `unfinalized-checkpoint-imports` | Public file imports `.deobfuscate-javascript/` checkpoint | Promote dependency first |
| `missing-provenance-header` | No `// Restored from ref/...` line | Add D1 provenance |
| `unformatted` | Prettier check failed | `scripts/format.ts` |
| `parse-error` | File does not parse | Fix syntax upstream |

### Full-restoration coverage codes

These fire from `analyzeFullRestorationCoverage()` even when individual files pass — including when `IMPORT_MAP.json` is absent (the stall state).

| Code | Meaning |
|------|---------|
| `full-restoration-checkpoints-not-drained` | `_full/checkpoints/` has files not yet promoted to `restored/` |
| `full-restoration-organize-incomplete` | Chunks reached `stages.finalized` but never `stages.promoted` |
| `full-restoration-public-file-in-hash-dir` | Promoted file lives under `button-bq66r8jD/` instead of semantic domain folder |
| `missing-import-map` | Manifest exists but `IMPORT_MAP.json` is missing |
| `full-restoration-missing-public-output` | Reachable local chunk has no import-map entry |
| `full-restoration-oversized-local` | Deep restore left a chunk as `oversized-local` |
| `full-restoration-app-feature-boundary` | App/feature chunk marked boundary/facade instead of restored |
| `full-restoration-mechanical-app-feature` | App chunk status is `mechanical-readable-restored` |
| `full-restoration-app-feature-not-accepted` | App chunk lacks `stages.finalized` or Stage 3 acceptance record |
| `full-restoration-app-feature-ts-nocheck` | App chunk finished with `@ts-nocheck` |
| `full-restoration-app-feature-facade` | App chunk finished as typed `export declare const` facade |
| `full-restoration-app-feature-empty-placeholder` | App chunk is `export {}` placeholder |
| `full-restoration-npm-boundary-not-resolved` | Known npm boundary still an `any`-facade |

<RequestExample>

```bash
# Deep pre-filter (D0 gate) — naming signal only on a Stage 2 checkpoint
bun scripts/quality-gate.ts "$WS/polished.tsx" --allow-flat --allow-mechanical-names

# Final deliverable gate — provenance required, typing enforced
bun scripts/quality-gate.ts restored/ --check-format

# In-progress whole-tree run — suppress stall checks
bun scripts/quality-gate.ts restored/ --allow-organize-incomplete
```

</RequestExample>

<ResponseExample>

```text
quality-gate: FAIL restored/composer/thread-panel.tsx
  [mechanical-names] Generated fallback names remain: contextParam14, DistO
  [untyped-component-props] Exported component props need explicit TS types: ThreadPanel

quality-gate: FAIL restored/IMPORT_MAP.json
  [full-restoration-checkpoints-not-drained] Mechanical checkpoints exist under _full/checkpoints/ but were not promoted into the public tree
```

</ResponseExample>

### Relaxation flags

Use these only for intermediate checkpoints, never public deliverables:

- `--allow-flat` — suppress `split-required` (intentional single-file artifact)
- `--allow-mechanical-names` — suppress `mechanical-names` (intermediate checkpoint only)
- `--allow-missing-provenance` — suppress provenance header check (pre-D1 candidate)
- `--allow-untyped` — suppress TypeScript typing checks (readable tier)
- `--vendored` — relax semantic naming on faithful vendored modules and boundary facades
- `--allow-organize-incomplete` — suppress checkpoint-drain stall checks during in-progress runs

## Verification checklist

Before reporting completion:

1. **Readable tier:** `quality-gate.ts` passes on promoted files; optional E1 naming self-review
2. **Deep tier:** D0 gate → semantic rewrite → D7 gate → E1–E4 LOOP → target-level `quality-gate.ts <target-dir>` exit 0
3. **Whole tree:** `ledger.ts frontier --stage promote` is empty; every reachable local chunk is `stages.promoted` (deep: + `stages.finalized`)

<Check>
The canonical exemplar for deep-mode Button output is embedded in `scripts/acceptance-checklist.md` — compare candidate files against its shape: typed `Props`, `as const` lookup tables, `forwardRef` + `displayName`, provenance header, bare npm imports.
</Check>

## Related pages

<CardGroup>
<Card title="Restoration pipeline" href="/restoration-pipeline">
Stage 1–3 flow, readable vs deep depth, and the restoration contract that defines done.
</Card>
<Card title="Workspace and output" href="/workspace-and-output">
Staging under `.deobfuscate-javascript/`, promote bar, kebab-case filenames, and `IMPORT_MAP.json`.
</Card>
<Card title="Full tree restoration" href="/full-tree-restoration">
Organize → promote loop, checkpoint drain, and target-level quality-gate coverage.
</Card>
<Card title="Pipeline caveats" href="/pipeline-caveats">
Ordering rules, sourcemap precedence, polish tier differences, and recovery steps.
</Card>
<Card title="Orchestration scripts" href="/orchestration-scripts">
`quality-gate.ts`, `promote-organized.ts`, and `plan-organize.ts` CLI reference.
</Card>
</CardGroup>

---

## 19. Pipeline caveats

> Ordering rules, eval safety in unpack, sourcemap precedence, polish tier differences, Prettier gitignore traps, wakaru caveats, vendor-leaf entry misidentification, and other documented failure modes with recovery steps.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/19-pipeline-caveats.md
- Generated: 2026-06-27T21:22:31.619Z

### Source Files

- `.agents/skills/deobfuscate-javascript/reference/caveats.md`
- `.agents/skills/deobfuscate-javascript/reference/examples.md`
- `.agents/skills/deobfuscate-javascript/scripts/check-entry.ts`
- `.agents/skills/deobfuscate-javascript/scripts/control-flow-report.ts`
- `.agents/skills/deobfuscate-javascript/workflows/webpack-bundle.md`
- `.agents/skills/deobfuscate-javascript/workflows/huge-single-file.md`

---
title: "Pipeline caveats"
description: "Ordering rules, eval safety in unpack, sourcemap precedence, polish tier differences, Prettier gitignore traps, wakaru caveats, vendor-leaf entry misidentification, and other documented failure modes with recovery steps."
---

The `deobfuscate-javascript` skill runs a fixed multi-stage pipeline—`sourcemap-check`, optional Stage 1 deobfuscation, wakaru normalization, extract/apply rename, polish, and format—where each step assumes the byte shape and symbol offsets produced by the previous one. Violating ordering, skipping precedence checks, or misidentifying a whole-tree entry point produces silent no-ops, stale `renames.json` ids, or a falsely “complete” restore that covers only a vendor leaf.

## Stage 1 ordering

Each Stage 1 pass has an input shape the prior pass must have produced. Running steps out of order silently no-ops or mangles the AST.

| Step | Must run after | Why |
|------|----------------|-----|
| `unpack` | — (first on packed input) | Packed input is one giant `CallExpression`; AST passes have nothing to chew on until unpacked. Layered packers re-detect after each pass. |
| `string-array` | `unpack` | Array literals are already-readable; decoding the giant array first wastes work. Inlining first lets `decode-strings` walk only used literals. |
| `decode-strings` | `string-array` | Depends on resolved array references. |
| `simplify` | `string-array` + `decode-strings` | Constant folding needs resolved string references. Running `simplify` first splits rotation IIFEs into statements `string-array` will not match. |
| `control-flow-report` | `simplify` | Opaque predicates fed to `simplify` get folded out; what remains is the real CFG worth reporting. |
| Stage 2 (`extract`) | Stage 1 complete | `extract.ts` byte offsets are stable within a source but invalid across Stage 1 rewrites. Always re-extract after Stage 1. |

<Warning>
Stage 1 invalidates sourcemaps. If a usable `.map` exists, sourcemap recovery is strictly better than Stage 1 + Stage 2. Run `sourcemap-check.ts` before committing to deobfuscation.
</Warning>

The `deobfuscate.ts` orchestrator runs these steps in order and continues on individual step failure by design—read the `--report` JSON for errors rather than assuming a clean pass.

## Sourcemap precedence

`sourcemap-check.ts` is the first gate on every restore path. When a recoverable map exists, abandon the rename pipeline.

<Steps>
<Step title="Run sourcemap-check">

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

</Step>
<Step title="Interpret the recommendation">

| Signal | Action |
|--------|--------|
| Adjacent `.map` or inline data-URL with parseable sources | Recover originals via `source-map-explorer` or `sourcesContent`—lossless, preserves names and comments. |
| `sourceMappingURL` points to missing file | Fetch the `.map` manually, or proceed with extract/apply knowing fidelity is lower. |
| No comment and no adjacent `.map` | Proceed with rename pipeline. |

</Step>
<Step title="Avoid downstream rewrites">

Do not run Stage 1, wakaru, or extract on code you intend to recover from a map—the rewrites shift the bytes the map indexes.

</Step>
</Steps>

`extract.ts` emits a warning when it sees a `sourceMappingURL` comment but step 0a was skipped. Treat that as a hard stop and re-run `sourcemap-check.ts`.

<Note>
Source maps are non-deterministic across builds. A `.map` from a different commit may not match runtime behavior—sanity-check recovered sources against the bundle you have.
</Note>

## Eval safety in unpack

`unpack.ts` unwraps Dean Edwards Packer, AAEncode, and URLEncode layers. Packer arg parsing and AAEncode decoding use `new Function(...)`, which executes the input (sandboxed, but still eval).

<ParamField body="--no-eval" type="boolean">
Refuses all eval paths. Exits 0 with input unchanged and `evalRefused: true` in the result. URLEncode (`decodeURIComponent`) still runs—no eval needed.
</ParamField>

Stderr warns before each eval. For untrusted input (malware samples, scraped blobs), probe with `--no-eval` first to confirm a wrapper exists without executing it:

```bash
bun scripts/unpack.ts "$WS/original.js" --no-eval
bun scripts/deobfuscate.ts "$WS/original.js" --no-eval --report "$WS/report.json"
```

Pass `--no-eval` through `deobfuscate.ts` when running the full Stage 1 orchestrator on untrusted sources.

## Wakaru caveats

`wakaru-normalize.ts` wraps `@wakaru/cli@1.5.0` as a pre-rename pass (Stage 2 Step 0b.5). It is default-on in the readable tier.

| Caveat | Detail | Recovery |
|--------|--------|----------|
| Byte offsets shift | wakaru rewrites the AST; `name@offset` ids captured before it are stale. | Extract and rename from `$WS/normalized.js`, never `original.js`. Order: `sourcemap-check → detect → (Stage 1) → wakaru → extract`. |
| Sourcemap first | Running wakaru rewrites bytes a `.map` indexes. | Skip wakaru when a usable map exists. |
| Not a deobfuscator | Recovers transpiler/minifier output, not Obfuscator.IO / Packer / control-flow flattening. | Run Stage 1 first on obfuscated input. |
| Not a semantic renamer | `smart_rename` is the same deterministic heuristic as `smart-rename.ts`. | Skill rename remains the hard bar. |
| `--unpack` forks restore root | Re-derives module boundaries/filenames matching nothing in `manifest.json` / `ledger.json` / `CHUNK_NAME_REGISTRY`. | Use `--unpack` only on a single scope-hoisted bundle. On an already-split chunk tree (e.g. `ref/webview/assets`), normalize chunk bodies only; rebuild the graph from real chunk files. |
| Fidelity at `aggressive` / `--dce` | Default is `--level standard`. `--dce` can drop side-effecting code. | Use `--level minimal` for fidelity-critical code; `aggressive` only with behavioral sanity check. |
| Availability | When binary is unreachable, wrapper passes input through unchanged (`skipped`, exit 0). Genuine parse errors exit 2 with passthrough output. | Offline/CI runs degrade gracefully; check stderr for `skipped`. |

<Warning>
Do not let wakaru's `un_esm` substitute for `resolve-npm-imports.ts` in full-restoration. The import graph is built by `build-import-graph.ts` from real chunk files.
</Warning>

## Polish tier differences

The readable tier runs `polish.ts --fast` (reading-aid subset). Deep mode drops `--fast` to run the import-resolution tail.

| Pass | Readable (`--fast`) | Deep (no `--fast`) |
|------|---------------------|---------------------|
| `strip-react-compiler` | ✓ | ✓ |
| `simplify` | ✓ | ✓ |
| `jsx-runtime` | ✓ | ✓ |
| `inline-defaults` | ✓ | ✓ |
| `normalize-exports` | ✓ | ✓ |
| `react-shim-elim` | skipped | ✓ |
| `resolve-npm-imports` | skipped | ✓ |
| `npm-cjs-shim-elim` | skipped | ✓ |
| `dead-shim-elim` | skipped | ✓ |

Import-resolution passes make output resolve against `node_modules` (compilability); they do not improve readability. `polish.ts` is idempotent—safe to re-run with different `--prefer` / `--skip` / `--stop-after`.

### Polish false positives and flags

| Transform | Risk | Mitigation |
|-----------|------|------------|
| `strip-react-compiler` | Any `let X = expr.c(N)` with numeric literal arg is treated as React Compiler cache. | `--skip strip-react-compiler` |
| `simplify` `(0, fn)(args)` → `fn(args)` | `this`-affecting rewrite; Rollup uses it to strip `this`. | `--skip simplify` or wakaru `--level minimal` |
| `jsx-runtime` | Rewrites any `.jsx` / `.jsxs` / `.jsxDEV` / `.Fragment` by name. | Audit after polish if you have user-defined `.jsx` methods |
| `inline-defaults` | Treats `??` like destructure default (`undefined` only vs `null`+`undefined`). | `--skip inline-defaults` for `null`-tolerant props |
| `--prefer` default | `polish.ts` defaults to `--prefer local`; standalone `normalize-exports.ts` defaults to `--prefer exported`. | Pass explicit `--prefer` when export alias matters |
| `--source` / `--description` | Provenance header is opt-in; re-running duplicates headers. | Run polish once per chunk with `--source ref/.../chunk.js` |
| Dead stubs | `var jsxRuntime = requireJsxRuntime();` may survive as unreferenced. | Delete manually after confirming no side effects |

## Prettier gitignore trap

Prettier 3 defaults `--ignore-path` to `[".gitignore", ".prettierignore"]`. Restore deliverables often live under gitignored trees (`restored/`, `ref/` in this repo). A plain `prettier --write restored/` silently skips every file and reports success.

**Symptom:** 400-character lines and un-parenthesized multi-line JSX that prettier insists is already clean. Copy the file outside the gitignored tree and `prettier --check` flags it.

`scripts/format.ts` pins `--ignore-path .prettierignore` so `.gitignore` is bypassed. When invoking prettier directly:

```bash
prettier --write --ignore-path .prettierignore restored/path/to/file.tsx
```

`promote-organized.ts` formats each deliverable via `format.ts` as it lands. `quality-gate.ts --check-format` uses the same bypass and soft-skips when prettier is unreachable.

## Vendor-leaf entry misidentification

`check-entry.ts` prevents pointing `build-import-graph.ts` at a transitive vendor-leaf chunk (e.g. `main-BDm-p1LA.js` imported by dozens of siblings) and declaring the app restored.

A real app entry has large local fan-out and is imported by ~nobody. A vendor leaf is the inverse.

| Signal | Threshold | Meaning |
|--------|-----------|---------|
| `isRoot` | Referenced by `index.html` `<script>` or `modulepreload` | Likely real entry |
| `inDegree` | ≥ 5 siblings import this chunk | High in-degree suggests dependency, not entry |
| `localOutDegree` | ≤ 8 local sibling imports | Low out-degree suggests leaf |
| `looksVendored` | `CHUNK_NAME_REGISTRY` hit, basename pattern, or content fingerprint | Vendor package chunk |
| `suspicious` | NOT root AND in-degree ≥ 5 AND out-degree ≤ 8 | Transitive dependency, not app entry |

Exit codes: `0` ok · `3` suspicious · `1` I/O or none discovered · `64` usage.

<Steps>
<Step title="Discover the real entry">

```bash
bun scripts/check-entry.ts --discover --root ref/webview/assets [--index ref/webview/index.html]
```

Prefers non-suspicious `<script>` roots over `modulepreload` (preload roots are dependencies, not entries).

</Step>
<Step title="Verify a manual candidate">

```bash
bun scripts/check-entry.ts ref/webview/assets/app-main-XXXX.js --root ref/webview/assets
```

High local out-degree (e.g. 305) keeps a subtree entry like `app-main-*` from flagging even when not in `index.html`.

</Step>
<Step title="Recover from misidentification">

Re-run `build-import-graph.ts` from the correct `index.html` script root or highest-fan-out non-vendored chunk. Inspect `manifest.json` closure size—vendor-leaf restores cover only a tiny subgraph.

</Step>
</Steps>

## Stage 2 rename caveats

| Issue | Cause | Recovery |
|-------|-------|------------|
| Program-scope-only rename | Pass 1 ran with `--scope-kind Program`; function bodies untouched | Re-extract from Pass-1 output with `--only-cryptic --min-refs 2` (no scope filter); apply Pass 2 |
| Apply renames 0 symbols | `renames.json` ids stale after Stage 1 or wakaru | Re-run `extract.ts`; rebuild `renames.json` from fresh `name@offset` ids |
| `_foo` / `__foo` chains | Target names collide within the same scope (shadowing) | Pick more specific names or accept prefixes |
| Stale names in strings | `scope.rename` does not update `eval`, `Function(...)`, or `window['a']` | Fix manually |
| 50 MB `symbols.json` | Unfiltered extract on huge bundle | `--only-cryptic --min-refs 3 --top 200 --max-same-scope 5 --context-size 300` |
| Properties not renamed | `obj.foo` and class methods are properties, not bindings | Expected—callers may depend on names |

<Note>
Do not re-rename an already-renamed file with the same `renames.json`—ids shift after generator reformatting. Pass 2 of the multi-pass workflow builds a new `renames.json` from Pass-1 output ids.
</Note>

## Decoder indirection and control-flow flattening

**String-array 0 replacements:** stderr reports `decoderIndirection: true` when arrays are accessed through a wrapper function. Run `simplify.ts` first (inlines small constant functions), then re-run `string-array`.

**Control-flow flattening:** `control-flow-report.ts` detects `while-switch` flatteners, split-string dispatches, and opaque predicates. It reports `rewriteHint` per item but does not mutate source. Automatic CFG reconstruction is unreliable—trace the dispatch graph manually (see Example 6 in the skill reference) and re-run `simplify.ts` on the rewritten code.

## Troubleshooting quick reference

<AccordionGroup>
<Accordion title="unpack says no packer detected but eval wrapper is visible">

The detector requires exact `(p, a, c, k, e, d|r)` parameter signature. Custom wrappers or renamed params won't match. Rename params to the standard signature, or paste the inner `function(p,a,c,k,e,d){...}('...', N, ...)` body into a JS REPL and execute manually.

</Accordion>
<Accordion title="deobfuscate orchestrator says step X errored but kept going">

By design—one failing AST pass does not abort the rest. Read `--report` JSON, fix input (often syntax error from prior mangling), or `--skip` that step.

</Accordion>
<Accordion title="Output still has jsx-runtime calls or backtick literals after rename">

Skipped polish. Run `bun scripts/polish.ts <renamed.js> --out <polished.js>` (add `--fast` for readable tier).

</Accordion>
<Accordion title="Output still has cache[N] or react.c(N) scaffolding">

Skipped `strip-react-compiler`. Re-run polish without `--skip strip-react-compiler`.

</Accordion>
<Accordion title="webcrack left file unsplit">

webcrack handles webpack-shaped bundles best. Rollup/esbuild/Vite output is flat ESM—feed original into extract/apply, or use wakaru `--unpack` on a single scope-hoisted bundle only (never on an already-split tree).

</Accordion>
<Accordion title="Local import path renamed and imports broke">

Do not rewrite local import paths—even hash suffixes are real filenames on disk. Only resolve to bare npm specifiers when certain the binding is a vendored copy of that package.

</Accordion>
<Accordion title="Two Restored-from headers">

Ran `polish.ts --source` twice. Polish always prepends—delete duplicate or run polish only once per chunk.

</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Restoration pipeline" href="/restoration-pipeline">
Stage 1, Stage 2, and Stage 3 contracts; readable vs deep depth; whole-tree vs single-file scope.
</Card>
<Card title="Handle obfuscated input" href="/obfuscated-input">
Stage 1 detect/unpack/string-array/decode-strings/simplify ordering and orchestrator flags.
</Card>
<Card title="Stage 2 scripts reference" href="/stage-2-scripts">
Polish flags, wakaru-normalize, extract filters, and rename pass controls.
</Card>
<Card title="Full tree restoration" href="/full-tree-restoration">
Entry discovery, import-graph orchestration, and quality-gate over the whole target.
</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, Prettier, wakaru, webcrack, source-map-explorer availability and graceful degradation.
</Card>
</CardGroup>

---

## 20. Maintaining the skill

> Self-improvement protocol for deobfuscate-javascript: when to update CHUNK_NAME_REGISTRY, fix scripts with tests, append caveats or codex-ref notes, commit discipline, and legal constraints on extracted Codex source.

- Page Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/pages/20-maintaining-the-skill.md
- Generated: 2026-06-27T21:23:39.444Z

### Source Files

- `.agents/skills/deobfuscate-javascript/SKILL.md`
- `.agents/skills/deobfuscate-javascript/scripts/resolve-npm-imports.ts`
- `.agents/skills/deobfuscate-javascript/scripts/quality-gate.test.ts`
- `.agents/skills/deobfuscate-javascript/reference/caveats.md`
- `.agents/skills/deobfuscate-javascript/reference/codex-ref.md`
- `README.md`

---
title: "Maintaining the skill"
description: "Self-improvement protocol for deobfuscate-javascript: when to update CHUNK_NAME_REGISTRY, fix scripts with tests, append caveats or codex-ref notes, commit discipline, and legal constraints on extracted Codex source."
---

The `deobfuscate-javascript` skill under `.agents/skills/deobfuscate-javascript/` is a living asset: agents running any restoration tier are expected to route discoveries back into the skill (scripts, registry, reference docs) so the next run benefits. The canonical protocol lives in `SKILL.md` under **Maintaining this skill (self-improvement protocol)**; this page operationalizes that contract for maintainers and agents.

<Note>
Skill maintenance is separate from restoration output. `ref/` and `restored/` are gitignored generated artifacts — commit skill changes and restoration checkpoints in different commits with different message conventions.
</Note>

## Three triggers

Update the skill when a restoration surfaces any of these:

| Trigger | Examples |
| --- | --- |
| **Obvious problem** | Script bug, `quality-gate.ts` or Stage 3 acceptance misfire, wrong flag/path in a stage or workflow doc |
| **Process optimization** | Manual step repeated across chunks that a script flag or pipeline ordering change could automate |
| **New npm package** | Vendored chunk whose stripped basename is missing from `CHUNK_NAME_REGISTRY`, or an external tool worth adopting |

The skill explicitly instructs agents: fix the skill itself, not only the current output, then commit under the discipline below.

## Route each finding to its home

| Finding | Destination |
| --- | --- |
| New vendored npm package | `CHUNK_NAME_REGISTRY` and optionally `ALIAS_REGISTRY` in `scripts/resolve-npm-imports.ts`; refresh `npm-leaf` examples in `SKILL.md` and `stages/stage-2-restore.md` |
| Script bug or misfiring gate | Fix under `scripts/`; add or extend `scripts/<name>.test.ts` |
| Wrong or stale documentation | Edit `SKILL.md`, stage docs, or workflow docs inline |
| Edge case or gotcha | Append to `reference/caveats.md` |
| Codex- or project-specific learning | Append to `reference/codex-ref.md` (boundary fingerprints, Pierre/Radix fork notes, semantic domain hints) |
| New automated step | Implement in the relevant script; document in stage/workflow docs; update routing and tools tables in `SKILL.md` if the flow changes |
| New JS/Babel dependency | Add to `package.json` and run `bun install` in the skill directory |
| New external binary (wakaru, webcrack, prettier, …) | Thin wrapper script with graceful degradation (model: `format.ts`, `wakaru-normalize.ts`); pin version in the invoke command; document under **Tools at a glance → External tools** — do not `bun install` Rust/Go CLIs |

```text
Restoration finding
        │
        ├─ data-only (registry, caveats, codex-ref, doc fix) ──► apply inline; safe mid-run
        ├─ behavior-changing script ──► fix + *.test.ts + bun test ──► commit skill
        └─ too risky mid-run ──► TODO in doc; keep restoration moving
```

## When to update CHUNK_NAME_REGISTRY

`CHUNK_NAME_REGISTRY` in `scripts/resolve-npm-imports.ts` is the authoritative map from stripped chunk basenames to npm specifiers. It drives:

- `resolve-npm-imports.ts` — rewrites `import … from "./clsx-DDuZWq6Y.js"` to bare `clsx`
- `build-import-graph.ts` — classifies reachable chunks as `npm-leaf` (BFS stops; chunk is not restored as app code)
- `check-entry.ts` — treats known registry entries as npm leaves, not local app chunks

### Basename extraction

`extractChunkBasename()` strips the path segment, file extension, then an 8-character Vite/Rollup hash (with a 10–12 character fallback). Multi-word names like `react-dom-De86Q4ix` become `react-dom`, not `react`.

### Registry entry shape

Each key is the **stripped basename** (hash-free), not the full filename:

<ParamField body="package" type="string" required>
Bare npm specifier to rewrite imports to (e.g. `"clsx"`, `"@dnd-kit/core"`, `"react/jsx-runtime"`).
</ParamField>

<ParamField body="defaultName" type="string">
Default local binding when the chunk has a default export and the alias is unknown (e.g. `clsx`, `React`, `Fuse`).
</ParamField>

<ParamField body="namedOnly" type="boolean">
When `true`, every specifier from the chunk is treated as a named import (e.g. `tslib`, `jsx-runtime`, `@floating-ui/react-dom`).
</ParamField>

### When to add an entry

Add to the registry when:

1. Stage 2/3 polish leaves `import … from "./some-pkg-HASH.js"` that should be a bare npm import.
2. `build-import-graph.ts` incorrectly classifies a known vendor chunk as `local` and BFS descends into package internals.
3. `codex-ref.md` documents a bundled third-party boundary whose basename stem does not match the registry (e.g. `isEqual-*` → `lodash`, `lib-*` → `react-intl`) — register the stem and prefer a `make-facade.ts --reexport` deliverable over deep-restoring vendor code.

<Warning>
Before registering `@pierre/*`, `@radix-ui/*`, or `zod` (`src-*`), confirm the chunk is stock npm, not a Codex fork. A wrong bare rewrite is harder to undo than leaving the local path. See `reference/codex-ref.md` fork caveats.
</Warning>

### ALIAS_REGISTRY

When the chunk basename is project-local (e.g. `./shared`) but the **local binding** is a well-known export (`useState`, `_React`, `FormattedMessage`), add or extend `ALIAS_REGISTRY` with `package`, `style`, and optional `renameLocalTo`.

### One-run workaround

Until the registry is updated, pass `--treat-as-npm <basename>` to `build-import-graph.ts` so a single run treats listed basenames as `npm-leaf` without descending. This is a stopgap — still add the permanent registry entry and a test.

<Steps>
<Step title="Identify the stripped basename">

Inspect the chunk path (e.g. `./react-hook-form-AbCdEf12.js` → `react-hook-form`). Cross-check `ref/package.json` dependencies when restoring Codex `./ref`.

</Step>

<Step title="Add CHUNK_NAME_REGISTRY and ALIAS_REGISTRY entries">

Choose `defaultName` vs `namedOnly` from how consumers import the chunk. Add cryptic alias mappings to `ALIAS_REGISTRY` when exports use minified local names.

</Step>

<Step title="Extend tests">

Add cases to `scripts/resolve-npm-imports.test.ts` (rewrite behavior) and `scripts/build-import-graph.test.ts` (`npm-leaf` classification). Existing tests cover `clsx`, `react-dom` hash anchoring, and `--treat-as-npm` overrides.

</Step>

<Step title="Refresh docs">

Update `npm-leaf` examples in `SKILL.md` and the chunk-name lookup section in `stages/stage-2-restore.md`. For Codex-specific vendor tables, append to `reference/codex-ref.md`.

</Step>

<Step title="Run the suite and commit">

```bash
cd .agents/skills/deobfuscate-javascript
bun test
```

Commit with `skill(deobfuscate-javascript): add <package> to CHUNK_NAME_REGISTRY`.

</Step>
</Steps>

## Fix scripts with tests

Behavior-changing script edits must ship with a green `bun test` run from `.agents/skills/deobfuscate-javascript/`. The skill ships 37 `scripts/*.test.ts` files plus `fixtures/` for Stage 1 techniques (packer, AAEncode, string-array, composite, control-flow-flat, and others).

| Area | Test file | What to extend |
| --- | --- | --- |
| Import resolution | `resolve-npm-imports.test.ts` | New registry entries, `extractChunkBasename` edge cases |
| Graph classification | `build-import-graph.test.ts` | `npm-leaf` vs `local`, `--treat-as-npm`, hash regression |
| Quality bar | `quality-gate.test.ts` | New issue codes, provenance rules, full-restoration coverage |
| Stage 1 | `unpack.test.ts`, `string-array.test.ts`, `deobfuscate.test.ts`, … | New obfuscation patterns — add a `fixtures/<technique>.min.js` fixture when extending matchers |

<RequestExample>

```bash
cd .agents/skills/deobfuscate-javascript
bun test                          # full suite
bun test scripts/quality-gate.test.ts   # single file
```

</RequestExample>

For a misfiring `quality-gate.ts` check: reproduce with a minimal source string in `quality-gate.test.ts` (the suite already tests provenance headers, cryptic params, duplicate headers, and CLI `--allow-missing-provenance`). Never commit with a red suite.

Non-standard string-array rotation patterns called out in `reference/caveats.md` should get a `fixtures/` sample so the matcher extension cannot regress.

## Append caveats and codex-ref notes

**`reference/caveats.md`** is the cross-stage FAQ: pipeline ordering, eval safety in `unpack.ts`, Prettier `.gitignore` traps, wakaru byte-offset shifts, polish tier differences, and troubleshooting entries. Append a new subsection when you discover a gotcha that will recur — do not bury one-off fixes only in restoration chat.

**`reference/codex-ref.md`** is the project profile for `./ref` (openai-codex-electron): entry discovery, Pierre/`@radix-ui` boundary fingerprints, bundled-vendor re-export table, semantic domain playbook, and Stage 3 acceptance expectations for this repo. Append when you learn:

- A basename trap (looks like Pierre but is Codex app code, or vice versa)
- A new bundled third-party boundary and its npm specifier
- Default command-frame or resume conventions that changed with a Codex.app update

Stage docs (`stages/stage-2-restore.md`, `stages/stage-3-finalize.md`) link back to the self-improvement protocol for missed npm packages — keep those links accurate when you extend the registry.

## Safety guards during an active restoration

| Change type | Mid-run policy |
| --- | --- |
| Registry entries, caveats, codex-ref, doc-only fixes | Safe to apply inline |
| Behavior-changing script | Requires green `bun test` before commit; do not leave suite red |
| Risky or unvalidated refactor | Smallest safe note (TODO in relevant doc); do not block the user's restoration on speculative work |

Data-only edits do not alter the in-flight pipeline. Script behavior changes can invalidate checkpoints, manifest offsets, or gate results — validate before relying on them in the same session.

## Commit discipline

At the end of each agent turn, commit — but **separate skill self-improvements from restoration output**:

| Commit kind | Message convention | Contents |
| --- | --- | --- |
| Skill maintenance | `skill(deobfuscate-javascript): <what changed>` | Scripts, tests, `SKILL.md`, `reference/*`, `package.json` |
| Restoration progress | `app-main: …` (repo convention) | Promoted files under `restored/`, `IMPORT_MAP.json`, public deliverables |

<Check>
Never commit `restored/.deobfuscate-javascript/` workspace intermediates. The whole `restored/` tree is gitignored; staging checkpoints, manifests, and per-chunk `$WS` dirs stay local.
</Check>

Skill commits should be reviewable in isolation: one logical change (e.g. one registry package, one gate fix) per commit when practical.

## Legal constraints on extracted Codex source

This repository is for **personal study and interoperability research** of software you have installed. The extracted Codex.app bundle is © OpenAI; respect the Codex desktop app license and terms of service.

| Rule | Implementation in this repo |
| --- | --- |
| Do not redistribute extracted or restored code | `ref/` and `restored/` are listed in `.gitignore` — regenerated by skills, not checked in |
| Do not publish OpenAI proprietary source | Skill commits contain tooling and documentation only; not `ref/webview/assets` chunks or full `restored/` trees |
| Refresh from your own install | `codex-app-ref-refresh` reads `/Applications/Codex.app/Contents/Resources/app.asar` locally |

<Warning>
A `make-facade.ts --passthrough` interim deliberately makes `restored/` depend on unrestored `ref/` chunks (`@ts-nocheck` + `// TODO`). That is a local development stopgap, not a license to redistribute either tree.
</Warning>

Maintainers improving the skill should never use skill commits as a channel to leak extracted app source. Registry keys and fixture snippets must be minimal and technique-focused, not full vendor bundles.

## Verification checklist

Before closing a skill-maintenance turn:

1. `bun test` exits 0 from `.agents/skills/deobfuscate-javascript/`.
2. New npm basenames appear in `CHUNK_NAME_REGISTRY` (and tests), not only `--treat-as-npm` overrides.
3. Recurring gotchas are in `reference/caveats.md` or `reference/codex-ref.md`, not only inline in one workflow.
4. Commit message uses the `skill(deobfuscate-javascript):` prefix and excludes `ref/` / `restored/` artifacts.

## Related pages

<CardGroup>
<Card title="Import graph and boundaries" href="/import-graph-and-boundaries">
How `CHUNK_NAME_REGISTRY` feeds `npm-leaf` classification, facade lifecycle, and quality-gate coverage.
</Card>
<Card title="Codex project profile" href="/codex-project-profile">
When to extend `codex-ref.md` with Pierre, Radix, and bundled-vendor boundary notes.
</Card>
<Card title="Pipeline caveats" href="/pipeline-caveats">
Ordering rules and failure modes that belong in `reference/caveats.md`.
</Card>
<Card title="Quality bar and anti-patterns" href="/quality-bar-and-anti-patterns">
Completion criteria and gate failures that often drive script fixes.
</Card>
<Card title="External tools and dependencies" href="/external-tools-and-dependencies">
How to adopt new Bun deps vs external binary wrappers.
</Card>
</CardGroup>

---
