Skip to content

feat(router): universal cross-provider router + research-audit gap fixes - #152

Merged
CodeWithJuber merged 13 commits into
masterfrom
feat/universal-router-research-gaps
Sep 22, 2026
Merged

CodeWithJuber merged 13 commits into
masterfrom
feat/universal-router-research-gaps

Conversation

@CodeWithJuber

@CodeWithJuber CodeWithJuber commented Sep 22, 2026

Copy link
Copy Markdown
Owner

Merged at f64c540. Four follow-up commits were pushed to this branch after the merge, so they are not in it. They are re-applied on top of v1.1.0 in #153:

  • the session learner on macOS;
  • the Windows home folder;
  • the Sonnet 5 price;
  • the release post-publish check.

Summary

Adds the opt-in universal router (forge route universal|outcome|fit|models) and closes seven gaps from a research-to-code audit. It is an 11-patch series applied with git am onto v1.0.0 (61385b3), plus two small commits.

  • Universal router. It recommends a model, or a cascade of models, across any provider. Model data lives in data/models.json and .forge/models.json, not in code, and a test enforces that.
    • What it learns: a multidimensional IRT model estimates each model's ability and each task's difficulty. Correlated failures across models are integrated with Gauss–Hermite quadrature, and a log-cost model estimates what each attempt costs.
    • What it picks: the cheapest cascade for the chosen objective: match-best-single (the default), target:p, value:$ or budget:$.
    • How it updates: a Bayesian refit from recorded outcomes (forge route outcome then forge route fit).
    • The default is unchanged: the tiered forge route (haiku / sonnet / opus / fable) stays as it is.
  • Audit gaps closed:
    • Commit gate on binary files: a binary file (git reports it binary and it contains a NUL byte) is scanned with the credential-format rules only. The fixed XMP packet id no longer counts as a secret, and a ghp_ token inside a binary is still refused.
    • B02: the "repeated gates multiply catch rates" claim is removed; the stated law is now (1−p)(1−c_max).
    • C09: the handoff writer and loader share one byte budget.
    • E03: the substrate pre-action check (and so the prompt hook) and the Stop gate's repair checklist also list sibling and forward files, each tagged with its relation.
    • verifyToken: the docs now show the 0.88 the code actually computes.
    • A13: learned-lesson consolidation is deterministic and driven by ledger evidence; the model path needs an explicit --llm.
  • Cleanup commit: the series re-serialized package.json and turned the description's em dash into a JSON unicode escape. The literal character is restored.
  • macOS fix (from this PR's first CI run): learn-consolidate.sh --llm wrapped the model call in GNU timeout, which stock macOS doesn't ship.

What stays the same

Verification

Run locally on Windows (Node 24) before the macOS fix; that fix changed no test count. CI was green on all 12 checks at f64c540.

Check Result
npm test 1,385 tests: 1,379 pass, 0 fail, 6 skipped
npm run lint 0 errors, 14 warnings (same as master)
npm run typecheck 0 errors
forge docs check passes

Why 6 tests skip here:

  • 4 are Windows-only guards: three verify polyglot tests, plus the learn-consolidate bash test.
  • 2 need opt-in settings: RUN_INTEGRATION and FORGE_PLAYWRIGHT.
  • The author's Linux run reported 1,382 pass and 3 skipped. It lists totals only, so its 3 skips are unnamed.

Consolidation script: run by hand in Git Bash, with a stub claude first on PATH and the real CLI kept off it.

  • The default path made 0 calls to claude.
  • --llm made exactly 1, to the stub.
  • With neither timeout nor gtimeout on PATH (the stock macOS setup), the original script never called the stub. The fixed one called it once, exited 1 and printed "originals kept", which is what the test asserts.

Router smoke test: forge route universal "Add pagination to the users endpoint" suggests gpt-5-mini, then minimax-m2.5 if a check fails. That gives P(success) 0.86 at an expected $0.040, against the best single model, claude-opus-4.6, at 0.84 and $0.271. The command writes no files.

Known limits

  • Benchmark not re-run here. Two results come from the author's pre-registered run 4 and were not reproduced for this PR:
    • 76.3% solved at $0.093 per task, against 75.1% at $0.364 for the best single model, on 350 held-out issues.
    • target:p is optimistic by 4–6 points.
  • Binary relaxation gap (inferred from the code, not tested). A text file with a deliberately added NUL byte skips the entropy check. The gate targets accidental leaks, so this is accepted.
  • Windows home-directory mismatch. learnedDir() uses node's homedir(), which reads USERPROFILE on Windows, while the bash learner writes under $HOME. Fixed in fix: follow-ups to #152 — session learner on macOS, Sonnet 5 price, release npm check #153.
  • Model ids. claude-fable-5 and claude-opus-4-8 are valid ids: Anthropic lists both under "Legacy models (still available)".
    • The current models are claude-fable-5-1 and claude-opus-5, at the same prices.
    • Moving the tier table onto them is a product decision, so this PR doesn't do it.
  • Disclosure from the author. While the A13 test was being written, it twice ran the real claude -p in the build environment. It sent the fixed consolidation prompt plus one heading line; no lesson text and no secrets. The test now puts a stub first on PATH.

The router design, benchmark and research audit are from the patch author.

🤖 Generated with Claude Code

CodeWithJuber and others added 12 commits September 22, 2026 14:01
The staged secret scan reads every file with `git diff --text`, and the
entropy leg of hasSecret flagged the XMP packet id W5M0MpCehiHzreSzNTczkc9d
that the XMP packet wrapper carries inside PDFs, JPEGs and PNGs, so
ordinary binary commits were refused.

- A staged file that git reports as binary (`--numstat` prints `-\t-`) AND
  whose added bytes contain a NUL (git's own content test) is scanned with
  SECRET_RE / CASE_RE only. A `binary` attribute on a text file does not
  qualify, so .gitattributes cannot switch the entropy leg off.
- The XMP packet id is a public constant, exempt from the entropy leg like
  lockfile integrity digests (whole-token match only).
- hasSecret / redactSecrets take `{ entropy: false }`; defaults unchanged.

The unscanned-file fail-closed path is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpQ15QdqdDUJLavNhExwvL
The formal synthesis (§5.3, corrected 2026-09-21) withdrew the claim that
running the same check at Stop, pre-commit and CI multiplies catch rates:
on the same diff the copies fire together, so they are nested checks and
the residual is (1−p)(1−c_max), not (1−p)·∏(1−cⱼ).

Rewrites the headers of src/commit_gate.js and src/gate.js, the two
ARCHITECTURE.md passages (§5 intro and the commit-boundary gate), and the
Mintlify verification-gates intro to the corrected law, and states what the
commit rung does add: catches for edits after the turn, hosts or sessions
where the Stop hook never ran, and sessions whose one Stop block was spent.
The gate.js header no longer calls the gate a cⱼ≈1 layer; its catch rate on
real misses is unmeasured. No behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpQ15QdqdDUJLavNhExwvL
writeState bounded the snapshot at 150 lines while stateBlock injected
only 80 at SessionStart, so rows 81-150 of a valid handoff were silently
dropped: the A4/A5 budget mismatch the formal synthesis's T4 correction
names, reproduced in lines.

- STATE_BUDGET_BYTES (8192, the synthesis's A5 cap) is the single budget,
  in one unit (UTF-8 bytes of the snapshot body), used by both sides.
- selectSnapshot keeps rows in A4 priority order (goal + criteria, next,
  decisions, gotchas + assumptions, in-progress, done) until the body fits;
  every header stays, and a cut section ends with "(+N more not kept …)".
  Sections are written in that order, so even a loader cut of a
  hand-edited file drops the least important rows first.
- stateBlock applies the same budget; a snapshot writeState produced always
  arrives whole. `maxLines` options are replaced by `budget`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpQ15QdqdDUJLavNhExwvL
… callers

The substrate pre-action check (and through it the ambient prompt hook and
the FORGE_ENFORCE gate) and the Stop gate's repair checklist ran the
reverse-only walk the empirical refutation measured at recall 0.022, where
94.7% of the misses were sibling files (audit E03).

- substrateCheck walks IMPACT_RELATIONS by default (the frozen SIBLING /
  FORWARD parameters already in atlas.js) and returns impact.relations,
  impact.fileRelations (file -> strongest relation) and relationCounts.
  renderSubstrate and the ambient advisory print `path (relation)` with a
  per-relation count and a one-line legend.
- repairReason lists the untouched co-change candidates, tagged.
- enforceDecision counts DEPENDENT_RELATIONS (reverse, llm-verified) toward
  its 25-file block by default and names the other candidates in its reason;
  `blastRelations` counts more. Counting everything would put 79 of this
  repo's 98 source files over the threshold (35 today) at precision ~0.09.
- impact(): a sibling/forward offer never relabels a reverse dependent, so a
  wide walk's reverse-tagged set equals the reverse-only answer. New helpers
  fileRelations, byRelation, relationRank, RELATION_ORDER.
- Scope decomposition and lesson matching keep the dependent set; predicted
  tests are ordered dependents first.
- `relations: ["reverse"]` is the explicit reverse-only option; forge impact,
  predict_impact, imagine and context keep their reverse-only default.
- source/substrate.json: impact faculties operational-v1 ->
  operational-v2-recall with a scoped guarantee; docs updated.

Tests use the serializer/deserializer/wire_format fixture for every caller,
including the real hook entrypoint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpQ15QdqdDUJLavNhExwvL
…mputes

ARCHITECTURE.md, docs/GUIDE.md, docs/cognitive-substrate/README.md and the
src/preflight.js comment said "Change verifyToken in src/auth.js to require
length > 20; update tests" scores ≈ 0.63 (medium risk); the code gives
0.878 (low risk). Re-running the pre-df1c5a3 preflight gives exactly 0.630:
the prior was hand-set when the task had one concrete anchor (the
filename), and df1c5a3 (2026-09-21) made a named code identifier a second
anchor. The docs now show 0.88 and explain the change; the weights are
untouched. A test pins 0.23 / 0.88 (0.63 at one anchor) and checks that the
two documented example outputs print the computed line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpQ15QdqdDUJLavNhExwvL
…odel

bin/learn-consolidate.sh sent every learned lesson to Haiku with "DROP
anything ... contradicted" and rewrote ~/.claude/skills/learned from the
answer: memory pruned by the model's own judgment, which the research
rejects (audit A13 / H25).

src/learn_consolidate.js makes the default path deterministic:
- exact and near-duplicate lessons within a project merge into their first
  occurrence (MinHash Jaccard >= 0.7, the ledger's clusters() threshold);
- a lesson is dropped only when its matching ledger claim (lesson/fact) in
  the same project is dormant (isDormant), tombstoned, or in the attic;
  a lesson with no matching claim is kept;
- originals are archived first, as before; --dry-run and --json report.

The script execs it by default (`--repo <root>` names the ledger, default
the cwd). The Haiku rewrite stays behind an explicit `--llm` first argument,
with "contradicted" removed from its prompt. Tests stub `claude` on PATH so
the suite never calls a model.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpQ15QdqdDUJLavNhExwvL
… + correlated cascade policy)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpQ15QdqdDUJLavNhExwvL
…d data in package files

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpQ15QdqdDUJLavNhExwvL
…command table; changelog

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpQ15QdqdDUJLavNhExwvL
The router patch series re-serialized package.json and wrote the
description's em dash as a — escape. Same string once parsed, but an
unrelated diff line; restore the literal character so the only package.json
change on this branch is the new "data" entry in "files".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@CodeWithJuber
CodeWithJuber force-pushed the feat/universal-router-research-gaps branch from f70ff14 to d6014b5 Compare September 22, 2026 12:01
bin/learn-consolidate.sh wrapped `claude -p` in `timeout 180`, a GNU
coreutils command stock macOS does not ship. With stderr sent to /dev/null
the missing command failed silently, claude never ran, and every --llm run
on a Mac ended in "response too short". The stub-claude test added in this
branch exposed it: Install smoke (macos-latest) failed reading calls.log.

The call now goes through `limited`: `timeout`, else Homebrew's `gtimeout`,
else the bare command. Checked in Git Bash with a PATH that has neither
(stub called once, exit 1, "originals kept") and with `timeout` present.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@CodeWithJuber
CodeWithJuber merged commit 97b2fc0 into master Sep 22, 2026
12 checks passed
CodeWithJuber added a commit that referenced this pull request Sep 22, 2026
fix: follow-ups to #152 — session learner on macOS, Sonnet 5 price, release npm check
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant