37 KiB
M0 — Load-spike discovery log
Date: 2026-10-17. Branch: claude/project-thread-gnv9zf.
Worktree: /home/john/Projects/RE/opencode-rea/.worktrees/m0-spike (tracking origin/main @ e337e39).
Spec: docs/IMPLEMENTATION_PROMPT.md §6 M0, reconciled against §4 (Verified facts).
Spike 1 — plugin loads in the real CLI
Commands (all in the worktree):
$ bun install
bun install v1.4.2 (744846f84)
...
+ @opencode/[email protected]
+ @opencode/[email protected]
+ @opencode/[email protected]
+ @types/[email protected]
+ [email protected]
+ [email protected]
532 packages installed [3.66s]
$ bun run build
$ tsc -p tsconfig.build.json
(exit 0; emits dist/index.js)
Root index.js re-export present (verbatim):
export { default } from "./dist/index.js";
export * from "./dist/index.js";
$ bun test --path-ignore-patterns 'review/**' test/e2e/smoke.test.ts
bun test v1.4.2 (744846f84)
test/e2e/smoke.test.ts:
[live-model behaviour unverified] smoke proving built plugin loads in real CLI
[live-model behaviour unverified] smoke exit: 0
1 pass
0 fail
6 expect() calls
Ran 1 test across 1 file. [2.26s]
EXIT_CODE=0
The passing test asserts exit 0, SMOKE-READY on stdout, a non-empty
fixture request log, and the absence of LoadError / Plugin must export
in server stderr — i.e. the built plugin loads. Matches §4.2.
Spike 2 — rea setup in a throwaway HOME
Exact isolation env (nothing else overridden; PATH/etc. inherited):
SPIKE_HOME=$(mktemp -d /tmp/opencode-m0-spike-XXXXXX) # -> /tmp/opencode-m0-spike-qoa12s
mkdir -p "$SPIKE_HOME/home" "$SPIKE_HOME/config"
HOME="$SPIKE_HOME/home" XDG_CONFIG_HOME="$SPIKE_HOME/config" \
OPENCODE_CONFIG_DIR="$SPIKE_HOME/config" \
node_modules/.bin/rea setup --client opencode --yes
Result: exit 0. A re-run in the same env reports the opencode client
state: aligned (idempotent).
Written file — $SPIKE_HOME/config/opencode.json (note: rea setup
honoured OPENCODE_CONFIG_DIR, writing $SPIKE_HOME/config/opencode.json
rather than $HOME/.config/opencode/opencode.json):
{
"mcp": {
"rea": {
"type": "local",
"command": [
"/home/john/Projects/RE/opencode-rea/.worktrees/m0-spike/node_modules/.bin/rea",
"mcp"
],
"enabled": true
}
}
}
This is exactly the shape §4.3 specifies
("mcp": { "rea": { "type": "local", "command": [..., "mcp"], "enabled": true } }).
Skill side-effect (confirms §4.3 second sentence): the setup also installed
reverse-engineer-anything into the isolated home:
$ ls "$SPIKE_HOME/home/.agents/skills/"
reverse-engineer-anything
Real-user-config safety: the user's real ~/.config/opencode/opencode.json
was untouched (mtime still Oct 15 20:34, two days before this spike).
Toolchain versions
$ node_modules/.bin/opencode --version
opencode v2.0.26 # matches §3 pin exactly
$ node --version
v26.10.0 # see discrepancy D1 below
$ node_modules/.bin/rea --version
6.5.0 # matches §4.5 rea-agents pin
$ bun --version
1.4.2
Discrepancies vs Section 4 (reconciled, none blocking)
- D1 — Node major differs from §3. §3 says
node --versionshould printv22.x (>=22.19). This machine hasv26.10.0. Reconciliation: §4.4 states REA needs^22.19 || ^24.11 || >=26; v26.10.0 satisfies that range, andrea doctor --client opencode --json(run with the real home, informational) reports"name":"node","ok":true,"classification":"healthy","detail":"26.10.0". Both spikes pass on v26, so M0 is green. re-box (M4) still pins Node 22 per the Dockerfile spec — this observation does not change that. - D2 — Local Ghidra is 12.1.2, not the §4.4 pinned 12.1.4.
rea doctorreportsghidra-version: 12.1.2; compatible 12.1.x release (verified build 12.1.4)at/home/john/.local/ghidra. Informational only: M0 requires no engines, and M5 pinsGhidra_12.1.4_buildfor re-box regardless. - D3 — Engine auto-detection is NOT HOME-isolated. Even under the throwaway
HOME, therea setupprovider inspection referenced the real/home/john/.local/ghidra(absolute well-known-path probing), and the inheritedJAVA_HOME(an Android Studio flatpak JBR) leaked into theghidra-javacheck, which doctor flagsmissing_dependency(JBR is not a full JDK 21+). Isolation covered config writes (proven by the untouched realopencode.json) but not engine search paths. Lesson for re-box/M5: engine paths must be set explicitly via absoluteREA_*env vars (which is already the plan inconfig/env.defaults); never rely on HOME-relative discovery. No spec change needed — recorded here. - D4 —
rea doctorhealthy=false on this dev machine. Causes: missing JADX/IDA config (expected — caller-supplied per §4.4), the JBR JAVA_HOME (D3), andskill:identitymissing in the real home. All out of M0 scope; none contradicts §4.
API lookups needed: none. M0 required no @opencode/plugin type lookups;
no new code was written.
M0 acceptance verdict
opencode --versionprintsopencode v2.0.26.- Smoke test prints
1 pass, exit code 0. - Throwaway-HOME
opencode.jsoncontains the exactmcp.realocal-command block. - Both observations written here in
docs/NOTES.md; discrepancies D1–D4 reconciled against §4 above (D1 against the §4.4 Node range + doctor evidence). No stop-and-reconcile blocker: nothing contradicts §4.
M1 — Bootstrap runner discovery log
Date: 2026-10-17/18 (UTC 2026-10-18 ~00:0x). Branch: claude/project-thread-gnv9zf.
Worktree: /home/john/Projects/RE/opencode-rea/.worktrees/m0-spike.
Spec: docs/IMPLEMENTATION_PROMPT.md §6 M1.
M1.1 — src/bootstrap.ts port
Ported from unity.gamedev.kit/src/bootstrap.ts (read in full) + src/v2.ts
(tool-registration shape, reference only — M1 adds no tool yet) +
src/shared.ts (SKILL_PACKS shape reference; REA packs defined fresh, no
Unity packs copied). Only node:child_process (spawn, spawnSync),
no node:fs/node:path use needed (void cwd keeps the reaBootstrapSteps
signature honest without fake filesystem probes). No new runtime dependencies
(package.json untouched; @opencode/plugin stays peer+dev).
Exact spec signatures implemented verbatim (src/bootstrap.ts):
BootstrapOptions { installSkills?; installMemory?; installWorkflows? }StepResult { label; status: "ok"|"failed"|"skipped"; detail }BootstrapStep { label, command, args, skip? }(skip returns reason or undefined)reaBootstrapSteps(cwd, o?),runCommand(command, args, {cwd, signal?, timeoutMs?}),runBootstrap(cwd, o?, {signal?, onStep?}),formatResults(results)
Step labels in exact order (asserted in test):
Skill pack p4nda0s/reverse-skills—npx -y skills@latest add p4nda0s/reverse-skills --agent opencode --yesSkill pack ljagiello/ctf-skills— same shapeSkill pack cyberkaida/reverse-engineering-assistant— same shapeSkill pack rehan-remade/universal-modder— same shapeOperator Helper CLI—npm install --global @aerovato/operator-helper@latestOperator Helper for OpenCode V2—operator-helper install opencode-v2OpenCode Workflows plugin—opencode plugin add @rphang/opencode-workflowswith askipguard (workflowsSkipReason(), exported): probesopencode --versionviaspawnSync(15 s timeout, Windows shell parity); returns a skip reason when the CLI is missing/unparseable (fail closed — no doomed install). A parseable version proceeds; sandbox-level incompatibility then surfaces as a step failure (see M1.3).
runCommand parity with the Unity kit: no TTY (stdio: ignore/pipe/pipe),
CI=1 NO_COLOR=1 FORCE_COLOR=0, Windows .cmd shim quoting + shell: true,
300 s default timeout, output tailed to ~1500 bytes (buffer capped at
TAIL*4 during collection). runBootstrap checks signal.throwIfAborted()
per step, honors skip, wires onStep(label, i, total), records
$ cmd args + tail in detail, and rethrows on abort. formatResults
renders [OK]/[FAILED]/[SKIPPED] summary lines plus a Failure output:
section (failures only; skipped reasons stay on the summary line).
M1.2 — unit tests
test/unit/bootstrap.test.ts (9 tests), run as:
$ bun test --path-ignore-patterns 'review/**' test/unit/bootstrap.test.ts
bun test v1.4.2 (744846f84)
9 pass
0 fail
32 expect() calls
Ran 9 tests across 1 file. [167.00ms]
Covers the three acceptance assertions (exact labels in order;
installSkills:false drops exactly the four skill steps; formatResults
renders [OK]/[FAILED]/[SKIPPED]) plus: exact pack commands, the
workflows skip presence, installMemory:false/installWorkflows:false
drops, failure-append vs clean-summary formatResults shapes, runCommand
exit-code/output capture and tail bound, and runBootstrap
all-disabled → [] (no network touched — the only runCommand tests use
local echo/node -e).
$ bun run check # tsc --noEmit -p tsconfig.json → exit 0
$ bun run build # tsc -p tsconfig.build.json → exit 0 (dist/index.js emitted)
M1.3 — workflows-load fact (Not-verified item 1): FAILS on 2.0.32
Question: does @rphang/opencode-workflows (upstream pinned to opencode
2.0.15) load and run under @opencode/[email protected]? Answer: NO — it fails
to load on 2.0.32; it loads on its pinned 2.0.15. All scratch work in
/tmp/opencode/wf-check (2.0.32) and /tmp/opencode/wf-2015 (2.0.15
control); the repo and the real ~/.config/opencode/opencode.json were
never touched (grep -c rphang ~/.config/opencode/opencode.json → 0).
Registry facts:
$ npm view @rphang/opencode-workflows version dist-tags
version = '0.2.0' # latest; only releases are 0.1.0, 0.2.0 — no newer fix exists
$ npm view @rphang/opencode-workflows dependencies peerDependencies engines
dependencies = { ajv, acorn, effect: '4.0.0-rc.112', '@opencode/codemode': '2.0.15' }
peerDependencies = { '@opencode/plugin': '2.0.15' }
engines = { opencode: '2.0.15' }
Install step (the exact M1 bootstrap command) works on both CLIs, fully
isolated (HOME/XDG_*/OPENCODE_CONFIG_DIR under the scratch dir):
$ ./node_modules/.bin/opencode --version
opencode v2.0.32
$ ./node_modules/.bin/opencode plugin add @rphang/opencode-workflows
Plugin "@rphang/opencode-workflows" installed and added to /tmp/opencode/wf-check/config/opencode.json
EXIT=0
(The package lands in <cache>/opencode/npm/@rphang/opencode-workflows@latest/<ts>/node_modules/….)
Load probe: minimal loopback OpenAI-compatible fixture (fixture.mjs,
answers title probes + one text line) + opencode run --standalone --format json --auto --print-logs --model fixture-test/fixture-model.
2.0.32 result — plugin REFUSED by the host sandbox:
timestamp=... level=WARN ... message="failed to load plugin" target=@rphang/opencode-workflows
cause="Cause([Die(Error: Cannot load \"effect/dist/unstable/http/index.js\"
from plugin node_modules: \"effect/dist/unstable/http/index.js\" is not
provided by OpenCode; plugins must use the host's \"effect\" and
\"@opencode/plugin\" modules.)])" role=server
2.0.15 control — plugin LOADS (same probe shape, provider block rewritten
to the 2.0.15 provider: schema after a configuration normalization diagnostic … kind=invalid told us providers:+settings: is rejected there):
$ grep -c "failed to load plugin" run-stderr2.log
0
$ grep -c "WORKFLOWS-LOAD-PROBE-OK" run-stdout2.jsonl
11
(EXIT=1 on both runs is a fixture artifact, not a product signal: the
minimal fixture omits finish_reason on the terminal stream chunk, so the
host reports provider.invalid-output … stream ended without finish_reason
after delivering the model text. The repo's test/support/model-fixture.ts
emits the finish chunk correctly; the scratch fixture was throwaway.)
Root-cause chain (all paths inside the scratch cache):
@rphang/[email protected]/dist/index.js→ bundled@opencode/[email protected]→dist/openapi/{runtime,types,index}.jscontainimport { HttpClient[,…] } from "effect/unstable/http".- The 2.0.32 plugin sandbox does not provide that
effectsubpath, so the plugin dies at load. The 2.0.15 host provides it (pinned peer), so it loads.
Discrepancy vs Section 4 (new)
- D5 — Not-verified item 1 resolves NEGATIVE.
@rphang/opencode-workflowsdoes not load under@opencode/[email protected](sandbox rejects its bundledeffect/dist/unstable/httpvia@opencode/[email protected]; no newer plugin release exists). Per §6 M1 / §7.6: fall back to REA's own subagent delegation for bounded fan-out; do not depend on (or block the build on) opencode-workflows. TheOpenCode Workflows pluginbootstrap step stays (guarded by its version-probeskip), and M2 must treat a workflows-install failure as non-fatal. Nothing else in §4 changes.
M1 acceptance verdict
test/unit/bootstrap.test.tsasserts exact labels in order,installSkills:falsedrops the four skill steps,formatResultsrenders[OK]/[FAILED]/[SKIPPED].bun test --path-ignore-patterns 'review/**' test/unit/bootstrap.test.ts→ all pass (9/9).- Workflows-load fact verified in
/tmpscratch and recorded here (D5: fails on 2.0.32, loads on pinned 2.0.15; fallback = REA subagent delegation, build not blocked).
M2 — rea_bootstrap_stack tool + /rea-bootstrap command discovery log
Date: 2026-10-18 (UTC). Branch: claude/project-thread-gnv9zf.
Worktree: /home/john/Projects/RE/opencode-rea/.worktrees/m0-spike.
Spec: docs/IMPLEMENTATION_PROMPT.md §6 M2 + §4.1 (tool transform editor
shapes, permission assert), §7, §5 layout. CLI pin stays 2.0.26 (no bump).
M2.1 — ctx.permission.assert availability probe (new discrepancy D6)
§4.1/§6-M2 say to call ctx.permission.assert(...) before running, citing
unity.gamedev.kit/src/v2.ts. But @opencode/[email protected] types declare no
assert: dist/promise/permission.d.ts is
Pick<PermissionApi, "list" | "get" | "reply"> & { hook }, and the bundled
@opencode/[email protected] generated permission API has only
request/saved/create/list/get/reply — no assert anywhere in the package.
The Unity kit targets @opencode/[email protected], so the reference may postdate
our pin. Verified empirically with a scratch probe plugin (tool returning
typeof ctx.permission.assert + Object.keys(ctx.permission), driven by the
real CLI + fixture model, scratch deleted afterwards):
- 2.0.26 (
node_modules/.bin/opencode,opencode v2.0.26):{"typeofAssert":"undefined","permissionKeys":["hook","list","get","reply"]} - 2.0.32 (
/tmp/opencode/wf-check/node_modules/.bin/opencode,opencode v2.0.32):{"typeofAssert":"function","permissionKeys":["hook","list","get","reply","assert"]}
Discrepancy vs Section 4 (new)
- D6 —
permission.assertdoes not exist on the pinned 2.0.26 host; it was added by 2.0.32. Calling it unconditionally would throwTypeError: not a functionat runtime on our pin. Design (fail-closed per Agency rules):src/bootstrap-tool.tsreadPermissionAssert()calls the host capability when present and otherwise throws a descriptive error refusing to run installs without approval. Nothing proceeds unapproved on any host. re-box (2.0.32) gates normally. Build not blocked.
M2.2 — implementation
File placement: new src/bootstrap-tool.ts (thin; reuses the M1 runner —
no node:fs/node:path, no new runtime deps, no ctx.mcp.transform).
src/index.ts setup() registers it after readiness; src/commands.ts
gains /rea-bootstrap; src/readiness.ts + src/bootstrap.ts adjusted
(see below). Exports added to src/index.ts for tests/docs-guard.
- Tool registration mirrors
gamedev_bootstrap_stack:editor.add({ name: "rea_bootstrap_stack", input: { installSkills?, installMemory?, installWorkflows? } booleans, additionalProperties: false, options: { codemode: false } }).executecallsreadPermissionAssertwith{ sessionID, agent, source: { type: "tool", messageID, id }, action: "rea_bootstrap_stack", resources: [directory], save: ["*"] }beforerunBootstrap(...)withonStepwired tocontext.progress, thenawait ctx.skill.reload(). Returns{ content, metadata: { results } }. - D5 (workflows failure non-fatal):
runBootstrapalready records step failures instead of throwing; the tool additionally appends an explicit fallback note viawithWorkflowsFallbackNote()when the workflows step is notok("bounded fan-out falls back to REA's own subagent delegation"). New shared constWORKFLOWS_STEP_LABELinsrc/bootstrap.ts(value unchanged, M1 unit assertions untouched). - New-tool fallout in
src/readiness.ts: the plugin's ownrea_bootstrap_stackmatches therea_prefix, so without an exclusion it would inflate the MCP-onlyreaToolCount(T08/T10 hermetic0broke: got- and a global
codemodeoption would clobber its direct mode. AddedPLUGIN_OWN_TOOL_NAMES = { rea_readiness, rea_bootstrap_stack }skipping it incountReaToolsByModeand in theapplyReaToolCatalogPolicycodemode loop.test/unit/readiness.test.tsextended with a bootstrap entry (counts unchanged).
- and a global
/rea-bootstrap(COMMAND_IDS+ definition): prompt tells the agent to callrea_bootstrap_stack(passing requested flags), thenrea_readiness, and report per-step results + readiness summary + the workflows-fallback note when applicable. No agent switch.test/unit/commands.test.tsandtest/integration/commands.test.tsexact lists updated to the four commands;test/unit/options.test.tssetup stub gains the host-guaranteedlocation(unity kit relies onctx.location.directorytoo).- Docs:
README.mdgains a/rea-bootstraprow ("three" → "four") and a "Bootstrap tool:rea_bootstrap_stack" section (flags, permission gate + D6 host note, workflows non-fatal + D5 note,codemode: false,{ content, metadata: { results } },ctx.skill.reload()).test/unit/docs.test.tsextended:"rea-bootstrap"in the command-id loop + a dedicated test asserting README tokens andsrc/bootstrap-tool.tsmarkers (permission.assert,ctx.skill.reload,runBootstrap,codemode).
M2.3 — live gating evidence (real hosts, built plugin, scratch tests deleted)
Zero-step calls (installSkills/installMemory/installWorkflows: false, so no
command ever runs) driven by the real CLI + fixture model:
- 2.0.26 +
--auto:tool_usestatus: "error",error: "rea_bootstrap_stack: host permission.assert is unavailable — refusing to run installs without approval. ..."— fail-closed, exit 0. - 2.0.32 +
--auto:tool_usestatus: "completed",metadata: { results: [], ... },output: ""— hostassertapproved the call end to end. - 2.0.32 without
--auto:status: "completed"as well (standalone-run host policy auto-resolves the programmatic assert; the user-facing denial path — assert rejects → execute rejects before any install — is proven deterministically intest/integration/bootstrap.test.tsvia an injected rejecting assert, plus the D6 fail-closed path via an assert-less host). Recorded as observed; host approval policy is not this plugin's logic.
M2.4 — test evidence
$ bun run check # tsc --noEmit -p tsconfig.json → exit 0
$ bun run build # tsc -p tsconfig.build.json → exit 0
$ bun test --path-ignore-patterns 'review/**' test/unit test/integration
126 pass, 0 fail, 2737 expect() calls, 22 files
Includes new test/integration/bootstrap.test.ts (6 tests: tool listed
after setup() with codemode: false + closed boolean schema; denial
rejects with exact assert shape { action: "rea_bootstrap_stack", resources: [directory], save: ["*"] } and no reload; approved all-false run
returns metadata.results: [] + one skill.reload(); assert-less host
refuses via /permission\.assert/; D5 note only on non-ok workflows;
COMMAND_IDS contains rea-bootstrap, no ":", command prompt names both
tools).
M2 acceptance verdict
test/integration/bootstrap.test.ts: aftersetup(),ctx.tool.list()includesrea_bootstrap_stack; calling it without approval is rejected (permission asserted with the exact spec shape);COMMAND_IDSincludesrea-bootstrapand contains no":".bun run checkpasses.bun test --path-ignore-patterns 'review/**' test/unit test/integration→ all pass (126/126).docs.test.ts(extended) confirms README documents the new tool and command (fails without the README section).- Workflows failure handled non-fatal (D5 note helper + unit-covered;
step failures never throw out of
runBootstrap).
M3 — Agent-prompt upgrades discovery log
Date: 2026-10-18 (UTC). Branch: claude/project-thread-gnv9zf.
Worktree: /home/john/Projects/RE/opencode-rea/.worktrees/m0-spike.
Spec: docs/IMPLEMENTATION_PROMPT.md §6 M3 + §4.4b (WASM routing rule) + §8.
M3.1 — agents/rea.md edits (prompt only, no code)
Added three short sections after ## Reporting, consistent with the
existing observations/inferences/unknowns discipline (no contradiction):
## Checkpoint/resume— before a long/branching step, write a one-line resume capsule (target identity, open Evidence IDs, question in flight) so a cold session can continue; capsule is a pointer, not evidence.## Challenger branch— when the leading hypothesis is weakly supported, spin one bounded alternate (one question, one entry point, disconfirming evidence type) before committing; record both in the ledger.## WASM routing—open_binaryrefuses WASM (wasm_requires_static_inspection); route.wasmtoinspect_wasm_artifact(orrea inspect-wasm-artifact <path> --format json), static only, WABT 1.0.42 viaREA_WABT_BIN_DIRECTORY. Source:rea/docs/wasm-artifacts.md:7-11.
Borrowed Goblin-D.M.T. discipline ideas only; no dependency added.
Frontmatter description untouched; no src/ changes, so
permission/effects behaviour is unchanged by construction. Body avoids the
literal description: token (keeps the no-frontmatter-leakage test green).
M3.2 — test evidence
$ bun test --path-ignore-patterns 'review/**' test/unit/agents.test.ts
30 pass, 0 fail, 406 expect() calls
$ bun run check # tsc --noEmit -p tsconfig.json → exit 0
$ bun test --path-ignore-patterns 'review/**' test/unit/docs.test.ts
6 pass, 0 fail
M3 acceptance verdict
agents.test.tsstill passes (file parses, non-emptydescriptionfrontmatter + body).rea.mdgains checkpoint/resume + challenger-branch sections and the WASM routing rule.- Permission/effects tests unchanged (no
src/edits).
M3b — Target-integrity edit guard discovery log
Date: 2026-10-18 (UTC). Branch: claude/project-thread-gnv9zf.
Worktree: /home/john/Projects/RE/opencode-rea/.worktrees/m0-spike.
Spec: docs/IMPLEMENTATION_PROMPT.md §6 M3b (exact deny list) + §7 + §8.
M3b.1 — implementation (no second hook, effect-gating untouched)
src/options.ts: exposedprotectTargets?: boolean(defaulttrue).KNOWN_KEYSgains"protectTargets"; non-boolean fails withinvalid plugin option "protectTargets";parseOptions(undefined|null|{})returnsprotectTargets: true. Guard checksoptions.protectTargets !== false, soundefined(older callers) stays enabled — fail closed.src/agents.ts: addedgetProtectedTargetReason()/isProtectedTargetPath()+findProtectedTargetResource()(checks bothevent.resourceandevent.resources[]).evaluateAgentPermission()runs the guard first — for every agent, before any effect-gating logic — denyingaction === "edit"on a protected target withPermission denied: edit of "<path>" is blocked by target-integrity guard (<reason>).protectTargets: falseskips only the guard. Existing specialist (rea-analyst/rea-auditor/rea-runtime) andruntimegating untouched.- Single hook preserved:
registerAgents()still callsctx.permission.hook("evaluate", …)exactly once (asserted in test). Removed theif (!isKnownAgentId(event.agent)) returnearly-return so the guard runs for unknown agents too; thectx.agent.gettail-override lookup stays known-agent-only.src/index.tsre-exports the two helpers.
Deny coverage (case-sensitive dirs, case-insensitive extensions/names):
targets/|fixtures/|captures/|ghidra_projects/|.rea/as any path segment; any segment ending.rep; basename .rea.backup(contains.rea.backup), .bundle.json, .envexcept exact.env.example, .(gpr|idb|i64|hop|rzdb|bndb|apk|ipa|asar|pcap|pcapng|core|dmp|wasm)$, .(pem|key|pfx|p12|keystore|jks)$, id_rsa*/id_ed25519*` prefix.
M3b.2 — test evidence
$ bun run check # tsc --noEmit -p tsconfig.json → exit 0
$ bun test --path-ignore-patterns 'review/**' test/unit/guard.test.ts
48 pass, 0 fail
$ bun test --path-ignore-patterns 'review/**' test/unit test/integration
174 pass, 0 fail, 2998 expect() calls, 23 files
test/unit/guard.test.ts (48 tests): per-path deny table (each dir, each of
the 14 binary/capture extensions, *.rep/, *.rea.backup*, *.bundle.json,
.env*, each secret extension, each key prefix) via evaluateAgentPermission
with agent: "rea"; singular-resource + unknown-agent fail-closed rows;
non-edit action untouched; allows for src/x.ts,
openspec/changes/a/proposal.md, .env.example; protectTargets:false allows
every denied path; parseOptions default/validation; single-hook assertion
plus live hook-handler deny for an unknown agent.
test/unit/options.test.ts updated for the new protectTargets: true default.
M3b acceptance verdict
- Guard table covers each deny case + the three allows;
protectTargets:falseallows all. bun run checkandbun test --path-ignore-patterns 'review/**' test/unit test/integrationpass.
M3c — license discovery (third-party skill packs)
All paths below are read-only references under /home/john/Projects/RE/review/;
nothing was edited there. Commands used:
$ for d in reverse-skills ctf-skills reverse-engineering-assistant universal-modder reverse-skill Anthropic-Cybersecurity-Skills; do echo "-- $d"; git -C /home/john/Projects/RE/review/$d log -1 --format=%H; git -C /home/john/Projects/RE/review/$d remote get-url origin; done
$ find <each pack> -maxdepth 2 -iname 'LICENSE*'; find review/reverse-engineering-assistant -maxdepth 3 -iname 'NOTICE*'
$ head -5 <each LICENSE>; diff -q review/reverse-engineering-assistant/LICENSE review/reverse-engineering-assistant/ReVa/LICENSE
On-demand packs (M1 REA_SKILL_PACKS, installed via npx skills add)
| Pack dir | LICENSE path | First 5 lines | Commit SHA | Remote |
|---|---|---|---|---|
review/reverse-skills |
(none — no LICENSE file in repo); license declared as MIT in README.md § ## 许可证 and README_EN.md § ## License |
npx skills remove rev-symbol rev-struct / ``` / (blank) / ## 许可证 / (blank) + MIT |
a2baa31c58a3567977188414da68c8c842057152 |
https://github.com/p4nda0s/reverse-skills |
review/ctf-skills |
LICENSE |
MIT License / (blank) / Copyright (c) 2026 Lukasz Jagiello / (blank) / Permission is hereby granted, free of charge, to any person obtaining a copy |
3bb6bad1a06664840d07b6e1e69bf19e6ca4672d |
https://github.com/ljagiello/ctf-skills |
review/reverse-engineering-assistant |
LICENSE and ReVa/LICENSE (byte-identical, diff -q → SAME) |
Apache License / Version 2.0, January 2004 / http://www.apache.org/licenses/ / (blank) / TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
a05a6b0a18649efd6d74ec8447bdadf8d468c365 |
https://github.com/cyberkaida/reverse-engineering-assistant |
review/universal-modder |
LICENSE |
MIT License / (blank) / Copyright (c) 2026 Rehan and universal-modder contributors / (blank) / Permission is hereby granted, free of charge, to any person obtaining a copy |
b8675c10d7091d7e4e00a2e40a15c601040b69a8 |
https://github.com/rehan-remade/universal-modder |
NOTICE check (Apache-2.0 ReVa)
find review/reverse-engineering-assistant -maxdepth 3 -iname 'NOTICE*' →
no output: no NOTICE file exists upstream, so there is nothing to copy.
Recorded in THIRD_PARTY_NOTICES.md with the instruction to copy it if one
appears later.
Deliberately NOT vendored
review/reverse-skill(zhaoxuya520/reverse-skill, MIT, 89 skills,cab634bd855fc287f6e420c1f36fd1a6b9245960) andreview/Anthropic-Cybersecurity-Skills(mukul975/Anthropic-Cybersecurity-Skills, Apache-2.0, 818 skills,54a798831d2266a3ca61ce68a7acb80b81160d57): on demand vianpx skills addonly, never vendored.review/reverse-skill/CTF-Sandbox-Orchestrator/LICENSEfirst 5 lines:GNU GENERAL PUBLIC LICENSE/Version 3, 29 June 2007/ (blank) /Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>/Everyone is permitted to copy and distribute verbatim copies→ GPLv3: NEVER copy (Gotcha 18).- IDA/Hopper: licensed, mount-or-host via
REA_IDA_MCP_CONFIG/HOPPER_LAUNCHER_PATH; not baked (Gotcha 11).
Thin-plugin verification (Decision 1)
$ ls skills/ # → rea-evidence-report rea-investigation rea-opencode rea-reconstruction (only the 4 rea-* skills)
$ grep -ril 'reverse-engineer-anything' skills/ # → only rea-opencode/SKILL.md mentions it, as a pointer ("use that skill for target…"), not a shadow registration
$ grep -n SKILL_IDS src/skills.ts # → 4 ids, unchanged; no SKILL_IDS edit in this stage
No skill files vendored into skills/ in this stage.
Packaging note (package.json files list)
package.json "files" is ["dist", "skills", "agents", "index.js"] — the
new root THIRD_PARTY_NOTICES.md is not in the published tarball file
list. Per the stage spec it stays at the repo root (as §5/§8 require); the
publish list is left untouched so docs.test.ts (README/package identity)
and the release footprint do not change. If attribution must ship inside the
npm tarball, add "THIRD_PARTY_NOTICES.md" to files in a later stage and
re-run the docs guard.
M8 — validation pass (2026-10-18 UTC)
Branch: claude/project-thread-gnv9zf (6 commits ahead of origin/main:
05f1eca M0 … 0b729f4 M3c). Worktree:
/home/john/Projects/RE/opencode-rea/.worktrees/m0-spike (clean —
git status --short empty at validation time). Spec:
docs/IMPLEMENTATION_PROMPT.md §6 M8 + §8 Definition of done (opencode-rea
items). No code changes needed this milestone: every gate below passed on
the as-built tree, so this entry is the only change.
Gates (exact commands + observed output)
$ bun run check # tsc --noEmit -p tsconfig.json
$ tsc --noEmit -p tsconfig.json
(exit 0, no output)
$ bun test --path-ignore-patterns 'review/**' test/unit test/integration
174 pass
0 fail
2998 expect() calls
Ran 174 tests across 23 files. [22.77s]
(exit 0)
$ bun test --path-ignore-patterns 'review/**' test/e2e/smoke.test.ts
[live-model behaviour unverified] smoke exit: 0
1 pass
0 fail
6 expect() calls
Ran 1 test across 1 file. [2.05s]
(exit 0)
§8 Definition-of-done check (opencode-rea items)
- M0 spikes re-run, observations in NOTES: yes (
docs(m0)commit05f1eca, §M0 entry above matches §4:opencode v2.0.26, smoke1 pass,opencode.jsonmcp.reablock). src/bootstrap.tsported, unit tests pass, workflows-load fact recorded: yes (M1 entry;test/unit/bootstrap.test.tsgreen inside the 174).rea_bootstrap_stack+/rea-bootstrapregistered, permission-gated, integration + docs tests pass: yes (M2 entry;test/integration/bootstrap.test.tsdocs.test.tsgreen inside the 174).
reaprompt gains checkpoint/resume + challenger-branch + WASM routing, agent tests still pass: verified live this milestone —agents/rea.md:49 ## Checkpoint/resume,:59 ## Challenger branch,:68 ## WASM routing(routes.wasmtoinspect_wasm_artifact, static only, WABT 1.0.42);test/unit/agents.test.tsgreen inside the 174.- Target-integrity edit guard +
protectTargets+ guard table: yes (M3b entry;test/unit/guard.test.tsgreen inside the 174;protectTargets:truedefault). THIRD_PARTY_NOTICES.mdlists every ported set with license + commit: yes (M3c commit0b729f4; 4 on-demand packs, GPLCTF-Sandbox-Orchestratorexplicitly excluded, no NOTICE file upstream for ReVa).docs/NOTES.mdexists with every discovery/discrepancy: this file.- No PR template in this repo (
find: no hits outsidenode_modules), so the fixed §6 body format is used verbatim for the PR. - Gotcha re-check: no
ctx.mcp.transformin plugin setup, noreview/edits, noeffect/@opencode/pluginruntime deps, opencode-rea pin still 2.0.26 (only re-box moves to 2.0.32) — all hold;bun run check+ the 174 would fail otherwise.
No blockers. 3x-fail budget untouched (0 failures this milestone).
Review-fix (opencode-rea PR #5): zero-step approval, skip reasons, workflows guard
Date: 2026-10-18 (UTC). Branch: claude/project-thread-gnv9zf (@ 1735ac1,
in sync with origin). Worktree clean at start (git status --short empty).
All three review findings verified against current code and fixed minimally —
no new runtime deps, no ctx.mcp.transform, no review/ edits.
(a) Zero-step calls rejected on hosts without permission.assert — VALID, fixed
Evidence: readPermissionAssert threw before runBootstrap for every call,
and the pinned host lacks the capability —
node_modules/.bin/opencode --version → opencode v2.0.26;
grep -c assert node_modules/@opencode/plugin/dist/promise/permission.d.ts → 0
(the type is Pick<PermissionApi, "list"|"get"|"reply"> & { hook }; assert
exists only on 2.0.32 per D6). A zero-step call
(installSkills/installMemory/installWorkflows: false) performs no installs —
only ctx.skill.reload() — so refusing it bought no safety.
Fix (src/bootstrap-tool.ts execute): compute
reaBootstrapSteps(directory, args) first (import added); skip the
readPermissionAssert gate only when the step list is empty. Any non-zero
install set still asserts first and still refuses with the permission.assert
message when the capability is missing (fail-closed preserved).
Tests: existing D6 fail-closed test untouched in behaviour (default input =
7 steps → still rejects); the approved zero-step test's
expect(cap.asserted).toHaveLength(1) was wrong under the fix (gate is
skipped, so nothing is asserted) → changed to 0; new test: zero-step on an
assert-less host succeeds with results: [] + one skill.reload().
(b) formatResults hid skipped detail — VALID, fixed
Evidence: summary line was [SKIPPED] <label> with no reason, and the unit
test asserted expect(out).not.toContain("opencode CLI not runnable"),
actively pinning the information loss.
Fix (src/bootstrap.ts formatResults): skipped lines now read
[SKIPPED] <label> — <detail>; [OK]/[FAILED] marks and labels unchanged,
and the Failure output: dump still covers failures only.
The old not.toContain assertion was wrong because an operator triaging a
skipped workflows step could not see why without digging into raw results —
changed to toContain the reason. withWorkflowsFallbackNote untouched
(still keys off results[].status, not the formatted string).
(c) Version guard treated any parseable version as workflows-compatible — VALID, fixed
Evidence: workflowsSkipReason returned undefined (do the install) for any
output matching /opencode\s+v?\d+\.\d+\.\d+/i; D5 proves 2.0.32 fails to load
(Cannot load effect/dist/unstable/http sandbox rejection) while only the
pinned 2.0.15 loads (upstream engines: { opencode: 2.0.15 }, no newer plugin
release). A parseable-but-broken version therefore led straight into a doomed
install.
Fix (src/bootstrap.ts): extracted pure
workflowsSkipReasonForVersionText(text) — 2.0.15 → undefined (proceed);
any other parseable version → skip reason naming the detected version and the
supported 2.0.15 line with the D5 fallback pointer; unparseable →
existing fail-closed reason. workflowsSkipReason() keeps the sync spawnSync
probe (no network) and delegates to it. Pure-function shape keeps the new unit
test (2.0.15 allow / 2.0.32 + 2.0.26 skip / empty-text skip) free of host
coupling and spawnSync mocks.
Note: on the pinned 2.0.26 host the workflows step now skips with a reason
instead of attempting the install — intended fail-closed behaviour; the skip
surfaces via the (b) summary line plus the existing D5 fallback note, and no
existing test relied on the old allow path.
Validation
$ bun run check # tsc --noEmit -p tsconfig.json → exit 0
$ bun test --path-ignore-patterns 'review/**' test/unit/bootstrap.test.ts
10 pass, 0 fail
$ bun test --path-ignore-patterns 'review/**' test/unit test/integration
176 pass, 0 fail, 3007 expect() calls, 23 files
No .skip, no deleted tests. 3x-fail budget: 1 transient failure (stale
asserted length assertion, fixed by updating the expectation) — no blocker.