The missing layer between your intent and your agent.
Models are capable. Their operating context is fragile. ForgeKit supplies the durable layer that travels with the repository and shows up before the next action.
ACTIVE CAPABILITY / 01
Context that survives the chat.
Forge keeps decisions, lessons, and project state in the repository—so Claude, Codex, Cursor, and the next agent all inherit the same working memory.
3 records recalled
TYPERECORDSTATE
decisionUse SQLite for local-first state94%
lessonRun schema checks before generation88%
preferenceKeep the CLI dependency-free82%
02 / The protocolOne request · five checks · one trace
Action should leave evidence.
Forge turns agent behavior into a reviewable sequence. Each meaningful move begins with context and ends with proof.
01Recall
Load relevant decisions and lessons.
02Classify
Measure scope, cost, and reversibility.
03Foresee
Map downstream surfaces before editing.
04Gate
Pause risky or under-specified actions.
05Trace
Record what changed and how it was verified.
03 / One sourceNine native targets
Change the agent. Keep the operating system.
One source emits each tool’s native configuration. Your rules and memory stay with the project—not the provider.
01CCClaude Code
02CXCodex
03CRCursor
04GMGemini
05AIAider
06CPCopilot
07WSWindsurf
08ZDZed
09CTContinue
Plus MCP configuration for Roo Code and VS Code-compatible clients.
04 / Evidence ledgerMeasured, not invented
Fast enough to stay in the loop.
ForgeKit publishes the measurements behind its claims. The numbers below come from repository benchmarks and evaluation reports—not a marketing dashboard.
+
diff --git a/scripts/build-pages.mjs b/scripts/build-pages.mjs
index 16a9584..e42d7fe 100644
--- a/scripts/build-pages.mjs
+++ b/scripts/build-pages.mjs
@@ -176,9 +176,8 @@ export async function collect({ live = process.env.BUILD_PAGES_LIVE === "1" } =
// ember/near-black color tokens, one accent, a system font stack. test/pages.test.js
// enforces that color + font parity across both public surfaces, plus a non-empty changes
// list and no phantom webfont — so the two can't silently drift into two different
-// "school-project" looks again. The fluid type/space scale is enforced on this page only:
-// the landing page is a built SPA that computes its own scale, and its shell HTML carries
-// just the critical-CSS tokens its pre-hydration paint actually uses.
+// "school-project" looks again. The fluid type/space scale is enforced on this page only;
+// the landing intentionally uses a separate editorial scale while sharing the brand source.
export function render(d) {
const live = d.github
? `${esc(d.github.stars)} stars${esc(d.github.forks)} forks${esc(d.github.issues)} open issues`
diff --git a/scripts/bump.mjs b/scripts/bump.mjs
index 0e5e4e7..d95d195 100644
--- a/scripts/bump.mjs
+++ b/scripts/bump.mjs
@@ -337,7 +337,12 @@ export function applyBump(root, currentVersion, newVersion, date) {
const landingRel = "landing/index.html";
const landing = readIfExists(path.join(root, landingRel));
if (landing !== null && /forgekit v\d+\.\d+\.\d+/.test(landing)) {
- write(landingRel, landing.replace(/forgekit v\d+\.\d+\.\d+/g, `forgekit v${newVersion}`));
+ write(
+ landingRel,
+ landing
+ .replace(/forgekit v\d+\.\d+\.\d+/g, `forgekit v${newVersion}`)
+ .replace(/("softwareVersion"\s*:\s*")\d+\.\d+\.\d+("?)/g, `$1${newVersion}$2`),
+ );
}
const roadmapRel = "ROADMAP.md";
diff --git a/test/bump.test.js b/test/bump.test.js
index 76e0170..cd0433b 100644
--- a/test/bump.test.js
+++ b/test/bump.test.js
@@ -300,7 +300,10 @@ function makeFixture() {
w(".claude-plugin/plugin.json", '{\n "name": "fixture",\n "version": "0.4.0"\n}\n');
w(".codex-plugin/plugin.json", '{\n "name": "fixture",\n "version": "0.4.0"\n}\n');
w("CITATION.cff", 'cff-version: 1.2.0\nversion: 0.4.0\ndate-released: "2026-07-06"\n');
- w("landing/index.html", '
forgekit v0.4.0 · MIT
\n');
+ w(
+ "landing/index.html",
+ '
forgekit v0.4.0 · MIT
\n',
+ );
w("ROADMAP.md", "# Roadmap\n\n## Now (`master`, v0.4.0)\n\nSome text.\n");
w("CHANGELOG.md", CHANGELOG);
return dir;
@@ -334,6 +337,7 @@ test("applyBump updates every version field in a fixture tree", () => {
assert.match(read("CITATION.cff"), /^version: 0\.5\.0$/m);
assert.match(read("CITATION.cff"), /^date-released: "2026-07-07"$/m);
assert.match(read("landing/index.html"), /forgekit v0\.5\.0/);
+ assert.match(read("landing/index.html"), /"softwareVersion":"0\.5\.0"/);
assert.match(read("ROADMAP.md"), /## Now \(`master`, v0\.5\.0\)/);
assert.match(read("CHANGELOG.md"), /## \[0\.5\.0\] - 2026-07-07/);
} finally {
diff --git a/test/pages.test.js b/test/pages.test.js
index 0849d8d..b6294fa 100644
--- a/test/pages.test.js
+++ b/test/pages.test.js
@@ -50,11 +50,8 @@ test("the status page derives its fluid type scale + spacing scale from the form
// page may not hand-pick its own font-size or margin/padding/gap magic numbers.
//
// Scope note: this is enforced on the generated status page only. The landing page
- // is now a built SPA (landing/assets/*, loaded from jsDelivr) that computes its own
- // scale; its shell HTML carries only the critical-CSS color + font tokens that the
- // pre-hydration paint actually consumes. Inlining --fs-N / --sp-N into that shell
- // would satisfy this assertion with markup nothing reads — a green test asserting
- // nothing. Color and font-stack parity ARE still enforced on both pages above.
+ // intentionally uses an editorial scale tuned for its product narrative. Color and
+ // font-stack parity ARE still enforced on both pages above.
const norm = (s) => s.replace(/\s+/g, "");
const status = norm(render(await collect({ live: false })));
for (const decl of typeScaleCss().split(";"))
@@ -93,15 +90,13 @@ test("landing benchmark metrics are numbers reports/benchmarks.md actually measu
for (const m of line.matchAll(/(\d+(?:\.\d+)?)\s*(ms|µs|s)\b/g))
measured.add(`${m[1]} ${m[2]}`);
}
- // The landing SPA renders its metrics client-side from a built chunk, so the shell
- // HTML states none. This no longer demands that a metric be present — it demands that
- // any metric the shell DOES state is one reports/benchmarks.md actually measured, so
- // the check still bites the moment a hardcoded number reappears. The "numbers must be
- // measured" guarantee itself is not lost: src/docs_check.js (check: "benchmarks")
- // enforces README <-> reports/benchmarks.md and runs in the same CI gate.
- const metrics = [...landing.matchAll(/\s*(\d+(?:\.\d+)?)\s*ms\s*<\/b/g)];
- for (const [, n] of metrics)
- assert.ok(measured.has(`${n} ms`), `landing claims ${n} ms but no benchmark row measures it`);
+ const metrics = [...landing.matchAll(/data-benchmark="(\d+(?:\.\d+)?)\s*(ms|µs|s)"/g)];
+ assert.ok(metrics.length > 0, "landing exposes at least one measured benchmark");
+ for (const [, n, unit] of metrics)
+ assert.ok(
+ measured.has(`${n} ${unit}`),
+ `landing claims ${n} ${unit} but no benchmark row measures it`,
+ );
});
// Metadata + freshness enforcement — each assertion below is a defect this change
@@ -144,17 +139,13 @@ test("canonical == og:url on both pages", async () => {
});
test("landing never states a stale package version", () => {
- // KNOWN DEBT: the landing SPA states its version inside a built chunk
- // (landing/assets/c-*.js currently say "forgekit v0.27.0" while package.json has moved
- // on). That string cannot be corrected from here — the SPA's source is not in this
- // repo, only its minified output, and hand-patching a build artifact to satisfy a test
- // would be worse than the drift. So this asserts the shell HTML states no WRONG
- // version, rather than requiring it to state one. Committing the landing source is the
- // real fix, after which the `shown.length > 0` requirement should come back.
const { version } = JSON.parse(repo("package.json"));
const shown = [...landing.matchAll(/forgekit v(\d+\.\d+\.\d+)/g)].map((m) => m[1]);
+ assert.ok(shown.length > 0, "landing states its package version");
for (const v of shown)
assert.equal(v, version, `landing shows v${v}, package.json is ${version}`);
+ const schemaVersion = landing.match(/"softwareVersion"\s*:\s*"(\d+\.\d+\.\d+)"/)?.[1];
+ assert.equal(schemaVersion, version, "landing structured data matches package.json");
});
test("sticky-nav blur stays compositor-light (<=8px)", () => {
@@ -162,29 +153,18 @@ test("sticky-nav blur stays compositor-light (<=8px)", () => {
assert.ok(Number(px) <= 8, `backdrop blur ${px}px > 8px is repaint-heavy on scroll`);
});
-test("every jsDelivr-pinned landing asset exists in landing/assets", () => {
- // The landing shell loads its JS/CSS chunks from jsDelivr pinned to a commit SHA,
- // because .github/workflows/static.yml copies only landing/index.html into _site — it
- // never deploys landing/assets/. So a pin naming a chunk that isn't in the repo 404s
- // the entire site with a green build and no other test noticing.
- //
- // This deliberately does NOT assert the SHA equals HEAD: the pin is only re-cut when a
- // chunk actually changes, so an == HEAD check would fail on every unrelated commit.
- // It checks the two things that are always true of a valid pin — a full-length SHA,
- // and a file that exists to be served.
- const pins = [
- ...landing.matchAll(
- /cdn\.jsdelivr\.net\/gh\/CodeWithJuber\/forgekit@([^/]+)\/landing\/assets\/([^"']+)/g,
- ),
- ];
- assert.ok(pins.length > 0, "landing pins at least one asset");
- for (const [, sha, file] of pins) {
- assert.match(sha, /^[0-9a-f]{40}$/, `pin for ${file} must be a full 40-char commit SHA`);
- assert.ok(
- existsSync(fileURLToPath(new URL(`../landing/assets/${file}`, import.meta.url))),
- `landing/index.html pins landing/assets/${file}, which does not exist`,
- );
- }
+test("landing runtime is source-owned and dependency-free", () => {
+ assert.doesNotMatch(
+ landing,
+ /cdn\.jsdelivr\.net|fonts\.googleapis\.com|esm\.sh/,
+ "landing must not depend on an external runtime or webfont",
+ );
+ const scripts = [...landing.matchAll(/