audit-manifest — item 13 tested a string with `Number.isFinite`, which never
coerces, so the branch was always false and "does the probe accept the next
patch/minor" could not fail. Verified end to end with a probe of `^6\.`: the
audit now reports "accepts 6.7.1 (the next patch)" and "accepts 6.8.0".
rea-shim — a zero-argument start printed usage and exited 0, while both launch
paths still carried an unreachable `argv.length > 0 ? argv : ["mcp"]` fallback.
With `failOnStartupError: false`, that exit 0 leaves REA silently absent from
every tool list. The two cases are now told apart by whether a terminal is
attached: usage + 0 for a person, usage + stderr + EX_USAGE(64) for a harness
that lost its arguments. Both dead fallbacks removed.
setup.mjs — three:
* provisioning cleared the destination BEFORE extracting, so a missing
extractor or a full disk destroyed a working install and replaced it with
nothing; the tree is cleared only for the rename now.
* a `.part` holding the whole body could not be rescued when the manifest
declared no size — the Range request got 416 on every retry. A pinned
sha256 is now enough evidence to finish the rename.
* `setup.lock` was written only when a run ENDED, so it recorded an outcome
and excluded nothing: the shim's detached run and a hand-run setup could
write the same `.part` and the same staging directory. It is claimed with
O_EXCL before the first byte moves; a live holder is reported with its pid
and log path and the run exits 75, while a dead pid or a `failed` lock is
taken over.
check-updates.ps1 — a missing `application.version` line was reported as a
version mismatch ("requires 12.1.x, currently "), sending the reader after a
newer Ghidra instead of a reinstall.
rea-bundle-bootstrap.ps1 — the engines check compared only major/minor, so
`^24.11.1` accepted 24.11.0; all three components compare now.
rea-shim PATH dedupe — Windows compared path spellings case-sensitively and
could prepend the vendored Node twice.
cordis.patch.yml — the home fallback skipped HOMEDRIVE+HOMEPATH, so a service
account resolved `/.dsh/...`. It now mirrors `os.homedir()`: USERPROFILE →
HOMEDRIVE+HOMEPATH → HOME. Both READMEs follow, because the audit compares the
documented expression with the shipped one.
setup.mjs header — the TOFU sentence promised "enforced afterwards" while an
`unverified` checksum is trusted on a cache hit. The comment now states what the
code does and names the gap in `bundle.json` instead of implying verification.
Tests 85 -> 88: extraction order, the audit's item-13 branch, and a live lock
blocking a second run; the bare-invocation case asserts EX_USAGE plus the
`--shim-help` path that must stay 0.
The setup step is mandatory but invisible: a market install or update replaces
the package and stops there, and a skipped setup fails silently in both
directions - no tools on a first install, the old REA after an upgrade. The
guide names the actual state instead of asking whether it was shown before.
- bin/rea-guide.mjs: computeGuideState compares the REA version recorded in
plugins/rea-bundle/bundle.json with the manifest pin and reports
missing / stale / unknown / unreadable / ok. Same pin rules as
scripts/pin-match.ps1 (dotted pin exact, bare major as a family), and it
reads UTF-16 records the way the shim and setup.mjs now do.
- index.mjs: serves GET /dsh-plugin-rea/guide-state, mounted only through
ctx.inject(['webServer']), so headless and ACP hosts load the plugin exactly
as before. Read-only: no provisioning, no spawning, no writes.
- client.js: shell.overlay card shown once per plugin version, with the exact
command for this install; "Later" and "I ran it" record the version in
localStorage, so the next upgrade re-arms it and the same version stays quiet.
- cordis.patch.yml: one extra row for the package entry only (no tools, no REA).
- 5 new tests (78 total); verify-branch section 9 checks all four ends of the
chain, so a rename cannot make the guide silently never appear.
Three defects made the published flow impossible to complete:
* The !!js scalar in the MCP row was unquoted, and a bare scalar containing
"? " and ": " is read by YAML as an explicit key/value pair. The loader
therefore received a mapping, so args[0] was {"[object Object]": "..."} and
the row could never start. Reproduced against the harness's own dialect
(JSON_SCHEMA plus the !!js type definition read out of app.asar).
* The DSH_HOME fallback stopped one level above <home>/.dsh, while the shim,
setup.mjs and bootstrap.ps1 all resolve to <home>/.dsh. DSH never sets
DSH_HOME — zero writes anywhere in the archive — so this was the default
path, not an edge case: the row pointed at a file provisioning never writes.
* The mirror step copied the shim and setup.mjs but not the manifest, and the
mirrored setup derives its plugin root from its own location. Background
provisioning therefore died on a bare ENOENT that stdio:"ignore" hid, while
the lock it had written suppressed retries for six hours.
Also fixed:
* The shim locates its bundle from its own position. MCP children have every
DSH_* name stripped (case-insensitively), so with a custom DSH_HOME the old
shim looked in ~/.dsh, reported "not provisioned" and started a second
~800 MB run there.
* setup records the real checkout in <bundle>/mirror.json, so a mirrored or
later-upgraded install finds the source instead of copying the bundle onto
itself and provisioning the previous manifest forever.
* Empty REA_BUNDLE_ROOT / DSH_HOME no longer degrade the root to "" or a
relative path, and --root/--cache are resolved (a relative root would be
recorded in bundle.json and make every component look deleted).
* fail() no longer recommends a mirrored copy that cannot work, and
check-updates.ps1 reports the migration state for old bundles.
Guarded by checks that were themselves reverse-tested — 4/4 deliberate
sabotages caught: unquoted !!js mapping detection, DSH_HOME agreement across
unset/empty/set, README-versus-patch drift, and mirror-list completeness. CI
now runs bundle:test and bundle:audit, and its YAML step registers the !!js
scalar constructor: safe_load had been rejecting the very row this repository
ships, so the workflow was permanently red.
Verified: audit clean; 13/13 unit tests on Windows and on Linux (WSL); 25/25
end-to-end path checks in a sandbox; and a full JSON-RPC handshake launched
from the row's own evaluated command line (rea 6.4.0, 170 tools) with DSH_HOME
absent from the child environment.
An external review of this branch found that the headline promise did not
actually work, and it was right. Each claim was checked before acting on it.
Fatal, now fixed:
- **The patch could never launch anything.** The JS string literal used
backslashes, and a single-quoted YAML scalar keeps them literal for JavaScript
to eat: '\plugins' is the BEL control character, '\rea-bundle' a carriage
return, '\bin' a backspace. Evaluated, the path was
"C:\...\.dshplugins<CR>ea-bundle<BS>in<CR>ea-shim.mjs". Forward slashes now,
with a DSH_HOME fallback so an unset variable no longer yields
"undefined/plugins/...".
- **Nothing created the file the patch points at.** `setup.mjs` contained no
copy of the shim anywhere, so provisioning could finish and the row would
still fail with MODULE_NOT_FOUND — and the documented "install the plugin
first and it provisions in the background" could never happen, because the
shim that would do that was never at the path the row launches. Setup now
mirrors bin/rea-shim.mjs and scripts/setup.mjs into the bundle root, before
the probe, which now tests the mirrored copy rather than the source tree.
- **`npm` was invoked from PATH.** The entire point of this branch is the
machine without Node, where PATH has no npm, so resolving the REA dependency
closure died with ENOENT on exactly the target machine. It now uses the npm
that ships inside the provisioned Node (verified: the portable Windows zip
puts it at <node>\node_modules\npm\bin\npm-cli.js and it runs).
- **`{exe}` was not substituted on POSIX.** The `resolved !== ""` guard in
swap() rejected the empty string, so `adb{exe}` stayed literal on Linux and
macOS and platform-tools failed its probe. Now replaced unconditionally, and
the test mirror matches. (The review also claimed the existing test suite
fails on ubuntu; it does not — the mirror derives EXE from the platform.)
Also from the review, verified and fixed:
- resolveProxy() is cached; it spawned reg.exe twice per download.
- sha256File() streams instead of reading the whole file, which for the Ghidra
archive meant a 570 MB buffer.
- The provisioning lock uses an exclusive create, so two rows starting together
cannot both provision.
- reaAgents' probe pointed at the pre-npm-install layout, reporting a healthy
vendored copy as unverifiable on every run.
- The node probe's regex accepted Node 20, which REA's engines reject.
- bootstrap.ps1 read its Node version and checksum from a hard-coded pair that
duplicated the manifest's pins; it now reads the manifest, refuses a TOFU
pin, and its ARM64 check no longer passes while downloading the x64 zip.
- check-updates.ps1 gained the bundle section bundle-design.md promised.
Two bugs I introduced while fixing the above, both caught by running things
rather than reading them:
- Editing check-updates.ps1 stripped its BOM. PowerShell 5.1 then read the file
as ANSI, mangling the CJK comments into parse errors. Restored, and the BOM
is now asserted. bootstrap.ps1 was ASCII-only so it still parsed, but it has
a BOM too now so a future Chinese comment cannot silently break it.
- The node probe's own-token fallback returned the vendor/node DIRECTORY, so
the probe spawned a folder and failed with ENOENT while the extraction was
fine — a self-inflicted replay of the bug class this commit fixes.
New: scripts/verify-branch.ps1, which checks syntax, PowerShell parse + BOM,
the manifest audit, the unit tests, that the patch is machine-path-free and
escape-safe, the published payload list, and a dry-run of the whole chain.
Verified end to end: with DSH_HOME set, provisioning reaches `setup complete`,
the file the patch computes exists, and launching it the way the row does
answers an MCP handshake as rea 6.4.0 with 170 tools.
Ship an all-in-one variant on its own branch: the published patch stays
machine-path-free and launches a shim that reads the per-machine state, so a
fresh DSH install gets working mcp__rea__* tools without hand-installing Node,
REA, a JDK or Ghidra.
- bin/rea-shim.mjs: resolves the bundle state, exports exactly the variables
REA reads, execs the vendored CLI. Starts detached provisioning on first run
and explains the remediation instead of breaking the harness.
- scripts/setup.mjs: staged, idempotent, resumable provisioning (detect,
resolve, fetch, verify, unpack, wire, probe) with a manifest of pinned
versions and checksums; --reuse adopts existing installs, --dry-run plans.
- scripts/rea-bundle-bootstrap.ps1: gets a Node-less Windows machine to a
provisioned bundle using only PowerShell, then hands over to setup.
- manifest/bundle.manifest.json: component pins with the checksum provenance
each one actually has (vendor-published vs measured-on-this-machine).
- docs/bundle-design.md: design, verified facts, and the upstream host gates
that make some capabilities impossible on Windows rather than unconfigured.
Verified end to end on Windows 11 x64: setup reaches 'setup complete', and an
MCP initialize handshake through the shim answers serverInfo rea 6.4.0 with no
REA on PATH.