Skip to content

Commit eb68ea9

Browse files
Merge pull request #30 from CodeWithJuber/claude/whitepaper-implementation-plan-x8f7bh
feat: substrate-v2 P5–P8 — loop closure, UI quality gate, forge dash, measured cost report
2 parents 2511fd4 + b92ee45 commit eb68ea9

20 files changed

Lines changed: 2904 additions & 4 deletions

CHANGELOG.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

99
### Added
1010

11+
- **Loop closure (P5 of the substrate-v2 plan): doom-loop diagnosis, imagination, CUSUM
12+
drift, checkpoint cadence.** `forge diagnose "<error>"` hashes each failure into a
13+
signature (line numbers, addresses, timestamps, and absolute paths normalized out) and
14+
counts recurrences in a 50-entry ring; the 3rd identical hit is thrash — it mints a
15+
content-addressed `diagnosis` claim into the team ledger and tells the agent to STOP
16+
retrying and escalate ONE model tier with the diagnosis as the prompt's head (the same
17+
loop becomes a one-per-team event, not one-per-session). `forge imagine "<task>"` is the
18+
static half of the consequence simulator (paper Eq. 4): entities → blast radius →
19+
predicted breaks with confidence, plus the minimal dry-run test suite via weighted greedy
20+
set cover (weight = file size as a duration proxy; classic ln-n approximation) and
21+
`riskScore = Σ confidence` — the sandboxed worktree runner that executes the suite is the
22+
P5 follow-up. `anchor.cusum()` adds the M4 one-sided CUSUM control chart (k = 0.35,
23+
h = 1.0): sustained small drift alarms, a single exploratory spike drains back to zero.
24+
`verify.checkpointCadence()` prices M6's "when to check?" as the optimal-stopping
25+
threshold rule `n* = ⌈checkCost / (pErr·tokensPerStep·costPerToken)⌉`, clamped to
26+
[1, 50] — every input measured or priced, no magic constants.
27+
1128
- **Context assembly + completeness gate (P4 of the substrate-v2 plan).** `forge context
1229
"<task>"` makes what goes into the window a budgeted optimization and makes
1330
*sufficiency* a computed set. The required-knowledge set `R(edit)` — the target's
@@ -20,6 +37,43 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
2037
implements it?"), shown in `forge substrate` and — under `FORGE_ENFORCE=1` — blocking:
2138
acting on missing context is acting on a guess. Incomplete context stops being a
2239
feeling and starts being a set difference.
40+
- **Generated-UI quality gate (P6 of the substrate-v2 plan).** Taste becomes measurable:
41+
`src/uifingerprint.js` extracts a deterministic design fingerprint from CSS/JSX/Tailwind
42+
classes — pure static parsing, no LLM, no screenshots — covering palette (HSL + 12-bin hue
43+
histogram), spacing (base unit by residual-minimization approximate GCD, on-scale
44+
fraction), font families, radius and shadow levels. Two distances gate generated UI:
45+
`slopDistance` to a shipped, rationale-documented generic-template signature set
46+
(default-Tailwind blue/indigo, stock Bootstrap, the AI-landing gradient) must stay HIGH,
47+
and `conformance` to the project's own fingerprint — stored as a shared `fingerprint`
48+
ledger claim via `mintProjectFingerprint` — must stay LOW; `uiGate` failures are
49+
actionable per-feature edits, never a bare score. Scale-conformance checks
50+
(spacing-on-base, radius/shadow level caps, palette bound) join `ASSERTABLE_CHECKS`.
51+
`forge uicheck` gains `fingerprint <file...> [--mint]` and `design <file...>` (exit 1 on
52+
fail) alongside the unchanged contrast math.
53+
- **Local dashboard (P7 of the substrate-v2 plan).** `forge dash [--port N]` serves a
54+
read-only lens on the substrate's state: a `node:http` stdlib server (localhost-only,
55+
zero runtime deps) with ONE self-contained HTML page — inline CSS/JS, no CDN, no
56+
framework, no build step. Panels: Ledger (claims with val bars, kind filter, contested
57+
claims — val ∈ [0.4, 0.6] with ≥1 contradiction — and per-author trust), Cost/Cache
58+
(stage counters + measured saved-token estimates from `.forge/metrics.jsonl`), and
59+
Impact (atlas blast-radius explorer via `/api/impact?target=X`). Every claim row shows
60+
its `forge ledger blame <id>` command — no unexplained scores anywhere in the UI. Data
61+
is separated from serving (`dashData()` vs `serve()` in `src/dash.js`) so the payload
62+
is tested without sockets, and corrupt/missing stores degrade to empty sections instead
63+
of taking down the lens. The ratify/retract POSTs are a follow-up; this phase never
64+
writes.
65+
- **Measured cost report (P8 of the substrate-v2 plan).** `forge cost --stages [--json]`
66+
computes per-stage cost factors as pure arithmetic over `.forge/metrics.jsonl`
67+
(`src/cost_report.js`): gate halt rate, tier-weighted cache hit rate (exact 1.0 / near
68+
0.85 / adapt 0.5), route saving priced against the always-premium baseline, and context
69+
assembly — then composes `C = C₀ · Π(1 − fᵢ)` over ONLY the measured stages. A stage with
70+
no events reports "no data", never a default; the composed figure is a lower bound whose
71+
caveats name every unmeasured stage; the paper's 62 % routing figure is cited as context,
72+
and ~90 % appears only as a labeled target. `substrateCheck` now meters the assumption
73+
gate on the explicit path (one `gate` halt/pass line per decision; ambient hooks stay
74+
write-free), `recordGate`/`recordRoute` give future stage wiring one obvious call each,
75+
and `reports/cost-eval.md` scaffolds the paired-run harness report with a truthful
76+
empty state.
2377

