ruvnet-brain 4.5.4 β 4.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/install.mjs +162 -27
- package/config/model-router/catalog.template.json +126 -52
- package/config/model-router/claude-terminal-mod/.claude-plugin/plugin.json +1 -0
- package/config/model-router/claude-terminal-mod/README.md +22 -0
- package/config/model-router/claude-terminal-mod/hooks/hooks.json +1 -0
- package/config/model-router/claude-terminal-mod/hooks/policy.default.mjs +2 -0
- package/config/model-router/claude-terminal-mod/hooks/register.js +66 -0
- package/config/model-router/claude-terminal-mod/hooks/routing.js +55 -0
- package/config/model-router/claude-terminal-mod/hooks/runtime.js +2 -0
- package/config/model-router/claude-terminal-mod/tests/native.test.ts +86 -0
- package/config/model-router/policy.default.mjs +97 -75
- package/config/model-router/qualification-contract.json +124 -0
- package/config/model-router/routing-eval-cases.json +275 -0
- package/config/model-router/routing-policy.template.json +76 -0
- package/config/model-router/weekly-analyst-instruction.md +60 -0
- package/data/model-catalog.json +44 -49
- package/package.json +6 -3
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/mcp/managed-cli-interface.mjs +9 -4
- package/plugin/scripts/capability-claim-evidence.mjs +9 -2
- package/plugin/scripts/codex-hook-adapter.mjs +18 -9
- package/plugin/scripts/project-capture-queue.mjs +7 -1
- package/plugin/scripts/project-progression-hook.mjs +16 -7
- package/plugin/scripts/project-progression-outbox.mjs +96 -15
- package/plugin/scripts/project-progression-producer.mjs +12 -1
- package/plugin/scripts/project-progression-store.mjs +53 -1
- package/plugin/scripts/project-transition-hook.mjs +19 -8
- package/plugin/scripts/session-snapshot-hook.mjs +2 -1
- package/scripts/claude-terminal-mod.mjs +89 -0
- package/scripts/codex-hook-trust-reconcile.mjs +247 -0
- package/scripts/codex-routed.sh +3 -36
- package/scripts/goldie-weekly.sh +8 -64
- package/scripts/metaharness-router.mjs +7 -1
- package/scripts/model-analyst-sandbox.mjs +54 -0
- package/scripts/model-currency-evidence.mjs +139 -0
- package/scripts/model-currency.mjs +230 -0
- package/scripts/model-native-catalog.mjs +111 -0
- package/scripts/model-native-qualification.mjs +251 -0
- package/scripts/model-router-agent-hook.mjs +136 -0
- package/scripts/model-router-dispatch.mjs +161 -0
- package/scripts/model-router-engine.mjs +155 -104
- package/scripts/model-routing-eval.mjs +108 -0
- package/scripts/model-routing-gateway.mjs +453 -0
- package/scripts/model-routing-launchers.mjs +209 -0
- package/scripts/model-routing-policy-promotion.mjs +203 -0
- package/scripts/model-terminal-gateway.mjs +310 -0
- package/scripts/model-terminal-launchers.mjs +300 -0
- package/scripts/model-weekly-analyst.mjs +299 -0
- package/scripts/model-weekly-assessment.mjs +91 -0
- package/scripts/model-weekly-cycle.mjs +183 -0
- package/scripts/model-weekly-qualification.mjs +362 -0
- package/scripts/native-subscription-usage.mjs +57 -0
- package/scripts/release-qualification-contract.mjs +50 -1
- package/scripts/release-qualification.mjs +9 -6
- package/scripts/security-guidance-codex-compat.mjs +142 -0
- package/scripts/user-model-prompt-hook.mjs +69 -0
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Created: 2026-06-29 22:36:38 EDT
|
|
|
7
7
|
|
|
8
8
|
# π§ RuvNet Brain
|
|
9
9
|
|
|
10
|
-
### π§ RuvNet Brain β [](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
|
|
11
11
|
|
|
12
12
|
**A portable, source-grounded brain over Reuven Cohen's (rUv's) RuvNet stack β delivered as a Claude Code plugin that makes Claude _use_ the stack instead of fighting it.**
|
|
13
13
|
|
|
@@ -562,7 +562,7 @@ node forge-ask-all.mjs --dir . --q "How does RuVector implement HNSW vector sear
|
|
|
562
562
|
|
|
563
563
|
This project versions in the open (see the live badge up top for the exact plugin version; the downloadable knowledge bundle is a separate track) β we don't claim βdone,β βcomplete,β or βzero hallucinations.β Where it stands:
|
|
564
564
|
|
|
565
|
-
- β
**The grounding brain is real and proven** β 199 public stores Β· 162,
|
|
565
|
+
- β
**The grounding brain is real and proven** β 199 public stores Β· 162,680 public source chunks, dual embeddings, cross-encoder rerank, plugin (MCP tool + explicit skills; automatic hooks retired), all re-runnable.
|
|
566
566
|
- β
**Code-level depth** β the code-rich repos are indexed to full function bodies; βhow is it implemented?β returns the implementation. Verified in the shipped bundle (clean-room 3/3).
|
|
567
567
|
- β
**Routing holds** β named 47/48, described 26/28, scenario 7/8; behavioral L1βL3 all pass (**L4 downgraded β it measures that the brain spoke, not that anything listened**); private stores fenced out of the public bundle (zero-leak verified).
|
|
568
568
|
- β οΈ **Two routing residuals** (above) β surfaced, not hidden.
|
package/bin/install.mjs
CHANGED
|
@@ -79,6 +79,9 @@ import {
|
|
|
79
79
|
} from '../scripts/console-runtime-identity.mjs';
|
|
80
80
|
import { shellDiff as pluginShellDiff } from '../plugin/scripts/host-shell-boundary.mjs';
|
|
81
81
|
import { codexTrustChanges, CODEX_TRUST_ACTION } from '../scripts/codex-hook-trust.mjs';
|
|
82
|
+
import { repairSecurityGuidance } from '../scripts/security-guidance-codex-compat.mjs';
|
|
83
|
+
import { installNativeLaunchers, installRoutingRuntime } from '../scripts/model-routing-launchers.mjs';
|
|
84
|
+
import { installTerminalLaunchers } from '../scripts/model-terminal-launchers.mjs';
|
|
82
85
|
import { readConsoleReceipts, replaceStaleConsoles } from '../scripts/console-instances.mjs';
|
|
83
86
|
import { moveBrain, MoveRefused } from '../scripts/move-brain.mjs';
|
|
84
87
|
import { brainLocation } from '../plugin/scripts/brain-location.mjs';
|
|
@@ -1629,6 +1632,7 @@ function codexManagedBlock(serverPath) {
|
|
|
1629
1632
|
'command = "node"',
|
|
1630
1633
|
`args = [${JSON.stringify(serverPath)}]`,
|
|
1631
1634
|
'startup_timeout_sec = 30',
|
|
1635
|
+
'env = { RUVNET_HOOK_HOST = "codex" }',
|
|
1632
1636
|
CODEX_BLOCK_END,
|
|
1633
1637
|
].join('\n');
|
|
1634
1638
|
}
|
|
@@ -1835,6 +1839,10 @@ export function wireCodexHost({
|
|
|
1835
1839
|
// land where the server will look for them, and the walk is transitive because a dependency's own
|
|
1836
1840
|
// dependency is no less required.
|
|
1837
1841
|
const deps = serverDependencies(source);
|
|
1842
|
+
// Progression capture reads its adapter version from this runtime resource, not an import.
|
|
1843
|
+
// Keep it beside the copied plugin scripts, bound to the same source as the MCP shell.
|
|
1844
|
+
deps.push({ spec: '../.claude-plugin/plugin.json',
|
|
1845
|
+
from: path.resolve(path.dirname(source), '..', '.claude-plugin', 'plugin.json') });
|
|
1838
1846
|
const missing = deps.filter((d) => !fs.existsSync(d.from));
|
|
1839
1847
|
if (missing.length) {
|
|
1840
1848
|
if (announce) warn(`MCP server dependency missing from this bundle (${missing.map((d) => d.spec).join(', ')}) β Codex left untouched (non-fatal)`);
|
|
@@ -2014,6 +2022,32 @@ export function codexPluginStatus(options = {}) {
|
|
|
2014
2022
|
};
|
|
2015
2023
|
}
|
|
2016
2024
|
|
|
2025
|
+
// User-scoped standing consent applies across projects, never implicitly to other installations.
|
|
2026
|
+
export function repairReleasedCodexHookTrust(status, { codexHome = codexHomeDir(), codexBin = process.env.CODEX_BIN || 'codex', cwd = process.cwd(), run = spawnSync } = {}) {
|
|
2027
|
+
if (TEST_MODE || !status?.installed || !status?.enabled) return { state: 'not-applicable', changed: false };
|
|
2028
|
+
let profile;
|
|
2029
|
+
try { profile = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.claude', 'model-router', 'profile.json'), 'utf8')); } catch { return { state: 'not-authorized', changed: false }; }
|
|
2030
|
+
if (profile.automaticHookTrustUpdates !== true) return { state: 'not-authorized', changed: false };
|
|
2031
|
+
let compatibility;
|
|
2032
|
+
try { compatibility = repairSecurityGuidance({ codexHome, apply: true }); }
|
|
2033
|
+
catch (error) { compatibility = { status: 'blocked', reason: error.message }; }
|
|
2034
|
+
const installedRoot = codexInstalledPluginRoot({ codexHome, status });
|
|
2035
|
+
if (!installedRoot) return { state: 'blocked', changed: false, reason: 'Installed plugin root unavailable' };
|
|
2036
|
+
const helper = path.join(REPO_ROOT, 'scripts', 'codex-hook-trust-reconcile.mjs');
|
|
2037
|
+
const result = run(process.execPath, [helper, '--reconcile-installed'], {
|
|
2038
|
+
input: JSON.stringify({ installedVersion: status.version, hooksPath: path.join(installedRoot, 'hooks', 'codex-hooks.json'),
|
|
2039
|
+
configPath: path.join(codexHome, 'config.toml'), nativeBinary: codexBin, cwd }),
|
|
2040
|
+
encoding: 'utf8', timeout: 30000, maxBuffer: 128 * 1024, env: process.env,
|
|
2041
|
+
});
|
|
2042
|
+
try {
|
|
2043
|
+
const receipt = JSON.parse(result.stdout);
|
|
2044
|
+
if (!result.error && ['registry-verified', 'unchanged', 'blocked', 'degraded'].includes(receipt.state)) return { ...receipt, compatibility };
|
|
2045
|
+
} catch { /* a command exit alone never establishes hook trust */ }
|
|
2046
|
+
return result.error?.code === 'ENOENT'
|
|
2047
|
+
? { state: 'blocked', changed: false, reason: 'Verified hook trust helper could not start' }
|
|
2048
|
+
: { state: 'degraded', changed: 'unknown', reason: 'Hook trust helper acknowledgement unavailable; inspect native registry before retrying' };
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2017
2051
|
export function wireCodexPlugin({
|
|
2018
2052
|
codexDir = codexHomeDir(),
|
|
2019
2053
|
codexHome = codexDir,
|
|
@@ -2062,9 +2096,13 @@ export function wireCodexPlugin({
|
|
|
2062
2096
|
const hooksNeedingReview = codexHooksNeedingReview(before.installed
|
|
2063
2097
|
? codexInstalledPluginRoot({ codexHome, status: before }) : null, path.join(REPO_ROOT, 'plugin'));
|
|
2064
2098
|
if (before.installed && before.enabled && versionSatisfies(before.version, expectedVersion)) {
|
|
2065
|
-
|
|
2099
|
+
const hookTrust = runJson === runCodexJson ? repairReleasedCodexHookTrust(before, options) : null;
|
|
2100
|
+
if (announce) {
|
|
2101
|
+
ok(`Codex Brain plugin already installed and enabled (${before.version || 'version unknown'}).`);
|
|
2102
|
+
if (hookTrust && ['blocked', 'degraded'].includes(hookTrust.state)) warn(`Automatic Brain hook trust repair ${hookTrust.state}: ${hookTrust.reason}`);
|
|
2103
|
+
}
|
|
2066
2104
|
return {
|
|
2067
|
-
host: true, action: 'unchanged', ...before,
|
|
2105
|
+
host: true, action: 'unchanged', ...before, ...(hookTrust ? { hookTrust } : {}),
|
|
2068
2106
|
shellChanged: shellBoundary.changed, shellChangedPaths: shellBoundary.paths,
|
|
2069
2107
|
restartRequired: false,
|
|
2070
2108
|
};
|
|
@@ -2107,14 +2145,17 @@ export function wireCodexPlugin({
|
|
|
2107
2145
|
if (announce) warn('Codex accepted the install command but the Brain plugin is not installed and enabled.');
|
|
2108
2146
|
return { host: true, action: 'verification-failed', expectedVersion, ...after };
|
|
2109
2147
|
}
|
|
2148
|
+
const hookTrust = runJson === runCodexJson ? repairReleasedCodexHookTrust(after, options) : null;
|
|
2149
|
+
const trustVerified = hookTrust && ['registry-verified', 'unchanged'].includes(hookTrust.state);
|
|
2110
2150
|
if (announce) {
|
|
2111
2151
|
ok(`Codex Brain plugin installed and enabled (${after.version || 'version unknown'}).`);
|
|
2152
|
+
if (hookTrust && ['blocked', 'degraded'].includes(hookTrust.state)) warn(`Automatic Brain hook trust repair ${hookTrust.state}: ${hookTrust.reason}`);
|
|
2112
2153
|
if (shellBoundary.restartRequired) {
|
|
2113
2154
|
warn(`boot-level plugin declarations changed; restart Codex, then review them in /hooks (${shellBoundary.paths.join(', ') || shellBoundary.reason}).`);
|
|
2114
2155
|
} else if (before.installed && before.version !== after.version) {
|
|
2115
2156
|
info(' body-only update: the Stable Spine is live on the next hook/MCP call; no restart is required.');
|
|
2116
2157
|
}
|
|
2117
|
-
if (hooksNeedingReview.length) {
|
|
2158
|
+
if (hooksNeedingReview.length && !trustVerified) {
|
|
2118
2159
|
warn(`Codex will NOT run ${hooksNeedingReview.length} Brain hook${hooksNeedingReview.length === 1 ? '' : 's'} until you review`
|
|
2119
2160
|
+ ` ${hooksNeedingReview.length === 1 ? 'it' : 'them'} (${hooksNeedingReview.map((h) => `${h.key.split(':').slice(-3).join(':')} ${h.status}`).join(', ')}).`);
|
|
2120
2161
|
info(` ${CODEX_TRUST_ACTION}`);
|
|
@@ -2123,7 +2164,8 @@ export function wireCodexPlugin({
|
|
|
2123
2164
|
return {
|
|
2124
2165
|
host: true,
|
|
2125
2166
|
action: before.installed ? 'updated' : 'installed',
|
|
2126
|
-
hooksNeedingReview,
|
|
2167
|
+
hooksNeedingReview: trustVerified ? [] : hooksNeedingReview,
|
|
2168
|
+
...(hookTrust ? { hookTrust } : {}),
|
|
2127
2169
|
...after,
|
|
2128
2170
|
shellChanged: shellBoundary.changed,
|
|
2129
2171
|
shellChangedPaths: shellBoundary.paths,
|
|
@@ -3934,6 +3976,7 @@ async function runUpdate() {
|
|
|
3934
3976
|
} catch (error) {
|
|
3935
3977
|
warn(`managed model additions were not merged (${error.message}); your catalog was left unchanged`);
|
|
3936
3978
|
}
|
|
3979
|
+
refreshManagedRouterDefault();
|
|
3937
3980
|
process.exitCode = 0;
|
|
3938
3981
|
return;
|
|
3939
3982
|
}
|
|
@@ -4207,6 +4250,8 @@ async function runUpdate() {
|
|
|
4207
4250
|
// updated. Additive and non-interactive; their overrides win, metered rows are never auto-enabled,
|
|
4208
4251
|
// and a failure here is reported but never fails an otherwise-good update.
|
|
4209
4252
|
if (updateStatus === 0) {
|
|
4253
|
+
const defaultReceipt = refreshManagedRouterDefault();
|
|
4254
|
+
recordRefreshAdvisory(refreshReceipt, 'managed-router-default', defaultReceipt.action === 'failed' ? 'SKIP' : 'PASS', defaultReceipt);
|
|
4210
4255
|
try {
|
|
4211
4256
|
const managed = applyManagedCatalogUpdate({
|
|
4212
4257
|
routerDir: path.join(os.homedir(), '.claude', 'model-router'),
|
|
@@ -4849,30 +4894,122 @@ export function parseNightlyAnswer(answer) {
|
|
|
4849
4894
|
// suppression flags) is testable in-process under RUVNET_BRAIN_IMPORT_ONLY=1 without a real install.
|
|
4850
4895
|
// Returns a status string; never throws (the caller also guards β a finished install must never
|
|
4851
4896
|
// be broken by an optional offer).
|
|
4897
|
+
// The default classifier is managed executable source; overrides belong in policy.mjs.
|
|
4898
|
+
// Preserve every prior default by its content digest before replacing it atomically.
|
|
4899
|
+
export function syncManagedRouterDefault({ routerDir = path.join(os.homedir(), '.claude', 'model-router'), packageRoot = REPO_ROOT } = {}) {
|
|
4900
|
+
const source = fs.readFileSync(path.join(packageRoot, 'config', 'model-router', 'policy.default.mjs'));
|
|
4901
|
+
const destination = path.join(routerDir, 'policy.default.mjs');
|
|
4902
|
+
fs.mkdirSync(routerDir, { recursive: true });
|
|
4903
|
+
const prior = fs.existsSync(destination) ? fs.readFileSync(destination) : null;
|
|
4904
|
+
if (prior?.equals(source)) return { action: 'unchanged' };
|
|
4905
|
+
let backup = null;
|
|
4906
|
+
if (prior) {
|
|
4907
|
+
backup = `${destination}.pre-managed-upgrade-${crypto.createHash('sha256').update(prior).digest('hex').slice(0, 16)}`;
|
|
4908
|
+
try { fs.writeFileSync(backup, prior, { flag: 'wx', mode: 0o600 }); }
|
|
4909
|
+
catch (error) {
|
|
4910
|
+
if (error.code !== 'EEXIST' || !fs.readFileSync(backup).equals(prior)) throw error;
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
const temporary = `${destination}.tmp-${process.pid}-${crypto.randomUUID()}`;
|
|
4914
|
+
try {
|
|
4915
|
+
fs.writeFileSync(temporary, source, { flag: 'wx', mode: prior ? fs.statSync(destination).mode & 0o777 : 0o600 });
|
|
4916
|
+
fs.renameSync(temporary, destination);
|
|
4917
|
+
} finally { fs.rmSync(temporary, { force: true }); }
|
|
4918
|
+
return { action: prior ? 'upgraded' : 'created', backup };
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
export function refreshInstalledModelLaunchers({ sourceRoot = REPO_ROOT, home = os.homedir() } = {}) {
|
|
4922
|
+
const terminalFile = path.join(home, '.cache', 'ruvnet-brain', 'model-routing', 'terminal-launcher-config.json');
|
|
4923
|
+
let terminal = { action: 'not-installed' };
|
|
4924
|
+
if (fs.existsSync(terminalFile)) {
|
|
4925
|
+
const previous = JSON.parse(fs.readFileSync(terminalFile, 'utf8'));
|
|
4926
|
+
const runtime = installRoutingRuntime({ sourceRoot, home, apply: true });
|
|
4927
|
+
const receipt = installTerminalLaunchers({ home, ...runtime, nodeBinary: previous.nodeBinary,
|
|
4928
|
+
realCodex: previous.realCodex, realClaude: previous.realClaude, apply: true });
|
|
4929
|
+
terminal = { action: previous.runtimeDigest === runtime.runtimeDigest ? 'unchanged' : 'updated',
|
|
4930
|
+
runtimeDigest: runtime.runtimeDigest, shellConflicts: receipt.shellConflicts };
|
|
4931
|
+
}
|
|
4932
|
+
const file = path.join(home, '.cache', 'ruvnet-brain', 'model-routing', 'launcher-config.json');
|
|
4933
|
+
if (!fs.existsSync(file)) return { action: 'not-installed', terminal };
|
|
4934
|
+
const previous = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
4935
|
+
const receipt = installNativeLaunchers({ sourceRoot, home, extensionsRoot: previous.extensionsRoot, nodeBinary: previous.nodeBinary, apply: true });
|
|
4936
|
+
return { action: receipt.config.runtimeDigest === previous.runtimeDigest ? 'unchanged' : 'updated', runtimeDigest: receipt.config.runtimeDigest, terminal };
|
|
4937
|
+
}
|
|
4938
|
+
|
|
4939
|
+
export function syncManagedRouterTools({ routerDir = path.join(os.homedir(), '.claude', 'model-router'), packageRoot = REPO_ROOT } = {}) {
|
|
4940
|
+
const destination = path.join(routerDir, 'bin');
|
|
4941
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
4942
|
+
let copied = 0;
|
|
4943
|
+
for (const name of ['model-router-engine.mjs', 'model-router-setup.mjs', 'model-router-status.mjs', 'model-router-outcome.mjs', 'subscription-hosts.mjs', 'review-model-defaults.mjs', 'dual-host-deliberation.mjs', 'dual-host-suggest.mjs', 'route-cheap.mjs', 'dispatch-receipt.mjs', 'metaharness-receipts.mjs', 'metaharness-router.mjs', 'model-router-dispatch.mjs', 'model-currency.mjs', 'model-currency-evidence.mjs', 'user-model-prompt-hook.mjs', 'model-router-agent-hook.mjs', 'model-weekly-assessment.mjs', 'model-weekly-analyst.mjs', 'model-weekly-cycle.mjs', 'model-weekly-qualification.mjs', 'model-native-qualification.mjs', 'model-routing-launchers.mjs', 'model-terminal-gateway.mjs', 'model-terminal-launchers.mjs', 'claude-terminal-mod.mjs', 'model-native-catalog.mjs', 'model-analyst-sandbox.mjs', 'model-routing-policy-promotion.mjs', 'model-routing-gateway.mjs', 'native-subscription-usage.mjs', 'codex-routed.sh', 'goldie-weekly.sh']) {
|
|
4944
|
+
const source = path.join(packageRoot, 'scripts', name);
|
|
4945
|
+
if (!fs.existsSync(source)) throw new Error(`Packaged router tool missing: ${name}`);
|
|
4946
|
+
const target = path.join(destination, name);
|
|
4947
|
+
const bytes = fs.readFileSync(source);
|
|
4948
|
+
if (fs.existsSync(target) && fs.readFileSync(target).equals(bytes)) continue;
|
|
4949
|
+
const temporary = `${target}.${crypto.randomUUID()}.tmp`;
|
|
4950
|
+
try { fs.writeFileSync(temporary, bytes, { mode: name.endsWith('.sh') ? 0o755 : 0o600 }); fs.renameSync(temporary, target); }
|
|
4951
|
+
finally { fs.rmSync(temporary, { force: true }); }
|
|
4952
|
+
copied++;
|
|
4953
|
+
}
|
|
4954
|
+
// Preserve package-relative imports without replacing user policy.mjs overrides.
|
|
4955
|
+
for (const runtimeRelative of [path.join('plugin', 'scripts', 'runtime-preferences.mjs'),
|
|
4956
|
+
path.join('config', 'model-router', 'policy.default.mjs')]) {
|
|
4957
|
+
const runtimeTarget = path.join(routerDir, runtimeRelative);
|
|
4958
|
+
const runtimeBytes = fs.readFileSync(path.join(packageRoot, runtimeRelative));
|
|
4959
|
+
if (!fs.existsSync(runtimeTarget) || !fs.readFileSync(runtimeTarget).equals(runtimeBytes)) {
|
|
4960
|
+
fs.mkdirSync(path.dirname(runtimeTarget), { recursive: true });
|
|
4961
|
+
const temporary = `${runtimeTarget}.${crypto.randomUUID()}.tmp`;
|
|
4962
|
+
try { fs.writeFileSync(temporary, runtimeBytes, { mode: 0o600 }); fs.renameSync(temporary, runtimeTarget); }
|
|
4963
|
+
finally { fs.rmSync(temporary, { force: true }); }
|
|
4964
|
+
copied++;
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
const qualificationSource = path.join(packageRoot, 'config', 'model-router', 'qualification-contract.json');
|
|
4968
|
+
const qualificationTarget = path.join(routerDir, 'qualification-contract.json');
|
|
4969
|
+
const qualificationBytes = fs.readFileSync(qualificationSource);
|
|
4970
|
+
if (!fs.existsSync(qualificationTarget) || !fs.readFileSync(qualificationTarget).equals(qualificationBytes)) {
|
|
4971
|
+
const temporary = `${qualificationTarget}.${crypto.randomUUID()}.tmp`;
|
|
4972
|
+
try { fs.writeFileSync(temporary, qualificationBytes, { mode: 0o600 }); fs.renameSync(temporary, qualificationTarget); }
|
|
4973
|
+
finally { fs.rmSync(temporary, { force: true }); }
|
|
4974
|
+
copied++;
|
|
4975
|
+
}
|
|
4976
|
+
const launchers = routerDir === path.join(os.homedir(), '.claude', 'model-router')
|
|
4977
|
+
? refreshInstalledModelLaunchers({ sourceRoot: packageRoot }) : { action: 'custom-router-directory' };
|
|
4978
|
+
return { action: copied ? 'updated' : 'unchanged', copied, launchers };
|
|
4979
|
+
}
|
|
4980
|
+
|
|
4981
|
+
function refreshManagedRouterDefault() {
|
|
4982
|
+
try {
|
|
4983
|
+
const receipt = syncManagedRouterDefault();
|
|
4984
|
+
syncManagedRouterTools();
|
|
4985
|
+
if (receipt.action === 'created') ok('installed managed model-router default (custom overrides belong in policy.mjs)');
|
|
4986
|
+
if (receipt.action === 'upgraded') ok(`upgraded managed model-router default; prior source preserved at ${receipt.backup}`);
|
|
4987
|
+
return receipt;
|
|
4988
|
+
} catch (error) {
|
|
4989
|
+
warn(`managed model-router default was not refreshed (${error.message}); native dispatch remains fail-closed`);
|
|
4990
|
+
return { action: 'failed' };
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4852
4994
|
// ββ MetaHarness router: config materialization + THIS user's subscription profile (2026-07-12) ββ
|
|
4853
4995
|
// Stuart's mandate: subscription-awareness must be per-user. Detect what the machine can PROVE
|
|
4854
4996
|
// (Codex auth mode from ~/.codex/auth.json's SHAPE β never its secrets), ASK what it can't (Claude
|
|
4855
4997
|
// plan tiers aren't probeable from disk), and RECORD both with their basis, so the router's
|
|
4856
4998
|
// $0-floor never assumes a plan this user doesn't have (billing them) or misses one they do
|
|
4857
4999
|
// (wasting it). Config templates ship in the npm package's config/; router tools are copied to
|
|
4858
|
-
// ~/.claude/model-router/bin/ because the npx run dir vanishes after install.
|
|
4859
|
-
// user-
|
|
5000
|
+
// ~/.claude/model-router/bin/ because the npx run dir vanishes after install. Managed defaults
|
|
5001
|
+
// upgrade with a backup; custom policy.mjs, profiles and reviewed allocation overrides remain user-owned. Non-fatal like every offer.
|
|
4860
5002
|
export async function offerRouterProfile() {
|
|
4861
5003
|
if (TEST_MODE) return 'suppressed';
|
|
4862
5004
|
const routerDir = path.join(os.homedir(), '.claude', 'model-router');
|
|
4863
5005
|
const pkgRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
4864
5006
|
step(
|
|
4865
|
-
'
|
|
4866
|
-
"
|
|
5007
|
+
'Model router β appropriate intelligence for each task',
|
|
5008
|
+
"native subscription routes preserve your overrides; weekly analysis checks the evidence",
|
|
4867
5009
|
);
|
|
4868
5010
|
|
|
4869
5011
|
fs.mkdirSync(path.join(routerDir, 'bin'), { recursive: true });
|
|
4870
|
-
|
|
4871
|
-
const policyDst = path.join(routerDir, 'policy.default.mjs');
|
|
4872
|
-
if (fs.existsSync(policySrc) && !fs.existsSync(policyDst)) {
|
|
4873
|
-
fs.copyFileSync(policySrc, policyDst);
|
|
4874
|
-
ok('installed policy.default.mjs (edit freely β goldie keeps prices fresh where scheduled)');
|
|
4875
|
-
}
|
|
5012
|
+
refreshManagedRouterDefault();
|
|
4876
5013
|
// ONE implementation of the managed-catalog step, shared with runUpdate() (issue #87). It used to
|
|
4877
5014
|
// live here only, which is exactly why existing users never received managed additions.
|
|
4878
5015
|
try {
|
|
@@ -4882,19 +5019,17 @@ export async function offerRouterProfile() {
|
|
|
4882
5019
|
} catch (error) {
|
|
4883
5020
|
warn(`managed model additions were not merged (${error.message}); your existing catalog was left unchanged`);
|
|
4884
5021
|
}
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
//
|
|
4889
|
-
//
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
ok(`${copied} router tools at ~/.claude/model-router/bin/ (stable path β the npx dir vanishes)`);
|
|
4897
|
-
}
|
|
5022
|
+
const toolsReceipt = syncManagedRouterTools({ routerDir, packageRoot: pkgRoot });
|
|
5023
|
+
if (toolsReceipt.copied) ok(`updated ${toolsReceipt.copied} managed routing tools`);
|
|
5024
|
+
|
|
5025
|
+
// Allocation is user-owned. A shipped seed retains its real review date; installation never
|
|
5026
|
+
// certifies it fresh or overwrites a reviewed user policy.
|
|
5027
|
+
const selectionSrc = path.join(pkgRoot, 'config', 'model-router', 'routing-policy.template.json');
|
|
5028
|
+
const selectionDst = path.join(routerDir, 'routing-policy.json');
|
|
5029
|
+
if (fs.existsSync(selectionSrc) && !fs.existsSync(selectionDst)) fs.copyFileSync(selectionSrc, selectionDst);
|
|
5030
|
+
const instructionSrc = path.join(pkgRoot, 'config', 'model-router', 'weekly-analyst-instruction.md');
|
|
5031
|
+
const instructionDst = path.join(routerDir, 'weekly-analyst-instruction.md');
|
|
5032
|
+
if (fs.existsSync(instructionSrc) && !fs.existsSync(instructionDst)) fs.copyFileSync(instructionSrc, instructionDst);
|
|
4898
5033
|
|
|
4899
5034
|
const profilePath = path.join(routerDir, 'profile.json');
|
|
4900
5035
|
if (fs.existsSync(profilePath)) { ok('subscription profile already exists β routing already uses it'); return 'already'; }
|
|
@@ -1,78 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Model candidate catalog for model-router-engine.mjs. EDIT ME freely. Pricing is $/Mtok. 'verified' names the source+date a price was confirmed live; null pricing means UNKNOWN \u2014 the engine will NOT invent a cost for it (same honesty rule as route-cheap.mjs). 'harness' = which agent(s) can launch each model \u2014 EMPTY [] means landscape-only: known to exist, but no wired execution path yet, so the engine will never select it (see model-router-engine.mjs pool filter). Do not add a real execution path (an entry to route-cheap.mjs PRICING or a harness value) until dispatch is actually implemented and tested \u2014 an engine that 'chooses' a model it can't run is worse than one that doesn't know the model exists. 'subscription' = harness(es) under which this model is covered by a flat subscription (Claude Max, Codex/ChatGPT), so its marginal cost to the user is ~$0 \u2014 the default policy treats those as free and will NOT prefer a billed model over them (never spend where the subscription is free).",
|
|
3
|
-
"managedVersion":
|
|
4
|
-
"updated": "2026-
|
|
3
|
+
"managedVersion": 3,
|
|
4
|
+
"updated": "2026-10-04 (five native subscription launch receipts; Fable remains landscape-only)",
|
|
5
5
|
"candidates": [
|
|
6
6
|
{
|
|
7
7
|
"id": "claude-haiku-4-5-20251001",
|
|
8
8
|
"provider": "anthropic",
|
|
9
|
-
"harness": [
|
|
10
|
-
|
|
11
|
-
],
|
|
12
|
-
"subscription": [
|
|
13
|
-
"claude-code"
|
|
14
|
-
],
|
|
9
|
+
"harness": [],
|
|
10
|
+
"subscription": [],
|
|
15
11
|
"tier": "cheap",
|
|
16
12
|
"costPerMTok": null,
|
|
17
13
|
"verified": null,
|
|
18
|
-
"note": "
|
|
14
|
+
"note": "Historical model retained for inventory only. No default execution eligibility in this refreshed template."
|
|
19
15
|
},
|
|
20
16
|
{
|
|
21
17
|
"id": "claude-sonnet-5",
|
|
22
18
|
"provider": "anthropic",
|
|
23
|
-
"harness": [
|
|
24
|
-
|
|
25
|
-
],
|
|
26
|
-
"subscription": [
|
|
27
|
-
"claude-code"
|
|
28
|
-
],
|
|
19
|
+
"harness": [],
|
|
20
|
+
"subscription": [],
|
|
29
21
|
"tier": "mid",
|
|
30
22
|
"costPerMTok": null,
|
|
31
|
-
"verified": null
|
|
23
|
+
"verified": null,
|
|
24
|
+
"note": "Historical model retained for inventory only. No default execution eligibility in this refreshed template."
|
|
32
25
|
},
|
|
33
26
|
{
|
|
34
27
|
"id": "claude-opus-4-8",
|
|
35
28
|
"provider": "anthropic",
|
|
36
|
-
"harness": [
|
|
37
|
-
|
|
38
|
-
],
|
|
39
|
-
"subscription": [
|
|
40
|
-
"claude-code"
|
|
41
|
-
],
|
|
29
|
+
"harness": [],
|
|
30
|
+
"subscription": [],
|
|
42
31
|
"tier": "frontier",
|
|
43
32
|
"costPerMTok": {
|
|
44
33
|
"in": 5,
|
|
45
34
|
"out": 25
|
|
46
35
|
},
|
|
47
|
-
"verified": "2026-07-07 route-cheap.mjs FRONTIER (API price; $0 under Max)"
|
|
36
|
+
"verified": "2026-07-07 route-cheap.mjs FRONTIER (API price; $0 under Max)",
|
|
37
|
+
"note": "Historical model retained for inventory only. No default execution eligibility in this refreshed template."
|
|
48
38
|
},
|
|
49
39
|
{
|
|
50
40
|
"id": "claude-opus-5",
|
|
51
41
|
"provider": "anthropic",
|
|
52
|
-
"harness": [
|
|
53
|
-
|
|
54
|
-
],
|
|
55
|
-
"subscription": [
|
|
56
|
-
"claude-code"
|
|
57
|
-
],
|
|
42
|
+
"harness": [],
|
|
43
|
+
"subscription": [],
|
|
58
44
|
"tier": "frontier",
|
|
59
45
|
"costPerMTok": null,
|
|
60
46
|
"verified": "2026-08-02 Claude Code 2.1.220 subscription launch (canonicalModel claude-opus-5)",
|
|
61
|
-
"note": "
|
|
47
|
+
"note": "Historical model retained for inventory only. No default execution eligibility in this refreshed template."
|
|
62
48
|
},
|
|
63
49
|
{
|
|
64
50
|
"id": "claude-fable-5",
|
|
65
51
|
"provider": "anthropic",
|
|
66
|
-
"harness": [
|
|
67
|
-
|
|
68
|
-
],
|
|
69
|
-
"subscription": [
|
|
70
|
-
"claude-code"
|
|
71
|
-
],
|
|
52
|
+
"harness": [],
|
|
53
|
+
"subscription": [],
|
|
72
54
|
"tier": "frontier",
|
|
73
55
|
"costPerMTok": null,
|
|
74
56
|
"verified": "2026-07-12 Artificial Analysis leaderboard (WebSearch) \u2014 listed as top-intelligence tier alongside GPT-5.6 Sol",
|
|
75
|
-
"note": "
|
|
57
|
+
"note": "Historical model retained for inventory only. No default execution eligibility in this refreshed template."
|
|
76
58
|
},
|
|
77
59
|
{
|
|
78
60
|
"id": "deepseek/deepseek-chat",
|
|
@@ -123,16 +105,12 @@
|
|
|
123
105
|
{
|
|
124
106
|
"id": "gpt-5.5",
|
|
125
107
|
"provider": "openai",
|
|
126
|
-
"harness": [
|
|
127
|
-
|
|
128
|
-
],
|
|
129
|
-
"subscription": [
|
|
130
|
-
"codex"
|
|
131
|
-
],
|
|
108
|
+
"harness": [],
|
|
109
|
+
"subscription": [],
|
|
132
110
|
"tier": "frontier",
|
|
133
111
|
"costPerMTok": null,
|
|
134
112
|
"verified": null,
|
|
135
|
-
"note": "
|
|
113
|
+
"note": "Historical model retained for inventory only. No default execution eligibility in this refreshed template."
|
|
136
114
|
},
|
|
137
115
|
{
|
|
138
116
|
"id": "gpt-5.6-sol",
|
|
@@ -142,7 +120,7 @@
|
|
|
142
120
|
"tier": "frontier",
|
|
143
121
|
"costPerMTok": null,
|
|
144
122
|
"verified": null,
|
|
145
|
-
"note": "
|
|
123
|
+
"note": "Historical model retained for inventory only. No default execution eligibility in this refreshed template."
|
|
146
124
|
},
|
|
147
125
|
{
|
|
148
126
|
"id": "gpt-5.6-terra",
|
|
@@ -152,7 +130,7 @@
|
|
|
152
130
|
"tier": "mid",
|
|
153
131
|
"costPerMTok": null,
|
|
154
132
|
"verified": null,
|
|
155
|
-
"note": "
|
|
133
|
+
"note": "Historical model retained for inventory only. No default execution eligibility in this refreshed template."
|
|
156
134
|
},
|
|
157
135
|
{
|
|
158
136
|
"id": "gpt-5.6-luna",
|
|
@@ -162,19 +140,17 @@
|
|
|
162
140
|
"tier": "cheap",
|
|
163
141
|
"costPerMTok": null,
|
|
164
142
|
"verified": null,
|
|
165
|
-
"note": "
|
|
143
|
+
"note": "Historical model retained for inventory only. No default execution eligibility in this refreshed template."
|
|
166
144
|
},
|
|
167
145
|
{
|
|
168
146
|
"id": "gpt-5-mini",
|
|
169
147
|
"provider": "openai",
|
|
170
|
-
"harness": [
|
|
171
|
-
"codex",
|
|
172
|
-
"claude-code"
|
|
173
|
-
],
|
|
148
|
+
"harness": [],
|
|
174
149
|
"subscription": [],
|
|
175
150
|
"tier": "mid",
|
|
176
151
|
"costPerMTok": null,
|
|
177
|
-
"verified": null
|
|
152
|
+
"verified": null,
|
|
153
|
+
"note": "Historical model retained for inventory only. No default execution eligibility in this refreshed template."
|
|
178
154
|
},
|
|
179
155
|
{
|
|
180
156
|
"id": "x-ai/grok-4.5",
|
|
@@ -230,6 +206,104 @@
|
|
|
230
206
|
},
|
|
231
207
|
"verified": "2026-07-12 OpenRouter /models live (direct API check)",
|
|
232
208
|
"note": "LANDSCAPE-ONLY: 295B MoE Apache-2.0, strong generalist, second-tier coder (SWE-bench V 78.0 vs GLM-5.2 84.2, vendor numbers). IMPORTANT: this is the PAID slug on purpose \u2014 the tencent/hy3:free promo window closes ~2026-07-20 and would silently die/bill; never wire the :free slug. See goldie/2026-07-12.md Q3."
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"id": "claude-sonnet-5-5",
|
|
212
|
+
"provider": "anthropic",
|
|
213
|
+
"harness": [
|
|
214
|
+
"claude-code"
|
|
215
|
+
],
|
|
216
|
+
"subscription": [
|
|
217
|
+
"claude-code"
|
|
218
|
+
],
|
|
219
|
+
"tier": "mid",
|
|
220
|
+
"costPerMTok": {
|
|
221
|
+
"in": 2,
|
|
222
|
+
"out": 10
|
|
223
|
+
},
|
|
224
|
+
"verified": "2026-10-04 actual native subscription launch, exact modelUsage identity and MODEL_OK receipt; official API prices",
|
|
225
|
+
"note": "Verified native Claude subscription launch on the current machine. Per-user coverage still requires the router profile; higher effort settings are recommendations, not benchmark results."
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"id": "claude-opus-5-5",
|
|
229
|
+
"provider": "anthropic",
|
|
230
|
+
"harness": [
|
|
231
|
+
"claude-code"
|
|
232
|
+
],
|
|
233
|
+
"subscription": [
|
|
234
|
+
"claude-code"
|
|
235
|
+
],
|
|
236
|
+
"tier": "frontier",
|
|
237
|
+
"costPerMTok": {
|
|
238
|
+
"in": 4,
|
|
239
|
+
"out": 20
|
|
240
|
+
},
|
|
241
|
+
"verified": "2026-10-04 actual native subscription launch, exact modelUsage identity and MODEL_OK receipt; official API prices",
|
|
242
|
+
"note": "Verified native Claude subscription launch on the current machine. Per-user coverage still requires the router profile; higher effort settings are recommendations, not benchmark results."
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"id": "claude-fable-5-1",
|
|
246
|
+
"provider": "anthropic",
|
|
247
|
+
"harness": [],
|
|
248
|
+
"subscription": [],
|
|
249
|
+
"tier": "frontier",
|
|
250
|
+
"costPerMTok": {
|
|
251
|
+
"in": 10,
|
|
252
|
+
"out": 50
|
|
253
|
+
},
|
|
254
|
+
"verified": "2026-10-04 official provider documentation + fresh native inventory",
|
|
255
|
+
"note": "Landscape only until native launch/coverage is verified. Account catalog presence or historical observation is not a fresh execution proof."
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"id": "gpt-6.1-sol",
|
|
259
|
+
"provider": "openai",
|
|
260
|
+
"harness": [
|
|
261
|
+
"codex"
|
|
262
|
+
],
|
|
263
|
+
"subscription": [
|
|
264
|
+
"codex"
|
|
265
|
+
],
|
|
266
|
+
"tier": "mid",
|
|
267
|
+
"costPerMTok": {
|
|
268
|
+
"in": 2,
|
|
269
|
+
"out": 10
|
|
270
|
+
},
|
|
271
|
+
"verified": "2026-10-04 actual native Codex subscription launch, requested model, completed turn and MODEL_OK receipt; official API prices",
|
|
272
|
+
"note": "Verified native Codex subscription launch on current machine. Coverage remains profile-scoped; selection does not switch the parent conversation."
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"id": "gpt-6-luna",
|
|
276
|
+
"provider": "openai",
|
|
277
|
+
"harness": [
|
|
278
|
+
"codex"
|
|
279
|
+
],
|
|
280
|
+
"subscription": [
|
|
281
|
+
"codex"
|
|
282
|
+
],
|
|
283
|
+
"tier": "cheap",
|
|
284
|
+
"costPerMTok": {
|
|
285
|
+
"in": 0.1,
|
|
286
|
+
"out": 0.5
|
|
287
|
+
},
|
|
288
|
+
"verified": "2026-10-04 actual native Codex subscription launch, requested model, completed turn and MODEL_OK receipt; official API prices",
|
|
289
|
+
"note": "Verified native Codex subscription launch on current machine. Coverage remains profile-scoped; selection does not switch the parent conversation."
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"id": "gpt-6-astra",
|
|
293
|
+
"provider": "openai",
|
|
294
|
+
"harness": [
|
|
295
|
+
"codex"
|
|
296
|
+
],
|
|
297
|
+
"subscription": [
|
|
298
|
+
"codex"
|
|
299
|
+
],
|
|
300
|
+
"tier": "frontier",
|
|
301
|
+
"costPerMTok": {
|
|
302
|
+
"in": 10,
|
|
303
|
+
"out": 50
|
|
304
|
+
},
|
|
305
|
+
"verified": "2026-10-04 actual native Codex subscription launch, requested model, completed turn and MODEL_OK receipt; official API prices",
|
|
306
|
+
"note": "Verified native Codex subscription launch on current machine. Coverage remains profile-scoped; selection does not switch the parent conversation."
|
|
233
307
|
}
|
|
234
308
|
]
|
|
235
309
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"name":"rnb-terminal-routing","version":"0.1.0","description":"Reviewed per-user native terminal routing"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Updated: 2026-10-04 16:10:00 EDT | Version 0.1.1
|
|
2
|
+
Created: 2026-10-04 16:10:00 EDT
|
|
3
|
+
|
|
4
|
+
# Native terminal routing prototype
|
|
5
|
+
|
|
6
|
+
Tested against the installed Claude Code 2.1.289 native binary and its generated mod declarations. Mods require 2.1.287 or later. This routes the native host's own `turn.step` middleware: it passes model and effort to `yield* next(...)`, preserving the native streaming result and TUI. It does not call a separate inference API or inject slash commands.
|
|
7
|
+
|
|
8
|
+
`prepareClaudeTerminalMod({ destination })` in `scripts/claude-terminal-mod.mjs` materializes a private plugin directory, generates absolute Node/helper/engine paths, and copies the approved pure classifier into the mod. The source directory is deliberately unprepared. No Node APIs run inside sandboxed mod code. `$.process.run` invokes the bridge with prompt JSON on stdin, never in arguments. The existing policy engine validates current per-user catalog, profile, reviewed model/effort allocation and subscription eligibility. Review age remains evidence for the weekly refresh and does not revoke the owner-approved allocation; it supplies no native/default/metered fallback.
|
|
9
|
+
|
|
10
|
+
A launcher must provide `RNB_CLAUDE_MOD_NONCE` (64 lowercase hexadecimal characters) and `RNB_CLAUDE_MOD_RECEIPT` (absolute receipt filename in an already prepared private directory). `session.start` calls the bridge to write an atomic, mode-0600 activation receipt. The launcher must compare nonce, exact real plugin root, native version, source digest, timestamp and live process state before accepting startup. `prepareClaudeTerminalMod` returns `modDigest`; `terminalModDigest` recomputes it. The receipt's `pid` is the bridge parent's mod worker, not an asserted main CLI PID. `--health` supports the same bounded receipt schema; it does not invent a native crash guard.
|
|
11
|
+
|
|
12
|
+
`prompt.submit` approves the prompt once. Its optional `turnId` denotes a running turn, not the upcoming idle turn. A bounded FIFO stores pending text and decisions in RAM only. `turn.start` binds the next approved prompt to the newly minted exact turn ID. If native settings hooks settled different text, the bridge reclassifies that text using the original task class as a minimum. Image-only submissions use a conservative hard-review classification while passing original attachments and text unchanged. Every backend step keeps the bound model/effort and exact turn ID/index. Completion and session end remove decisions. Queue/active maps cap at 32 entries; pending prompts expire after five minutes. Direct mid-turn deliveries (a running `turnId` without `wait`) refuse because no new authoritative `turn.start` exists; queue the prompt to start its own turn. Subagent steps and unbound continuations refuse instead of borrowing the main turn's decision.
|
|
13
|
+
|
|
14
|
+
A prompt/helper failure returns `{ drop }` without `next`. A step failure returns an immediate native refusal result without `next`, retaining turn ID/index. Every consequential hook has a `.catch` handler whose refusal does no process, filesystem or model work. Missing activation refuses subsequent prompts and steps.
|
|
15
|
+
|
|
16
|
+
## Failure boundary
|
|
17
|
+
|
|
18
|
+
Claude Code can skip all hooks if the mod fails to load, the native mod worker crashes, hooks are disabled, another guard refuses registration, or a hook and its catch handler both fail. Other mods later in the chain can rewrite model/effort. A startup receipt proves that the activation hook ran for that nonce and source digest; it is not a perpetual guarantee that routing hooks remain active. This prototype cannot claim universal fail-closed enforcement. The root CLI wrapper owns the startup guard and any further runtime monitoring. Native model inference and rendered TUI acceptance remain separate checks; plugin tests stub the engine boundary and incur no inference.
|
|
19
|
+
|
|
20
|
+
## Checks
|
|
21
|
+
|
|
22
|
+
Run `claude plugin validate config/model-router/claude-terminal-mod`, `claude plugin test config/model-router/claude-terminal-mod`, and `vitest run tests/unit/claude-terminal-mod.test.mjs`. The native tests exercise the installed mod runtime, including two different turn allocations, high coding effort, final-text class floors, missing decisions and helper exceptions. Unit tests call the actual policy engine against isolated catalog/profile/policy fixtures, including stale allocation, invalid policy, absent hard model and disabled subscription states.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"modules":["./register.js"]}
|