@codewithjuber/forgekit 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/CHANGELOG.md +6 -1
- package/package.json +1 -2
- package/public/index.html +3 -3
- package/src/cli.js +30 -7
- package/src/cortex_mcp.js +74 -1
- package/src/dash.html +31 -24
- package/src/dash.js +36 -0
- package/src/emit/copilot.js +1 -1
- package/src/emit/gemini.js +1 -1
- package/src/emit/mcp.js +2 -2
- package/src/init.js +15 -4
- package/src/sync.js +1 -1
- package/templates/project-layer/CLAUDE.md +11 -14
- package/skills/cognitive-substrate/SKILL.md +0 -56
- package/skills/cognitive-substrate/references/capability-map.md +0 -17
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "forgekit",
|
|
4
4
|
"displayName": "Forge",
|
|
5
|
-
"version": "0.8.
|
|
5
|
+
"version": "0.8.1",
|
|
6
6
|
"description": "One config, every AI coding tool — cognitive substrate, tools, crew, guards, atlas, lean, recall from one source.",
|
|
7
7
|
"author": { "name": "CodeWithJuber" },
|
|
8
8
|
"license": "MIT",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgekit",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "One config, every AI coding tool — cognitive substrate, MCP tools, guards, atlas, recall, and routing from one source.",
|
|
5
5
|
"author": { "name": "CodeWithJuber", "url": "https://github.com/CodeWithJuber" },
|
|
6
6
|
"homepage": "https://github.com/CodeWithJuber/forgekit#readme",
|
|
7
7
|
"repository": "https://github.com/CodeWithJuber/forgekit",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"keywords": ["codex", "mcp", "ai-coding", "agents-md", "cognitive-substrate"],
|
|
10
|
-
"skills": "
|
|
10
|
+
"skills": "global/tools",
|
|
11
11
|
"mcpServers": ".mcp.json",
|
|
12
12
|
"interface": {
|
|
13
13
|
"displayName": "Forge",
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.8.1] - 2026-07-08
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
9
13
|
## [0.8.0] - 2026-07-08
|
|
10
14
|
|
|
11
15
|
|
|
@@ -454,7 +458,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
454
458
|
check; coverage + type-checking (`tsc --checkJs`); 2026 production-standard rules;
|
|
455
459
|
OWASP-LLM / NIST SSDF / SLSA control mapping.
|
|
456
460
|
|
|
457
|
-
[Unreleased]: https://github.com/CodeWithJuber/forgekit/compare/v0.8.
|
|
461
|
+
[Unreleased]: https://github.com/CodeWithJuber/forgekit/compare/v0.8.1...HEAD
|
|
462
|
+
[0.8.1]: https://github.com/CodeWithJuber/forgekit/compare/v0.8.0...v0.8.1
|
|
458
463
|
[0.8.0]: https://github.com/CodeWithJuber/forgekit/compare/v0.7.0...v0.8.0
|
|
459
464
|
[0.7.0]: https://github.com/CodeWithJuber/forgekit/compare/v0.6.0...v0.7.0
|
|
460
465
|
[0.6.0]: https://github.com/CodeWithJuber/forgekit/compare/v0.5.0...v0.6.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codewithjuber/forgekit",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "One brain for every AI coding agent \u2014 the cognitive substrate every frozen model is missing (proof-carrying memory, impact foresight, enforced guardrails), authored once and delivered as native config to Claude Code, Codex, Cursor, Gemini, Aider, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
"hooks",
|
|
40
40
|
".claude-plugin",
|
|
41
41
|
".codex-plugin",
|
|
42
|
-
"skills",
|
|
43
42
|
".mcp.json",
|
|
44
43
|
"bin",
|
|
45
44
|
"brand.json",
|
package/public/index.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>forgekit status — live repository data</title><meta name="description" content="One
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>forgekit status — live repository data</title><meta name="description" content="One brain for every AI coding agent — the cognitive substrate every frozen model is missing (proof-carrying memory, impact foresight, enforced guardrails), authored once and delivered as native config to Claude Code, Codex, Cursor, Gemini, Aider, and more."><meta name="theme-color" content="#171310"><style>
|
|
2
2
|
:root{--ink:#171310;--surface:#201a15;--surface-2:#272019;--line:#372c22;--faint:#7d7263;--muted:#a99e90;--ember:#f26430;--fg:#f2ede7;--r-s:6px;--r-m:12px;--r-pill:999px;--shadow:0 24px 64px rgba(23,19,16,.56);--mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;--sans:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
3
3
|
*{box-sizing:border-box}
|
|
4
4
|
body{margin:0;background:radial-gradient(960px 400px at 85% -10%,rgba(242,100,48,.16),transparent 70%),var(--ink);color:var(--fg);font:16px/1.65 var(--sans);-webkit-font-smoothing:antialiased}
|
|
@@ -39,7 +39,7 @@ h2{font-size:24px;letter-spacing:-.01em;margin:0 0 16px}
|
|
|
39
39
|
code{font:500 13px var(--mono);background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-s);padding:0 8px}
|
|
40
40
|
footer{padding:32px 0;color:var(--faint);font-size:14px}
|
|
41
41
|
@media(max-width:800px){.grid{grid-template-columns:1fr}}
|
|
42
|
-
</style></head><body><div class="wrap"><header class="nav"><a class="brand" href="../">forge<em>kit</em></a><nav class="links" aria-label="Primary"><a href="../">Landing</a><a href="#quickstart">Quickstart</a><a href="#changes">Latest</a><a href="#sources">Data Sources</a><a href="https://github.com/CodeWithJuber/forgekit">GitHub ↗</a></nav></header><main id="top"><section class="hero" style="border-bottom:0;padding-bottom:0"><p class="eyebrow">@codewithjuber/forgekit · v0.
|
|
42
|
+
</style></head><body><div class="wrap"><header class="nav"><a class="brand" href="../">forge<em>kit</em></a><nav class="links" aria-label="Primary"><a href="../">Landing</a><a href="#quickstart">Quickstart</a><a href="#changes">Latest</a><a href="#sources">Data Sources</a><a href="https://github.com/CodeWithJuber/forgekit">GitHub ↗</a></nav></header><main id="top"><section class="hero" style="border-bottom:0;padding-bottom:0"><p class="eyebrow">@codewithjuber/forgekit · v0.8.0 · Node >=20</p><h1>Live status, straight from the repository.</h1><p class="lead">One brain for every AI coding agent — the cognitive substrate every frozen model is missing (proof-carrying memory, impact foresight, enforced guardrails), authored once and delivered as native config to Claude Code, Codex, Cursor, Gemini, Aider, and more.</p><p><a class="btn primary" href="#quickstart">Install in 60 seconds</a> <a class="btn" href="https://github.com/CodeWithJuber/forgekit#readme">Read the docs</a></p><div class="meta"><span class="chip">MIT license</span><span class="chip">0 runtime dependencies</span><span class="chip">claude/forge-work-system-setup-p26ka5 @ 26586e2</span><span class="chip">live GitHub stats disabled</span></div></section><div class="grid" aria-label="Measured outcomes"><article class="cell"><div class="metric"><em>0.43 ms</em></div><strong>blast-radius lookup</strong><p class="muted">Measured from this repo's benchmark report, not a marketing placeholder.</p><p class="src">reports/benchmarks.md</p></article><article class="cell"><div class="metric"><em>118 ms</em></div><strong>pre-action gate</strong><p class="muted">Assumptions, routing, reuse, context, impact, scope, and anchoring.</p><p class="src">reports/benchmarks.md</p></article><article class="cell"><div class="metric"><em>62.1%</em></div><strong>cost saved</strong><p class="muted">Documented from the white-paper prototype and exposed by Forge cost reports.</p><p class="src">whitepaper prototype</p></article></div><section id="quickstart"><h2>Quickstart</h2><div class="terminal">npm install -g @codewithjuber/forgekit
|
|
43
43
|
forge init
|
|
44
44
|
forge doctor
|
|
45
|
-
forge substrate "Change auth validation and update tests"</div></section><section id="changes"><h2>Latest repo changes</h2><div class="card"><ul class="list"
|
|
45
|
+
forge substrate "Change auth validation and update tests"</div></section><section id="changes"><h2>Latest repo changes</h2><div class="card"><ul class="list"></ul><p class="muted">Benchmark sections indexed: 3 · benchmarks file updated 2026-07-08.</p></div></section><section id="sources"><h2>Data Sources</h2><div class="card"><p class="muted">No mock data is used. This page is regenerated from repository files during CI (generated 2026-07-08T18:42:41.820Z from 26586e2). Enable <code>BUILD_PAGES_LIVE=1</code> to refresh public GitHub counters with ETag/Last-Modified caching.</p><ul class="list"><li>package.json</li><li>README.md</li><li>CHANGELOG.md</li><li>reports/benchmarks.md</li><li>https://api.github.com/repos/CodeWithJuber/forgekit (optional, no auth, only when BUILD_PAGES_LIVE=1)</li></ul></div></section></main><footer>WCAG-minded semantic HTML, keyboard focus, responsive 320px–1920px+, and reduced-motion-safe. Same design tokens as the landing page — <code>forge uicheck design</code> gates both.</footer></div></body></html>
|
package/src/cli.js
CHANGED
|
@@ -40,17 +40,40 @@ const COMMANDS = {
|
|
|
40
40
|
brand: "print the active brand token map",
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
const GROUPS = {
|
|
44
|
+
Core: ["init", "sync", "doctor", "catalog"],
|
|
45
|
+
Memory: ["cortex", "recall", "remember", "brain", "ledger", "reuse"],
|
|
46
|
+
Substrate: [
|
|
47
|
+
"substrate",
|
|
48
|
+
"preflight",
|
|
49
|
+
"route",
|
|
50
|
+
"impact",
|
|
51
|
+
"scope",
|
|
52
|
+
"context",
|
|
53
|
+
"anchor",
|
|
54
|
+
"diagnose",
|
|
55
|
+
"imagine",
|
|
56
|
+
"lean",
|
|
57
|
+
],
|
|
58
|
+
Quality: ["verify", "scan", "spec", "taste", "uicheck", "harden"],
|
|
59
|
+
Config: ["config", "cost", "dash", "brand", "atlas"],
|
|
60
|
+
};
|
|
61
|
+
|
|
43
62
|
const printVersion = () => console.log(`${BRAND.brand} (${BRAND.pkg}) v${BRAND.version}`);
|
|
44
63
|
|
|
45
64
|
function printHelp() {
|
|
46
65
|
printVersion();
|
|
47
66
|
console.log(`\n${BRAND.tagline}\n`);
|
|
48
67
|
console.log(`Usage: ${BRAND.cli} <command> [options]\n`);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
for (const [group, cmds] of Object.entries(GROUPS)) {
|
|
69
|
+
console.log(`${group}:`);
|
|
70
|
+
for (const name of cmds) {
|
|
71
|
+
if (COMMANDS[name]) console.log(` ${name.padEnd(12)} ${COMMANDS[name]}`);
|
|
72
|
+
}
|
|
73
|
+
console.log();
|
|
52
74
|
}
|
|
53
|
-
console.log(
|
|
75
|
+
console.log(`Start here: \`${BRAND.cli} catalog\``);
|
|
76
|
+
console.log(`Run \`${BRAND.cli} <command> --help\` for details.`);
|
|
54
77
|
}
|
|
55
78
|
|
|
56
79
|
async function run(argv) {
|
|
@@ -139,7 +162,7 @@ async function run(argv) {
|
|
|
139
162
|
console.log(`${BRAND.brand} sync — one source → every tool\n`);
|
|
140
163
|
for (const r of report) {
|
|
141
164
|
console.log(
|
|
142
|
-
` ${r.action.padEnd(16)} ${String(r.target).padEnd(22)} ${r.tool}${r.note ?
|
|
165
|
+
` ${r.action.padEnd(16)} ${String(r.target).padEnd(22)} ${r.tool}${r.note ? ` · ${r.note}` : ""}`,
|
|
143
166
|
);
|
|
144
167
|
}
|
|
145
168
|
for (const w of warnings) console.warn(` ! ${w}`);
|
|
@@ -158,7 +181,7 @@ async function run(argv) {
|
|
|
158
181
|
const icon = { ok: "✓", warn: "!", fail: "✗" };
|
|
159
182
|
console.log(`${BRAND.brand} doctor\n`);
|
|
160
183
|
for (const r of results) console.log(` ${icon[r.status]} ${r.label.padEnd(16)} ${r.note}`);
|
|
161
|
-
console.log(`\n${failed === 0 ? "all clear" : failed
|
|
184
|
+
console.log(`\n${failed === 0 ? "all clear" : `${failed} problem(s)`}`);
|
|
162
185
|
if (failed) process.exitCode = 1;
|
|
163
186
|
return;
|
|
164
187
|
}
|
|
@@ -596,7 +619,7 @@ async function run(argv) {
|
|
|
596
619
|
if (r.findings?.length) {
|
|
597
620
|
for (const f of r.findings) console.log(` [${f.sev}] ${f.msg}`);
|
|
598
621
|
} else if (r.raw) {
|
|
599
|
-
console.log(
|
|
622
|
+
console.log(` ${r.raw.trim().split("\n").slice(-6).join("\n ")}`);
|
|
600
623
|
} else {
|
|
601
624
|
console.log(" no obvious red flags");
|
|
602
625
|
}
|
package/src/cortex_mcp.js
CHANGED
|
@@ -8,7 +8,7 @@ import { createInterface } from "node:readline";
|
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { lessonsForContext, summary } from "./cortex.js";
|
|
10
10
|
import { report as costReport } from "./cost_report.js";
|
|
11
|
-
import { dashData } from "./dash.js";
|
|
11
|
+
import { dashData, dashSummary } from "./dash.js";
|
|
12
12
|
import { diagnose } from "./diagnose.js";
|
|
13
13
|
import { doctor } from "./doctor.js";
|
|
14
14
|
import { assessTask, clarifyBlock, preflightRepo } from "./preflight.js";
|
|
@@ -130,6 +130,12 @@ const TOOLS = [
|
|
|
130
130
|
"Dashboard JSON payload — ledger stats, metrics, atlas info. Same data forge dash serves at /api/data.",
|
|
131
131
|
inputSchema: { type: "object", properties: {} },
|
|
132
132
|
},
|
|
133
|
+
{
|
|
134
|
+
name: "forge_dash_summary",
|
|
135
|
+
description:
|
|
136
|
+
"Lightweight dashboard health check — just counts (claims, tombstoned, contested, atlas built, metric events). Cheaper than forge_dash_data.",
|
|
137
|
+
inputSchema: { type: "object", properties: {} },
|
|
138
|
+
},
|
|
133
139
|
{
|
|
134
140
|
name: "forge_brain",
|
|
135
141
|
description: "Project memory index — list all remembered facts stored in .forge/brain/.",
|
|
@@ -173,6 +179,44 @@ const TOOLS = [
|
|
|
173
179
|
"Provider detection — which API provider is active (auto-detected or configured), env vars set, and health checks.",
|
|
174
180
|
inputSchema: { type: "object", properties: {} },
|
|
175
181
|
},
|
|
182
|
+
{
|
|
183
|
+
name: "forge_remember",
|
|
184
|
+
description:
|
|
185
|
+
"Store a durable fact in this repo's portable memory (.forge/brain/). Use for non-obvious, lasting knowledge (env quirks, decisions, gotchas).",
|
|
186
|
+
inputSchema: {
|
|
187
|
+
type: "object",
|
|
188
|
+
properties: {
|
|
189
|
+
name: { type: "string", description: "short slug for the fact (used as filename)" },
|
|
190
|
+
body: { type: "string", description: "the fact content (markdown)" },
|
|
191
|
+
},
|
|
192
|
+
required: ["name", "body"],
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "forge_ledger_ratify",
|
|
197
|
+
description:
|
|
198
|
+
"Promote a ledger claim's confidence — record an independent oracle ratification (the claim held under test).",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
id: { type: "string", description: "claim ID or unique prefix" },
|
|
203
|
+
},
|
|
204
|
+
required: ["id"],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "forge_ledger_retract",
|
|
209
|
+
description:
|
|
210
|
+
"Tombstone a ledger claim with a reason — mark it as no longer valid so it stops influencing routing and memory.",
|
|
211
|
+
inputSchema: {
|
|
212
|
+
type: "object",
|
|
213
|
+
properties: {
|
|
214
|
+
id: { type: "string", description: "claim ID or unique prefix" },
|
|
215
|
+
reason: { type: "string", description: "why the claim is being retracted" },
|
|
216
|
+
},
|
|
217
|
+
required: ["id", "reason"],
|
|
218
|
+
},
|
|
219
|
+
},
|
|
176
220
|
];
|
|
177
221
|
|
|
178
222
|
async function callTool(name, args = {}) {
|
|
@@ -212,6 +256,7 @@ async function callTool(name, args = {}) {
|
|
|
212
256
|
}
|
|
213
257
|
if (name === "forge_cost") return JSON.stringify(costReport(root), null, 2);
|
|
214
258
|
if (name === "forge_dash_data") return JSON.stringify(dashData(root), null, 2);
|
|
259
|
+
if (name === "forge_dash_summary") return JSON.stringify(dashSummary(root), null, 2);
|
|
215
260
|
if (name === "forge_brain") {
|
|
216
261
|
try {
|
|
217
262
|
const { brainStore, list, buildIndex } = await import("./brain.js");
|
|
@@ -287,6 +332,34 @@ async function callTool(name, args = {}) {
|
|
|
287
332
|
2,
|
|
288
333
|
);
|
|
289
334
|
}
|
|
335
|
+
if (name === "forge_remember") {
|
|
336
|
+
const { brainStore, remember } = await import("./brain.js");
|
|
337
|
+
const store = brainStore(root);
|
|
338
|
+
remember(store, String(args.name ?? ""), String(args.body ?? ""));
|
|
339
|
+
return `Remembered "${args.name}" in ${store}.`;
|
|
340
|
+
}
|
|
341
|
+
if (name === "forge_ledger_ratify") {
|
|
342
|
+
const { ratify, repoLedger, getClaimByPrefix } = await import("./ledger_store.js");
|
|
343
|
+
const { gitAuthor } = await import("./util.js");
|
|
344
|
+
const dir = repoLedger(root);
|
|
345
|
+
const claim = getClaimByPrefix(dir, String(args.id ?? ""));
|
|
346
|
+
if (!claim) return `No claim matching prefix "${args.id}".`;
|
|
347
|
+
ratify(dir, claim.id, { author: gitAuthor(), t: today() });
|
|
348
|
+
return `Ratified claim ${claim.id}.`;
|
|
349
|
+
}
|
|
350
|
+
if (name === "forge_ledger_retract") {
|
|
351
|
+
const { tombstone, repoLedger, getClaimByPrefix } = await import("./ledger_store.js");
|
|
352
|
+
const { gitAuthor } = await import("./util.js");
|
|
353
|
+
const dir = repoLedger(root);
|
|
354
|
+
const claim = getClaimByPrefix(dir, String(args.id ?? ""));
|
|
355
|
+
if (!claim) return `No claim matching prefix "${args.id}".`;
|
|
356
|
+
tombstone(dir, claim.id, {
|
|
357
|
+
author: gitAuthor(),
|
|
358
|
+
reason: String(args.reason ?? ""),
|
|
359
|
+
t: today(),
|
|
360
|
+
});
|
|
361
|
+
return `Retracted claim ${claim.id}: ${args.reason}`;
|
|
362
|
+
}
|
|
290
363
|
return null;
|
|
291
364
|
}
|
|
292
365
|
|
package/src/dash.html
CHANGED
|
@@ -210,7 +210,7 @@ const bar = (v, { dim = false } = {}) =>
|
|
|
210
210
|
|
|
211
211
|
// Every claim row carries its provenance affordance — click to copy the exact command.
|
|
212
212
|
const blameCell = (id8) =>
|
|
213
|
-
|
|
213
|
+
`<td><span class="blame" data-id8="${esc(id8)}">forge ledger blame ${esc(id8)}</span></td>`;
|
|
214
214
|
|
|
215
215
|
// The two writes (spec §2), per live row. Ratify = human-only hikma promotion (the
|
|
216
216
|
// server stamps YOUR git identity as author); retract = tombstone with a reason.
|
|
@@ -240,37 +240,37 @@ let DATA = null;
|
|
|
240
240
|
|
|
241
241
|
function renderLedger(d) {
|
|
242
242
|
const L = d.ledger;
|
|
243
|
-
$("hdr-repo").textContent =
|
|
243
|
+
$("hdr-repo").textContent = `· ${d.repo}`;
|
|
244
244
|
$("hdr-meta").textContent =
|
|
245
|
-
L.stats.total
|
|
245
|
+
`${L.stats.total} claims · ${L.stats.tombstoned} tombstoned · day ${d.nowDay}`;
|
|
246
246
|
$("ledger-stats").innerHTML =
|
|
247
247
|
'<span class="stat"><span class="n">' + L.stats.total + '</span><span class="l">claims</span></span>' +
|
|
248
248
|
'<span class="stat"><span class="n ember">' + L.stats.val.trusted + '</span><span class="l">trusted ≥0.65</span></span>' +
|
|
249
249
|
'<span class="stat"><span class="n">' + L.stats.val.uncertain + '</span><span class="l">uncertain</span></span>' +
|
|
250
250
|
'<span class="stat"><span class="n">' + L.stats.val.dormant + '</span><span class="l">dormant <0.35</span></span>' +
|
|
251
251
|
Object.entries(L.stats.byKind).map(([k, n]) =>
|
|
252
|
-
|
|
252
|
+
`<span class="stat"><span class="n">${n}</span><span class="l">${esc(k)}</span></span>`).join("");
|
|
253
253
|
|
|
254
254
|
const kinds = Object.keys(L.stats.byKind).sort();
|
|
255
255
|
const sel = $("kind-filter");
|
|
256
256
|
const keep = sel.value;
|
|
257
257
|
sel.innerHTML = '<option value="">all</option>' +
|
|
258
|
-
kinds.map((k) =>
|
|
258
|
+
kinds.map((k) => `<option value="${esc(k)}">${esc(k)}</option>`).join("");
|
|
259
259
|
if (kinds.includes(keep)) sel.value = keep;
|
|
260
260
|
|
|
261
261
|
const shown = L.claims.filter((c) => !sel.value || c.kind === sel.value);
|
|
262
|
-
$("claim-count").textContent = shown.length
|
|
262
|
+
$("claim-count").textContent = `${shown.length} of ${L.stats.total} shown`;
|
|
263
263
|
$("claims").innerHTML = shown.length ? claimTable(shown)
|
|
264
264
|
: '<div class="empty">no claims yet — mint one: forge remember "<name>" "<fact>"</div>';
|
|
265
265
|
|
|
266
266
|
$("contested").innerHTML = L.contested.length
|
|
267
|
-
?
|
|
267
|
+
? `<span class="tag contested">${L.contested.length} contested</span>${claimTable(L.contested)}`
|
|
268
268
|
: '<div class="empty">none — no live claim is both uncertain and contradicted</div>';
|
|
269
269
|
|
|
270
270
|
const trust = Object.entries(L.trust).sort((a, b) => b[1] - a[1]);
|
|
271
271
|
$("trust").innerHTML = trust.length
|
|
272
272
|
? "<table><tbody>" + trust.map(([a, u]) =>
|
|
273
|
-
|
|
273
|
+
`<tr><td class="muted">${esc(a)}</td><td>${bar(u)}</td></tr>`).join("") +
|
|
274
274
|
"</tbody></table>"
|
|
275
275
|
: '<div class="empty">no authored claims yet</div>';
|
|
276
276
|
}
|
|
@@ -280,35 +280,42 @@ function renderCost(d) {
|
|
|
280
280
|
const saved = stages.reduce((n, [, s]) => n + (s.savedEstimate || 0), 0);
|
|
281
281
|
const events = stages.reduce((n, [, s]) => n + (s.events || 0), 0);
|
|
282
282
|
const spend = d.spend;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
283
|
+
const noData = (!spend || spend.totalCost <= 0) && saved === 0 && events === 0;
|
|
284
|
+
if (noData) {
|
|
285
|
+
$("cost-stats").innerHTML =
|
|
286
|
+
'<div class="empty">no cost data yet — run forge substrate or forge cost to start tracking</div>';
|
|
287
|
+
} else {
|
|
288
|
+
$("cost-stats").innerHTML =
|
|
289
|
+
(spend && spend.totalCost > 0
|
|
290
|
+
? '<span class="stat"><span class="n ember">$' + spend.totalCost.toFixed(2) + '</span><span class="l">real spend</span></span>' +
|
|
291
|
+
'<span class="stat"><span class="n">' + spend.sessions + '</span><span class="l">sessions</span></span>'
|
|
292
|
+
: '') +
|
|
293
|
+
'<span class="stat"><span class="n' + (spend && spend.totalCost > 0 ? '' : ' ember') + '">' + saved.toLocaleString() + '</span><span class="l">est. tokens saved</span></span>' +
|
|
294
|
+
'<span class="stat"><span class="n">' + events + '</span><span class="l">stage events</span></span>';
|
|
295
|
+
}
|
|
296
|
+
if (spend?.byModel?.length) {
|
|
291
297
|
$("spend-section").innerHTML = '<h3>Spend by model</h3>' +
|
|
292
298
|
'<table><thead><tr><th>model</th><th>cost</th><th>in tok</th><th>out tok</th></tr></thead><tbody>' +
|
|
293
299
|
spend.byModel.map((m) => '<tr><td class="mono">' + esc(m.model) + '</td><td class="num">$' + m.cost.toFixed(4) +
|
|
294
300
|
'</td><td class="num">' + (m.inTokens || 0).toLocaleString() +
|
|
295
301
|
'</td><td class="num">' + (m.outTokens || 0).toLocaleString() + '</td></tr>').join('') + '</tbody></table>';
|
|
296
302
|
} else {
|
|
297
|
-
$("spend-section").innerHTML =
|
|
303
|
+
$("spend-section").innerHTML =
|
|
304
|
+
'<div class="empty">no model spend data yet — real costs appear when ccusage is configured</div>';
|
|
298
305
|
}
|
|
299
306
|
$("stages").innerHTML = stages.length
|
|
300
307
|
? "<table><thead><tr><th>stage</th><th>events</th><th>outcomes</th><th>saved</th></tr></thead><tbody>" +
|
|
301
308
|
stages.map(([name, s]) =>
|
|
302
309
|
'<tr><td class="mono">' + esc(name) + '</td><td class="num">' + s.events + "</td><td>" +
|
|
303
310
|
Object.entries(s.byOutcome || {}).map(([o, n]) =>
|
|
304
|
-
|
|
311
|
+
`<span class="tag">${esc(o)} ${n}</span>`).join(" ") +
|
|
305
312
|
'</td><td class="num">' + (s.savedEstimate || 0).toLocaleString() + "</td></tr>").join("") +
|
|
306
313
|
"</tbody></table>"
|
|
307
314
|
: '<div class="empty">no metrics yet — stages append to .forge/metrics.jsonl as you work</div>';
|
|
308
315
|
$("recent").innerHTML = d.metrics.recent.slice().reverse().map((e) =>
|
|
309
316
|
"<li>" + new Date(e.t).toLocaleTimeString() + ' <span class="st">' + esc(e.stage) + "</span> " +
|
|
310
|
-
esc(e.outcome || "") + (e.savedEstimate ?
|
|
311
|
-
"</li>").join("") || '<li class="empty"
|
|
317
|
+
esc(e.outcome || "") + (e.savedEstimate ? ` <span class="plus">+${e.savedEstimate}</span>` : "") +
|
|
318
|
+
"</li>").join("") || '<li class="empty">no recent events — the substrate records cache hits, gate passes, and routing decisions as you work</li>';
|
|
312
319
|
}
|
|
313
320
|
|
|
314
321
|
async function runImpact() {
|
|
@@ -320,9 +327,9 @@ async function runImpact() {
|
|
|
320
327
|
return;
|
|
321
328
|
}
|
|
322
329
|
out.innerHTML = '<div class="empty">tracing…</div>';
|
|
323
|
-
const res = await fetch(
|
|
330
|
+
const res = await fetch(`/api/impact?target=${encodeURIComponent(target)}`);
|
|
324
331
|
const r = await res.json();
|
|
325
|
-
if (!res.ok) { out.innerHTML =
|
|
332
|
+
if (!res.ok) { out.innerHTML = `<div class="empty">${esc(r.error || "error")}</div>`; return; }
|
|
326
333
|
if (!r.found) {
|
|
327
334
|
out.innerHTML = '<div class="empty">"' + esc(target) + '" not found in the atlas (' +
|
|
328
335
|
r.totalGraphNodes + " nodes)</div>";
|
|
@@ -338,7 +345,7 @@ async function runImpact() {
|
|
|
338
345
|
'</td><td class="mono">' + esc(x.node.name || x.id) + '</td><td class="muted">' +
|
|
339
346
|
esc(x.node.file || "") + "</td></tr>").join("") +
|
|
340
347
|
"</tbody></table>" +
|
|
341
|
-
(r.impacted.length > 40 ?
|
|
348
|
+
(r.impacted.length > 40 ? `<div class="empty">… ${r.impacted.length - 40} more</div>` : "") +
|
|
342
349
|
'<div class="empty" style="margin-top:8px">same trace on the CLI: forge impact ' + esc(target) + "</div>";
|
|
343
350
|
}
|
|
344
351
|
|
|
@@ -376,7 +383,7 @@ document.addEventListener("click", (ev) => {
|
|
|
376
383
|
}
|
|
377
384
|
const el = ev.target.closest(".blame");
|
|
378
385
|
if (!el) return;
|
|
379
|
-
navigator.clipboard?.writeText(
|
|
386
|
+
navigator.clipboard?.writeText(`forge ledger blame ${el.dataset.id8}`).then(() => {
|
|
380
387
|
el.classList.add("copied");
|
|
381
388
|
const prev = el.textContent;
|
|
382
389
|
el.textContent = "copied ✓";
|
package/src/dash.js
CHANGED
|
@@ -111,6 +111,42 @@ export function dashData(root, { nowDay = epochDay() } = {}) {
|
|
|
111
111
|
return { repo: basename(root), nowDay, ledger, metrics, atlas, spend };
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Lightweight summary — counts and status only (no full claim list).
|
|
116
|
+
* @param {string} root
|
|
117
|
+
* @param {{nowDay?: number}} [opts]
|
|
118
|
+
*/
|
|
119
|
+
export function dashSummary(root, { nowDay = epochDay() } = {}) {
|
|
120
|
+
let ledgerStats = emptyLedger().stats;
|
|
121
|
+
let contested = 0;
|
|
122
|
+
try {
|
|
123
|
+
const dir = repoLedger(root);
|
|
124
|
+
ledgerStats = stats(dir, nowDay);
|
|
125
|
+
const all = loadClaims(dir);
|
|
126
|
+
contested = all.filter((c) => {
|
|
127
|
+
if (c.tombstone) return false;
|
|
128
|
+
const v = val(c, nowDay);
|
|
129
|
+
if (v < CONTESTED_BAND[0] || v > CONTESTED_BAND[1]) return false;
|
|
130
|
+
return (c.evidence ?? []).some((e) => validOutcome(e) && e.result === "contradict");
|
|
131
|
+
}).length;
|
|
132
|
+
} catch {}
|
|
133
|
+
let atlasBuilt = false;
|
|
134
|
+
try {
|
|
135
|
+
atlasBuilt = Boolean(loadAtlas(root));
|
|
136
|
+
} catch {}
|
|
137
|
+
let metricEvents = 0;
|
|
138
|
+
try {
|
|
139
|
+
metricEvents = readMetrics(root).length;
|
|
140
|
+
} catch {}
|
|
141
|
+
return {
|
|
142
|
+
claims: ledgerStats.total,
|
|
143
|
+
tombstoned: ledgerStats.tombstoned,
|
|
144
|
+
contested,
|
|
145
|
+
atlasBuilt,
|
|
146
|
+
metricEvents,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
114
150
|
const HTML_PATH = join(dirname(fileURLToPath(import.meta.url)), "dash.html");
|
|
115
151
|
|
|
116
152
|
const sendJson = (res, code, body) => {
|
package/src/emit/copilot.js
CHANGED
package/src/emit/gemini.js
CHANGED
|
@@ -29,7 +29,7 @@ export default {
|
|
|
29
29
|
const had = current.includes("AGENTS.md");
|
|
30
30
|
context.fileName = [...new Set([...current, "AGENTS.md"])];
|
|
31
31
|
settings._forge = ctx.shared.markerString(ctx.hash);
|
|
32
|
-
const action = ctx.shared.writeIfChanged(path, JSON.stringify(settings, null, 2)
|
|
32
|
+
const action = ctx.shared.writeIfChanged(path, `${JSON.stringify(settings, null, 2)}\n`);
|
|
33
33
|
return {
|
|
34
34
|
tool: this.tool,
|
|
35
35
|
target: ".gemini/settings.json",
|
package/src/emit/mcp.js
CHANGED
|
@@ -33,7 +33,7 @@ function mergeJson(path, key, servers) {
|
|
|
33
33
|
}
|
|
34
34
|
if (!added) return { action: "unchanged", note: "present" };
|
|
35
35
|
mkdirSync(dirname(path), { recursive: true });
|
|
36
|
-
writeFileSync(path, JSON.stringify(obj, null, 2)
|
|
36
|
+
writeFileSync(path, `${JSON.stringify(obj, null, 2)}\n`);
|
|
37
37
|
return { action: "written", note: `+${added} server(s)` };
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -58,7 +58,7 @@ function emitContinueYaml(dir, servers) {
|
|
|
58
58
|
lines.push(` - name: ${name}`, " type: stdio", ` command: ${def.command}`, " args:");
|
|
59
59
|
for (const a of def.args || []) lines.push(` - ${JSON.stringify(a)}`);
|
|
60
60
|
}
|
|
61
|
-
const content = lines.join("\n")
|
|
61
|
+
const content = `${lines.join("\n")}\n`;
|
|
62
62
|
if (existsSync(path) && readFileSync(path, "utf8") === content)
|
|
63
63
|
return { action: "unchanged", note: "present" };
|
|
64
64
|
mkdirSync(dir, { recursive: true });
|
package/src/init.js
CHANGED
|
@@ -57,17 +57,28 @@ function unionStrings(a = [], b = []) {
|
|
|
57
57
|
return [...set];
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
/**
|
|
60
|
+
/** Extract guard identity (basename + trailing args) from a hook command for dedup.
|
|
61
|
+
* `bash ~/.forge/guards/cortex.sh prompt` and
|
|
62
|
+
* `"${CLAUDE_PLUGIN_ROOT}"/global/guards/cortex.sh prompt` both → `cortex.sh prompt`. */
|
|
63
|
+
function guardKey(command) {
|
|
64
|
+
const m = command.match(/([^/\\"]+\.sh)\s*(.*)/);
|
|
65
|
+
return m ? `${m[1]} ${m[2]}`.trim() : command;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Merge Forge hook entries into existing hook arrays, matching by guard identity to avoid duplicates. */
|
|
61
69
|
function mergeHooks(existing = {}, template = {}) {
|
|
62
70
|
const merged = { ...existing };
|
|
63
71
|
for (const [event, entries] of Object.entries(template)) {
|
|
64
72
|
const existingEntries = merged[event] || [];
|
|
65
|
-
const
|
|
66
|
-
existingEntries
|
|
73
|
+
const existingKeys = new Set(
|
|
74
|
+
existingEntries
|
|
75
|
+
.flatMap((e) => (e.hooks || []).map((h) => h.command))
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
.map(guardKey),
|
|
67
78
|
);
|
|
68
79
|
const newEntries = [];
|
|
69
80
|
for (const entry of entries) {
|
|
70
|
-
const hooks = (entry.hooks || []).filter((h) => !
|
|
81
|
+
const hooks = (entry.hooks || []).filter((h) => !existingKeys.has(guardKey(h.command)));
|
|
71
82
|
if (hooks.length) {
|
|
72
83
|
newEntries.push({ ...entry, hooks });
|
|
73
84
|
}
|
package/src/sync.js
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
|
-
#
|
|
1
|
+
# <Project> — project instructions
|
|
2
2
|
|
|
3
|
-
Drop this into a
|
|
3
|
+
Drop this into a repo root. Adds project context on top of the global
|
|
4
4
|
`~/.claude/CLAUDE.md`. Keep it lean; prune anything Claude gets right without it.
|
|
5
5
|
|
|
6
6
|
Shared cross-tool rules (stack, commands, engineering rules) live in **@AGENTS.md**
|
|
7
7
|
so Cursor/Codex/Gemini read the same source. This file holds only Claude-specific
|
|
8
|
-
+
|
|
8
|
+
+ project-specific bits. Keep CLAUDE.md thin; put general rules in AGENTS.md.
|
|
9
9
|
|
|
10
10
|
## Stack
|
|
11
|
-
- Runtime:
|
|
12
|
-
- LLM: routed through
|
|
13
|
-
configured routing/env
|
|
14
|
-
|
|
15
|
-
- Delivery surfaces: customer AI **chat widget** (`widget.js`) and **Telegram**
|
|
16
|
-
owner control plane. Infra: Linode VPS + Cloudflare Tunnel/Access.
|
|
11
|
+
- Runtime: <runtime>. DB: <database>.
|
|
12
|
+
- LLM: routed through <provider> — never hardcode a single provider; use the
|
|
13
|
+
configured routing/env.
|
|
14
|
+
- Delivery surfaces: <describe your app surfaces>.
|
|
17
15
|
|
|
18
16
|
## Non-negotiables
|
|
19
|
-
- **Grounding-first.** Customer-facing answers
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
- **Grounding-first.** Customer-facing answers must come from verified data, never
|
|
18
|
+
guessed. If unverified, say so or defer — do not invent numbers, features, or
|
|
19
|
+
availability.
|
|
22
20
|
- **Secrets** live in env / the secrets store, never in code, logs, or commits.
|
|
23
21
|
`.env*` and key files are hook-protected — don't work around it.
|
|
24
|
-
- Neutral, worldwide tone in customer replies (no forced regional greeting).
|
|
25
22
|
|
|
26
23
|
## Workflow
|
|
27
24
|
- Prefer readymade, well-maintained OSS libraries/SDKs over hand-rolled code —
|
|
@@ -29,7 +26,7 @@ so Cursor/Codex/Gemini read the same source. This file holds only Claude-specifi
|
|
|
29
26
|
- Before DB or deploy changes: read the migration/deploy path first; never run
|
|
30
27
|
destructive SQL (`DROP`/`TRUNCATE`) or touch prod without explicit confirmation.
|
|
31
28
|
- Verify every change: run the app's tests/typecheck/build and show output.
|
|
32
|
-
- Deploy via the
|
|
29
|
+
- Deploy via the `<your deploy skill>` skill (safe, staged, with rollback notes).
|
|
33
30
|
|
|
34
31
|
## Commands
|
|
35
32
|
<!-- Fill these in from package.json so Claude doesn't guess: -->
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cognitive-substrate
|
|
3
|
-
description: >-
|
|
4
|
-
Use before ambiguous, expensive, multi-file, or mutating coding work to run
|
|
5
|
-
Forge's cognitive substrate: assumption gate, model routing, impact
|
|
6
|
-
prediction, scope decomposition, memory/lesson lookup, minimality check, and
|
|
7
|
-
verification planning. Trigger when a user asks to edit/refactor/fix/design
|
|
8
|
-
production code, integrate features, choose model effort, inspect blast
|
|
9
|
-
radius, or avoid agent assumptions.
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# Cognitive Substrate
|
|
13
|
-
|
|
14
|
-
Wrap the frozen model in a pre-action check: gate assumptions, route model effort, predict
|
|
15
|
-
blast radius, decompose scope, surface past lessons, and plan verification — **before** editing.
|
|
16
|
-
|
|
17
|
-
## When to run it
|
|
18
|
-
|
|
19
|
-
Any ambiguous, expensive, multi-file, or mutating task. Skip one-line, well-specified fixes.
|
|
20
|
-
In Claude Code it fires automatically on every prompt (UserPromptSubmit hook); in other
|
|
21
|
-
agents run the CLI yourself, or call the MCP tool `substrate_check`.
|
|
22
|
-
|
|
23
|
-
## Run
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
forge substrate "<task>" --json # full contract; use the fields below
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Act on the result
|
|
30
|
-
|
|
31
|
-
1. **`okToProceed: false`** → ask the returned `assumption.questions` before editing. Do not guess.
|
|
32
|
-
2. **`route.tier`** → start at that model tier (cheapest capable); escalate only after an external verifier fails.
|
|
33
|
-
3. **`impact.impactedFiles`** → read these before editing a named symbol/file (the blast radius).
|
|
34
|
-
4. **`scope.clusters`** → split independent groups into separate sessions; note coupled files you didn't name.
|
|
35
|
-
5. **`memory.advisory`** → context, not law; tests and human corrections override it.
|
|
36
|
-
6. **`verification.checklist`** → run it and show output before claiming done.
|
|
37
|
-
|
|
38
|
-
## Worked example
|
|
39
|
-
|
|
40
|
-
`forge substrate "make the auth better"` → `proceed: ASK FIRST` + clarify
|
|
41
|
-
questions: ask them instead of editing. A clear task returns `proceed: yes` plus
|
|
42
|
-
the impacted files — including importers you didn't name.
|
|
43
|
-
|
|
44
|
-
## Deeper single checks
|
|
45
|
-
|
|
46
|
-
`forge preflight "<task>"` (assumptions) · `forge route "<task>"` (model tier) ·
|
|
47
|
-
`forge impact <symbol|file>` (blast radius) · `forge scope <file…>` (decomposition) ·
|
|
48
|
-
`forge context "<task>"` (budgeted context assembly; *computes* what's missing) ·
|
|
49
|
-
`forge imagine "<task>" [--run]` (predicted breaks + minimal covering test suite;
|
|
50
|
-
`--run` dry-runs it in a sandboxed worktree) ·
|
|
51
|
-
`forge diagnose "<error>"` (doom-loop: 3× the same failure signature = stop retrying,
|
|
52
|
-
escalate one tier with the minted diagnosis claim at the head of the prompt).
|
|
53
|
-
MCP equivalents: `assumption_gate`, `route_task`, `predict_impact`, `scope_files`.
|
|
54
|
-
|
|
55
|
-
For the full guide (how it works, extending it, the honesty boundary) and the white paper,
|
|
56
|
-
see `docs/cognitive-substrate/README.md`. `references/capability-map.md` maps faculties to commands.
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Cognitive substrate capability map
|
|
2
|
-
|
|
3
|
-
| Paper capability | Forge surface | Guarantee |
|
|
4
|
-
| --- | --- | --- |
|
|
5
|
-
| Memory | `forge recall`, `forge cortex`, `forge ledger` | Facts/lessons persist as content-addressed ledger claims; `forge ledger blame` shows provenance. |
|
|
6
|
-
| Learning | `forge cortex`, ledger oracles | External outcomes (tests, CI, human accept/revert) move claim confidence; model weights do not change. |
|
|
7
|
-
| Imagination | `forge imagine [--run]`, `forge impact` | Predicted breaks + minimal covering test suite; `--run` dry-runs it in a sandboxed worktree. |
|
|
8
|
-
| Self-correction | `forge verify`, `forge diagnose` | Tests/builds beat model claims; 3× the same failure signature mints a diagnosis + escalation. |
|
|
9
|
-
| Impact-awareness | `forge atlas`, `forge impact` | Known symbols/files and likely dependents are surfaced. |
|
|
10
|
-
| M1 routing | `forge route` | Transparent model-tier recommendation. |
|
|
11
|
-
| M2 assumption gate | `forge preflight`, `forge context` | Under-specified tasks return *computed* missing-set questions. |
|
|
12
|
-
| M3 decomposition | `forge scope` | Import clusters show independent vs coupled files. |
|
|
13
|
-
| M4 goal anchoring | `forge anchor`, `forge substrate` | Changed files are checked against the stated goal. |
|
|
14
|
-
| M5 anti-over-engineering | `forge lean`, `forge uicheck design` | Footprint vs ask; UI slop-distance + fingerprint conformance gate. |
|
|
15
|
-
| M6 inline verification | `forge verify` | External checks are required before done. |
|
|
16
|
-
|
|
17
|
-
Limits: static graph edges are conservative; memory relevance and model routing are advisory; non-hook tools cannot be forcibly blocked.
|