@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.
@@ -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.0",
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.0",
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": "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.0...HEAD
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.0",
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 config for every AI coding agent — a cross-tool config layer plus a cognitive substrate (memory, blast-radius, guardrails) for Claude Code, Codex, Cursor, Gemini, Aider, and more."><meta name="theme-color" content="#171310"><style>
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.6.0 · Node &gt;=20</p><h1>Live status, straight from the repository.</h1><p class="lead">One config for every AI coding agent — a cross-tool config layer plus a cognitive substrate (memory, blast-radius, guardrails) for 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/whitepaper-implementation-plan-x8f7bh @ 26b76cd</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 vs always-premium</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
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 &gt;=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"><li>Optional embeddings tier (src/embed.js, ADR-0005; ROADMAP &quot;Next&quot;): set</li><li>forge uicheck visual &lt;file-or-url&gt; — the Playwright visual loop</li><li>Ledger read-path flip (P2). Reads are now a merged view (legacy ∪ ledger) via the</li></ul><p class="muted">Benchmark sections indexed: 3 · benchmarks file updated 2026-07-07.</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-07T21:15:58.111Z from 26b76cd). 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>
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
- console.log("Commands:");
50
- for (const [name, desc] of Object.entries(COMMANDS)) {
51
- console.log(` ${name.padEnd(8)} ${desc}`);
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(`\nRun \`${BRAND.cli} <command> --help\` for details.`);
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 ? " · " + 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 + " problem(s)"}`);
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(" " + r.raw.trim().split("\n").slice(-6).join("\n "));
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
- '<td><span class="blame" data-id8="' + esc(id8) + '">forge ledger blame ' + esc(id8) + "</span></td>";
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 = " + d.repo;
243
+ $("hdr-repo").textContent = ${d.repo}`;
244
244
  $("hdr-meta").textContent =
245
- L.stats.total + " claims · " + L.stats.tombstoned + " tombstoned · day " + d.nowDay;
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 &lt;0.35</span></span>' +
251
251
  Object.entries(L.stats.byKind).map(([k, n]) =>
252
- '<span class="stat"><span class="n">' + n + '</span><span class="l">' + esc(k) + "</span></span>").join("");
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) => '<option value="' + esc(k) + '">' + esc(k) + "</option>").join("");
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 + " of " + L.stats.total + " shown";
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 "&lt;name&gt;" "&lt;fact&gt;"</div>';
265
265
 
266
266
  $("contested").innerHTML = L.contested.length
267
- ? '<span class="tag contested">' + L.contested.length + " contested</span>" + claimTable(L.contested)
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
- '<tr><td class="muted">' + esc(a) + "</td><td>" + bar(u) + "</td></tr>").join("") +
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
- $("cost-stats").innerHTML =
284
- (spend && spend.totalCost > 0
285
- ? '<span class="stat"><span class="n ember">$' + spend.totalCost.toFixed(2) + '</span><span class="l">real spend</span></span>' +
286
- '<span class="stat"><span class="n">' + spend.sessions + '</span><span class="l">sessions</span></span>'
287
- : '') +
288
- '<span class="stat"><span class="n' + (spend && spend.totalCost > 0 ? '' : ' ember') + '">' + saved.toLocaleString() + '</span><span class="l">est. tokens saved</span></span>' +
289
- '<span class="stat"><span class="n">' + events + '</span><span class="l">stage events</span></span>';
290
- if (spend && spend.byModel && spend.byModel.length) {
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
- '<span class="tag">' + esc(o) + " " + n + "</span>").join(" ") +
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 ? ' <span class="plus">+' + e.savedEstimate + "</span>" : "") +
311
- "</li>").join("") || '<li class="empty">—</li>';
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("/api/impact?target=" + encodeURIComponent(target));
330
+ const res = await fetch(`/api/impact?target=${encodeURIComponent(target)}`);
324
331
  const r = await res.json();
325
- if (!res.ok) { out.innerHTML = '<div class="empty">' + esc(r.error || "error") + "</div>"; return; }
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 ? '<div class="empty">… ' + (r.impacted.length - 40) + " more</div>" : "") +
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("forge ledger blame " + el.dataset.id8).then(() => {
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) => {
@@ -1,7 +1,7 @@
1
1
  // GitHub Copilot's coding agent reads the root AGENTS.md natively (since 2025-08-28).
2
2
  export default {
3
3
  tool: "Copilot",
4
- emit(ctx) {
4
+ emit(_ctx) {
5
5
  return {
6
6
  tool: this.tool,
7
7
  target: "AGENTS.md",
@@ -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) + "\n");
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) + "\n");
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") + "\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
- /** Merge Forge hook entries into existing hook arrays, matching by command to avoid duplicates. */
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 existingCommands = new Set(
66
- existingEntries.flatMap((e) => (e.hooks || []).map((h) => h.command)).filter(Boolean),
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) => !existingCommands.has(h.command));
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
@@ -31,7 +31,7 @@ export function assemble(rules) {
31
31
  for (const rule of section.rules || []) out.push(`- ${rule}`);
32
32
  out.push("");
33
33
  }
34
- return out.join("\n").trimEnd() + "\n";
34
+ return `${out.join("\n").trimEnd()}\n`;
35
35
  }
36
36
 
37
37
  function loadRules(targetRoot) {
@@ -1,27 +1,24 @@
1
- # Hostlelo — project instructions
1
+ # <Project> — project instructions
2
2
 
3
- Drop this into a Hostlelo repo root. Adds project context on top of the global
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
- + Hostlelo-specific bits. Keep CLAUDE.md thin; put general rules in AGENTS.md.
8
+ + project-specific bits. Keep CLAUDE.md thin; put general rules in AGENTS.md.
9
9
 
10
10
  ## Stack
11
- - Runtime: Node.js. DB: self-hosted Postgres (18+) with **pgvector** on the VPS.
12
- - LLM: routed through **OpenRouter** — never hardcode a single provider; use the
13
- configured routing/env, and pick cheap models for classify/embed, stronger
14
- models only for hard generation.
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 (esp. prices/plans) must come from
20
- the verified KB, never guessed. If unverified, say so or defer — do not invent
21
- numbers, features, or availability.
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 `hostlelo-deploy` skill (safe, staged, with rollback notes).
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.