diff --git a/.agents/skills/effort-graph/release.json b/.agents/skills/effort-graph/release.json
deleted file mode 100644
index a0d4235f..00000000
--- a/.agents/skills/effort-graph/release.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "format": 1,
- "flatbreadVersion": "1.0.0-alpha.22",
- "effortGraphVersion": "0.1.0-alpha.0",
- "gitTag": "v1.0.0-alpha.22"
-}
diff --git a/.agents/skills/effort-graph/SKILL.md b/.agents/skills/proof/SKILL.md
similarity index 100%
rename from .agents/skills/effort-graph/SKILL.md
rename to .agents/skills/proof/SKILL.md
diff --git a/.agents/skills/effort-graph/glossary.md b/.agents/skills/proof/glossary.md
similarity index 100%
rename from .agents/skills/effort-graph/glossary.md
rename to .agents/skills/proof/glossary.md
diff --git a/.agents/skills/effort-graph/reference.md b/.agents/skills/proof/reference.md
similarity index 100%
rename from .agents/skills/effort-graph/reference.md
rename to .agents/skills/proof/reference.md
diff --git a/.agents/skills/proof/release.json b/.agents/skills/proof/release.json
new file mode 100644
index 00000000..26e56748
--- /dev/null
+++ b/.agents/skills/proof/release.json
@@ -0,0 +1,6 @@
+{
+ "format": 1,
+ "flatbreadVersion": "1.0.0",
+ "effortGraphVersion": "1.0.0",
+ "gitTag": "v1.0.0"
+}
diff --git a/.agents/skills/effort-graph/setup.md b/.agents/skills/proof/setup.md
similarity index 100%
rename from .agents/skills/effort-graph/setup.md
rename to .agents/skills/proof/setup.md
diff --git a/.cursor/agents/flatbread-architecture-planner.md b/.cursor/agents/flatbread-architecture-planner.md
index 281c06d8..99fc8365 100644
--- a/.cursor/agents/flatbread-architecture-planner.md
+++ b/.cursor/agents/flatbread-architecture-planner.md
@@ -31,7 +31,7 @@ Keep recommendations aligned with `flatbread-flow-pmf-audit.md` and `flatbread-f
## Output Schema For DAG Handoff
-When invoked inside a DAG task (the `proof` skill / `@flatbread/proof` package), keep the response under ~1800 chars and lead with these exact `##` headings so downstream tasks can find them after the 2000-char upstream stitch cap:
+When invoked inside a Proof DAG task (external CLI from https://github.com/FlatbreadLabs/proof, package `@flatbread/proof`), keep the response under ~1800 chars and lead with these exact `##` headings so downstream tasks can find them after the 2000-char upstream stitch cap:
```
## Current contract
diff --git a/.cursor/agents/flatbread-code-review-orchestrator.md b/.cursor/agents/flatbread-code-review-orchestrator.md
index 956c0e1b..c2cf4be5 100644
--- a/.cursor/agents/flatbread-code-review-orchestrator.md
+++ b/.cursor/agents/flatbread-code-review-orchestrator.md
@@ -1,13 +1,13 @@
---
name: flatbread-code-review-orchestrator
-description: Orchestrates an adversarial code-review DAG over a Flatbread change diff using `@flatbread/proof` — picks ≤5 reviewer perspectives dynamically from the diff, runs them in a single rank, and merges their findings through a judge that emits chunk-bound structured feedback.
+description: Orchestrates an adversarial code-review DAG over a Flatbread change diff using the external Proof CLI (`@flatbread/proof`) — picks ≤5 reviewer perspectives dynamically from the diff, runs them in a single rank, and merges their findings through a judge that emits chunk-bound structured feedback.
readonly: true
tools: ReadFile, Glob, rg, Shell
---
# Flatbread Code-Review Orchestrator
-You orchestrate an adversarial code review over a Flatbread change diff. Reviewers run as parallel local subagents under `@flatbread/proof`, then a judge merges their findings into structured feedback bound to specific diff chunks. You do not edit code. You produce a DAG, run it, and report.
+You orchestrate an adversarial code review over a Flatbread change diff. Reviewers run as parallel local subagents under the external Proof CLI (`@flatbread/proof` from https://github.com/FlatbreadLabs/proof), then a judge merges their findings into structured feedback bound to specific diff chunks. You do not edit code. You produce a DAG, run it, and report.
Your loyalty is to **correctness** and **wide, robust test coverage**. Pedantry is the failure mode you must avoid (see `## Anti-Pedantry Guardrails`).
@@ -91,18 +91,23 @@ Pass the diff to each subtask by **file path reference**, not by inlining. The d
### 4. Run the DAG
+Proof is an external package (`@flatbread/proof` from https://github.com/FlatbreadLabs/proof). It is not a workspace package in this monorepo. Install it as a root dependency (`pnpm add -Dw @flatbread/proof`) or otherwise ensure `pnpm exec proof` resolves before continuing. Also set `CURSOR_API_KEY` (or load it from `.env`).
+
```bash
[ -n "$CURSOR_API_KEY" ] || { [ -f .env ] && set -a && source .env && set +a; }
-CANVAS_PATH="$HOME/.cursor/projects/$(pwd | sed 's|^/||; s|/|-|g')/canvases/dag-review-$(git rev-parse --short HEAD).canvas.tsx"
+pnpm exec proof --help >/dev/null || {
+ echo "Install @flatbread/proof so pnpm exec proof resolves (pnpm add -Dw @flatbread/proof)."
+ exit 1
+}
-[ -f "$(git rev-parse --show-toplevel)/packages/proof/dist/run_dag.js" ] || pnpm -F @flatbread/proof build
+CANVAS_PATH="$HOME/.cursor/projects/$(pwd | sed 's|^/||; s|/|-|g')/canvases/dag-review-$(git rev-parse --short HEAD).canvas.tsx"
pnpm exec proof --init-only --dag /tmp/review-dag.json --canvas-path "$CANVAS_PATH"
open "$CANVAS_PATH" >/dev/null 2>&1 || true
```
-Then surface the canvas link in chat using the exact text `Open Canvas` (per the `proof` skill), then run for real:
+Then surface the canvas link in chat using the exact text `Open Canvas`, then run for real:
```bash
pnpm exec proof --dag /tmp/review-dag.json --canvas-path "$CANVAS_PATH"
@@ -128,7 +133,6 @@ Each perspective is read-only, scoped, and produces the same handoff schema (`##
| `docs-and-positioning` | LOW | `*.md`, `docs/**`, `flatbread-flow-*.md` | Repositioning drift (Flatbread is _not_ a general flat-file database; see `flatbread-major-migration` skill). Stale commands. Broken cross-links. |
| `release-discipline` | MED | `package.json` version bumps, `scripts/publish.ts`, `CHANGELOG.md`, `pnpm-workspace.yaml` | Coordinated monorepo bumps for breaking changes; prerelease train discipline (`1.0.0-alpha.N` / `-beta.N`); accidental `latest` dist-tag; missing migration notes. |
| `perf-and-caching` | MED | `**/cache*.ts`, `**/hash*.ts`, `**/dependencyCheck*.ts`, hot-path resolver edits | Cache key collisions, unbounded memoization, accidental O(n²) on file count, FS calls in tight loops. |
-| `proof-runtime-internals` | HIGH | `packages/proof/src/**` | DAG topo-sort correctness, stream throttling/idle-timeout edges, supervisor exit-code (75) contract, canvas-write atomicity, `--restart-on-runner-change` boundaries. |
You may add a one-off bespoke perspective if the diff's center of gravity is outside this catalog (e.g. a new `packages//`), but keep the cap at 5 total.
diff --git a/.cursor/agents/flatbread-proof-runtime-skeptic.md b/.cursor/agents/flatbread-proof-runtime-skeptic.md
deleted file mode 100644
index c9e5a9f5..00000000
--- a/.cursor/agents/flatbread-proof-runtime-skeptic.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-name: flatbread-proof-runtime-skeptic
-description: Read-only reviewer for Proof runtime invariants, loop semantics, resume/restart behavior, and failure-mode ergonomics.
-readonly: true
-tools: ReadFile, Glob, rg, Shell
----
-
-# Flatbread Proof Runtime Skeptic
-
-You review `@flatbread/proof` like a failure analyst. Assume orchestration logic, task ordering, resume/restart boundaries, and budget semantics are wrong until the code and tests prove otherwise.
-
-## Bias
-
-- Prefer boring runtime behavior over clever API surface.
-- Treat hidden state, precedence rules, and partial reruns as high risk.
-- Treat confusing logs, canvas states, or restart semantics as DevEx bugs, not documentation nits.
-
-## Focus
-
-- Runtime correctness for DAG execution, especially dependency ordering, rank behavior, partial reruns, and terminal outcomes.
-- Interaction of DAG schema, CLI flags, persisted state, sidecar artifacts, and self-hosting restarts.
-- Whether tests prove the runtime contract contributors will depend on.
-- Whether a contributor debugging a bad proof run would get actionable evidence.
-- Prefer the focused proof suite command `pnpm -F @flatbread/proof test` when validating proof runtime behavior; root `pnpm test` should also cover it.
-
-## Output
-
-Lead with findings, ordered by severity. Prefer concrete runtime breakage, observability gaps, and validation holes over stylistic commentary.
-
-## Output Schema For DAG Handoff
-
-Use these exact headings:
-
-```
-## Persona
-## Bias
-## Blockers
-## High-severity findings
-## Medium-severity findings
-## Low-severity findings
-## Residual risk
-## Recommended next DAG tasks
-```
-
-Each finding is one bullet: `path/to/file.ts:line — risk -> minimal fix`.
-Keep the response under ~1800 chars when used inside a DAG.
diff --git a/.cursor/dags/README.md b/.cursor/dags/README.md
new file mode 100644
index 00000000..41031302
--- /dev/null
+++ b/.cursor/dags/README.md
@@ -0,0 +1,40 @@
+# Flatbread Proof DAGs
+
+Flatbread-specific DAG JSON for the external Proof CLI
+([`@flatbread/proof`](https://github.com/FlatbreadLabs/proof)). These files
+belong here, not in the Proof repo: their prompts call monorepo commands such
+as `pnpm --filter @flatbread/…` and name Flatbread packages, ports, and agents.
+
+## Layout
+
+- `flatbread/` — workspace orchestration templates
+ - `dag-schema-migration.json` — schema-breaking migration (21 tasks)
+ - `dag-codegen-change.json` — codegen-focused change
+ - `dag-docs-sync.json` — docs/positioning sync
+ - `dag-flatbread-flow-pmf-audit.json` — PMF audit flow
+
+## Prerequisites
+
+1. Install Proof so `pnpm exec proof` resolves from this repo root, for example:
+
+ ```bash
+ pnpm add -Dw @flatbread/proof
+ ```
+
+ Or install from the Proof repository if the package is not on the registry
+ you use yet.
+
+2. Set `CURSOR_API_KEY` (or load it from `.env`).
+
+## Run a DAG
+
+From the Flatbread repo root:
+
+```bash
+pnpm exec proof --init-only --dag .cursor/dags/flatbread/dag-schema-migration.json
+pnpm exec proof --dag .cursor/dags/flatbread/dag-schema-migration.json
+```
+
+Replace the DAG path with any file under `flatbread/`. Edit task prompts and
+`depends_on` before a real run; `--init-only` confirms the rank shape without
+spending model calls.
diff --git a/.cursor/skills/proof/examples/flatbread/dag-codegen-change.json b/.cursor/dags/flatbread/dag-codegen-change.json
similarity index 100%
rename from .cursor/skills/proof/examples/flatbread/dag-codegen-change.json
rename to .cursor/dags/flatbread/dag-codegen-change.json
diff --git a/.cursor/skills/proof/examples/flatbread/dag-docs-sync.json b/.cursor/dags/flatbread/dag-docs-sync.json
similarity index 100%
rename from .cursor/skills/proof/examples/flatbread/dag-docs-sync.json
rename to .cursor/dags/flatbread/dag-docs-sync.json
diff --git a/.cursor/skills/proof/examples/dag-flatbread-flow-pmf-audit.json b/.cursor/dags/flatbread/dag-flatbread-flow-pmf-audit.json
similarity index 98%
rename from .cursor/skills/proof/examples/dag-flatbread-flow-pmf-audit.json
rename to .cursor/dags/flatbread/dag-flatbread-flow-pmf-audit.json
index ec07e91c..814bff61 100644
--- a/.cursor/skills/proof/examples/dag-flatbread-flow-pmf-audit.json
+++ b/.cursor/dags/flatbread/dag-flatbread-flow-pmf-audit.json
@@ -46,7 +46,7 @@
"id": "recommend-roadmap",
"depends_on": ["synthesize-pmf-gaps"],
"complexity": "HIGH",
- "subtask_prompt": "You are acting as `flatbread-architecture-planner` producing a roadmap recommendation. Follow its output schema. Output must lead with these `##` headings verbatim: `## Current contract`, `## Proposed contract`, `## Migration impact`, `## Validation plan`, `## Human checkpoints`.\n\nBased on the synthesized PMF gaps, recommend a concise product direction and roadmap. Output a sharper positioning statement, 3-5 product primitives to add or clarify (each with file/package anchor), near-term experiments, and what not to build yet. `## Migration impact` should map each recommendation to the affected packages so a follow-up `flatbread-major-migration` DAG (template at `.cursor/skills/proof/examples/flatbread/dag-schema-migration.json`) can be authored from this output without re-deriving scope."
+ "subtask_prompt": "You are acting as `flatbread-architecture-planner` producing a roadmap recommendation. Follow its output schema. Output must lead with these `##` headings verbatim: `## Current contract`, `## Proposed contract`, `## Migration impact`, `## Validation plan`, `## Human checkpoints`.\n\nBased on the synthesized PMF gaps, recommend a concise product direction and roadmap. Output a sharper positioning statement, 3-5 product primitives to add or clarify (each with file/package anchor), near-term experiments, and what not to build yet. `## Migration impact` should map each recommendation to the affected packages so a follow-up `flatbread-major-migration` DAG (template at `.cursor/dags/flatbread/dag-schema-migration.json`) can be authored from this output without re-deriving scope."
}
]
}
diff --git a/.cursor/skills/proof/examples/flatbread/dag-schema-migration.json b/.cursor/dags/flatbread/dag-schema-migration.json
similarity index 100%
rename from .cursor/skills/proof/examples/flatbread/dag-schema-migration.json
rename to .cursor/dags/flatbread/dag-schema-migration.json
diff --git a/.cursor/skills/dag-task-runner/SKILL.md b/.cursor/skills/dag-task-runner/SKILL.md
deleted file mode 100644
index af19dbfa..00000000
--- a/.cursor/skills/dag-task-runner/SKILL.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-name: dag-task-runner
-description: DEPRECATED ALIAS — the DAG task runner has been promoted to the workspace package @flatbread/proof. Use the `proof` skill (.cursor/skills/proof/SKILL.md) for new work; this entry only exists to redirect agents that still reference the old name.
----
-
-# DAG Task Runner — moved to `proof`
-
-This skill has been renamed and promoted from a copy-into-skill bundle to a first-class Flatbread monorepo package.
-
-## What changed
-
-| Before | After |
-| -------------------------------------------------------- | -------------------------------------------- |
-| Skill name `dag-task-runner` | Skill name `proof` |
-| Runtime in `.cursor/skills/dag-task-runner/scripts/*.ts` | Runtime in `packages/proof/src/*.ts` |
-| Run via `tsx .cursor/skills/.../run_dag.ts` | Run via `pnpm exec proof` |
-| Supervisor `tsx .../run_dag_supervisor.ts` | Supervisor `pnpm exec proof-supervisor` |
-| Default state dir `.dag-runner/` | Default state dir `.proof/` |
-| Log prefix `[dag-runner]` / `[dag-runner-supervisor]` | Log prefix `[proof]` / `[proof-supervisor]` |
-| Examples at `.cursor/skills/dag-task-runner/examples/` | Examples at `.cursor/skills/proof/examples/` |
-
-CLI flag names, the DAG JSON schema, the `.canvas.tsx` shape, oracle / pause / convergence semantics, and the public library API are all unchanged. Existing DAG JSON files and persisted run-state files (move them from `.dag-runner/` to `.proof/` if you want to resume) work as-is.
-
-## What to do
-
-1. Open `.cursor/skills/proof/SKILL.md` for the canonical workflow.
-2. Replace any hardcoded `.cursor/skills/dag-task-runner/scripts/run_dag.ts` paths in your prompts / playbooks with the `pnpm exec proof` invocation.
-3. If you have an in-flight run with `.dag-runner/run-state.json`, either rename the directory to `.proof/` or pass the old path explicitly via `--state-path`.
-
-## Why
-
-`dag-task-runner` was always a copy-into-project bundle, which meant every project carried its own bit-rotted snapshot of the runtime. Promoting it to `@flatbread/proof` lets the runtime evolve in lockstep with the rest of the Flatbread monorepo (tsup builds, lint, type checks) and gives downstream tooling a stable `import { parseDAG, computeRanks, ... } from '@flatbread/proof'` library surface alongside the CLI.
diff --git a/.cursor/skills/flatbread-major-migration/SKILL.md b/.cursor/skills/flatbread-major-migration/SKILL.md
index 4013ce83..f38492a4 100644
--- a/.cursor/skills/flatbread-major-migration/SKILL.md
+++ b/.cursor/skills/flatbread-major-migration/SKILL.md
@@ -64,7 +64,7 @@ Treat breaking work as a coordinated monorepo release across `flatbread`, `@flat
## Default DAG Shape
-When this skill is run under `proof` (the `@flatbread/proof` package; legacy alias `dag-task-runner`), use the topology in `flatbread-flow-agentic-workflows.md` ("DAG Topology" section). The canonical schema-migration shape — express the DAG via `depends_on` only; the runner computes ranks via Kahn topo-sort. The shape below is what `pnpm exec proof --init-only` produces for the starter template (21 tasks across 7 ranks):
+When this skill is run under the external Proof CLI (`@flatbread/proof` from https://github.com/FlatbreadLabs/proof), use the topology in `flatbread-flow-agentic-workflows.md` ("DAG Topology" section). The canonical schema-migration shape — express the DAG via `depends_on` only; the runner computes ranks via Kahn topo-sort. The shape below is what `pnpm exec proof --init-only` produces for the starter template (21 tasks across 7 ranks):
```
rank 1 diag-schema, diag-resolvers, diag-types, diag-codegen, diag-cli,
@@ -79,7 +79,7 @@ rank 6 verify-cli (reviewer; b
rank 7 browser-verify (browser-verifier; binds port 5057, depends_on impl-examples + verify-cli)
```
-Two ranks bind port `5057` (`verify-cli`, `browser-verify`); they must remain on distinct ranks. A starter JSON lives at `.cursor/skills/proof/examples/flatbread/dag-schema-migration.json`. Edit task contents and `depends_on`, then re-run `--init-only` to confirm the rank shape didn't regress.
+Two ranks bind port `5057` (`verify-cli`, `browser-verify`); they must remain on distinct ranks. A starter JSON lives at `.cursor/dags/flatbread/dag-schema-migration.json`. Edit task contents and `depends_on`, then re-run `--init-only` to confirm the rank shape didn't regress. See `.cursor/dags/README.md` for how to install and run Proof against these DAGs.
### Safe parallel cuts
diff --git a/.cursor/skills/proof/SKILL.md b/.cursor/skills/proof/SKILL.md
deleted file mode 100644
index 6bafdfe4..00000000
--- a/.cursor/skills/proof/SKILL.md
+++ /dev/null
@@ -1,270 +0,0 @@
----
-name: proof
-description: Decompose a user's task into a DAG of subtasks and execute them with Cursor SDK local subagents in topological order, rendering live streaming status to a canvas. Each task has a complexity (HIGH/MED/LOW) that maps to a model. Use when the user asks to fan out work, decompose a task into a DAG, run subagents in parallel, or break a large task into a dependency graph.
----
-
-# Proof
-
-Decomposes a user-described task into a JSON DAG, then runs each node as a Cursor SDK local subagent (with parents' outputs stitched into the child's prompt). Live DAG state — including each running subagent's streaming output — is rendered into a `.canvas.tsx` that the runner rewrites on every status transition; the IDE hot-recompiles so the user sees subagents move through `PENDING -> RUNNING -> FINISHED/ERROR` in real time.
-
-The runtime ships as the workspace package `@flatbread/proof` (`packages/proof`). It exposes two CLIs — `proof` (runner) and `proof-supervisor` (self-hosting wrapper) — plus a public library API for tooling that wants to author or inspect DAGs programmatically.
-
-## When to use
-
-Trigger when the user says any of:
-
-- "decompose this task", "break this into a DAG", "fan out subagents"
-- "run this as a graph of subtasks"
-- a multi-step request where some steps clearly depend on others and others can run in parallel
-
-Skip when the task is a single-shot edit, a quick question, or already linear enough that one agent turn would handle it.
-
-## Workflow
-
-### Step 1 — Generate a DAG JSON
-
-You (the parent agent) author the DAG inline using your understanding of the user's task. Schema:
-
-```json
-{
- "title": "",
- "models": {
- "HIGH": {
- "id": "gpt-5.4",
- "params": [{ "id": "reasoning", "value": "high" }]
- },
- "MED": "composer-2",
- "LOW": {
- "id": "gpt-5.4-nano",
- "params": [{ "id": "reasoning", "value": "low" }]
- }
- },
- "tasks": [
- {
- "id": "",
- "depends_on": ["", "..."],
- "complexity": "HIGH | MED | LOW",
- "subtask_prompt": ""
- }
- ]
-}
-```
-
-Rules:
-
-- Every `depends_on` entry must reference another task's `id`.
-- No cycles. The runner rejects cyclic DAGs at parse time.
-- `complexity` controls the model the subagent uses (see table below). Pick `HIGH` for novel/complex reasoning, `MED` for typical implementation, `LOW` for mechanical/lookup tasks.
-- Optional top-level `models` can override the default complexity → model map for this DAG. Values can be plain SDK model id strings or model selection objects of the shape `{ "id": "...", "params": [{ "id": "...", "value": "..." }] }`, with `params` omitted when unused.
-- `subtask_prompt` should read like a standalone request — the runner automatically prepends a short summary of upstream task outputs, so you do not need to repeat them.
-- Do **not** put two tasks that write to the same file in the same rank (siblings within a rank run concurrently and would race).
-
-#### Maximize parallelism — this is the whole point of the runner
-
-The runner executes tasks within a rank **concurrently** via `Promise.all`. A linear `A → B → C → D` DAG wastes that capability. Before finalizing the DAG, actively decompose the problem to surface independent work:
-
-1. **Default to no dependencies.** Add a `depends_on` entry **only** when the child task literally cannot start without the parent's output. "Logically follows" is not a dependency.
-2. **Split read-only research and discovery into a wide first rank.** Codebase grepping, doc reading, dependency scans, schema lookups, test inventory — these almost always share rank 1 with no edges between them.
-3. **Fan out post-implementation work.** Tests, docs, changelog entries, type updates, lint fixes typically all depend on the same implementation task and on nothing else — put them in one rank, not a chain.
-4. **Use diamonds, not lines.** If two tasks both feed into a third, model that explicitly: rank 1 has the two parents, rank 2 is the merge.
-5. **Same-rank file-write safety.** The one hard constraint: don't put two tasks in the same rank if they would write the same file. Either serialize them with a `depends_on`, or merge them into one task.
-
-Quality bar: when you sketch the rank structure (rank 1 → rank 2 → …), at least one rank should contain more than one task in any non-trivial problem. If your DAG is a single chain of 1-task ranks, you almost certainly missed parallelism — go back and look again.
-
-The example shipped with the skill (`.cursor/skills/proof/examples/example_dag.json`) demonstrates the pattern: rank 1 fans out to two read-only research tasks, rank 2 merges them into a design, rank 3 implements, and rank 4 fans out again to tests + docs.
-
-Write the JSON to a temp file **and immediately generate the initial canvas** so the user can open it while subagents spin up. Run all of the following in a single shell block:
-
-```bash
-# 0. Pick a canvas path
-CANVAS_PATH="$HOME/.cursor/projects//canvases/dag-.canvas.tsx"
-
-# 1. Write the DAG JSON
-cat > /tmp/dag-.json <<'JSON'
-{ "title": "...", "tasks": [ ... ] }
-JSON
-
-# 2. Build the @flatbread/proof package once per workspace install
-# (skipped if dist/ is already present; safe to re-run).
-[ -f "$(git rev-parse --show-toplevel)/packages/proof/dist/run_dag.js" ] || \
- pnpm -F @flatbread/proof build
-
-# 3. Generate the initial all-PENDING canvas (no CURSOR_API_KEY needed)
-pnpm exec proof \
- --init-only \
- --dag /tmp/dag-.json \
- --canvas-path "$CANVAS_PATH"
-
-# 4. Best-effort auto-open of the canvas file; ignore failure in headless/non-macOS environments
-open "$CANVAS_PATH" >/dev/null 2>&1 || true
-```
-
-The canvas path is:
-
-```
-~/.cursor/projects//canvases/dag-.canvas.tsx
-```
-
-`` is derived from the cwd's absolute path by stripping the leading `/`, replacing path separators with `-`, and sanitizing other non-alphanumeric characters within each path segment to `-`. Example: cwd `/Users/me/Code/myapp` → slug `Users-me-Code-myapp`. Use the same `` you used for the DAG JSON filename so they're easy to correlate.
-
-### Step 2 — Surface the canvas link in chat
-
-Now that the file exists on disk, post a Markdown hyperlink with the exact text `Open Canvas` and a `file://` URL, plus the absolute path for fallback:
-
-> I created a live canvas: [Open Canvas](file:///Users//.cursor/projects//canvases/dag-.canvas.tsx)
-> Fallback path: `/Users//.cursor/projects//canvases/dag-.canvas.tsx`
-
-Always use the link text `Open Canvas`. Use the absolute path in both the `file://` URL and fallback path, never `~/`. Do this **before** Step 3 so the user can open the canvas while subagents are still spinning up. The Step 1 shell block already attempts to auto-open the canvas with `open`; if that fails, continue and rely on the chat link.
-
-### Step 3 — Run the DAG
-
-Ensure `CURSOR_API_KEY` is set (the runner fails fast if missing), then launch:
-
-```bash
-[ -n "$CURSOR_API_KEY" ] || { [ -f .env ] && set -a && source .env && set +a; }
-
-pnpm exec proof \
- --dag /tmp/dag-.json \
- --canvas-path "$CANVAS_PATH"
-```
-
-If the DAG is expected to edit the runner itself (`packages/proof/src/**`), launch through the supervisor instead so source edits take effect at a process boundary:
-
-```bash
-pnpm exec proof-supervisor \
- --dag /tmp/dag-.json \
- --canvas-path "$CANVAS_PATH" \
- --state-path "$HOME/.cursor/projects//dag-state/.json"
-```
-
-The supervisor passes `--restart-on-runner-change` to the runner. When runner runtime files change after a rank or convergence iteration, the child runner persists state, marks the canvas `RESTARTING RUNNER`, exits `75`, and the supervisor relaunches with `--resume-state` so pending tasks continue under the new source. After editing `packages/proof/src/**`, run `pnpm -F @flatbread/proof build` so the relaunch picks up the new code.
-
-Same `--canvas-path` as Step 1. The runner:
-
-1. Validates the DAG and reuses the existing canvas file.
-2. For each rank (Kahn topo-sort), launches ready tasks concurrently as local Cursor SDK agents and rewrites the canvas as each one transitions, streaming assistant text into each task card live.
-3. Automatically skips tasks whose upstream dependencies failed (marks them `ERROR` with a "Skipped: upstream task(s) … failed" message).
-4. Captures each subagent's final assistant text, status, token usage, and duration.
-5. Writes a final canvas with summary stats.
-6. Artifact output (default, suppress with `--no-artifacts` or override path with `--full-output-dir`; skipped entirely for `--init-only` and `--dry-check-cmds`):
- - **At run start:** writes `_dag.json` (the original DAG definition) to the artifacts directory.
- - **As each task finishes:** writes `${taskId}.md` (full transcript for `kind: task`, `oracle`, and `pause`).
- - **At run end:** best-effort `_index.md` (run summary table with timestamps, outcome, and per-task links for transcripts that exist); write failures are logged as `[proof]` warnings rather than crashing the runner.
-7. On SIGINT/SIGTERM/SIGHUP, cancels all in-flight subagents before finalizing the canvas.
-
-#### CLI knobs
-
-| Flag | Default | Purpose |
-| ------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `--models-file ` | — | JSON file containing a partial complexity → model override map. |
-| `--state-path ` | — | Persist resumable state after rank boundaries. |
-| `--resume-state ` | — | Resume from a persisted state file. |
-| `--restart-on-runner-change` | `false` | Exit `75` after runner runtime files change so a supervisor can relaunch. |
-| `--task-timeout-ms ` | `1200000` (20 min) | Marks a task `ERROR` if it runs too long. |
-| `--stream-publish-ms ` | `500` | Throttles live canvas streaming writes. |
-| `--stream-idle-timeout-ms ` | `300000` (5 min) | Marks a task `ERROR` if no stream events arrive. |
-| `--debounce ` | `200` | Canvas write debounce interval. |
-| `--full-output-dir ` | computed default | Per-task transcripts + `_index.md` + `_dag.json`. Default: `/.flatbread/artifacts/dag--/`. Override path or suppress with `--no-artifacts`. |
-| `--no-artifacts` | `false` | Suppresses per-task transcripts, `_index.md`, and `_dag.json`; does **not** suppress `--findings-dir` JSON sidecars (separate code path). Canvas is still written. |
-
-### Step 4 — Summarize
-
-After the runner exits, briefly summarize what completed/failed and re-link the canvas with the exact text `[Open Canvas](file:///Users//.cursor/projects//canvases/dag-.canvas.tsx)` so the user can scroll back to it. Include the absolute fallback path only if useful.
-
-## Complexity → model
-
-| Complexity | Model |
-| ---------- | ----------------- |
-| HIGH | `claude-opus-4-7` |
-| MED | `composer-2` |
-| LOW | `gpt-5.4-nano` |
-
-Override any subset inline with top-level DAG `models`, or pass a reusable profile with `--models-file `. Values can be plain SDK model id strings or SDK model selections with `params`. At run time, Proof calls `Cursor.models.list()`, validates ids and param values, and expands partial selections by requiring requested params to match a catalog variant, then choosing the valid variant whose omitted params best match the model's default variant. Precedence is defaults < DAG `models` < `--models-file`. The Cursor model catalog can vary by account.
-
-To use a cheaper high-capability GPT model, use the base SDK id plus params, not a suffix-style id:
-
-```json
-{
- "models": {
- "HIGH": {
- "id": "gpt-5.4",
- "params": [{ "id": "reasoning", "value": "high" }]
- }
- }
-}
-```
-
-### Discovering valid model ids
-
-Many Cursor CLI catalog models encode reasoning effort and Max Mode as **slug suffixes** (e.g. `claude-opus-4-7-thinking-max`, `gpt-5.5-extra-high`, `gpt-5.3-codex-xhigh`), but the Cursor SDK may accept only base slugs plus `params`. Do not compose SDK model ids from CLI suffixes by hand: use `{ "id": "gpt-5.4", "params": [{ "id": "reasoning", "value": "high" }] }`, not `gpt-5.4-high`. For SDK-bound code, prefer `Cursor.models.list()` or the SDK's `ConfigurationError` catalog over `cursor-agent --list-models`.
-
-Ways to enumerate model ids:
-
-```bash
-# CLI catalog — useful for CLI runs, not authoritative for @cursor/sdk
-cursor-agent --list-models
-
-# SDK-flavored alternative — also prints any per-model `parameters` and preset `variants`
-pnpm -F @flatbread/proof models:list # all ids
-pnpm -F @flatbread/proof models:list # detail for one model
-pnpm -F @flatbread/proof models:list --grep # case-insensitive filter
-pnpm -F @flatbread/proof models:list --json
-```
-
-## Auth
-
-The runner reads `CURSOR_API_KEY` from the environment. Set it however you usually manage secrets:
-
-```bash
-export CURSOR_API_KEY=crsr_...
-```
-
-If the current workspace has a `.env` containing it, source that first:
-
-```bash
-set -a && source .env && set +a
-```
-
-## CLI options
-
-| Flag | Default | Notes |
-| ---------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `--dag` | required | Path to the DAG JSON file. |
-| `--canvas-path` | composed from below | Full path to the canvas file. Preferred as an absolute path for parent-managed flow; relative paths are accepted and resolve from the runner process cwd, not `--cwd`. |
-| `--canvas` | — | Canvas filename stem (no `.canvas.tsx`). Used only if `--canvas-path` is omitted. |
-| `--canvases-dir` | derived from cwd | Override the canvases output directory. Used only with `--canvas`. |
-| `--cwd` | `process.cwd()` | Working dir each subagent operates in. |
-| `--models-file` | — | JSON file containing a partial complexity → model override map. |
-| `--debounce` | `200` (ms) | Canvas write debounce interval. |
-| `--init-only` | `false` | Write the initial all-`PENDING` canvas and exit. No `CURSOR_API_KEY` required. |
-| `--full-output-dir` | computed default | Per-task transcripts as `${taskId}.md` plus `_index.md` and `_dag.json`. Defaults to `/.flatbread/artifacts/dag--/`. Override with an explicit path or suppress with `--no-artifacts`. |
-| `--no-artifacts` | `false` | Suppresses per-task transcripts, `_index.md`, and `_dag.json`; does **not** suppress `--findings-dir` JSON sidecars (separate code path). Canvas is still written. |
-| `--findings-dir` | — | Per-task JSON sidecars as `${taskId}.findings.json` for original runs and `${taskId}.iter.findings.json` for convergence re-runs. Schema: `{ taskId, iteration, status, durationMs, sections }`. |
-| `--state-path` | — | Persist resumable runner state. Defaults to `.proof/run-state.json` when `--restart-on-runner-change` is set. |
-| `--resume-state` | — | Load a persisted `RunState` and skip already terminal tasks. |
-| `--restart-on-runner-change` | `false` | Detect runner runtime file changes after safe boundaries and exit `75` for supervisor restart. |
-| `--max-runner-restarts` | `20` | Supervisor-only cap for relaunches from `proof-supervisor`. |
-| `--task-timeout-ms` | `1200000` (20 min) | Marks a task `ERROR` if it exceeds this duration. |
-| `--stream-publish-ms` | `500` (ms) | Throttles live canvas streaming writes to avoid excessive cloning. |
-| `--stream-idle-timeout-ms` | `300000` (5 min) | Marks a task `ERROR` if no stream events arrive within this window. |
-
-## Caveats
-
-- Per-task markdown transcripts, a run index (`_index.md`), and the DAG definition (`_dag.json`) are written under **`/.flatbread/artifacts/`** by default on **full DAG runs** (not `--init-only` or `--dry-check-cmds`). Pass `--no-artifacts` to suppress transcripts/index/DAG JSON, or `--full-output-dir` to override the path. `_index.md` links only transcripts that exist; if an individual transcript write fails, that row is marked as a missing transcript. **`--no-artifacts` does not disable `--findings-dir`** — for fully clean disk output, omit `--findings-dir` as well. In CI or read-only workspaces you may want `--no-artifacts` or a writable `--full-output-dir`.
-- When using `proof-supervisor`, each **child runner process** recomputes the default artifacts path with a new timestamp unless you pin a stable directory. The supervisor forwards the full argv to each child (only `--max-runner-restarts` is stripped), so put **`--full-output-dir ` on the supervisor invocation** if every restart should write into the same artifacts folder.
-- `--resume-state` creates a new artifact directory for the resumed session; tasks completed in prior sessions do not have transcripts in the new directory.
-- Local runtime only — every subagent runs against `--cwd` (defaults to wherever you invoke the runner).
-- Sibling tasks in the same rank run in parallel; do not let them write the same files.
-- Inline MCP servers and sub-sub-agents are not configured by this runner.
-- A failed upstream task skips downstream dependents (`ERROR` with `Skipped:` when any upstream is **`ERROR`** or **`BUDGET-EXCEEDED`**).
-- Canvas-inlined streamed text stays bounded (**`CANVAS_DISPLAY_CAP = 4000`** tail per task plus the existing `[...truncated N earlier chars...]` banner). For `kind: 'task'`, child prompts, in-process convergence loops, findings sidecars, and artifact markdown use a separate **execution transcript**; resumed runs can reconstruct it when the same `--full-output-dir` is reused and `transcriptPath` points at the mirrored stream file. Pause/oracle tasks still use their bounded status/output text. Upstream excerpts default to the same **2000-char section-aware policy** as before, now with explicit counted banners when trimming. Set **`DAG.outputPolicy.upstream`** to **`"full"`** to stitch full parent transcripts (mind model context limits).
-- Timed-out tasks are marked `ERROR` instead of staying indefinitely in `RUNNING`.
-- SIGINT/SIGTERM/SIGHUP gracefully cancel all in-flight subagents and finalize the canvas before exiting.
-- Unexpected unhandled rejections from SDK internals are suppressed to prevent runner crashes; uncaught exceptions are logged and trigger a clean shutdown.
-
-## Reference
-
-- Package: `@flatbread/proof` at `packages/proof`
-- DAG schema example: `.cursor/skills/proof/examples/example_dag.json`
-- Library exports: `import { parseDAG, computeRanks, ... } from '@flatbread/proof'`
-- Cursor SDK docs: https://cursor.com/docs/api/sdk/typescript
diff --git a/.cursor/skills/proof/examples/example_dag.json b/.cursor/skills/proof/examples/example_dag.json
deleted file mode 100644
index 26167c20..00000000
--- a/.cursor/skills/proof/examples/example_dag.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "title": "Build a browser graphing calculator",
- "tasks": [
- {
- "id": "research-calculator-scope",
- "depends_on": [],
- "complexity": "LOW",
- "subtask_prompt": "Sketch the smallest reasonable feature set for a dependency-free browser graphing calculator. Include expression input, x/y viewport controls, canvas plotting, error states, and a few sample expressions. Output as markdown bullets only — do not write any code yet."
- },
- {
- "id": "research-expression-safety",
- "depends_on": [],
- "complexity": "LOW",
- "subtask_prompt": "Summarize a conservative approach for evaluating math expressions in a small browser-only graphing calculator without third-party dependencies. Cover supported functions, variable handling for x, validation, and user-facing parse errors. Output as markdown bullets only — do not write code."
- },
- {
- "id": "design",
- "depends_on": ["research-calculator-scope", "research-expression-safety"],
- "complexity": "MED",
- "subtask_prompt": "Combine the upstream research into a one-page implementation plan for the graphing calculator. Specify file paths, DOM structure, core function signatures, expression evaluation strategy, coordinate transforms, and error handling. Output a markdown design doc — still no code."
- },
- {
- "id": "implement",
- "depends_on": ["design"],
- "complexity": "MED",
- "subtask_prompt": "Implement the design as `index.html` in the current working directory. It must be a single dependency-free HTML file with embedded CSS and JavaScript, draw function graphs on a canvas, expose viewport controls, and show validation errors without crashing. After writing the file, describe how to open and use it."
- },
- {
- "id": "tests",
- "depends_on": ["implement"],
- "complexity": "LOW",
- "subtask_prompt": "Add a `test_graphing_calculator.mjs` script in the cwd that checks the pure expression parsing/evaluation helpers and coordinate transform helpers from `index.html`. Use only Node built-ins such as `node:test`, `node:assert`, and `node:fs`. Run it with `node --test test_graphing_calculator.mjs` and include the output in your reply."
- },
- {
- "id": "docs",
- "depends_on": ["implement"],
- "complexity": "LOW",
- "subtask_prompt": "Write a short `README.md` in the cwd describing what the graphing calculator does, how to open `index.html`, supported math syntax with examples, viewport controls, and known limitations. Do not modify `index.html`."
- }
- ]
-}
diff --git a/.flatbread-efforts/blobs/blb-blob-cite-design-notes--j3jbgc0cymdb2t9g.md b/.flatbread-proof/blobs/blb-blob-cite-design-notes--j3jbgc0cymdb2t9g.md
similarity index 100%
rename from .flatbread-efforts/blobs/blb-blob-cite-design-notes--j3jbgc0cymdb2t9g.md
rename to .flatbread-proof/blobs/blb-blob-cite-design-notes--j3jbgc0cymdb2t9g.md
diff --git a/.flatbread-efforts/citations/cit-flatbread-refs-docs-intent--ew3x4qpx1x3c1cy2.md b/.flatbread-proof/citations/cit-flatbread-refs-docs-intent--ew3x4qpx1x3c1cy2.md
similarity index 100%
rename from .flatbread-efforts/citations/cit-flatbread-refs-docs-intent--ew3x4qpx1x3c1cy2.md
rename to .flatbread-proof/citations/cit-flatbread-refs-docs-intent--ew3x4qpx1x3c1cy2.md
diff --git a/.flatbread-efforts/citations/cit-local-blob-design-dump--pg7g6qy1td7yjqpy.md b/.flatbread-proof/citations/cit-local-blob-design-dump--pg7g6qy1td7yjqpy.md
similarity index 100%
rename from .flatbread-efforts/citations/cit-local-blob-design-dump--pg7g6qy1td7yjqpy.md
rename to .flatbread-proof/citations/cit-local-blob-design-dump--pg7g6qy1td7yjqpy.md
diff --git a/.flatbread-efforts/constraints/con-graphql-is-a-read-interface-not-the-product--4966nqjsnsmwq1fs.md b/.flatbread-proof/constraints/con-graphql-is-a-read-interface-not-the-product--4966nqjsnsmwq1fs.md
similarity index 100%
rename from .flatbread-efforts/constraints/con-graphql-is-a-read-interface-not-the-product--4966nqjsnsmwq1fs.md
rename to .flatbread-proof/constraints/con-graphql-is-a-read-interface-not-the-product--4966nqjsnsmwq1fs.md
diff --git a/.flatbread-efforts/constraints/con-mutation-enum-stays-deliberately-small--02k06bxbjwrjfp9x.md b/.flatbread-proof/constraints/con-mutation-enum-stays-deliberately-small--02k06bxbjwrjfp9x.md
similarity index 100%
rename from .flatbread-efforts/constraints/con-mutation-enum-stays-deliberately-small--02k06bxbjwrjfp9x.md
rename to .flatbread-proof/constraints/con-mutation-enum-stays-deliberately-small--02k06bxbjwrjfp9x.md
diff --git a/.flatbread-efforts/constraints/con-mutation-enum-stays-deliberately-small--45v1ae3neq26g1rz.md b/.flatbread-proof/constraints/con-mutation-enum-stays-deliberately-small--45v1ae3neq26g1rz.md
similarity index 100%
rename from .flatbread-efforts/constraints/con-mutation-enum-stays-deliberately-small--45v1ae3neq26g1rz.md
rename to .flatbread-proof/constraints/con-mutation-enum-stays-deliberately-small--45v1ae3neq26g1rz.md
diff --git a/.flatbread-efforts/constraints/con-prettier-remains-the-markdown-yaml-writer--wrawg3b0hp779hd8.md b/.flatbread-proof/constraints/con-prettier-remains-the-markdown-yaml-writer--wrawg3b0hp779hd8.md
similarity index 100%
rename from .flatbread-efforts/constraints/con-prettier-remains-the-markdown-yaml-writer--wrawg3b0hp779hd8.md
rename to .flatbread-proof/constraints/con-prettier-remains-the-markdown-yaml-writer--wrawg3b0hp779hd8.md
diff --git a/.flatbread-efforts/constraints/con-repo-files-are-the-source-of-truth--pnd7w2bs2zvk7ye9.md b/.flatbread-proof/constraints/con-repo-files-are-the-source-of-truth--pnd7w2bs2zvk7ye9.md
similarity index 100%
rename from .flatbread-efforts/constraints/con-repo-files-are-the-source-of-truth--pnd7w2bs2zvk7ye9.md
rename to .flatbread-proof/constraints/con-repo-files-are-the-source-of-truth--pnd7w2bs2zvk7ye9.md
diff --git a/.flatbread-efforts/decisions/dec-add-export-cli-and-unified-watch--k9dpxj57yrcv0pbz.md b/.flatbread-proof/decisions/dec-add-export-cli-and-unified-watch--k9dpxj57yrcv0pbz.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-add-export-cli-and-unified-watch--k9dpxj57yrcv0pbz.md
rename to .flatbread-proof/decisions/dec-add-export-cli-and-unified-watch--k9dpxj57yrcv0pbz.md
diff --git a/.flatbread-efforts/decisions/dec-adopt-a-bounded-status-briefing-fast-path-for-ef--kcw0rw39g3b2ym2h.md b/.flatbread-proof/decisions/dec-adopt-a-bounded-status-briefing-fast-path-for-ef--kcw0rw39g3b2ym2h.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-adopt-a-bounded-status-briefing-fast-path-for-ef--kcw0rw39g3b2ym2h.md
rename to .flatbread-proof/decisions/dec-adopt-a-bounded-status-briefing-fast-path-for-ef--kcw0rw39g3b2ym2h.md
diff --git a/.flatbread-efforts/decisions/dec-brand-the-agent-memory-surface-as-crumb-graph--fvskcvagx3a7sybe.md b/.flatbread-proof/decisions/dec-brand-the-agent-memory-surface-as-crumb-graph--fvskcvagx3a7sybe.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-brand-the-agent-memory-surface-as-crumb-graph--fvskcvagx3a7sybe.md
rename to .flatbread-proof/decisions/dec-brand-the-agent-memory-surface-as-crumb-graph--fvskcvagx3a7sybe.md
diff --git a/.flatbread-efforts/decisions/dec-brand-the-agent-memory-surface-as-crumb-trail--tngncepdbwjkh9jc.md b/.flatbread-proof/decisions/dec-brand-the-agent-memory-surface-as-crumb-trail--tngncepdbwjkh9jc.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-brand-the-agent-memory-surface-as-crumb-trail--tngncepdbwjkh9jc.md
rename to .flatbread-proof/decisions/dec-brand-the-agent-memory-surface-as-crumb-trail--tngncepdbwjkh9jc.md
diff --git a/.flatbread-efforts/decisions/dec-bridge-committed-generations-into-live-reads--mc3728t4w1kyqcqq.md b/.flatbread-proof/decisions/dec-bridge-committed-generations-into-live-reads--mc3728t4w1kyqcqq.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-bridge-committed-generations-into-live-reads--mc3728t4w1kyqcqq.md
rename to .flatbread-proof/decisions/dec-bridge-committed-generations-into-live-reads--mc3728t4w1kyqcqq.md
diff --git a/.flatbread-efforts/decisions/dec-canonical-forward-edges-and-journaled-save-or-un--sv9x93svkfz4a98r.md b/.flatbread-proof/decisions/dec-canonical-forward-edges-and-journaled-save-or-un--sv9x93svkfz4a98r.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-canonical-forward-edges-and-journaled-save-or-un--sv9x93svkfz4a98r.md
rename to .flatbread-proof/decisions/dec-canonical-forward-edges-and-journaled-save-or-un--sv9x93svkfz4a98r.md
diff --git a/.flatbread-efforts/decisions/dec-defer-shared-flatbread-write-extraction--xxa1ge2chw6x3xx0.md b/.flatbread-proof/decisions/dec-defer-shared-flatbread-write-extraction--xxa1ge2chw6x3xx0.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-defer-shared-flatbread-write-extraction--xxa1ge2chw6x3xx0.md
rename to .flatbread-proof/decisions/dec-defer-shared-flatbread-write-extraction--xxa1ge2chw6x3xx0.md
diff --git a/.flatbread-efforts/decisions/dec-distribute-the-effort-graph-as-a-versioned-agent--8as4sybr34zcqyqh.md b/.flatbread-proof/decisions/dec-distribute-the-effort-graph-as-a-versioned-agent--8as4sybr34zcqyqh.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-distribute-the-effort-graph-as-a-versioned-agent--8as4sybr34zcqyqh.md
rename to .flatbread-proof/decisions/dec-distribute-the-effort-graph-as-a-versioned-agent--8as4sybr34zcqyqh.md
diff --git a/.flatbread-efforts/decisions/dec-effort-get-digests-always-include-the-full-recor--xs7rnbha3zx8qdj9.md b/.flatbread-proof/decisions/dec-effort-get-digests-always-include-the-full-recor--xs7rnbha3zx8qdj9.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-effort-get-digests-always-include-the-full-recor--xs7rnbha3zx8qdj9.md
rename to .flatbread-proof/decisions/dec-effort-get-digests-always-include-the-full-recor--xs7rnbha3zx8qdj9.md
diff --git a/.flatbread-efforts/decisions/dec-iterate-typed-selection-and-nullability--0khw2zkc51hy230t.md b/.flatbread-proof/decisions/dec-iterate-typed-selection-and-nullability--0khw2zkc51hy230t.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-iterate-typed-selection-and-nullability--0khw2zkc51hy230t.md
rename to .flatbread-proof/decisions/dec-iterate-typed-selection-and-nullability--0khw2zkc51hy230t.md
diff --git a/.flatbread-efforts/decisions/dec-keep-and-extract-validation-api--e96yngmrjqcjdxcn.md b/.flatbread-proof/decisions/dec-keep-and-extract-validation-api--e96yngmrjqcjdxcn.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-keep-and-extract-validation-api--e96yngmrjqcjdxcn.md
rename to .flatbread-proof/decisions/dec-keep-and-extract-validation-api--e96yngmrjqcjdxcn.md
diff --git a/.flatbread-efforts/decisions/dec-keep-dags-acyclic-model-refinement-as-bounded-lo--9ptqq1ck3sax00fq.md b/.flatbread-proof/decisions/dec-keep-dags-acyclic-model-refinement-as-bounded-lo--9ptqq1ck3sax00fq.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-keep-dags-acyclic-model-refinement-as-bounded-lo--9ptqq1ck3sax00fq.md
rename to .flatbread-proof/decisions/dec-keep-dags-acyclic-model-refinement-as-bounded-lo--9ptqq1ck3sax00fq.md
diff --git a/.flatbread-efforts/decisions/dec-keep-effort-graph-as-the-product-name--r5wr2vdjwjs9bs13.md b/.flatbread-proof/decisions/dec-keep-effort-graph-as-the-product-name--r5wr2vdjwjs9bs13.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-keep-effort-graph-as-the-product-name--r5wr2vdjwjs9bs13.md
rename to .flatbread-proof/decisions/dec-keep-effort-graph-as-the-product-name--r5wr2vdjwjs9bs13.md
diff --git a/.flatbread-efforts/decisions/dec-keep-effort-graph-secondary-with-a-primary-wedge--estattvqnhffm2dc.md b/.flatbread-proof/decisions/dec-keep-effort-graph-secondary-with-a-primary-wedge--estattvqnhffm2dc.md
similarity index 86%
rename from .flatbread-efforts/decisions/dec-keep-effort-graph-secondary-with-a-primary-wedge--estattvqnhffm2dc.md
rename to .flatbread-proof/decisions/dec-keep-effort-graph-secondary-with-a-primary-wedge--estattvqnhffm2dc.md
index d5d165c7..3e0a7a20 100644
--- a/.flatbread-efforts/decisions/dec-keep-effort-graph-secondary-with-a-primary-wedge--estattvqnhffm2dc.md
+++ b/.flatbread-proof/decisions/dec-keep-effort-graph-secondary-with-a-primary-wedge--estattvqnhffm2dc.md
@@ -2,10 +2,12 @@
id: dec-keep-effort-graph-secondary-with-a-primary-wedge--estattvqnhffm2dc
effort: eff-effort-graph-memory-and-agent-wedge--szeqvmgqjqnhd002
title: Keep Effort Graph secondary with a primary-wedge gate
-state: accepted
+state: superseded
created_at: '2026-07-18T19:43:02.802Z'
derives_from:
- fnd-filtered-retrieval-is-94-percent-smaller--qpvz4vch5hygye20
+superseded_by:
+ - dec-lead-flatbread-1-0-with-effort-graph-agent-memor--nhxsqm8sd43s2avx
---
Keep the graph as a strong candidate secondary vertical rather than displacing
diff --git a/.flatbread-efforts/decisions/dec-keep-relation-first-content-layer--rc3pc0vczp6qhp3t.md b/.flatbread-proof/decisions/dec-keep-relation-first-content-layer--rc3pc0vczp6qhp3t.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-keep-relation-first-content-layer--rc3pc0vczp6qhp3t.md
rename to .flatbread-proof/decisions/dec-keep-relation-first-content-layer--rc3pc0vczp6qhp3t.md
diff --git a/.flatbread-proof/decisions/dec-lead-flatbread-1-0-with-effort-graph-agent-memor--nhxsqm8sd43s2avx.md b/.flatbread-proof/decisions/dec-lead-flatbread-1-0-with-effort-graph-agent-memor--nhxsqm8sd43s2avx.md
new file mode 100644
index 00000000..acd898ae
--- /dev/null
+++ b/.flatbread-proof/decisions/dec-lead-flatbread-1-0-with-effort-graph-agent-memor--nhxsqm8sd43s2avx.md
@@ -0,0 +1,21 @@
+---
+id: dec-lead-flatbread-1-0-with-effort-graph-agent-memor--nhxsqm8sd43s2avx
+effort: eff-effort-graph-memory-and-agent-wedge--szeqvmgqjqnhd002
+title: Lead Flatbread 1.0 with Effort Graph agent memory
+state: accepted
+created_at: '2026-07-29T10:43:48.897Z'
+supersedes:
+ - dec-keep-effort-graph-secondary-with-a-primary-wedge--estattvqnhffm2dc
+---
+
+## Context
+
+Flatbread 1.0 is the first stable npm release. The release brief asks the public docs and package copy to lead with the utility already shipped through Effort Graph, while keeping Flatbread useful for general static relational content.
+
+## Decision
+
+Lead Flatbread 1.0 positioning with Effort Graph as Git-native memory for coding agents. Present static relational content for sites, docs, and internal tools as a first-class general use. Keep GraphQL and generated TypeScript as read interfaces over the graph, not the product identity.
+
+## Consequences
+
+The root README, npm descriptions, positioning guide, and release notes use this hierarchy. The release does not add a hosted CMS, semantic search, general writes, or a new runtime contract. Proof remains workflow tooling rather than the memory product. Revisit this lead only when user evidence shows another use case explains Flatbread more clearly.
diff --git a/.flatbread-efforts/decisions/dec-make-the-effort-graph-the-planning-authority--gq4xegmc0hhbqjf7.md b/.flatbread-proof/decisions/dec-make-the-effort-graph-the-planning-authority--gq4xegmc0hhbqjf7.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-make-the-effort-graph-the-planning-authority--gq4xegmc0hhbqjf7.md
rename to .flatbread-proof/decisions/dec-make-the-effort-graph-the-planning-authority--gq4xegmc0hhbqjf7.md
diff --git a/.flatbread-efforts/decisions/dec-modernize-reachable-checks-before-replacing-tool--q7xpn1g7rk65xvzj.md b/.flatbread-proof/decisions/dec-modernize-reachable-checks-before-replacing-tool--q7xpn1g7rk65xvzj.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-modernize-reachable-checks-before-replacing-tool--q7xpn1g7rk65xvzj.md
rename to .flatbread-proof/decisions/dec-modernize-reachable-checks-before-replacing-tool--q7xpn1g7rk65xvzj.md
diff --git a/.flatbread-efforts/decisions/dec-name-citeable-longform-payloads-blob--9b11q14fgsx2ytve.md b/.flatbread-proof/decisions/dec-name-citeable-longform-payloads-blob--9b11q14fgsx2ytve.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-name-citeable-longform-payloads-blob--9b11q14fgsx2ytve.md
rename to .flatbread-proof/decisions/dec-name-citeable-longform-payloads-blob--9b11q14fgsx2ytve.md
diff --git a/.flatbread-efforts/decisions/dec-route-agent-reads-through-the-flatbread-query-en--476qb9qk878yfg62.md b/.flatbread-proof/decisions/dec-route-agent-reads-through-the-flatbread-query-en--476qb9qk878yfg62.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-route-agent-reads-through-the-flatbread-query-en--476qb9qk878yfg62.md
rename to .flatbread-proof/decisions/dec-route-agent-reads-through-the-flatbread-query-en--476qb9qk878yfg62.md
diff --git a/.flatbread-efforts/decisions/dec-separate-execution-and-display-planes--spm2ckxvdsch6h9m.md b/.flatbread-proof/decisions/dec-separate-execution-and-display-planes--spm2ckxvdsch6h9m.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-separate-execution-and-display-planes--spm2ckxvdsch6h9m.md
rename to .flatbread-proof/decisions/dec-separate-execution-and-display-planes--spm2ckxvdsch6h9m.md
diff --git a/.flatbread-efforts/decisions/dec-ship-citation-collection-with-optional-blob--fyga3x876n7rcnmn.md b/.flatbread-proof/decisions/dec-ship-citation-collection-with-optional-blob--fyga3x876n7rcnmn.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-ship-citation-collection-with-optional-blob--fyga3x876n7rcnmn.md
rename to .flatbread-proof/decisions/dec-ship-citation-collection-with-optional-blob--fyga3x876n7rcnmn.md
diff --git a/.flatbread-efforts/decisions/dec-specify-blockingdecisions-over-effort-scoped-edg--pxwaenyra0aa365j.md b/.flatbread-proof/decisions/dec-specify-blockingdecisions-over-effort-scoped-edg--pxwaenyra0aa365j.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-specify-blockingdecisions-over-effort-scoped-edg--pxwaenyra0aa365j.md
rename to .flatbread-proof/decisions/dec-specify-blockingdecisions-over-effort-scoped-edg--pxwaenyra0aa365j.md
diff --git a/.flatbread-efforts/decisions/dec-store-graph-artifacts-in-repo-by-default--xxpgwm9sv25j07z7.md b/.flatbread-proof/decisions/dec-store-graph-artifacts-in-repo-by-default--xxpgwm9sv25j07z7.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-store-graph-artifacts-in-repo-by-default--xxpgwm9sv25j07z7.md
rename to .flatbread-proof/decisions/dec-store-graph-artifacts-in-repo-by-default--xxpgwm9sv25j07z7.md
diff --git a/.flatbread-efforts/decisions/dec-use-isolated-schema-factories-without-a-cache--8bn823dg29yfvbzv.md b/.flatbread-proof/decisions/dec-use-isolated-schema-factories-without-a-cache--8bn823dg29yfvbzv.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-use-isolated-schema-factories-without-a-cache--8bn823dg29yfvbzv.md
rename to .flatbread-proof/decisions/dec-use-isolated-schema-factories-without-a-cache--8bn823dg29yfvbzv.md
diff --git a/.flatbread-efforts/decisions/dec-use-prefixed-random-ids-with-reviewable-slugs--xdg764ejat1kacd2.md b/.flatbread-proof/decisions/dec-use-prefixed-random-ids-with-reviewable-slugs--xdg764ejat1kacd2.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-use-prefixed-random-ids-with-reviewable-slugs--xdg764ejat1kacd2.md
rename to .flatbread-proof/decisions/dec-use-prefixed-random-ids-with-reviewable-slugs--xdg764ejat1kacd2.md
diff --git a/.flatbread-efforts/decisions/dec-use-profiles-not-separate-schemas--27p0wakkyxj0kfc1.md b/.flatbread-proof/decisions/dec-use-profiles-not-separate-schemas--27p0wakkyxj0kfc1.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-use-profiles-not-separate-schemas--27p0wakkyxj0kfc1.md
rename to .flatbread-proof/decisions/dec-use-profiles-not-separate-schemas--27p0wakkyxj0kfc1.md
diff --git a/.flatbread-efforts/decisions/dec-use-semantic-mutations-and-a-standalone-writer--2d0m3tkqhad4yyhr.md b/.flatbread-proof/decisions/dec-use-semantic-mutations-and-a-standalone-writer--2d0m3tkqhad4yyhr.md
similarity index 100%
rename from .flatbread-efforts/decisions/dec-use-semantic-mutations-and-a-standalone-writer--2d0m3tkqhad4yyhr.md
rename to .flatbread-proof/decisions/dec-use-semantic-mutations-and-a-standalone-writer--2d0m3tkqhad4yyhr.md
diff --git a/.flatbread-efforts/efforts/eff-effort-graph-memory-and-agent-wedge--szeqvmgqjqnhd002.md b/.flatbread-proof/efforts/eff-effort-graph-memory-and-agent-wedge--szeqvmgqjqnhd002.md
similarity index 100%
rename from .flatbread-efforts/efforts/eff-effort-graph-memory-and-agent-wedge--szeqvmgqjqnhd002.md
rename to .flatbread-proof/efforts/eff-effort-graph-memory-and-agent-wedge--szeqvmgqjqnhd002.md
diff --git a/.flatbread-efforts/efforts/eff-flatbread-product-branding--zt7b35sa05kyvhdz.md b/.flatbread-proof/efforts/eff-flatbread-product-branding--zt7b35sa05kyvhdz.md
similarity index 100%
rename from .flatbread-efforts/efforts/eff-flatbread-product-branding--zt7b35sa05kyvhdz.md
rename to .flatbread-proof/efforts/eff-flatbread-product-branding--zt7b35sa05kyvhdz.md
diff --git a/.flatbread-efforts/efforts/eff-local-runtime-and-ownership-loop--g28gfbb0kdrnpe2t.md b/.flatbread-proof/efforts/eff-local-runtime-and-ownership-loop--g28gfbb0kdrnpe2t.md
similarity index 100%
rename from .flatbread-efforts/efforts/eff-local-runtime-and-ownership-loop--g28gfbb0kdrnpe2t.md
rename to .flatbread-proof/efforts/eff-local-runtime-and-ownership-loop--g28gfbb0kdrnpe2t.md
diff --git a/.flatbread-efforts/efforts/eff-proof-and-contributor-operating-system--ahhgtafvdhg4dfve.md b/.flatbread-proof/efforts/eff-proof-and-contributor-operating-system--ahhgtafvdhg4dfve.md
similarity index 100%
rename from .flatbread-efforts/efforts/eff-proof-and-contributor-operating-system--ahhgtafvdhg4dfve.md
rename to .flatbread-proof/efforts/eff-proof-and-contributor-operating-system--ahhgtafvdhg4dfve.md
diff --git a/.flatbread-efforts/efforts/eff-relational-content-foundation--8a8332x4cazgf2k0.md b/.flatbread-proof/efforts/eff-relational-content-foundation--8a8332x4cazgf2k0.md
similarity index 100%
rename from .flatbread-efforts/efforts/eff-relational-content-foundation--8a8332x4cazgf2k0.md
rename to .flatbread-proof/efforts/eff-relational-content-foundation--8a8332x4cazgf2k0.md
diff --git a/.flatbread-efforts/findings/fnd-accepted-decisions-disagree-on-how-blobs-are-cit--z9s360sgahmtjz7h.md b/.flatbread-proof/findings/fnd-accepted-decisions-disagree-on-how-blobs-are-cit--z9s360sgahmtjz7h.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-accepted-decisions-disagree-on-how-blobs-are-cit--z9s360sgahmtjz7h.md
rename to .flatbread-proof/findings/fnd-accepted-decisions-disagree-on-how-blobs-are-cit--z9s360sgahmtjz7h.md
diff --git a/.flatbread-efforts/findings/fnd-adrs-created-a-second-planning-authority--j1waeg8qh900sqee.md b/.flatbread-proof/findings/fnd-adrs-created-a-second-planning-authority--j1waeg8qh900sqee.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-adrs-created-a-second-planning-authority--j1waeg8qh900sqee.md
rename to .flatbread-proof/findings/fnd-adrs-created-a-second-planning-authority--j1waeg8qh900sqee.md
diff --git a/.flatbread-efforts/findings/fnd-bounded-status-briefing-protocol-halves-effort-g--ht1jd1hsssf7mv2y.md b/.flatbread-proof/findings/fnd-bounded-status-briefing-protocol-halves-effort-g--ht1jd1hsssf7mv2y.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-bounded-status-briefing-protocol-halves-effort-g--ht1jd1hsssf7mv2y.md
rename to .flatbread-proof/findings/fnd-bounded-status-briefing-protocol-halves-effort-g--ht1jd1hsssf7mv2y.md
diff --git a/.flatbread-efforts/findings/fnd-citation-and-blob-links-now-stay-within-one-effo--gkyd4dczafebk4fz.md b/.flatbread-proof/findings/fnd-citation-and-blob-links-now-stay-within-one-effo--gkyd4dczafebk4fz.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-citation-and-blob-links-now-stay-within-one-effo--gkyd4dczafebk4fz.md
rename to .flatbread-proof/findings/fnd-citation-and-blob-links-now-stay-within-one-effo--gkyd4dczafebk4fz.md
diff --git a/.flatbread-efforts/findings/fnd-citation-and-blob-records-now-match-the-current--p6c37v29mx4jjfr2.md b/.flatbread-proof/findings/fnd-citation-and-blob-records-now-match-the-current--p6c37v29mx4jjfr2.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-citation-and-blob-records-now-match-the-current--p6c37v29mx4jjfr2.md
rename to .flatbread-proof/findings/fnd-citation-and-blob-records-now-match-the-current--p6c37v29mx4jjfr2.md
diff --git a/.flatbread-efforts/findings/fnd-citation-collection-keeps-flatbread-refs-intact--z33ar5vyjxzr7ys0.md b/.flatbread-proof/findings/fnd-citation-collection-keeps-flatbread-refs-intact--z33ar5vyjxzr7ys0.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-citation-collection-keeps-flatbread-refs-intact--z33ar5vyjxzr7ys0.md
rename to .flatbread-proof/findings/fnd-citation-collection-keeps-flatbread-refs-intact--z33ar5vyjxzr7ys0.md
diff --git a/.flatbread-efforts/findings/fnd-cites-and-citation-blob-skip-same-effort-checks--pts8c8ar72vaevvh.md b/.flatbread-proof/findings/fnd-cites-and-citation-blob-skip-same-effort-checks--pts8c8ar72vaevvh.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-cites-and-citation-blob-skip-same-effort-checks--pts8c8ar72vaevvh.md
rename to .flatbread-proof/findings/fnd-cites-and-citation-blob-skip-same-effort-checks--pts8c8ar72vaevvh.md
diff --git a/.flatbread-efforts/findings/fnd-crumb-rebrand-refuted-in-review-effort-graph-is--n8kb269z7vz8jhyk.md b/.flatbread-proof/findings/fnd-crumb-rebrand-refuted-in-review-effort-graph-is--n8kb269z7vz8jhyk.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-crumb-rebrand-refuted-in-review-effort-graph-is--n8kb269z7vz8jhyk.md
rename to .flatbread-proof/findings/fnd-crumb-rebrand-refuted-in-review-effort-graph-is--n8kb269z7vz8jhyk.md
diff --git a/.flatbread-efforts/findings/fnd-dogfooding-the-read-cli-surfaced-three-real-defe--g21hg77x72hdty56.md b/.flatbread-proof/findings/fnd-dogfooding-the-read-cli-surfaced-three-real-defe--g21hg77x72hdty56.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-dogfooding-the-read-cli-surfaced-three-real-defe--g21hg77x72hdty56.md
rename to .flatbread-proof/findings/fnd-dogfooding-the-read-cli-surfaced-three-real-defe--g21hg77x72hdty56.md
diff --git a/.flatbread-efforts/findings/fnd-exports-strengthen-ownership-but-lack-external-v--fx2qtjpcm8mtpw4f.md b/.flatbread-proof/findings/fnd-exports-strengthen-ownership-but-lack-external-v--fx2qtjpcm8mtpw4f.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-exports-strengthen-ownership-but-lack-external-v--fx2qtjpcm8mtpw4f.md
rename to .flatbread-proof/findings/fnd-exports-strengthen-ownership-but-lack-external-v--fx2qtjpcm8mtpw4f.md
diff --git a/.flatbread-efforts/findings/fnd-filtered-retrieval-is-94-percent-smaller--qpvz4vch5hygye20.md b/.flatbread-proof/findings/fnd-filtered-retrieval-is-94-percent-smaller--qpvz4vch5hygye20.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-filtered-retrieval-is-94-percent-smaller--qpvz4vch5hygye20.md
rename to .flatbread-proof/findings/fnd-filtered-retrieval-is-94-percent-smaller--qpvz4vch5hygye20.md
diff --git a/.flatbread-efforts/findings/fnd-generated-read-api-is-useful-but-not-fully-type--vmr23k0t6qx3hk1k.md b/.flatbread-proof/findings/fnd-generated-read-api-is-useful-but-not-fully-type--vmr23k0t6qx3hk1k.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-generated-read-api-is-useful-but-not-fully-type--vmr23k0t6qx3hk1k.md
rename to .flatbread-proof/findings/fnd-generated-read-api-is-useful-but-not-fully-type--vmr23k0t6qx3hk1k.md
diff --git a/.flatbread-efforts/findings/fnd-getrecord-digests-call-the-same-excerpt-as-brows--q5rbnhpxc697x2dv.md b/.flatbread-proof/findings/fnd-getrecord-digests-call-the-same-excerpt-as-brows--q5rbnhpxc697x2dv.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-getrecord-digests-call-the-same-excerpt-as-brows--q5rbnhpxc697x2dv.md
rename to .flatbread-proof/findings/fnd-getrecord-digests-call-the-same-excerpt-as-brows--q5rbnhpxc697x2dv.md
diff --git a/.flatbread-efforts/findings/fnd-one-canonical-schema-needs-mapping-profiles--k2yvq5e9rcpsm1s1.md b/.flatbread-proof/findings/fnd-one-canonical-schema-needs-mapping-profiles--k2yvq5e9rcpsm1s1.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-one-canonical-schema-needs-mapping-profiles--k2yvq5e9rcpsm1s1.md
rename to .flatbread-proof/findings/fnd-one-canonical-schema-needs-mapping-profiles--k2yvq5e9rcpsm1s1.md
diff --git a/.flatbread-efforts/findings/fnd-phase-1-has-no-blockers-but-retains-test-gaps--91h2ncmytj8mq40r.md b/.flatbread-proof/findings/fnd-phase-1-has-no-blockers-but-retains-test-gaps--91h2ncmytj8mq40r.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-phase-1-has-no-blockers-but-retains-test-gaps--91h2ncmytj8mq40r.md
rename to .flatbread-proof/findings/fnd-phase-1-has-no-blockers-but-retains-test-gaps--91h2ncmytj8mq40r.md
diff --git a/.flatbread-efforts/findings/fnd-pmf-rubric-understates-shipped-validation-and-wa--p04gd8xfknwvz2pe.md b/.flatbread-proof/findings/fnd-pmf-rubric-understates-shipped-validation-and-wa--p04gd8xfknwvz2pe.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-pmf-rubric-understates-shipped-validation-and-wa--p04gd8xfknwvz2pe.md
rename to .flatbread-proof/findings/fnd-pmf-rubric-understates-shipped-validation-and-wa--p04gd8xfknwvz2pe.md
diff --git a/.flatbread-efforts/findings/fnd-postable-brand-names-favor-trail-over-graph--zzpgaw0kvqtaqmxt.md b/.flatbread-proof/findings/fnd-postable-brand-names-favor-trail-over-graph--zzpgaw0kvqtaqmxt.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-postable-brand-names-favor-trail-over-graph--zzpgaw0kvqtaqmxt.md
rename to .flatbread-proof/findings/fnd-postable-brand-names-favor-trail-over-graph--zzpgaw0kvqtaqmxt.md
diff --git a/.flatbread-efforts/findings/fnd-primary-onboarding-still-bypasses-the-unified-wa--qqvckprax45hyd2y.md b/.flatbread-proof/findings/fnd-primary-onboarding-still-bypasses-the-unified-wa--qqvckprax45hyd2y.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-primary-onboarding-still-bypasses-the-unified-wa--qqvckprax45hyd2y.md
rename to .flatbread-proof/findings/fnd-primary-onboarding-still-bypasses-the-unified-wa--qqvckprax45hyd2y.md
diff --git a/.flatbread-efforts/findings/fnd-public-onboarding-now-directs-users-to-watch-mod--cwgqyjjvr6j9gx8n.md b/.flatbread-proof/findings/fnd-public-onboarding-now-directs-users-to-watch-mod--cwgqyjjvr6j9gx8n.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-public-onboarding-now-directs-users-to-watch-mod--cwgqyjjvr6j9gx8n.md
rename to .flatbread-proof/findings/fnd-public-onboarding-now-directs-users-to-watch-mod--cwgqyjjvr6j9gx8n.md
diff --git a/.flatbread-efforts/findings/fnd-public-readmes-retain-superseded-live-reload-gui--naafmpvt3pcdes4p.md b/.flatbread-proof/findings/fnd-public-readmes-retain-superseded-live-reload-gui--naafmpvt3pcdes4p.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-public-readmes-retain-superseded-live-reload-gui--naafmpvt3pcdes4p.md
rename to .flatbread-proof/findings/fnd-public-readmes-retain-superseded-live-reload-gui--naafmpvt3pcdes4p.md
diff --git a/.flatbread-efforts/findings/fnd-reference-integrity-is-roadmap-critical--2ss712xpmsfh77xf.md b/.flatbread-proof/findings/fnd-reference-integrity-is-roadmap-critical--2ss712xpmsfh77xf.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-reference-integrity-is-roadmap-critical--2ss712xpmsfh77xf.md
rename to .flatbread-proof/findings/fnd-reference-integrity-is-roadmap-critical--2ss712xpmsfh77xf.md
diff --git a/.flatbread-efforts/findings/fnd-relation-first-starter-reaches-a-typed-read--8anm4wxbjm9f348z.md b/.flatbread-proof/findings/fnd-relation-first-starter-reaches-a-typed-read--8anm4wxbjm9f348z.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-relation-first-starter-reaches-a-typed-read--8anm4wxbjm9f348z.md
rename to .flatbread-proof/findings/fnd-relation-first-starter-reaches-a-typed-read--8anm4wxbjm9f348z.md
diff --git a/.flatbread-efforts/findings/fnd-root-readme-roadmap-reference-is-already-removed--w56t43aa10v311hj.md b/.flatbread-proof/findings/fnd-root-readme-roadmap-reference-is-already-removed--w56t43aa10v311hj.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-root-readme-roadmap-reference-is-already-removed--w56t43aa10v311hj.md
rename to .flatbread-proof/findings/fnd-root-readme-roadmap-reference-is-already-removed--w56t43aa10v311hj.md
diff --git a/.flatbread-efforts/findings/fnd-root-readme-still-links-to-the-deleted-roadmap--k38zmv7pqv7z95ts.md b/.flatbread-proof/findings/fnd-root-readme-still-links-to-the-deleted-roadmap--k38zmv7pqv7z95ts.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-root-readme-still-links-to-the-deleted-roadmap--k38zmv7pqv7z95ts.md
rename to .flatbread-proof/findings/fnd-root-readme-still-links-to-the-deleted-roadmap--k38zmv7pqv7z95ts.md
diff --git a/.flatbread-efforts/findings/fnd-schemas-need-per-build-isolation--vdjfmtqb0dbjfcqk.md b/.flatbread-proof/findings/fnd-schemas-need-per-build-isolation--vdjfmtqb0dbjfcqk.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-schemas-need-per-build-isolation--vdjfmtqb0dbjfcqk.md
rename to .flatbread-proof/findings/fnd-schemas-need-per-build-isolation--vdjfmtqb0dbjfcqk.md
diff --git a/.flatbread-efforts/findings/fnd-skill-and-hard-constraint-still-teach-13-mutatio--gvg2btns0q7rp0eq.md b/.flatbread-proof/findings/fnd-skill-and-hard-constraint-still-teach-13-mutatio--gvg2btns0q7rp0eq.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-skill-and-hard-constraint-still-teach-13-mutatio--gvg2btns0q7rp0eq.md
rename to .flatbread-proof/findings/fnd-skill-and-hard-constraint-still-teach-13-mutatio--gvg2btns0q7rp0eq.md
diff --git a/.flatbread-efforts/findings/fnd-supersession-transitions-decision-state-on-the-r--2m807tcfjz2jz9gt.md b/.flatbread-proof/findings/fnd-supersession-transitions-decision-state-on-the-r--2m807tcfjz2jz9gt.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-supersession-transitions-decision-state-on-the-r--2m807tcfjz2jz9gt.md
rename to .flatbread-proof/findings/fnd-supersession-transitions-decision-state-on-the-r--2m807tcfjz2jz9gt.md
diff --git a/.flatbread-efforts/findings/fnd-unified-watch-loop-is-the-intended-runtime-contr--t9ghag8yqxgf3p5t.md b/.flatbread-proof/findings/fnd-unified-watch-loop-is-the-intended-runtime-contr--t9ghag8yqxgf3p5t.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-unified-watch-loop-is-the-intended-runtime-contr--t9ghag8yqxgf3p5t.md
rename to .flatbread-proof/findings/fnd-unified-watch-loop-is-the-intended-runtime-contr--t9ghag8yqxgf3p5t.md
diff --git a/.flatbread-efforts/findings/fnd-versioned-skill-distribution-passes-full-reposit--hm5cy9j2zts96dg4.md b/.flatbread-proof/findings/fnd-versioned-skill-distribution-passes-full-reposit--hm5cy9j2zts96dg4.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-versioned-skill-distribution-passes-full-reposit--hm5cy9j2zts96dg4.md
rename to .flatbread-proof/findings/fnd-versioned-skill-distribution-passes-full-reposit--hm5cy9j2zts96dg4.md
diff --git a/.flatbread-efforts/findings/fnd-workshop-shortlist-favors-crumb-graph-over-yeast--tv3ydt3wfb3n4y0g.md b/.flatbread-proof/findings/fnd-workshop-shortlist-favors-crumb-graph-over-yeast--tv3ydt3wfb3n4y0g.md
similarity index 100%
rename from .flatbread-efforts/findings/fnd-workshop-shortlist-favors-crumb-graph-over-yeast--tv3ydt3wfb3n4y0g.md
rename to .flatbread-proof/findings/fnd-workshop-shortlist-favors-crumb-graph-over-yeast--tv3ydt3wfb3n4y0g.md
diff --git a/.flatbread-efforts/issues/iss-agents-cannot-obtain-full-record-bodies-through--ekfpcg6hrkwgy287.md b/.flatbread-proof/issues/iss-agents-cannot-obtain-full-record-bodies-through--ekfpcg6hrkwgy287.md
similarity index 100%
rename from .flatbread-efforts/issues/iss-agents-cannot-obtain-full-record-bodies-through--ekfpcg6hrkwgy287.md
rename to .flatbread-proof/issues/iss-agents-cannot-obtain-full-record-bodies-through--ekfpcg6hrkwgy287.md
diff --git a/.flatbread-efforts/issues/iss-define-blocking-decision-semantics--3bj9ph7ppab2c7g2.md b/.flatbread-proof/issues/iss-define-blocking-decision-semantics--3bj9ph7ppab2c7g2.md
similarity index 100%
rename from .flatbread-efforts/issues/iss-define-blocking-decision-semantics--3bj9ph7ppab2c7g2.md
rename to .flatbread-proof/issues/iss-define-blocking-decision-semantics--3bj9ph7ppab2c7g2.md
diff --git a/.flatbread-efforts/issues/iss-implement-blob-collection-and-crumb-graph-cites--g2c7m6j39we5xy3z.md b/.flatbread-proof/issues/iss-implement-blob-collection-and-crumb-graph-cites--g2c7m6j39we5xy3z.md
similarity index 100%
rename from .flatbread-efforts/issues/iss-implement-blob-collection-and-crumb-graph-cites--g2c7m6j39we5xy3z.md
rename to .flatbread-proof/issues/iss-implement-blob-collection-and-crumb-graph-cites--g2c7m6j39we5xy3z.md
diff --git a/.flatbread-efforts/issues/iss-implement-crumb-graph-rename-across-packages-cli--dp6jvt2kafab7m4t.md b/.flatbread-proof/issues/iss-implement-crumb-graph-rename-across-packages-cli--dp6jvt2kafab7m4t.md
similarity index 100%
rename from .flatbread-efforts/issues/iss-implement-crumb-graph-rename-across-packages-cli--dp6jvt2kafab7m4t.md
rename to .flatbread-proof/issues/iss-implement-crumb-graph-rename-across-packages-cli--dp6jvt2kafab7m4t.md
diff --git a/.flatbread-efforts/issues/iss-implement-crumb-trail-rename-across-packages-cli--316rdzpt80sdxkw1.md b/.flatbread-proof/issues/iss-implement-crumb-trail-rename-across-packages-cli--316rdzpt80sdxkw1.md
similarity index 100%
rename from .flatbread-efforts/issues/iss-implement-crumb-trail-rename-across-packages-cli--316rdzpt80sdxkw1.md
rename to .flatbread-proof/issues/iss-implement-crumb-trail-rename-across-packages-cli--316rdzpt80sdxkw1.md
diff --git a/.flatbread-efforts/issues/iss-live-reload-and-export-cli-remain-gaps--xe19gzqf654xeyg4.md b/.flatbread-proof/issues/iss-live-reload-and-export-cli-remain-gaps--xe19gzqf654xeyg4.md
similarity index 100%
rename from .flatbread-efforts/issues/iss-live-reload-and-export-cli-remain-gaps--xe19gzqf654xeyg4.md
rename to .flatbread-proof/issues/iss-live-reload-and-export-cli-remain-gaps--xe19gzqf654xeyg4.md
diff --git a/.flatbread-efforts/issues/iss-proof-output-retention-follow-ups-remain-open--4fkjb93v3pey43y0.md b/.flatbread-proof/issues/iss-proof-output-retention-follow-ups-remain-open--4fkjb93v3pey43y0.md
similarity index 100%
rename from .flatbread-efforts/issues/iss-proof-output-retention-follow-ups-remain-open--4fkjb93v3pey43y0.md
rename to .flatbread-proof/issues/iss-proof-output-retention-follow-ups-remain-open--4fkjb93v3pey43y0.md
diff --git a/.flatbread-efforts/issues/iss-skill-scoped-records-filter-example-over-constra--vd0gcnpc9cm6jzjh.md b/.flatbread-proof/issues/iss-skill-scoped-records-filter-example-over-constra--vd0gcnpc9cm6jzjh.md
similarity index 100%
rename from .flatbread-efforts/issues/iss-skill-scoped-records-filter-example-over-constra--vd0gcnpc9cm6jzjh.md
rename to .flatbread-proof/issues/iss-skill-scoped-records-filter-example-over-constra--vd0gcnpc9cm6jzjh.md
diff --git a/.flatbread-efforts/issues/iss-typed-selection-builder-remains-a-product-gap--4zawc913n4gnyfcw.md b/.flatbread-proof/issues/iss-typed-selection-builder-remains-a-product-gap--4zawc913n4gnyfcw.md
similarity index 100%
rename from .flatbread-efforts/issues/iss-typed-selection-builder-remains-a-product-gap--4zawc913n4gnyfcw.md
rename to .flatbread-proof/issues/iss-typed-selection-builder-remains-a-product-gap--4zawc913n4gnyfcw.md
diff --git a/.flatbread-efforts/issues/iss-writedecision-with-supersedes-leaves-the-superse--by624gyf21ex42sv.md b/.flatbread-proof/issues/iss-writedecision-with-supersedes-leaves-the-superse--by624gyf21ex42sv.md
similarity index 100%
rename from .flatbread-efforts/issues/iss-writedecision-with-supersedes-leaves-the-superse--by624gyf21ex42sv.md
rename to .flatbread-proof/issues/iss-writedecision-with-supersedes-leaves-the-superse--by624gyf21ex42sv.md
diff --git a/.flatbread-efforts/risks/rsk-full-transcripts-can-overflow-prompts-and-leak-s--3p7ybk07jqe5w593.md b/.flatbread-proof/risks/rsk-full-transcripts-can-overflow-prompts-and-leak-s--3p7ybk07jqe5w593.md
similarity index 100%
rename from .flatbread-efforts/risks/rsk-full-transcripts-can-overflow-prompts-and-leak-s--3p7ybk07jqe5w593.md
rename to .flatbread-proof/risks/rsk-full-transcripts-can-overflow-prompts-and-leak-s--3p7ybk07jqe5w593.md
diff --git a/.gitignore b/.gitignore
index ec928b8e..0a5ba9ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,5 +16,5 @@ yarn-error.log
.pnpm-debug.log
**/.flatbread-efforts/.journal/
**/.flatbread/effort-graph/read-cache/
-# proof / local DAG artifacts
+# Local Proof DAG artifacts (external Proof tool may write here)
.flatbread/artifacts/
diff --git a/AGENTS.md b/AGENTS.md
index cdbbc9ff..7908798e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -45,9 +45,9 @@ See `CONTRIBUTING.md` for full details. Quick reference:
- **Lint**: `pnpm lint` (prettier)
- **Lint fix (after edits)**: `pnpm lint:fix:fast` (writes formatting repo-wide to match `pnpm lint`; staged-only: `pnpm lint:fix`, also runs via `.husky/pre-commit`)
- **Typecheck**: `pnpm typecheck`
-- **Test**: `pnpm test` (builds, then runs ava + vitest suites, including `@flatbread/proof` bounded-loop coverage). For the focused proof loop suite: `pnpm -F @flatbread/proof test`. Vitest packages use `pnpm -F @flatbread/utils exec vitest run` / `pnpm -F @flatbread/codegen exec vitest run` (`run` avoids watch mode).
+- **Test**: `pnpm test` (builds, then runs ava + vitest suites). Vitest packages use `pnpm -F @flatbread/utils exec vitest run` / `pnpm -F @flatbread/codegen exec vitest run` (`run` avoids watch mode).
- **Full verify**: `pnpm verify` (lint + typecheck + build + test)
-- **Proof loop contract**: explicit `DAG.loops[].reexecute.tasks` subsets must be dependency-closed, multiple loops must have disjoint re-execution sets, and `DAG.loops` must not be combined with `--converge-on`.
+- **Proof**: the DAG task runner now lives at https://github.com/FlatbreadLabs/proof.
- **Dev server**: `pnpm play` (GraphQL on port 5057, Next.js on port 3000). From `examples/nextjs`, prefer `pnpm exec flatbread start -- next dev --turbopack`. Use `flatbread start` — `flatbread dev` is not a CLI command.
### Mergify Stacks
@@ -60,7 +60,6 @@ The repo uses Mergify stacks for PR management. The `mergify-cli` is installed v
### Gotchas
-- **`@flatbread/proof` requires `CURSOR_RIPGREP_PATH`.** The proof package uses `@cursor/sdk` which expects a bundled ripgrep. In Cloud Agent VMs, set `export CURSOR_RIPGREP_PATH=/usr/bin/rg` to use the system ripgrep (included in the update script).
- **Native build scripts are approved in `pnpm-workspace.yaml`.** The `onlyBuiltDependencies` list allows esbuild, sharp, @swc/core, etc. to run their postinstall scripts automatically during `pnpm install`.
- **Vitest packages run in watch mode by default.** Always use `vitest run` (not bare `vitest`) to get a single run and exit.
- **`flatbread` CLI is not on PATH globally.** From `examples/nextjs`, prefer `pnpm exec flatbread …` (local binary), or `npx flatbread` from a shell. The `pnpm play` script from the root handles this automatically.
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..f1c71157
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,98 @@
+# Changelog
+
+Notes for the Flatbread release train. Some packages also keep their own
+changelog; this file covers the repository as a whole.
+
+## 1.0.0
+
+First stable release. The alpha train ended at `flatbread@1.0.0-alpha.22`.
+
+Twelve packages make up the 1.0 release set. They all ship at `1.0.0` and move
+in lockstep from here. One version number across the set, so you no longer have
+to work out which alpha of one package matches which alpha of another.
+
+Nine were already on npm as alphas and go to `1.0.0` from there:
+
+`flatbread`, `@flatbread/core`, `@flatbread/config`, `@flatbread/codegen`,
+`@flatbread/resolver-svimg`, `@flatbread/source-filesystem`,
+`@flatbread/transformer-markdown`, `@flatbread/transformer-yaml`, and
+`@flatbread/utils`.
+
+Three reach npm for the first time in this release:
+
+- `@flatbread/effort-graph` writes and reads agent memory records.
+- `@flatbread/explorer` serves the single-page app for browsing a content graph.
+- `@flatbread/proof` runs a task DAG of Cursor subagents.
+
+### Packaging
+
+- Every package declares `engines.node: ">=20.19"`. The old floors were
+ `^14.13.1 || >=16.0.0`, `>=18`, and in several packages nothing at all. Node
+ 18, and Node 20 before 20.19, are no longer supported.
+- `@flatbread/config` declares `@flatbread/core` as a runtime dependency. It
+ was a devDependency, which worked inside this monorepo and nowhere else.
+- `@flatbread/core` no longer emits type declarations a packed install cannot
+ resolve. Its `.d.ts` files reached into the private paths
+ `graphql/jsutils/Maybe` and `graphql/jsutils/ObjMap`; they now use public
+ GraphQL types, and `FlatbreadProvider.query()` declares its return as
+ GraphQL's public `ExecutionResult`. `vfile@5.3.4` moves from devDependencies
+ to dependencies, because the public types name `VFile`.
+- `@flatbread/codegen` widens its peer range on `@flatbread/config` and
+ `@flatbread/core` from `workspace:*` to `workspace:^`, so it publishes a
+ caret range instead of an exact pin.
+- `flatbread` drops five runtime dependencies that no code imported:
+ `apollo-server-core`, `apollo-server-express`, `express-graphql`,
+ `remark-github`, and `serialize-javascript`. `picomatch` moves to
+ devDependencies; only a test uses it. Versions move too: `@apollo/server` to
+ 5.5.1, the pinned `graphql` to 16.14.2, and `@flatbread/core`'s `lodash-es`
+ to 4.18.1.
+- `@flatbread/codegen` moves its four GraphQL Code Generator packages (`cli`,
+ `typescript`, `typescript-operations`, and `typed-document-node`) onto
+ supported major lines, and `@flatbread/config` moves its pinned `esbuild`
+ from 0.15.1 to 0.25.0. Nothing you get out changes: the same schema still
+ generates byte-identical files.
+- `@flatbread/proof` stops shipping `src` and `scripts`, tests and all, in its
+ npm tarball. The bins and `dist` still ship, which is all the package needs
+ to run.
+- `@flatbread/resolver-svimg` points its `repository`, `homepage`, and `bugs`
+ links at `FlatbreadLabs/flatbread`. They still named the old
+ `tonyketcham/flatbread` fork.
+- Every package carries a description that says what that package does.
+
+### Known dependency limits
+
+A clean install of all twelve packages reports seven `npm audit` entries that
+npm marks as having no fix available; the main `flatbread` install and its
+nine-package set report none, so the two packages below do not affect it.
+
+- `@flatbread/proof` inherits three moderate and high entries from
+ `@cursor/sdk`, which depends on `@connectrpc/connect-node@1.x` and through it
+ `undici@5.x`. No current or later `@cursor/sdk` release clears them, and the
+ later releases require Node 22.13 or newer.
+- `@flatbread/resolver-svimg` inherits four high entries from its required peer
+ `svimg`, which depends on `sharp`. The latest `svimg@4` still pins `sharp`
+ below the patched 0.35 line, and it changes the import API this resolver
+ uses.
+
+### Docs
+
+- The READMEs and `docs/positioning.md` now lead with the Effort Graph: an
+ agent's Efforts, Issues, Findings, Decisions, Constraints, Risks, Citations,
+ and Blobs written as Markdown records in your repository. The reasoning gets
+ committed and reviewed like the code.
+- Relational content for sites, docs, and internal tools stays a first-class
+ second path on the same engine, and GraphQL is described as one read
+ interface over the graph rather than as the product.
+- Removed the banner that called the whole project experimental. The narrower
+ qualifiers still hold: the generated TypeScript read API is a prototype, and
+ its selection-string escape hatch is experimental.
+- The `@flatbread/effort-graph` README no longer claims Git ignores the journal
+ for you. It names the default `.flatbread-efforts` root, lists the two
+ `.gitignore` lines you must add, and points at
+ `flatbread effort bootstrap --verify`, which exits nonzero when setup is
+ incomplete.
+
+There is no migration guide. Of the changes above, the Node floor is the one
+that can break an install. For anything else that moved since
+`1.0.0-alpha.22`, read the Git history; the alpha train did not keep
+per-release notes.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ca2c83f9..abc055ba 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -73,19 +73,19 @@ pnpm build
- Negative: invalid inputs, edge cases, and error handling/failure modes.
- Place tests in the relevant package and use its existing runner/config.
- Root `pnpm test` builds the workspace, runs the AVA suite configured by `ava.config.js`, then runs the package-local Vitest suites.
- - Bounded-loop coverage for `@flatbread/proof` is exercised by both `pnpm test` and `pnpm -F @flatbread/proof test`.
- - That focused proof suite is the quickest check for loop parser/runtime guards such as explicit rerun validation, overlapping-loop rejection, and convergence iteration accounting.
- Vitest is currently used by `@flatbread/codegen` and `@flatbread/utils`.
- - `@flatbread/proof` exposes a package-local AVA entrypoint for the loop schema suite; most other packages are covered by the root AVA suite or do not yet expose a package-local `test` script.
+ - Most other packages are covered by the root AVA suite or do not yet expose a package-local `test` script.
- `pnpm lint` is the enforced Prettier formatting gate. After editing, run `pnpm lint:fix:fast` so formatting matches CI (Cursor agents: see `.cursor/rules/post-edit-lint-fix.mdc`). On commit, `.husky/pre-commit` runs `pnpm lint:fix` (Pretty Quick on staged files). `pnpm lint:eslint` is an optional/manual root ESLint check until the linting stack is modernized.
- Helpful commands:
- Local CI parity: `pnpm verify`
- Root test suite: `pnpm test`
- - Proof bounded-loop suite: `pnpm -F @flatbread/proof test`
- Package-local test scripts where present: `pnpm -r --if-present test`
- Single package: `pnpm -F test`
- Watch (where supported): `pnpm -F test:watch`
+Proof (the DAG task runner for Cursor agents) now lives at
+https://github.com/FlatbreadLabs/proof.
+
## Releasing packages
There are two steps:
@@ -93,6 +93,20 @@ There are two steps:
1. Bump versions where there are changes
2. Publish the changed packages
+### One-time note for 1.0
+
+The 1.0 release put every published package on `1.0.0` at once, rather than
+letting each package carry its own alpha number. `pnpm bump` still works the
+same way: it preselects the packages that changed plus their workspace
+dependents. Only bump every package together again when a release calls for it.
+
+`packages/effort-graph/skills/effort-graph/release.json` records the version an
+end user installs. Edit that file, not the copy in `.agents/`. Then run
+`pnpm skills:sync` to refresh the `.agents/` copy and `pnpm skills:pack-check`,
+which fails unless `flatbreadVersion` and `effortGraphVersion` match the current
+`package.json` versions and `gitTag` equals `v`. `pnpm verify`
+runs both checks.
+
### 1) Bump versions only where there are changes
Use the interactive bump script:
@@ -189,7 +203,8 @@ Details:
deleted after publication.
End users install the skill from that release tag and install the matching
-`flatbread` version:
+`flatbread` version. Replace `X` with the released version — `1.0.0` for the
+first stable release, so the tag is `v1.0.0`:
```bash
npx skills add https://github.com/FlatbreadLabs/flatbread/tree/vX/packages/effort-graph/skills/effort-graph --skill effort-graph
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..c5424559
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) Flatbread contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/ava.config.js b/ava.config.js
index 531f2ef9..8d03af88 100644
--- a/ava.config.js
+++ b/ava.config.js
@@ -7,10 +7,10 @@ export default {
concurrency: 4,
files: [
'packages/**/*.test.(j|t)s',
+ 'scripts/**/*.test.(j|t)s',
// Codegen + utils use Vitest under src/__tests__. Keep those out of the
- // root AVA run, but allow AVA-owned proof coverage under the same folder
- // layout so `pnpm test` exercises the proof bounded-loop suite and its
- // parser/runtime guardrails.
+ // root AVA run so `pnpm test` only exercises AVA-owned package and root
+ // script coverage.
'!packages/codegen/src/__tests__/**',
'!packages/utils/src/__tests__/**',
// Explorer SPA uses Node's built-in test runner (see package scripts).
diff --git a/docs/positioning.md b/docs/positioning.md
index 2408eb25..c6f5e2f2 100644
--- a/docs/positioning.md
+++ b/docs/positioning.md
@@ -6,15 +6,38 @@ To compare Flatbread with databases, CMSs, and other file-based tools, see
[Comparing Flatbread with other tools](./pmf-decision-rubric.md). For keeping
and moving your data, see [data ownership](./data-ownership.md).
-Turn files in Git into typed, related content for your TypeScript app. A
-Flatbread project has collections, records, and `refs` that link records.
-Generated types and [GraphQL](https://graphql.org/) operations are common ways
-for an app to read that data; they do not define what Flatbread is.
+Flatbread turns files in Git into a typed relational graph. A project has
+collections, records, and `refs` that link records. Generated types and
+[GraphQL](https://graphql.org/) operations are common ways for an app to read
+that graph; they do not define what Flatbread is.
**Flatbread** reads content from your repository and file system. Plugins
control how it reads files and turns them into data.
-**Who it is for:** Teams building TypeScript sites, internal tools, and starter
+## The lead use case: memory for coding agents
+
+The [Effort Graph](../packages/effort-graph/README.md) is a Flatbread content
+model for what a coding agent works out along the way. An agent records an
+Effort and then writes Issues, Findings, Decisions, Constraints, Risks,
+Citations, and Blobs against it. Each record is a markdown file under
+`.flatbread-efforts/`, so it is committed, diffed, reviewed, and reverted like
+source. Writes go through `flatbread effort write`; reads come back as bounded
+digests from `flatbread effort list`, `records`, `relations`,
+`blocking-decisions`, and `get`.
+
+That solves a plain problem: an agent that closes its session forgets why it
+chose what it chose. Putting the reasoning in the repository keeps it next to
+the code it explains, and keeps it readable by a person.
+
+## The general case: relational content
+
+Everything above is one content model on a general engine. The same
+collections, `refs`, filters, and generated types back sites, docs, and
+internal tools. Posts point at authors; authors point at each other. Model your
+own collections and you get the same typed graph.
+
+**Who it is for:** People building coding agents that need memory a human can
+review in Git, and teams building TypeScript sites, internal tools, and starter
projects that want versioned, reviewable content and links between entries
without setting up a CMS database.
@@ -23,13 +46,15 @@ without setting up a CMS database.
- It is not a hosted CMS, dashboard, or writing UI.
- It is not a general-purpose GraphQL platform or database. Transactions,
detailed access control, and many concurrent writers are outside its scope.
-- [`flatbread start --watch`](./local-dev-loop.md) reloads valid content and
- config changes. Changes to Flatbread packages still need their own rebuild or
+- It does not reload its own packages.
+ [`flatbread start --watch`](./local-dev-loop.md) picks up valid content and
+ config changes, but a change to a Flatbread package needs a rebuild and a
restart.
-**GraphQL:** In the default setup, GraphQL reads data that Flatbread has already
-loaded (`schema → operations → codegen`). Start with files and configuration,
-then choose how your app reads the data. The
+**GraphQL:** GraphQL is one read interface over the graph. In the default setup
+it reads data that Flatbread has already loaded, following
+`schema → operations → codegen`. Start with files and configuration, then
+choose how your app reads the data. The
[Quickstart](../packages/flatbread/README.md#quickstart-posts-authors-and-tags)
shows posts, authors, and tags from files through generated types.
diff --git a/package.json b/package.json
index 54fb5e3a..3b00968d 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
"lint:fix": "pnpm lint:fix:prettier",
"lint:fix:fast": "prettier --write --plugin-search-dir=. .",
"lint:fix:prettier": "pretty-quick --staged",
- "typecheck": "pnpm --filter @flatbread/proof --filter @flatbread/explorer typecheck",
+ "typecheck": "pnpm --filter @flatbread/explorer typecheck",
"play": "cd examples/nextjs && pnpm dev",
"preplay:efforts": "pnpm --filter @flatbread/explorer build",
"play:efforts": "pnpm exec flatbread start --watch --open",
@@ -40,7 +40,7 @@
"test:explorer": "pnpm --filter @flatbread/explorer test",
"test": "pnpm build && pnpm test:ava && pnpm test:vitest && pnpm test:explorer",
"verify": "pnpm skills:check && pnpm skills:pack-check && pnpm lint && pnpm typecheck && pnpm build && pnpm test",
- "cursor:fetch-cloud-agent": "pnpm --filter @flatbread/proof exec node scripts/fetch-cloud-agent-conversation.mjs",
+ "cursor:fetch-cloud-agent": "node scripts/fetch-cloud-agent-conversation.mjs",
"dev:test": "ava --watch --verbose",
"prepare": "husky install"
},
@@ -58,7 +58,6 @@
"@flatbread/codegen": "workspace:*",
"@flatbread/config": "workspace:*",
"@flatbread/core": "workspace:*",
- "@flatbread/proof": "workspace:*",
"@flatbread/resolver-svimg": "workspace:*",
"@flatbread/source-filesystem": "workspace:*",
"@flatbread/transformer-markdown": "workspace:*",
@@ -66,6 +65,7 @@
},
"devDependencies": {
"@ava/typescript": "3.0.1",
+ "@cursor/sdk": "^1.0.9",
"@flatbread/effort-graph": "workspace:*",
"@nrwl/workspace": "14.4.3",
"@types/inquirer": "8.2.1",
diff --git a/packages/codegen/CHANGELOG.md b/packages/codegen/CHANGELOG.md
index 7c4ed882..54745bad 100644
--- a/packages/codegen/CHANGELOG.md
+++ b/packages/codegen/CHANGELOG.md
@@ -1,154 +1,49 @@
# @flatbread/codegen
-## 1.0.0-alpha.1
-
-### 🎉 Initial Release
-
-- **New package** for automatic TypeScript type generation from Flatbread GraphQL schemas
-- **GraphQL Code Generator integration** with intelligent caching and hashing
-- **CLI support** via `npx flatbread codegen` command
-- **Comprehensive configuration options** including plugin customization
-- **Framework-agnostic design** supporting React, Vue, Angular, Next.js, SvelteKit, and more
-
-### ✨ Features
-
-- **5 Codegen Strategies Analyzed**: Evaluated GraphQL Code Generator, gql.tada, GraphQL Request + React Query, Shopify's graphql-codegen, and urql + codegen
-- **Intelligent Caching**: Avoid regeneration when Flatbread config and schema haven't changed
-- **Hash-based Change Detection**: Uses SHA256 hashing of config, schema, and documents
-- **Plugin System**: Full support for GraphQL Code Generator's extensive plugin ecosystem
-- **Type Safety**: Generated TypeScript types for queries, mutations, and subscriptions
-- **CLI Integration**: Seamless integration with existing `flatbread` CLI
-- **Watch Mode**: Automatic regeneration on file changes
-- **Cache Management**: Manual cache clearing and invalidation options
-
-### 🏗️ Architecture
-
-- **Modular Design**: Separate modules for hashing, caching, generation, and CLI
-- **Peer Dependencies**: Lightweight integration with existing Flatbread packages
-- **TypeScript-first**: Full TypeScript support with comprehensive type definitions
-- **Testing**: Comprehensive test suite with vitest
-
-### 📦 API
-
-#### Configuration
-
-```ts
-interface CodegenOptions {
- enabled?: boolean;
- outputDir?: string;
- outputFile?: string;
- plugins?: string[];
- codegenConfig?: Partial;
- pluginConfig?: Record>;
- watch?: boolean;
- cache?: boolean;
- documents?: string[];
- schema?: {
- includeIntrospection?: boolean;
- includeDeprecated?: boolean;
- };
-}
-```
-
-#### Core Functions
-
-- `generateTypes()` - Generate TypeScript types from GraphQL schema
-- `generateTypesWithDocuments()` - Include GraphQL documents in generation
-- `watchAndGenerate()` - Watch mode for automatic regeneration
-- `hashCodegenInputs()` - Generate hash for change detection
-- `loadCache()` / `saveCache()` - Cache management
-
-#### CLI Commands
-
-```bash
-npx flatbread codegen # Generate types
-npx flatbread codegen --watch # Watch mode
-npx flatbread codegen --clear-cache # Force regeneration
-npx flatbread codegen --verbose # Detailed logging
-```
+## 1.0.0
-### 🎯 Default Configuration
+First stable release, published in lockstep with the rest of Flatbread at
+`1.0.0`.
-- **Plugins**: `['typescript', 'typescript-operations', 'typed-document-node']`
-- **Output**: `./src/generated/graphql.ts`
-- **Caching**: Enabled by default
-- **Schema Options**: Include deprecated fields, exclude introspection
+- Requires Node 20.19 or newer (`engines.node: ">=20.19"`).
+- The peer range on `@flatbread/config` and `@flatbread/core` moves from
+ `workspace:*` to `workspace:^`, so the published package asks for a caret
+ range instead of an exact version.
+- Besides GraphQL schema and operation types, the generated file now contains a
+ prototype TypeScript read API, `createFlatbreadReadApi()`, built from your
+ configured collections, fields, and refs. It runs its reads through the
+ GraphQL layer, and its selection-string escape hatch is experimental.
-### 🤝 Framework Support
+Current defaults:
-- **React**: Apollo Client, urql, React Query
-- **Vue**: Vue Apollo, urql
-- **Angular**: Apollo Angular
-- **Next.js**: SSG/SSR with GraphQL Request
-- **SvelteKit**: Load functions with GraphQL Request
-- **Node.js**: Server-side GraphQL clients
+- Output: `./generated/graphql.ts`
+- Plugins: `typescript`, `typescript-operations`, `typed-document-node`, or
+ pick a `preset` of `basic`, `operations`, or `full`
+- Caching: on
+- Schema: deprecated fields included, introspection excluded
-### 📚 Documentation
-
-- **Comprehensive README** with usage examples and configuration options
-- **Comparative Analysis** of 5 different GraphQL codegen approaches
-- **Framework Integration** guides for popular frontend frameworks
-- **Troubleshooting** section with common issues and solutions
-
-### 🧪 Testing
-
-- **Hash Functions**: Comprehensive tests for configuration and schema hashing
-- **Type Definitions**: Tests for default options and type safety
-- **Vitest Integration**: Modern testing framework with TypeScript support
-
-### 🔄 Dependencies
-
-- **@graphql-codegen/cli**: ^5.0.0
-- **@graphql-codegen/typescript**: ^4.1.6
-- **@graphql-codegen/typescript-operations**: ^4.2.0
-- **@graphql-codegen/typed-document-node**: ^2.3.0
-- **@graphql-typed-document-node/core**: ^3.1.0
-- **fs-extra**: ^11.1.0
-- **kleur**: ^4.1.5
-
-### 🎨 Examples
-
-- **Next.js Configuration**: Complete example with codegen configuration
-- **GraphQL Documents**: Sample queries and mutations for type generation
-- **Framework Integration**: Examples for multiple frontend frameworks
-
-### ⚡ Performance
-
-- **Zero Runtime Overhead**: Generated types are compile-time only
-- **Intelligent Caching**: Skip regeneration when nothing has changed
-- **Fast Hashing**: SHA256-based change detection
-- **Incremental Builds**: Only regenerate when necessary
-
-### 🏃♂️ Getting Started
-
-1. Add to your `flatbread.config.ts`:
-
-```ts
-export default defineConfig({
- // ... your existing config
- codegen: {
- enabled: true,
- outputDir: './src/generated',
- outputFile: 'graphql.ts',
- },
-});
-```
-
-2. Generate types:
+CLI:
```bash
-npx flatbread codegen
+flatbread codegen # generate once
+flatbread codegen --watch # regenerate when files change
+flatbread codegen --clear-cache # force a full regeneration
+flatbread codegen --verbose # log what it is doing
```
-3. Use in your app:
-
-```ts
-import type { Post, GetPostsQuery } from './generated/graphql';
-```
+Versions between `1.0.0-alpha.1` and `1.0.0` were part of the alpha train and
+have no separate notes.
-### 🔮 Future Plans
+## 1.0.0-alpha.1
-- **gql.tada Integration**: Support for compile-time type inference
-- **Custom Strategies**: Plugin system for alternative codegen approaches
-- **IDE Integration**: VS Code extension for seamless development experience
-- **Performance Monitoring**: Built-in performance metrics and optimization suggestions
+Initial release.
+
+- Generates TypeScript from a Flatbread GraphQL schema with
+ [GraphQL Code Generator](https://www.the-guild.dev/graphql/codegen).
+- Adds the `flatbread codegen` command, including `--watch`, `--clear-cache`,
+ and `--verbose`.
+- Skips regeneration when nothing changed, comparing SHA256 hashes of the
+ config, schema, and documents.
+- Exposes `generateTypes()`, `generateTypesWithDocuments()`,
+ `watchAndGenerate()`, `hashCodegenInputs()`, `loadCache()`, and `saveCache()`.
+- Reads its settings from the `codegen` key in `flatbread.config.*`.
diff --git a/packages/codegen/package.json b/packages/codegen/package.json
index 40c94450..76f11c1a 100644
--- a/packages/codegen/package.json
+++ b/packages/codegen/package.json
@@ -1,7 +1,7 @@
{
"name": "@flatbread/codegen",
- "version": "1.0.0-alpha.2",
- "description": "Automatic TypeScript type generation for Flatbread GraphQL schemas",
+ "version": "1.0.0",
+ "description": "Generates TypeScript types, typed document nodes, and a prototype read API from a Flatbread content model.",
"type": "module",
"scripts": {
"build": "tsup",
@@ -29,16 +29,19 @@
"dist",
"*.d.ts"
],
+ "engines": {
+ "node": ">=20.19"
+ },
"dependencies": {
"@flatbread/utils": "workspace:*",
- "@graphql-codegen/cli": "^5.0.7",
- "@graphql-codegen/typed-document-node": "^2.3.13",
- "@graphql-codegen/typescript": "^4.1.6",
- "@graphql-codegen/typescript-operations": "^4.6.1",
+ "@graphql-codegen/cli": "^6.3.1",
+ "@graphql-codegen/typed-document-node": "^6.1.8",
+ "@graphql-codegen/typescript": "^5.0.10",
+ "@graphql-codegen/typescript-operations": "^5.1.0",
"@graphql-typed-document-node/core": "^3.2.0",
"chokidar": "^3.6.0",
"fs-extra": "^11.3.1",
- "graphql": "16.5.0",
+ "graphql": "16.14.2",
"kleur": "^4.1.5"
},
"devDependencies": {
@@ -50,8 +53,8 @@
"vitest": "^4.1.5"
},
"peerDependencies": {
- "@flatbread/config": "workspace:*",
- "@flatbread/core": "workspace:*",
+ "@flatbread/config": "workspace:^",
+ "@flatbread/core": "workspace:^",
"@graphql-typed-document-node/core": "^3.1.0"
}
}
diff --git a/packages/config/package.json b/packages/config/package.json
index 9a3452f6..34c97921 100644
--- a/packages/config/package.json
+++ b/packages/config/package.json
@@ -1,7 +1,7 @@
{
"name": "@flatbread/config",
- "version": "1.0.0-alpha.9",
- "description": "Flatbread's user config processing",
+ "version": "1.0.0",
+ "description": "Loads, validates, and types flatbread.config.* files. Provides defineConfig().",
"type": "module",
"publishConfig": {
"main": "dist/index.js",
@@ -35,11 +35,14 @@
"dist",
"*.d.ts"
],
+ "engines": {
+ "node": ">=20.19"
+ },
"dependencies": {
- "esbuild": "0.15.1"
+ "@flatbread/core": "workspace:*",
+ "esbuild": "0.25.0"
},
"devDependencies": {
- "@flatbread/core": "workspace:*",
"@types/node": "16.11.47",
"tsup": "6.2.1",
"typescript": "4.7.4"
diff --git a/packages/core/package.json b/packages/core/package.json
index 3833197f..61431888 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,7 +1,7 @@
{
"name": "@flatbread/core",
- "version": "1.0.0-alpha.16",
- "description": "Flatbread's essential logic",
+ "version": "1.0.0",
+ "description": "Flatbread's engine: runs source and transformer plugins, validates records and refs, and builds the GraphQL schema plus JSON and CSV snapshots.",
"type": "module",
"scripts": {
"build": "tsup",
@@ -28,18 +28,21 @@
"dist",
"*.d.ts"
],
+ "engines": {
+ "node": ">=20.19"
+ },
"dependencies": {
- "graphql": "16.5.0",
+ "graphql": "16.14.2",
"graphql-compose": "9.0.8",
- "lodash-es": "4.17.21",
+ "lodash-es": "4.18.1",
"matcher": "5.0.0",
- "plur": "5.1.0"
+ "plur": "5.1.0",
+ "vfile": "5.3.4"
},
"devDependencies": {
"@types/lodash-es": "4.17.6",
"@types/node": "16.11.47",
"tsup": "6.2.1",
- "typescript": "4.7.4",
- "vfile": "5.3.4"
+ "typescript": "4.7.4"
}
}
diff --git a/packages/core/src/providers/base.ts b/packages/core/src/providers/base.ts
index a37cbce1..f4514235 100644
--- a/packages/core/src/providers/base.ts
+++ b/packages/core/src/providers/base.ts
@@ -2,7 +2,8 @@ import { generateSchema } from '../generators/schema';
import { FlatbreadConfig } from '../types';
import { initializeConfig } from '../utils/initializeConfig';
-import { graphql, GraphQLArgs, GraphQLSchema } from 'graphql';
+import { graphql } from 'graphql';
+import type { ExecutionResult, GraphQLArgs, GraphQLSchema } from 'graphql';
/**
* **Flatbread Provider**
@@ -23,7 +24,7 @@ export class FlatbreadProvider {
* @param args GraphQLArgs needed for executing a query. Typically, this is just a standard GraphQL query.
* @returns GraphQL response
*/
- async query(args: Omit) {
+ async query(args: Omit): Promise {
const schema = await this.schemaPromise;
return await graphql({ schema, ...args });
}
diff --git a/packages/core/src/types.test.ts b/packages/core/src/types.test.ts
index 00aef624..40d37bae 100644
--- a/packages/core/src/types.test.ts
+++ b/packages/core/src/types.test.ts
@@ -1,4 +1,5 @@
import test from 'ava';
+import { readFileSync } from 'node:fs';
import type {
Content,
ContentEntry,
@@ -37,6 +38,9 @@ type ContentNodeIdUsesIdentifierField = Assert<
type OverrideResolveReturnsUnknown = Assert<
Equal, unknown>
>;
+type OverrideDescriptionMatchesGraphQL = Assert<
+ Equal
+>;
test('core public content types expose narrowed relation surfaces', (t) => {
const entry: ContentEntry = {
@@ -63,9 +67,31 @@ test('core public content types expose narrowed relation surfaces', (t) => {
t.is(unsafeString, 'value');
});
+test('core declarations use only the public GraphQL type surface', (t) => {
+ const declarations = readFileSync(
+ new URL('../dist/index.d.ts', import.meta.url),
+ 'utf8'
+ );
+
+ t.false(declarations.includes('graphql/jsutils/'));
+});
+
+test('core publishes dependencies used by its declarations', (t) => {
+ const manifest = JSON.parse(
+ readFileSync(new URL('../package.json', import.meta.url), 'utf8')
+ ) as {
+ dependencies?: Record;
+ devDependencies?: Record;
+ };
+
+ t.is(manifest.dependencies?.vfile, '5.3.4');
+ t.false('vfile' in (manifest.devDependencies ?? {}));
+});
+
void (0 as unknown as ContentEntryRefsAreTyped);
void (0 as unknown as ContentNodeKeepsUnknownFields);
void (0 as unknown as SourceFetchUsesContent);
void (0 as unknown as SourceFetchByTypeReturnsVFiles);
void (0 as unknown as ContentNodeIdUsesIdentifierField);
void (0 as unknown as OverrideResolveReturnsUnknown);
+void (0 as unknown as OverrideDescriptionMatchesGraphQL);
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index 15f60f79..292d60c1 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -1,9 +1,9 @@
-import {
+import type {
GraphQLFieldConfigArgumentMap,
+ GraphQLFieldConfig,
GraphQLInputType,
GraphQLSchema,
} from 'graphql';
-import { Maybe } from 'graphql/jsutils/Maybe';
import type { VFile } from 'vfile';
// Import CodegenOptions type from the codegen package
@@ -195,7 +195,7 @@ export interface Override {
field: string;
type: GraphQLInputType | string;
args?: GraphQLFieldConfigArgumentMap;
- description?: Maybe;
+ description?: GraphQLFieldConfig['description'];
resolve: (
data: unknown,
extended: {
diff --git a/packages/effort-graph/README.md b/packages/effort-graph/README.md
deleted file mode 100644
index 53dacac6..00000000
--- a/packages/effort-graph/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# `@flatbread/effort-graph`
-
-This package writes Flatbread Effort Graph records to Markdown files in your
-repository. It uses a journal so a change that affects several files either
-finishes completely or is undone.
-
-Version 1 supports these actions: `CreateEffort`, `SetEffortStatus`,
-`WriteIssue`, `WriteFinding`, `WriteDecision`, `WriteConstraint`, `WriteRisk`,
-`WriteCitation`, `WriteBlob`, `Supersede`, `Invalidate`, `ResolveIssue`,
-`AcceptDecision`, `MitigateRisk`, and `SetRiskState`.
-
-The journal is stored in `/.journal` and is ignored by Git. Use
-`effortGraphContent()` to add Efforts, Issues, Findings, Decisions,
-Constraints, Risks, Citations, and Blobs to a Flatbread configuration. The
-writer checks links between those record types. Epistemic records may
-optionally `cites` Citation ids (Flatbread `refs`). A Citation body alone is
-valid (e.g. a URL); an optional `blob` ref attaches a longform payload.
-
-Read [`skills/effort-graph/glossary.md`](./skills/effort-graph/glossary.md) for
-the portable Effort Graph domain model.
-
-The packaged Agent Skill is in `skills/effort-graph/`. The repository copy in
-`.agents/skills/effort-graph/` is generated from those files. Run
-`pnpm skills:sync` from the repository root after changing the skill.
-
-## Install the Effort Graph skill
-
-Install from a release tag, then activate the skill for setup:
-
-```bash
-npx skills add https://github.com/FlatbreadLabs/flatbread/tree/vX/packages/effort-graph/skills/effort-graph --skill effort-graph
-npm install --save-dev flatbread@X
-```
-
-Replace the placeholders with `gitTag` and `flatbreadVersion` from
-`skills/effort-graph/release.json`. See `skills/effort-graph/setup.md` for
-equivalent `pnpm`, `yarn`, and `bun` commands.
diff --git a/packages/effort-graph/package.json b/packages/effort-graph/package.json
deleted file mode 100644
index 3fcac788..00000000
--- a/packages/effort-graph/package.json
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- "name": "@flatbread/effort-graph",
- "version": "0.1.0-alpha.0",
- "description": "Standalone semantic writer for the Flatbread Effort Graph — journaled multi-file mutations over epistemic markdown artifacts.",
- "type": "module",
- "scripts": {
- "build": "tsup",
- "dev": "node scripts/watch-skills.mjs",
- "skills:sync": "node scripts/sync-skills.mjs",
- "skills:check": "node scripts/sync-skills.mjs --check",
- "skills:pack-check": "node scripts/pack-skills.mjs",
- "test": "pnpm --dir ../.. exec ava \"packages/effort-graph/src/__tests__/**/*.test.{js,ts}\"",
- "typecheck": "tsc -p tsconfig.json --noEmit"
- },
- "repository": {
- "type": "git",
- "url": "git+https://github.com/FlatbreadLabs/flatbread.git",
- "directory": "packages/effort-graph"
- },
- "homepage": "https://github.com/FlatbreadLabs/flatbread#readme",
- "author": "Tony Ketcham ",
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/FlatbreadLabs/flatbread/issues"
- },
- "exports": {
- ".": "./dist/index.js"
- },
- "main": "dist/index.js",
- "module": "dist/index.js",
- "types": "dist/index.d.ts",
- "files": [
- "dist",
- "skills",
- "*.d.ts"
- ],
- "dependencies": {
- "gray-matter": "^4.0.3",
- "zod": "^3.24.1"
- },
- "devDependencies": {
- "@flatbread/core": "workspace:*",
- "@flatbread/transformer-markdown": "workspace:*",
- "@types/node": "25.6.2",
- "tsup": "8.5.1",
- "typescript": "6.0.3"
- }
-}
diff --git a/packages/effort-graph/skills/effort-graph/release.json b/packages/effort-graph/skills/effort-graph/release.json
deleted file mode 100644
index a0d4235f..00000000
--- a/packages/effort-graph/skills/effort-graph/release.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "format": 1,
- "flatbreadVersion": "1.0.0-alpha.22",
- "effortGraphVersion": "0.1.0-alpha.0",
- "gitTag": "v1.0.0-alpha.22"
-}
diff --git a/packages/effort-graph/src/index.ts b/packages/effort-graph/src/index.ts
deleted file mode 100644
index 9e97bced..00000000
--- a/packages/effort-graph/src/index.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-export * from './types.js';
-export * from './schemas.js';
-export * from './ids.js';
-export * from './frontmatter.js';
-export * from './index-store.js';
-export * from './writer.js';
-export * from './preset.js';
-export * from './errors.js';
-export * from './snapshot.js';
-export * from './decision-lifecycle.js';
-export { acquireWriterLock } from './lock.js';
-export { recoverJournal } from './journal.js';
-export * from './live.js';
-export * from './journalBarrier.js';
-export * from './digest.js';
-export {
- READ_RELATIONS,
- EffortGraphConsistencyError,
- EffortGraphInvalidCursorError,
- EffortGraphReadValidationError,
- canonicalizeReadQuery,
- parseGenerationToken,
- pruneReadCache,
- readQueryHash,
-} from './read.js';
-export type {
- ConsistencyErrorShape,
- EffortStatus,
- ReadOptions,
- ReadQuery,
-} from './read.js';
diff --git a/packages/effort-graph/tsconfig.json b/packages/effort-graph/tsconfig.json
deleted file mode 100644
index ad0af6d7..00000000
--- a/packages/effort-graph/tsconfig.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "baseUrl": "../..",
- "ignoreDeprecations": "6.0",
- "module": "ESNext",
- "moduleResolution": "Bundler",
- "types": ["node"]
- },
- "include": ["src/**/*.ts", "tsup.config.ts"],
- "exclude": ["dist", "node_modules"]
-}
diff --git a/packages/effort-graph/tsup.config.ts b/packages/effort-graph/tsup.config.ts
deleted file mode 100644
index b2df2e08..00000000
--- a/packages/effort-graph/tsup.config.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { defineConfig } from 'tsup';
-
-export default defineConfig({
- entry: ['src/index.ts'],
- format: ['esm'],
- dts: true,
- clean: true,
- sourcemap: true,
- target: 'node18',
- external: ['@flatbread/core'],
-});
diff --git a/packages/explorer/package.json b/packages/explorer/package.json
index 30aed741..666d6685 100644
--- a/packages/explorer/package.json
+++ b/packages/explorer/package.json
@@ -1,7 +1,7 @@
{
"name": "@flatbread/explorer",
- "version": "0.1.0-alpha.0",
- "description": "Content-relation explorer SPA for Flatbread — generic graph shell with Effort Graph as the first preset.",
+ "version": "1.0.0",
+ "description": "Single-page app for browsing a Flatbread content graph, served by flatbread start. Ships an Effort Graph preset.",
"type": "module",
"scripts": {
"build": "pnpm build:node && pnpm build:web",
@@ -9,7 +9,7 @@
"build:web": "vite build",
"dev": "vite",
"typecheck": "tsc -p tsconfig.json --noEmit",
- "test": "node --import tsx --test src/node/matchPreset.test.ts src/node/staticDir.test.ts src/web/core/endpoints.test.ts src/web/core/physics/forces.test.ts src/web/core/physics/simulation.test.ts src/web/presets/effort-graph/glyphs.test.ts src/web/presets/effort-graph/lifecycle.test.ts src/web/presets/effort-graph/normalize.test.ts src/web/presets/effort-graph/query.test.ts src/web/presets/effort-graph/useEffortGraphLive.test.ts"
+ "test": "node --import tsx --test src/node/matchPreset.test.ts src/node/staticDir.test.ts src/web/core/endpoints.test.ts src/web/core/physics/forces.test.ts src/web/core/physics/simulation.test.ts src/web/presets/proof/glyphs.test.ts src/web/presets/proof/lifecycle.test.ts src/web/presets/proof/normalize.test.ts src/web/presets/proof/query.test.ts src/web/presets/proof/useProofLive.test.ts"
},
"repository": {
"type": "git",
@@ -32,6 +32,9 @@
"dist",
"*.d.ts"
],
+ "engines": {
+ "node": ">=20.19"
+ },
"dependencies": {
"@flatbread/effort-graph": "workspace:*"
},
diff --git a/packages/explorer/src/web/app/components/DetailDrawer.tsx b/packages/explorer/src/web/app/components/DetailDrawer.tsx
index 31f2e801..e79bf2c4 100644
--- a/packages/explorer/src/web/app/components/DetailDrawer.tsx
+++ b/packages/explorer/src/web/app/components/DetailDrawer.tsx
@@ -1,11 +1,11 @@
import { useEffect, useMemo, useRef } from 'react';
import { oklchCss, effortColor, retiredOklch } from '@/core/oklch';
-import { PRIMITIVES, primitiveOklch } from '@/presets/effort-graph/primitives';
+import { PRIMITIVES, primitiveOklch } from '@/presets/proof/primitives';
import {
effectiveLifecycle,
type LifecycleIndex,
-} from '@/presets/effort-graph/lifecycle';
-import type { GraphEdge, GraphNode } from '@/presets/effort-graph/types';
+} from '@/presets/proof/lifecycle';
+import type { GraphEdge, GraphNode } from '@/presets/proof/types';
import { useTheme } from '../hooks/useTheme';
import { MarkdownSurface } from './MarkdownSurface';
import {
diff --git a/packages/explorer/src/web/app/components/GraphCanvas.tsx b/packages/explorer/src/web/app/components/GraphCanvas.tsx
index 09837e45..21a5d2b2 100644
--- a/packages/explorer/src/web/app/components/GraphCanvas.tsx
+++ b/packages/explorer/src/web/app/components/GraphCanvas.tsx
@@ -29,25 +29,25 @@ import {
structuralOklch,
type Oklch,
} from '@/core/oklch';
-import { PRIMITIVES, primitiveOklch } from '@/presets/effort-graph/primitives';
+import { PRIMITIVES, primitiveOklch } from '@/presets/proof/primitives';
import {
CIRCLE_SEGMENTS,
GLYPH_OUTLINES,
RING_INNER_RATIO,
glyphExtent,
type GlyphId,
-} from '@/presets/effort-graph/glyphs';
+} from '@/presets/proof/glyphs';
import {
buildAlivenessMap,
isOpenBlocker,
type Aliveness,
type EffectiveLifecycle,
-} from '@/presets/effort-graph/lifecycle';
+} from '@/presets/proof/lifecycle';
import type {
GraphEdge,
GraphEdgeKind,
GraphNode,
-} from '@/presets/effort-graph/types';
+} from '@/presets/proof/types';
import { useTheme, type ColorMode } from '../hooks/useTheme';
import {
RELATION_META,
diff --git a/packages/explorer/src/web/app/components/Legend.tsx b/packages/explorer/src/web/app/components/Legend.tsx
index a6575d1a..e281689e 100644
--- a/packages/explorer/src/web/app/components/Legend.tsx
+++ b/packages/explorer/src/web/app/components/Legend.tsx
@@ -11,8 +11,8 @@ import {
PRIMITIVES,
PRIMITIVE_ORDER,
primitiveOklch,
-} from '@/presets/effort-graph/primitives';
-import type { GraphNode } from '@/presets/effort-graph/types';
+} from '@/presets/proof/primitives';
+import type { GraphNode } from '@/presets/proof/types';
import { useTheme } from '../hooks/useTheme';
import {
PrimitiveGlyph,
diff --git a/packages/explorer/src/web/app/components/EffortGraphApp.tsx b/packages/explorer/src/web/app/components/ProofApp.tsx
similarity index 96%
rename from packages/explorer/src/web/app/components/EffortGraphApp.tsx
rename to packages/explorer/src/web/app/components/ProofApp.tsx
index 6a11d464..3e185367 100644
--- a/packages/explorer/src/web/app/components/EffortGraphApp.tsx
+++ b/packages/explorer/src/web/app/components/ProofApp.tsx
@@ -1,13 +1,13 @@
import { lazy, Suspense, useEffect, useMemo } from 'react';
import { resolveGraphqlEndpoint } from '@/core/endpoints';
-import { useEffortGraphLive } from '@/presets/effort-graph/useEffortGraphLive';
+import { useEffortGraphLive } from '@/presets/proof/useProofLive';
import {
buildAlivenessMap,
buildLifecycleIndex,
summarizeGraph,
-} from '@/presets/effort-graph/lifecycle';
-import type { GraphNode } from '@/presets/effort-graph/types';
+} from '@/presets/proof/lifecycle';
+import type { GraphNode } from '@/presets/proof/types';
import { TopBar } from './TopBar';
import { Legend } from './Legend';
diff --git a/packages/explorer/src/web/app/components/RelationLegend.tsx b/packages/explorer/src/web/app/components/RelationLegend.tsx
index 8f2b8191..f30b20ea 100644
--- a/packages/explorer/src/web/app/components/RelationLegend.tsx
+++ b/packages/explorer/src/web/app/components/RelationLegend.tsx
@@ -1,14 +1,8 @@
-import type {
- GraphEdgeKind,
- GraphNodeKind,
-} from '@/presets/effort-graph/types';
+import type { GraphEdgeKind, GraphNodeKind } from '@/presets/proof/types';
import type { ColorMode } from '@/core/oklch';
import { oklchCss, structuralOklch } from '@/core/oklch';
-import { PRIMITIVES, primitiveOklch } from '@/presets/effort-graph/primitives';
-import {
- RING_INNER_RATIO,
- glyphSvgPoints,
-} from '@/presets/effort-graph/glyphs';
+import { PRIMITIVES, primitiveOklch } from '@/presets/proof/primitives';
+import { RING_INNER_RATIO, glyphSvgPoints } from '@/presets/proof/glyphs';
export type RelationGroupId =
| 'lineage'
diff --git a/packages/explorer/src/web/app/components/TopBar.tsx b/packages/explorer/src/web/app/components/TopBar.tsx
index c64a1395..3fb45731 100644
--- a/packages/explorer/src/web/app/components/TopBar.tsx
+++ b/packages/explorer/src/web/app/components/TopBar.tsx
@@ -1,9 +1,6 @@
import { useTheme } from '../hooks/useTheme';
-import type { GraphSummary } from '@/presets/effort-graph/lifecycle';
-import {
- liveStatusLabel,
- type LiveStatus,
-} from '@/presets/effort-graph/useEffortGraphLive';
+import type { GraphSummary } from '@/presets/proof/lifecycle';
+import { liveStatusLabel, type LiveStatus } from '@/presets/proof/useProofLive';
interface TopBarProps {
status: LiveStatus;
diff --git a/packages/explorer/src/web/app/main.tsx b/packages/explorer/src/web/app/main.tsx
index 21c744a2..82b0af22 100644
--- a/packages/explorer/src/web/app/main.tsx
+++ b/packages/explorer/src/web/app/main.tsx
@@ -1,7 +1,7 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { ThemeProvider } from './hooks/useTheme';
-import { EffortGraphApp } from './components/EffortGraphApp';
+import { EffortGraphApp } from './components/ProofApp';
import './globals.css';
const root = document.getElementById('root');
diff --git a/packages/explorer/src/web/presets/effort-graph/glyphs.test.ts b/packages/explorer/src/web/presets/proof/glyphs.test.ts
similarity index 100%
rename from packages/explorer/src/web/presets/effort-graph/glyphs.test.ts
rename to packages/explorer/src/web/presets/proof/glyphs.test.ts
diff --git a/packages/explorer/src/web/presets/effort-graph/glyphs.ts b/packages/explorer/src/web/presets/proof/glyphs.ts
similarity index 100%
rename from packages/explorer/src/web/presets/effort-graph/glyphs.ts
rename to packages/explorer/src/web/presets/proof/glyphs.ts
diff --git a/packages/explorer/src/web/presets/effort-graph/lifecycle.test.ts b/packages/explorer/src/web/presets/proof/lifecycle.test.ts
similarity index 100%
rename from packages/explorer/src/web/presets/effort-graph/lifecycle.test.ts
rename to packages/explorer/src/web/presets/proof/lifecycle.test.ts
diff --git a/packages/explorer/src/web/presets/effort-graph/lifecycle.ts b/packages/explorer/src/web/presets/proof/lifecycle.ts
similarity index 100%
rename from packages/explorer/src/web/presets/effort-graph/lifecycle.ts
rename to packages/explorer/src/web/presets/proof/lifecycle.ts
diff --git a/packages/explorer/src/web/presets/effort-graph/normalize.test.ts b/packages/explorer/src/web/presets/proof/normalize.test.ts
similarity index 100%
rename from packages/explorer/src/web/presets/effort-graph/normalize.test.ts
rename to packages/explorer/src/web/presets/proof/normalize.test.ts
diff --git a/packages/explorer/src/web/presets/effort-graph/normalize.ts b/packages/explorer/src/web/presets/proof/normalize.ts
similarity index 100%
rename from packages/explorer/src/web/presets/effort-graph/normalize.ts
rename to packages/explorer/src/web/presets/proof/normalize.ts
diff --git a/packages/explorer/src/web/presets/effort-graph/primitives.ts b/packages/explorer/src/web/presets/proof/primitives.ts
similarity index 100%
rename from packages/explorer/src/web/presets/effort-graph/primitives.ts
rename to packages/explorer/src/web/presets/proof/primitives.ts
diff --git a/packages/explorer/src/web/presets/effort-graph/query.test.ts b/packages/explorer/src/web/presets/proof/query.test.ts
similarity index 100%
rename from packages/explorer/src/web/presets/effort-graph/query.test.ts
rename to packages/explorer/src/web/presets/proof/query.test.ts
diff --git a/packages/explorer/src/web/presets/effort-graph/query.ts b/packages/explorer/src/web/presets/proof/query.ts
similarity index 100%
rename from packages/explorer/src/web/presets/effort-graph/query.ts
rename to packages/explorer/src/web/presets/proof/query.ts
diff --git a/packages/explorer/src/web/presets/effort-graph/types.ts b/packages/explorer/src/web/presets/proof/types.ts
similarity index 100%
rename from packages/explorer/src/web/presets/effort-graph/types.ts
rename to packages/explorer/src/web/presets/proof/types.ts
diff --git a/packages/explorer/src/web/presets/effort-graph/useEffortGraphLive.test.ts b/packages/explorer/src/web/presets/proof/useProofLive.test.ts
similarity index 98%
rename from packages/explorer/src/web/presets/effort-graph/useEffortGraphLive.test.ts
rename to packages/explorer/src/web/presets/proof/useProofLive.test.ts
index 98b20298..01d0ed10 100644
--- a/packages/explorer/src/web/presets/effort-graph/useEffortGraphLive.test.ts
+++ b/packages/explorer/src/web/presets/proof/useProofLive.test.ts
@@ -9,7 +9,7 @@ import {
rollbackRequestedGeneration,
shouldCommitGeneration,
type LiveStatus,
-} from './useEffortGraphLive';
+} from './useProofLive';
import type { SchemaProbeResult } from './query';
const SAMPLE_SCHEMA: SchemaProbeResult = {
diff --git a/packages/explorer/src/web/presets/effort-graph/useEffortGraphLive.ts b/packages/explorer/src/web/presets/proof/useProofLive.ts
similarity index 100%
rename from packages/explorer/src/web/presets/effort-graph/useEffortGraphLive.ts
rename to packages/explorer/src/web/presets/proof/useProofLive.ts
diff --git a/packages/explorer/tsconfig.json b/packages/explorer/tsconfig.json
index 1aafd509..e6378fe0 100644
--- a/packages/explorer/tsconfig.json
+++ b/packages/explorer/tsconfig.json
@@ -15,11 +15,10 @@
"@flatbread/codegen": ["./packages/codegen/src/index.ts"],
"@flatbread/core": ["./packages/core/src/index.ts"],
"@flatbread/config": ["./packages/config/src/index.ts"],
- "@flatbread/proof": ["./packages/proof/src/index.ts"],
"@flatbread/resolver-svimg": ["./packages/resolver-svimg/src/index.ts"],
"@flatbread/utils": ["./packages/utils/src/index.ts"],
"@flatbread/explorer": ["./packages/explorer/src/node/index.ts"],
- "@flatbread/effort-graph": ["./packages/effort-graph/src/index.ts"],
+ "@flatbread/effort-graph": ["./packages/proof/src/index.ts"],
"@flatbread/source-filesystem": [
"./packages/source-filesystem/src/index.ts"
],
diff --git a/packages/flatbread/README.md b/packages/flatbread/README.md
index 31286bfa..56d44fe8 100644
--- a/packages/flatbread/README.md
+++ b/packages/flatbread/README.md
@@ -16,30 +16,49 @@
-Turn files in Git into typed, related content for your TypeScript app.
-**[GraphQL](https://graphql.org/)** and codegen are common ways to read that
-content, but they are not the only options. See
-[Flatbread positioning](https://github.com/FlatbreadLabs/flatbread/blob/main/docs/positioning.md).
-
-**Flatbread** reads content from your repository and file system. Plugins control
-how it reads files and turns them into data.
-
-**Who it is for:** Teams building TypeScript sites, internal tools, and starter
-projects that want versioned, reviewable content and links between entries
-without setting up a CMS database.
+Flatbread turns files in Git into a typed relational graph. Files are the
+records. `refs` in `flatbread.config.js` link them. You read the graph through
+**[GraphQL](https://graphql.org/)**, generated TypeScript, or the `flatbread`
+CLI.
+
+People use it two ways.
+
+**Durable memory for coding agents.** The
+[Effort Graph](https://github.com/FlatbreadLabs/flatbread/tree/main/packages/effort-graph)
+stores an agent's reasoning as markdown records in the repository: Efforts,
+Issues, Findings, Decisions, Constraints, Risks, Citations, and Blobs. An agent
+writes them with `flatbread effort write` and reads them back through bounded
+queries such as `flatbread effort list` and
+`flatbread effort blocking-decisions`. Records live under
+`.flatbread-efforts/`, so you commit, review, diff, and revert them like any
+other file, and the memory outlives the session that produced it. The bundled
+[Effort Graph skill](https://github.com/FlatbreadLabs/flatbread/blob/main/packages/effort-graph/skills/effort-graph/SKILL.md)
+teaches an agent the commands.
+
+**Relational content for sites, docs, and internal tools.** Markdown and YAML
+files become typed collections that link to each other. A post names its
+authors by id, and Flatbread resolves them. You get versioned, reviewable
+content and joins over files without a CMS database. Start with the
+[Quickstart](#quickstart-posts-authors-and-tags).
+
+Both paths run on the same engine. Plugins control how Flatbread reads files
+and turns them into data.
+
+**Who it is for:** People building coding agents that need memory they can
+review in Git, and teams building TypeScript sites, internal tools, and starter
+projects that want versioned content with links between entries.
**What Flatbread does not do:**
- It is not a hosted CMS, dashboard, or writing UI.
- It is not a general-purpose GraphQL platform or database. Transactions,
detailed access control, and many concurrent writers are outside its scope.
-- Run `flatbread start --watch` to update valid content and config changes
- while you work. Changes to Flatbread packages still need their own rebuild or
- restart. See the
+- It does not reload its own packages. `flatbread start --watch` picks up valid
+ content and config changes while you work, but a change to a Flatbread
+ package needs a rebuild and a restart. See the
[local development loop](https://github.com/FlatbreadLabs/flatbread/blob/main/docs/local-dev-loop.md).
-**GraphQL:** GraphQL is a common way to read Flatbread data. It does not define
-the product. For more detail, see
+**GraphQL:** GraphQL is one read interface over the graph. For more detail, see
[Flatbread positioning](https://github.com/FlatbreadLabs/flatbread/blob/main/docs/positioning.md).
**Glossary:** Definitions for collections, relations, IDs, validation, and the
@@ -58,12 +77,11 @@ introspection, and generated TypeScript remain available when you move away
from Flatbread. See
[data ownership](https://github.com/FlatbreadLabs/flatbread/blob/main/docs/data-ownership.md).
-For contributing to this monorepo, use Node 20.19+ with pnpm 10.33.x. Runtime support for published packages is tracked by each package's own metadata.
+Every published package requires Node 20.19 or newer. To work on this monorepo,
+use Node 20.19+ with pnpm 10.33.x.
## Quickstart (posts, authors, and tags)
-🚧 This project is experimental; the API may change before `v1.0`.
-
Start with the **Next.js example** in `examples/nextjs`. It reads shared
Markdown from `examples/content` through its `content/` symlink. The commands
below use that layout.
@@ -111,7 +129,7 @@ The Next example points `flatbread.config.js` at `content/markdown/...` **relati
### Traceability: same relation model (files, config, query interface)
-The table below ties the **Git-native** model to the default **GraphQL** read layer without implying GraphQL is the product’s whole identity—GraphQL is one [**query interface**](https://github.com/FlatbreadLabs/flatbread/blob/main/docs/glossary.md#query-interface); files and config remain the source of truth.
+The table below follows one relation from files to config to the generated GraphQL schema. Files and config are the source of truth; GraphQL is one [query interface](https://github.com/FlatbreadLabs/flatbread/blob/main/docs/glossary.md#query-interface) over them.
| Layer | You see… | Glossary |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -173,20 +191,20 @@ query GetPostsAuthorsAndTags {
}
```
-After codegen, your app imports types from **`./generated/graphql`**. The **result shape** of that operation is typed (for example **`GetPostsAuthorsAndTagsQuery`**)—relations resolve to **`Author`** objects while **`tags`** stay a **string array** on **`Post`**, matching the file metadata—the same row as the [illustrative JSON](#traceability-same-relation-model-files-config-query-interface) under **Traceability**.
+After codegen, your app imports types from **`./generated/graphql`**. The result shape of that operation is typed, for example **`GetPostsAuthorsAndTagsQuery`**. Relations resolve to **`Author`** objects; **`tags`** stays a string array on **`Post`**, matching the file metadata. That is the same shape as the [illustrative JSON](#traceability-same-relation-model-files-config-query-interface) under **Traceability**.
-The generated file also exposes a prototype **TypeScript read API** derived from the configured content model. In the Next.js example, [`examples/nextjs/lib/read.ts`](https://github.com/FlatbreadLabs/flatbread/blob/main/examples/nextjs/lib/read.ts) wires **`createFlatbreadReadApi()`** to the existing GraphQL fetcher and reads **posts**, **authors**, and **tags** with a generated default selection—no hand-written GraphQL document at the call site.
+The generated file also exposes a prototype **TypeScript read API** derived from the configured content model. In the Next.js example, [`examples/nextjs/lib/read.ts`](https://github.com/FlatbreadLabs/flatbread/blob/main/examples/nextjs/lib/read.ts) wires **`createFlatbreadReadApi()`** to the existing GraphQL fetcher. It reads **posts**, **authors**, and **tags** with a generated default selection, so there is no hand-written GraphQL document at the call site.
#### Choosing a read interface
-Flatbread starts with related content files for TypeScript apps. Files define
-records, frontmatter fields, IDs, and `refs`; `flatbread.config.js` tells
-Flatbread how to group them into typed collections. **GraphQL** and the
-generated TypeScript API are two ways for your app to read the same data.
+Files come first. They define records, frontmatter fields, IDs, and `refs`;
+`flatbread.config.js` tells Flatbread how to group them into typed collections.
+**GraphQL** and the generated TypeScript API are two ways for your app to read
+the same data.
-Use **GraphQL operations** when your app needs explicit query documents, custom selections, Apollo or other GraphQL clients, persisted operations, or direct access to the GraphQL endpoint. Add `.graphql` documents, include fields like **`tags`** and **`authors`**, and rerun codegen so operation types such as **`GetPostsAuthorsAndTagsQuery`** match the posts/authors/tags graph.
+Use **GraphQL operations** when your app needs explicit query documents, custom selections, Apollo or another GraphQL client, persisted operations, or direct access to the GraphQL endpoint. Add `.graphql` documents, include fields like **`tags`** and **`authors`**, and rerun codegen so operation types such as **`GetPostsAuthorsAndTagsQuery`** match the posts/authors/tags graph.
-Use the prototype **generated TypeScript read API** when your app wants collection-shaped helpers for common reads from the configured Flatbread model, especially simple app reads such as posts, authors, tags, and resolved relations without writing GraphQL at each call site. The generated helpers currently execute through the GraphQL layer and still offer an experimental selection-string escape hatch, so both paths expose the same typed content graph backed by the same flat files while GraphQL remains the stable low-level interface.
+Use the prototype **generated TypeScript read API** when you want collection-shaped helpers instead of a GraphQL document at each call site. It suits plain reads: posts, authors, tags, and resolved relations. The helpers still run through the GraphQL layer, and their selection-string escape hatch is experimental. Both paths read the same typed graph from the same files; GraphQL is the stable lower-level interface.
Default filesystem + markdown wiring uses the bundled [`source-filesystem`](https://github.com/FlatbreadLabs/flatbread/tree/main/packages/source-filesystem) and [`transformer-markdown`](https://github.com/FlatbreadLabs/flatbread/tree/main/packages/transformer-markdown) plugins (`flatbread` re-exports them).
@@ -219,7 +237,7 @@ export default defineConfig({
### 5 · Reading the graph: GraphQL (after the model exists)
-Flatbread builds a content **graph from files**. In the default toolchain, **GraphQL is one read interface**: schema + resolver shape over that graph—not “Flatbread is a GraphQL CMS.”
+Flatbread builds a content graph from files. GraphQL is one read interface over that graph: a generated schema and the resolvers behind it.
Wire your framework so the CLI wraps dev/build (**`flatbread start`** passes through your command after **`--`**). There is **no** `flatbread dev` subcommand.
@@ -256,7 +274,7 @@ for the cases that still need a rebuild or restart.
Outside this monorepo:
```bash
-pnpm add flatbread@latest
+pnpm add flatbread
```
Scaffold **`flatbread.config.js`**:
@@ -270,13 +288,19 @@ Point **`content`** entries at **your** `posts/` and **`authors/`** folders, reu
More detail on the bundled example is in the
[Next.js example README](https://github.com/FlatbreadLabs/flatbread/blob/main/examples/nextjs/README.md).
+For agent memory instead of site content, add `effortGraphContent()` to your
+config, then run `flatbread effort bootstrap` to check the setup and
+`flatbread effort bootstrap --verify` to fail when something is missing. The
+[Effort Graph README](https://github.com/FlatbreadLabs/flatbread/blob/main/packages/effort-graph/README.md)
+has the install commands.
+
## Query arguments (GraphQL read interface)
When **GraphQL** is your read interface, list fields use the following arguments in order of application.
### `filter`
-Each collection in the GraphQL schema can be passed a `filter` argument to constrain your results, sifting for only what you want. Any leaf field should be able to be used in a filter.
+Every collection in the GraphQL schema takes a `filter` argument that narrows the results. Any leaf field can be used in a filter.
The syntax for `filter` is based on a subset of [MongoDB's query syntax](https://docs.mongodb.com/manual/reference/operator/query/).
@@ -354,7 +378,7 @@ Caveats:
- Currently cannot infer date strings and then compare `Date` types in filters
- should work if you dynamically pass in a `Date` object from your client, though not extensively tested
- - if you wanna take a shot at that, start a PR for [adding arg typeOf checks and subsequent unique comparator functions 🥪](https://github.com/FlatbreadLabs/flatbread/blob/main/packages/core/src/utils/sift.ts)
+ - to fix this, add argument `typeof` checks and the matching comparator functions in [`packages/core/src/utils/sift.ts`](https://github.com/FlatbreadLabs/flatbread/blob/main/packages/core/src/utils/sift.ts), then open a pull request
#### Combining multiple filters
@@ -382,7 +406,7 @@ result = [{ title: 'My pretzel collection' }];
### `sortBy`
-Sorts by the given field. Accepts a root-level field name. Defaults to not sortin' at all.
+Sorts by the given field. Accepts a root-level field name. Defaults to no sorting.
### `order`
diff --git a/packages/flatbread/package.json b/packages/flatbread/package.json
index 93fff2a5..c519714c 100644
--- a/packages/flatbread/package.json
+++ b/packages/flatbread/package.json
@@ -1,7 +1,7 @@
{
"name": "flatbread",
- "version": "1.0.0-alpha.22",
- "description": "Consume relational, flat-file data using GraphQL 🥯 inside damn near any framework.",
+ "version": "1.0.0",
+ "description": "Git-native memory for coding agents and relational content for TypeScript apps. Files in your repo become a typed graph you read over GraphQL, generated TypeScript, or the CLI.",
"type": "module",
"scripts": {
"build": "tsup",
@@ -30,8 +30,11 @@
"dist",
"*.d.ts"
],
+ "engines": {
+ "node": ">=20.19"
+ },
"dependencies": {
- "@apollo/server": "5.0.0",
+ "@apollo/server": "5.5.1",
"@apollo/utils.keyvaluecache": "^4.0.0",
"@as-integrations/express5": "1.1.2",
"@flatbread/codegen": "workspace:*",
@@ -44,18 +47,12 @@
"@flatbread/transformer-yaml": "workspace:*",
"@flatbread/utils": "workspace:*",
"@parcel/watcher": "^2.5.1",
- "apollo-server-core": "^3.13.0",
- "apollo-server-express": "^3.13.0",
"cors": "^2.8.5",
"express": "^5.1.0",
- "express-graphql": "^0.12.0",
"gradient-string": "^2.0.2",
- "graphql": "16.5.0",
+ "graphql": "16.14.2",
"kleur": "^4.1.5",
- "picomatch": "4.0.5",
- "remark-github": "^11.2.4",
- "sade": "^1.8.1",
- "serialize-javascript": "^6.0.2"
+ "sade": "^1.8.1"
},
"devDependencies": {
"@types/cors": "2.8.12",
@@ -64,7 +61,7 @@
"@types/node": "16.11.47",
"@types/picomatch": "4.0.3",
"@types/sade": "1.7.4",
- "@types/serialize-javascript": "5.0.2",
+ "picomatch": "4.0.5",
"tsup": "6.2.1",
"typescript": "4.7.4",
"vfile": "5.3.4"
diff --git a/packages/flatbread/src/cli/index.ts b/packages/flatbread/src/cli/index.ts
index e1767119..c429680b 100644
--- a/packages/flatbread/src/cli/index.ts
+++ b/packages/flatbread/src/cli/index.ts
@@ -7,7 +7,7 @@ import { networkInterfaces, release } from 'node:os';
import orchestrateProcesses from './runner';
import initConfig from './initConfig';
import { createCodegenCommand } from '@flatbread/codegen';
-import { registerEffortCommands } from './effort';
+import { registerEffortCommands } from './proof';
import {
EXPLORER_ENDPOINT,
GRAPHQL_ENDPOINT,
diff --git a/packages/flatbread/src/cli/effort.test.ts b/packages/flatbread/src/cli/proof.test.ts
similarity index 99%
rename from packages/flatbread/src/cli/effort.test.ts
rename to packages/flatbread/src/cli/proof.test.ts
index 8a222af0..757aa57c 100644
--- a/packages/flatbread/src/cli/effort.test.ts
+++ b/packages/flatbread/src/cli/proof.test.ts
@@ -26,7 +26,7 @@ import {
handleEffortWrite,
inspectEffortBootstrap,
mapEffortCliOptions,
-} from './effort.js';
+} from './proof.js';
type TeardownContext = {
teardown(callback: () => void | Promise): void;
diff --git a/packages/flatbread/src/cli/effort.ts b/packages/flatbread/src/cli/proof.ts
similarity index 99%
rename from packages/flatbread/src/cli/effort.ts
rename to packages/flatbread/src/cli/proof.ts
index 91598c2e..5a40d991 100644
--- a/packages/flatbread/src/cli/effort.ts
+++ b/packages/flatbread/src/cli/proof.ts
@@ -18,7 +18,7 @@ import {
relations,
getRecord,
listEfforts,
-} from '../effort/read.js';
+} from '../proof/read.js';
import type { PrimitiveKind, ReadRelation } from '@flatbread/effort-graph';
export interface EffortCliOptions {
diff --git a/packages/flatbread/src/graphql/liveServer.ts b/packages/flatbread/src/graphql/liveServer.ts
index 01fabad2..7880a732 100644
--- a/packages/flatbread/src/graphql/liveServer.ts
+++ b/packages/flatbread/src/graphql/liveServer.ts
@@ -18,7 +18,7 @@ import cors from 'cors';
import express, { type RequestHandler } from 'express';
import http from 'http';
import { loadFlatbreadConfig } from '../utils/getSchema';
-import { createEffortGraphComposition } from './effortGraphComposition';
+import { createEffortGraphComposition } from './proofComposition';
import { mountExplorer } from './explorerMount';
import { buildWatchIgnore } from './watchIgnore';
diff --git a/packages/flatbread/src/graphql/liveServerEffortGraph.test.ts b/packages/flatbread/src/graphql/liveServerProof.test.ts
similarity index 100%
rename from packages/flatbread/src/graphql/liveServerEffortGraph.test.ts
rename to packages/flatbread/src/graphql/liveServerProof.test.ts
diff --git a/packages/flatbread/src/graphql/effortGraphComposition.test.ts b/packages/flatbread/src/graphql/proofComposition.test.ts
similarity index 96%
rename from packages/flatbread/src/graphql/effortGraphComposition.test.ts
rename to packages/flatbread/src/graphql/proofComposition.test.ts
index e7395c4b..5fd66472 100644
--- a/packages/flatbread/src/graphql/effortGraphComposition.test.ts
+++ b/packages/flatbread/src/graphql/proofComposition.test.ts
@@ -8,7 +8,7 @@ import type {
LiveSchemaReloader,
SchemaSnapshot,
} from '@flatbread/core';
-import { createEffortGraphComposition } from './effortGraphComposition.js';
+import { createEffortGraphComposition } from './proofComposition.js';
function fakeReloader(): LiveSchemaReloader {
let generation = 0;
diff --git a/packages/flatbread/src/graphql/effortGraphComposition.ts b/packages/flatbread/src/graphql/proofComposition.ts
similarity index 100%
rename from packages/flatbread/src/graphql/effortGraphComposition.ts
rename to packages/flatbread/src/graphql/proofComposition.ts
diff --git a/packages/flatbread/src/effort/read.ts b/packages/flatbread/src/proof/read.ts
similarity index 100%
rename from packages/flatbread/src/effort/read.ts
rename to packages/flatbread/src/proof/read.ts
diff --git a/packages/proof/README.md b/packages/proof/README.md
index ae077796..82d64ed6 100644
--- a/packages/proof/README.md
+++ b/packages/proof/README.md
@@ -1,241 +1,75 @@
-# Proof
-
-Proof is Flatbread's DAG task runner for Cursor agents. It decomposes a task into a graph of subagents, runs each node in topological order, and writes a live `.canvas.tsx` so you can watch the work move from `PENDING` to `RUNNING` to `FINISHED` or `ERROR`.
-
-The package ships as `@flatbread/proof` and exposes:
-
-- `proof`: run a DAG or initialize its canvas.
-- `proof-supervisor`: run Proof in self-hosting mode so edits to `packages/proof/src/**` can be picked up between ranks.
-- Library exports for tooling that wants to author, validate, or inspect DAGs programmatically.
-
-## Quick Start
-
-Build the package once after installing dependencies:
-
-```bash
-pnpm -F @flatbread/proof build
+# `@flatbread/effort-graph`
+
+Git-native memory for coding agents. Installing this package gives you three
+things:
+
+- **Record types.** An agent records the work it is doing as an **Effort**,
+ then writes what it learns against that Effort: **Issues**, **Findings**,
+ **Decisions**, **Constraints**, **Risks**, **Citations**, and **Blobs**.
+- **Write operations.** A typed mutation turns into Markdown files on disk, and
+ the writer checks the links between records before it commits them.
+- **A Flatbread content model.** `effortGraphContent()` adds those eight record
+ types to a Flatbread configuration, so the same files come back as a typed
+ graph you can query and page through.
+
+Every record is a Markdown file in your repository, so you commit, diff,
+review, and revert an agent's reasoning the same way you handle code, and the
+next session can read it back.
+
+Writes go through a journal, so a change that touches several files either
+finishes in full or leaves nothing behind: if the process dies mid-write, the
+next run restores the earlier contents of the unfinished change.
+
+Version 1 supports these actions: `CreateEffort`, `SetEffortStatus`,
+`WriteIssue`, `WriteFinding`, `WriteDecision`, `WriteConstraint`, `WriteRisk`,
+`WriteCitation`, `WriteBlob`, `Supersede`, `Invalidate`, `ResolveIssue`,
+`AcceptDecision`, `MitigateRisk`, and `SetRiskState`.
+
+An Issue, Finding, Decision, Constraint, or Risk may name Citation ids in
+`cites` (Flatbread `refs`). A Citation body alone is valid (e.g. a URL); an
+optional `blob` ref attaches a long payload such as a document, JSON, or image.
+
+## Where records live, and what to ignore
+
+`effortGraphContent()` stores the graph under `.flatbread-efforts` in your
+project root. Pass a path to choose another root:
+`effortGraphContent('path/to/graph')`.
+
+Two paths hold working state that Git should not track: the write journal at
+`/.journal`, and the derived read cache at
+`.flatbread/effort-graph/read-cache`. Nothing adds them to `.gitignore` for
+you, so add these lines yourself:
+
+```gitignore
+**/.flatbread-efforts/.journal/
+**/.flatbread/effort-graph/read-cache/
```
-Create a DAG JSON file:
-
-```json
-{
- "title": "Build a tiny CLI todo app",
- "tasks": [
- {
- "id": "design",
- "depends_on": [],
- "complexity": "LOW",
- "subtask_prompt": "Design the minimal CLI commands and file layout."
- },
- {
- "id": "implement",
- "depends_on": ["design"],
- "complexity": "MED",
- "subtask_prompt": "Implement the todo CLI based on the design."
- }
- ]
-}
-```
+For a custom root, replace `.flatbread-efforts` with that root. The read cache
+path stays the same.
-Initialize a canvas without requiring `CURSOR_API_KEY`:
+`flatbread effort bootstrap` reports what is still missing — the config entry
+or either ignore rule. `flatbread effort bootstrap --verify` reports the same
+and exits nonzero when anything is missing, which makes it usable in CI.
-```bash
-pnpm exec proof \
- --init-only \
- --dag /tmp/example-dag.json \
- --canvas-path /tmp/example-dag.canvas.tsx
-```
+## The domain model and the packaged skill
-Run the DAG:
+Read [`skills/effort-graph/glossary.md`](./skills/effort-graph/glossary.md) for
+the portable Effort Graph domain model.
-```bash
-export CURSOR_API_KEY=crsr_...
+The packaged Agent Skill is in `skills/effort-graph/`. The repository copy in
+`.agents/skills/effort-graph/` is generated from those files. Run
+`pnpm skills:sync` from the repository root after changing the skill.
-pnpm exec proof \
- --dag /tmp/example-dag.json \
- --canvas-path /tmp/example-dag.canvas.tsx
-```
+## Install the Effort Graph skill
-## DAG Shape
-
-Every DAG has a `title` and a `tasks` array. Each task needs:
-
-- `id`: unique kebab-case task id.
-- `depends_on`: ids of parent tasks that must finish first.
-- `complexity`: `HIGH`, `MED`, or `LOW`; maps to a Cursor model.
-- `subtask_prompt`: standalone instructions for the subagent.
-
-Proof computes ranks with Kahn topological sort and runs sibling tasks in the same rank concurrently. Avoid placing two sibling tasks in the same rank if they write the same files.
-
-Optional top-level `models` can override the default complexity map with plain
-SDK model id strings or SDK model selections:
-
-```json
-{
- "models": {
- "HIGH": {
- "id": "gpt-5.4",
- "params": [{ "id": "reasoning", "value": "high" }]
- },
- "MED": "composer-2",
- "LOW": {
- "id": "gpt-5.4-nano",
- "params": [{ "id": "reasoning", "value": "low" }]
- }
- }
-}
-```
-
-Use the object shape when you need `params`; use a string when the model id is
-enough. For example, use `{ "id": "gpt-5.4", "params": [{ "id": "reasoning", "value": "high" }] }`, not a suffix-style id like `gpt-5.4-high`.
-
-When a DAG runs, Proof calls `Cursor.models.list()`, validates model ids and
-param values, and expands partial selections to the closest valid SDK preset
-variant using that model's default variant for omitted params. `--init-only`
-does not call the SDK, so it can still render a canvas without `CURSOR_API_KEY`.
-
-Optional task kinds add control gates:
-
-- `kind: "oracle"` runs a shell command and records pass/fail evidence.
-- `kind: "pause"` waits for a checkpoint sentinel so a human can inspect or approve before downstream work continues.
-
-## `DAG.loops`
-
-Bounded convergence loops can live in the DAG itself instead of only on the CLI. This keeps the run reproducible: contributors do not need to remember a matching `--converge-on ... --max-iterations ...` flag pair.
-
-```json
-{
- "title": "implement then review until clean",
- "loops": [
- {
- "convergeOn": "review",
- "maxIterations": 3,
- "reexecute": { "kind": "tasks", "tasks": ["implement"] }
- }
- ],
- "tasks": [
- {
- "id": "implement",
- "depends_on": [],
- "complexity": "MED",
- "subtask_prompt": "Implement the feature."
- },
- {
- "id": "review",
- "depends_on": ["implement"],
- "complexity": "HIGH",
- "subtask_prompt": "Review the implementation. Use `## Blockers` and `## High-severity findings` when needed."
- }
- ]
-}
-```
-
-Notes:
-
-- Omit `id` to get the default `loop-` id.
-- Omit `reexecute` to re-run the full ancestor cone, which matches the legacy CLI behavior.
-- `reexecute: { "kind": "tasks", "tasks": [...] }` must stay inside the convergence task's ancestor cone and be dependency-closed for every non-`convergeOn` task it names; invalid subsets fail fast during DAG parsing with the missing ancestor ids.
-- Parsed explicit rerun lists always include `convergeOn` itself, even if the authored JSON omits it.
-- `DAG.loops` and `--converge-on` are mutually exclusive. If the DAG already declares loops, remove the CLI flag instead of relying on precedence.
-- Multiple loops are allowed only when their re-execution sets are disjoint, so one loop cannot invalidate another loop's converged result later in the run.
-
-## Artifact Output
-
-By default, every **full DAG run** writes per-task markdown transcripts to a timestamped directory (not `--init-only`, which exits before artifact setup, and not `--dry-check-cmds`, which never enters the runner):
-
-```
-/.flatbread/artifacts/dag--/
- _dag.json # The original DAG definition
- _index.md # Run summary: outcome, timings, and links to all transcripts
- .md # Full agent output for each task (kind: task, oracle, or pause)
- .stream.txt # Append-only assistant transcript mirror (`kind: task` only)
-```
-
-**Execution vs canvas:**
-
-- For `kind: "task"` only, stitched prompts, in-process convergence parsing (`--converge-on` / `DAG.loops`), `${task-id}.findings.json` payloads (`--findings-dir`), and `.md` derive from an **execution-authoritative** transcript. Resumed runs can reconstruct that transcript when the same `--full-output-dir` is reused and `transcriptPath` points at `${task-id}.stream.txt`; otherwise legacy bounded `resultText` remains the fallback.
-- The inlined canvas payload snapshots only a **4000-character display tail** (`CANVAS_DISPLAY_CAP`) per task plus an optional **`transcriptPath`** when `${task-id}.stream.txt` is mirrored.
-- Author `DAG.outputPolicy.upstream` as `"full"` or `"summarize"` (default) to widen or keep the upstream excerpt policy; trims carry visible counted banners.
-- Downstream nodes are skipped with `ERROR` when any upstream is `ERROR` or `BUDGET-EXCEEDED`.
-
-Paths resolve from `--cwd` (defaults to the process working directory). The live canvas still defaults under `~/.cursor/projects//canvases/` when using `--canvas` without `--canvas-path`.
-
-Previously, transcripts only appeared when you passed `--full-output-dir`; now they land under `.flatbread/` by default. Use `--no-artifacts` for opt-out, or `--full-output-dir` to redirect elsewhere.
-
-`--no-artifacts` suppresses transcripts, `_index.md`, and `_dag.json` only. **`--findings-dir` JSON sidecars use a separate path** — omit that flag (or point it elsewhere) if you need completely artifact-free output besides the canvas.
-
-To suppress artifact writing:
+Install from a release tag, then activate the skill for setup:
```bash
-pnpm exec proof --dag /tmp/my.json --canvas-path /tmp/my.canvas.tsx --no-artifacts
-```
-
-To write artifacts to a custom path:
-
-```bash
-pnpm exec proof --dag /tmp/my.json --canvas-path /tmp/my.canvas.tsx \
- --full-output-dir /path/to/my-artifacts/
-```
-
-## Project Skill
-
-The canonical Cursor skill entrypoint lives at:
-
-```text
-.cursor/skills/proof/SKILL.md
-```
-
-Use that skill when a request asks to decompose work, run subagents in parallel, or execute a task as a dependency graph. The legacy `.cursor/skills/dag-task-runner/SKILL.md` entry remains as a compatibility handoff and points to Proof.
-
-## Self-Hosting Mode
-
-When the DAG may edit Proof itself, use the supervisor:
-
-```bash
-pnpm exec proof-supervisor \
- --dag /tmp/example-dag.json \
- --canvas-path /tmp/example-dag.canvas.tsx \
- --state-path /tmp/example-dag-state.json
-```
-
-The supervisor adds `--restart-on-runner-change`. If runtime files change after a rank, Proof persists state, exits with code `75`, and the supervisor resumes from the state file under the rebuilt runtime.
-
-Each supervisor-spawned runner picks a **new default** `.flatbread/artifacts/dag--/` directory unless you pin **`--full-output-dir ` on the supervisor command** so every child inherits the same path.
-
-After editing `packages/proof/src/**`, rebuild before resuming packaged CLI runs:
-
-```bash
-pnpm -F @flatbread/proof build
-```
-
-## Useful Commands
-
-```bash
-pnpm -F @flatbread/proof typecheck
-pnpm -F @flatbread/proof build
-pnpm -F @flatbread/proof test
-pnpm test
-pnpm -F @flatbread/proof models:list
-pnpm exec proof --dry-check-cmds --dag .cursor/skills/proof/examples/example_dag.json
-```
-
-`pnpm -F @flatbread/proof test` is the focused bounded-loop suite. Root `pnpm test` also reaches that AVA file through `ava.config.js`.
-
-## Library API
-
-Proof also exposes helpers for tooling:
-
-```ts
-import {
- computeRanks,
- createModelSelectionResolver,
- parseDAG,
- resolveModelSelectionFromCatalog,
- runDryCheck,
- type DAG,
- type TaskState,
-} from '@flatbread/proof';
+npx skills add https://github.com/FlatbreadLabs/flatbread/tree/v1.0.0/packages/effort-graph/skills/effort-graph --skill effort-graph
+npm install --save-dev flatbread@1.0.0
```
-The public API includes DAG parsing and rank computation, model resolution, canvas state types, convergence helpers, dry command checks, oracle and pause helpers, and self-hosting state utilities.
+The tag and version come from `gitTag` and `flatbreadVersion` in
+`skills/effort-graph/release.json`. See `skills/effort-graph/setup.md` for the
+equivalent `pnpm`, `yarn`, and `bun` commands.
diff --git a/packages/proof/bin/proof-supervisor.js b/packages/proof/bin/proof-supervisor.js
deleted file mode 100755
index 468147a8..00000000
--- a/packages/proof/bin/proof-supervisor.js
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env node
-import { resolve } from 'path';
-import { existsSync } from 'fs';
-
-if (process.env.FLATBREAD_CI) {
- const cliPath = resolve(
- process.cwd(),
- 'node_modules',
- '@flatbread',
- 'proof',
- 'dist',
- 'run_dag_supervisor.js'
- );
-
- if (existsSync(cliPath)) {
- import('../dist/run_dag_supervisor.js');
- } else {
- console.log('@flatbread/proof supervisor CLI is not available');
- }
-} else {
- import('../dist/run_dag_supervisor.js');
-}
diff --git a/packages/proof/bin/proof.js b/packages/proof/bin/proof.js
deleted file mode 100755
index b350074a..00000000
--- a/packages/proof/bin/proof.js
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env node
-import { resolve } from 'path';
-import { existsSync } from 'fs';
-
-if (process.env.FLATBREAD_CI) {
- const cliPath = resolve(
- process.cwd(),
- 'node_modules',
- '@flatbread',
- 'proof',
- 'dist',
- 'run_dag.js'
- );
-
- if (existsSync(cliPath)) {
- import('../dist/run_dag.js');
- } else {
- console.log('@flatbread/proof CLI is not available');
- }
-} else {
- import('../dist/run_dag.js');
-}
diff --git a/packages/proof/package.json b/packages/proof/package.json
index 975eff90..412afe35 100644
--- a/packages/proof/package.json
+++ b/packages/proof/package.json
@@ -1,23 +1,23 @@
{
- "name": "@flatbread/proof",
- "version": "0.1.0-alpha.0",
- "description": "Decompose a task into a DAG of subagents and prove they did the work — live canvas, oracles, pause gates, and convergence loops.",
+ "name": "@flatbread/effort-graph",
+ "version": "1.0.0",
+ "description": "Git-native memory records for coding agents. Writes typed Effort, Issue, Finding, Decision, Constraint, Risk, Citation, and Blob records to Markdown as journaled changes, and reads them back in bounded pages.",
"type": "module",
"scripts": {
"build": "tsup",
- "dev": "tsup --watch src",
- "test": "pnpm --dir ../.. exec ava \"packages/proof/src/__tests__/**/*.test.ts\"",
- "test:watch": "pnpm --dir ../.. exec ava --watch \"packages/proof/src/__tests__/**/*.test.ts\"",
- "typecheck": "tsc -p tsconfig.json --noEmit",
- "models:list": "tsx src/list_models.ts",
- "cursor:fetch-cloud-agent": "node scripts/fetch-cloud-agent-conversation.mjs"
+ "dev": "node scripts/watch-skills.mjs",
+ "skills:sync": "node scripts/sync-skills.mjs",
+ "skills:check": "node scripts/sync-skills.mjs --check",
+ "skills:pack-check": "node scripts/pack-skills.mjs",
+ "test": "pnpm --dir ../.. exec ava \"packages/proof/src/__tests__/**/*.test.{js,ts}\"",
+ "typecheck": "tsc -p tsconfig.json --noEmit"
},
"repository": {
"type": "git",
"url": "git+https://github.com/FlatbreadLabs/flatbread.git",
"directory": "packages/proof"
},
- "homepage": "https://github.com/FlatbreadLabs/flatbread/tree/main/packages/proof#readme",
+ "homepage": "https://github.com/FlatbreadLabs/flatbread#readme",
"author": "Tony Ketcham ",
"license": "MIT",
"bugs": {
@@ -29,27 +29,23 @@
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
- "bin": {
- "proof": "bin/proof.js",
- "proof-supervisor": "bin/proof-supervisor.js"
- },
"files": [
- "bin",
"dist",
- "src",
- "scripts",
+ "skills",
"*.d.ts"
],
"engines": {
- "node": ">=18"
+ "node": ">=20.19"
},
"dependencies": {
- "@cursor/sdk": "^1.0.9"
+ "@flatbread/core": "workspace:*",
+ "gray-matter": "^4.0.3",
+ "zod": "^3.24.1"
},
"devDependencies": {
- "@types/node": "^22.10.0",
- "tsup": "^8.3.0",
- "tsx": "^4.19.0",
- "typescript": "^5.7.0"
+ "@flatbread/transformer-markdown": "workspace:*",
+ "@types/node": "25.6.2",
+ "tsup": "8.5.1",
+ "typescript": "6.0.3"
}
}
diff --git a/packages/effort-graph/scripts/pack-skills.mjs b/packages/proof/scripts/pack-skills.mjs
similarity index 98%
rename from packages/effort-graph/scripts/pack-skills.mjs
rename to packages/proof/scripts/pack-skills.mjs
index 6f0794f2..c441227d 100644
--- a/packages/effort-graph/scripts/pack-skills.mjs
+++ b/packages/proof/scripts/pack-skills.mjs
@@ -11,7 +11,7 @@ export const forbiddenInvocation = 'node packages/flatbread/bin/flatbread.js';
export function verifyReleaseIdentity(canonicalTexts, packageVersions) {
const entry = canonicalTexts.find(
- ({ path }) => path === 'skills/effort-graph/release.json'
+ ({ path }) => path === 'skills/proof/release.json'
);
if (!entry)
throw new Error('Canonical skill payload is missing release.json');
diff --git a/packages/effort-graph/scripts/sync-skills.mjs b/packages/proof/scripts/sync-skills.mjs
similarity index 96%
rename from packages/effort-graph/scripts/sync-skills.mjs
rename to packages/proof/scripts/sync-skills.mjs
index 2b9e7371..92996172 100644
--- a/packages/effort-graph/scripts/sync-skills.mjs
+++ b/packages/proof/scripts/sync-skills.mjs
@@ -6,19 +6,19 @@ const scriptDirectory = dirname(fileURLToPath(import.meta.url));
export const repositoryRoot = resolve(scriptDirectory, '../../..');
export const defaultSource = resolve(
repositoryRoot,
- 'packages/effort-graph/skills/effort-graph'
+ 'packages/proof/skills/proof'
);
export const defaultDestination = resolve(
repositoryRoot,
- '.agents/skills/effort-graph'
+ '.agents/skills/proof'
);
export const managedSkillNames = [
- 'effort-graph',
+ 'proof',
'effort-modeling',
'grill-with-efforts',
];
export const managedSkillSources = managedSkillNames.map((name) =>
- resolve(repositoryRoot, 'packages/effort-graph/skills', name)
+ resolve(repositoryRoot, 'packages/proof/skills', name)
);
async function entries(root) {
diff --git a/packages/effort-graph/scripts/watch-skills.mjs b/packages/proof/scripts/watch-skills.mjs
similarity index 100%
rename from packages/effort-graph/scripts/watch-skills.mjs
rename to packages/proof/scripts/watch-skills.mjs
diff --git a/packages/effort-graph/skills/effort-modeling/CONTEXT-FORMAT.md b/packages/proof/skills/effort-modeling/CONTEXT-FORMAT.md
similarity index 100%
rename from packages/effort-graph/skills/effort-modeling/CONTEXT-FORMAT.md
rename to packages/proof/skills/effort-modeling/CONTEXT-FORMAT.md
diff --git a/packages/effort-graph/skills/effort-modeling/DECISION-BODY.md b/packages/proof/skills/effort-modeling/DECISION-BODY.md
similarity index 100%
rename from packages/effort-graph/skills/effort-modeling/DECISION-BODY.md
rename to packages/proof/skills/effort-modeling/DECISION-BODY.md
diff --git a/packages/effort-graph/skills/effort-modeling/SKILL.md b/packages/proof/skills/effort-modeling/SKILL.md
similarity index 100%
rename from packages/effort-graph/skills/effort-modeling/SKILL.md
rename to packages/proof/skills/effort-modeling/SKILL.md
diff --git a/packages/effort-graph/skills/grill-with-efforts/SKILL.md b/packages/proof/skills/grill-with-efforts/SKILL.md
similarity index 100%
rename from packages/effort-graph/skills/grill-with-efforts/SKILL.md
rename to packages/proof/skills/grill-with-efforts/SKILL.md
diff --git a/packages/effort-graph/skills/effort-graph/SKILL.md b/packages/proof/skills/proof/SKILL.md
similarity index 100%
rename from packages/effort-graph/skills/effort-graph/SKILL.md
rename to packages/proof/skills/proof/SKILL.md
diff --git a/packages/effort-graph/skills/effort-graph/glossary.md b/packages/proof/skills/proof/glossary.md
similarity index 100%
rename from packages/effort-graph/skills/effort-graph/glossary.md
rename to packages/proof/skills/proof/glossary.md
diff --git a/packages/effort-graph/skills/effort-graph/reference.md b/packages/proof/skills/proof/reference.md
similarity index 100%
rename from packages/effort-graph/skills/effort-graph/reference.md
rename to packages/proof/skills/proof/reference.md
diff --git a/packages/proof/skills/proof/release.json b/packages/proof/skills/proof/release.json
new file mode 100644
index 00000000..26e56748
--- /dev/null
+++ b/packages/proof/skills/proof/release.json
@@ -0,0 +1,6 @@
+{
+ "format": 1,
+ "flatbreadVersion": "1.0.0",
+ "effortGraphVersion": "1.0.0",
+ "gitTag": "v1.0.0"
+}
diff --git a/packages/effort-graph/skills/effort-graph/setup.md b/packages/proof/skills/proof/setup.md
similarity index 100%
rename from packages/effort-graph/skills/effort-graph/setup.md
rename to packages/proof/skills/proof/setup.md
diff --git a/packages/effort-graph/src/__tests__/decision-lifecycle.test.ts b/packages/proof/src/__tests__/decision-lifecycle.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/decision-lifecycle.test.ts
rename to packages/proof/src/__tests__/decision-lifecycle.test.ts
diff --git a/packages/effort-graph/src/__tests__/digest.test.ts b/packages/proof/src/__tests__/digest.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/digest.test.ts
rename to packages/proof/src/__tests__/digest.test.ts
diff --git a/packages/effort-graph/src/__tests__/ids.test.ts b/packages/proof/src/__tests__/ids.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/ids.test.ts
rename to packages/proof/src/__tests__/ids.test.ts
diff --git a/packages/effort-graph/src/__tests__/journal-barrier.test.ts b/packages/proof/src/__tests__/journal-barrier.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/journal-barrier.test.ts
rename to packages/proof/src/__tests__/journal-barrier.test.ts
diff --git a/packages/effort-graph/src/__tests__/journal-recovery.test.ts b/packages/proof/src/__tests__/journal-recovery.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/journal-recovery.test.ts
rename to packages/proof/src/__tests__/journal-recovery.test.ts
diff --git a/packages/effort-graph/src/__tests__/live-bridge.test.ts b/packages/proof/src/__tests__/live-bridge.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/live-bridge.test.ts
rename to packages/proof/src/__tests__/live-bridge.test.ts
diff --git a/packages/effort-graph/src/__tests__/lock.test.ts b/packages/proof/src/__tests__/lock.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/lock.test.ts
rename to packages/proof/src/__tests__/lock.test.ts
diff --git a/packages/proof/src/__tests__/loops.test.ts b/packages/proof/src/__tests__/loops.test.ts
deleted file mode 100644
index b3078cb4..00000000
--- a/packages/proof/src/__tests__/loops.test.ts
+++ /dev/null
@@ -1,470 +0,0 @@
-import test from 'ava';
-import {
- parseDAG,
- resolveConvergenceLoops,
- type DAG,
- type DAGConvergenceLoop,
- type RawTask,
-} from '../index.js';
-import { resolveLoopReexecuteIds } from '../converge_loop.js';
-
-const baseTasks: RawTask[] = [
- {
- id: 'research',
- depends_on: [],
- complexity: 'LOW',
- subtask_prompt: 'research',
- kind: 'task',
- },
- {
- id: 'design',
- depends_on: ['research'],
- complexity: 'MED',
- subtask_prompt: 'design',
- kind: 'task',
- },
- {
- id: 'implement',
- depends_on: ['design'],
- complexity: 'MED',
- subtask_prompt: 'implement',
- kind: 'task',
- },
- {
- id: 'review',
- depends_on: ['implement'],
- complexity: 'HIGH',
- subtask_prompt: 'review',
- kind: 'task',
- },
-];
-
-function dagWith(loops: unknown): unknown {
- return {
- title: 'loop-tests',
- tasks: baseTasks.map((t) => ({
- id: t.id,
- depends_on: t.depends_on,
- complexity: t.complexity,
- subtask_prompt: t.subtask_prompt,
- })),
- loops,
- };
-}
-
-test('parseDAG accepts a minimal loops entry with defaults', (t) => {
- const dag = parseDAG(dagWith([{ convergeOn: 'review', maxIterations: 2 }]));
- t.truthy(dag.loops);
- t.is(dag.loops!.length, 1);
- t.is(dag.loops![0].convergeOn, 'review');
- t.is(dag.loops![0].maxIterations, 2);
-});
-
-test('resolveConvergenceLoops fills defaults', (t) => {
- const resolved = resolveConvergenceLoops([
- { convergeOn: 'review', maxIterations: 2 },
- ]);
- t.is(resolved[0].id, 'loop-review');
- t.deepEqual(resolved[0].reexecute, { kind: 'ancestors' });
-});
-
-test('parseDAG rejects convergeOn referencing unknown task id', (t) => {
- t.throws(
- () => parseDAG(dagWith([{ convergeOn: 'nope', maxIterations: 2 }])),
- { message: /not a task id/ }
- );
-});
-
-test('parseDAG rejects non-positive maxIterations', (t) => {
- t.throws(
- () => parseDAG(dagWith([{ convergeOn: 'review', maxIterations: 0 }])),
- { message: /maxIterations must be a positive integer/ }
- );
- t.throws(
- () => parseDAG(dagWith([{ convergeOn: 'review', maxIterations: -1 }])),
- { message: /maxIterations must be a positive integer/ }
- );
- t.throws(
- () => parseDAG(dagWith([{ convergeOn: 'review', maxIterations: 1.5 }])),
- { message: /maxIterations must be a positive integer/ }
- );
-});
-
-test('parseDAG rejects two loops with the same convergeOn', (t) => {
- t.throws(
- () =>
- parseDAG(
- dagWith([
- { convergeOn: 'review', maxIterations: 2 },
- { convergeOn: 'review', maxIterations: 3 },
- ])
- ),
- { message: /duplicate convergeOn/ }
- );
-});
-
-test('parseDAG rejects two loops with the same explicit id', (t) => {
- t.throws(
- () =>
- parseDAG(
- dagWith([
- { id: 'shared', convergeOn: 'review', maxIterations: 2 },
- { id: 'shared', convergeOn: 'design', maxIterations: 2 },
- ])
- ),
- { message: /resolved loop id.*shared.*collides/ }
- );
-});
-
-test("parseDAG rejects loops whose resolved ids collide (explicit id matches another loop's default)", (t) => {
- // Loop 0 has no explicit id: resolves to 'loop-review' via default.
- // Loop 1 explicitly sets id: 'loop-review', convergeOn a different task.
- // Before the fix these two loops silently produced duplicate resolved ids;
- // after the fix parseDAG must throw.
- t.throws(
- () =>
- parseDAG(
- dagWith([
- { convergeOn: 'review', maxIterations: 2 },
- { id: 'loop-review', convergeOn: 'implement', maxIterations: 2 },
- ])
- ),
- { message: /resolved loop id.*loop-review.*collides/ }
- );
-});
-
-test('parseDAG rejects explicit ids that collide with defaulted loop ids', (t) => {
- t.throws(
- () =>
- parseDAG(
- dagWith([
- { convergeOn: 'review', maxIterations: 2 },
- { id: 'loop-review', convergeOn: 'design', maxIterations: 2 },
- ])
- ),
- { message: /duplicate loop id/ }
- );
-});
-
-test('parseDAG accepts explicit reexecute.tasks when the subset is dependency-closed', (t) => {
- const dag = parseDAG(
- dagWith([
- {
- convergeOn: 'review',
- maxIterations: 2,
- reexecute: {
- kind: 'tasks',
- tasks: ['research', 'design', 'implement'],
- },
- },
- ])
- );
- const reexec = dag.loops![0].reexecute!;
- t.is(reexec.kind, 'tasks');
- if (reexec.kind === 'tasks') {
- // convergeOn is injected so the loop body always re-runs the
- // convergence task itself after upstream re-execution.
- t.deepEqual([...reexec.tasks].sort(), [
- 'design',
- 'implement',
- 'research',
- 'review',
- ]);
- }
-});
-
-test('parseDAG deduplicates convergeOn from reexecute.tasks when caller includes it explicitly', (t) => {
- const dag = parseDAG(
- dagWith([
- {
- convergeOn: 'review',
- maxIterations: 2,
- reexecute: {
- kind: 'tasks',
- tasks: ['research', 'design', 'implement', 'review'],
- }, // review = convergeOn
- },
- ])
- );
- const reexec = dag.loops![0].reexecute!;
- t.is(reexec.kind, 'tasks');
- if (reexec.kind === 'tasks') {
- // 'review' must appear exactly once despite being both the convergeOn and explicit in the list
- t.deepEqual([...reexec.tasks].sort(), [
- 'design',
- 'implement',
- 'research',
- 'review',
- ]);
- }
-});
-
-test('parseDAG accepts a pause task as convergeOn (behavior: allowed, convergence semantics may be vacuous)', (t) => {
- const raw = {
- title: 'pause-convergeOn',
- tasks: [
- { id: 'gate', depends_on: [], subtask_prompt: 'wait', kind: 'pause' },
- ],
- loops: [{ convergeOn: 'gate', maxIterations: 1 }],
- };
- const dag = parseDAG(raw);
- t.is(dag.loops![0].convergeOn, 'gate');
-});
-
-test('parseDAG rejects reexecute.tasks outside the ancestor cone', (t) => {
- // 'review' depends on 'implement' which depends on 'design' which depends
- // on 'research'. A task `unrelated` that is not in that cone should be
- // rejected (we synthesize one off the side of the DAG).
- const raw = {
- title: 'cone-test',
- tasks: [
- ...baseTasks.map((t) => ({
- id: t.id,
- depends_on: t.depends_on,
- complexity: t.complexity,
- subtask_prompt: t.subtask_prompt,
- })),
- {
- id: 'sibling',
- depends_on: [],
- complexity: 'LOW',
- subtask_prompt: 'sibling',
- },
- ],
- loops: [
- {
- convergeOn: 'review',
- maxIterations: 2,
- reexecute: { kind: 'tasks', tasks: ['sibling'] },
- },
- ],
- };
- t.throws(() => parseDAG(raw), {
- message: /not the convergeOn task and is not a transitive ancestor/,
- });
-});
-
-test('parseDAG rejects reexecute.tasks containing unknown task ids', (t) => {
- t.throws(
- () =>
- parseDAG(
- dagWith([
- {
- convergeOn: 'review',
- maxIterations: 2,
- reexecute: { kind: 'tasks', tasks: ['ghost'] },
- },
- ])
- ),
- { message: /unknown task id/ }
- );
-});
-
-test('parseDAG rejects non-closed reexecute.tasks subsets', (t) => {
- t.throws(
- () =>
- parseDAG(
- dagWith([
- {
- convergeOn: 'review',
- maxIterations: 2,
- reexecute: { kind: 'tasks', tasks: ['implement'] },
- },
- ])
- ),
- { message: /must be dependency-closed/ }
- );
-});
-
-test('parseDAG rejects unknown reexecute.kind', (t) => {
- t.throws(
- () =>
- parseDAG(
- dagWith([
- {
- convergeOn: 'review',
- maxIterations: 2,
- reexecute: { kind: 'all', tasks: [] },
- },
- ])
- ),
- { message: /reexecute\.kind must be one of/ }
- );
-});
-
-test('parseDAG with no loops still works', (t) => {
- const dag = parseDAG({
- title: 'no-loops',
- tasks: [
- {
- id: 'only',
- depends_on: [],
- complexity: 'LOW',
- subtask_prompt: 'x',
- },
- ],
- });
- t.is(dag.loops, undefined);
-});
-
-test('resolveLoopReexecuteIds with ancestors returns the full cone', (t) => {
- const dag = parseDAG(
- dagWith([{ convergeOn: 'review', maxIterations: 2 }])
- ) as DAG;
- const resolved = resolveConvergenceLoops(dag.loops!);
- const ids = resolveLoopReexecuteIds(resolved[0], dag);
- t.deepEqual([...ids].sort(), ['design', 'implement', 'research', 'review']);
-});
-
-test('resolveLoopReexecuteIds with explicit tasks honors the allow-list', (t) => {
- const dag = parseDAG(
- dagWith([
- {
- convergeOn: 'review',
- maxIterations: 2,
- reexecute: {
- kind: 'tasks',
- tasks: ['research', 'design', 'implement'],
- },
- },
- ])
- ) as DAG;
- const resolved = resolveConvergenceLoops(dag.loops!);
- const ids = resolveLoopReexecuteIds(resolved[0], dag);
- // Only the explicit allow-list + convergence task itself.
- t.deepEqual([...ids].sort(), ['design', 'implement', 'research', 'review']);
-});
-
-test('resolveConvergenceLoops preserves user-provided id when set', (t) => {
- const dag = parseDAG(
- dagWith([{ id: 'review-loop', convergeOn: 'review', maxIterations: 3 }])
- );
- const resolved = resolveConvergenceLoops(dag.loops!);
- t.is(resolved[0].id, 'review-loop');
- t.is(resolved[0].maxIterations, 3);
-});
-
-test('parseDAG accepts multiple loops when their re-execution sets are disjoint', (t) => {
- const tasks = [
- {
- id: 'research',
- depends_on: [],
- complexity: 'LOW',
- subtask_prompt: 'r',
- },
- {
- id: 'docs',
- depends_on: [],
- complexity: 'MED',
- subtask_prompt: 'd',
- },
- {
- id: 'docs-review',
- depends_on: ['docs'],
- complexity: 'HIGH',
- subtask_prompt: 'dr',
- },
- {
- id: 'impl',
- depends_on: [],
- complexity: 'MED',
- subtask_prompt: 'i',
- },
- {
- id: 'impl-review',
- depends_on: ['impl'],
- complexity: 'HIGH',
- subtask_prompt: 'ir',
- },
- ];
- const dag = parseDAG({
- title: 'multi-loop',
- tasks,
- loops: [
- { convergeOn: 'docs-review', maxIterations: 2 },
- { convergeOn: 'impl-review', maxIterations: 2 },
- ],
- });
- t.is(dag.loops!.length, 2);
- const resolved = resolveConvergenceLoops(dag.loops!);
- t.deepEqual(
- resolved.map((l) => l.id),
- ['loop-docs-review', 'loop-impl-review']
- );
-});
-
-test('parseDAG rejects loops with overlapping re-execution sets', (t) => {
- t.throws(
- () =>
- parseDAG({
- title: 'overlap',
- tasks: [
- {
- id: 'shared',
- depends_on: [],
- complexity: 'LOW',
- subtask_prompt: 'shared',
- },
- {
- id: 'docs',
- depends_on: ['shared'],
- complexity: 'MED',
- subtask_prompt: 'docs',
- },
- {
- id: 'docs-review',
- depends_on: ['docs'],
- complexity: 'HIGH',
- subtask_prompt: 'docs review',
- },
- {
- id: 'impl',
- depends_on: ['shared'],
- complexity: 'MED',
- subtask_prompt: 'impl',
- },
- {
- id: 'impl-review',
- depends_on: ['impl'],
- complexity: 'HIGH',
- subtask_prompt: 'impl review',
- },
- ],
- loops: [
- { convergeOn: 'docs-review', maxIterations: 2 },
- { convergeOn: 'impl-review', maxIterations: 2 },
- ],
- }),
- { message: /must have disjoint re-execution sets/ }
- );
-});
-
-test('parseDAG rejects non-array loops', (t) => {
- t.throws(() => parseDAG(dagWith({ convergeOn: 'review' })), {
- message: /must be an array/,
- });
-});
-
-test('DAGConvergenceLoop type round-trips through resolveConvergenceLoops', (t) => {
- const declared: DAGConvergenceLoop[] = [
- {
- id: 'r',
- convergeOn: 'review',
- maxIterations: 5,
- reexecute: {
- kind: 'tasks',
- tasks: ['research', 'design', 'implement', 'review'],
- },
- },
- ];
- const resolved = resolveConvergenceLoops(declared);
- t.deepEqual(resolved[0], {
- id: 'r',
- convergeOn: 'review',
- maxIterations: 5,
- reexecute: {
- kind: 'tasks',
- tasks: ['research', 'design', 'implement', 'review'],
- },
- });
-});
diff --git a/packages/proof/src/__tests__/output-retention-phase1.test.ts b/packages/proof/src/__tests__/output-retention-phase1.test.ts
deleted file mode 100644
index 8c6c7894..00000000
--- a/packages/proof/src/__tests__/output-retention-phase1.test.ts
+++ /dev/null
@@ -1,250 +0,0 @@
-import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
-import { tmpdir } from 'node:os';
-import { dirname, join } from 'node:path';
-import { fileURLToPath } from 'node:url';
-import test from 'ava';
-
-import { parseDAG } from '../dag.js';
-import {
- buildConvergenceContext,
- extractConvergenceFindings,
-} from '../converge_loop.js';
-import {
- TaskTranscriptStore,
- taskStreamArtifactRelPath,
-} from '../task_transcript.js';
-import type { TaskState } from '../canvas_writer.js';
-import {
- CANVAS_DISPLAY_CAP,
- excerptUpstreamForPrompt,
- parseUpstreamSections,
- renderUpstreamSections,
- summarizeUpstreamForPrompt,
- UPSTREAM_SNIPPET_CAP,
-} from '../upstream_policy.js';
-import { renderCanvasSource, initialRunState } from '../canvas_writer.js';
-import { writeFindingsSidecar } from '../findings_sidecar.js';
-
-test('parseDAG accepts DAG.outputPolicy.upstream', (t) => {
- const dag = parseDAG({
- title: 'pol',
- outputPolicy: { upstream: 'full' },
- tasks: [
- {
- id: 'a',
- depends_on: [],
- complexity: 'LOW',
- subtask_prompt: 'do',
- },
- ],
- });
- t.is(dag.outputPolicy?.upstream, 'full');
-});
-
-test('parseDAG rejects invalid outputPolicy upstream value', (t) => {
- t.throws(
- () =>
- parseDAG({
- title: 'bad',
- outputPolicy: { upstream: 'everything' },
- tasks: [
- {
- id: 'a',
- depends_on: [],
- complexity: 'LOW',
- subtask_prompt: 'do',
- },
- ],
- }),
- { message: /upstream must be/ }
- );
-});
-
-test('parseDAG rejects unknown outputPolicy keys', (t) => {
- t.throws(
- () =>
- parseDAG({
- title: 'bad-key',
- outputPolicy: { upstram: 'full' },
- tasks: [
- {
- id: 'a',
- depends_on: [],
- complexity: 'LOW',
- subtask_prompt: 'do',
- },
- ],
- }),
- { message: /DAG\.outputPolicy\.upstram is not supported/ }
- );
-});
-
-test('summarize upstream attaches counted excerpt banner instead of omitting rationale', (t) => {
- const filler = 'y'.repeat(5000);
- const { excerpt } = summarizeUpstreamForPrompt(filler, UPSTREAM_SNIPPET_CAP);
- t.true(
- excerpt.includes('[...upstream excerpt:') &&
- excerpt.includes('parent output was 5000 chars')
- );
- t.false(/^[^\n]+\u2026$/u.test(excerpt.trim().split(/\n/).pop() ?? ''));
-});
-
-test('full upstream excerpt includes late marker past multi-kchar parents', (t) => {
- const preamble = 'z'.repeat(2800);
- const tailMarker = `${'x'.repeat(9100)}MARKER_LATE`;
- const blob = `${preamble}\n## Section one\nstuff\n## Blockers\n${tailMarker}`;
- const full = excerptUpstreamForPrompt(blob, 'full');
- t.true(full.includes('MARKER_LATE'));
-});
-
-test('convergence extract sees late section beyond legacy STREAM cap window', (t) => {
- const long = `${'p'.repeat(6000)}\n## Blockers\n- late blocker\n`;
- const f = extractConvergenceFindings(long);
- t.true(f.hasIssues);
- t.true(f.blockerLines.some((l) => l.includes('late blocker')));
-});
-
-test('convergence extraContext carries late blockers under full upstream excerpt mode', (t) => {
- const long = `${'p'.repeat(6000)}\n## Blockers\n- still broken\n`;
- const ctx = buildConvergenceContext('reviewer', 2, long, 'full');
- t.true(ctx.includes('## Blockers'));
- t.true(ctx.includes('still broken'));
-});
-
-test('findings sidecar uses parseSource (full transcript) over bounded resultText', async (t) => {
- const dir = mkdtempSync(join(tmpdir(), 'proof-sidecar-'));
- try {
- const ts: TaskState = {
- id: 'task-a',
- depends_on: [],
- complexity: 'LOW',
- subtask_prompt: 'x',
- status: 'FINISHED',
- model: 'gpt-5.4',
- resultText: '## Blockers\n(none)',
- };
- const longTruth = `${'z'.repeat(5000)}\n## Blockers\n- deep blocker line\n`;
- await writeFindingsSidecar(dir, ts, { parseSource: longTruth });
- const raw = readFileSync(join(dir, 'task-a.findings.json'), 'utf8');
- const parsed = JSON.parse(raw) as { sections: Record };
- t.true(parsed.sections.Blockers?.includes('deep blocker line'));
- } finally {
- rmSync(dir, { recursive: true, force: true });
- }
-});
-
-test('upstream section parsing keeps canvas truncation banner before headings', (t) => {
- const line = '[...truncated 9000 earlier chars...]';
- const body = `${line}\n## Blockers\nhit\n`;
- const sections = parseUpstreamSections(body);
- t.true(sections.some((s) => s.heading === 'Upstream truncation notice'));
- const rendered = renderUpstreamSections(sections);
- t.true(rendered.includes(line));
-});
-
-test('upstream section parsing keeps freeform preamble before headings', (t) => {
- const body = `Important preface before headings.\nStill preface.\n## Findings\nhit\n## Proposed contract\nkeep\n`;
- const sections = parseUpstreamSections(body);
- t.is(sections[0]?.heading, 'Upstream preamble');
- const rendered = renderUpstreamSections(sections);
- t.true(rendered.includes('Important preface before headings.'));
-});
-
-test('summarize upstream does not rewrite author-owned trailing ellipsis', (t) => {
- const body = [
- '## Summary',
- 'This sentence intentionally trails off…',
- '',
- '## Current contract',
- 'drop me '.repeat(500),
- '',
- '## Findings',
- 'keep this section',
- ].join('\n');
- const { excerpt } = summarizeUpstreamForPrompt(body, 500);
- t.true(excerpt.includes('trails off…'));
- t.false(excerpt.includes('[...truncated in excerpt body at char cap …]'));
-});
-
-test('task transcript mirror serializes overlapping flushes in append order', async (t) => {
- const dir = mkdtempSync(join(tmpdir(), 'proof-stream-'));
- const store = new TaskTranscriptStore();
- try {
- await store.beginMirroredAppend('task-a', dir);
- store.append('task-a', 'a');
- const first = store.flushStreamMirror('task-a');
- store.append('task-a', 'b');
- const second = store.flushStreamMirror('task-a');
- await Promise.all([first, second]);
- await store.flushStreamMirror('task-a');
- const raw = readFileSync(
- join(dir, taskStreamArtifactRelPath('task-a')),
- 'utf8'
- );
- t.is(raw, 'ab');
- } finally {
- rmSync(dir, { recursive: true, force: true });
- }
-});
-
-test('task transcript store reads existing mirror files after resume', (t) => {
- const dir = mkdtempSync(join(tmpdir(), 'proof-stream-resume-'));
- const store = new TaskTranscriptStore();
- try {
- const rel = taskStreamArtifactRelPath('task-a');
- writeFileSync(join(dir, rel), 'full transcript from prior process', 'utf8');
- store.registerExistingMirror('task-a', dir, rel);
- t.is(store.getJoined('task-a'), 'full transcript from prior process');
- } finally {
- rmSync(dir, { recursive: true, force: true });
- }
-});
-
-test('canvas render growth stays bounded by display-sized tails versus megabyte dumps', (t) => {
- const tasks = Array.from({ length: 5 }, (_, i) => ({
- id: `t${i}`,
- depends_on: [] as string[],
- complexity: 'LOW' as const,
- subtask_prompt: `${'prompt:'.repeat(200)}\n`,
- }));
- const dag = parseDAG({ title: 'canvas-env', tasks });
- const fresh = (): ReturnType =>
- initialRunState(dag, () => ({
- id: 'gpt-5.4',
- }));
-
- const baselineLen = renderCanvasSource(fresh()).length;
-
- const cappedState = fresh();
- cappedState.tasks.forEach((st) => {
- st.resultText = `[...truncated 800000 earlier chars...]\n${'a'.repeat(
- CANVAS_DISPLAY_CAP
- )}`;
- });
- const cappedLen = renderCanvasSource(cappedState).length;
-
- const leakyState = fresh();
- leakyState.tasks.forEach((st) => {
- st.resultText = `[...truncated 800000 earlier chars...]\n${'b'.repeat(
- 12000
- )}`;
- });
- const uncappedLen = renderCanvasSource(leakyState).length;
-
- t.true(cappedLen < baselineLen + 5 * CANVAS_DISPLAY_CAP + 96000);
-
- /** Longer fake transcripts should substantially grow the inlined JSON blob. */
- t.true(
- uncappedLen - cappedLen > 35000,
- 'expected materially larger stringify when payloads stay long'
- );
-});
-
-test('runOne skips children when upstream is BUDGET-EXCEEDED (guard in run_dag)', (t) => {
- const path = join(dirname(fileURLToPath(import.meta.url)), '../run_dag.ts');
- const src = readFileSync(path, 'utf8');
- const idx = src.indexOf('failedDeps = task.depends_on.filter');
- t.not(idx, -1);
- const snippet = src.slice(idx, idx + 450);
- t.true(snippet.includes("'BUDGET-EXCEEDED'"));
-});
diff --git a/packages/effort-graph/src/__tests__/pack-skills.test.js b/packages/proof/src/__tests__/pack-skills.test.js
similarity index 92%
rename from packages/effort-graph/src/__tests__/pack-skills.test.js
rename to packages/proof/src/__tests__/pack-skills.test.js
index 3fd2a324..d1080b91 100644
--- a/packages/effort-graph/src/__tests__/pack-skills.test.js
+++ b/packages/proof/src/__tests__/pack-skills.test.js
@@ -5,10 +5,10 @@ import {
} from '../../scripts/pack-skills.mjs';
const canonicalFiles = [
- 'skills/effort-graph/release.json',
- 'skills/effort-graph/SKILL.md',
- 'skills/effort-graph/reference.md',
- 'skills/effort-graph/setup.md',
+ 'skills/proof/release.json',
+ 'skills/proof/SKILL.md',
+ 'skills/proof/reference.md',
+ 'skills/proof/setup.md',
];
const release = JSON.stringify({
format: 1,
diff --git a/packages/effort-graph/src/__tests__/planner.test.ts b/packages/proof/src/__tests__/planner.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/planner.test.ts
rename to packages/proof/src/__tests__/planner.test.ts
diff --git a/packages/effort-graph/src/__tests__/preset.test.ts b/packages/proof/src/__tests__/preset.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/preset.test.ts
rename to packages/proof/src/__tests__/preset.test.ts
diff --git a/packages/effort-graph/src/__tests__/read.test.ts b/packages/proof/src/__tests__/read.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/read.test.ts
rename to packages/proof/src/__tests__/read.test.ts
diff --git a/packages/effort-graph/src/__tests__/schemas.test.ts b/packages/proof/src/__tests__/schemas.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/schemas.test.ts
rename to packages/proof/src/__tests__/schemas.test.ts
diff --git a/packages/effort-graph/src/__tests__/skills.test.ts b/packages/proof/src/__tests__/skills.test.ts
similarity index 96%
rename from packages/effort-graph/src/__tests__/skills.test.ts
rename to packages/proof/src/__tests__/skills.test.ts
index 171051f7..22a5c2e6 100644
--- a/packages/effort-graph/src/__tests__/skills.test.ts
+++ b/packages/proof/src/__tests__/skills.test.ts
@@ -6,7 +6,7 @@ import { promisify } from 'node:util';
import test from 'ava';
const run = promisify(execFile);
-const script = resolve('packages/effort-graph/scripts/sync-skills.mjs');
+const script = resolve('packages/proof/scripts/sync-skills.mjs');
test('skill projection syncs, removes stale files, and checks without writing', async (t) => {
const root = await mkdtemp(resolve(tmpdir(), 'flatbread-skills-'));
diff --git a/packages/effort-graph/src/__tests__/snapshot.test.ts b/packages/proof/src/__tests__/snapshot.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/snapshot.test.ts
rename to packages/proof/src/__tests__/snapshot.test.ts
diff --git a/packages/effort-graph/src/__tests__/writer.test.ts b/packages/proof/src/__tests__/writer.test.ts
similarity index 100%
rename from packages/effort-graph/src/__tests__/writer.test.ts
rename to packages/proof/src/__tests__/writer.test.ts
diff --git a/packages/proof/src/canvas_writer.ts b/packages/proof/src/canvas_writer.ts
deleted file mode 100644
index ea35ad46..00000000
--- a/packages/proof/src/canvas_writer.ts
+++ /dev/null
@@ -1,973 +0,0 @@
-/**
- * Renders the runner's in-memory state into a self-contained `.canvas.tsx`
- * file. The IDE hot-recompiles on file change, so calling write() repeatedly
- * gives the user a live view of the DAG run.
- *
- * The canvas is fully static React + cursor/canvas — all state is inlined as
- * a `const STATE = {...}` literal. Only that literal changes between writes;
- * the rendered template is identical.
- */
-
-import { writeFile, mkdir } from 'node:fs/promises';
-import { dirname } from 'node:path';
-import {
- formatModelSelection,
- normalizeModelSelection,
- type Complexity,
- type DAG,
- type ModelSelection,
- type ModelSpec,
- type TaskKind,
-} from './dag.js';
-
-export type TaskStatus =
- | 'PENDING'
- | 'RUNNING'
- | 'FINISHED'
- | 'ERROR'
- | 'AWAITING_APPROVAL'
- | 'BUDGET-EXCEEDED';
-
-export interface TaskState {
- id: string;
- depends_on: string[];
- complexity: Complexity;
- subtask_prompt: string;
- status: TaskStatus;
- model: string;
- modelSelection?: ModelSelection;
- /** `'task'` (default), `'pause'`, or `'oracle'`. Undefined is normalized to `'task'`. */
- kind?: TaskKind;
- /**
- * Shell command for `kind: 'oracle'` tasks. Surfaced in the canvas so the
- * gate's pass/fail criterion is visible without reading the result body.
- * Undefined for every other kind.
- */
- command?: string;
- /** Regex source the oracle's output is matched against (defaults to `.*`). */
- expect?: string;
- startedAt?: number;
- finishedAt?: number;
- resultText?: string;
- /**
- * Relative path (under the run artifact directory) to the append-only stream
- * mirror for this task's full assistant transcript. Canvas shows bounded
- * `resultText`; this pointer is for locating the authoritative stream file.
- */
- transcriptPath?: string;
- errorMessage?: string;
- inputTokens?: number;
- outputTokens?: number;
- durationMs?: number;
- /**
- * Convergence-loop re-execution counter. 0/undefined = original run; bumped
- * by 1 each time `--converge-on` re-runs this task to address upstream
- * reviewer findings.
- */
- iteration?: number;
- /**
- * Absolute path to the sentinel file the runner created for a `kind: 'pause'`
- * task. Set when status === `AWAITING_APPROVAL`; persisted afterwards so the
- * canvas can show "approved by removing ".
- */
- checkpointPath?: string;
-}
-
-export interface RunState {
- title: string;
- startedAt: number;
- finishedAt?: number;
- /**
- * Aggregate outcome of the entire run.
- *
- * - `SUCCESS` — every task finished cleanly.
- * - `FAILED` — at least one task ended in `ERROR`.
- * - `INTERRUPTED` — the runner caught a fatal signal (SIGINT/SIGTERM/SIGHUP).
- * - `BUDGET_EXCEEDED` — a budget ceiling was crossed: either the
- * `--converge-on` loop exhausted `--max-iterations` with the convergence
- * task still reporting blockers, OR `dag.budget.maxTokensTotal` was
- * exceeded. Both paths exit with `EXIT_BUDGET_EXCEEDED` (4) so
- * wrappers can branch on budget overflows without parsing logs. Hyphen
- * form (`BUDGET-EXCEEDED`) is reserved for the per-task `TaskStatus`;
- * the run-level field uses underscores to match the rest of this enum.
- * - `RESTARTING_RUNNER` — runner runtime files changed mid-run; the
- * supervisor should relaunch the runner from persisted state so the next
- * process executes the newly edited source.
- */
- runOutcome?:
- | 'SUCCESS'
- | 'FAILED'
- | 'INTERRUPTED'
- | 'BUDGET_EXCEEDED'
- | 'RESTARTING_RUNNER';
- runMessage?: string;
- tasks: TaskState[];
-}
-
-export function initialRunState(
- dag: DAG,
- modelFor: (c: Complexity) => ModelSpec
-): RunState {
- return {
- title: dag.title,
- startedAt: Date.now(),
- tasks: dag.tasks.map((t) => {
- const modelSelection = normalizeModelSelection(
- modelFor(t.complexity),
- `model for task ${t.id}`
- );
- return {
- id: t.id,
- depends_on: t.depends_on,
- complexity: t.complexity,
- subtask_prompt: t.subtask_prompt,
- status: 'PENDING',
- model: formatModelSelection(modelSelection),
- modelSelection,
- // Normalize undefined kind → 'task' so downstream consumers (canvas
- // template, runner dispatcher) never have to ?? again.
- kind: t.kind ?? 'task',
- // Surface oracle-only fields so the canvas can render the gate's
- // command / expectation without reading the streamed result body.
- ...(t.kind === 'oracle'
- ? { command: t.command, expect: t.expect }
- : {}),
- };
- }),
- };
-}
-
-/**
- * Debounced writer. Multiple write() calls inside the debounce window collapse
- * into one filesystem write — the latest state always wins.
- */
-export class CanvasWriter {
- private pending: RunState | null = null;
- private timer: NodeJS.Timeout | null = null;
- private inFlight: Promise = Promise.resolve();
- private writeSeq = 0;
- private lastFailedWriteSeq = 0;
- private lastWriteError: unknown = null;
-
- constructor(
- private readonly canvasPath: string,
- private readonly debounceMs: number = 200
- ) {}
-
- schedule(state: RunState): void {
- this.pending = state;
- if (this.timer) return;
- this.timer = setTimeout(() => {
- this.timer = null;
- const snapshot = this.pending;
- this.pending = null;
- if (snapshot) {
- this.enqueueWrite(snapshot);
- }
- }, this.debounceMs);
- }
-
- /** Force-flush any pending write and await disk completion. */
- async flush(): Promise {
- if (this.timer) {
- clearTimeout(this.timer);
- this.timer = null;
- }
- const snapshot = this.pending;
- this.pending = null;
- const targetWriteSeq = snapshot
- ? this.enqueueWrite(snapshot)
- : this.writeSeq;
- await this.inFlight;
- if (targetWriteSeq > 0 && this.lastFailedWriteSeq === targetWriteSeq) {
- throw this.lastWriteError;
- }
- }
-
- private enqueueWrite(state: RunState): number {
- const seq = ++this.writeSeq;
- this.inFlight = this.inFlight.then(async () => {
- try {
- await this.writeNow(state);
- if (this.lastFailedWriteSeq < seq) {
- this.lastWriteError = null;
- }
- } catch (err) {
- this.lastFailedWriteSeq = seq;
- this.lastWriteError = err;
- }
- });
- return seq;
- }
-
- private async writeNow(state: RunState): Promise {
- const source = renderCanvasSource(state);
- await mkdir(dirname(this.canvasPath), { recursive: true });
- await writeFile(this.canvasPath, source, 'utf8');
- }
-}
-
-export function renderCanvasSource(state: RunState): string {
- const stateLiteral = JSON.stringify(state, null, 2);
- return `${HEADER}\n\nconst STATE: RunState = ${stateLiteral};\n\n${BODY}\n`;
-}
-
-const HEADER = `/* AUTO-GENERATED by @flatbread/proof. Do not edit by hand — the runner overwrites this file. */
-import {
- Card,
- CardBody,
- CardHeader,
- Divider,
- H1,
- H2,
- Pill,
- Stack,
- Stat,
- Text,
- computeDAGLayout,
- useHostTheme,
-} from 'cursor/canvas';
-import { useEffect, useMemo, useState } from 'react';
-
-type TaskStatus =
- | 'PENDING'
- | 'RUNNING'
- | 'FINISHED'
- | 'ERROR'
- | 'AWAITING_APPROVAL'
- | 'BUDGET-EXCEEDED';
-type Complexity = 'HIGH' | 'MED' | 'LOW';
-type TaskKind = 'task' | 'pause' | 'oracle';
-
-// Keep in sync with ModelParameterValue / ModelSelection in dag.ts.
-interface ModelParameterValue {
- id: string;
- value: string;
-}
-
-interface ModelSelection {
- id: string;
- params?: ModelParameterValue[];
-}
-
-interface TaskState {
- id: string;
- depends_on: string[];
- complexity: Complexity;
- subtask_prompt: string;
- status: TaskStatus;
- model: string;
- modelSelection?: ModelSelection;
- kind?: TaskKind;
- command?: string;
- expect?: string;
- startedAt?: number;
- finishedAt?: number;
- resultText?: string;
- /**
- * Relative path (artifact dir) for the authoritative stream transcript.
- * Canvas shows bounded resultText strings; transcriptPath reveals the mirror file path.
- */
- transcriptPath?: string;
- errorMessage?: string;
- inputTokens?: number;
- outputTokens?: number;
- durationMs?: number;
- iteration?: number;
- checkpointPath?: string;
-}
-
-interface RunState {
- title: string;
- startedAt: number;
- finishedAt?: number;
- runOutcome?:
- | 'SUCCESS'
- | 'FAILED'
- | 'INTERRUPTED'
- | 'BUDGET_EXCEEDED'
- | 'RESTARTING_RUNNER';
- runMessage?: string;
- tasks: TaskState[];
-}`;
-
-const BODY = String.raw`const NODE_H = 64;
-const SCROLL_STORAGE_KEY = '@flatbread/proof:scroll-y';
-const COMPLETED_DOT_COLOR = '#22c55e';
-const AWAITING_DOT_COLOR = '#f59e0b';
-const BUDGET_DOT_COLOR = '#ef4444';
-const COMPACT_BREAKPOINT_PX = 720;
-
-function effectiveKind(t: TaskState): TaskKind {
- return t.kind ?? 'task';
-}
-
-function pillToneFor(status: TaskStatus): 'neutral' | 'info' | 'success' | 'warning' {
- switch (status) {
- case 'PENDING':
- return 'neutral';
- case 'RUNNING':
- return 'info';
- case 'FINISHED':
- return 'success';
- case 'ERROR':
- return 'warning';
- case 'AWAITING_APPROVAL':
- return 'warning';
- case 'BUDGET-EXCEEDED':
- return 'warning';
- }
-}
-
-function complexityTone(c: Complexity): 'neutral' | 'info' | 'warning' {
- switch (c) {
- case 'HIGH':
- return 'warning';
- case 'MED':
- return 'info';
- case 'LOW':
- return 'neutral';
- }
-}
-
-function formatDuration(ms?: number): string {
- if (ms === undefined) return '—';
- if (ms < 1000) return ms + 'ms';
- const s = ms / 1000;
- if (s < 60) return s.toFixed(1) + 's';
- const m = Math.floor(s / 60);
- const rem = Math.round(s - m * 60);
- return m + 'm ' + rem + 's';
-}
-
-function elapsed(state: RunState): number {
- const end = state.finishedAt ?? Date.now();
- return end - state.startedAt;
-}
-
-function totalTokens(state: RunState): { input: number; output: number } {
- let input = 0;
- let output = 0;
- for (const t of state.tasks) {
- input += t.inputTokens ?? 0;
- output += t.outputTokens ?? 0;
- }
- return { input, output };
-}
-
-function taskElementId(taskId: string): string {
- return 'task-card-' + taskId;
-}
-
-function useViewportWidth(): number {
- const [width, setWidth] = useState(1024);
-
- useEffect(() => {
- if (typeof window === 'undefined') return;
- const update = (): void => setWidth(window.innerWidth);
- update();
- window.addEventListener('resize', update);
- return () => window.removeEventListener('resize', update);
- }, []);
-
- return width;
-}
-
-function getScrollY(): number {
- if (typeof window === 'undefined') return 0;
- return Math.max(window.scrollY ?? 0, 0);
-}
-
-function saveScrollY(): void {
- if (typeof window === 'undefined') return;
- try {
- window.sessionStorage.setItem(SCROLL_STORAGE_KEY, String(getScrollY()));
- } catch {
- // ignore storage failures
- }
-}
-
-function restoreScrollY(): void {
- if (typeof window === 'undefined') return;
- let target = 0;
- try {
- const raw = window.sessionStorage.getItem(SCROLL_STORAGE_KEY);
- if (!raw) return;
- const parsed = Number(raw);
- if (!Number.isFinite(parsed) || parsed <= 0) return;
- target = Math.floor(parsed);
- } catch {
- return;
- }
-
- // Retry because hot-reload can run before content height has settled.
- let attempts = 0;
- const maxAttempts = 8;
- const tick = (): void => {
- attempts += 1;
- const scrollHeight = Math.max(
- document.documentElement?.scrollHeight ?? 0,
- document.body?.scrollHeight ?? 0,
- );
- const maxY = Math.max(scrollHeight - window.innerHeight, 0);
- if (maxY <= 0) {
- if (attempts < maxAttempts) window.requestAnimationFrame(tick);
- return;
- }
- const desiredY = Math.min(target, maxY);
- window.scrollTo({ top: desiredY, behavior: 'auto' });
- if (attempts < maxAttempts && Math.abs(getScrollY() - desiredY) > 2) {
- window.requestAnimationFrame(tick);
- }
- };
- window.requestAnimationFrame(tick);
-}
-
-function DAGGraph({
- state,
- onNodeClick,
-}: {
- state: RunState;
- onNodeClick?: (taskId: string) => void;
-}): JSX.Element {
- const theme = useHostTheme();
- const viewportWidth = useViewportWidth();
- const isCompact = viewportWidth < COMPACT_BREAKPOINT_PX;
- const nodeWidth = isCompact ? 168 : 200;
- const nodeGap = isCompact ? 24 : 40;
- const rankGap = isCompact ? 60 : 72;
- const layoutPadding = isCompact ? 12 : 24;
- const titleLimit = Math.max(12, Math.floor((nodeWidth - 44) / 7));
- const layout = computeDAGLayout({
- nodes: state.tasks.map((t) => ({ id: t.id })),
- edges: state.tasks.flatMap((t) =>
- t.depends_on.map((d) => ({ from: d, to: t.id })),
- ),
- direction: 'vertical',
- nodeWidth,
- nodeHeight: NODE_H,
- rankGap,
- nodeGap,
- padding: layoutPadding,
- });
-
- const byId = new Map(state.tasks.map((t) => [t.id, t]));
-
- function nodeFill(status: TaskStatus): string {
- switch (status) {
- case 'PENDING':
- return theme.fill.tertiary;
- case 'RUNNING':
- return theme.fill.secondary;
- case 'FINISHED':
- return theme.fill.secondary;
- case 'ERROR':
- return theme.fill.secondary;
- case 'AWAITING_APPROVAL':
- return theme.fill.secondary;
- case 'BUDGET-EXCEEDED':
- return theme.fill.secondary;
- }
- }
-
- function nodeStroke(status: TaskStatus): string {
- switch (status) {
- case 'PENDING':
- return theme.stroke.tertiary;
- case 'RUNNING':
- return theme.accent.primary;
- case 'FINISHED':
- return COMPLETED_DOT_COLOR;
- case 'ERROR':
- return theme.stroke.primary;
- case 'AWAITING_APPROVAL':
- return AWAITING_DOT_COLOR;
- case 'BUDGET-EXCEEDED':
- return BUDGET_DOT_COLOR;
- }
- }
-
- function statusGlyph(status: TaskStatus): string {
- switch (status) {
- case 'PENDING':
- return '○';
- case 'RUNNING':
- return '◐';
- case 'FINISHED':
- return '●';
- case 'ERROR':
- return '×';
- case 'AWAITING_APPROVAL':
- return '⏸';
- case 'BUDGET-EXCEEDED':
- return '⊘';
- }
- }
-
- function statusGlyphColor(status: TaskStatus): string {
- switch (status) {
- case 'PENDING':
- return theme.text.tertiary;
- case 'RUNNING':
- return theme.accent.primary;
- case 'FINISHED':
- return COMPLETED_DOT_COLOR;
- case 'ERROR':
- return theme.text.primary;
- case 'AWAITING_APPROVAL':
- return AWAITING_DOT_COLOR;
- case 'BUDGET-EXCEEDED':
- return BUDGET_DOT_COLOR;
- }
- }
-
- return (
-