diff --git a/.claude/commands/i/engine-active-goal.md b/.claude/commands/i/engine-active-goal.md new file mode 100644 index 0000000..1dd82e7 --- /dev/null +++ b/.claude/commands/i/engine-active-goal.md @@ -0,0 +1,116 @@ +# /engine-active-goal + +## Plain English + +Session-bound active-goal primitives for accumulating several small change +requests onto ONE goal's branch/worktree instead of minting a new goal — and +therefore a new branch, a new worktree seed, and a new dev-server boot — for +every request. A **goal** is an objective; several requests toward the same +objective belong on one branch and one PR, with tasks as the unit beneath it. +This command is the mechanical support for that: it does NOT decide anything by +itself except the one piece that must never be left to model judgement (the +always-a-new-goal list in step 2). + +## When to use this + +Inside the engine-goal-run and engine-goal-new steps, on every follow-up change +request in a session that already has an active goal/worktree open: + +1. `check-scope` the paths the request is about to touch. If it fires, say so + out loud and create a new goal (the engine-goal-create step); do not + improvise around it. +2. If it doesn't fire, the remaining call is judgement: does this fit the + bound goal's stated objective (same feature area)? If yes, stay on its + worktree and add another task under it (the engine-create step, or the + bridge's `pm_spawn_tasks`) rather than opening a new goal. +3. `grow` the bound goal's description so its stated scope keeps matching the + accumulated diff — do this every time scope is added, not just at the end. +4. Periodically `rebase` the active branch onto the default branch, especially + before a long pause or handing the session off. + +## Prerequisites (one-time) + +- Same `.build/.sync` + `PM_DAEMON_TOKEN` as the engine-goal and + engine-goal-create steps. +- `bind`, `grow` and `rebase` key the binding on the AI session's id, read from + `CLAUDE_CODE_SESSION_ID` — set automatically by sessions that support it. + Where your tool does not set it, those three refuse and say so: skip the + binding, pass `--goal ` to `grow` explicitly, and rebase with plain + `git fetch` + `git rebase origin/`. `status` and `unbind` + degrade gracefully without it, and `check-scope` never needs it at all (it's a + pure, offline classification). + +## Steps + +1. **Bind the session to the goal it just took/created**, once, right after + `ibuild engine goal take ` (or a fresh `goal-create`): + ```bash + ibuild engine active-goal bind + ``` + Overwrites any prior binding for this session — a session accumulates on + ONE goal at a time. + +2. **Before implementing a follow-up request**, classify the paths it's about + to touch: + ```bash + ibuild engine active-goal check-scope [path...] + ``` + Exits 0 either way — the verdict is in the printed text, never silent: + - **`ALWAYS a new goal`** — the paths hit schema/migrations, an API route, + an engine/daemon wire-contract surface, auth, or billing. These need the + full queued pipeline regardless of size — create a new goal (the + engine-goal-create step) and say which reason fired. + - **`clear`** — the mechanical check found nothing. The ONE remaining + question is a judgement call this command deliberately does not + make: does the request fit the bound goal's stated objective? Same + feature area → stay on this worktree. Different feature area → new goal + anyway, and say so. + +3. **When staying on the bound goal**, fold the new scope into its + description as soon as you know it (don't wait until the end of the + session): + ```bash + ibuild engine active-goal grow --add "" + ibuild engine active-goal grow --done-when "" + ``` + Both flags may be passed together in one call. `--add` appends a bullet to + a `## Session growth` section (a running log of what accumulated); + `--done-when` REPLACES the `## Done when` section's body, since acceptance + criteria are graded from the latest version and a stale prior version left + in place would just be confusing. Omit `--goal` to target the bound goal; + pass it to target a different one explicitly. + +4. **Periodically, and always before a long pause or handoff**, keep the + branch from rotting against a fast-moving `main`: + ```bash + ibuild engine active-goal rebase + ``` + Fetches and rebases onto the repo's default branch (or `--base `). + A clean rebase or "already up to date" prints and exits 0. A **conflict is + surfaced, not resolved** — the rebase is left in progress (git's own + mid-rebase state) so you resolve it the same way you would by hand: + `git status` to see the conflicted files (also listed in the command's own + output), fix them, `git add`, `git rebase --continue` (or `git rebase + --abort` to back out). + +5. **Check what's bound**, or clear it when the session is done accumulating + (e.g. after the goal is delivered and a genuinely new objective starts): + ```bash + ibuild engine active-goal status + ibuild engine active-goal unbind + ``` + +## Notes + +- The binding is a small pointer file in the OS temp directory, keyed by the + session id. It tracks which GOAL a session is accumulating scope on, which + outlives any single run. +- `check-scope`'s always-new-goal list is intentionally **mechanical, not a + model judgement call** — an assertion that can't be verified isn't worth + making. Only "is this the same feature area?" is left to you, and this + command says so explicitly every time rather than silently deciding it. +- This command never creates, admits, queues, or spawns anything itself — it + only reads/writes the goal's description and the session pointer, and runs + `git fetch`/`git rebase` in the current worktree. Creating a new goal is + still the engine-goal-create step; adding a task under the bound goal is + still the engine-create step (or `pm_spawn_tasks`). diff --git a/.claude/commands/i/engine-gates.md b/.claude/commands/i/engine-gates.md new file mode 100644 index 0000000..5276494 --- /dev/null +++ b/.claude/commands/i/engine-gates.md @@ -0,0 +1,142 @@ +# /engine-gates + +## Plain English + +Run **this repo's own review checks** against the code you have checked out, and +record what they found against the engine run you're holding — from the terminal, +with no fleet machine involved. + +A task's **review** phase finishes on the gate reports a fleet machine normally +produces: the test result, the patch-coverage measurement, and one result per +declared check (typecheck, lint, import cycles, integration). Without this, a +hand-driven review has no way to produce them, and the engine parks the task +waiting for a verdict nobody can give it. This runs the **same commands** the +fleet runs (the ones this repo declares in `pm-agent.yaml`) and records the +**same reports**, through the same code. + +## When to use + +- The developer says "run the checks", "run the gates", "run the review checks", + "why is my review stuck waiting", or "the review says the gates couldn't run" — + while holding an engine run on the **review** phase. +- Before `ibuild engine report` on a hand-driven review: the report is what + applies these results, so record them first. + +Developers never type this — they say it in natural language and you run the flow. + +## Prerequisites + +- A claimed run on the **review** phase: `.goals/.engine` is present (written by + `ibuild engine work` / `ibuild engine claim`) and its `phase` is `review`. The + command refuses on any other phase, because there is no review to record a + verdict for yet. +- A **project sync token** in `.build/.sync` as `BUILD_SYNC_TOKEN=pm_sync_…` — + the same credential `pm_submit_artifact` uses, and what stamps the reports with + this run's provenance. +- A **daemon token** in `.env.local` as `PM_DAEMON_TOKEN=pm_daemon_…` — it reads + the repo's declared commands and keeps the run's lease alive while the suite + runs. `ibuild setup` (auto-run by the first `ibuild sync`) writes both. +- Run from inside the checkout the run belongs to — the gates grade whatever is + checked out right here. + +## Steps + +1. **See what would run**, without running it: + ```bash + ibuild engine gates --dry-run + ``` + Prints the actual commands, which tests it would select and why, and what the + results would do to the task. Runs nothing, records nothing, exits 0. + +2. **Run them**: + ```bash + ibuild engine gates --yes + ``` + **This changes shared state**, so it asks first: in a terminal you're asked to + continue; with no terminal (a script, or an agent's shell) it refuses unless + you pass `--yes`. Live output streams to your terminal as it goes. + + When it finishes it prints one line per report — what was graded and how it + came out — and says which selection ran. + +3. **Then report the run** (only once the gates are green): + ```bash + ibuild engine report --yes + ``` + +## Options + +- `--full` — grade the **whole** declared suite instead of only the tests your + branch's changes affect. Use it when you want the broad answer, or when a + change's blast radius is wider than its diff suggests. + You do **not** need it to clear a standing block: if the task still carries a red + test suite or an open patch-coverage block, the command reads that off the task + and grades the whole suite anyway, saying so before it starts. (An affected-scope + green cannot clear either block — the engine refuses a narrow result as evidence + for one raised by the whole suite — so that round would be spent for nothing.) + If it cannot reach the task to check, it says so and runs the default selection. +- `--no-setup` — skip the repo's declared install/codegen step and use the + dependencies already in this checkout. Faster, but a stale dependency tree can + redden a perfectly good change — so the default runs the setup. +- `--base=` — diff against `` instead of this repo's default branch. + Reach for it when the goal's pull request targets something other than `main`; + otherwise the coverage measurement is taken against a much bigger diff than the + one under review. + +## Notes + +- **The whole loop, end to end:** the gates are the review phase's middle step — + open the run with the engine-work step, record deliverables with the + engine-artifact step, then report with the engine-report step. +- **Re-runnable, by design.** Fix what failed and run it again — the newest report + for each gate is the one that counts. No flag, no cleanup, nothing to undo. This + is why it is its own verb rather than a flag on `ibuild engine report`: running + the checks is not reporting. +- **A gate this repo declares no command for runs nothing and records nothing.** + Silence means "not graded" — never "passed". If the repo declares no gate + commands at all, the command refuses rather than record a green report for + commands that never ran; declare them in `pm-agent.yaml` and try again. +- **A red gate is recorded.** It becomes a must-fix finding the task cannot + complete over — the same one a fleet run would have raised. That is the point: + fix the code, run `ibuild engine gates --yes` again, and the newer report + replaces it. +- **Exit code**: 0 when every gate that ran came back green, 1 when one came back + red or when nothing ran at all. The reports are recorded either way — the + summary says so in words, so don't read the exit code as "nothing was sent". +- **It runs in your environment, minus what would grade the machine.** The commands + see your shell and whatever `.env` files your suite already reads — not the fleet + container's stored environment. If the repo declares environment variables this + shell doesn't set, the preview says so before you commit to the run. + Four things are WITHHELD, the same list the fleet's own gate withholds: `*_API_KEY`, + `*_AUTH_TOKEN`, the `ANTHROPIC_*` / `CLAUDE_*` / `CLAUDECODE` namespaces, and + `GIT_CONFIG*`. A gate grades your diff, and an ambient git-config override (or a + provider key a suite happens to read) makes it grade your laptop instead. The run + names the withheld variables once, by name only, so you can see what it took. + `PM_HERMETIC_GATE_ENV_DISABLED=1` turns it off if your suite genuinely needs one + of them. + This applies to the commands that GRADE. The repo's declared install/codegen step + still sees your whole shell, because an install may legitimately need a registry + token or a git `url.insteadOf` rewrite — so a variable that breaks *setup* is + still yours to unset. +- **It keeps the run alive.** A full suite takes minutes. This checks in while it + runs, and stops early (recording nothing) if the run has already been closed — + rather than spending ten more minutes grading a run that can't accept the result. +- **It grades your branch MERGED WITH ITS BASE, the same tree CI grades.** CI + checks out the pull request's merge commit, so a gate run against the bare branch + answers a different question. So before the declared commands run, the run merges + the base into the checkout, and resets it back out afterwards. It says which it + did on the line above the suite, and the recorded `test_report` carries a + `gateTree` block saying the same thing (`merged` / `up_to_date` / `branch` plus + the reason it fell back). + **A checkout with uncommitted changes to tracked files refuses the merge** and + grades the bare branch instead, naming the files — putting the tree back is a hard + reset, and your unstaged work is not the gate's to discard. A merge conflict, an + unreachable base or any git error falls back the same way; none of them reds a gate. + `PM_GATE_MERGE_BASE_DISABLED=1` skips the merge entirely and grades the bare branch, + at the cost of a result that can disagree with CI at the same head. +- **Commit before you start, and don't edit while it runs.** The reset at the end + is a hard reset of the checkout: edits made mid-run are lost. +- **Ctrl-C is safe.** A gate that didn't finish records nothing, and your commits are + never touched. Interrupting inside the brief merge window is the one case that can + leave the base merged into your checkout: `git status` shows it, and + `git reset --hard ORIG_HEAD` (or your branch's head sha) puts it back. diff --git a/.claude/commands/i/engine-goal-files.md b/.claude/commands/i/engine-goal-files.md new file mode 100644 index 0000000..f84494c --- /dev/null +++ b/.claude/commands/i/engine-goal-files.md @@ -0,0 +1,140 @@ +--- +name: engine-goal-files +version: "1.0.0" +description: List or download the current goal's attachments (specs, mockups, sample data) into the worktree so they can actually be read. +--- +# /engine-goal-files + +## Plain English + +Show — and pull onto disk — the files attached to a **goal**: the spec, mockup, +or sample data someone uploaded in the Build web app. The browser download route +is cookie-only, so without this a terminal session simply cannot read them. + +By default it reports the current run's goal (resolved from `.goals/.engine`); +pass a goal to inspect a specific one — either its **display number** (`G#2320`, +`#2320` or just `2320`, the number in the web app's goal URL) or its id. With +`--download` the bytes land in `.tasks/attachments//` and your session +can open them by path. + +> **Read the attachments before you implement.** A goal that carries a spec or a +> mockup means the written description is deliberately incomplete. Implementing +> without opening them is guessing. + +## When to use + +- Before starting work on a goal — always, as part of reading the goal. +- The developer says "what's attached to this goal", "download the spec", + "there's a mockup on the goal". +- The goal text references a document, screenshot, or data file you can't see. + +Developers never type this — they say it in natural language and you run the +flow on their behalf (see `.build/CLAUDE.md` §3). + +## Prerequisites + +- **Nothing**, in the normal case. The fleet and `ibuild engine work` (local) + **pre-stage** a goal's attachments into `.tasks/attachments//` when + they set up the run, and a staged read needs neither `.build/.sync` nor any + token. +- To pass a **display number** (`#2320`) rather than an id you need a **daemon + token** plus `.build/.sync`, because the number has to be looked up. An id + needs neither and always works. Without a token, a `#N` argument is refused + with that advice rather than silently answered about the wrong goal. +- To discover attachments that were *not* staged you need a **daemon token** in + `.env.local` as `PM_DAEMON_TOKEN=pm_daemon_…` (Build web app → Settings → Agent + Tokens) plus `.build/.sync` for the base URL. A project **sync** token is not + enough: it can fetch a known attachment's bytes but cannot discover a goal's + file ids. + +## Steps + +### 1. List what's attached + +```bash +ibuild engine goal-files +``` + +- No argument → the current run's goal (from `.goals/.engine`). +- Explicit goal → `ibuild engine goal-files `, where `` is a + display number (`G#2320` / `#2320` / `2320`), a goal slug or title fragment, + or a goal id. Anything but an id is resolved through the same lookup + `ibuild engine goal` and `ibuild engine watch` use. +- Prints each attachment's filename, content type, size, and id — or + `Goal has no attachments.`, which is a perfectly normal answer. +- If the files are already staged on disk it prints their paths instead and + makes no network call at all — open them directly. + +### 2. Download them into the worktree + +```bash +ibuild engine goal-files --download # current run's goal +ibuild engine goal-files --download # a specific goal (#2320 or an id) +``` + +Writes each file to `.tasks/attachments//` (`-d` is a synonym +for `--download`), plus an `index.json` manifest, so a later run holding only a +sync token can enumerate them with no API call. + +### 3. Read them, then work + +Open the downloaded paths and treat their contents as part of the goal's +requirements before writing any code. + +## An unreadable attachment is an INFRA failure — never a design discussion + +This is a hard rule, not a preference (it is the same rule as `.build/CLAUDE.md` +§4). + +If this command reports any of the following: + +- the `ENGINE_ATTACHMENT_INFRA_FAILURE` marker / a `.UNREADABLE` sentinel in + `.tasks/attachments//` (it exits with a dedicated non-`1` code, `3`), +- an attachment fetch failing with **401 / 403**, +- or that it cannot read or stage the goal's attachments at all, + +then the goal HAS attachments whose contents are unknown to this run. That is an +**infrastructure / credentials failure** for an operator to fix (re-upload, fix +the daemon token, re-run so staging retries) — not a scope gap. + +**Do:** + +1. **Stop.** Do not implement against a guessed spec. +2. Fail the run loudly — `ibuild engine report --fail` — naming the `goalId` and + quoting the exact error text, so the fleet recognises the signature and can + re-vend credentials and requeue. + +**Do NOT** route it into `pm_request_design_discussion`, `pm_ask_question`, or +any other clarification pause. A clarification round-trip cannot heal a broken +credential, and the server rejects attachment/credential-flavoured design +questions as a backstop. + +If the goal genuinely has **no** attachments, the "nothing staged" message is +expected — proceed normally. + +## Failure recovery + +- **`No goalId given and no active run (.goals/.engine absent)`** — you're not + inside a claimed run. Pass a goal ref, or claim/open the task first (the + engine-claim or engine-work step). +- **`Can't look up "#2320" here`** — a display number needs a lookup, and either + `.build/.sync` or the daemon token is missing. Pass the goal's id instead + (copy it from the goal's page in the web app, or from the folder names under + `.tasks/attachments/`), or run with no argument inside a claimed run. +- **"nothing staged on disk and no daemon token available"** — staging didn't + run or had nothing to stage. Re-run `ibuild engine work` / re-claim so staging + retries, or set `PM_DAEMON_TOKEN` to list directly. If the goal's work depends + on an attachment you still can't read, apply the INFRA rule above. +- **`.build/.sync not found`** — this folder isn't linked to a Build project; + run `ibuild sync` from the project root (or `ibuild sync --paste-token`). + +## Notes + +- Filenames are basename-stripped before writing, so a server-supplied path can + never escape `.tasks/attachments//`. +- Resolution order is: **staged manifest on disk** → **daemon-token discovery + + download** → a clear "can't discover" message. The staged path is what makes + this work inside a fleet container that holds only a project sync token. +- Related: the engine-work step (stages attachments when it opens a run), the + engine-goal step, and the engine-report step (`--fail` is how you report the + infra failure above). diff --git a/.claude/commands/i/engine-goal-new.md b/.claude/commands/i/engine-goal-new.md new file mode 100644 index 0000000..b29b172 --- /dev/null +++ b/.claude/commands/i/engine-goal-new.md @@ -0,0 +1,138 @@ +--- +name: engine-goal-new +version: "1.0.0" +description: Create a goal from a request, then ask how to run it — human here, human in a new session, or the fleet — and apply that choice. +--- +# /engine-goal-new + +## Plain English + +Turn a request or the current conversation into one or more engine goals, then +ask the developer **how each should run** rather than assuming. This is the +"start a goal for X" flow: it stops one step short of building anything, so +the run-mode choice is always a deliberate one, not a default. When a request +decomposes into several independently shippable outcomes, this also ASKS +whether to create them as a tracked Group rather than filing them flat +silently — see step 2. + +## When to use + +- "start a goal for X", "new goal for X", "file a goal for this bug". +- The developer describes work with no existing goal to attach it to (check + the engine-goals step first — never create a duplicate). + +## Prerequisites (one-time) + +Same as every engine command: `PM_DAEMON_TOKEN` in `.env.local`, run from the +project root or below. + +## Steps + +### 1. Dedupe first + +```bash +ibuild engine goals +``` + +Match on intent. If something already covers this, report its state +(`ibuild engine goal status `) and stop here — reuse it via the +engine-goal-run step, don't create a second one. + +### 2. Decide how many goals this really is + +Before creating anything, check whether the request is genuinely **several +independently reviewable/shippable outcomes** — not just several steps toward +one PR. See "Tasks vs Group parts" below for the line; most requests are one +goal, even ones that need several implementation steps. + +- **One goal** → skip to step 3. +- **More than one** → do NOT file the parts flat and do NOT decide silently. + Ask the developer, listing the parts (with `pm_ask_question` if the PM + bridge is configured, your tool's own multiple-choice question otherwise): + + > "This looks like N separate outcomes — ``, ` title>`, … . Create them as a Group (tracked and delivered together), or + > as N separate goals?" + + - **"Create as a Group"** → create the parent goal first (step 3, using a + title that names the umbrella), then create each part (step 3, once per + part) with `--parent ` — `goal-create` already accepts + `--parent`; there is no separate "group" flag. + - **"Create as separate goals"** → create each part as its own goal (step 3, + once per part), with no `--parent`. + +#### Tasks vs Group parts — the one discriminator + +A goal already decomposes into **tasks** — a planning task, then one or more +implementation tasks, all shipping through the goal's single PR. A Group is +not a second way to say the same thing: + +- **Task**: a step toward ONE PR. Shares the goal's single branch, single + review, single acceptance. If the pieces would only ever be accepted or + rejected together, they're tasks under one goal — the default. +- **Group part**: its own reviewable, shippable unit — own branch, own PR, + own review/acceptance, billed on its own delivery. Propose a Group only + when the pieces could each be accepted or rejected independently, or one PR + would be a mixed PR nobody could review, or they target different + repos/surfaces with different "done when"s. + +When in doubt, it's tasks under one goal — that costs the developer nothing to +correct later (a goal can still be split into a Group after the fact via the +engine-goal step's attach/detach, which this step doesn't change). + +### 3. Create it, dormant + +```bash +ibuild engine goal-create "" --desc "<what>" --why "<why>" \ + --done-when "<acceptance>" --type <feature|bug|chore> --priority <low|medium|high> \ + --repos <owner/repo> --dormant [--parent <parentGoalId>] +``` + +`--dormant` is required — born task-less, nothing spawns before a run mode is +chosen. Write a real description: what, why, and a concrete "done when". Pass +`--parent <parentGoalId>` only when step 2 decided this is a Group part; omit +it for a single goal or a Group's parent itself. + +### 4. Ask the run mode + +Ask the developer, in plain language, how this goal should run: + +- **"I'll build it now, in this session"** — human-driven, right here. +- **"I'll build it, but in a fresh session"** — human-driven, started + elsewhere. +- **"Hand it to the fleet"** — agent-driven, unattended. + +Don't guess this — the three modes have materially different consequences +(who can touch the branch, whether the fleet can claim it, whether it needs a +review checkpoint from a human). + +### 5. Apply the choice + +Repeat steps 3–5 once per goal decided in step 2 (a single goal, or a Group's +parent plus each of its parts). + +| Choice | Commands | +| --- | --- | +| Build it now, this session | `ibuild engine goal take <goalId>` then `ibuild engine active-goal bind <goalId>`, then continue with the engine-goal-run step from its step 4 onward | +| Build it, fresh session | `ibuild engine goal take <goalId>` now, then tell the developer to run `ibuild engine goal session <goalId>` — a lean fresh session in the goal's worktree (no personal plugins/MCP servers), already started on the engine-goal-run step for that goal | +| Hand it to the fleet | `ibuild engine goal queue <goalId>` — warn in one line: this admits the goal and an idle fleet agent may claim it immediately | + +`take` is the fleet lockout (flips the goal to human-driven; refuses fleet +claims, queue and resume). `queue` is the opposite: it admits the goal and +makes it claimable. Never run both on the same goal. + +## Failure recovery + +- **`.build/.sync not found`** — this folder isn't linked to a Build project. + Run `ibuild sync` from the project root. +- **Repo mismatch** — if the target repo belongs to a different project than + this folder, `ibuild engine goal-create` resolves against the wrong + project; create the goal from a checkout of the right project instead of + hand-building an API call. + +## Notes + +- This command never writes code — it stops at "goal exists, run mode + applied." The actual build loop is the engine-goal-run step. +- Related: the engine-goal-run, engine-goals, engine-goal and engine-queue + steps. diff --git a/.claude/commands/i/engine-goal-run.md b/.claude/commands/i/engine-goal-run.md new file mode 100644 index 0000000..a28bb3b --- /dev/null +++ b/.claude/commands/i/engine-goal-run.md @@ -0,0 +1,464 @@ +--- +name: engine-goal-run +version: "1.3.0" +description: Work one goal in this session — or hand several goals (or a group) to `ibuild engine goal run`, which builds them on this computer, several at a time when that is safe. +--- +# /engine-goal-run + +## Plain English + +Work on a goal **in this session** — take it so the fleet can't claim it out +from under you, drive it to a reviewable state, stop and show the developer +what changed, then hand the actual merge to the delivery queue instead of +racing a fast-moving `main` by hand. This is the **Build → Preview → Iterate → +Land** loop, spelled entirely as `ibuild engine …` verbs. + +Three argument shapes: + +- **A goal reference** (`G#123`, `123`, a slug, or a goal id) → go straight to + step 2 (status), then step 3 (take). +- **SEVERAL goal references, or a group** → do not work them here. Hand the + whole list to `ibuild engine goal run` — see *Several goals at once* below. +- **A description** of what to build or fix → check *Step 0.5* first — a + follow-up request in a session already working a goal usually belongs on + that goal, not a new one. + +## When to use + +- "work on goal N", "let's do <name>", "fix X" (with no goal open yet). +- The developer wants YOU driving the implementation this session, not the + fleet — the goal must never be `queue`d or `resume`d while this runs. + +## Several goals at once + +When the developer names more than one goal — or a group — run this, from the +project root, and let it do the rest: + +```bash +ibuild engine goal run G#3296 G#3309 G#3166 # the goals they named +ibuild engine goal run G#3174 # a group: runs its parts +ibuild engine goal run G#3296 G#3309 --dry-run # the plan, taking nothing +``` + +What it does, so you do not have to: + +- **Refuses, in plain words**, any goal that has shipped, was closed, is paused, + or is already being built by somebody else. +- **Offers the goals that belong with the ones they named** — same group, a + dependency either way, an overlapping file list, the same feature — and + **asks** before adding any. It never adds one on its own, and it never + touches a goal nobody named. +- **Decides what can run at the same time.** Goals whose plans change the same + files, or that wait for one another, run in order; the rest run side by side, + as many as this computer's processors, memory and free disk allow. A goal + with no plan yet runs on its own, because what it changes is not yet known. +- **Gives every goal a fresh session for every task phase** — investigation, + execution, and each review round — in its own worktree, the same way a fleet + machine works it: each phase starts clean, picking up from the short hand-over + note the phase before it left in that goal's own `.goals/handover.md`, instead + of the previous phase's whole conversation. A session that crosses a context + ceiling (250k tokens by default; `--context-ceiling <n>` to change it) + restarts fresh for the SAME phase at the next safe milestone — never mid-gates + — so a long-running goal never drags a bloated conversation into its next + step. Every one of these sessions launches on Sonnet unless overridden, in + order, by `--model`, `ANTHROPIC_MODEL`, or `~/.claude/settings.json`'s + `model` field. +- **Hands each goal off** with `land --no-watch` when it is ready, and shows one + line per goal on a board: what it is doing now, how long it has been at it, + and how many sessions/how much context it has used so far (e.g. + `checks running · session 3 · 140k`). + +Two things it deliberately does not do: it never watches CI or the merge queue +after a hand-off, and it never bypasses permissions. `--max <n>` lowers how +many run at once; `--follow <goal>` also prints that one goal's progress lines. + +**This does not make one session drive two goals.** Every goal still gets its +own session — that rule (below) is what the verb is built out of, not an +exception to it. + +### Waiting for the run to end — the exit is the event + +The runner exits on its own when it is done, so **its exit is the event you +wait for**. Start it as a **background step your tool tracks** — one that +wakes this session when the command exits: + +```bash +ibuild engine goal run G#3296 G#3309 > goal-run.log 2>&1; echo "goal-run exit=$?" +``` + +Never start it with `nohup`, `&`, `disown` or `setsid`. A detached process +belongs to nobody, so its exit never reaches the session. + +**The exit can come seconds after the start.** If any named goal is refused +(shipped, closed, paused, already being built by somebody else), the runner +prints one `[engine-goal] …` line and exits 1 before it prints any board or +`All done.`. Treat that wake like any other: read the line, then read status. + +**What the exit code tells you, and what it does not:** + +- `0` — every goal was handed off **or is waiting on a person** (`needs_you`). + An exit 0 does not mean every goal was handed off. +- `1` — a goal was refused before the run started, or at least one goal + failed (including a goal that could not start because the disk was too full). + +**The outcome of record is `ibuild engine status <goal>`, for EVERY goal you +named.** The log is only a hint: each board redraw is appended to it as new +lines, followed by `All done.` and a final board, and its wording can change. +Never grep it to decide whether a goal was handed off, refused or failed. + +```bash +ibuild engine status G#3296 +ibuild engine status G#3309 +``` + +**A kill notification, or a resumed session, means the outcome is unknown.** +Re-read `ibuild engine status` for every goal. Never assume the run is still +going, and never assume it finished well. + +**To start a goal that must wait for another one to merge**, wait on the merge +inside the session, never in a detached helper: + +1. Run `ibuild engine watch <goal> --wait` as a tracked background step. It + stays quiet until the goal needs you or moves on, then exits. +2. When it wakes you, read `ibuild engine status <goal>`. +3. Once it has merged, start the next `ibuild engine goal run` as a tracked + background step again. + +**Rejected patterns, and why:** + +- **A `tail -f` / `tail -F` + grep watcher as the only signal.** `tail` never + exits, so the run ending is never an event; a missed line or a killed + watcher leaves the session asleep for good. +- **Timed polling** (sleep loops, scheduled wake-ups). It is polling, which + push-not-poll forbids, and it notices an ended run late. +- **A helper that does not outlive the session** (a detached watcher or + poller). It disappears with the session, and the next goal never starts. + +## Prerequisites (one-time) + +Same as every engine command: `PM_DAEMON_TOKEN` in `.env.local`, run from the +project root or below. See the engine-work step if this is the first command +run in a fresh checkout. + +## Step 0.5 — already working a goal this session? + +Before treating a request as a brand-new objective, check whether this session +is already bound to one (see the engine-active-goal step): + +```bash +ibuild engine active-goal status +``` + +- **Nothing bound** → skip to step 1. +- **A goal IS bound and its worktree is still open** → classify what this + request would touch — mechanical, never a judgement call: + + ```bash + ibuild engine active-goal check-scope <path> [path...] + ``` + + - **"ALWAYS a new goal"** (schema/migration, API route, engine/daemon wire + contract, auth, or billing) → say which reason fired, then fall through to + step 1 as a genuinely new goal. + - **"clear"** → the remaining question is judgement: does this fit the bound + goal's stated objective? If yes: stay in the same worktree, add a task + under the bound goal instead of a new goal (`ibuild engine create "<task + title>" --goal <goalId>`), fold the added scope into the goal's own + description (`ibuild engine active-goal grow --add "<what this adds>"`, + plus `--done-when "<updated criteria>"` if the acceptance bar moved), then + go straight to step 5 in the existing worktree. If it's a different + feature area, say so and fall through to step 1. + +## Step 1 — look for an existing goal before creating one + +Never create a duplicate. + +```bash +ibuild engine goals +``` + +Match on intent, not exact title (see the engine-goals step). + +- **Found one** → report its state (`ibuild engine goal status <goalId>`) and + reuse it. If it is already delivered or accepted, say so and STOP — do not + reopen it. +- **None** → create it (step 1.5). + +## Step 1.5 — create it + +```bash +ibuild engine goal-create "<title>" --desc "<what>" --why "<why>" \ + --done-when "<acceptance>" --type <feature|bug|chore> --priority <low|medium|high> \ + --repos <owner/repo> --dormant +``` + +`--dormant` is required: the goal is born task-less, so nothing spawns before +it is taken. Write a real description — this is the durable record, not this +session's scratch notes. + +## Step 2 — read its state + +```bash +ibuild engine goal status <goalId> +``` + +Already `accepted` or `delivered`? Say so and STOP. + +Then read what is attached to it — a spec or mockup means the written +description is deliberately incomplete (see the engine-goal-files step): + +```bash +ibuild engine goal-files <goalId> +``` + +## Step 3 — check nobody else already has it, then take it + +`ibuild engine goal take` itself refuses a goal that is already being driven — +by a different human-mode session, by a live interactive run lease that isn't +this session's, or by a fleet machine currently running it — naming who or +what is on it instead of silently stealing the goal out from under them: + +```bash +ibuild engine goal take <goalId> +``` + +Flips the goal to human-driven and refuses fleet claims, queue and resume — +stronger than pausing. When the developer has confirmed the other driver +should genuinely be pre-empted (not the default — ask first), override the +refusal explicitly: + +```bash +ibuild engine goal take <goalId> --take-over # pre-empt a human-mode driver +ibuild engine goal take <goalId> --take-over --stop-running # also end a live fleet run on it +``` + +A takeover is recorded against the goal either way, so it is never silent. +**Never `ibuild engine goal queue` a goal you intend to hand-drive**: queueing +admits it and invites the fleet back in. + +Then bind this session to it, so a later request in the same session finds it +via *Step 0.5* instead of opening a new goal: + +```bash +ibuild engine active-goal bind <goalId> +``` + +## Step 4 — epic? wait out the blockers, then open the worktree + +If the goal has a parent goal or is blocked by another one, do not poll — +subscribe: + +```bash +ibuild engine watch <blockingGoalRef> --wait +``` + +It stays quiet until something actionable happens, then exits. Only proceed +once every blocker is delivered. + +Then open the run **from the primary clone** — creating a task first if the +goal doesn't have one yet: + +```bash +ibuild engine create "<what this task does>" --goal <goalId> # only if the goal has no task yet +ibuild engine work <taskId> +``` + +Note the worktree path it prints, and the two lines about which base it used +and whether the seeded dependencies typecheck. Read them before writing code. + +If this session has no PM bridge attached, start the keepalive now so a long +stretch of coding does not let the run's lease lapse (see the +engine-heartbeat step): + +```bash +ibuild engine heartbeat --loop +``` + +Run it as a tracked background step; it stops itself when the run ends. + +## Step 5 — drive implementation to ready-to-deliver, in this session + +Work **inside the worktree** from here on: run every build/test command with +the worktree as the working directory, and read/edit files by absolute path +under the worktree root. + +The loop: + +1. Implement the change. +2. Record each phase's deliverable — the bridge's `pm_submit_artifact` when + the session has it, otherwise `ibuild engine artifact submit` (see the + engine-artifact step). +3. `ibuild engine gates` (review phase only) from the worktree — it runs this + repo's own declared checks and records the reports a review needs (see the + engine-gates step). +4. `ibuild engine report` to advance each phase (investigation → execution → + review). +5. Repeat until the goal is **ready to deliver**: every task complete, the + review verdict clean at the current head, CI green. Then go to step 5.5. + +**Do not call `ibuild engine goal accept` yourself.** On a repo where `main` +merges continuously, a hand-driven accept loses the race by construction. +Step 6 below is the fix. + +Rules that save a round trip: + +- Do not push mid-review-round — it moves the head and re-breaks + review-at-head. Batch pushes. +- A review verdict must grade every pre-merge criterion — omitting one FAILS + it, omitting a finding RESOLVES it. +- Review with the engine-review step, never a raw code-review invocation — it + must resolve files against the worktree, not the primary clone. +- Ship the below-1024px responsive treatment in the same PR for any UI change. +- Rebase periodically and always before a long pause: + `ibuild engine active-goal rebase`. +- **A rebase conflict** → `git rebase --abort` (never hand-resolve it here) and + go to step 6: hand off and let the delivery queue's proactive rebase resolve + it — a regenerable lockfile or purely-additive code is auto-resolved. Only + hand-fix a conflict that is IN a file this goal's own diff owns, and only + once the merge train has already reported it as a real, same-line + disagreement. +- **A CI failure not caused by this goal's own diff** (a repo-wide + staleness/collision guard, a stale generated snapshot, an unrelated + version-guard) is not yours to fix — push what you have and go to step 6; + the delivery queue's own triage (repo-wide / flake / infra) handles it. + Reserve a hand `git merge origin/main` for the rare case where `arm-accept` + itself reports a conflict it cannot resolve, and say so explicitly first. +- A `hand-back` 422 means the goal wasn't in human mode — harmless, go + straight to `arm-accept`. +- A follow-up request while this goal is open → back to *Step 0.5*. + +## Step 5.5 — review checkpoint: ask, don't wait on free text + +Ready-to-deliver is a checkpoint, not a green light. Stop and report, in one +short message: + +- **The PR URL** (`ibuild engine goal status <goalId>` prints it). +- **A live preview**, if this repo has one — never claim a preview URL that + does not exist; say plainly when the repo has none and offer a local + screenshot instead for UI work. + +Then ask the next step as real options — `pm_ask_question` when the PM bridge +is available, else your tool's own multiple-choice question — never a +plain-text "say iterate or ship it" sentence: + +``` +question: "G#<n> is built and its checks pass. What next?" +options: + - "Preview it" — start the app here and try it; you can ask for changes while it runs + - "Make changes" — tell me what to change + - "Ship it" — hand it to the delivery queue (merges once checks pass) +``` + +Do not proceed to step 6 unprompted — this question IS the review gate. + +- **Preview it** → start the app from THIS worktree (reuse one already + running from it; a server from another checkout is not this goal's code) — + `ibuild engine goal preview <goalId> --local` where that verb is available, + otherwise this repo's own declared dev command — and open it in your tool's + built-in browser when it has one, else the OS default browser. Then go + straight into iterate mode: say where it's running, and after each change + re-ask this same question with "Preview it" reworded to "Refresh preview". +- **Make changes** → the ordinary step-5 loop again: new commits, gates, + a fresh review round so review-at-head is re-stamped on the new head. Come + back to this checkpoint with the same handles when it is green and clean + again. +- **Ship it** → step 6, stopping any preview server first. If the developer + instead wants to click Accept themselves, that's the manual-stop variant + (`--manual-stop`) — offer it only when they say so in chat, never by + default. + +## Step 6 — land it + +Once ready-to-deliver, hand the goal to the delivery queue rather than staying +the merge queue yourself: + +```bash +ibuild engine goal land <goalId> +``` + +This composes `hand-back` + `arm-accept` + `watch <goalId> --wait` in one +verb, and prints the goal's readiness before doing any of it. What each of the +three calls buys, and how to read what `land` prints when the watch wakes, is +in the engine-goal step's **Land** item (its step 7); do not duplicate it here. + +### Default: hand off and stop, once armed — do not wait for accepted + +**One goal per session, and this is the exit.** The moment `land`'s one-shot +confirmation watch comes back clean, close out immediately — do not hold the +session or the worktree open until the goal reaches accepted/delivered: + +```bash +ibuild engine active-goal unbind +ibuild engine finish <taskId> # FROM THE PRIMARY CLONE +``` + +Tell the developer the goal is armed and riding the delivery queue — not +"watching until it merges." Only an auto-driven goal is a delivery-queue +member: only it gets its branch proactively rebased when it holds headship, +and only it can ride a merge train, where the throwaway train branch means +its review never goes stale. Holding a session and worktree open purely to +babysit a goal to acceptance starves the delivery queue of exactly that — +don't do it by default, and do not start another goal in the same session +afterward: clear the conversation first, or open a fresh one with +`ibuild engine goal session <goalId>` (see the engine-goal-new step, step 4). + +If what the watch surfaces instead is the goal's OWN diff being red (or a +pause that reopened work on it), fix it before closing out: +`ibuild engine goal disarm-accept <goalId>` → `ibuild engine goal take +<goalId>` → fix in the same worktree → back to step 5.5 → step 6 again. A +routine event the engine is already handling itself — `ci_infra_rerun`, +`branch_refreshed`, `ci_platform_outage`, or similar — needs nothing; close +out anyway. + +**Changed your mind after arming?** Not a new mode, just the same cycle: +`ibuild engine goal disarm-accept <goalId>` → `take` → edit → back to step 5.5 +→ step 6 again. + +### Manual-stop variant (the developer wants to click Accept themselves) + +This is a deliberate deviation from the default above — offer it only when +the developer asks for it, never switch into it unprompted: + +```bash +ibuild engine goal land <goalId> --manual-stop +``` + +Keep the session and the worktree open until `ibuild engine goal status +<goalId>` says accepted — an un-armed goal is not a delivery-queue member, so +its branch is not proactively rebased; a long wait can still need one refresh +round before the developer's click lands. The merge can also come back +needing a fix (a conflict, a red diff, a fleet review finding); when it does, +same cycle as above: `disarm-accept` → `take` → fix → step 5.5 → step 6. +Once `status` says accepted, close out the same way (`active-goal unbind` + +`engine finish <taskId>` from the primary clone). + +## Failure recovery + +- **A blocker never starts moving** — `ibuild engine watch <blocker> --wait` + can wait a long time on a dormant, unqueued goal. Surface this rather than + waiting silently forever; ask whether to work the blocker directly, keep + waiting, or proceed with a stub. +- **A 409 names a batch** — read its `code`: a batch/merge-train code means + the delivery queue already has this goal, not an error to fix; keep + watching. +- **The interactive lease drops** (a very long gates run, a long pause) — + re-run `ibuild engine work <taskId> --force` only if the run genuinely died; + `ibuild engine status <task>` tells you first. +- **`ibuild engine gates` hard-resets the checkout when it finishes** — commit + before starting it, and don't edit the worktree while it runs; edits made + mid-run are silently destroyed. +- **A `hand-back` revived a planning task** — hand-back can re-arm decompose + and leave a duplicate planning task open; cancel the duplicate from the + goal's page in the Build web app. +- **An `out_of_band_commit` pause fired after pushing while the goal was + taken** — resolve it rather than re-pushing: answer the pause on the goal's + page in the Build web app and choose to continue. +- **No PR is open yet for this goal** — link or create one: + `ibuild engine goal link-pr <goalId> [--number N]`, or start the next goal + with `--pr-boundary` so one opens automatically. + +## Notes + +- Related: the engine-goal-new step (create + choose a run mode from scratch), + and the engine-goal, engine-work, engine-watch, engine-goals and + engine-review steps. diff --git a/.claude/commands/i/engine-goals.md b/.claude/commands/i/engine-goals.md new file mode 100644 index 0000000..6ae811e --- /dev/null +++ b/.claude/commands/i/engine-goals.md @@ -0,0 +1,77 @@ +--- +name: engine-goals +version: "1.0.0" +description: List this project's engine goals — the dedupe-by-intent read before creating a new one. +--- +# /engine-goals + +## Plain English + +List the project's **goals** — a terminal read of what already exists, so you +can check "does something like this already exist?" before minting a new one. +Defaults to open/in-flight goals (not `accepted`/`cancelled`); pass `--all` for +the full history. + +## When to use + +- Before creating a goal — dedupe by intent, not just by title match + (the engine-goal-run and engine-goal-new steps do this automatically). +- "what goals are open right now?", "is there already a goal for X?", "list the + project's goals". + +## Prerequisites (one-time) + +- A **daemon token** in `.env.local` as `PM_DAEMON_TOKEN=pm_daemon_…` (PM web UI + → Settings → Agent Tokens) — the same token the rest of the local engine loop + uses. +- Run from the project root or below (`.build/.sync` is found by walking up). + +## Steps + +```bash +ibuild engine goals # open/in-flight goals: "#<n> <status> <title>" +ibuild engine goals --all # every goal, including accepted/cancelled +ibuild engine goals --limit 50 # stop after N rows +ibuild engine goals --tag urgent --tag billing # goals carrying EVERY named tag (AND) +ibuild engine goals --category Payments # goals in that category +ibuild engine goals --starred # goals YOU starred (your own star only) +``` + +The three label filters combine with each other and with `--all` / `--limit`, +and are applied by the server, so they are correct across every page. `--tag` +repeats or takes a comma list. A tag or category name nobody has used yet +lists nothing — it never falls back to "no filter". A `--tag` / `--category` +typed with no name is refused before anything is sent. + +Sample output: + +``` +#42 open Fix the login redirect loop +#57 in_review Add CSV export to the reports page +``` + +## Why this pages instead of a raw API call + +The unpaged read of this same endpoint caps at **100 rows ordered by +priority** — a moving window that can silently drop a goal you can prove is +active (a medium/low-priority goal crowded out as others are created or +re-prioritized). This command instead follows the response's cursor page by +page until there are no more, ordered by creation time — an exactly-once +sweep, never a window. Never hand-build the unpaged API call as a substitute +for this command. + +## Failure recovery + +- **`.build/.sync not found`** — this folder isn't linked to a Build project. + Run `ibuild sync` from the project root (or `ibuild sync --paste-token`). +- **Missing / rejected `PM_DAEMON_TOKEN`** — mint an agent token in the PM web UI + (Settings → Agent Tokens) and put it in `.env.local`, or re-run `ibuild setup`. + +## Notes + +- READ-ONLY — this changes nothing. +- `--limit` stops the sweep early and says so in its own line when it does, so + a truncated list is never mistaken for the whole project. +- Related: the engine-goal step (act on one goal), the engine-goal-create step + (mint a new one — check here first), and the engine-goal-run step (the + workflow that uses this for dedupe). diff --git a/.claude/commands/i/engine-heartbeat.md b/.claude/commands/i/engine-heartbeat.md new file mode 100644 index 0000000..432d384 --- /dev/null +++ b/.claude/commands/i/engine-heartbeat.md @@ -0,0 +1,84 @@ +# /engine-heartbeat + +## Plain English + +Keep a **bridge-less** interactive run's lease alive — from the terminal, in the +background, with no ritual command to remember. + +An interactive run is reclaimed after an hour of total silence. A session with +the PM bridge attached renews that lease on its own heartbeat, and every +`ibuild engine` command renews it too — but a bare-terminal drive with no bridge +can go the whole hour between `ibuild engine work` and `ibuild engine report` +typing no engine command at all, while it reads, writes and tests code, and lose +the run to that silence. This command needs no discipline to remember: start it +once and it keeps checking in for you. + +## When to use + +- You're hand-driving a task with no PM bridge in this checkout, and the work will + take longer than a few minutes between `ibuild engine` commands. +- The developer says "keep this run alive", "I'm going to be heads-down on this for + a while", or you notice the session has no bridge (`.mcp.json` absent, or pointed + at a container-only path) while holding a long-running interactive task. +- A push to the goal branch ALSO renews the lease on its own — this command is for + the gap *between* pushes, not a replacement for pushing your work. + +Developers never type this — they say what they're about to do and you start it +for them, in the background, so it doesn't block the rest of the session. + +## Prerequisites + +- A run in progress: `.goals/.engine` is present (written by + `ibuild engine work` / `ibuild engine claim`). +- `.build/.sync` with `BUILD_SYNC_URL=…` / `BUILD_SYNC_PROJECT_ID=…` (the base URL + and project the heartbeat route lives under) — the same file every + `ibuild engine` command reads. +- A **daemon token** as `PM_DAEMON_TOKEN`, resolved the same way + `ibuild engine work` and `ibuild engine report` resolve it (the environment, + then `.env.local` walking up from the current directory). + +## Steps + +1. **Start it as a background step**, right after `ibuild engine work` opens the + run — before the long stretch of coding begins: + + ```bash + ibuild engine heartbeat --loop & + ``` + + In an AI coding session, run it as a background step your tool tracks instead + of a shell `&` — same effect, and you're only interrupted if it ever prints + something. + +2. **Work normally.** Nothing about the rest of the loop changes — implement, + record deliverables (`pm_submit_artifact` or the engine-artifact step), + `ibuild engine report` — this just runs alongside it. + +3. **It stops itself.** The moment `.goals/.engine` clears (your + `ibuild engine report` ran) or the server says the run it was renewing is + already closed, it logs why and exits 0. Nothing to tear down by hand. + +## Options + +- *(bare)* — one quick beat: renew the run named in `.goals/.engine` right now, + print the result, exit. Useful as a one-off check-in, or to confirm the run is + still live before a report. +- `--loop` — the keepalive: repeat that beat every ~20 minutes until the run ends. + This is the mode you leave running in the background. +- `--interval-ms <n>` — override the cadence (only meaningful with `--loop`; for + tests/CI, not everyday use — the ~20-minute default is comfortably inside the + 60-minute interactive limit even if one beat is missed). + +## Notes + +- **Re-reads `.goals/.engine` on every beat**, not a snapshot taken at start — + if you `ibuild engine work` a new task in the same worktree while the loop from + a previous task is still running, the next beat picks up the new run + automatically rather than renewing a stale one. +- **Never an error loop.** A beat that can't reach the server logs it and simply + retries on the next interval; a `--loop` run only ever stops on "the run is + gone" or "the server says it's already closed" — never on a transient network + blip. +- **No new write path.** Every beat calls the same run-heartbeat route the fleet + and `ibuild engine report` / `ibuild engine status` already call — this command + only decides *when* to call it.