From 1e47df0c642bc9f1f3f2665d06c362fd999a1e03 Mon Sep 17 00:00:00 2001 From: Juber Shaikh <40266375+CodeWithJuber@users.noreply.github.com> Date: Mon, 6 Jul 2026 00:12:50 +0400 Subject: [PATCH 1/2] feat(substrate): auto-run in Claude Code, harden prototype, slim package, pro docs - Ambient auto-use: UserPromptSubmit hook injects the full substrate advisory (assumption gate + routing + blast radius + memory + verify) when it matters, silent otherwise. Load-only, fail-safe, never writes .forge/ from a hook. - Cross-tool auto-use: source/rules.json substrate section emits the run-before- risky-work rule into every tool config via forge init. - Security: drop pickle cache in impact_oracle world_model (RCE via caller-supplied cache_dir); JSON node-link only + cache_dir containment. - Package: stop publishing docs/cognitive-substrate (~2MB); remove redundant zips (source lives unzipped in research/). npm tarball ~2.3MB -> 282KB. - Perf: substrateCheck no longer recomputes the assumption assessment. - Docs: professional example-rich cognitive-substrate guide; README consolidated. --- CHANGELOG.md | 12 + README.md | 99 +++--- docs/cognitive-substrate/README.md | 281 ++++++++++++++---- .../deliverable-package.md | 68 +++-- .../cognitive-substrate/impact_oracle_src.zip | Bin 26890 -> 0 bytes docs/cognitive-substrate/router_gate_src.zip | Bin 31556 -> 0 bytes global/tools/cognitive-substrate/SKILL.md | 62 +++- package.json | 1 - .../impact_oracle/world_model.py | 67 +++-- skills/cognitive-substrate/SKILL.md | 62 +++- source/rules.json | 12 + src/cortex_hook_main.js | 26 +- src/substrate.js | 65 +++- 13 files changed, 566 insertions(+), 189 deletions(-) delete mode 100644 docs/cognitive-substrate/impact_oracle_src.zip delete mode 100644 docs/cognitive-substrate/router_gate_src.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index c12496a..5fedd0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- **Substrate now auto-runs in Claude Code.** The `UserPromptSubmit` hook injects the full substrate advisory (assumption gate + model routing + blast radius + memory + verify) when it matters, silent otherwise. Load-only — never builds/writes `.forge/` from a hook, fail-safe, never blocks. +- **Cross-tool auto-use.** Added a `substrate` section to `source/rules.json` so `forge init` emits the "run substrate before risky work" rule into every tool's config (AGENTS.md, .cursor/rules, …). +- **Docs rewritten** — `docs/cognitive-substrate/README.md` is a professional, example-rich guide (real command output, auto-use setup, extension points); README/SKILL aligned. + +### Fixed + +- **Security (research prototype): removed pickle-based cache** in `impact_oracle/world_model.py` — it was an insecure-deserialization (RCE) vector on a caller-supplied `cache_dir`. Now JSON node-link only, with `cache_dir` containment inside `root`. +- **Smaller npm package** — stopped publishing `docs/cognitive-substrate` (a ~2 MB whitepaper + zips); tarball dropped to ~280 KB. Removed the redundant `*_src.zip` binaries (source lives unzipped in `research/`). +- **Perf** — `substrateCheck` no longer recomputes the assumption assessment. + ### Added - **Forge Cognitive Substrate** — one pre-action command (`forge substrate`) and MCP surface (`substrate_check`, `predict_impact`, `assumption_gate`) that combines assumption gating, transparent model routing, impact prediction, scope decomposition, Cortex lessons, minimality warnings, and verification planning. diff --git a/README.md b/README.md index 367226f..c04d41e 100644 --- a/README.md +++ b/README.md @@ -17,26 +17,41 @@ enforces the non-negotiables as deterministic guards, and adds a code-graph, cross-session memory, and a cost governor. Works with **Claude Code, Codex, Cursor, Gemini CLI, Aider, Copilot, Windsurf/Devin, and Zed**. +**Install** — pick one: + +```bash +# A) Claude Code / Codex — as a plugin (recommended) +/plugin marketplace add CodeWithJuber/forgekit +/plugin install forgekit + +# B) Clone and run the installer (puts `forge` on your PATH) +git clone https://github.com/CodeWithJuber/forgekit.git +cd forgekit && bash install.sh +``` + +**Use** — in any repository: + ```bash -# clone, then: -bash install.sh # symlinks into ~/.forge + ~/.claude, puts `forge` on PATH -forge init # in any repo: emit every tool's config from one source +forge init # emit every tool's config from one source forge substrate "task" # assumption gate + route + impact + scope + verify forge doctor # verify everything is wired ``` -Prefer a plugin? `/plugin marketplace add ` then `/plugin install forgekit`. -Prefer npm? Forge publishes to **GitHub Packages** as `@codewithjuber/forgekit`. Point the -scope at the registry and authenticate once (GitHub Packages requires a token even for public -installs), then run it: +
+CI / devcontainer install via npm (GitHub Packages) + +Forge publishes to GitHub Packages as `@codewithjuber/forgekit`. GitHub Packages requires a +token even for public installs, so authenticate the scope once, then run it: ```bash echo "@codewithjuber:registry=https://npm.pkg.github.com" >> ~/.npmrc -echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN" >> ~/.npmrc # token needs read:packages +echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN" >> ~/.npmrc # needs read:packages npx @codewithjuber/forgekit init ``` -All three channels drive the **same** `global/` tree. +
+ +All channels drive the **same** `global/` tree. ## Why @@ -92,44 +107,44 @@ On **Claude Code** it's fully ambient (hooks). Other tools read the lessons from and a zero-dependency MCP server (`forge cortex-mcp`). Everything lives in `.forge/lessons/` — git-committable and auditable. Try it: `node examples/cortex-demo.mjs`. -## Forge Cognitive Substrate — one pre-action gate +## Forge Cognitive Substrate — the check that runs before every edit -Forge now wraps the agent loop with the paper's cognitive-substrate controls. Run one command before ambiguous, expensive, or mutating work: +A frozen model can't remember, can't foresee, and can't see what an edit will break. The +**cognitive substrate** supplies those faculties from the outside: a fast, mostly-deterministic +check (no extra LLM call) that runs _before_ the agent touches code. One command does it all: ```bash -forge substrate "Fix the checkout bug in `src/payments.ts` and add tests" -forge substrate "Refactor auth" --json -forge impact computeTax +forge substrate "Change verifyToken in src/auth.js to require length > 20; update tests" ``` -`substrate` returns the assumption gate, model route, impact radius, scope clusters, relevant Cortex lessons, minimality warnings, and a verification checklist. MCP-capable extensions get the same flow through `substrate_check`, `predict_impact`, and `assumption_gate`. Deterministic checks are asserted; memory relevance, routing fit, and minimality remain advisory where the research is not a hard guarantee. - -Full paper bundle and original artifacts live in -[`docs/cognitive-substrate/`](docs/cognitive-substrate/): PDF, HTML, evidence map, -ecosystem map, and the original router-gate / impact-oracle prototype packages. - -## Forge Preflight — size the work before spending tokens - -An LLM is a fixed-capacity stochastic predictor. Most of the cost/quality bleed comes from -feeding it the wrong-sized task with the wrong-sized context and then over-trusting the output. -**Preflight** is the cheap, deterministic layer that runs _before_ the tokens — no LLM, no guessing: - -- **`forge preflight ""`** — the assumption detector. Scans the task for symbols/files the - repo doesn't define — the things the model would otherwise _assume_ — and surfaces them so it - asks instead of confabulating. Also fires on `UserPromptSubmit`. (The research whitespace: no - shipping tool pre-scans the repo before acting.) -- **`forge route ""`** — recommends the cheapest _capable_ model (Haiku → Sonnet → Opus → - Fable) from code-task complexity (files, fan-out, churn, past-mistake density, ambiguity). A - prime-finder gets Haiku, not Fable. `forge route gateway` emits a LiteLLM config for real - auto-routing. -- **`forge scope `** — a zero-dep import graph → independent clusters (“run these as - separate sessions”) + the coupled files you didn't mention. -- **`forge uicheck `** — exact WCAG contrast math. The design rules (anti-slop, empty - states, specific errors, AI-UX patterns) emit to every tool; the frontend-verifier _asserts_ - only the deterministic and keeps taste _advisory_ — so AI UI-audits stop hallucinating. - -Everything is advisory and never blocks. Cross-tool via the `preflight_check` / `route_task` / -`scope_files` MCP tools. +It returns, in one contract: the **assumption gate** (is the task clear enough to start?), +the cheapest **capable model**, the predicted **blast radius** (which files an edit breaks), +**scope** clusters (what to split into separate sessions), relevant **Cortex lessons**, +**minimality** warnings, and a **verification** checklist. + +**It runs itself.** In Claude Code a `UserPromptSubmit` hook fires the substrate on every +prompt and adds a short advisory only when something needs attention — never blocking, never +nagging on a clean task. Other tools (Codex, Cursor, Gemini, Aider…) get a rule in their +config telling the agent to run it, plus the MCP tools `substrate_check`, `assumption_gate`, +`predict_impact`, `route_task`, and `scope_files`. + +Each check is also its own command: + +| Command | Answers | +| ----------------------------- | ------------------------------------------------------------------ | +| `forge preflight ""` | Is this clear enough to start? (flags unknown names + vague words) | +| `forge route ""` | Cheapest capable model — Haiku → Sonnet → Opus → Fable | +| `forge impact ` | What will this edit break? (reverse-dependency blast radius) | +| `forge scope ` | Independent vs. coupled files → separate sessions | +| `forge uicheck ` | Exact WCAG contrast math for UI work | + +Deterministic checks (repo grounding, graph traversal, routing arithmetic) are **asserted**; +model fit, minimality, and memory relevance stay **advisory**. Everything is advisory overall +and never blocks — tests and human corrections always win. + +**→ Full guide with worked examples, auto-use setup, and how to extend it: +[`docs/cognitive-substrate/`](docs/cognitive-substrate/)** (also holds the white paper, +evidence map, and ecosystem map). ## Commands diff --git a/docs/cognitive-substrate/README.md b/docs/cognitive-substrate/README.md index 5e85ea3..c47d7aa 100644 --- a/docs/cognitive-substrate/README.md +++ b/docs/cognitive-substrate/README.md @@ -1,74 +1,243 @@ -# Cognitive Substrate Paper Bundle +# The Forge Cognitive Substrate -This directory is the research-backed product map for Forge's cognitive-substrate system. -It keeps the full paper, evidence maps, and original prototype packages next to the -production Node implementation so users can install once and use the system from any -Forge-supported agent. +**Coding agents forget what they learned, assume what they don't know, and break code they +can't see.** The substrate is a fast, mostly-deterministic check that runs *before* an agent +edits your code: it flags an unclear task, picks the cheapest capable model, and shows what +an edit will break — all from the repo you already have, with no extra LLM call. -## Fastest Path +In Claude Code it runs **automatically**. In other tools you (or the agent) run one command. + +> Why this exists, in one line: a frozen model is a stateless function `y = f(x)` — no memory, +> no foresight, a fixed window. Those faculties can't be prompted in; they have to be supplied +> from the outside. This is that outside layer. Full argument: the white paper +> ([PDF](./cognitive_substrate_whitepaper.pdf) · [HTML](./cognitive_substrate_whitepaper.html)). + +--- + +## Install (about 2 minutes) + +```bash +# Claude Code / Codex — install as a plugin +/plugin marketplace add CodeWithJuber/forgekit +/plugin install forgekit + +# …or clone and run the installer (puts `forge` on your PATH) +git clone https://github.com/CodeWithJuber/forgekit.git +cd forgekit && bash install.sh +``` + +Then, inside any project: + +```bash +forge init # writes each AI tool's native config from one source +forge atlas # builds the code graph (needed for blast-radius checks) +``` + +That's it. `forge init` configures Claude Code, Codex, Cursor, Gemini, Aider, Copilot, Zed, +Continue, and Roo where supported. + +--- + +## It runs itself (the main benefit) + +You don't have to remember to use it. + +**In Claude Code** — a hook runs the substrate on **every prompt** and adds a short note *only +when something needs attention* (unclear task, big blast radius, pricey model). It never blocks +and never nags on a clean, simple task. Real example — you type *"refactor computeTax in +math.js"* and the agent silently receives: + +```text +Forge substrate — pre-action advisory (advisory, never blocks): +- Under-specified (high risk). Ask before editing: + • What constraints must be respected: performance, dependencies, style, or compatibility? +- Suggested model: Haiku 4.5 (simple); escalate only on a verifier failure. +- Predicted blast radius (2): invoice.js, math.js. Review these before editing. +- Verify with: review impacted files before editing · run the narrowest affected test first +``` + +**In other AI tools** (Codex, Cursor, Gemini, Aider…) — `forge init` writes a rule into their +config file telling the agent to run the check itself, and exposes it as MCP tools it can call. +See [In other AI tools](#in-other-ai-tools). + +--- + +## The one command ```bash -# one time, from this repo -bash install.sh +forge substrate "" +``` + +It runs the whole check and gives a plain verdict. Two real examples: -# one time, inside any project -forge init +**Vague task → it tells you to ask first:** -# every day, before ambiguous or mutating work -forge substrate "Fix the checkout bug and add tests" -forge substrate "Refactor auth safely" --json -forge impact computeTax +```console +$ forge substrate "make the auth better" + + proceed: ASK FIRST + assumption: high risk · completeness 0.23 + clarify: + - What exactly should this produce, and how will we know it is correct? + impact: 0 file(s) predicted ``` -For MCP-capable tools, call the same flow through: +**Clear task → it clears you and shows the blast radius:** + +```console +$ forge substrate "Change verifyToken in src/auth.js to require length > 20; update tests" + + proceed: yes + assumption: medium risk · completeness 0.63 + route: Haiku 4.5 (simple) + impact: 3 file(s) predicted + - src/auth.js + - src/login.js (imports verifyToken — you didn't mention it) + - src/session.js (imports verifyToken — you didn't mention it) + verify: + - run the narrowest affected test first, then the broader suite +``` -- `substrate_check` — full pre-action gate. -- `assumption_gate` — ask/proceed decision and clarifying questions. -- `predict_impact` — blast-radius prediction for a symbol or file. +The second run found the two files that import `verifyToken` but you never named — the +"forgot the coupled file" bug, caught *before* the edit. Add `--json` for machine-readable +output (see [Use it in a script](#use-it-in-a-script)). -## Install Choices +--- -| Path | Best For | Steps | +## The five checks (each is also its own command) + +`forge substrate` bundles these. Run any one on its own when that's all you need. + +| Command | Answers | One-line example | | --- | --- | --- | -| Claude/Codex plugin | Extension-style install | Install the plugin, then run `forge init` in each repo. | -| Git clone | Lowest friction local setup | `bash install.sh`, then `forge init`. | -| npm package | CI/devcontainer usage | `npx @codewithjuber/forgekit init`. | +| `forge preflight ""` | Is this task clear enough to start? | flags names not in the repo + vague words | +| `forge route ""` | Which model is cheapest-but-capable? | trivial → Haiku · hard → Opus/Fable | +| `forge impact ` | What will this edit break? | reverse-dependency blast radius | +| `forge scope ` | Can this be split into sessions? | independent vs. coupled files | +| `forge verify` | Did it actually work? | runs the real tests/build, not the model's word | + +Real output for the two most-used: -After `forge init`, Forge emits native config for Claude Code, Codex, Cursor, Gemini, -Aider, Copilot/VS Code, Windsurf/Devin, Zed, Continue, and Roo where supported. Tools -without hooks receive advisory context plus MCP config; Forge does not pretend it can -force hooks into hosts that do not expose them. +```console +$ forge preflight "fix the thing in authManager so it works properly" -## Included Artifacts +- `authManager` — not found in the code. Different name, or should it be created? +- Ambiguous: "properly" — state concrete acceptance criteria. +- Which specific file, module, component, or symbol should this change touch? +``` + +```console +$ forge impact verifyToken + target: verifyToken ✓ found + impacted files: 3 + - src/auth.js + - src/login.js + - src/session.js +``` -- [`cognitive_substrate_whitepaper.pdf`](./cognitive_substrate_whitepaper.pdf) — full paper. -- [`cognitive_substrate_whitepaper.html`](./cognitive_substrate_whitepaper.html) — browser-readable paper. -- [`deliverable-package.md`](./deliverable-package.md) — package overview and headline results. -- [`evidence_map.md`](./evidence_map.md) — source/status map for load-bearing evidence. -- [`ecosystem_map.md`](./ecosystem_map.md) — capability-vs-tooling map. -- [`impact_oracle_src.zip`](./impact_oracle_src.zip) — original Prototype I package. -- [`router_gate_src.zip`](./router_gate_src.zip) — original Prototype II package. -- [`../../research/python-prototypes/`](../../research/python-prototypes/) — unzipped prototype source preserved for auditability. +--- -## Paper-To-Forge Map +## Read the result (what to do) -| Paper capability | Production Forge surface | Status | +| Field | What it means | Do this | | --- | --- | --- | -| Memory | `forge recall`, `forge cortex` | File-backed and auditable; relevance is advisory. | -| Learning | `forge cortex` | Outcome-confirmed lessons; model weights do not change. | -| Imagination | `forge impact`, `forge substrate` | Static graph blast-radius simulation. | -| Self-correction | `forge verify`, doom-loop guard | External checks beat model claims. | -| Impact-awareness | `forge atlas`, `forge impact` | Known symbols/files and likely dependents surfaced. | -| M1 routing | `forge route`, `forge substrate` | Transparent cheapest-capable tier recommendation. | -| M2 assumption gate | `forge preflight`, `forge substrate` | Under-specified tasks return questions. | -| M3 decomposition | `forge scope`, `forge substrate` | Independent/coupled file clusters. | -| M4 goal anchoring | `forge substrate` | One pre-action objective/risk/verification summary. | -| M5 anti-over-engineering | `forge substrate`, `lean-guard` | Broad work gets minimality warnings. | -| M6 inline verification | `forge verify` | Checklist and external verification discipline. | - -## Honest Boundary - -Deterministic parts are asserted: repo symbol/file grounding, graph traversal, emitted -config, protected-file guards, and test/build commands. Research-edge judgments remain -advisory: memory relevance, model fit, scope minimality, and whether a verification -checklist is sufficient for a particular production environment. +| `okToProceed: false` | task is under-specified | ask the `assumption.questions`, don't guess | +| `route.tier` | cheapest capable model | start there; only escalate if a verifier fails | +| `impact.impactedFiles` | predicted blast radius | read these before editing | +| `scope.clusters` | independent vs. coupled work | split independent groups into separate sessions | +| `memory.advisory` | past lessons for this area | context, not law — tests override it | +| `verification.checklist` | how to prove it works | run it, show the output, then say "done" | + +--- + +## In other AI tools + +Tools without a hook surface get the substrate two ways, both written by `forge init`: + +1. **A rule in their config** (`AGENTS.md`, `.cursor/rules`, `GEMINI.md`, …): *"Before + ambiguous, expensive, multi-file, or mutating work, run `forge substrate "" --json` + (or the MCP tool `substrate_check`). If `okToProceed` is false, ask the questions first."* + +2. **MCP tools** any MCP-capable agent can call directly: + + | MCP tool | Does | + | --- | --- | + | `substrate_check` | full pre-action check | + | `assumption_gate` | ask/proceed + questions | + | `predict_impact` | blast radius | + | `route_task` | model recommendation | + | `scope_files` | independent vs. coupled | + +Forge never pretends it can force a hook into a tool that has none — it's ambient on Claude +Code, and agent-invoked everywhere else. + +--- + +## Use it in a script + +```bash +forge substrate "update verifyToken in src/auth.js" --json +``` + +```jsonc +{ + "okToProceed": false, + "assumption": { "risk": "high", "shouldAsk": true, "questions": ["…"] }, + "route": { "tier": "simple", "model": { "name": "Haiku 4.5" } }, + "impact": { "impactedFiles": ["src/auth.js", "src/login.js"] }, + "verification": { "checklist": ["npm test", "npm run typecheck"] } +} +``` + +Gate your agent's next step on `okToProceed`; feed `route.tier` to your model picker; read +`impact.impactedFiles` before editing. + +--- + +## Extend it + +Small, pure functions — change the one piece you need, then run `npm test`. + +| To change… | Edit | +| --- | --- | +| how often it asks | `source/substrate.json` → `defaults.askThreshold` (0.6) | +| blast-radius sensitivity | `source/substrate.json` → `defaults.impactThreshold` (0.1) | +| a routing signal | `src/route.js` → `rubricComplexity()` | +| an assumption question | `src/preflight.js` → `DIMENSIONS[]` | +| the verify checklist | `src/substrate.js` → `verificationChecklist()` | +| when the ambient hook speaks | `src/substrate.js` → `substrateContext()` | +| the cross-tool wording | `source/rules.json` → `substrate` section (then `forge init`) | + +--- + +## Honest limits + +- **Heuristic, not benchmarked.** Rubrics were tuned on small hand-labeled sets. Judge after + real use. +- **The graph is regex-approximate.** Dynamic dispatch / DI / generated code can be missed — + impact is *conservative* (catches the obvious dependents), not a sound call graph. +- **Assumption detection is lexical.** It catches unclear *names and wording*, not wrong + *intent*. +- **Auto-run needs a hook surface** — fully ambient on Claude Code; agent-invoked elsewhere. + +What's **asserted** (safe to gate on): repo symbol/file grounding, graph traversal, scope +decomposition, the routing arithmetic, and the test/build commands. What's **advisory** +(flagged, never asserted): whether the model is capable enough, whether a change is +over-engineered, and whether a past lesson is relevant. Tests and human corrections always win. + +--- + +## Learn more + +- **White paper** — the full argument: [PDF](./cognitive_substrate_whitepaper.pdf) · + [HTML](./cognitive_substrate_whitepaper.html) +- **[Package overview](./deliverable-package.md)** — headline results and prototypes +- **[Evidence map](./evidence_map.md)** — every load-bearing statistic re-graded against + primary sources (5 confirmed, 5 vendor-reported, 2 dropped) +- **[Ecosystem map](./ecosystem_map.md)** — each capability vs. the real 2026 tool stack +- **[Prototype source](../../research/python-prototypes/)** — the auditable Python originals + +**How the paper maps to what ships:** memory → `recall`/`cortex` · learning → `cortex` · +imagination → `impact` · self-correction → `verify` · impact-awareness → `atlas`/`impact` · +M1 routing → `route` · M2 assumption gate → `preflight` · M3 decomposition → `scope` · +M4 goal-anchoring, M5 minimality, M6 verification → `substrate`. diff --git a/docs/cognitive-substrate/deliverable-package.md b/docs/cognitive-substrate/deliverable-package.md index dbc8f98..89f9578 100644 --- a/docs/cognitive-substrate/deliverable-package.md +++ b/docs/cognitive-substrate/deliverable-package.md @@ -1,41 +1,48 @@ # A Cognitive Substrate for Coding Agents — Deliverable Package + ### Theory → Evidence → Build-Map edition (v2) -**One-line thesis:** The faculties a coding agent lacks — memory, learning, imagination, self-correction, impact-awareness — are not gaps in the model's *knowledge* but structural consequences of what a frozen transformer *is* (a stateless map `y = f_θ(x)`, fixed weights, bounded window). They cannot be prompted or tooled away; they can only be supplied by **re-wrapping the input→process→output loop** into a closed, stateful cycle around the frozen model. +**One-line thesis:** The faculties a coding agent lacks — memory, learning, imagination, self-correction, impact-awareness — are not gaps in the model's _knowledge_ but structural consequences of what a frozen transformer _is_ (a stateless map `y = f_θ(x)`, fixed weights, bounded window). They cannot be prompted or tooled away; they can only be supplied by **re-wrapping the input→process→output loop** into a closed, stateful cycle around the frozen model. -**What v2 adds.** The first edition argued the five faculties from first principles and prototyped the one that is buildable today. This edition (1) **grounds the argument in the field's own evidence** — twelve load-bearing pain-point statistics independently re-grounded from primary sources and graded *confirmed / vendor-reported / unverifiable*; (2) adds **six metacognitive mechanisms** the frozen loop also lacks (routing, assumption gate, decomposition, goal-anchoring, anti-over-engineering, inline verification); (3) **maps all eleven capabilities against the real 2026 Claude-Code stack**, marking each solved / partial / residual-gap so we say clearly *what not to build*; and (4) ships a **second runnable prototype** — a complexity-aware router + assumption gate, evaluated live on real models. +**What v2 adds.** The first edition argued the five faculties from first principles and prototyped the one that is buildable today. This edition (1) **grounds the argument in the field's own evidence** — twelve load-bearing pain-point statistics independently re-grounded from primary sources and graded _confirmed / vendor-reported / unverifiable_; (2) adds **six metacognitive mechanisms** the frozen loop also lacks (routing, assumption gate, decomposition, goal-anchoring, anti-over-engineering, inline verification); (3) **maps all eleven capabilities against the real 2026 Claude-Code stack**, marking each solved / partial / residual-gap so we say clearly _what not to build_; and (4) ships a **second runnable prototype** — a complexity-aware router + assumption gate, evaluated live on real models. -> **Governing discipline (the user's, adopted throughout):** *AI output is mathematically-calculated probability — non-deterministic, and never blindly trusted.* Every claim in this package is graded by how well it is sourced; every prototype decision is a transparent, attributable rule rather than another opaque model call; and trust is always earned by an **external** check, never asserted by the model. +> **Governing discipline (the user's, adopted throughout):** _AI output is mathematically-calculated probability — non-deterministic, and never blindly trusted._ Every claim in this package is graded by how well it is sourced; every prototype decision is a transparent, attributable rule rather than another opaque model call; and trust is always earned by an **external** check, never asserted by the model. --- ## What's in this package ### 1. The white paper (core deliverable) — 48 pp + - **`cognitive_substrate_whitepaper.pdf`** / **`cognitive_substrate_whitepaper.html`** — the full study, 13 sections + 3 appendices, 7 figures. - - **§1–3** the root cause and the five faculties (from v1): *why* each faculty is structurally absent (P1 statelessness, P2 frozen weights, P3 bounded context), each grounded in the real literature. - - **§4 Evidence** *(new)* — the twelve statistics, re-grounded. 5 confirmed, 5 vendor-reported, 2 unverifiable. + - **§1–3** the root cause and the five faculties (from v1): _why_ each faculty is structurally absent (P1 statelessness, P2 frozen weights, P3 bounded context), each grounded in the real literature. + - **§4 Evidence** _(new)_ — the twelve statistics, re-grounded. 5 confirmed, 5 vendor-reported, 2 unverifiable. - **§5** the Qur'anic epistemic lens — design framing/ethics, never technical authority. - - **§6 Six mechanisms** *(new)* — M1 routing, M2 assumption gate, M3 decomposition, M4 goal-anchoring, M5 anti-over-engineering, M6 inline verification — each formalized, with ecosystem status and a Qur'anic anchor. + - **§6 Six mechanisms** _(new)_ — M1 routing, M2 assumption gate, M3 decomposition, M4 goal-anchoring, M5 anti-over-engineering, M6 inline verification — each formalized, with ecosystem status and a Qur'anic anchor. - **§7** the cognitive substrate, now with the six-mechanism metacognitive control layer (Figure 3). - - **§8 Prototype I** the impact oracle (from v1). **§9 Prototype II** *(new)* the router + gate. **§10 Build-map** *(new)* the ranked opportunity list. + - **§8 Prototype I** the impact oracle (from v1). **§9 Prototype II** _(new)_ the router + gate. **§10 Build-map** _(new)_ the ranked opportunity list. - **§11** new-vs-reinvented. **§12** limitations. **§13** conclusion. ### 2. Prototype I — Codebase World-Model + Impact Oracle -- **`impact_oracle_src.zip`** — parses a codebase (AST) into a **persistent dependency graph**, predicts the **blast radius** of a proposed edit via reverse-dependency traversal with confidence decay. `python demo.py` runs end-to-end; `pytest` → **36 tests pass** with zero setup. Builds opportunity #3. -### 3. Prototype II — Complexity-aware router + Assumption gate *(new)* -- **`router_gate_src.zip`** — the two mechanisms at the top of the build-map, composed as `gate → route → execute → verify → escalate`. Both are **transparent additive rubrics**, not opaque LLM calls; escalation is driven by an external check. `python demo.py`, `pytest` → **19 tests pass**, `python evaluate.py --live` reproduces the live numbers. +- [`research/python-prototypes/impact_oracle/`](../../research/python-prototypes/impact_oracle/) — parses a codebase (AST) into a **persistent dependency graph**, predicts the **blast radius** of a proposed edit via reverse-dependency traversal with confidence decay. `python demo.py` runs end-to-end; `pytest` → **36 tests pass** with zero setup. Builds opportunity #3. (Shipped in production as `forge impact` / `forge atlas`.) + +### 3. Prototype II — Complexity-aware router + Assumption gate _(new)_ + +- [`research/python-prototypes/router_gate/`](../../research/python-prototypes/router_gate/) — the two mechanisms at the top of the build-map, composed as `gate → route → execute → verify → escalate`. Both are **transparent additive rubrics**, not opaque LLM calls; escalation is driven by an external check. `python demo.py`, `pytest` → **19 tests pass**, `python evaluate.py --live` reproduces the live numbers. (Shipped in production as `forge route` / `forge preflight`.) - **`eval_results.json`** — the live evaluation record (real measured tokens). -### 4. Evidence & ecosystem maps *(new)* +### 4. Evidence & ecosystem maps _(new)_ + - **`evidence_map.json`** / **`evidence_map.md`** — every load-bearing statistic, its primary source, and its status. - **`ecosystem_map.json`** / **`ecosystem_map.md`** — every faculty & mechanism vs. the real stack, with residual gap and proposed contribution. ### 5. Figures & schematics -- **`schematic_loop.png`** (Fig 1) · **`schematic_system.png`** (Fig 2) · **`schematic_extended.png`** (Fig 3, *new* — the six-mechanism control layer) · **`impact_graph.png`** (Fig 4) · **`eval_precision_recall.png`** (Fig 5) · **`schematic_router_loop.png`** (Fig 6, *new*) · **`router_eval.png`** (Fig 7, *new*). + +- **`schematic_loop.png`** (Fig 1) · **`schematic_system.png`** (Fig 2) · **`schematic_extended.png`** (Fig 3, _new_ — the six-mechanism control layer) · **`impact_graph.png`** (Fig 4) · **`eval_precision_recall.png`** (Fig 5) · **`schematic_router_loop.png`** (Fig 6, _new_) · **`router_eval.png`** (Fig 7, _new_). ### 6. Supporting artifacts (from v1) + - **`gap_map.json`/`.md`**, **`references.json`** (32 sources), **`quran_lens.json`/`.md`** (14-row concept→faculty→design-principle mapping). --- @@ -43,43 +50,46 @@ ## The honest headline results ### Prototype I — Impact Oracle (against mutation-derived ground truth, 5 real edits) -| Method | Precision | Recall | F1 | -|---|---|---|---| -| **Graph Oracle** (ours) | 0.63 | **1.00** | 0.75 | -| Grep baseline (what agents do today) | 0.73 | 0.94 | **0.79** | -| Edited-file-only | 1.00 | 0.53 | 0.65 | + +| Method | Precision | Recall | F1 | +| ------------------------------------ | --------- | -------- | -------- | +| **Graph Oracle** (ours) | 0.63 | **1.00** | 0.75 | +| Grep baseline (what agents do today) | 0.73 | 0.94 | **0.79** | +| Edited-file-only | 1.00 | 0.53 | 0.65 | The oracle does **not** dominate F1 — grep edges it at the default threshold, and we say so. What the oracle uniquely provides is **guaranteed recall**: for "show me everything my edit could break," a silent miss costs far more than an extra file to check, and only the structural oracle drives false negatives to zero (precision tunable, best F1 = 0.79 at threshold 0.4). ### Prototype II — Router + Gate (live, on real models: haiku / sonnet / opus) -| Metric | Result | -|---|---| -| Gate accuracy (should-ask) | 30/30 · precision 1.00 · recall 1.00 | -| Routing accuracy (well-specified tasks) | 21/21 exact tier | -| **Real cost saved vs always-premium** | **62.1%** (same measured tokens) | -| Execution-verified sub-experiment | 3/3 routed-down outputs passed real test cases | -**Honest caveat (both prototypes):** these are **demonstrations, not benchmarks**. The router's 30-task set is hand-labeled and the rubric thresholds were tuned against it, so perfect separation shows the rubric *can* distinguish these cases — not field accuracy. The oracle's evaluation is 5 mutations + 2 stdlib scale checks. We apply the "retired SWE-bench Verified" caution (§4, confirmed) to our own numbers. +| Metric | Result | +| --------------------------------------- | ---------------------------------------------- | +| Gate accuracy (should-ask) | 30/30 · precision 1.00 · recall 1.00 | +| Routing accuracy (well-specified tasks) | 21/21 exact tier | +| **Real cost saved vs always-premium** | **62.1%** (same measured tokens) | +| Execution-verified sub-experiment | 3/3 routed-down outputs passed real test cases | + +**Honest caveat (both prototypes):** these are **demonstrations, not benchmarks**. The router's 30-task set is hand-labeled and the rubric thresholds were tuned against it, so perfect separation shows the rubric _can_ distinguish these cases — not field accuracy. The oracle's evaluation is 5 mutations + 2 stdlib scale checks. We apply the "retired SWE-bench Verified" caution (§4, confirmed) to our own numbers. ## What the evidence re-grounding caught -The independent re-grounding **changed our claims** — three widely-repeated numbers did not survive and are *not* used as fact in this paper: +The independent re-grounding **changed our claims** — three widely-repeated numbers did not survive and are _not_ used as fact in this paper: + - **"2.74× more vulnerabilities"** is not traceable to Veracode's own report (only their 45% OWASP figure is); likely conflated with a separate study. - **"17% lower comprehension / 400K sessions"** merges two different studies — the session study contains no comprehension finding. - **GitClear 4× vs 8×** internal inconsistency and **JetBrains 77%** could not be located in primary form. -That a re-grounding pass corrected the paper is the point, not an embarrassment: it is the same discipline the architecture makes structural — *a stored fact is provisional until an external check confirms it.* +That a re-grounding pass corrected the paper is the point, not an embarrassment: it is the same discipline the architecture makes structural — _a stored fact is provisional until an external check confirms it._ ## The build-opportunity map (what to build, what to skip) -**Already solved — do not rebuild:** M1 routing (model tiering + gateways like LiteLLM/OpenRouter) and M3 decomposition (subagents, Agent-Teams). The router prototype's honest contribution is only the *transparency layer*, and we say so. +**Already solved — do not rebuild:** M1 routing (model tiering + gateways like LiteLLM/OpenRouter) and M3 decomposition (subagents, Agent-Teams). The router prototype's honest contribution is only the _transparency layer_, and we say so. **The genuine whitespace, ranked:** (1) **assumption/uncertainty gate** — the project's named root failure and the field's named gap; nothing supplies calibrated known-unknowns. (2) **validity-anchored memory** — backends store notes, none tracks invalidation-by-correction. (3) **mandatory pre-action impact gate** — indexers retrieve, none is a deterministic blast-radius check. (4) outcome-validated learning. (5) doom-loop / root-cause correction. (6) scope-minimality. This paper prototypes #1 and #3 — the two where a single session can produce checkable ground truth. ## What is genuinely new vs. reinvented -Most components are borrowed (external memory, fast/slow learning, code graphs, model tiering — all exist). The contribution is **the composition and the framing**: the closed-loop shape; **validity-anchored memory** (prune by whether a past prediction was confirmed by an *external* oracle, not by the model's own judgment); wiring exact impact analysis into a **mandatory pre-action gate**; a **transparent** router/gate that explains every decision; and deriving *which* safeguards are non-negotiable from a coherent epistemology. That turns scattered literatures and named-but-unsolved gaps into one buildable architecture aimed squarely at coding agents. +Most components are borrowed (external memory, fast/slow learning, code graphs, model tiering — all exist). The contribution is **the composition and the framing**: the closed-loop shape; **validity-anchored memory** (prune by whether a past prediction was confirmed by an _external_ oracle, not by the model's own judgment); wiring exact impact analysis into a **mandatory pre-action gate**; a **transparent** router/gate that explains every decision; and deriving _which_ safeguards are non-negotiable from a coherent epistemology. That turns scattered literatures and named-but-unsolved gaps into one buildable architecture aimed squarely at coding agents. ## Scope & limitations (stated honestly) -Two faculties/mechanisms are prototyped, not eleven. The impact oracle's static analysis is single-language (Python) and conservative on dynamic dispatch. The router/gate rubrics are keyword heuristics tuned on a small hand-labeled set. Memory validity, outcome learning, and doom-loop diagnosis remain *specified but unbuilt* — the harder research gaps, marked as such rather than gestured at with a demo. The lens is framing: reject it and you lose the organizing vocabulary but none of the technical content. +Two faculties/mechanisms are prototyped, not eleven. The impact oracle's static analysis is single-language (Python) and conservative on dynamic dispatch. The router/gate rubrics are keyword heuristics tuned on a small hand-labeled set. Memory validity, outcome learning, and doom-loop diagnosis remain _specified but unbuilt_ — the harder research gaps, marked as such rather than gestured at with a demo. The lens is framing: reject it and you lose the organizing vocabulary but none of the technical content. diff --git a/docs/cognitive-substrate/impact_oracle_src.zip b/docs/cognitive-substrate/impact_oracle_src.zip deleted file mode 100644 index 3354953fe1ebbbe21b7d939b216c8b289072948c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26890 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≪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