2478
- **Proof-carrying reuse cache (P3 of the substrate-v2 plan).** `forge reuse` turns
2579
"reuse already-generated code" from prose into a deterministic system: verified code

reports/cost-eval.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Cost evaluation — measured stage factors (P8)
2+
3+
> Status: **no data yet.** This document is the artifact the P8 harness
4+
> ([docs/plans/substrate-v2/05-cost-model.md](../docs/plans/substrate-v2/05-cost-model.md) §3)
5+
> fills with measurements. Until a cell below holds a measured number, the only claimable
6+
> figures are the paper's: 62 % routing saving on live tokens (paper §9). The plan's ~90 %
7+
> composed figure is a **target**, not a result, and does not appear in this table.
8+
9+
## Methodology
10+
11+
The cost model is multiplicative — `C = C₀ · Π(1 − fᵢ)` over independent stages — so each
12+
stage factor is measured separately and composed arithmetically, never asserted:
13+
14+
1. **Instrumentation.** Every substrate stage appends one line to `.forge/metrics.jsonl`
15+
(`{t, stage, outcome, tokensIn, tokensOut, tier, savedEstimate, ref}``src/metrics.js`).
16+
`forge cost --stages` computes the per-stage factors from those lines (`src/cost_report.js`);
17+
a stage with no events reports **no data**, never a default.
18+
2. **Paired runs.** Baseline (always-premium, read-everything, no cache) vs. substrate over
19+
the same replay corpus (N ≥ 100 real tasks, stratified repeat-heavy / mixed / cold), the
20+
paper §9 methodology: identical tokens repriced, so every saving is arithmetic on measured
21+
tokens.
22+
3. **Correctness guard (spec §3).** A saving counts only if the external verifier passes the
23+
output. A routed-down answer that fails is not a saving; a cache hit that gets reverted is
24+
recorded as a *negative* entry.
25+
26+
## Measured factors
27+
28+
| stage | factor | events | measured? |
29+
|---|---|---|---|
30+
| gate (M2 halt rate) || 0 | no data yet — run with metrics enabled |
31+
| cache (reuse, tier-weighted) || 0 | no data yet — run with metrics enabled |
32+
| route (vs always-premium) || 0 | no data yet — run with metrics enabled |
33+
| context (assembly ρ) || 0 | no data yet — run with metrics enabled |
34+
| **composed (measured stages only)** || 0 | nothing to compose yet |
35+
36+
Secondary counters (doom-loop halts avoided, M5 lean, avoided rework) are reported alongside
37+
when populated — they are deliberately excluded from the multiplication (spec §1).
38+
39+
## How to populate this table
40+
41+
Metrics accrue as the substrate is actually used — each command below appends stage-tagged
42+
lines to `.forge/metrics.jsonl`:
43+
44+
```sh
45+
# gate + cache: every explicit pre-action check meters both stages
46+
forge substrate "<task>"
47+
48+
# cache: explicit reuse queries and mints
49+
forge reuse query "<what you are about to build>"
50+
51+
# then read the measured factors (and paste them here):
52+
forge cost --stages # human table
53+
forge cost --stages --json # machine-readable, for this report
54+
```
55+
56+
Route and context events are emitted via `recordRoute` / a future context-assembly hook
57+
(`src/cost_report.js`) as those stages gain live wiring.
58+
59+
## Caveats that ship with any number placed here
60+
61+
- Stage rates are **workload-dependent**: factors describe the recorded traffic of one repo,
62+
not a general claim (spec §2 — repeat-heavy warm-ledger workloads differ from cold starts).
63+
- The composed reduction is a **lower bound from measured stages only**; unmeasured stages
64+
contribute nothing rather than a target.
65+
- Until the paired-run harness with the correctness guard has run, per-stage factors from
66+
live metrics are unguarded observational numbers, not eval results.

