| name | deobfuscate-javascript |
| description | Reverse-engineer minified, obfuscated, packed, or bundled JavaScript into readable or typed TS/TSX, including Codex.app `./ref/webview/assets` restoration. Use for .min.js, dist bundles, Vite/Rollup/Webpack chunks, obfuscated snippets, or requests to humanify, deobfuscate, deminify, unpack, decode, make readable, rename variables, reverse engineer, ๅฎๆด่ฟๅ, ๆทฑๅบฆ่ฟๅ, ๅๆททๆท, ็พๅไปฃ็ , ็ๆ่ฟๆฎต JS, ้ๅฝๅๅ้, ๅ็ผ่ฏ. Whole-tree restores (index.html + assets, "restore the code/whole tree") default to deep: deobfuscate if needed, smart-rename, polish, resolve npm imports, typed semantic TSX rewrite, acceptance review, import graph/ledger, and full-target quality gate draining every reachable project-local chunk. Quick/readable/ๅฟซ้ or lone snippets use readable naming-quality restore. Vendor data/packages become npm leaves or re-export shims, not restored package bodies. |
Deobfuscate JavaScript
Reverse-engineer minified/obfuscated JavaScript back to readable code. Three stages: Stage 1 (only if obfuscated) and Stage 2 (rename + polish) do the readable restore; Stage 3 adds the typed rewrite + acceptance review and is the deep-tier add-on.
- Stage 1 โ Deobfuscation: unwind obfuscation transforms (Packer/AAEncode unpack, Obfuscator.IO string arrays, hex/unicode/base64 escapes, dead code, opaque predicates). Pure Babel passes, no LLM. Run only on obfuscated input. Detail: stage-1-deobfuscate.md.
- Stage 2 โ Restore to readable (two phases). (a) Rename cryptic identifiers (
a, e, t, _0x4f3, โฆ) to meaningful names โ a TS+Bun port of humanify where you play the renamer, running smart-rename.ts first so you only hand-name the residue. (b) Polish undoes bundler/compiler transforms โ the reading-aid subset (--fast: React Compiler cache[N] strip, (0, fn)(...) unwrap, backtick โ string, JSX-runtime โ JSX, destructure defaults, aliased exports) is the default; the import-resolution tail (npm/shim/dead-shim elimination) is deep mode only. Detail: stage-2-restore.md.
- Stage 3 โ Finalize (deep mode only) (two phases). (a) Semantic rewrite turns the mechanical checkpoint into idiomatic typed TypeScript โ semantic filenames, provenance header, import map, types/
.tsx recipes, directory splits, import repair. (b) Acceptance review โ the host reads every delivered file end-to-end against the four-category quality bar and iterates until each passes; no sub-agent and no authorization required (an independent reviewer sub-agent is optional). The default tier ships the Stage 2 readable file without Stage 3. Detail: stage-3-finalize.md.
Default scope and depth
Two independent axes: scope (one file vs the whole import tree) and depth (readable vs deep). Pick scope from the input shape. For a whole-tree restore the default depth is deep ("restore the code" means complete + typed); a lone snippet defaults to readable. Drop a whole tree to readable only on an explicit "quick"/"readable"/"ๅฟซ้" request.
- Default scope โ whole tree from
index.html. For an app (index.html + sibling-chunk asset tree), read index.html, auto-discover the entry (check-entry.ts --discover --root <assets-dir>, or omit the positional to build-import-graph.ts), then recursively restore every reachable project-local chunk. Per-chunk pipeline: Stage 1 (if obfuscated) โ wakaru pre-pass (wakaru-normalize.ts; recovers ES6 classes, async/await, optional chaining, destructuring, TS enums, โฆ; default-on, auto-skips if @wakaru/cli absent) โ Stage 2 rename (smart-rename first, then hand-name residue) โ reading-aid polish (polish.ts --fast) โ format โ organize in staging, then promote into shared restored/ (semantic subfolders, kebab filenames, provenance headers, one restored/IMPORT_MAP.json); script/batch output never goes straight into restored/. Bundled vendor data (Shiki grammars + themes, data libs like 3Dmol) is detected by content and treated as npm-leaf โ NOT restored; consumers import the bare specifier (@shikijs/langs/<id>, @shikijs/themes/<id>), so restored/ holds app code only. Readable depth's hard bar is naming quality; deep depth adds complete types + every reachable chunk promoted.
- Fallback scope โ single file. When the input is a lone pasted snippet, or there is no
index.html / asset tree, restore just that one file โ workflows/small-minified.md. Same per-file pipeline, no import graph.
- Depth โ deep (default for whole tree) vs readable (quick opt-out). Deep = meaningful names plus Stage 3 (typed
.tsx + acceptance review), full npm-import resolution (Stage 2's deep polish tail), and import-graph/ledger orchestration draining every reachable chunk to promoted; also triggered on "deep"/"full"/"ๅฎๆด"/"ๆทฑๅบฆ"/"production"/"typed"/"restore the whole tree". Readable = meaningful names, reading-aid polish, untyped โ only on "quick"/"readable"/"ๅฟซ้"/"็ฒ็ฅ" or a lone snippet. Both organize and promote; promote-organized.ts defaults --tier deep (enforces Props/param types), --tier readable relaxes the typing gate.
Deep is a completion bar, not an upsell: a whole-tree restore is "done" only when every reachable project-local chunk reaches stages.promoted and quality-gate.ts <target> passes (Restoration contract). Readable is a downgrade the user asks for, not the resting state.
Output conventions
restored/ is a clean-only deliverable zone โ the canonical staging โ organize โ promote rule (referenced everywhere below). Anything a batch or script restore emits (auto-restore-full.ts checkpoints, a swept polish.ts, --write-target-checkpoints files, any hash-basename .tsx) is a mechanical checkpoint, not a deliverable; it goes into the gitignored staging tree under restored/.deobfuscate-javascript/ (_full/checkpoints/<basename>.tsx for the batch executor, per-chunk $WS/ otherwise), never straight into restored/. Promote only after the host organizes it. The promotion bar, in every tier: good readability (semantic names, no mechanical fallbacks like buttonValue3 / contextParam14), friendly kebab filenames, clear semantic-domain structure, and prettier-formatted (promote-organized.ts runs format.ts on every deliverable, so restored/ is never raw @babel/generator output) โ plus complete types (Props interfaces on exported components, param/return types where they help) whenever the restore is deep/full (where all auto-restore-full.ts output lives). Only genuinely hand-restored single readable-tier deliverables may promote untyped, and even those draft in $WS, get organized, then land โ never raw script output. For a whole-tree batch restore this is driven, not eyeballed: plan-organize.ts proposes a domain + kebab path per chunk; promote-organized.ts drains the promote frontier (typed deliverable โ quality gate โ copy into restored/<domain>/ โ IMPORT_MAP update โ import rewrite). Complete only when every reachable chunk reaches stages.promoted and quality-gate.ts <target-dir> passes โ see full-restoration.md โ Step 4.
- One shared restore root โ
restored/ โ mirrors the source assets dir (e.g. ref/webview/assets). Every entry restores into it; no per-entry folder (no restored/app-main/).
- Semantic-domain subfolders group output (
app-shell/, composer/, utils/, icons/, โฆ); original chunk identity lives in each file's provenance header, not the layout.
- One shared import map โ
restored/IMPORT_MAP.json โ reused regardless of entry. Never per-chunk/per-session/per-entry maps (no APP_MAIN_IMPORT_MAP.json).
- kebab-case file and directory names (
button.tsx, download-icon.tsx, app-shell/), but React component + type names stay PascalCase (JSX requires it) โ button.tsx exports Button, download-icon.tsx exports DownloadIcon. quality-gate.ts enforces this (non-kebab-filename).
While restoring, if you hit a script bug, a step worth automating, or an unrecognized npm package, update the skill itself and commit it separately โ see Maintaining this skill.
Vendor/npm hard stop
Any task that will create or edit restored/vendor/* (including nested compat-*, stable-exports/*, or runtime barrels) is gated. Before the first edit, read reference/vendor-npm.md in full and run the directory audit:
bun .agents/skills/deobfuscate-javascript/scripts/vendor-npm-preflight.ts restored/vendor
The non-negotiable core: public vendor files are npm-first โ the default deliverable is a thin npm-backed re-export shim, never a hand-written "compatible subset" of a stock package. A missing dependency means add it to the nearest package.json, not evidence for a local body. The intent gate (vendor-npm-preflight.ts <target> --decision --intent npm-shim|local-body) must exit 0 before writing code; public vendor files fail closed to npm-shim unless recorded Codex-fork or app/runtime-wrapper proof exists. The full evidence order, touch protocol, aggregator-barrel rules, and shim recipe live in reference/vendor-npm.md.
Restoration contract
Default tier (readable restore)
- Find the entry (whole tree is the default). With an
index.html + asset tree, auto-discover: bun scripts/check-entry.ts --discover --root <assets-dir> (or omit the positional to build-import-graph.ts). Restore every reachable project-local chunk (per-chunk steps run across the tree, orchestrated by full-restoration.md). No index.html/tree (a lone snippet) โ single-file flow, small-minified.md.
- Analyze first. Always run
sourcemap-check; use detect / extract per chunk.
- Restore to readable. Stage 1 if obfuscated โ wakaru-normalize (
bun scripts/wakaru-normalize.ts "$WS/original.js" -o "$WS/normalized.js"; auto-skips when absent, always leaves a normalized.js) โ Stage 2 rename (smart-rename first, then hand-name residue until single-letter density is low) โ reading-aid polish (polish.ts --rename --fast) โ format. One-shot: polish.ts "$WS/normalized.js" --rename --fast --source <original-path> --out draft.tsx --format (file operand is the normalized output; --source points at the original for the provenance header); then hand-name what smart-rename left.
- Stage, organize, then promote (canonical rule: Output conventions). The one-shot writes its draft into
$WS/; promote into restored/ (semantic subfolders, kebab filenames, // Restored from <path> header, shared restored/IMPORT_MAP.json) only after organizing โ never copy a mechanical checkpoint in. The hard bar at readable depth is naming quality โ meaningful identifiers, no generated fallback names (buttonValue3, contextParam14); types, npm-import resolution, and the reviewer loop stay optional (an optional naming-only self-review is available for extra confidence).
Deep / production mode only
When the user asks for deep/full/typed/production output, continue past the readable draft:
- Build the graph (full mode). Build
manifest.json + ledger.json with build-import-graph.ts and build-symbol-ledger.ts so renaming is resumable and parallel-safe across the chunk tree.
- Create a mechanical checkpoint. Stage 2 scripts or
auto-restore-full.ts produce a broad readable checkpoint (may keep hash basenames + fallback names; never the final deliverable). wakaru-normalize may run per chunk body here too, with two guards: never --unpack an already-split tree (it re-derives module boundaries, forking the restore root), and treat wakaru's import/export specifiers as untrusted โ build-import-graph.ts must rebuild the manifest from on-disk files after wakaru, and resolve-npm-imports.ts + CHUNK_NAME_REGISTRY stay the authoritative rewriter (wakaru's un_esm doesn't substitute).
- Rewrite semantically as the host agent. Read the checkpoint with the original source, graph/ledger, producer/consumer imports, and project conventions; rewrite into maintainable code โ meaningful filenames/identifiers, typed props, semantic exports, resolved npm imports, clear structure.
- Pre-filter only. Run prettier and
quality-gate.ts for cheap failures. Passing does not mean done.
- Acceptance-review LOOP. The host agent reads every delivered file end-to-end against the four-category quality bar in
scripts/acceptance-checklist.md, rewrites each NEEDS_FIX, and re-reads until all pass โ no sub-agent and no authorization required. An independent reviewer sub-agent is optional (extra eyes only, never a blocker, never fabricate verdicts or skim). Complete only after every file passes.
- Full-target completion audit. For whole-tree restores, run
quality-gate.ts <target-dir> after the loop. It checks the manifest + shared import map and fails if any reachable local/oversized-local app-feature chunk is still missing, mechanical, @ts-nocheck, empty placeholder, typed facade, or lacks Stage 3 acceptance evidence. Not a boundaries/ grep or IMPORT_MAP.status === "done" scan.
Dual-track output (deep mode): hidden checkpoints preserve original hash basenames; public files use semantic names, with chunk identity in provenance headers and the one shared restored/IMPORT_MAP.json (reuse + append; see Output conventions).
Full restoration mode is not a new stage โ it's an orchestration layer on top of Stages 1โ3 coordinating the per-file pipeline across a connected component of the import graph (entry chunk + every sibling it pulls in, transitively to the leaves). It adds manifest.json (file-level dependency graph + stage status) and ledger.json (per-file symbol checklist + cross-file binding table) so renaming is resumable and parallelizable at file granularity. "deep"/"full"/"ๅฎๆด"/"ๆทฑๅบฆ"/"restore referenced chunks" โ every reachable project-local sibling is in scope by default. Detail: workflows/full-restoration.md.
Delta / boundary replacement inside an existing restore
When the target restore root already has a shared IMPORT_MAP.json and .deobfuscate-javascript/_full/manifest.json, first check whether the requested chunk already maps to a public file or typed boundary facade. If it does, treat the task as a delta restore unless the user explicitly asks to rebuild the whole reachable graph.
Delta restore contract:
- Reuse the existing target, manifest, ledger, and import map; no parallel restore root.
- Restore the requested chunk in its existing
_full/files/<basename>/ workspace. Restore additional chunks only when missing from the import map or explicitly requested.
- Rewrite imports through the shared
IMPORT_MAP.json: finalized local deps โ semantic public paths; npm deps โ bare specifiers; unresolved runtime/vendor deps โ documented boundary facade paths.
- Replace the mapped boundary/public file with the semantic candidate; update the import-map entry (
restored, exports, status, drop boundary when it's no longer a facade).
- Validate: format,
quality-gate.ts on changed public path(s), the target TypeScript check when the root has a tsconfig.json, and Stage 3 acceptance on changed public files only. All-tree acceptance only when the whole public tree changed or the user asked.
Vendor/npm preflight โ before restoring restored/vendor/*
Blocking preflight; the full protocol (evidence order, intent gates, aggregator-barrel resolution, the npm-shim deliverable recipe) is in reference/vendor-npm.md โ read it before any restored/vendor/* edit. See Vendor/npm hard stop above for the audit command.
Boundaries and terminal nodes
Full-restoration mode records npm-leaf, oversized-local, external, and faced-boundary chunks as terminal nodes โ referenced by consumers but never renamed or given a workspace. restored/boundaries/*.ts has exactly three terminal states โ known npm package โ bare re-export shim (done), genuine app/host runtime โ typed facade or opt-in passthrough (an open boundary, never done), restore + promote out of boundaries/ (the only completion for runtime facades) โ a permanent any-facade is never acceptable, and project/feature chunks are never faced ("big" or "imported by many" is not a facing reason). Deep/full mode recurses the entire reachable project-local graph to the leaves; final proof is quality-gate.ts <target-dir> over the whole public target. The terminal-node trigger table, boundary lifecycle detail, and build-import-graph.ts depth flags (--max-lines, --include) are in reference/boundaries.md โ read it when classifying a chunk as a boundary or resolving one.
Run Stage 1 before Stage 2 on obfuscated input โ extract.ts byte offsets are invalidated by Stage 1 rewrites. On purely minified (not obfuscated) input, skip Stage 1.
Current project profile โ codex-app-code ./ref
When the workspace contains ref/package.json with name: "openai-codex-electron" or the user asks to restore ./ref, load reference/codex-ref.md before choosing a workflow.
Default assumptions for this repo:
- Primary bundled-code root:
ref/webview/assets.
- Main app entry: read
ref/webview/index.html; current builds usually route through index-*.js, app-main-*.js, and app-shell-*.js.
- Default restore root:
restored/ (mirrors ref/webview/assets), with semantic-domain subfolders (app-shell/, composer/, utils/, icons/). Reuse the single shared restored/IMPORT_MAP.json before creating any new map. Do not create a per-entry folder such as restored/app-main/. Auto-discover the entry with scripts/check-entry.ts --discover --root ref/webview/assets.
- Treat
ref/node_modules/**, ref/native-menu-locales/**, CSS, WASM, images, and obvious vendor/data chunks as references or terminal boundaries unless the user explicitly asks to restore them.
- Vendored UI packages โ
@pierre/trees / @pierre/diffs boundaries, not app code. The file-tree and diff-view subsystems are bundled @pierre/trees (Preact) + @pierre/diffs (Shiki). Codex FORKED them (React fork of @pierre/trees; settings-entangled @pierre/diffs gate), so a clean bare-import swap is usually infeasible โ keep the forked wrapper, relabel provenance, boundary-ize (quality-gate.ts --vendored); only self-contained leaves are clean bare imports (worker-* โ @pierre/diffs/worker, parsePatchFiles-* โ @pierre/diffs). Fingerprints: --trees-*/--diffs-* CSS vars, data-file-tree-*/data-diffs-* attrs, pierre-light/dark(-soft) themes, the @pierre/truncate css hereโฆ comment. Engine chunks: file-tree-search-input-*, shiki-highlight-provider-gate-*, file-diff-*, diff-unified-*, parsePatchFiles-*, worker-*. NOT-Pierre traps: diff-stats-*, diff-view-mode-*, use-diff-annotations-* (PDF.js), parse-diff-* (Codex), diff-* grammar (Shiki), treeView-SZITEDCU-*/treemap-* (Mermaid), worktree-* (git). Detail: codex-ref.md โ Boundary classification.
- Before starting fresh, inspect existing
restored/**/README.md, IMPORT_MAP.json, .deobfuscate-javascript/_full/manifest.json, and .deobfuscate-javascript/_full/ledger.json; resume rather than duplicating semantic modules.
When to use this skill
Trigger whenever the user:
- Provides a
.min.js, bundle.js, dist/ file, or any JS with cryptic short names and wants to understand or work with it.
- Provides obviously obfuscated code:
_0x identifiers, eval(function(p,a,c,k,e,d){...}), AAEncode emoji glyphs, String.fromCharCode(72,101,...) walls, hex/unicode-escaped literals.
- Says "humanify", "deobfuscate", "deminify", "unminify", "unpack", "decode", "make readable", "rename variables", "reverse engineer"; or in Chinese "ๅๆททๆท", "็พๅไปฃ็ ", "็ๆ่ฟๆฎต JS", "้ๅฝๅๅ้", "ๅ็ผ่ฏ".
- Asks "what does bundle.js do?" while pointing at minified/obfuscated code, or works in a project with a
modules//og/ folder of previously deobfuscated chunks.
Don't trigger if the code is already readable โ just read and explain. Nor for plain beautification โ npx prettier --write <file> when there are no cryptic names and no obfuscation. wakaru alone is not a substitute โ npx @wakaru/cli un-minifies but its smart_rename is the same deterministic heuristic as smart-rename.ts, not semantic naming; use it as a pre-pass inside Stage 2, not as the deliverable.
Routing โ three questions, then load one workflow
Step 0 (always): run scripts/sourcemap-check.ts first. If a .map exists, recover originals via npx source-map-explorer instead โ that beats any rename. For the current repo's ./ref tree, load reference/codex-ref.md first.
Step 0.5 (whole tree / ./ref / any multi-chunk app โ the default): auto-discover the entry: scripts/check-entry.ts --discover --root <assets-dir> reads index.html, picks + sanity-checks the app entry, prints its path (build-import-graph.ts does the same when you omit the positional). If discovery (or manual check-entry.ts <entry> --root <assets-dir>) exits 3, the entry is a transitive vendor leaf, not the app โ restoring from it yields a tiny closure that looks complete (the "got 6 files, called it done" trap). A real app entry has large local fan-out and ~nobody imports it; a leaf is the inverse. Switch to the index.html <script> root (or a high-fan-out app-main-* chunk) first.
Step 0.6 (existing restore delta): if the target has IMPORT_MAP.json + _full/manifest.json, check whether the requested chunk is already a boundary/public output โ prefer the delta/boundary-replacement contract above before a new whole-tree restore. "Complete" then means complete replacement for that scoped chunk unless the user asks to rebuild the reachable graph.
Step 0.7 (mechanical normalization โ readable tier, default-on): on non-obfuscated minified/transpiled input, run wakaru-normalize.ts before extract.ts. Byte-rewriting like Stage 1, so the order is sourcemap-check โ detect โ (Stage 1 if obfuscated) โ wakaru-normalize โ extract โ extract/rename from the normalized output, never original.js. Skip when a usable .map exists (recover via source-map-explorer). Auto-skips when @wakaru/cli is absent. In deep/full mode it's guarded, not default-on (Restoration contract โ deep step 2).
Then answer in order:
| Question | Then |
|---|
Obfuscated? (_0x arrays, Packer, AAEncode, hex/unicode walls, opaque predicates) | Stage 1 first via full-obfuscation.md, then continue below |
Has an index.html + asset tree? (the default) โ auto-discover the entry, restore the whole reachable tree | whole-tree restore โ full-restoration.md at deep depth by default (typed .tsx + acceptance + drain every chunk to promoted); only "quick"/"readable"/"ๅฟซ้" downgrades to the untyped readable pass |
Otherwise โ a lone pasted snippet or single chunk, no index.html/tree | fallback: single file โ small-minified.md |
The single-file fallback workflow handles the sub-cases inline (it links the right technique): React/JSX โ recover JSX in --fast polish; โฅ 3 exports / registry โ multi-export-bundle.md split (a deep-tier step); โฅ 500 KB or > 1000 symbols โ huge-single-file.md batching; webpack id:(e,t,n)=>{} modules โ webpack-bundle.md webcrack pre-split; stdin/stdout chaining โ piped.md. Load a technique doc only when its sub-case applies.
For end-to-end worked traces, see reference/examples.md. For caveats, gotchas, and troubleshooting, see reference/caveats.md.
Quality bar โ anti-patterns to refuse before declaring done
Before declaring any restore done, read reference/quality-bar.md and check the output against it. The hard bar in every tier is naming/readability: no program-scope-only renames (bodies still let k = โฆ, [A, M] = useState(false)), no generated fallback names (buttonValue3, contextParam14), no React Compiler scaffolding in load-bearing functions, no lowercase JSX tags that are really component aliases, no known npm package rewritten as a local implementation, no mechanical/batch checkpoint promoted into restored/, no bundle body merely moved/renamed into a semantic folder, and never checkpoints-built-but-nothing-promoted. Deep/typed/production output additionally must pass the typing/structure list (typed .tsx, split multi-export bundles, no faced app chunks, Stage 3 acceptance actually performed) โ all itemized with cures in reference/quality-bar.md.
Tools
Every script's purpose + "Run when" routing, external-tool notes, the shared import-map convention, and exit codes live in reference/tools.md โ load it when you need a flag or aren't sure which script fits. The ones used in nearly every run:
| Tool | Role |
|---|
scripts/sourcemap-check.ts | Always first โ a recoverable .map beats any rename |
scripts/check-entry.ts | --discover --root <assets-dir> finds + sanity-checks the app entry (exit 3 = vendor leaf, wrong entry) |
scripts/build-import-graph.ts | BFS the chunk tree โ _full/manifest.json (full-restoration mode) |
scripts/wakaru-normalize.ts | Default-on mechanical pre-pass (readable tier); auto-skips when absent |
scripts/polish.ts | One-shot Stage 2: --rename --fast --format is the readable-tier pipeline; full chain (no --fast) resolves npm imports in deep |
scripts/quality-gate.ts | Hard gate before promote; quality-gate.ts <target-dir> --check-format is the whole-tree completion proof |
scripts/vendor-npm-preflight.ts | Blocking guard for any restored/vendor/* edit โ see Vendor/npm hard stop |
scripts/format.ts | Prettier wrapper; every deliverable is formatted |
Workspace convention (TL;DR)
Every intermediate file lives in a hidden per-chunk workspace under the target output directory:
INPUT=ref/webview/assets/spinner-D37df5tU.js
TARGET=restored
WS="$TARGET/.deobfuscate-javascript/$(basename "$INPUT" .js)"
mkdir -p "$WS"
cp "$INPUT" "$WS/original.js"
Then run every script against $WS/original.js and write every output back into $WS/. Everything a script or batch run emits stays in the .deobfuscate-javascript/ staging tree ($WS/ per chunk, _full/checkpoints/ for the batch executor); $TARGET/ (restored/) receives only organized deliverables โ this is the staging โ organize โ promote rule in full at Output conventions. Add .deobfuscate-javascript/ to .gitignore โ one entry covers every chunk in the target.
For full restoration mode, a shared _full/ subdir sits next to the per-chunk ones, holding manifest.json, ledger.json, and locks/ so chunks coordinate as one tree. The public import map is shared at the restore root, not inside each $WS. Full convention โ multi-module sub-workspaces, plan files, split-plan layout โ at stages/workspace.md.
Maintaining this skill (self-improvement protocol)
The skill is a living asset. When a run surfaces (1) a script bug or misfiring gate, (2) a manual step worth automating, or (3) a new/missed npm package identity, fix the skill itself โ registry entry, script + test, or doc โ and commit it separately from restoration output (skill(deobfuscate-javascript): <what changed>). The findingโhome routing table, safety guard for mid-run edits, and commit discipline are in reference/maintenance.md โ read it before making the skill change.
Files in this skill
Load sub-files on demand โ each entry says when.
Routing & overviews
- SKILL.md โ this file. Routing + contracts + workspace TL;DR; always loaded.
- reference/codex-ref.md โ project profile for restoring this repo's extracted Codex.app
./ref tree. Load before any ./ref work.
- reference/vendor-npm.md โ vendor/npm preflight, intent gates, touch protocol, shim recipe. Load before any
restored/vendor/* edit.
- reference/boundaries.md โ terminal-node kinds, boundary lifecycle, graph depth flags. Load when classifying or resolving a boundary.
- reference/quality-bar.md โ the anti-pattern checklist. Load before declaring any restore done.
- reference/tools.md โ every script with purpose + "Run when", external tools, import-map default, exit codes. Load when picking a tool or flag.
- reference/maintenance.md โ self-improvement protocol detail. Load when routing a discovered fix back into the skill.
- stages/workspace.md โ the
$WS convention in full.
- reference/examples.md โ nine end-to-end worked traces.
- reference/caveats.md โ gotchas, ordering rules, troubleshooting FAQ.
- reference/naming-heuristics.md โ the mechanical rename rules (React props, event handlers, iteratee callbacks, hook returns) that
smart-rename.ts automates.
- reference/bundler-runtimes.md โ esbuild vs Rolldown vs Vite runtime helper signatures (
__esm/__commonJS/__export/__toESM/__require); read before hand-converting __export/__esm call sites.
Per-stage detail
- stages/stage-1-deobfuscate.md โ detect โ unpack โ string-array โ decode-strings โ simplify โ control-flow-report โ orchestrator, + ordering rules.
- stages/stage-2-restore.md โ Phase A (rename): sourcemap-check โ setup โ webcrack โ extract โ decide names โ Step 2.5 โ apply โ verify. Phase B (polish): strip-react-compiler โ simplify โ jsx-runtime โ inline-defaults โ normalize-exports โ (deep-mode import tail) โ
polish.ts โ format.
- stages/stage-3-finalize.md โ deep mode only. Phase A (rewrite): D0 gate โ D0.5 filenames โ D1 provenance โ D2 imports โ D3โD4 cleanup โ D5 typed
.tsx โ D6 format โ D7 gate. Phase B (acceptance): read each file E1 naming / E2 readability / E3 formatting / E4 other โ rewrite โ re-read until pass (sub-agent optional). Default tier: optional naming-only (E1) self-review.
Workflows by scenario
Scripts
Every script is described once, with its "Run when" routing, in reference/tools.md. All scripts live under scripts/; each behavior-changing script has a sibling scripts/<name>.test.ts.
Tests + fixtures + deps
scripts/*.test.ts โ bun test suite (run from skill dir).
fixtures/ โ one per technique (packed.dean-edwards.min.js, aaencode.min.js, url-encoded.min.js, string-array-obfuscator.min.js, from-char-code.min.js, dead-code.min.js, opaque-predicates.min.js, control-flow-flat.min.js, composite.min.js, example.min.js).
- package.json โ bun deps (
@babel/parser, @babel/traverse, @babel/generator, @babel/types).