From e053d5af254467e97cc5d53eeff18272d464989e Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Sat, 25 Jul 2026 12:59:05 +0800 Subject: [PATCH 1/4] docs: opus 5 becomes a peer orchestrator across routing surfaces --- README.md | 2 +- plugins/fusion/rules-manifest.json | 1 + plugins/fusion/rules/orchestration.md | 2 +- plugins/fusion/skills/doctor/SKILL.md | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 130fc1a..24e5e81 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Requested by prompts (the orchestrator follows the installed instructions; not r ## Model roles -Roles bind to alias tiers, not to specific models, so a same tier release (Sonnet 5 under `sonnet`, a future Opus) needs zero configuration, and `best[1m]` floats the orchestrator to future Fable releases or degrades it to the latest Opus when Fable is unavailable. The one deliberate exception is `fusion:trivial-worker`, pinned to the full ID `claude-haiku-4-5` for machines where ANTHROPIC_DEFAULT_HAIKU_MODEL remaps the haiku alias; bump it by hand when a newer cheap tier ships. It is a fallback for trivial work when no eligible peer lane remains, or when Claude-only tools or privacy are required, not the default for single file tasks. Peer engine defaults live in each CLI's config (`~/.grok/config.toml`, `~/.codex/config.toml`); `/fusion:config` reads and changes them interactively. Alias semantics are Claude Code behavior as observed on 2.1.x, and nothing here sets your main model for you: pick it yourself, for example `/model best`. `/fusion:doctor` audits all of this. +Roles bind to alias tiers, not to specific models, so a same tier release (Sonnet 5 under `sonnet`, a new Opus generation) needs zero configuration, and `best[1m]` floats the orchestrator to future Fable releases or to the latest Opus when Fable is unavailable; since Opus 5 the two are the same top tier, so that float is lateral rather than a degradation. The one deliberate exception is `fusion:trivial-worker`, pinned to the full ID `claude-haiku-4-5` for machines where ANTHROPIC_DEFAULT_HAIKU_MODEL remaps the haiku alias; bump it by hand when a newer cheap tier ships. It is a fallback for trivial work when no eligible peer lane remains, or when Claude-only tools or privacy are required, not the default for single file tasks. Peer engine defaults live in each CLI's config (`~/.grok/config.toml`, `~/.codex/config.toml`); `/fusion:config` reads and changes them interactively. Alias semantics are Claude Code behavior as observed on 2.1.x, and nothing here sets your main model for you: pick it yourself, for example `/model best`. `/fusion:doctor` audits all of this. ## Benchmark diff --git a/plugins/fusion/rules-manifest.json b/plugins/fusion/rules-manifest.json index 792946a..27c6709 100644 --- a/plugins/fusion/rules-manifest.json +++ b/plugins/fusion/rules-manifest.json @@ -8,6 +8,7 @@ "35136a84eefb3d1debe84fecfa0d667ffbfd782efaff5f6bd2bd99ac2994b6dc", "44432f9aba3bed7d5029ecc38b8cf36100261fa045eb238b3df777d038ac0c00", "4960b1b6ffe5091429d7d78cfb38fee625325adc91c1f7821e6389c198894b56", + "50693459c23a833b0012025399bb6d666740a749d856f5466654bdc03c0fbf67", "5c1dbef35d32e6ad194af7c514ad38786c9485dd2c25edd09946b2e8cb76c6b4", "5e4b52c0304ee1906203d2551e9bea77a8d8c480ac41306fe85c2057b6ca4f06", "6d2e66c19ec296ffe2e3fce9705c0b68f9656c76aad0510877b5c6344e8a0655", diff --git a/plugins/fusion/rules/orchestration.md b/plugins/fusion/rules/orchestration.md index 762640c..bcf9005 100644 --- a/plugins/fusion/rules/orchestration.md +++ b/plugins/fusion/rules/orchestration.md @@ -2,7 +2,7 @@ This policy governs the main session. If the Agent tool is not available to you, you are a delegate: ignore this file and follow your own agent instructions. Each companion plugin owns its engine runtime details; this policy owns only cross-engine orchestration and collection invariants. -The policy is model agnostic. If the session falls back from Fable to Opus, keep orchestrating under the same rules and consider /model best to restore the strongest available orchestrator. Opus holds no standing routing seat and serves only as the fallback orchestrator when Fable is unavailable. +The policy is model agnostic. Fable and Opus 5 are peer orchestrators of the same top tier: either may hold the main session as a deliberate choice, switching between them is lateral (latency, fast mode availability, and quota mix are the deciders), and no posture, gate, fleet default, delegation rule, or verification requirement in this policy conditions on which of the two is active. Orchestrating on Opus 5 is never a degraded mode and never a reason to narrow functionality. Older Opus generations sit below this tier; a session that falls below it keeps orchestrating under the same rules and considers /model best to restore a top tier orchestrator. Neither orchestrator model gains a standing worker or advisory routing seat from being the session model; those seats stay as explicitly assigned (fusion:deep-reasoner on Fable, fusion:fast-worker on Sonnet, fusion:trivial-worker on Haiku). ## Output invariant diff --git a/plugins/fusion/skills/doctor/SKILL.md b/plugins/fusion/skills/doctor/SKILL.md index 6f66233..4333b6b 100644 --- a/plugins/fusion/skills/doctor/SKILL.md +++ b/plugins/fusion/skills/doctor/SKILL.md @@ -21,7 +21,7 @@ Checks to perform: 4. List ~/.claude/agents/ if it exists. A file whose name matches a plugin agent (deep-reasoner, fast-worker, trivial-worker) registers as a duplicate agent alongside the fusion: namespaced one and confuses auto delegation; flag it as a stale leftover to delete unless the user says it is a deliberate local override. 5. Compare the template hash of ~/.claude/rules/orchestration.md against the template hash of ${CLAUDE_PLUGIN_ROOT}/rules/orchestration.md, using the same normalization as rules-sync: replace everything between the `` and `` sentinel lines with the fixed normalized marker before computing SHA-256. Prefer `node --input-type=module` with `hashRulesTemplate` exported by `${CLAUDE_PLUGIN_ROOT}/scripts/lib/rules-template.mjs`; a sed or similar comparison that strips only the sentinel region is also acceptable. Missing live rules or a mismatched template hash means the static routing policy is stale; point at /fusion:setup to install or update it. A user selected live model table may differ from the scored canonical table and is healthy when the template hashes match. 6. Remind the user of the role model: the main Claude session is the only control plane and final judge. Claude workers are native specialists rather than an independent model family, Codex owns ordinary implementation and default deep external review, and Grok keeps the protected `burst`, `independence`, `live-web`, and `large-context` roles. The orchestrator, deep reasoning, mechanical, and trivial Claude tiers are roles bound to alias tiers, not to specific models, so same tier releases (for example Sonnet 5 under the sonnet alias) adopt their role with no configuration change. Only a genuinely new tier above or between the existing rungs requires a decision. -7. Note the recovery moves: if the main session fell back from Fable (safety classifier or availability), /model best restores the strongest available orchestrator. Peer engine models are owned by their own CLIs (~/.grok/config.toml, ~/.codex/config.toml); this setup never pins them. +7. Note the recovery moves: a main session on Opus 5 by deliberate choice is a healthy top tier configuration, not a fallback. If the session dropped below the top tier (safety classifier or availability), /model best restores the strongest available orchestrator. Peer engine models are owned by their own CLIs (~/.grok/config.toml, ~/.codex/config.toml); this setup never pins them. 8. Point at /codex:setup and /grok:setup for peer runtime health checks beyond config defaults; peer config defaults themselves are covered in the next step. 9. Peer engine defaults: extract the model related keys from ~/.grok/config.toml and the model and model_reasoning_effort keys from ~/.codex/config.toml (never read or print the full files; they sit next to credentials; use a scoped rg query rather than Read on those two files). Report each peer's configured default and flag grok drift when the grok CLI default is absent from the capability table or when that table's recorded lane or scores disagree with the default shown by the CLI. Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/fusion-config.mjs" audit` and report its Codex listing section as one checklist item. It reads only ~/.codex/models_cache.json (or FUSION_CODEX_MODELS_CACHE) and never refreshes it or invokes the Codex binary. Flag the configured-but-absent IDs and unconfigured gpt-* newcomers it reports, including the listing mtime. Treat `listing unavailable` as a warning without aborting the doctor run. /fusion:config is the write path for changing these scores; this audit never changes model-routing.json. When the capability table has no scored models at all, recommend /fusion:config propose so routing stops relying on the qualitative lane descriptions alone. 10. Audit the installed Codex surface against the foreground invariant. Glob `~/.claude/plugins/cache/claude-code-fusion/codex/*/commands/` and `~/.claude/plugins/cache/claude-code-fusion/codex/*/agents/`, then inspect the task, rescue, review, adversarial-review, and codex-rescue instructions. Task and review calls must stay foreground regardless of complexity, expected duration, review size, or model unless the incoming user request explicitly supplied `--background`; the rescue agent's companion Bash call must use the 10 minute tool timeout. A package that cannot fit the cap must be split or routed elsewhere. Fusion orchestration gives every detached job it creates one same turn collection attempt capped at 540000ms; a timeout must remain explicitly uncollected with the job id and result command. Direct Codex slash command instructions inspect through status and collect through result; Fusion's monitor is notification only. Confirm that task and rescue expose opt in `--web`, require `--write --network` for sandbox network access, and leave both settings disabled when absent. Flag any instruction that can detach implicitly or accepts a foreground receipt as a result. From 3f081217252c1c5dabe056fdaa99b0c18a44b5cc Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Sat, 25 Jul 2026 12:59:05 +0800 Subject: [PATCH 2/4] fix: re-verify in-flight records before the stop advisory emits --- plugins/fusion/scripts/worker-lifecycle.mjs | 14 +++++- tests/fusion-worker-lifecycle.test.mjs | 48 ++++++++++++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/plugins/fusion/scripts/worker-lifecycle.mjs b/plugins/fusion/scripts/worker-lifecycle.mjs index 69755a5..abfd76a 100644 --- a/plugins/fusion/scripts/worker-lifecycle.mjs +++ b/plugins/fusion/scripts/worker-lifecycle.mjs @@ -1805,6 +1805,12 @@ function inFlightAdvisorySignature(records) { .join(","); } +export function reverifyInFlightRecords(records, tasks, env = process.env) { + return records + .map((record) => readWorkerRecord(record.taskId, env)) + .filter((record) => record && !terminalCollectedRecord(record) && !terminalTransportObserved(record, runtimeTaskForRecord(record, tasks))); +} + function claimStopAdvisory(sessionId, kind, signature, env) { let claimed = false; updateWorkerSessionState(sessionId, env, (current) => { @@ -1926,9 +1932,13 @@ function handleStop(input, env) { return; } if (inFlight.length > 0) { - const signature = inFlightAdvisorySignature(currentRecords); + const verifiedInFlight = reverifyInFlightRecords(inFlight, tasks, env); + if (verifiedInFlight.length === 0) { + return; + } + const signature = inFlightAdvisorySignature(verifiedInFlight); if (!input.stop_hook_active && claimStopAdvisory(input.session_id, "in-flight", signature, env)) { - writeOutput(hookOutput("Stop", `Fusion task${inFlight.length === 1 ? "" : "s"} ${inFlight.map((record) => record.taskId).join(", ")} ${inFlight.length === 1 ? "is" : "are"} still in flight. Collection is armed and will be required after terminal notification.`)); + writeOutput(hookOutput("Stop", `Fusion task${verifiedInFlight.length === 1 ? "" : "s"} ${verifiedInFlight.map((record) => record.taskId).join(", ")} ${verifiedInFlight.length === 1 ? "is" : "are"} still in flight. Collection is armed and will be required after terminal notification.`)); } return; } diff --git a/tests/fusion-worker-lifecycle.test.mjs b/tests/fusion-worker-lifecycle.test.mjs index afbb824..ec64c72 100644 --- a/tests/fusion-worker-lifecycle.test.mjs +++ b/tests/fusion-worker-lifecycle.test.mjs @@ -7,7 +7,7 @@ import { test } from "node:test"; import { fusionRepositoryKey } from "../plugins/fusion/scripts/fusion-stats.mjs"; import { createWorkerRecord, readWorkerSessionState, readWorkerRecords, recordWorkerAcceptance, updateWorkerRecord, WORKER_COLLECTION_METHODS } from "../plugins/fusion/scripts/lib/worker-state.mjs"; -import { validateWorkerBrief, workerBudgetFailure, workerLimits } from "../plugins/fusion/scripts/worker-lifecycle.mjs"; +import { reverifyInFlightRecords, validateWorkerBrief, workerBudgetFailure, workerLimits } from "../plugins/fusion/scripts/worker-lifecycle.mjs"; const repoRoot = path.join(import.meta.dirname, ".."); const script = path.join(repoRoot, "plugins", "fusion", "scripts", "worker-lifecycle.mjs"); @@ -4038,6 +4038,7 @@ test("a queued verdict settles at the notification-driven terminal transition", background_tasks: [] }); assert.strictEqual(stopped.stdout, ""); + assert.doesNotMatch(stopped.stdout, /still in flight/); const settled = record(box); assert.strictEqual(settled.transportStatus, "done"); assert.strictEqual(settled.acceptance, "accepted"); @@ -4045,6 +4046,51 @@ test("a queued verdict settles at the notification-driven terminal transition", assert.strictEqual(settled.pendingVerdict, undefined); }); +test("a completion notification reconciled during Stop does not emit an in-flight advisory", (t) => { + const box = sandbox(t); + const workerTranscript = path.join(box.root, "stop-notification.output"); + const finalMessage = "notification result\ndelivery: complete\nverification: passed"; + fs.writeFileSync(workerTranscript, `${JSON.stringify({ type: "assistant", message: { content: [{ type: "text", text: finalMessage }] } })}\n`, "utf8"); + const workerDispatch = dispatch(box); + run(box, workerDispatch); + run(box, { + ...workerDispatch, + hook_event_name: "PostToolUse", + tool_response: { isAsync: true, status: "async_launched", agentId: "stop-notification", outputFile: workerTranscript } + }); + fs.appendFileSync(box.transcript, `${JSON.stringify({ type: "user", message: { content: "\nstop-notification\ncompleted\n" } })}\n`, "utf8"); + + const stopped = stop(box); + + assert.strictEqual(stopped.status, 0); + assert.doesNotMatch(stopped.stdout, /still in flight/); + assert.strictEqual(record(box).transportStatus, "done"); + assert.strictEqual(record(box).collectionMethod, WORKER_COLLECTION_METHODS.TASK_NOTIFICATION); +}); + +test("in-flight advisory re-verification excludes a record collected after the initial scan", (t) => { + const box = sandbox(t); + const worker = createWorkerRecord({ + taskId: `fusion-${"b".repeat(24)}`, + sessionId: "session-1", + agentType: "fusion:fast-worker", + workspaceRoot: box.cwd + }, envFor(box)); + const candidate = updateWorkerRecord(worker.taskId, envFor(box), (current) => ({ ...current, agentId: "reverify-collected", backgroundTaskId: "reverify-collected", transportStatus: "pending_async" })); + const collectedAt = new Date().toISOString(); + updateWorkerRecord(worker.taskId, envFor(box), (current) => ({ + ...current, + transportStatus: "done", + collectedAt, + collectionMethod: WORKER_COLLECTION_METHODS.TASK_NOTIFICATION, + acceptance: "accepted", + acceptanceRecordedAt: collectedAt, + awaitingVerdict: false + })); + + assert.deepStrictEqual(reverifyInFlightRecords([candidate], [{ id: "reverify-collected", status: "running" }], envFor(box)), []); +}); + test("queued accepted verdicts re-arm settlement after cancelled and incomplete hook transitions", (t) => { const cancelled = sandbox(t); run(cancelled, dispatch(cancelled)); From 80220cb27de14a669dca6915b1fd57d11157b5f6 Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Sat, 25 Jul 2026 12:59:05 +0800 Subject: [PATCH 3/4] feat: value free report and maintenance requests pass the stats direct path --- plugins/fusion/scripts/fusion-stats.mjs | 7 ++- plugins/fusion/skills/stats/SKILL.md | 2 +- tests/fusion-stats.test.mjs | 81 +++++++++++++++++++++++++ tests/raw-transport-surface.test.mjs | 5 +- 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/plugins/fusion/scripts/fusion-stats.mjs b/plugins/fusion/scripts/fusion-stats.mjs index 3a6a97e..aa79b7a 100644 --- a/plugins/fusion/scripts/fusion-stats.mjs +++ b/plugins/fusion/scripts/fusion-stats.mjs @@ -2579,6 +2579,11 @@ function isStrictDirectRecordArguments(argv) { } } +function isStrictDirectReportOrMaintenanceArguments(argv) { + const flags = new Set(["--json", "--audit", "--all", "--prune-dead"]); + return argv.length > 0 && argv.every((token) => flags.delete(token)); +} + function writeRecordConfirmation({ kind, engine = null, jobId = null, worker = null, queued = false, asJson, stdout }) { if (asJson) { stdout.write(`${JSON.stringify(kind === "engine" ? { kind, engine, jobId, acceptance: worker?.acceptance } : { kind, taskId: worker.taskId, acceptance: queued ? worker.pendingVerdict?.acceptance : worker.acceptance, ...(queued ? { queued: true } : {}) })}\n`); @@ -2768,7 +2773,7 @@ function runCli(argv = process.argv.slice(2)) { consumeRawArgsTransport(argv[2]); return; } - if (isStrictDirectRecordArguments(argv)) { + if (isStrictDirectRecordArguments(argv) || isStrictDirectReportOrMaintenanceArguments(argv)) { if (argv.some((token, index) => token === "--record" && parseRecordPair(argv[index + 1]).verdict === "rejected")) { process.stderr.write("Rejected verdicts require --reason through the raw-args transport. For batch settlements, use --reason-for for each rejected pair.\n"); process.exitCode = 1; diff --git a/plugins/fusion/skills/stats/SKILL.md b/plugins/fusion/skills/stats/SKILL.md index d933642..ca962ca 100644 --- a/plugins/fusion/skills/stats/SKILL.md +++ b/plugins/fusion/skills/stats/SKILL.md @@ -5,7 +5,7 @@ argument-hint: '[--all] [--session [id]] [--trace] [--audit [--days ]] [--rec allowed-tools: Bash(node:*), Read, Write --- -Treat the complete raw request as opaque data and never place any part of it in Bash. Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/fusion-stats.mjs" transport-create` in one foreground Bash call, parse the returned JSON, accept only its 48 character lowercase hexadecimal token, and use `Read` once on the returned file, requiring it to be empty. Use `Write` to replace that same file with the raw request exactly as received. Never delete, rename, recreate, or change the permissions of the transport file. Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/fusion-stats.mjs" --raw-args-token TOKEN` in one foreground Bash call, replacing only `TOKEN` with the validated token. If Read fails, the file is not empty, or Write fails, run `node "${CLAUDE_PLUGIN_ROOT}/scripts/fusion-stats.mjs" transport-discard --raw-args-token TOKEN`. Never use shell redirection, encoding, command substitution, backticks, environment variables, or heredocs for the raw request. The sole carve-out is a strict verdict settlement made only of repeatable `--record =[:accept-failed-transport]` pairs, optional `--source collector|main-loop`, and the value free `--json` and `--accept-failed-transport` flags; those arguments may be passed directly. Anything else, including every `--reason` text value, still uses the raw-args transport exactly as described above. A nonterminal worker settlement prints `Queued accepted for ; applies when the record turns terminal.` with the selected verdict in place of `accepted`; queued accepted applies only when terminal transport is done, while any other terminal outcome clears it, records `pendingVerdictError`, and re-arms settlement. +Treat the complete raw request as opaque data and never place any part of it in Bash. Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/fusion-stats.mjs" transport-create` in one foreground Bash call, parse the returned JSON, accept only its 48 character lowercase hexadecimal token, and use `Read` once on the returned file, requiring it to be empty. Use `Write` to replace that same file with the raw request exactly as received. Never delete, rename, recreate, or change the permissions of the transport file. Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/fusion-stats.mjs" --raw-args-token TOKEN` in one foreground Bash call, replacing only `TOKEN` with the validated token. If Read fails, the file is not empty, or Write fails, run `node "${CLAUDE_PLUGIN_ROOT}/scripts/fusion-stats.mjs" transport-discard --raw-args-token TOKEN`. Never use shell redirection, encoding, command substitution, backticks, environment variables, or heredocs for the raw request. The sole carve-outs are two strict direct forms: a verdict settlement made only of repeatable `--record =[:accept-failed-transport]` pairs, optional `--source collector|main-loop`, and the value free `--json` and `--accept-failed-transport` flags; and a value free report or maintenance request made only of any combination of `--json`, `--audit`, `--all`, and `--prune-dead`, each at most once. Those arguments may be passed directly. Anything else, including every `--reason` text value and `--session `, still uses the raw-args transport exactly as described above. A nonterminal worker settlement prints `Queued accepted for ; applies when the record turns terminal.` with the selected verdict in place of `accepted`; queued accepted applies only when terminal transport is done, while any other terminal outcome clears it, records `pendingVerdictError`, and re-arms settlement. Present report output to the user as-is. It is already compact markdown; do not add prose around it. An unavailable engine section is expected when that peer's plugin is not installed or has no job state; do not treat it as an error. The underlying script accepts `--json` for machine readable output. `--audit` summarizes the last seven days of retained inline guard events by default; add `--all` for the full retained window or `--session ` for one session. `/fusion:stats --record` writes the verdict into the Codex job record through the companion's `record-acceptance` subcommand. An `accepted` verdict for a job whose transport failed requires `--accept-failed-transport`. The acceptance forms are used only after collection and verification; return the confirmation line without turning transport completion into acceptance on its own. Preserve the report's acceptance anomalies block when it appears. diff --git a/tests/fusion-stats.test.mjs b/tests/fusion-stats.test.mjs index 8110e31..9eee77e 100644 --- a/tests/fusion-stats.test.mjs +++ b/tests/fusion-stats.test.mjs @@ -1454,6 +1454,87 @@ function createTerminalWorker({ taskId, env, workspaceRoot, peerEngine = null, p })); } +test("direct --json emits the Fusion stats JSON report", (t) => { + const dir = sandbox(t); + const stateRoot = path.join(dir, "codex-state"); + writeCodexJob(stateRoot, dir, "direct-json", { status: "done", jobClass: "task", createdAt: "2026-07-25T00:00:00.000Z" }); + + const result = runDirect({ cwd: dir, codexState: stateRoot }, ["--json"]); + + assert.strictEqual(result.status, 0, result.stderr); + const report = JSON.parse(result.stdout); + assert.strictEqual(report.scope, dir); + assert.strictEqual(report.codex.totalJobs, 1); +}); + +test("direct --audit prints the audit report", (t) => { + const dir = sandbox(t); + + const result = runDirect({ cwd: dir, codexState: path.join(dir, "missing") }, ["--audit"]); + + assert.strictEqual(result.status, 0, result.stderr); + assert.match(result.stdout, /# Fusion inline audit/); +}); + +test("direct --json --all emits the all-workspace JSON report", (t) => { + const dir = sandbox(t); + const stateRoot = path.join(dir, "codex-state"); + writeCodexJob(stateRoot, path.join(dir, "other-workspace"), "direct-all", { status: "done", jobClass: "task", createdAt: "2026-07-25T00:00:00.000Z" }); + + const result = runDirect({ cwd: dir, codexState: stateRoot }, ["--json", "--all"]); + + assert.strictEqual(result.status, 0, result.stderr); + assert.strictEqual(JSON.parse(result.stdout).codex.totalJobs, 1); +}); + +test("direct --prune-dead runs against fixture state", (t) => { + const dir = sandbox(t); + const stateRoot = path.join(dir, "codex-state"); + writeCodexJob(stateRoot, path.join(dir, "gone-workspace"), "direct-prune", { status: "done", jobClass: "task", createdAt: "2026-07-25T00:00:00.000Z" }); + + const result = runDirect({ cwd: dir, codexState: stateRoot }, ["--prune-dead"]); + + assert.strictEqual(result.status, 0, result.stderr); + assert.match(result.stdout, /# Fusion dead workspace prune \(dry run\)/); + assert.match(result.stdout, /codex-state/); +}); + +test("direct --session requires the raw-args transport", (t) => { + const dir = sandbox(t); + + const result = runDirect({ cwd: dir, codexState: path.join(dir, "missing") }, ["--session", "123e4567-e89b-12d3-a456-426614174000"]); + + assert.notStrictEqual(result.status, 0); + assert.match(result.stderr, /Fusion stats requests must be supplied through --raw-args-token\./); +}); + +test("direct --source without --record requires the raw-args transport", (t) => { + const dir = sandbox(t); + + const result = runDirect({ cwd: dir, codexState: path.join(dir, "missing") }, ["--source", "main-loop"]); + + assert.notStrictEqual(result.status, 0); + assert.match(result.stderr, /Fusion stats requests must be supplied through --raw-args-token\./); +}); + +test("direct report requests reject unsupported flags", (t) => { + const dir = sandbox(t); + + const result = runDirect({ cwd: dir, codexState: path.join(dir, "missing") }, ["--json", "--bogus"]); + + assert.notStrictEqual(result.status, 0); + assert.match(result.stderr, /Fusion stats requests must be supplied through --raw-args-token\./); +}); + +test("direct report requests reject duplicate flags", (t) => { + const dir = sandbox(t); + + const result = runDirect({ cwd: dir, codexState: path.join(dir, "missing") }, ["--json", "--json"]); + + assert.notStrictEqual(result.status, 0); + assert.match(result.stderr, /Fusion stats requests must be supplied through --raw-args-token\./); +}); + test("--record settles a Fusion task and its Codex peer with direct strict argv", (t) => { const dir = sandbox(t); const stateRoot = path.join(dir, "codex-state"); diff --git a/tests/raw-transport-surface.test.mjs b/tests/raw-transport-surface.test.mjs index 4d08eb6..deb14f5 100644 --- a/tests/raw-transport-surface.test.mjs +++ b/tests/raw-transport-surface.test.mjs @@ -90,9 +90,10 @@ test("every executable private transport surface follows its declared Write tran assert.match(content, /Read(?: call)? fails/, file); assert.match(content, /file is not empty/, file); if (file === "plugins/fusion/skills/stats/SKILL.md") { - assert.match(content, /sole carve-out is a strict verdict settlement/, file); + assert.match(content, /sole carve-outs are two strict direct forms: a verdict settlement/, file); assert.match(content, /repeatable `--record =\[:accept-failed-transport\]` pairs, optional `--source collector\|main-loop`, and the value free `--json` and `--accept-failed-transport` flags/, file); - assert.match(content, /Anything else, including every `--reason` text value, still uses the raw-args transport exactly as described above\./, file); + assert.match(content, /a value free report or maintenance request made only of any combination of `--json`, `--audit`, `--all`, and `--prune-dead`, each at most once/, file); + assert.match(content, /Anything else, including every `--reason` text value and `--session `, still uses the raw-args transport exactly as described above\./, file); } } }); From 28a3f708283d01a19baff52f56c4d00ae786f6f6 Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Sat, 25 Jul 2026 12:59:05 +0800 Subject: [PATCH 4/4] chore: release 0.0.38 --- .claude-plugin/marketplace.json | 8 ++++---- CHANGELOG.md | 6 ++++++ plugins/codex/.claude-plugin/plugin.json | 2 +- plugins/fusion/.claude-plugin/plugin.json | 2 +- plugins/grok/.claude-plugin/plugin.json | 2 +- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 769678c..30c617c 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,14 +6,14 @@ "email": "hi@okis.dev" }, "description": "Multi-model orchestration marketplace for Claude Code.", - "version": "0.0.37", + "version": "0.0.38", "plugins": [ { "name": "grok", "source": "./plugins/grok", "displayName": "Grok Companion", "description": "Local Grok CLI delegation: task, review, resumable history, best-of-n tournaments, background jobs, stats, and setup health checks.", - "version": "0.0.37", + "version": "0.0.38", "author": { "name": "Harry Yep" }, @@ -34,7 +34,7 @@ "source": "./plugins/codex", "displayName": "Codex Companion", "description": "First party local Codex CLI delegation for tasks, reviews, resumable threads, and durable background jobs.", - "version": "0.0.37", + "version": "0.0.38", "author": { "name": "Harry Yep" }, @@ -55,7 +55,7 @@ "source": "./plugins/fusion", "displayName": "Fusion Orchestrator", "description": "Multi-model orchestration: tier agents, routing rules, blind panel, ultra fleet, model config, and drift doctor.", - "version": "0.0.37", + "version": "0.0.38", "author": { "name": "Harry Yep" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d6ddd8..ccdba0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # changelog +## 0.0.38 + +- Fable and Opus 5 are peer orchestrators: the routing preamble drops the Opus as fallback framing (switching is lateral; posture, gates, fleet defaults, and delegation never condition on which of the two is active), the readme reframes `best[1m]` floating as lateral within the top tier, and doctor treats a deliberate Opus 5 session as healthy instead of a fallback to recover +- the stop hook's in flight advisory closes a cross process staleness window: candidate records are fresh read and filtered through the existing terminal collected predicates immediately before signing and emission (`reverifyInFlightRecords`), so an overlapping stop pass can no longer announce a task as still in flight after its notification driven terminal flip landed +- `/fusion:stats` direct arguments gain a second closed family: value free report and maintenance requests (`--json`, `--audit`, `--all`, `--prune-dead`, each at most once) pass without the raw-args transport; the settlement family, the rejection message, and the transport requirement for `--session` and all free text are unchanged, and the skill carve-out wording now matches the predicate verbatim + ## 0.0.37 - settlement and telemetry become more discriminating: batched settlement validates every pair before writing, reports per pair rejection reasons, and `/fusion:stats` adds lane drift, per SKU trends, narrow wave watch, and an acceptance epoch cutoff; the worker lifecycle adds a parent context advisory, package type and brief byte capture, recalibrated trivial worker budgets, and token observations, while the inline guard gains tail allowance and softens zero dispatches diff --git a/plugins/codex/.claude-plugin/plugin.json b/plugins/codex/.claude-plugin/plugin.json index 4911700..ef97fe6 100644 --- a/plugins/codex/.claude-plugin/plugin.json +++ b/plugins/codex/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "codex", "displayName": "Codex Companion", - "version": "0.0.37", + "version": "0.0.38", "description": "First party local Codex CLI delegation for tasks, reviews, resumable threads, and durable background jobs.", "author": { "name": "Harry Yep" diff --git a/plugins/fusion/.claude-plugin/plugin.json b/plugins/fusion/.claude-plugin/plugin.json index 363c9ea..2d67d46 100644 --- a/plugins/fusion/.claude-plugin/plugin.json +++ b/plugins/fusion/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "fusion", "displayName": "Fusion Orchestrator", - "version": "0.0.37", + "version": "0.0.38", "description": "Multi-model orchestration: tier agents, routing rules, blind panel, ultra fleet, model config, and drift doctor.", "author": { "name": "Harry Yep" diff --git a/plugins/grok/.claude-plugin/plugin.json b/plugins/grok/.claude-plugin/plugin.json index bdc9ed4..32e4f18 100644 --- a/plugins/grok/.claude-plugin/plugin.json +++ b/plugins/grok/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "grok", "displayName": "Grok Companion", - "version": "0.0.37", + "version": "0.0.38", "description": "Local Grok CLI delegation: task, review, resumable history, background jobs, stats, and setup health checks.", "author": { "name": "Harry Yep"