J>AnFOCA$QSCRN&aGEHpnvURq`zk5q7`ZrB%) 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 diff --git a/global/tools/cognitive-substrate/SKILL.md b/global/tools/cognitive-substrate/SKILL.md index 0fcfb96..5575f99 100644 --- a/global/tools/cognitive-substrate/SKILL.md +++ b/global/tools/cognitive-substrate/SKILL.md @@ -11,21 +11,61 @@ description: >- # Cognitive Substrate -Before acting on non-trivial or mutating code tasks, run: +Wrap the frozen model in a pre-action check: gate assumptions, route model effort, predict +blast radius, decompose scope, surface past lessons, and plan verification — **before** editing. + +## When to run it + +Trigger on any ambiguous, expensive, multi-file, or mutating task: edit/refactor/fix/design +production code, integrate a feature, choose model effort, inspect blast radius, or when a +request is vague. Skip it for one-line, well-specified fixes. + +> In Claude Code this fires automatically on every prompt (UserPromptSubmit hook). In other +> agents, run it yourself with the CLI, or call the MCP tool `substrate_check`. + +## Run ```bash -forge substrate "" --json +forge substrate "" --json # full contract; use the fields below +``` + +## Act on the result + +1. **`okToProceed: false`** → ask the returned `assumption.questions` before editing. Do not guess. +2. **`route.tier`** → start at that model tier (cheapest capable); escalate only after an external verifier fails. +3. **`impact.impactedFiles`** → read these before editing a named symbol/file (the blast radius). +4. **`scope.clusters`** → split independent groups into separate sessions; note coupled files you didn't name. +5. **`memory.advisory`** → context, not law; tests and human corrections override it. +6. **`verification.checklist`** → run it and show output before claiming done. + +## Worked example + +```console +$ forge substrate "make the auth better" + proceed: ASK FIRST + assumption: high risk · completeness 0.23 + clarify: + - What exactly should this produce, and how will we know it is correct? +``` + +→ Under-specified. Ask the clarify question instead of editing. + +```console +$ forge substrate "Change verifyToken in src/auth.js to require length > 20; update tests" + proceed: yes + impact: 3 file(s) predicted + - src/auth.js + - src/login.js (importer you did not name) + - src/session.js (importer you did not name) ``` -Use the result this way: +→ Cleared to proceed, but review the two coupled importers first. -1. If `okToProceed` is false, ask the returned `assumption.questions` before editing. -2. Use `route.model` as the cheapest capable tier recommendation; do not escalate without a verifier failure. -3. Read `impact.impactedFiles` before editing a named symbol/file. -4. Use `scope.clusters` to split independent work into separate sessions. -5. Treat `memory.advisory` as context, not law; tests and human corrections override it. -6. Run the `verification.checklist` before claiming completion. +## Single-mechanism commands -For details, read `references/capability-map.md` only when you need to explain how the paper's faculties map to Forge commands. +`forge preflight ""` (assumptions) · `forge route ""` (model tier) · +`forge impact ` (blast radius) · `forge scope ` (decomposition). +MCP equivalents: `assumption_gate`, `route_task`, `predict_impact`, `scope_files`. -The full paper bundle is in `docs/cognitive-substrate/` when the repository is available: PDF, HTML, evidence map, ecosystem map, and original prototype packages. +For the full guide (how it works, extending it, the honesty boundary) and the white paper, +see `docs/cognitive-substrate/README.md`. `references/capability-map.md` maps faculties to commands. diff --git a/package.json b/package.json index 28134a8..21b19cf 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "source", "global", "templates", - "docs/cognitive-substrate", "plugin", "hooks", ".claude-plugin", diff --git a/research/python-prototypes/impact_oracle/impact_oracle/world_model.py b/research/python-prototypes/impact_oracle/impact_oracle/world_model.py index 936d37e..cfd7bdc 100644 --- a/research/python-prototypes/impact_oracle/impact_oracle/world_model.py +++ b/research/python-prototypes/impact_oracle/impact_oracle/world_model.py @@ -2,24 +2,22 @@ Builds and maintains a directed graph whose nodes are symbols and whose edges are structural dependencies. The graph is persisted to disk as -JSON (node-link format) plus a NetworkX pickle. On subsequent runs only -files whose content hash has changed are re-parsed (incremental update). +JSON (node-link format) only — never pickle, which would execute arbitrary +code on load. On subsequent runs only files whose content hash has changed +are re-parsed (incremental update). """ from __future__ import annotations import json import os -import pickle from pathlib import Path from typing import Any import networkx as nx from impact_oracle.parser import ( - DependencyEdge, ParseResult, - SymbolNode, content_hash, discover_python_files, parse_file, @@ -43,7 +41,17 @@ def __init__(self, root: str, cache_dir: str | None = None): Directory to persist the graph. Defaults to ``/.impact_oracle_cache``. """ self.root = os.path.abspath(root) - self.cache_dir = cache_dir or os.path.join(self.root, ".impact_oracle_cache") + # Defense-in-depth: a caller-supplied cache_dir must resolve inside root, so a + # hostile MCP argument cannot point persistence I/O at an arbitrary location. + if cache_dir is None: + self.cache_dir = os.path.join(self.root, ".impact_oracle_cache") + else: + resolved = os.path.abspath(cache_dir) + if os.path.commonpath([resolved, self.root]) != self.root: + raise ValueError( + f"cache_dir {cache_dir!r} must be inside root {self.root!r}" + ) + self.cache_dir = resolved self.graph: nx.DiGraph = nx.DiGraph() # content-hash cache: filepath (relative) -> hash self._file_hashes: dict[str, str] = {} @@ -174,29 +182,26 @@ def summary(self) -> dict[str, Any]: def _save_cache(self): os.makedirs(self.cache_dir, exist_ok=True) - # 1. JSON node-link (portable) + # JSON node-link is the ONLY persistence format. Pickle was removed: loading a + # pickle executes arbitrary code, and cache_dir can be caller-supplied, which made + # graph.pkl an insecure-deserialization (RCE) vector. JSON is data-only and safe. data = nx.node_link_data(self.graph) with open(os.path.join(self.cache_dir, "graph.json"), "w") as f: json.dump(data, f, indent=1) - # 2. Pickle (fast reload) - with open(os.path.join(self.cache_dir, "graph.pkl"), "wb") as f: - pickle.dump(self.graph, f, protocol=pickle.HIGHEST_PROTOCOL) - # 3. File hashes with open(os.path.join(self.cache_dir, "file_hashes.json"), "w") as f: json.dump(self._file_hashes, f) - # 4. File-node mapping with open(os.path.join(self.cache_dir, "file_nodes.json"), "w") as f: json.dump(self._file_nodes, f) def _load_cache(self): - pkl_path = os.path.join(self.cache_dir, "graph.pkl") + json_path = os.path.join(self.cache_dir, "graph.json") hash_path = os.path.join(self.cache_dir, "file_hashes.json") fn_path = os.path.join(self.cache_dir, "file_nodes.json") - if os.path.exists(pkl_path): + if os.path.exists(json_path): try: - with open(pkl_path, "rb") as f: - self.graph = pickle.load(f) + with open(json_path) as f: + self.graph = nx.node_link_graph(json.load(f)) except Exception: self.graph = nx.DiGraph() @@ -221,8 +226,11 @@ def _apply_parse_result(self, result: ParseResult): for edge in result.edges: self.graph.add_edge( - edge.source, edge.target, - kind=edge.kind, confidence=edge.confidence, lineno=edge.lineno, + edge.source, + edge.target, + kind=edge.kind, + confidence=edge.confidence, + lineno=edge.lineno, ) self._file_nodes[result.file] = node_names @@ -251,15 +259,30 @@ def _resolve_cross_module_edges(self): continue # Try parent.v as a resolution # e.g. target 'utils.helper_func' might live as 'demo_package.utils.helper_func' - candidates = [n for n in existing if n.endswith(f".{v}") or n.endswith(f".{v.split('.')[-1]}")] + candidates = [ + n + for n in existing + if n.endswith(f".{v}") or n.endswith(f".{v.split('.')[-1]}") + ] if len(candidates) == 1: edges_to_remove.append((u, v)) - edges_to_add.append((u, candidates[0], {**data, "confidence": data.get("confidence", 1.0) * 0.9})) + edges_to_add.append( + ( + u, + candidates[0], + {**data, "confidence": data.get("confidence", 1.0) * 0.9}, + ) + ) elif len(candidates) > 1: # ambiguous: pick the best match (longest common suffix) - best = max(candidates, key=lambda c: len(os.path.commonprefix([c[::-1], v[::-1]]))) + best = max( + candidates, + key=lambda c: len(os.path.commonprefix([c[::-1], v[::-1]])), + ) edges_to_remove.append((u, v)) - edges_to_add.append((u, best, {**data, "confidence": data.get("confidence", 1.0) * 0.7})) + edges_to_add.append( + (u, best, {**data, "confidence": data.get("confidence", 1.0) * 0.7}) + ) for u, v in edges_to_remove: if self.graph.has_edge(u, v): diff --git a/skills/cognitive-substrate/SKILL.md b/skills/cognitive-substrate/SKILL.md index 0fcfb96..5575f99 100644 --- a/skills/cognitive-substrate/SKILL.md +++ b/skills/cognitive-substrate/SKILL.md @@ -11,21 +11,61 @@ description: >- # Cognitive Substrate -Before acting on non-trivial or mutating code tasks, run: +Wrap the frozen model in a pre-action check: gate assumptions, route model effort, predict +blast radius, decompose scope, surface past lessons, and plan verification — **before** editing. + +## When to run it + +Trigger on any ambiguous, expensive, multi-file, or mutating task: edit/refactor/fix/design +production code, integrate a feature, choose model effort, inspect blast radius, or when a +request is vague. Skip it for one-line, well-specified fixes. + +> In Claude Code this fires automatically on every prompt (UserPromptSubmit hook). In other +> agents, run it yourself with the CLI, or call the MCP tool `substrate_check`. + +## Run ```bash -forge substrate "" --json +forge substrate "" --json # full contract; use the fields below +``` + +## Act on the result + +1. **`okToProceed: false`** → ask the returned `assumption.questions` before editing. Do not guess. +2. **`route.tier`** → start at that model tier (cheapest capable); escalate only after an external verifier fails. +3. **`impact.impactedFiles`** → read these before editing a named symbol/file (the blast radius). +4. **`scope.clusters`** → split independent groups into separate sessions; note coupled files you didn't name. +5. **`memory.advisory`** → context, not law; tests and human corrections override it. +6. **`verification.checklist`** → run it and show output before claiming done. + +## Worked example + +```console +$ forge substrate "make the auth better" + proceed: ASK FIRST + assumption: high risk · completeness 0.23 + clarify: + - What exactly should this produce, and how will we know it is correct? +``` + +→ Under-specified. Ask the clarify question instead of editing. + +```console +$ forge substrate "Change verifyToken in src/auth.js to require length > 20; update tests" + proceed: yes + impact: 3 file(s) predicted + - src/auth.js + - src/login.js (importer you did not name) + - src/session.js (importer you did not name) ``` -Use the result this way: +→ Cleared to proceed, but review the two coupled importers first. -1. If `okToProceed` is false, ask the returned `assumption.questions` before editing. -2. Use `route.model` as the cheapest capable tier recommendation; do not escalate without a verifier failure. -3. Read `impact.impactedFiles` before editing a named symbol/file. -4. Use `scope.clusters` to split independent work into separate sessions. -5. Treat `memory.advisory` as context, not law; tests and human corrections override it. -6. Run the `verification.checklist` before claiming completion. +## Single-mechanism commands -For details, read `references/capability-map.md` only when you need to explain how the paper's faculties map to Forge commands. +`forge preflight ""` (assumptions) · `forge route ""` (model tier) · +`forge impact ` (blast radius) · `forge scope ` (decomposition). +MCP equivalents: `assumption_gate`, `route_task`, `predict_impact`, `scope_files`. -The full paper bundle is in `docs/cognitive-substrate/` when the repository is available: PDF, HTML, evidence map, ecosystem map, and original prototype packages. +For the full guide (how it works, extending it, the honesty boundary) and the white paper, +see `docs/cognitive-substrate/README.md`. `references/capability-map.md` maps faculties to commands. diff --git a/source/rules.json b/source/rules.json index 35a9e65..49ea743 100644 --- a/source/rules.json +++ b/source/rules.json @@ -11,6 +11,18 @@ "Fix root causes, not symptoms. Never suppress an error to make a check pass." ] }, + { + "id": "substrate", + "title": "Cognitive substrate (run before acting)", + "rules": [ + "Before ambiguous, expensive, multi-file, or mutating work, run `forge substrate \"\" --json` (or call the MCP tool `substrate_check`). In Claude Code this fires automatically on each prompt; in other tools invoke it yourself.", + "If it returns `okToProceed:false`, ask the returned `assumption.questions` before editing — do not guess an under-specified task.", + "Start at the recommended `route.tier` (cheapest capable model); escalate only after an external verifier fails, never preemptively.", + "Read `impact.impactedFiles` (predicted blast radius) before editing a named symbol/file, and split `scope.clusters` of independent work into separate sessions.", + "Treat `memory.advisory` and minimality warnings as advisory; tests, graph membership, and human corrections always override the model.", + "Run `verification.checklist` and show the output before claiming done." + ] + }, { "id": "reuse", "title": "Reuse & dependencies", diff --git a/src/cortex_hook_main.js b/src/cortex_hook_main.js index da08ae8..6e0542c 100644 --- a/src/cortex_hook_main.js +++ b/src/cortex_hook_main.js @@ -5,9 +5,15 @@ // // modes: capture (PostToolUse Edit|Write|Bash) — log a signal event // prompt (UserPromptSubmit) — log a user-utterance event +// preflight (UserPromptSubmit) — inject the substrate pre-action advisory +// pre-edit (PreToolUse Edit|Write) — advise on lessons/risk before an edit // stop (Stop) — distill the session into lessons // session-start (SessionStart) — inject learned lessons as context -import { applyDistillation, lessonsForContext, startupBlock } from "./cortex.js"; +import { + applyDistillation, + lessonsForContext, + startupBlock, +} from "./cortex.js"; import { appendSessionEvent, classifyEvent, @@ -16,7 +22,7 @@ import { readSession, } from "./cortex_hook.js"; import { load } from "./lessons_store.js"; -import { clarifyBlock, preflightRepo } from "./preflight.js"; +import { substrateCheck, substrateContext } from "./substrate.js"; // Opt-in: distill newly-created lessons into real prose via a cheap model call. Off by // default (deterministic template is used); fail-safe (any error → keep the template). @@ -67,13 +73,21 @@ async function main() { const block = startupBlock(root, today); if (block) emit("SessionStart", block); } else if (mode === "pre-edit") { - const advice = await preEditAdvisory(root, hook.tool_input?.file_path, today); + const advice = await preEditAdvisory( + root, + hook.tool_input?.file_path, + today, + ); if (advice) emit("PreToolUse", advice); } else if (mode === "preflight") { - // Assumption detector: does the task name things the repo doesn't define? + // Ambient cognitive substrate: assumption gate + (when an atlas is already cached) + // model routing, blast-radius, memory, and minimality — surfaced before the agent acts. + // allowBuild:false keeps it cheap and never writes .forge/ from a hook; advisory only. if (typeof hook.prompt === "string" && hook.prompt.trim()) { - const block = clarifyBlock(preflightRepo(root, hook.prompt, { allowBuild: false })); - if (block) emit("UserPromptSubmit", block); + const advisory = substrateContext( + substrateCheck(root, hook.prompt, { allowBuild: false }), + ); + if (advisory) emit("UserPromptSubmit", advisory); } } } diff --git a/src/substrate.js b/src/substrate.js index 75c6481..cafc5b3 100644 --- a/src/substrate.js +++ b/src/substrate.js @@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url"; import { build as buildAtlas, impact as impactGraph, load as loadAtlas } from "./atlas.js"; import { matchingLessons } from "./cortex.js"; import { load as loadLessons } from "./lessons_store.js"; -import { assessTask, clarifyBlock, preflightRepo, referencedEntities } from "./preflight.js"; +import { clarifyBlock, preflightRepo, referencedEntities } from "./preflight.js"; import { routeTask } from "./route.js"; import { decompose } from "./scope.js"; @@ -65,15 +65,22 @@ export function predictImpact(root, target, { threshold = 0.1 } = {}) { return impactGraph(atlas, target, { threshold }); } -export function substrateCheck(root, task, { threshold = 0.1, askThreshold = 0.6 } = {}) { +export function substrateCheck( + root, + task, + { threshold = 0.1, askThreshold = 0.6, allowBuild = true } = {}, +) { const text = String(task || ""); const entities = referencedEntities(text); - const preflight = preflightRepo(root, text, { askThreshold }); - const assumption = assessTask(text, { askThreshold }); + const preflight = preflightRepo(root, text, { askThreshold, allowBuild }); const route = routeTask(root, text); - const atlas = loadAtlas(root) || buildAtlas({ root }); + // allowBuild:false (ambient hooks) uses the atlas only if one is already cached — never + // builds or writes .forge/atlas.json from a hook. Impact is then best-effort. + const atlas = loadAtlas(root) || (allowBuild ? buildAtlas({ root }) : null); const impactTargets = [...new Set([...entities.symbols, ...entities.files])].slice(0, 8); - const impacts = impactTargets.map((target) => impactGraph(atlas, target, { threshold })); + const impacts = atlas + ? impactTargets.map((target) => impactGraph(atlas, target, { threshold })) + : []; const impactedFiles = [...new Set(impacts.flatMap((r) => r.impactedFiles || []))].sort(); const scopedFiles = [...new Set([...entities.files, ...impactedFiles])]; const scope = scopedFiles.length @@ -84,9 +91,9 @@ export function substrateCheck(root, task, { threshold = 0.1, askThreshold = 0.6 symbols: entities.symbols, }); const result = { - okToProceed: !preflight.assumption.shouldAsk && !assumption.shouldAsk, + okToProceed: !preflight.assumption.shouldAsk, task: text, - assumption: preflight.assumption.shouldAsk ? preflight.assumption : assumption, + assumption: preflight.assumption, clarify: clarifyBlock(preflight), route, entities, @@ -94,9 +101,11 @@ export function substrateCheck(root, task, { threshold = 0.1, askThreshold = 0.6 scope, memory: { matchingLessons: lessons.length, - advisory: lessons - .slice(0, 5) - .map((lesson) => ({ id: lesson.id, status: lesson.status, scope: lesson.scope })), + advisory: lessons.slice(0, 5).map((lesson) => ({ + id: lesson.id, + status: lesson.status, + scope: lesson.scope, + })), }, minimality: { warnings: minimalityWarnings(text, route, preflight) }, verification: { checklist: verificationChecklist(root) }, @@ -147,3 +156,37 @@ export function renderSubstrate(result) { for (const c of result.verification.checklist) lines.push(` - ${c}`); return lines.join("\n"); } + +// Compact advisory for AMBIENT injection (Claude Code UserPromptSubmit additionalContext). +// Returns "" unless there is something worth surfacing — never nags on a well-specified, +// low-impact task. Gated on: must-ask assumptions, a premium model recommendation, +// predicted blast radius, or a minimality warning. +export function substrateContext(result) { + const worthSaying = + result.assumption.shouldAsk || + result.impact.impactedFiles.length > 0 || + result.minimality.warnings.length > 0 || + ["opus", "fable"].includes(result.route.key); + if (!worthSaying) return ""; + const lines = ["Forge substrate — pre-action advisory (advisory, never blocks):"]; + if (result.assumption.shouldAsk) { + lines.push( + `- Under-specified (${result.assumption.risk} risk). Ask before editing:`, + ...result.assumption.questions.map((q) => ` • ${q}`), + ); + } + lines.push( + `- Suggested model: ${result.route.model.name} (${result.route.tier}); escalate only on a verifier failure.`, + ); + if (result.impact.impactedFiles.length) { + const files = result.impact.impactedFiles; + lines.push( + `- Predicted blast radius (${files.length}): ${files.slice(0, 8).join(", ")}${files.length > 8 ? " …" : ""}. Review these before editing.`, + ); + } + for (const w of result.minimality.warnings) lines.push(`- Minimality: ${w}`); + if (result.memory.matchingLessons) + lines.push(`- ${result.memory.matchingLessons} past lesson(s) match this area (advisory).`); + lines.push(`- Verify with: ${result.verification.checklist.join(" · ")}`); + return lines.join("\n"); +} From e400c3613e4bb62b0bca76ac2b2f7150dfae7851 Mon Sep 17 00:00:00 2001 From: Juber Shaikh <40266375+CodeWithJuber@users.noreply.github.com> Date: Mon, 6 Jul 2026 00:17:02 +0400 Subject: [PATCH 2/2] fix(ci): pin Biome to 2.5.2 and match schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI's floating @biomejs/biome ^2.0.0 resolved to 2.5.2 while biome.json's schema stayed 2.0.0 — the mismatch hard-failed `biome check`. Pin the exact version (matches the repo's pinning ethos) and bump the schema URL to 2.5.2; reformat. --- biome.json | 2 +- package-lock.json | 2 +- package.json | 4 ++-- src/cortex_hook_main.js | 16 +++------------- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/biome.json b/biome.json index abe63b0..19e1794 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json", + "$schema": "https://biomejs.dev/schemas/2.5.2/schema.json", "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, "files": { "ignoreUnknown": true, diff --git a/package-lock.json b/package-lock.json index df412c4..b4d6202 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "forge": "src/cli.js" }, "devDependencies": { - "@biomejs/biome": "^2.0.0", + "@biomejs/biome": "2.5.2", "@types/node": "^26", "typescript": "^6" }, diff --git a/package.json b/package.json index 21b19cf..5405bd3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@codewithjuber/forgekit", "version": "0.3.1", - "description": "One config, every AI coding tool — a cross-tool configuration layer for agentic coding assistants (Claude Code, Codex, Cursor, Gemini, Aider, and more).", + "description": "One config, every AI coding tool \u2014 a cross-tool configuration layer for agentic coding assistants (Claude Code, Codex, Cursor, Gemini, Aider, and more).", "type": "module", "bin": { "forge": "src/cli.js" @@ -68,7 +68,7 @@ "zero-dependencies" ], "devDependencies": { - "@biomejs/biome": "^2.0.0", + "@biomejs/biome": "2.5.2", "@types/node": "^26", "typescript": "^6" } diff --git a/src/cortex_hook_main.js b/src/cortex_hook_main.js index 6e0542c..721c3ef 100644 --- a/src/cortex_hook_main.js +++ b/src/cortex_hook_main.js @@ -9,11 +9,7 @@ // pre-edit (PreToolUse Edit|Write) — advise on lessons/risk before an edit // stop (Stop) — distill the session into lessons // session-start (SessionStart) — inject learned lessons as context -import { - applyDistillation, - lessonsForContext, - startupBlock, -} from "./cortex.js"; +import { applyDistillation, lessonsForContext, startupBlock } from "./cortex.js"; import { appendSessionEvent, classifyEvent, @@ -73,20 +69,14 @@ async function main() { const block = startupBlock(root, today); if (block) emit("SessionStart", block); } else if (mode === "pre-edit") { - const advice = await preEditAdvisory( - root, - hook.tool_input?.file_path, - today, - ); + const advice = await preEditAdvisory(root, hook.tool_input?.file_path, today); if (advice) emit("PreToolUse", advice); } else if (mode === "preflight") { // Ambient cognitive substrate: assumption gate + (when an atlas is already cached) // model routing, blast-radius, memory, and minimality — surfaced before the agent acts. // allowBuild:false keeps it cheap and never writes .forge/ from a hook; advisory only. if (typeof hook.prompt === "string" && hook.prompt.trim()) { - const advisory = substrateContext( - substrateCheck(root, hook.prompt, { allowBuild: false }), - ); + const advisory = substrateContext(substrateCheck(root, hook.prompt, { allowBuild: false })); if (advisory) emit("UserPromptSubmit", advisory); } }