- name
- pi-matt-implement-flow
- description
- Orchestrate the implement stage of a Matt-style ticket graph: read spec + tickets, compute the frontier, dispatch parallel coder subagents (each in its own managed worktree), run a two-axis review per ticket, send fixes back to the same coder, merge with an integration-test gate, recompute the frontier, and finish with a whole-branch final review. Replaces hand-worked blockers-first ticket queues and per-ticket /clear.
- disable-model-invocation
- true
# Implement, orchestrated (pi)
You are the **orchestrator**. You write no feature code: you dispatch, verify, merge, and keep the **frontier** moving until the spec is built on one branch.
The tickets came from `/to-tickets`: a **task graph** of tracer-bullet slices, each declaring the tickets that **block** it. The frontier is every open ticket whose blockers are all closed and that is `ready-for-agent` (tickets from `/to-tickets` are already agent-ready — never send them through `/triage`). Work it with up to N `coder` subagents at once. N comes from the skill argument (`/pi-matt-implement-flow [N]`); otherwise from `mattImplementFlow.maxConcurrent` in pi settings; otherwise 3.
Talk to subagents through **context pointers** (paths, branch names, commit SHAs). Content they can read themselves stays out of the message.
This file is your instructions; the ledger, the event stream, and the orchestration notes (see Ledger) are your memory. **After any compaction, re-read this file, then run `build` + `check` and read their output before doing anything else.**
## Names you dispatch
Agents are registered as a pi package. Always use the full names — the bare `coder` resolves to the user's `worker` alias, and the bare `reviewer` to the builtin:
- `pi-matt-implement-flow.coder`
- `pi-matt-implement-flow.reviewer`
- `pi-matt-implement-flow.final-reviewer`
Skills are baked into the agents (`tdd`/`codebase-design` for the coder, `code-review` for the reviewers). You never call skills on their behalf. On a merge conflict you follow `resolving-merge-conflicts` yourself.
Verify registration once before the first dispatch (`subagent({ action: "list", capabilities: true })`). If the three agents are missing, stop and tell the user to run `pi install <this package path>`.
## Preconditions
Check all of these before dispatching; on a failure, stop and tell the user what to change.
- **Clean tree**: `git status --porcelain` must be empty (pi rejects worktree dispatch on a dirty tree — untracked files count). If it is dirty, classify first: tracker/ledger runtime files → fix the ignore rules (below); your own pending status writes → commit them as `chore(tickets): ...`; anything else → stop and ask. Never stash on your own.
- **Ignore rules for runtime state**: ensure `.gitignore` covers `.pi/matt-implement/` (append a marked block if missing; mention it once to the user). If ticket-status writes would dirty git (a git-visible truth layer, e.g. `.scratch/`) and that directory is not ignored, plan to commit ticket-status writes separately (see the loop).
- **Git repo with at least one commit** (worktrees cannot be created from an unborn HEAD).
- **Tracker setup artifacts (范本识别)**: the issue tracker should have been provided to you — the target repo's `docs/agents/issue-tracker.md` (with the `triage-labels.md` beside it), pointed at from its `## Agent skills` block. Both are `/setup-matt-pocock-skills` products and the run's single tracker configuration — there is no `--tracker` flag repeating the choice. At every tracker-touching ledger command (`init` / `snapshot-init` / `claim` / `sync`) the script reads the artifacts, identifies the upstream template (H1 heading + anchor phrases; user-edited prose is tolerated), and loads the matching contract preset. Two conditions are explicit stop-and-report failures, never a guess, a degradation, or a derived wizard: **artifact missing** → tell the user to run `/setup-matt-pocock-skills` and stop; **template unrecognized** → the script refuses with `仅支持 local / github / gitlab 三种` — pass that refusal on and stop. Follow that file; if the tickets it names cannot be found, stop and ask the user where they are.
- **Contract capabilities (按契约能力执行)**: every tracker difference in this file is a declared capability of the identified contract, never a per-tracker branch — reading this file tells you the behavior because the behavior follows the capabilities. Which tracker steps exist at all (snapshot pull, pre-seal sync, 占坑) is likewise a declaration: contracts with a tracker write surface have them, contracts where the ticket files are the truth layer have none (zero change). The four capabilities that shape the flow:
- **占坑强度 (claim strength)**: the spec claim is an advisory lock — it reserves the feature and stops a second session before it writes; it is not an atomic mutex.
- **closeWithComment**: whether a close carries its closing comment in one action, or the comment is noted first and the close follows — the pre-seal sync orders the actions.
- **收尾面 (closing surface)**: the review surface the feature branch closes through — a PR, an MR, or none; the pre-seal sync always precedes marking it ready.
- **票集边来源 (ticket-set edges)**: where the ticket set's blocking edges come from — native sub-issues, parent look-ups, or the declared ticket list.
- **Spec 引用 (spec reference)**: the identifier that locates this run's spec (tracker doc convention, or the user's argument), in the reference form the identified contract declares — a spec file path, or a native ticket reference (`42` / `#42` / a full issue URL). Every review needs it; the `init` event pins the local spec file it resolves to.
- **占坑 (claim the spec)** — when the contract has a claim (a tracker write surface), it is the run's first write on the tracker, before any pull: `node <this-package>/scripts/ledger.js claim --runtime-dir .pi/matt-implement/<slug> --spec <spec 引用>` — the contract's claim template reserves the spec to this run so the tracker shows the feature as in-flight. Conflicts (the spec already claimed by someone else) come back as a script refusal naming the assignee: stop and ask before any further write — change target, take over, or coordinate. The claim is released at the end: the pre-seal sync closes the spec, or the abandon sync (`sync --mode abandon`) unassigns it. Contracts without a tracker write surface have no claim step — the ticket files are the truth layer (zero change).
- **Test command**: determine the project's full-suite command (`package.json` `scripts.test`, Makefile, …). If ambiguous, ask once and pass it to the `init` event.
- **Graph**: every ticket has a `Blocked by` line (or a native blocking link, where the contract's edge sources provide one) and the initial frontier is non-empty. An empty frontier with open tickets means a cycle — stop and report.
## Flow configuration
This run's shape — whether each ticket gets a reviewer, the per-ticket fix budget, and the coder concurrency — is read from the package-private `mattImplementFlow` key in pi settings (`<repo>/.pi/settings.json` wins per field over `~/.pi/agent/settings.json`; never write any other settings key), then **frozen as `init` flags** in the ledger:
| key | default | meaning |
|---|---|---|
| `reviewer` | `true` | per-ticket two-axis review + fix loop; `false` = merge straight after the platform test gate (the whole-branch final-reviewer still runs) |
| `maxFixRounds` | `2` | fix attempts per ticket before escalation; only meaningful when `reviewer` is on |
| `maxConcurrent` | `3` | parallel coders; the skill argument wins |
Pass them to `init` as `--reviewer on|off --max-fix-rounds N --max-concurrent N`; omitted flags mean the defaults. The ledger script enforces the frozen shape from that point on: with `reviewer=off` it rejects `verdict`/`fix` events and lets `merge` proceed without a verdict, and the fix budget is whatever the snapshot recorded. Configure via `/matt-flow-config` → "Configure flow options"; changes apply from the next run's `init`, never mid-run.
## Ledger
Your memory has three layers, each with exactly one owner. Terms (per `CONTEXT.md`): ledger 台账 / event stream 事件流 / orchestration notes 编排笔记 / record 记账 / seal 封账 / reconcile 对账. The old phrase "event log" is retired — never use it.
- **Event stream** — `.pi/matt-implement/<feature-slug>/events.jsonl`. Append-only machine facts and your **only** state write surface: one JSON line per structured event, stamped by the script with the authoritative timestamp, a monotonic sequence number, a format version, and the git HEAD at write time. You never provide timestamps; you never touch this file directly.
- **Ledger** — `.pi/matt-implement/<feature-slug>/ledger.md`. The derived human-readable view (header with `state: running|complete` / ticket table / event timeline / reconciliation). **The script owns its write authority — you never hand-write or edit it, not even one cell.** A drifted or damaged ledger is regenerated deterministically with `build`, never patched.
- **Orchestration notes** — `.pi/matt-implement/<feature-slug>/notes.md`. Prose memory: process narrative, lessons, the user's verbal decisions. Facts ("what happened, when") go to the event stream; prose ("why, what we learned") goes here. Prose never competes with the event stream as a source of truth.
- **You never hand-write the ledger.** Every state transition is recorded (记账) with one script command: `node <this-package>/scripts/ledger.js add <type> --runtime-dir .pi/matt-implement/<feature-slug> [flags]` (`<this-package>` is the directory containing this SKILL.md).
- **Event types, flags-style (never raw JSON)**: `init` / `dispatch` / `settled` / `verdict` / `fix` / `merge` / `escalate` / `final` / `anomaly` / `pr` / `close` — run `--help` for each type's exact flag set; free text is always `--note`.
- **The script validates before writing**: bad payloads are rejected with a reason — fix and retry immediately (your context is freshest now); state-machine violations and definite git contradictions are rejected outright; facts that are merely not-yet-verifiable (e.g. a worktree not yet in `git worktree list`) come back as warnings and the event is recorded.
- **There are no bypass flags.** When you disagree with the validator, record `anomaly --note "..."` and stop to report.
Three command disciplines:
1. **Record on every state transition**: dispatch, settle, verdict, fix dispatch, merge, escalation, the final review's verdict (终审裁决 — one run-level `final` event per round), PR transitions; `close` (封账) seals the run — the ledger flips to `state: complete` and every further record is rejected.
2. **Reconcile (对账) before every dispatch and every merge**: `node <this-package>/scripts/ledger.js check --runtime-dir ...` — non-zero exit means ledger-truth drift, listed item by item. Fix the world to match truth or truth to match the world; never the ledger by hand. When the run reads a tracker snapshot, the ticket files this reads are the snapshot's copies — the tracker body is a delayed mirror outside the truth layer; fix `Status:` on the snapshot, never on the tracker's own body.
3. **Regenerate after compaction**: `node <this-package>/scripts/ledger.js build --runtime-dir ...` prints the full four-section ledger; continue from its output plus the orchestration notes, never from memory.
Review bundles go to `.pi/matt-implement/<feature-slug>/reviews/<NN>-r<k>.diff`, findings to `.../findings/<NN>-r<k>.md` — pass the findings path to the `verdict` event via `--findings`.
## The loop
Restate the plan to the user in at most ten lines (branch, ticket count, first frontier, N), then proceed without waiting.
### Cold resume (续跑) — the same command, an unfinished run
Before Round 0, check whether this run already exists: if `.pi/matt-implement/<slug>/events.jsonl` exists and the ledger header reads `state: running` (not sealed), the same command is a **continuation (续跑)**, not a new run — skip ahead and keep going:
1. **Skip init and the pull — both.** Never re-record `init` (the script rejects a second one), and never re-run `snapshot-init`: an existing snapshot is refused, never overwritten or re-pulled — a re-pull would let the tracker's lagging state overwrite the local truth (ADR-0003). The refusal is the continuation guard, not an error to work around. **One recovery wedge inside the guard**: if the snapshot already exists but the event stream carries **no `init` event** (a crash between `snapshot-init` and the `init` recording — the pull landed, the ledger did not), record `init` with `--spec .pi/matt-implement/<slug>/tracker/spec.md`, the snapshot's own spec copy — the truth layer enumerates ticket files by the same `dirname(spec)/issues/` convention — and continue with the rest of Round 0; the pull is still never re-run.
2. **Rebuild, then reconcile**: `node <this-package>/scripts/ledger.js build --runtime-dir .pi/matt-implement/<slug>` (台账再生) followed by `check` (对账); read their output — the regenerated ledger carries the full state and any ledger-truth drift item by item — then read the orchestration notes.
3. **Continue from the frontier** the ledger reports: remaining tickets, the fix loop, merges, and the final gate all pick up where the event stream left off.
(续跑 is a run-level action. A subagent's retained-context resume inside the loop is a platform mechanism — the glossary keeps the two apart.)
### Round 0 — graph, branch, baseline
1. Read the spec and every ticket, resolve the flow configuration (above), then record `init`(记账)— `--branch --branch-base --baseline-sha --spec --test-command [--reviewer on|off --max-fix-rounds N --max-concurrent N --tickets 01,02,1042]` — as the ledger's first event. There is no `--tracker` flag: the script auto-identifies the tracker from the setup artifacts and records the identified tracker with the event. The flow flags freeze this run's shape; everything downstream is derived from it; the baseline the init pins is what later failures are attributable against. `--tickets` (the same list snapshot-init takes) freezes the run's ticket-set boundary — once recorded, out-of-boundary dispatches are refused mid-run; omit it when the set resolves from the contract's edge sources (native sub-issues / parent look-ups). `--spec` is the spec file the run actually reads:
- **The contract materializes a tracker snapshot** — pull first, read after: `node <this-package>/scripts/ledger.js snapshot-init --runtime-dir .pi/matt-implement/<slug> --spec <spec 引用>`(the reference form the contract declares;add `--tickets 01,02,1042` only when the ticket-set resolution needs the fallback list)materializes the whole tracker into the **tracker snapshot** under `.pi/matt-implement/<slug>/tracker/` — the spec with a `Source:` line for its tracker origin, one local-shaped file per ticket. The snapshot is the pending-push truth; the tracker body is its delayed mirror, not written to mid-run. From here on the orchestrator, the coder briefs, the ledger, and both reviewers read and write those local paths with zero form fork, and `init`'s `--spec` is the snapshot's `spec.md`. A refusal because a snapshot already exists is the continuation guard — the run already started; go to Cold resume (above).
- **The truth layer is already the local files** — nothing to pull: the spec and tickets under `.scratch/<slug>/` are the local files every brief has always pointed at (zero change).
2. **Environment survey**: read `.gitignore` and enumerate every runtime path a coder worktree will not contain (`.scratch/`, `.pi/`, `data/`, …). Write the survey into a **环境事实 (environment facts)** section of the orchestration notes with exactly three elements: the gitignored path list, where real data actually lives, and the validation discipline (validate against real data through the scratch directory). Every coder brief's Worktree-reality parenthetical and its data paths are picked per ticket from this section — filtering is your job; coordination prose (routing decisions, user rulings, process narrative) never enters a brief, and coders never read the notes file whole. The survey is prose: it lands only in the orchestration notes, never in the event stream (no new event type).
3. On the default branch, create `feat/<feature-slug>` from HEAD; on any other branch, stay on it and record it.
4. Run the full test suite once; record the baseline (green or the failing list) in the orchestration notes.
5. With a remote, push the branch and open a **draft closing surface** — a PR where the host runs pull requests, an MR where it runs merge requests — whose body's closing keywords cover the spec and merged tickets only — escalated tickets stay open (their sync keeps them open with an explanatory comment), so never list them in the closing list; record `pr --state opened-draft`.
Optionally, when a survey finding is a durable repo-level lesson (e.g. a CLI syntax trap), suggest graduating it into the committed `AGENTS.md` — coder worktrees carry committed files automatically, at zero brief cost.
### Each round — dispatch the frontier
Count running coders; while below N and the frontier is non-empty, claim the next tickets(认领)— write `Status: claimed` on the ticket's truth-layer file — the tracker snapshot's copy when the run reads one, the ticket file itself when the files are the truth layer; never a tracker-body write — tracker-side progress happens only at the pre-seal sync — and dispatch one wave — **exactly** one top-level subagent workflow call with `async: true`:
```js
const results = await runs.all([
{
key: "t-01",
agent: "pi-matt-implement-flow.coder",
task: `<coder brief — see Briefs>`,
worktree: true,
gate: {
command: "node <this-package>/scripts/mechanical-report.js --base <baseCommit> --test-command \"<testCommand>\"",
output: "json",
schema: {
type: "object",
required: ["headSha", "testResult", "changedFiles", "validationOutput"],
properties: {
headSha: { type: "string" },
testResult: { type: "string" },
changedFiles: { type: "array", items: { type: "string" } },
validationOutput: { type: "array", items: { type: "string" } }
},
additionalProperties: false
},
timeoutMs: 600000
}
}
// ...one entry per claimed ticket, up to N
]);
return results.map(r => ({ key: r.key, ok: r.ok, runId: r.runId ?? null, structured: r.structuredOutput ?? null, artifacts: r.artifactPaths ?? null }));
```
The `schema` in the dispatch above is a copy of the mechanical-report contract. Its single source of truth is `scripts/mechanical-report.js` (`REPORT_SCHEMA`, self-describing via `--print-schema`) — the copy must stay verbatim-identical (the self-check cross-compares them; drift fails the suite):
```json
{
"type": "object",
"required": [
"headSha",
"testResult",
"changedFiles",
"validationOutput"
],
"properties": {
"headSha": {
"type": "string"
},
"testResult": {
"type": "string"
},
"changedFiles": {
"type": "array",
"items": {
"type": "string"
}
},
"validationOutput": {
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false
}
```
Ver en GitHub