Roadmap "Next" wave: ledger read-path flip, optional embeddings tier, Playwright visual gate#35
Merged
CodeWithJuber merged 6 commits intoJul 7, 2026
Conversation
The PCM ledger has been the convergent write store since P1 while the legacy stores served every read, so knowledge arriving via `forge ledger merge` never reached injection. New src/ledger_read.js maps lesson claims onto the legacy lesson shape (status derived from evidence: tombstoned → retired, val ≥ 0.6 → active, val < 0.45 with a contradiction → quarantined, else candidate) and exposes merged lesson/fact reads deduped by legacy id/slug with the local file winning — best-effort, so a missing or corrupt ledger degrades to legacy-only. Flipped read surfaces: cortex lessonsForContext/startupBlock/summary, substrate advisory, route past-mistake density, recall list/MEMORY.md, brain AGENTS.md index. Write paths (recordMistake's confirm-vs-create lookup, recordContradiction, applyDistillation) deliberately keep reading the legacy files they edit; convergence comes from content-addressed claim ids. reconcileFacts now only tombstones locally-authored claims so a merged teammate fact survives `forge recall consolidate`. Legacy formats are still written — full retirement is the next step (ROADMAP updated). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fc2MvWJbQ1cyNQ76ymv4hs
…styles through the design gate
The static fingerprint (src/uifingerprint.js) parses source CSS/classes and
cannot see rendered reality. New src/uivisual.js closes the loop (spec
07-ui-quality-gate.md §5) while keeping package.json dependency-free
(ADR-0005 optional tier):
- resolvePlaywright(): dynamic import of playwright-core/playwright, or an
explicit FORGE_PLAYWRIGHT module path; null on absence, never a throw.
- renderedFingerprint(): headless chromium at 1280x800 + 390x844, walks
visible elements and lifts computed color/background-color, margin/
padding/gap, font-family, border-radius, box-shadow — margins via the
Typed OM so used auto-margins (centering residue) don't pollute the
spacing scale, zero-client-rect elements (e.g. <option>) skipped so UA
stylesheet noise stays out. Values serialize into synthetic CSS fed to
the SAME fingerprintText vector as the static gate; one screenshot per
viewport lands in .forge/ui/.
- visualGate(): rendered fingerprint through the identical uiGate +
scaleChecks + taste-profile pipeline as `uicheck design`.
- CLI: `forge uicheck visual <file-or-url> [--taste <name>] [--json]
[--remote]`. Missing browser runtime prints an enable hint and exits 0
("skipped" is not failure); gate failure exits 1. Security default:
non-loopback http(s) targets are refused — fetching arbitrary URLs by
default is an exfiltration hazard — with --remote as the explicit
override, enforced before any browser work.
Tests: 10 new (URL guard incl. lookalike hosts, computed-record → vector
mapping, graceful absence, CLI exit codes) + one live test that auto-skips
without a runtime; verified live against src/dash.html — the rendered
fingerprint reproduces the static gate's findings (4px spacing base, 100%
on-scale, radii [4,10], 0 shadows). Docs: GUIDE section, CHANGELOG, spec §5
status, ROADMAP.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fc2MvWJbQ1cyNQ76ymv4hs
# Conflicts: # CHANGELOG.md
…near-match MinHash rel is weak on very short specs (the documented honest limit): one changed word swings the estimated Jaccard below threshold. This adds the ADR-0005 optional tier — a pluggable embedding provider that, when configured, replaces the lexical similarity in Eq. 3 retrieval and the reuse cache's near-match. MinHash remains the always-working, zero-dependency default. - src/embed.js (stdlib only): FORGE_EMBED=cmd:<shell-command> (JSON over stdin/stdout — any local model or script) or http:<url> (OpenAI-compatible, $FORGE_EMBED_MODEL / $FORGE_EMBED_KEY via env only, never logged, never in argv); getProvider() memoized per env value; embed() with timeout, size caps, and best-effort null on any failure; cosine(); content-hash-keyed disk cache (.forge/embed-cache.jsonl, corrupt-tolerant reader, size-capped truncate-oldest) so repeated retrieval never re-pays the provider. - src/ledger.js: score()/retrieve() accept an optional sim(query, claim); the pure core never imports embed.js — callers assemble the sim. A null or non-finite sim value falls back to MinHash per claim; negatives clamp to 0. - src/reuse.js: lookup() takes the same optional sim; reuseQuery/reusePeek construct it via embed.claimSim when FORGE_EMBED is set. Cosine thresholds NEAR_COS/ADAPT_COS = 0.85/0.7 sit above Jaccard's 0.8/0.6 because dense cosines have a higher noise floor (unrelated sentences score 0.4–0.6 where unrelated shingle sets sit near 0); per-candidate MinHash fallback when a vector is missing; LSH prefilter skipped under sim (banding indexes sketches, not vectors). - CLI: no new flags — the env var is the switch; forge ledger query and forge reuse query print the backend that served (sim: minhash / sim: embed(cmd)) so results are explainable. - Tests (no network): deterministic fake provider fixture proves the short reworded spec MinHash misses becomes a near hit with the provider; crash/garbage/timeout degrade cleanly to MinHash; the disk cache avoids re-spawning (spawn-counted via side-effect file); CLI backend lines. 401 baseline tests stay green (now 412); tsc 0 errors; biome clean. - Docs: README honest limits, GUIDE FORGE_EMBED section, CHANGELOG. package.json untouched — dependencies stays absent (ADR-0005 condition 3). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fc2MvWJbQ1cyNQ76ymv4hs
…l and reuse # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ships the three items at the top of ROADMAP "Next", built in parallel worktrees and integrated on top of v0.6.0.
1. Ledger read-path flip (
src/ledger_read.js)Reads are now a merged view (legacy ∪ ledger), so teammate knowledge that arrives with
forge ledger mergeactually reaches injection and retrieval:claimToLessonmaps ledgerlessonclaims onto the legacy lesson shape with an evidence-derived status: tombstoned → retired, val ≥ 0.6 → active, val < 0.45 with a contradiction → quarantined, else candidate.lessonsForContext/startupBlock/summary(and through themforge cortex, the MCP server, doctor, and the session-start/pre-edit hooks), the substrate advisory count, routing's past-mistake density,recall list/ MEMORY.md, and brain's AGENTS.md index.recordMistake's confirm-vs-create lookup,recordContradiction,applyDistillation) deliberately keep reading the legacy store they edit — convergence comes from content-addressed claim ids.reconcileFactsnow only tombstones locally-authored claims, soforge recall consolidatecan no longer silently delete merged teammate facts.forge ledger merge, injects intostartupBlock/lessonsForContextwith no legacy file materialized.2. Optional embeddings tier (
src/embed.js, ADR-0005)FORGE_EMBED=cmd:<command>(stdin/stdout JSON protocol — any local model or script) orFORGE_EMBED=http:<url>(OpenAI-compatible;$FORGE_EMBED_MODEL/$FORGE_EMBED_KEY, key never logged).forge reuse queryandforge ledger queryreplace the MinHashrelterm with embedding cosine (near/adapt ≥ 0.85/0.7 — a higher bar than Jaccard's 0.8/0.6 to match dense cosine's noise floor), fixing the documented weak spot on very short specs. Both commands print the backend that served (sim: minhash/sim: embed(cmd))..forge/embed-cache.jsonl(content-hash keyed, corrupt-line tolerant, truncate-oldest under a 4 MiB cap). Any provider failure — crash, timeout, garbage, wrong count — degrades silently to MinHash.dependenciesstays empty; the pure ledger core never imports the tier (callers injectsim).3. Playwright visual gate (
forge uicheck visual <file-or-url>)auto-margins kept symbolic via the Typed OM; never-painted UA noise filtered bygetClientRects()), and runs the identicaldesigngate over that rendered vector. Screenshots land in.forge/ui/.npm i -D playwright-coreorFORGE_PLAYWRIGHT=<path>enables it.file://; http(s) only for loopback hosts; non-local URLs refused with an explicit exfiltration warning (--remoteto override); lookalike hosts and non-web schemes refused.Verification
npm test: 437 pass, 0 fail, 1 auto-skip (401 baseline unmodified + 16 readflip + 11 embed + 10 visual; the skip is the Playwright live test without a configured browser runtime — it ran 411/411 with one in the build worktree).npm run typecheck: 0 errors; biome clean on all touched files;node scripts/bump.mjs check: version fields agree at 0.6.0.forge doctorall clear;forge ledger queryprints the newsim:backend line live.🤖 Generated with Claude Code
https://claude.ai/code/session_01Fc2MvWJbQ1cyNQ76ymv4hs
Generated by Claude Code