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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ The four layers, brand-named and emitted cross-tool:
drift from. Prose rules in CLAUDE.md get acknowledged and then forgotten after
compaction; a guard does not. Every enforceable invariant belongs here.
- **mcp** — the protocol layer. Forge ships one stdio server (`src/cortex_mcp.js`)
exposing 20 MCP tools: the substrate checks (`substrate_check` / `predict_impact` /
exposing 21 MCP tools: the substrate checks (`substrate_check` / `predict_impact` /
`assumption_gate` / `rank_code` / …), memory reads AND writes (`forge_remember`,
ledger ratify/retract), and ops/health — the full table is in docs/GUIDE.md.

Expand Down Expand Up @@ -521,16 +521,16 @@ from the tree it describes.
```mermaid
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#201a15','primaryTextColor':'#f2ede7','primaryBorderColor':'#372c22','lineColor':'#f26430','secondaryColor':'#272019','tertiaryColor':'#171310','edgeLabelBackground':'#201a15','clusterBkg':'#171310','clusterBorder':'#4a3b2e','fontFamily':'ui-sans-serif, system-ui, sans-serif','fontSize':'14px'},'flowchart':{'curve':'basis','padding':10,'nodeSpacing':36,'rankSpacing':44}}}%%
flowchart LR
test["test<br/>98 files"]
src["src<br/>93 files"]
test["test<br/>100 files"]
src["src<br/>94 files"]
landing["landing<br/>60 files"]
research["research<br/>35 files"]
bench["bench<br/>2 files"]
global["global<br/>2 files"]
scripts["scripts<br/>2 files"]
docs["docs<br/>1 file"]
examples["examples<br/>1 file"]
test -- 191 --> src
test -- 195 --> src
bench -- 7 --> src
examples -- 4 --> src
test -- 2 --> scripts
Expand Down
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,45 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- **`forge collide` — the parallel-session conflict radar.** The everyday failure of
the agent-fleet era: two sessions silently edit the same or import-coupled files and
the conflict surfaces at merge time. Every session already mints a ledger summary of
the files it touched, and those claims team-merge over plain git — so "who else was
just in here?" is a pure read: no server, no presence protocol, no new storage.
`risk = 1 − ∏(1 − recᵢ × strengthᵢ)` over recent foreign sessions (7-day recency
half-life — a collision is about now; direct hits count full, 1-hop import
neighbors half). Advisory and fail-open, with hook-minted absolute paths
relativized like the rank join. Exposed to every MCP-capable agent as
`collide_check` (21 MCP tools — counts and tables regenerated by
`forge docs render`).

### Fixed

- **The rank hazard join now matches production claims.** Hook-minted lessons and
session summaries store raw tool-input paths (absolute), while the atlas speaks
repo-relative POSIX — so `forge rank`'s history overlay never matched a real claim
and hazard silently degenerated to bare centrality (found by adversarial review,
reproduced against the live mint pipeline). `history()` now relativizes claim paths
against the repo root; a test pins the production path shape.
- **`beliefDiff` tombstone edge cases.** A claim minted _and_ retracted inside the
diff window was reported as "appeared" with a live confidence — a retracted claim
presented as a current belief; it now lands in `retired` (`from:null, to:null`).
Claims already tombstoned before the window no longer surface as strengthened or
weakened through pure decay — dead beliefs don't move.
- **`forge rank` determinism and hardening.** All orderings now use locale-independent
codepoint comparison (`localeCompare` consults ICU tables that differ across
machines, contradicting the module's own cross-machine guarantee); `centrality()`
counts a duplicated atlas node id once, as PageRank already did; a corrupt
`.forge/atlas.json` degrades to the `built:false` hint instead of crashing the CLI
and hanging the `rank_code` MCP call; a negative `--top` clamps instead of slicing
in from the end of the list.
- **Temporal CLI guards.** `forge ledger diff` refuses a `<since>` after `<until>`
(previously printed silently inverted classes), and `ledger at`/`diff` reject
impossible calendar dates (`2026-02-31`) instead of letting `Date.parse` roll them
into a day nobody asked about.

## [0.29.0] - 2026-08-07

### Added
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ git pull && forge ledger merge <path-to-their-ledger>

On Claude Code the substrate then runs on **every prompt automatically** via a
`UserPromptSubmit` hook — advisory only, silent on clean tasks. Every other tool gets a
native config rule plus **20 MCP tools** it can call itself — pre-action checks
native config rule plus **21 MCP tools** it can call itself — pre-action checks
(`substrate_check`, `predict_impact`, `assumption_gate`, `route_task`, `scope_files`),
memory reads and writes, and ops/health — the full list with schemas is in
[`docs/GUIDE.md`](docs/GUIDE.md#mcp-tools).
Expand Down Expand Up @@ -257,6 +257,7 @@ that never clobbers your existing settings (skip it with `install.sh --no-settin
| | `forge deja` | anti-repetition — have you done this task before? ranks prior solved/verified sessions |
| | `forge reuse` | proof-carrying code cache — query <spec> / mint <spec> --file <path> / stats |
| | `forge rank` | load-bearing code — PageRank centrality × past-incident history, circular-dependency clusters, chokepoint files |
| | `forge collide` | parallel-session conflict radar — who else recently touched the files (or their import neighbors) you are editing |
<!-- forge:render:commands-table:end -->

**→ Every command with a worked example and real output:
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ confidence only from independent oracles, and merges across teammates conflict-f
exposing the complexity tiers as model aliases; point `ANTHROPIC_BASE_URL` at the
proxy and every model call routes through it.
- **MCP server** — the cortex MCP server (`src/cortex_mcp.js`) exposes read-path
tools for ledger, brain, atlas, recall, cost, substrate, and dashboard (20 MCP tools
tools for ledger, brain, atlas, recall, cost, substrate, and dashboard (21 MCP tools
as of 0.8.x, including the write tools added in 0.8.0).
- **Cost dashboard** — `forge dash` serves a local HTML dashboard showing model spend,
event timeline, and ledger health from `.forge/` data.
Expand Down
45 changes: 36 additions & 9 deletions docs/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ recipes, and how to extend each piece. If you just want to get going, the
Every command is real and wired. Grouped by what it does:

<!-- forge:render:command-groups:begin (generated by `forge docs render` — do not edit) -->
| Group | Commands |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Core** | `forge init` · `forge sync` · `forge doctor` · `forge tools` · `forge catalog` · `forge docs` · `forge update` · `forge config` |
| **Substrate** | `forge substrate` · `forge preflight` · `forge impact` · `forge scope` · `forge context` · `forge route` · `forge verify` · `forge precommit` |
| **Memory** | `forge cortex` · `forge recall` · `forge remember` · `forge brain` · `forge ledger` · `forge handoff` · `forge decide` · `forge know` |
| **Quality** | `forge scan` · `forge spec` · `forge harden` · `forge radar` |
| **Config** | `forge brand` · `forge atlas` · `forge stack` · `forge integrations` · `forge cost` |
| **Labs (experimental)** | `forge taste` · `forge uicheck` · `forge imagine` · `forge lean` · `forge anchor` · `forge diagnose` · `forge dash` · `forge report` · `forge deja` · `forge reuse` · `forge rank` |
| Group | Commands |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Core** | `forge init` · `forge sync` · `forge doctor` · `forge tools` · `forge catalog` · `forge docs` · `forge update` · `forge config` |
| **Substrate** | `forge substrate` · `forge preflight` · `forge impact` · `forge scope` · `forge context` · `forge route` · `forge verify` · `forge precommit` |
| **Memory** | `forge cortex` · `forge recall` · `forge remember` · `forge brain` · `forge ledger` · `forge handoff` · `forge decide` · `forge know` |
| **Quality** | `forge scan` · `forge spec` · `forge harden` · `forge radar` |
| **Config** | `forge brand` · `forge atlas` · `forge stack` · `forge integrations` · `forge cost` |
| **Labs (experimental)** | `forge taste` · `forge uicheck` · `forge imagine` · `forge lean` · `forge anchor` · `forge diagnose` · `forge dash` · `forge report` · `forge deja` · `forge reuse` · `forge rank` · `forge collide` |
<!-- forge:render:command-groups:end -->

Storage in one line: the code graph is `.forge/atlas.json` (plain JSON, not SQLite); the
Expand Down Expand Up @@ -283,6 +283,32 @@ Forge impact — blast radius
- src/session.js
```

### `forge collide` — who else is in these files right now?

The everyday failure of running agents in parallel: two sessions (yours and a
teammate's — or two of your own) silently edit the same or import-coupled files and
the conflict surfaces at merge time. The ledger already knows the answer — every
session mints a summary claim listing the files it touched, and those claims
team-merge over plain git — so `forge collide` is a pure read: no server, no presence
protocol, no new storage. Risk composes by the house noisy-OR,
`1 − ∏(1 − recᵢ × strengthᵢ)`, over recent foreign sessions: recency uses a
deliberately short 7-day half-life (a collision is about _now_), and strength counts
direct file hits full and 1-hop import neighbors half. Advisory, fail-open, and
exposed to every MCP-capable agent as `collide_check` — an agent can ask "is anyone
else in here?" before its first edit.

```console
$ forge collide
checking 2 file(s) in play
collision risk █████░░░ 0.68

amina day 20671 rec 0.91
direct src/ledger.js
coupled src/ledger_sync.js

advisory — coordinate or pull their ledger before editing the shared files
```

### `forge rank` — what here is dangerous to touch?

The standing companion to `forge impact`: impact answers "what breaks if I change X",
Expand Down Expand Up @@ -1233,7 +1259,7 @@ one extra turn, exactly when that turn was owed.
> `forge substrate "<task>" --json` (or the MCP tool `substrate_check`). If
> `okToProceed` is false, ask the questions first; read `impact.impactedFiles` before editing.

…and exposes the substrate as **20 MCP tools** any MCP-capable agent can call directly
…and exposes the substrate as **21 MCP tools** any MCP-capable agent can call directly
(the stdio server is launched with `forge cortex-mcp`, wired automatically via the
emitted `.mcp.json`):

Expand All @@ -1260,6 +1286,7 @@ emitted `.mcp.json`):
| `forge_provider_status` | Provider detection — which API provider is active (auto-detected or configured), env vars set, and health checks. |
| `forge_remember` | Store a durable fact in this repo's portable memory (.forge/brain/). |
| `forge_ledger_ratify` | Promote a ledger claim's confidence — record an independent oracle ratification (the claim held under test). |
| `collide_check` | Parallel-session conflict radar — which recent teammate/agent sessions touched the files (or their import neighbors) you are about to edit, from the team-merged Forge ledger. |
| `rank_code` | Which code is load-bearing and dangerous to touch — PageRank centrality over the Forge atlas graph joined with past-incident history from the evidence ledger, plus circular-dependency clusters and chokepoint files whose removal disconnects the import graph. |
| `forge_ledger_retract` | Tombstone a ledger claim with a reason — mark it as no longer valid so it stops influencing routing and memory. |
<!-- forge:render:mcp-tools:end -->
Expand Down
11 changes: 11 additions & 0 deletions mintlify/cli/substrate.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ Predict the blast radius for a symbol or file from the atlas graph.
forge impact <symbol-or-file>
```

## `forge collide`

Parallel-session conflict radar: which recent teammate/agent sessions touched the
files (or their import neighbors) you are editing, read from the team-merged ledger —
`risk = 1 − ∏(1 − rec × strength)` over recent foreign sessions. Also exposed as the
`collide_check` MCP tool.

```bash
forge collide [<file>…] [--json]
```

## `forge rank`

Load-bearing code: weighted PageRank centrality over the atlas graph joined with
Expand Down
2 changes: 1 addition & 1 deletion mintlify/concepts/config-compiler.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Each layer is brand-named and emitted cross-tool.
does not. Every enforceable invariant belongs here.
</Accordion>
<Accordion title="mcp — the protocol layer" icon="plug">
Forge ships one stdio server (`src/cortex_mcp.js`) exposing 20 MCP tools: the
Forge ships one stdio server (`src/cortex_mcp.js`) exposing 21 MCP tools: the
substrate checks (`substrate_check` / `predict_impact` / `assumption_gate` / …),
memory reads _and_ writes (`forge_remember`, ledger ratify/retract), and ops/health.
</Accordion>
Expand Down
2 changes: 1 addition & 1 deletion mintlify/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ forge substrate "Change verifyToken in src/auth.js to require length > 20; updat
<Note>
On Claude Code the substrate runs on **every prompt automatically** via a
`UserPromptSubmit` hook — advisory only, silent on clean tasks. Every other tool gets
a native config rule plus 20 MCP tools it can call itself.
a native config rule plus 21 MCP tools it can call itself.
</Note>

If `forge substrate` says `ASK FIRST`, ask the returned questions before editing. Read
Expand Down
4 changes: 3 additions & 1 deletion src/anchor.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ export function onGoalScore(goalTokens, fileTokens) {
// advisory coarse check; widen to a managed-file manifest if that ever matters.
const NOISE = /(^|\/)\.forge\/|(^|\/)\.[^/]+\/|^\.[^/]+$|(^|\/)(AGENTS|CLAUDE)\.md$/i;

function gitFiles(root) {
/** The working diff (changed vs HEAD + untracked), forge-noise filtered — exported so
* collide.js reads "what am I touching" the exact same way the drift check does. */
export function gitFiles(root) {
const run = (args) => {
try {
return execFileSync("git", args, {
Expand Down
37 changes: 36 additions & 1 deletion src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,11 @@ HANDLERS.ledger = async (argv) => {
const parseDay = (s) => {
if (/^\d{1,6}$/.test(s ?? "")) return Number(s); // bare epoch-day
const t = Date.parse(`${s}T00:00:00Z`);
return Number.isNaN(t) ? null : Math.floor(t / 86_400_000);
if (Number.isNaN(t)) return null;
// Round-trip check: Date.parse silently rolls impossible dates over (2026-02-31
// → March 3rd), which would answer a temporal query for a day nobody asked about.
if (new Date(t).toISOString().slice(0, 10) !== s) return null;
return Math.floor(t / 86_400_000);
};
if (sub === "at") {
const day = parseDay(args[2]);
Expand Down Expand Up @@ -928,6 +932,13 @@ HANDLERS.ledger = async (argv) => {
process.exitCode = 1;
return;
}
if (a > b) {
// beliefDiff's contract is dayA ≤ dayB; a reversed window would print silently
// inverted appeared/retired classes, so refuse loudly instead.
console.error(` <since> (day ${a}) is after <until> (day ${b}) — swap the arguments`);
process.exitCode = 1;
return;
}
const lg = await import("./ledger.js");
const d = lg.beliefDiff(ls.loadState(dir), a, b);
if (json) return console.log(JSON.stringify({ since: a, until: b, ...d }, null, 2));
Expand Down Expand Up @@ -1215,6 +1226,30 @@ HANDLERS.atlas = async (argv) => {
}
return;
};
HANDLERS.collide = async (argv) => {
const { collideReport } = await import("./collide.js");
const json = argv.includes("--json");
const files = argv.slice(1).filter((a) => !a.startsWith("--"));
const r = collideReport(process.cwd(), { files });
if (json) return console.log(JSON.stringify(r, null, 2));
heading(`${BRAND.brand} collide — parallel-session conflict radar\n`);
if (!r.mine.length) return console.log(" working tree clean — nothing to collide with");
console.log(paint(` checking ${r.mine.length} file(s) in play`, "dim"));
if (!r.sessions.length)
return console.log(" no recent foreign session touched these files or their import neighbors");
console.log(` collision risk ${bar(r.risk, 8)} ${r.risk.toFixed(2)}\n`);
for (const s of r.sessions.slice(0, 8)) {
console.log(
` ${paint(s.author || "(unknown)", "accent")} day ${s.day} ${paint(`rec ${s.rec.toFixed(2)}`, "dim")}`,
);
for (const f of s.direct) console.log(` ${paint("direct ", "warn")} ${f}`);
for (const f of s.coupled) console.log(` ${paint("coupled", "dim")} ${f}`);
}
console.log(
paint("\n advisory — coordinate or pull their ledger before editing the shared files", "dim"),
);
return;
};
HANDLERS.rank = async (argv) => {
const { rankReport } = await import("./rank.js");
const json = argv.includes("--json");
Expand Down
Loading
Loading