Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Added

- **Proof-carrying reuse cache (P3 of the substrate-v2 plan).** `forge reuse` turns
"reuse already-generated code" from prose into a deterministic system: verified code
becomes an `artifact` claim keyed by a normalized task fingerprint (volatile literals →
typed placeholders; MinHash sketch + 16×8 LSH banding for near-match), looked up through
the exact → near → adapt → miss ladder. An artifact serves ONLY while its proof holds —
confidence above the 0.6 floor (an unverified mint sits at the 0.5 prior and does not
serve) and every declared dependency still resolving in the atlas; a failed revalidation
appends a `graph.reval` contradiction, so stale code demotes itself for the whole team.
`forge reuse query|mint|stats`, a reuse stage in `forge substrate` (read-only on the
ambient hook path), and `src/metrics.js` — the stage-tagged `.forge/metrics.jsonl` the
cost model's measured savings are computed from. The `reuse-first` skill now calls the
cache before advising a repo search.

- **Team memory (P2 of the substrate-v2 plan).** The PCM ledger becomes shared:
`forge ledger merge <path>` performs the conflict-free semilattice merge of any other
ledger tree (a teammate's checkout, a worktree, a backup) — identical knowledge minted
Expand Down
9 changes: 8 additions & 1 deletion global/tools/reuse-first/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ Default to reusing and following what exists. New code is a liability; the best
change is the smallest one that fits the codebase.

## 1. Reuse before building
- Search the repo first (`grep`/`glob`/serena LSP): is there an existing util,
- **Ask the proof-carrying cache first**: `forge reuse query "<what you're about to
build>"`. A hit is code this team already generated AND verified — its test/accept
evidence travels with it (`forge ledger blame <id>` shows why to trust it):
- **EXACT / NEAR hit** → use that artifact; do not regenerate.
- **ADAPT hit** → read it, start from it, generate only the delta.
- **miss** → build it, then `forge reuse mint "<spec>" --file <path> --ref <test-run>`
so the next teammate (or session) gets the hit.
- Search the repo next (`grep`/`glob`/serena LSP): is there an existing util,
component, hook, service, or pattern that already does this? Extend it.
- If not in-repo, is there a maintained library? Vet it with `tech-selector`
(current best from Context7 + web + GitHub health) before adding a dependency.
Expand Down
102 changes: 102 additions & 0 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const COMMANDS = {
spec: "spec-as-contract — init (OpenSpec) / lock / check drift",
cortex: "self-correcting project memory — status / why <symbol>",
ledger: "proof-carrying memory — stats / verify / show / blame / query / merge / import",
reuse: "proof-carrying code cache — query <spec> / mint <spec> --file <path> / stats",
preflight: "assumption check — what a task names that the repo doesn't define",
route: "recommend the cheapest capable model for a task (+ gateway config)",
impact: "predict blast radius for a symbol or file from the atlas graph",
Expand Down Expand Up @@ -325,6 +326,107 @@ async function run(argv) {
process.exitCode = 1;
return;
}
if (cmd === "reuse") {
const ru = await import("./reuse.js");
const { load: loadAtlas } = await import("./atlas.js");
const { epochDay } = await import("./util.js");
const root = process.cwd();
const nowDay = epochDay();
const json = argv.includes("--json");
const flagVal = (name) => {
const i = argv.indexOf(name);
return i >= 0 ? argv[i + 1] : undefined;
};
const args = argv.filter(
(a, i) => !a.startsWith("--") && argv[i - 1] !== "--file" && argv[i - 1] !== "--ref",
);
const sub = args[1] || "stats";
if (sub === "query") {
const spec = args.slice(2).join(" ");
if (!spec) {
console.error('usage: forge reuse query "<what you are about to build>" [--json]');
process.exitCode = 1;
return;
}
const r = ru.reuseQuery(root, spec, { atlas: loadAtlas(root), nowDay });
if (json)
return console.log(
JSON.stringify(
{ tier: r.tier, artifact: r.artifact?.id, jaccard: r.jaccard, reasons: r.reasons },
null,
2,
),
);
if (r.tier === "miss") {
console.log(" miss — nothing verified matches; generate, then `forge reuse mint` it");
} else {
const a = r.artifact;
console.log(
` ${r.tier.toUpperCase()} hit (similarity ${(r.jaccard ?? 1).toFixed(2)}) — ${a.body.form}${a.body.code?.path ? ` at ${a.body.code.path}` : ""}`,
);
console.log(
` claim ${a.id.slice(0, 12)} — \`forge ledger blame ${a.id.slice(0, 8)}\` for its proof`,
);
if (r.tier === "adapt")
console.log(
" adapt tier: inject as a verified starting point, generate only the delta",
);
}
for (const why of r.reasons) console.log(` note: ${why}`);
return;
}
if (sub === "mint") {
const spec = args.slice(2).join(" ");
const file = flagVal("--file");
const ref = flagVal("--ref");
if (!spec || !file) {
console.error(
'usage: forge reuse mint "<task the code solves>" --file <path> [--ref <test-run/commit>] [--json]',
);
process.exitCode = 1;
return;
}
const { repoLedger } = await import("./ledger_store.js");
const desc = ru.describeFile(root, file);
const r = ru.mintArtifact(
repoLedger(root),
{ spec, form: "module", ...desc },
ref
? { evidence: { oracle: "test.run", result: "confirm", ref }, t: nowDay }
: { t: nowDay },
);
if (json) return console.log(JSON.stringify(r, null, 2));
if (!r.ok) {
console.error(` ${r.reason}`);
process.exitCode = 1;
return;
}
console.log(
` minted: ${r.id.slice(0, 12)} (${desc.iface.length} export(s), ${desc.deps.length} dep(s))`,
);
console.log(
r.serves
? " serving: yes — verification evidence attached"
: " serving: NOT YET — no evidence; attach a verified test/commit ref (--ref) or it stays at the 0.5 prior",
);
return;
}
if (sub === "stats") {
const { summarize } = await import("./metrics.js");
const s = summarize(root).cache ?? { events: 0, byOutcome: {}, savedEstimate: 0 };
if (json) return console.log(JSON.stringify(s, null, 2));
console.log(`${BRAND.brand} reuse — proof-carrying code cache\n`);
console.log(` lookups: ${s.events}`);
for (const [o, n] of Object.entries(s.byOutcome)) console.log(` ${o}: ${n}`);
console.log(` est. tokens saved: ${s.savedEstimate}`);
return;
}
console.error(
`reuse: unknown subcommand "${sub}" (query <spec> | mint <spec> --file <path> | stats)`,
);
process.exitCode = 1;
return;
}
if (cmd === "atlas") {
const a = await import("./atlas.js");
const sub = argv[1] || "build";
Expand Down
54 changes: 54 additions & 0 deletions src/metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// forge metrics — the measurement backbone of the cost model
// (docs/plans/substrate-v2/05-cost-model.md): every substrate stage appends one
// JSONL line, and savings are later ARITHMETIC on these lines, never an estimate
// asserted after the fact. Append-only, corrupt-line tolerant, best-effort — a
// metrics failure must never break the stage it measures.
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
import { join } from "node:path";

export const metricsPath = (root = process.cwd()) => join(root, ".forge", "metrics.jsonl");

/**
* Record one stage event: { t?, stage, outcome?, tokensIn?, tokensOut?, savedEstimate?,
* tier?, ref?, ... }. `t` defaults to wall-clock ms (metrics are telemetry, not
* content-addressed protocol state — clock use is fine here, unlike the ledger).
*/
export function record(root, entry) {
try {
const dir = join(root, ".forge");
mkdirSync(dir, { recursive: true });
appendFileSync(metricsPath(root), `${JSON.stringify({ t: Date.now(), ...entry })}\n`);
return { ok: true };
} catch {
return { ok: false };
}
}

/** All events, oldest first (corrupt lines skipped). Optional stage filter.
* @param {string} root
* @param {{stage?: string}} [opts] */
export function read(root, { stage } = {}) {
const path = metricsPath(root);
if (!existsSync(path)) return [];
const out = [];
for (const line of readFileSync(path, "utf8").split("\n")) {
if (!line.trim()) continue;
try {
const e = JSON.parse(line);
if (!stage || e.stage === stage) out.push(e);
} catch {}
}
return out;
}

/** Counts by stage → outcome, plus summed token-saving estimates per stage. */
export function summarize(root) {
const stages = {};
for (const e of read(root)) {
const s = (stages[e.stage] ??= { events: 0, byOutcome: {}, savedEstimate: 0 });
s.events++;
if (e.outcome) s.byOutcome[e.outcome] = (s.byOutcome[e.outcome] ?? 0) + 1;
if (Number.isFinite(e.savedEstimate)) s.savedEstimate += e.savedEstimate;
}
return stages;
}
Binary file added src/reuse.js
Binary file not shown.
28 changes: 28 additions & 0 deletions src/substrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { matchingLessons } from "./cortex.js";
import { leanRepo } from "./lean.js";
import { load as loadLessons } from "./lessons_store.js";
import { clarifyBlock, preflightRepo, referencedEntities } from "./preflight.js";
import { reusePeek, reuseQuery } from "./reuse.js";
import { routeTask } from "./route.js";
import { decompose } from "./scope.js";
import { epochDay } from "./util.js";

function loadSubstrateSpec() {
const path = join(dirname(dirname(fileURLToPath(import.meta.url))), "source", "substrate.json");
Expand Down Expand Up @@ -214,6 +216,24 @@ export function substrateCheck(
// impacted files change — the impacted files that ARE tests, plus each impacted source file's
// sibling test. Cheap, exact-ish, and surfaced BEFORE the edit (not after, like verify).
const predictedTests = predictFailingTests(root, impactedFiles);
// P3 reuse stage: has this team already built (and verified) this? The explicit gate
// meters + writes evidence (reuseQuery); the ambient hook path stays read-only
// (reusePeek) so a per-prompt hook never appends to the ledger or metrics.
const reuse = (() => {
try {
const opts = { atlas, nowDay: epochDay() };
const r = allowBuild ? reuseQuery(root, text, opts) : reusePeek(root, text, opts);
return {
tier: r.tier,
artifact: r.artifact
? { id: r.artifact.id, path: r.artifact.body.code?.path, form: r.artifact.body.form }
: undefined,
jaccard: r.jaccard,
};
} catch {
return { tier: "miss" }; // cache trouble must never block the gate
}
})();
const scopedFiles = [...new Set([...entities.files, ...impactedFiles])];
const scope = scopedFiles.length
? decompose(root, scopedFiles)
Expand All @@ -229,6 +249,7 @@ export function substrateCheck(
clarify: clarifyBlock(preflight),
route,
entities,
reuse,
impact: { targets: impactTargets, reports: impacts, impactedFiles, predictedTests },
scope,
memory: {
Expand Down Expand Up @@ -343,6 +364,13 @@ export function renderSubstrate(result) {
` route: ${result.route.model.name} (${result.route.tier}) · complexity ${result.route.score.toFixed(2)}`,
);
if (result.route.reasons.length) lines.push(` driven by: ${result.route.reasons.join(", ")}`);
if (result.reuse && result.reuse.tier !== "miss") {
const a = result.reuse.artifact;
lines.push(
"",
` reuse: ${result.reuse.tier.toUpperCase()} hit — verified ${a?.form ?? "artifact"}${a?.path ? ` at ${a.path}` : ""} (\`forge ledger show ${a?.id.slice(0, 8)}\`) — start from it, don't regenerate`,
);
}
lines.push("", ` impact: ${result.impact.impactedFiles.length} file(s) predicted`);
for (const file of result.impact.impactedFiles.slice(0, 10)) lines.push(` - ${file}`);
if (result.impact.impactedFiles.length > 10)
Expand Down
Loading
Loading