diff --git a/hack/browser/.gitignore b/hack/browser/.gitignore
deleted file mode 100644
index 05f49c9b..00000000
--- a/hack/browser/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-node_modules/
-genspec-wasi.wasm
diff --git a/hack/browser/README.md b/hack/browser/README.md
deleted file mode 100644
index bc37f3f3..00000000
--- a/hack/browser/README.md
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-# Browser probe
-
-A throwaway check that the `wasip1` artifact runs in a browser, and what it costs. Not the
-playground — the seed of one.
-
-```sh
-npm install
-GOOS=wasip1 GOARCH=wasm go build -o genspec-wasi.wasm ../../cmd/genspec-wasi
-
-node node-probe.js # the whole thing without a browser
-npm run serve # then open http://localhost:8099/ for the real one
-```
-
-`genspec-wasi.wasm` and `node_modules/` are generated; neither is committed.
-
-## What it establishes
-
-Under [`@bjorn3/browser_wasi_shim`](https://github.com/bjorn3/browser_wasi_shim) (114 KB, no
-dependencies), scanning a module held entirely in the shim's in-memory filesystem:
-
-| workload | run | `poll_oneoff` | definitions |
-|---|---|---|---|
-| 1 package | 86 ms cold, 10 ms warm | 0 calls | 1 |
-| 50 packages, 150 files | 492 ms | 0 calls | 750 |
-| 200 packages, 600 files | 3548 ms | 0 calls | 6000 |
-
-Compiling the 14.2 MB module takes 22 ms once; instantiating is ~4–5 ms and does not grow with the
-workload.
-
-**`poll_oneoff` never fires.** It was the risk worth checking, because the shim implements it by
-spinning (`while (endTime > getNow()) {}`) rather than yielding. Go reaches it only through the
-netpoller, and only when the scheduler runs out of work — which a single-goroutine scan never does.
-It is not guaranteed to stay at zero: longer runs under wazero showed 9–14 calls. But the shim
-supports exactly the shape Go emits (one clock subscription, which is all `netpollinit` ever
-registers), so when it does fire it works, and the cost is bounded by the delay Go asked for.
-That is a reason to keep the scan in a worker, not a reason to avoid this shim.
-
-Measured under Node, which shares V8 with Chrome. What it does not measure is a browser engine's
-own compile time for a 14 MB module, or anything about Firefox and Safari.
-
-## One run per instance
-
-The module exports `_start` and `memory`, and nothing else. It is a WASI **command**: run to
-completion, terminated by `proc_exit`. Go's `wasip1` target emits no reactor form — there is no
-`_initialize` and no callable export — so a fresh instance per scan is forced rather than chosen.
-
-That costs nothing worth avoiding. Compile once, keep the `WebAssembly.Module`, and instantiate per
-run; the measurements above are with the module reused across runs. It also means each scan starts
-on a clean heap, so nothing leaks from one run into the next.
diff --git a/hack/browser/fixture.js b/hack/browser/fixture.js
deleted file mode 100644
index ecbdc7f6..00000000
--- a/hack/browser/fixture.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// The module the probe scans. Kept in one place so the worker and any later app agree on it.
-export const goMod = `module example.com/demo
-
-go 1.25.0
-`;
-
-export const petSrc = `package models
-
-// Pet is an animal in the store.
-//
-// swagger:model pet
-type Pet struct {
- // The pet's identifier
- // required: true
- ID int64 \`json:"id"\`
-
- // The pet's name
- // max length: 50
- Name string \`json:"name"\`
-}
-`;
diff --git a/hack/browser/index.html b/hack/browser/index.html
deleted file mode 100644
index e64e70e2..00000000
--- a/hack/browser/index.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
codescan WASI probe
-
-codescan WASI probe
-Runs the wasip1 artifact under @bjorn3/browser_wasi_shim, in a worker, and reports what it cost.
-running…
-timings
-stdout (the specification)
-stderr
-
diff --git a/hack/browser/node-probe.js b/hack/browser/node-probe.js
deleted file mode 100644
index 55725abb..00000000
--- a/hack/browser/node-probe.js
+++ /dev/null
@@ -1,99 +0,0 @@
-// The same run as the page, under Node, so the shim can be exercised without a browser.
-//
-// What this does NOT tell us: how long a browser engine takes to compile a 15 MB module. Everything
-// else - the shim's filesystem, its poll_oneoff, and whether the artifact produces the right spec -
-// is the same code.
-
-import { readFile } from "node:fs/promises";
-import {
- WASI, File, Directory, OpenFile, ConsoleStdout, PreopenDirectory,
-} from "./node_modules/@bjorn3/browser_wasi_shim/dist/index.js";
-import { goMod, petSrc } from "./fixture.js";
-
-// The shim logs every path it touches unless told otherwise.
-const { debug } = await import("./node_modules/@bjorn3/browser_wasi_shim/dist/debug.js");
-debug.enable(false);
-
-const enc = new TextEncoder();
-const file = (s) => new File(enc.encode(s));
-
-const bytes = await readFile("./genspec-wasi.wasm");
-let t = performance.now();
-const module = await WebAssembly.compile(bytes);
-const compileMs = performance.now() - t;
-
-// bigTree builds a module with many packages, so the scan runs long enough for Go's scheduler to
-// idle - which is the only thing that makes it call poll_oneoff, and therefore the only thing that
-// makes the shim's busy-wait matter.
-function bigTree(pkgs, typesPer) {
- const entries = [["go.mod", file(goMod)]];
- for (let p = 0; p < pkgs; p++) {
- const files = new Map();
- for (let f = 0; f < 3; f++) {
- let src = `package p${p}\n\n`;
- for (let t = 0; t < typesPer; t++) {
- src += `// T${f}_${t} is a model.\n//\n// swagger:model t${p}_${f}_${t}\ntype T${f}_${t} struct {\n` +
- `\tID int64 \`json:"id"\`\n\tName string \`json:"name"\`\n\tTags []string \`json:"tags"\`\n}\n\n`;
- }
- files.set(`f${f}.go`, file(src));
- }
- entries.push([`p${p}`, new Directory(files)]);
- }
-
- return new PreopenDirectory("/src", new Map(entries));
-}
-
-async function run(label, extraArgs, tree) {
- let out = "", err = "";
- const fds = [
- new OpenFile(new File([])),
- ConsoleStdout.lineBuffered((l) => { out += l + "\n"; }),
- ConsoleStdout.lineBuffered((l) => { err += l + "\n"; }),
- tree || new PreopenDirectory("/src", new Map([
- ["go.mod", file(goMod)],
- ["models", new Directory(new Map([["pet.go", file(petSrc)]]))],
- ])),
- ];
-
- const args = ["genspec-wasi.wasm", "-loader=own", "-goos", "linux", "-goarch", "amd64",
- ...extraArgs, "-workdir", "/src", "./..."];
- const wasi = new WASI(args, [], fds);
-
- let pollCalls = 0, pollMs = 0, maxPollMs = 0;
- const imports = wasi.wasiImport;
- const rawPoll = imports.poll_oneoff.bind(imports);
- imports.poll_oneoff = (...a) => {
- const t0 = performance.now();
- const r = rawPoll(...a);
- const d = performance.now() - t0;
- pollMs += d; maxPollMs = Math.max(maxPollMs, d); pollCalls++;
-
- return r;
- };
-
- t = performance.now();
- const instance = await WebAssembly.instantiate(module, { wasi_snapshot_preview1: imports });
- const instantiateMs = performance.now() - t;
-
- t = performance.now();
- const code = wasi.start(instance);
- const runMs = performance.now() - t;
-
- let defs = null;
- try { defs = Object.keys(JSON.parse(out).definitions || {}); } catch { /* not JSON */ }
-
- console.log(`--- ${label} ---`);
- console.log(` exit=${code} instantiate=${instantiateMs.toFixed(1)}ms run=${runMs.toFixed(0)}ms`);
- console.log(` poll_oneoff: ${pollCalls} calls, ${pollMs.toFixed(1)}ms total, ${maxPollMs.toFixed(1)}ms worst`);
- console.log(` definitions: ${defs ? defs.length : "STDOUT WAS NOT JSON"}`);
- if (err.trim()) console.log(` stderr: ${err.trim().split("\n").slice(0, 3).join(" | ")}`);
-
- return { defs, out };
-}
-
-console.log(`compile: ${compileMs.toFixed(0)}ms for ${(bytes.length / 1048576).toFixed(1)} MB\n`);
-const a = await run("small: 1 package", ["-stub-stdlib"]);
-const b = await run("small again, same compiled module", ["-stub-stdlib"]);
-await run("medium: 50 packages, 150 files, 750 models", ["-stub-stdlib"], bigTree(50, 5));
-await run("large: 200 packages, 600 files, 6000 models", ["-stub-stdlib"], bigTree(200, 10));
-console.log(`\nreuse of the compiled module across runs: ${JSON.stringify(a.defs) === JSON.stringify(b.defs) ? "OK" : "DIFFERENT"}`);
diff --git a/hack/browser/package-lock.json b/hack/browser/package-lock.json
deleted file mode 100644
index 70ae6b45..00000000
--- a/hack/browser/package-lock.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "name": "codescan-browser-probe",
- "version": "0.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "codescan-browser-probe",
- "version": "0.0.0",
- "dependencies": {
- "@bjorn3/browser_wasi_shim": "^0.4.2"
- }
- },
- "node_modules/@bjorn3/browser_wasi_shim": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@bjorn3/browser_wasi_shim/-/browser_wasi_shim-0.4.2.tgz",
- "integrity": "sha512-/iHkCVUG3VbcbmEHn5iIUpIrh7a7WPiwZ3sHy4HZKZzBdSadwdddYDZAII2zBvQYV0Lfi8naZngPCN7WPHI/hA=="
- }
- }
-}
diff --git a/hack/browser/package.json b/hack/browser/package.json
deleted file mode 100644
index 29b86701..00000000
--- a/hack/browser/package.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "codescan-browser-probe",
- "version": "0.0.0",
- "private": true,
- "type": "module",
- "description": "Throwaway probe: does the WASI artifact run in a browser, and what does it cost?",
- "scripts": {
- "serve": "python3 -m http.server 8099"
- },
- "dependencies": {
- "@bjorn3/browser_wasi_shim": "^0.4.2"
- }
-}
diff --git a/hack/browser/probe.js b/hack/browser/probe.js
deleted file mode 100644
index be67151a..00000000
--- a/hack/browser/probe.js
+++ /dev/null
@@ -1,44 +0,0 @@
-const worker = new Worker("./worker.js", { type: "module" });
-const $ = (id) => document.getElementById(id);
-
-// The status line is assembled, not written as markup. Everything in it comes back from the
-// worker — an exit code, and definition names read out of whatever module was scanned — and
-// none of that has any business being parsed as HTML.
-const status = (cls, text) => {
- const span = document.createElement("span");
- span.className = cls;
- span.textContent = text;
- $("status").replaceChildren(span);
-};
-
-worker.onmessage = (e) => {
- const r = e.data;
- if (!r.ok) {
- status("bad", "FAILED");
- $("err").textContent = r.error;
-
- return;
- }
-
- const defs = (() => {
- try { return Object.keys(JSON.parse(r.out).definitions || {}); } catch { return null; }
- })();
-
- status(
- defs ? "ok" : "bad",
- defs
- ? `ran, exit ${r.code}, definitions: ${defs.join(", ") || "(none)"}`
- : `ran, exit ${r.code}, but stdout was not JSON`,
- );
-
- $("timings").textContent = [
- `compile ${r.compileMs.toFixed(0)} ms (once; reusable across runs)`,
- `instantiate ${r.instantiateMs.toFixed(1)} ms`,
- `run ${r.runMs.toFixed(0)} ms`,
- `poll_oneoff ${r.pollCalls} calls, ${r.pollMs.toFixed(1)} ms spinning`,
- ].join("\n");
- $("out").textContent = r.out || "(empty)";
- $("err").textContent = r.err || "(empty)";
-};
-
-worker.postMessage({ url: "./genspec-wasi.wasm" });
diff --git a/hack/browser/worker.js b/hack/browser/worker.js
deleted file mode 100644
index 252d3acd..00000000
--- a/hack/browser/worker.js
+++ /dev/null
@@ -1,84 +0,0 @@
-// Runs one scan and reports what it cost.
-//
-// In a worker for two reasons: a scan is seconds of solid CPU, and the shim's poll_oneoff busy-waits
-// rather than yielding, so on the main thread it would stall the page outright.
-
-import {
- WASI, File, Directory, OpenFile, ConsoleStdout, PreopenDirectory,
-} from "./node_modules/@bjorn3/browser_wasi_shim/dist/index.js";
-import { goMod, petSrc } from "./fixture.js";
-
-const enc = new TextEncoder();
-const file = (s) => new File(enc.encode(s));
-
-// Compiling a 14 MB module is the expensive part and the result is reusable: proc_exit ends an
-// instance for good, so every run needs a fresh instance but not a fresh compile.
-let compiled = null;
-let compileMs = 0;
-
-async function moduleOnce(url) {
- if (compiled) return compiled;
- const t0 = performance.now();
- compiled = await WebAssembly.compileStreaming(fetch(url));
- compileMs = performance.now() - t0;
-
- return compiled;
-}
-
-function guestTree() {
- return new PreopenDirectory("/src", new Map([
- ["go.mod", file(goMod)],
- ["models", new Directory(new Map([["pet.go", file(petSrc)]]))],
- ]));
-}
-
-async function run(url) {
- const module = await moduleOnce(url);
-
- let out = "", err = "";
- const fds = [
- new OpenFile(new File([])),
- ConsoleStdout.lineBuffered((l) => { out += l + "\n"; }),
- ConsoleStdout.lineBuffered((l) => { err += l + "\n"; }),
- guestTree(),
- ];
-
- const args = [
- "genspec-wasi.wasm", "-loader=own", "-stub-stdlib",
- "-goos", "linux", "-goarch", "amd64",
- "-workdir", "/src", "./...",
- ];
- const wasi = new WASI(args, [], fds);
-
- // The shim spins inside poll_oneoff for the whole requested delay. Measure it: if Go asks for real
- // sleeps this is dead wall-clock, and it is the one thing that would rule this shim out.
- let pollCalls = 0, pollMs = 0;
- const imports = wasi.wasiImport;
- const rawPoll = imports.poll_oneoff.bind(imports);
- imports.poll_oneoff = (...a) => {
- const t = performance.now();
- const r = rawPoll(...a);
- pollMs += performance.now() - t;
- pollCalls++;
-
- return r;
- };
-
- const t1 = performance.now();
- const instance = await WebAssembly.instantiate(module, { wasi_snapshot_preview1: imports });
- const instantiateMs = performance.now() - t1;
-
- const t2 = performance.now();
- const code = wasi.start(instance);
- const runMs = performance.now() - t2;
-
- return { out, err, code, compileMs, instantiateMs, runMs, pollCalls, pollMs };
-}
-
-self.onmessage = async (e) => {
- try {
- self.postMessage({ ok: true, ...(await run(e.data.url)) });
- } catch (ex) {
- self.postMessage({ ok: false, error: String(ex && ex.stack || ex) });
- }
-};