src/anchor.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,33 @@ export function goalDrift(root, goal, opts = {}) {
165165
};
166166
}
167167

168+
/**
169+
* M4 — one-sided CUSUM control chart over a drift-signal series (spec §5:
170+
* docs/plans/substrate-v2/06-faculties-and-mechanisms.md). A raw threshold on a
171+
* single checkpoint's drift Dₜ is noisy — one exploratory step legitimately wanders.
172+
* CUSUM accumulates only the excess over the allowance k (Cₜ = max(0, Cₜ₋₁ + Dₜ − k))
173+
* and alarms at Cₜ > h, which detects SUSTAINED small drift with provably minimal
174+
* detection delay for a given false-alarm rate (classical sequential analysis),
175+
* while a single within-tolerance spike drains back to zero instead of alarming.
176+
* Defaults k = 0.35, h = 1.0 per the spec (calibration lands in P8). Pure.
177+
* @param {number[]} signals drift per checkpoint, Dₜ ∈ [0, 1] (non-numeric → 0)
178+
* @param {{k?: number, h?: number}} [opts]
179+
* @returns {{alarm: boolean, C: number[], firstAlarm: number}} firstAlarm = index of
180+
* the first checkpoint whose statistic crossed h, or -1 if none did.
181+
*/
182+
export function cusum(signals, { k = 0.35, h = 1.0 } = {}) {
183+
const C = [];
184+
let c = 0;
185+
let firstAlarm = -1;
186+
for (let i = 0; i < signals.length; i++) {
187+
const d = Number(signals[i]);
188+
c = Math.max(0, c + (Number.isFinite(d) ? d : 0) - k);
189+
C.push(c);
190+
if (firstAlarm < 0 && c > h) firstAlarm = i;
191+
}
192+
return { alarm: firstAlarm >= 0, C, firstAlarm };
193+
}
194+
168195
export function renderAnchor(r) {
169196
const lines = ["Forge anchor — goal-drift check", ""];
170197
if (!r.changed.length)

0 commit comments

Comments
 (0)