vinta-ai-workflows 0.6.1 → 0.7.0-alpha2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +288 -0
- package/README.md +21 -13
- package/package.json +2 -1
- package/schemas/README.md +5 -1
- package/schemas/postmortem.v1.schema.json +339 -0
- package/schemas/vinta-ai-workflows-config.v1.schema.json +13 -1
- package/schemas/workflow.v1.schema.json +833 -0
- package/skills/vinta-bootstrap-ai-tools/SKILL.md +9 -1
- package/skills/vinta-derive-skills/SKILL.md +6 -2
- package/skills/vinta-derive-skills/resources/foundation-skills/plan-feature/SKILL.md +622 -32
- package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/SKILL.md +24 -2
- package/skills/vinta-derive-skills/resources/plan-execution/README.md +21 -4
- package/skills/vinta-derive-skills/resources/plan-execution/partials/agent-models.md +30 -3
- package/skills/vinta-derive-skills/resources/plan-execution/partials/commit-strategy/modular.md +27 -14
- package/skills/vinta-derive-skills/resources/plan-execution/partials/commit-strategy/stacked.md +12 -18
- package/skills/vinta-derive-skills/resources/plan-execution/partials/implementer-prompt.md +19 -9
- package/skills/vinta-derive-skills/resources/plan-execution/partials/model-pick.md +25 -9
- package/skills/vinta-derive-skills/resources/plan-execution/partials/parallel-lanes.md +240 -0
- package/skills/vinta-derive-skills/resources/plan-execution/partials/review-layers.md +56 -5
- package/skills/vinta-derive-skills/resources/plan-execution/partials/worktree-seam.md +13 -6
- package/skills/vinta-derive-skills/resources/plan-execution/shell/amend-plan-template.md +17 -12
- package/skills/vinta-derive-skills/resources/plan-execution/shell/implement-phase-template.md +5 -3
- package/skills/vinta-derive-skills/resources/plan-execution/shell/implement-plan-template.md +94 -57
- package/skills/vinta-derive-skills/resources/plan-execution/shell/integrate-phase-template.md +2 -1
- package/skills/vinta-derive-skills/resources/plan-execution/shell/review-phase-template.md +16 -5
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plan-feature
|
|
3
|
-
description: Author a phased implementation plan for a new feature following the repo's `ai-plans/` conventions. Use when the user asks to "plan", "design", "scope", or "break down" a feature, write an implementation plan / IMPLEMENTATION_PLAN.md, or turn a spec/idea into a phased roadmap. Always interrogates the requester before drafting.
|
|
3
|
+
description: Author a phased implementation plan for a new feature following the repo's `ai-plans/` conventions — including the dependency graph that lets independent phases be implemented in parallel. Use when the user asks to "plan", "design", "scope", or "break down" a feature, write an implementation plan / IMPLEMENTATION_PLAN.md, or turn a spec/idea into a phased roadmap. Always interrogates the requester before drafting.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plan Feature
|
|
7
7
|
|
|
8
8
|
Plans live in `ai-plans/` as `YYYY-MM-DD-FEATURE_NAME_IMPLEMENTATION_PLAN.md` (uppercase + underscores). `..._SPEC.md` sibling exists → **read first**. Plan translates spec into phased delivery, doesn't re-derive requirements. No spec? Point at [create-spec](../create-spec/SKILL.md) first; plan without spec = plausible-sounding but unverified. Spec/plan pair share `YYYY-MM-DD-FEATURE_NAME` prefix.
|
|
9
9
|
|
|
10
|
+
Every plan ships **two** files: the markdown above, and its executable sibling `ai-plans/<feature-kebab>.workflow.json` — the same phase graph in the form an orchestrator runs. See "Emit the executable workflow". Written every time; never gated on a question.
|
|
11
|
+
|
|
10
12
|
## Step 0 — Interrogate before drafting (NON-NEGOTIABLE)
|
|
11
13
|
|
|
12
14
|
**Never assume requester want.** *"Plan bookmarks feature"* hide ≥dozen decisions cheaper to surface now than unwind in Phase 4.
|
|
@@ -56,6 +58,8 @@ Never flatten ten questions into one prose paragraph just because tool unavailab
|
|
|
56
58
|
1. **Explicitly out of scope** for v1? Force non-goals list — where most plans drift.
|
|
57
59
|
2. v1.x / v2 already implied? Name it so we don't bake assumptions into v1's data model.
|
|
58
60
|
3. **Phase granularity:** one phase per spec use-case (more, smaller PRs) or allow bundling closely-related use-cases (fewer, larger phases)? **Default: one use-case per phase** — confirm or override. Drives the "One use-case per phase" rule under **Phase structure**.
|
|
61
|
+
4. **Hard sequencing constraints:** anything that *must* land before something else for a reason the code doesn't show — a deploy window, a data backfill that has to finish first, an external team's review, a contract another repo is already coding against? Name them. Everything else gets its dependency edge from the code itself (see **Phase dependencies and parallel execution**), and the executor runs whatever is independent at the same time.
|
|
62
|
+
5. **Parallelism appetite:** is the team fine with several phases being implemented concurrently — several open PRs at once, several branches in flight, reviewers seeing a fan of PRs instead of a chain? **Default: yes.** Say no when review capacity is the bottleneck rather than implementation, or when the repo has a merge queue that serializes anyway.
|
|
59
63
|
|
|
60
64
|
### C. Data model & storage
|
|
61
65
|
1. New table, new column on existing, JSONB blob, side table, no persistence?
|
|
@@ -155,6 +159,8 @@ Pushback *"just write the plan"*: write it but **mark every assumption explicitl
|
|
|
155
159
|
Method / path / payload / response shape / errors.
|
|
156
160
|
|
|
157
161
|
## 5. Phased Rollout
|
|
162
|
+
Opens with the **Crew** table and the **Execution graph** table (see "Staff
|
|
163
|
+
the plan" and "Phase dependencies and parallel execution"), then the phases.
|
|
158
164
|
See "Phase structure" below.
|
|
159
165
|
|
|
160
166
|
## 6. Risk & Rollout Notes
|
|
@@ -181,6 +187,100 @@ Don't invent new top-level sections. Skip non-applicable (e.g. omit "API Design"
|
|
|
181
187
|
- **Foundation phase**: `Phase 0` for pure scaffolding (new app skeleton, no behavior change). Optional.
|
|
182
188
|
- Consistent inside one plan: don't mix `Phase 2.1` with `Phase 3a`.
|
|
183
189
|
|
|
190
|
+
**Numbering is a reading aid, not an execution order.** What actually orders the build is each phase's `**Depends on**:` line — see below. Number the phases so a human reads them top to bottom in a sensible narrative; let the dependency graph decide what runs when.
|
|
191
|
+
|
|
192
|
+
## Phase dependencies and parallel execution
|
|
193
|
+
|
|
194
|
+
[implement-plan](../implement-plan/SKILL.md) implements phases **concurrently** — one worktree lane per phase in flight — whenever the graph says two phases don't need each other. That only works if the plan says what needs what. So every phase carries a `**Depends on**:` line, and **Phased Rollout** opens with the graph those lines imply.
|
|
195
|
+
|
|
196
|
+
### `**Depends on**:` — one line per phase, always present
|
|
197
|
+
|
|
198
|
+
```markdown
|
|
199
|
+
**Depends on**: Phase 1 (the `BookmarkFolder` model and its migration), Phase 2 (the `bookmark_repository.list_for_user` method this endpoint calls).
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
or, for a phase that needs nothing:
|
|
203
|
+
|
|
204
|
+
```markdown
|
|
205
|
+
**Depends on**: nothing — starts from the base branch.
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Rules:
|
|
209
|
+
|
|
210
|
+
- **Name a phase only when this phase's code would not compile, import, or pass its tests without it.** The dependency is a *code* fact: a model, a column, a symbol, a migration, an endpoint, a fixture. If you can't name the artifact, there is no edge.
|
|
211
|
+
- **One clause per edge, naming the artifact.** The prose after the em dash is what the reviewer (and the implementer's prompt) reads to understand the coupling. `**Depends on**: Phase 1` with no reason is a smell — usually it means "Phase 1 comes first in the list", which is not a dependency.
|
|
212
|
+
- **Don't chain by habit.** The most common planning mistake here is writing `Phase 4` depends on `Phase 3` depends on `Phase 2` when in truth all three only need the Phase 1 migration. That single reflex turns a 3-lane plan into a 4-week queue.
|
|
213
|
+
- **Do declare the edges that exist.** The opposite failure is worse: two phases that both rewrite the same use case, declared independent, get implemented simultaneously against divergent bases and collide at merge.
|
|
214
|
+
- **No cycles.** Two phases that need each other are one phase, or the boundary is drawn in the wrong place.
|
|
215
|
+
- **Cross-repo phases (`Phase Nb`) can be depended on**, but everything downstream of one inherits its deploy cadence — and the executor defers the whole subtree. Prefer designing so in-repo work depends on a *contract* (accept and drop the field) rather than on the producer actually shipping.
|
|
216
|
+
|
|
217
|
+
### The Execution graph table
|
|
218
|
+
|
|
219
|
+
Second thing under **Phased Rollout**, right after the **Crew** table (see "Staff the plan") and before the phases:
|
|
220
|
+
|
|
221
|
+
```markdown
|
|
222
|
+
### Execution graph
|
|
223
|
+
|
|
224
|
+
Wave = how deep a phase sits in the dependency graph. Phases in the same wave have no
|
|
225
|
+
dependency on each other and are implemented concurrently.
|
|
226
|
+
|
|
227
|
+
| Wave | Phases | Agent | Depends on |
|
|
228
|
+
|---|---|---|---|
|
|
229
|
+
| 1 | Phase 0, Phase 1b | `junior`, `mid-2` | — |
|
|
230
|
+
| 2 | Phase 1, Phase 2 | `mid-1`, `mid-2` | Phase 0 |
|
|
231
|
+
| 3 | Phase 3 | `senior` | Phase 1, Phase 2 |
|
|
232
|
+
| 4 | Phase 4 — remove the `bookmarks-v2` flag | `junior` | Phase 3 (deferred — soak-gated) |
|
|
233
|
+
|
|
234
|
+
**File overlap:** phases in the same wave touch disjoint files, with one exception —
|
|
235
|
+
Phase 1 and Phase 2 both export from `@app/bookmarks/__init__.py`. Trivial merge.
|
|
236
|
+
|
|
237
|
+
**Idle:** `senior` has nothing until wave 3 and `mid-1` nothing until wave 2 — the
|
|
238
|
+
foundation phase is the whole of wave 1 and only one agent can write it.
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The table is **derived from the `**Depends on**:` lines, not authored independently.** Compute it: a phase with no dependencies is wave 1; otherwise its wave is one past the deepest phase it depends on. The executor recomputes this and will flag a table that disagrees.
|
|
242
|
+
|
|
243
|
+
**The Agent column is the staffing schedule, and it is the point of writing the table down.** Reading across a row tells you who is working that wave; reading down a column tells you who is not. The **Idle** note under the table says the second part out loud, because a wave nobody notices is a wave that silently doubles the plan's wall clock. It is a statement of fact, not an apology — a genuinely sequential foundation phase leaves everyone idle and that is correct. What it must never be is a surprise.
|
|
244
|
+
|
|
245
|
+
The `depends_on` edges in the workflow JSON come off the **same** lines — table and JSON are two renderings of one graph, never two graphs kept in sync by hand. See "Emit the executable workflow".
|
|
246
|
+
|
|
247
|
+
### Same-wave phases must not fight over the same files
|
|
248
|
+
|
|
249
|
+
Before publishing the plan, cross-check the **Touch List**: for every pair of phases in the same wave, look at their file sets. Overlap means two agents editing one file on two branches at once, and a merge conflict at the wave boundary.
|
|
250
|
+
|
|
251
|
+
- **Trivial overlap** (a shared `__init__.py`, a settings registry, a route table) — fine. Note it under the graph table so the reviewer isn't surprised.
|
|
252
|
+
- **Real overlap** (the same use case, the same serializer, the same view) — **add the dependency edge** and let one phase build on the other. A serialized pair that merges cleanly beats a parallel pair that needs a human to untangle.
|
|
253
|
+
|
|
254
|
+
### Design *for* parallelism when it's cheap
|
|
255
|
+
|
|
256
|
+
Two habits pay for themselves:
|
|
257
|
+
|
|
258
|
+
- **Front-load shared scaffolding into a wave-1 foundation phase.** Types, the migration, the empty module, the fixture. Every use-case phase then depends only on that one phase, and they all run at once, instead of forming a chain.
|
|
259
|
+
- **Split by seam, not by layer, when the seams are independent.** Four use-cases on the same entity are four independent phases if they only share the model. Four layers of one use-case (repository → service → serializer → view) are a chain no matter how you number them.
|
|
260
|
+
|
|
261
|
+
Don't contort the plan for concurrency, though. A genuinely sequential feature is a chain of waves of one, and that is a correct plan.
|
|
262
|
+
|
|
263
|
+
### Read the previous runs' post-mortems before drawing the graph
|
|
264
|
+
|
|
265
|
+
Every plan you write is a guess about coupling. Every plan the orchestrator *ran* turned that guess into evidence, and it wrote the evidence down: one `postmortem.json` per finished run under `.vinta-ai-maestro/runs/<run-id>/`, plus any copy the team committed beside its plan as `ai-plans/<feature-kebab>.postmortem.json`. **Read them before the `**Depends on**:` lines, not after.** Newest first, and all of them — one run is an anecdote, three runs saying the same thing about the same layer is a rule about this codebase.
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
ls -t .vinta-ai-maestro/runs/*/postmortem.json ai-plans/*.postmortem.json 2>/dev/null | head -5
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Each file carries `findings` and `gaps`. Use them like this:
|
|
272
|
+
|
|
273
|
+
- **`missing_dependencies`** — a phase failed, a phase it did *not* declare landed, and only then did it pass. The previous plan was missing that edge. If this feature couples the same two layers, **declare the edge here**, naming the artifact. Entry is ordering evidence, not proof (the file's own `gate_result_unrecorded` gap says so) — confirm the coupling exists in the code before you draw it.
|
|
274
|
+
- **`wave_conflicts`** — two same-wave phases that actually fought, with the contested `paths`. Cross-check those paths against this plan's **Touch List**: two phases of yours touching one of them in the same wave is the same defect repeating. Add the edge, or split so only one phase owns the file.
|
|
275
|
+
- **`duration_divergences`** — `direction: "longer"` means that phase set its wave's wall clock alone, so every peer you parallelised it with bought nothing; keep comparable-size work together and let the long pole start in wave 1. `"shorter"` means a small phase sat behind a long one and could have been folded in or moved earlier. Sizing, never time estimates in the plan body.
|
|
276
|
+
- **`unused_dependencies`** — edges the run proved nobody needed. Drop the equivalent edge here. **Empty is not evidence of a tight graph**: check `gaps` first, because a `dependency_use_unrecorded` entry means dependency use was never measured on that run, not that every edge earned its place.
|
|
277
|
+
|
|
278
|
+
Rules for using them:
|
|
279
|
+
|
|
280
|
+
- **Match by artifact and path, never by phase id.** `p3` in an old run is not `Phase 3` here. The transferable fact is "the serializer phase needed the migration phase's column", not the id.
|
|
281
|
+
- **A finding is an input, not plan content.** Don't quote post-mortems in the plan body, don't cite run ids, don't add a section about them. They change edges, waves and splits — that's all the reader should ever see.
|
|
282
|
+
- **No post-mortems in the repo?** Nothing to do, and nothing to say about it. Draw the graph from the code.
|
|
283
|
+
|
|
184
284
|
### Each phase MR-sized
|
|
185
285
|
|
|
186
286
|
Reviewer should read ≤1500 LoC + understand in isolation. Guidelines:
|
|
@@ -226,6 +326,8 @@ Doesn't fit → split: `Phase 4a — Static validation`, `Phase 4b — Resolutio
|
|
|
226
326
|
**Goal**: one sentence on user-visible (or producer-visible) outcome.
|
|
227
327
|
"Ship value: none on its own" → say so explicitly + justify why scaffolding needed.
|
|
228
328
|
|
|
329
|
+
**Depends on**: {phase ids, each with the artifact this phase needs from it — or `nothing — starts from the base branch`}. See "Phase dependencies and parallel execution". **Required on every phase**; the executor refuses to guess.
|
|
330
|
+
|
|
229
331
|
**Feature flag**: `{flag-key}` — {gated path; what runs when off vs on}.
|
|
230
332
|
Omit only if phase is purely scaffolding (no reachable behavior) or **Guiding Decisions** explicitly marks "no flag — purely additive surface".
|
|
231
333
|
|
|
@@ -243,9 +345,9 @@ Tests:
|
|
|
243
345
|
- **E2E** (only when e2e coverage was opted into at Step 0 AND this phase reaches the browser): {e2e/tests/<app>/<id>-<slug>.spec.ts} — happy path covering the new flow. Spec writes screenshots to the Playwright default output dir via `testInfo.outputPath(...)`; post-run copy step moves them into `pr-screenshots/<id>-<step>.png`. Follow [add-e2e-test](../add-e2e-test/SKILL.md).
|
|
244
346
|
<!-- e2e:end -->
|
|
245
347
|
|
|
246
|
-
**
|
|
348
|
+
**Assigned to**: `{crew-id}` (Tier {N}) — {why this phase needs that tier, in the rubric's terms}. See "Staff the plan".
|
|
247
349
|
|
|
248
|
-
**Review models** (optional — omit for the
|
|
350
|
+
**Review models** (optional — omit for the derived default): reviewer Tier {N}, fixer Tier {N} — {why this phase warrants a review above the one its tier already implies}. See "Staff the plan".
|
|
249
351
|
|
|
250
352
|
**Reusable skills**: {invoke `Skill(name)` — see "Project skills"}.
|
|
251
353
|
|
|
@@ -283,6 +385,8 @@ Gated on real-world signal, not phase number — can't merge until flag on 100%
|
|
|
283
385
|
|
|
284
386
|
**Goal**: delete flag + dead off-branch so feature becomes unconditional. **Prerequisite**: flag has been on for 100% of tenants in production for at least {soak window — typically 2 weeks, or one full end-of-month/quarter cycle if feature touches reporting}, with no rollback or incident attributed.
|
|
285
387
|
|
|
388
|
+
**Depends on**: every gated phase — {list them} — since this phase deletes the branches they added.
|
|
389
|
+
|
|
286
390
|
**Feature flag**: removed in this phase.
|
|
287
391
|
|
|
288
392
|
Changes:
|
|
@@ -299,7 +403,7 @@ Tests:
|
|
|
299
403
|
- Existing test suite passes unchanged on on-branch.
|
|
300
404
|
- Remove flag-parametrized tests no longer make sense.
|
|
301
405
|
|
|
302
|
-
**
|
|
406
|
+
**Assigned to**: `{the roster's Tier 1 member}` (Tier 1) — mechanical deletion + inlining; exact precedent by construction, since every line it removes was added by a phase above it.
|
|
303
407
|
|
|
304
408
|
**Reusable skills**: none — pure cleanup.
|
|
305
409
|
|
|
@@ -308,60 +412,120 @@ Acceptance: `grep -r "{flag-key}" app/ tests/` returns nothing, feature behaves
|
|
|
308
412
|
|
|
309
413
|
Place as separate, numbered, last-in-list entry inside **Phased Rollout**. Also in **Touch List** under its own phase. **Required**.
|
|
310
414
|
|
|
311
|
-
###
|
|
415
|
+
### Put the slowest-moving dependency in wave 1
|
|
416
|
+
|
|
417
|
+
Common mistake: leave cross-repo producer wiring for last, then discover the upstream repo's deploy cadence is two weeks. Give the slow path **no dependencies** so it starts in wave 1 (e.g. *"accept field, validate, drop on floor"*) + let fast in-repo work fill in behind it. Typical shape: `Phase 1` (API stub) and `Phase 1b` (cross-repo producer) both depend on nothing and run in wave 1; `Phase 2`+ (in-repo persistence) depends on `Phase 1` only — so it does **not** wait on the cross-repo lane.
|
|
312
418
|
|
|
313
|
-
|
|
419
|
+
Watch for the accidental version of this: making an in-repo phase `**Depends on**: Phase 1b` when it only needs the *contract*, not the producer's deploy. That one edge parks the whole in-repo plan behind another team's release train.
|
|
420
|
+
|
|
421
|
+
### Flag-removal phase depends on everything
|
|
422
|
+
|
|
423
|
+
The mandatory final flag-removal phase depends on **every** gated phase — it deletes the branches they added. Say so in its `**Depends on**:` line. It sits alone in the deepest wave and is deferred by the executor anyway (soak-gated), so this edge costs nothing and documents the real constraint.
|
|
314
424
|
|
|
315
425
|
### Never give time estimates
|
|
316
426
|
|
|
317
427
|
**Don't** write "~2 days" / "1 sprint" / "ETA: …". Time estimates for AI-implemented work pointless + become targets LLM optimizes against. **LoC sizing (`~150 LoC`) fine** — reviewability signal, not time.
|
|
318
428
|
|
|
319
|
-
##
|
|
429
|
+
## Staff the plan
|
|
430
|
+
|
|
431
|
+
A plan does not pick a model per phase. It **staffs a team**, then assigns each phase to somebody on it.
|
|
432
|
+
|
|
433
|
+
The difference is not cosmetic. Choosing a tier phase by phase answers "what should run this one" ten times and never adds it up, so the two questions that actually decide what a feature costs go unasked: **how many agents does this need at once, and is any of them too junior for what it was handed.** A roster asks both before a line is written, and it is falsifiable — you can look at it and say "nobody needs three seniors here".
|
|
434
|
+
|
|
435
|
+
### The Crew table
|
|
436
|
+
|
|
437
|
+
First thing under **Phased Rollout**, before the **Execution graph**:
|
|
438
|
+
|
|
439
|
+
```markdown
|
|
440
|
+
### Crew
|
|
441
|
+
|
|
442
|
+
| Agent | Role | Tier | Takes | Why this tier |
|
|
443
|
+
|---|---|---|---|---|
|
|
444
|
+
| `junior` | implementer | 1 | Phase 0, Phase 4 | An empty module and a flag deletion — exact precedent, both of them. |
|
|
445
|
+
| `mid-1` | implementer | 2 | Phase 1 | A DRF viewset mirroring `@app/tags/api/views.py` almost line for line. |
|
|
446
|
+
| `mid-2` | implementer | 2 | Phase 1b, Phase 2 | Serializer with cross-field validation; the producer stub is the same shape. |
|
|
447
|
+
| `senior` | implementer | 4 | Phase 3 | Cycle detection over a user-mutable tree — no precedent in this repo. |
|
|
448
|
+
| `reviewer` | reviewer | 4 | — reviews every phase | Reads the tree phase too; a reviewer below the hardest phase can never take it. |
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Implementers write; reviewers read; nobody does both.** That is not a convention, it is the shape of the document — an implementer cannot be assigned a review and a reviewer cannot be assigned a phase, so an agent grading its own work is not something a plan can express. It replaces the older "review one tier above the author" rule, which was a proxy for independence and broke in both directions: a phase covered by the top-tier implementer had nobody above it, and a tier says nothing about *who* now that a member is a durable agent rather than a model id.
|
|
452
|
+
|
|
453
|
+
Staff **at least one reviewer** on every plan. A roster of implementers only still runs — reviews fall back to the project's `agent_models.reviewer`, cold, one session per phase — but the reviewer then never accumulates anything about the codebase, which is most of what makes review cheap by phase three.
|
|
454
|
+
|
|
455
|
+
**A reviewer's tier is a floor too.** It reads phases at or below its own tier, so a Tier 2 reviewer cannot take the Tier 4 phase. One reviewer at the plan's hardest tier is the simple answer; two (a cheap one and a capable one) is worth it on a plan with a wide tier spread, because then the Tier 1 phases are not read by the priciest model you have.
|
|
456
|
+
|
|
457
|
+
**Every implementer has to earn their place, and there are exactly two ways to do it:**
|
|
458
|
+
|
|
459
|
+
- **Concurrency.** A wave of three phases needs three pairs of hands, and they must be hands *at or above* each of those phases' tiers — two mids and a junior cannot run three Tier 2 phases two-wide, because the junior is not allowed to take one. So go wave by wave: sort the wave's phase tiers, sort the roster's tiers, and check the roster covers them one for one.
|
|
460
|
+
- **Cheapness.** A member below the roster's other tiers earns their place by taking work the dearer members would otherwise do. A junior who runs the migration and the flag deletion is worth having even in a graph that is never two phases wide, because those two phases run at Tier 1 instead of Tier 2.
|
|
461
|
+
|
|
462
|
+
**A member who does neither should not be on the roster** — a second mid in a graph that is never two wide adds no concurrency and saves nothing, and the executor refuses a member assigned no phase at all.
|
|
463
|
+
|
|
464
|
+
Note what this does *not* say: the roster is not capped at the widest wave. Concurrency is capped there, but a cheaper member is bought with money, not with parallelism, and adding one to a narrow graph is a perfectly good trade — it now costs a worktree, which is the honest price (see "Every member gets a desk").
|
|
465
|
+
|
|
466
|
+
Two more rules:
|
|
467
|
+
|
|
468
|
+
- **Prefer fewer tiers over more.** Two mids beat one mid and one "upper-mid": a tier is a floor on what a member may be handed, so a roster that draws fine distinctions only makes it harder to cover for a busy peer.
|
|
469
|
+
- **The `Takes` column is the assignment**, and it must agree with every phase's `**Assigned to**:` line. They are two renderings of one decision, so write the phases and let the table fall out — never the reverse.
|
|
470
|
+
|
|
471
|
+
### Assigning a phase
|
|
472
|
+
|
|
473
|
+
Two constraints, and they pull against each other. That is what makes this a judgement rather than a lookup.
|
|
320
474
|
|
|
321
|
-
|
|
475
|
+
**The rubric tier is a floor.** Never hand a Tier 3 phase to a junior to save money. A cheap model on work above its tier does not fail cleanly — it produces plausible code that fails review two rounds later, and by then nothing points back at the staffing decision. Default to the cheapest tier that plausibly works; that is not the same as the cheapest tier available.
|
|
322
476
|
|
|
323
|
-
**
|
|
477
|
+
**Nobody should be idle while work they could do is queued.** If the senior is busy from wave 1 to wave 4 and the two juniors are idle after wave 1, the plan is a senior-shaped chain with decoration. Rebalance by splitting a phase or moving a dependency, not by handing the senior's work to a junior.
|
|
324
478
|
|
|
325
|
-
|
|
326
|
-
- `**Review models**:` (optional) overrides the reviewer and/or fixer tier **for this phase only**. Use it when a phase is riskier than average — high blast-radius change, subtle concurrency / transaction logic, security-sensitive surface, a migration that's hard to undo — and you want a more capable reviewer or fixer than the project default. Name a tier for reviewer, fixer, or both; omit either to leave that role on the default.
|
|
327
|
-
- **Precedence** (resolved by `implement-plan` / `review-phase`): a phase's `**Review models**:` override wins → else the project-wide `agent_models.reviewer` / `agent_models.fixer` tier in `.vinta-ai-workflows.yaml` → else the runtime default. So the project keeps sane defaults and the plan only speaks up for the phases that need a different review model.
|
|
328
|
-
- The mechanical-step models (worktree prep, opening the PR / integrate) are **not** plan-owned — they stay under `agent_models` in `.vinta-ai-workflows.yaml`. Don't add worktree/PR model hints to a phase; they'd be ignored.
|
|
479
|
+
When those two genuinely cannot both hold, **the floor wins and the plan says so** in the **Idle** note. A wave that serializes is a schedule; a phase run below its tier is a defect.
|
|
329
480
|
|
|
330
|
-
|
|
481
|
+
### The tier rubric
|
|
331
482
|
|
|
332
|
-
**Concrete model IDs per tier live in [resources/ai-models.yaml](resources/ai-models.yaml) — read that file when
|
|
483
|
+
**Concrete model IDs per tier live in [resources/ai-models.yaml](resources/ai-models.yaml) — read that file when staffing the roster. Never recall model names from memory; they go stale as vendors ship.** The tiers below define *when* each applies (stable judgement); the IDs drift, and a nightly job keeps the resource current. Note the file's `last_verified` date — if it's far in the past, the IDs may be stale; flag that rather than trusting them blindly.
|
|
333
484
|
|
|
334
|
-
|
|
485
|
+
#### Tier 1 — cheapest/fastest (boilerplate, exact-precedent edits)
|
|
335
486
|
**Use for**: single migration adding column or index, exporting from `__init__.py`, registering admin, scaffolding empty Django app, thin serializer mirroring existing pattern verbatim.
|
|
336
487
|
|
|
337
|
-
|
|
488
|
+
#### Tier 2 — standard pattern application
|
|
338
489
|
**Use for**: repository methods, DRF serializer with non-trivial validation, ViewSet wiring with filterset, pytest unit/integration tests against established fixtures, simple HStore/ArrayField additions.
|
|
339
490
|
|
|
340
|
-
|
|
491
|
+
#### Tier 3 — multi-file orchestration, business logic, SQL views
|
|
341
492
|
**Use for**: use case coordinating across repositories with non-trivial branching, new `vw_*` view + non-managed model + migration, serializer with cross-field validation affecting use-case behavior, integration tests covering concurrency edges.
|
|
342
493
|
|
|
343
|
-
|
|
494
|
+
#### Tier 4 — architectural / novel / hard
|
|
344
495
|
**Use for**: cycle detection in user-mutable trees, transactional batch protocols with deferred constraints, partitioned-to-partitioned FK design, perf tuning slow query against partitioned hot table, debugging heisenbug.
|
|
345
496
|
|
|
346
|
-
###
|
|
497
|
+
### Who reviews
|
|
347
498
|
|
|
348
|
-
|
|
499
|
+
**The cheapest reviewer on the roster whose tier is at or above the phase's.** Not one tier above the author — the independence comes from the role, so a peer-tier review is a genuine second pair of eyes rather than the same agent grading itself.
|
|
349
500
|
|
|
350
|
-
|
|
501
|
+
Three things follow that are worth knowing before you staff:
|
|
351
502
|
|
|
352
|
-
|
|
503
|
+
- **A reviewer is claimed, not borrowed.** It has one session, so two reviews as the same reviewer would collide over it; a phase whose reviewer is busy waits its turn. One reviewer on a three-lane plan is a queue at the review step — usually fine, occasionally the reason to staff a second.
|
|
504
|
+
- **A reviewer below every phase is refused.** Its tier is a floor, so a Tier 1 reviewer on a plan whose cheapest phase is Tier 2 would never be picked. The executor will not accept it.
|
|
505
|
+
- **No reviewer on the roster falls back to `agent_models.reviewer`**, cold, one session per phase. That is exactly what every plan did before, and it is the one case where a roster leaves something on the table.
|
|
353
506
|
|
|
354
|
-
|
|
507
|
+
Add `**Review models**:` **only** when a phase needs a review above what the roster would pick — high blast-radius, subtle concurrency or transaction logic, a security-sensitive surface, a migration that is hard to undo:
|
|
355
508
|
|
|
356
|
-
|
|
509
|
+
> **Review models**: reviewer Tier 4 — this phase rewrites the transactional batch-apply protocol with deferred constraints; a subtle ordering bug here corrupts data, so the independent review runs on the most capable model regardless of the author's tier. Fixer left on the roster default.
|
|
357
510
|
|
|
358
|
-
|
|
511
|
+
**Precedence** (resolved by `implement-plan` / `review-phase`): a phase's `**Review models**:` override wins → else the cheapest roster reviewer at or above the phase's tier → else the project-wide `agent_models.reviewer` / `agent_models.fixer` in `.vinta-ai-workflows.yaml` → else the runtime default.
|
|
359
512
|
|
|
360
|
-
|
|
513
|
+
### Every implementer gets a desk
|
|
361
514
|
|
|
362
|
-
|
|
515
|
+
**An implementer keeps one worktree, and one session, for the whole run.** This is why the roster is worth writing down rather than being a way to pick models: the agent that takes Phase 4 is the agent that took Phase 1, and it still knows where things live, how the suite is run and what this codebase's conventions are. That rediscovery is most of what a cold agent's first turn of a phase is spent on.
|
|
363
516
|
|
|
364
|
-
|
|
517
|
+
It works because the *directory* stops moving. An agent whose worktree changed between phases would be reasoning about paths it is not standing in — that, not the reset, was what made cross-phase reuse unsafe. With the directory pinned, the only open question is which files changed while the member was away, and git answers that exactly: the executor hands the continued agent the list, plus whether the previous phase's own work is in this tree at all.
|
|
518
|
+
|
|
519
|
+
**Reviewers have no desk, and that is the point.** A reviewer reads the lane it is reviewing — the implementer's worktree, with the phase's changes still **uncommitted** in it. Review sits before the commit so that findings are fixed in the working tree, rather than recorded as a mistake on the branch plus a correction after it. A reviewer with a checkout of its own would be reading a committed snapshot: strictly less than what is there, and too late to act on.
|
|
520
|
+
|
|
521
|
+
What this costs, and what to weigh when sizing the roster:
|
|
522
|
+
|
|
523
|
+
- **A worktree and a set of forked databases per implementer.** Adding a cheaper implementer to save a tier on two phases is a real trade now — disk against money — rather than free.
|
|
524
|
+
- **Lane capacity is the number of implementers.** Not the widest wave: an implementer idle in wave 1 still has a desk waiting. Reviewers add nothing to it.
|
|
525
|
+
- **A reviewer's session carries only sometimes.** Its directory moves with the phase it reads, so it continues when two consecutive reviews land in the same lane and starts cold otherwise. Nothing to declare — and a good reason to prefer fewer, busier implementers over many idle ones.
|
|
526
|
+
- **A member whose phase failed starts their next one cold.** Their context is the context that failed, and a wrong conclusion costs more to inherit than a repository costs to re-read. Nothing to declare; the executor does it.
|
|
527
|
+
|
|
528
|
+
**The mechanical-step models are still not plan-owned.** Worktree prep and opening the PR stay under `agent_models` in `.vinta-ai-workflows.yaml`. Don't put them on the roster; they'd be ignored.
|
|
365
529
|
|
|
366
530
|
## Project skills to leverage
|
|
367
531
|
|
|
@@ -396,6 +560,404 @@ See `upsert_records` in [records.py:92-96](../<app>/<module>/models/records.py#L
|
|
|
396
560
|
|
|
397
561
|
Don't mix styles within one sentence. In **Touch List**, use `@path` for new files + `[name](relative-path)` for edited files when want line numbers.
|
|
398
562
|
|
|
563
|
+
## Emit the executable workflow
|
|
564
|
+
|
|
565
|
+
Alongside the markdown plan, write `ai-plans/<feature-kebab>.workflow.json` — same directory, feature name lowercased with hyphens (`BOOKMARK_FOLDERS` → `ai-plans/bookmark-folders.workflow.json`), no date prefix. The markdown is what humans review; the JSON is the same phase graph in the form an orchestrator runs — one worktree lane per phase, branches cut from each phase's dependencies, gates queued behind capacity limits instead of stampeding.
|
|
566
|
+
|
|
567
|
+
**Unconditional.** Write it on every plan. Don't ask, don't gate it on a config field, don't skip it because the project has no orchestrator installed — a project without one carries a few KB it never reads, and a project that installs one later finds its plans already executable. The one thing that is *not* free is emitting it inconsistently: a half-populated `ai-plans/` teaches the team the file is optional.
|
|
568
|
+
|
|
569
|
+
**It is not a second source of truth.** Every value in it is read off the plan you just wrote. Write the plan first, then transcribe. If a field has no answer in the plan, the plan is missing something — go fix the plan, not the JSON.
|
|
570
|
+
|
|
571
|
+
First key in the file is `"$schema"`, pointing at `https://github.com/vintasoftware/vinta-ai-workflows/schemas/workflow.v1.schema.json`. Editors validate against it as you type, which is when a typo is cheap; without it the first thing that reads the file is the executor, an hour into a run.
|
|
572
|
+
|
|
573
|
+
### Mapping the plan onto the document
|
|
574
|
+
|
|
575
|
+
| Field | Comes from |
|
|
576
|
+
|---|---|
|
|
577
|
+
| `$schema` | The URL above, literally. |
|
|
578
|
+
| `schema_version` | `1`. |
|
|
579
|
+
| `id` | The feature kebab — same slug as the filename. It lands in branch names (`plan/{id}/wave-2`), so kebab-case only, no dates, no underscores. |
|
|
580
|
+
| `plan_ref` | Repo-relative path of the markdown plan, e.g. `ai-plans/2026-03-04-BOOKMARK_FOLDERS_IMPLEMENTATION_PLAN.md`. |
|
|
581
|
+
| `plan_context_refs` | The two plan sections that bound **every** phase, as anchors into the plan you just wrote: `<plan_ref>#1-goals` (which carries Non-goals with it) and `<plan_ref>#2-guiding-decisions`, in that order. Same file-and-anchor form as `prompt_ref`. See "Plan-level context". |
|
|
582
|
+
| `base_branch` | What `**Depends on**: nothing — starts from the base branch` means concretely: the repo's default branch, unless **Guiding Decisions** names a long-lived feature branch. |
|
|
583
|
+
| `project` | The databases a lane must **fork** to be a working checkout, plus the command that migrates the template they are forked from. Omit entirely when lanes can share the main checkout's database — see "The `project` block". This is the one part of the document you *ask* about rather than transcribe. |
|
|
584
|
+
| `defaults.harness` | The agent CLI the team runs — `claude-code`, `codex`, or `opencode`. `claude-code` unless the project says otherwise. |
|
|
585
|
+
| `crew` | The **Crew** table, transcribed: one entry per row keyed by the Agent id, carrying that row's `tier` and the model id that tier resolves to in [resources/ai-models.yaml](resources/ai-models.yaml). Required whenever the plan has a roster, which is every plan. |
|
|
586
|
+
| `defaults.model` | The concrete model id for the tier **most** phases carry, pulled from [resources/ai-models.yaml](resources/ai-models.yaml). A staffed workflow never reads it for a phase — every node's model comes off its member — but it is still required, and it is what an amendment adding an unstaffed node would fall back to. |
|
|
587
|
+
| `defaults.pipeline` | `standard-phase` — see "The pipeline block". |
|
|
588
|
+
| `defaults.max_session_turns` | Omit (defaults to 12). It caps how many turns one reused agent session may take before the executor starts a fresh one; the executor reuses sessions across a phase's implement and fix turns, so this is a context-window guard rather than something a plan tunes. |
|
|
589
|
+
| `resources.lane` | `{"capacity": N, "kind": "worktree"}`. **Required** — a lane pool is where phases are dispatched, and a workflow without one has nowhere to run. `N` = the number of **implementers** on the roster, because each keeps one worktree for the whole run. Reviewers add nothing: they read the lane under review. How many run *at once* is still capped by the graph and by the project's parallel-lane budget (3 when unstated); an implementer idle in wave 1 still has a desk. |
|
|
590
|
+
| `resources.<pool>` | One `{"kind": "semaphore"}` pool per expensive shared thing a gate contends for — the test database, the e2e browser grid, a staging deploy slot. `capacity: 1` when only one can run at a time. |
|
|
591
|
+
| `gates.<id>` | The checks a phase must pass, as **shell commands run in the phase's lane** — the project's real typecheck / test / lint invocations, not an agent and not prose. Give the slow ones `requires` naming the pool they contend for, and a `timeout_s` that is generous rather than tight. |
|
|
592
|
+
| `nodes[]` | One per phase, in plan order. |
|
|
593
|
+
| `nodes[].id` | `p` + the phase number, lowercased: `Phase 1` → `p1`, `Phase 4a` → `p4a`, `Phase 1b` → `p1b`. |
|
|
594
|
+
| `nodes[].name` | The phase title without its `Phase N —` prefix. |
|
|
595
|
+
| `nodes[].prompt_ref` | `<plan_ref>#phase-<number>` — the anchor of that phase's heading. Anchor on the number, not the slugified full title: the number is the part that survives a title edit, and the phase brief the implementer reads is located by its `### Phase N` prefix. |
|
|
596
|
+
| `nodes[].depends_on[]` | **One entry per clause** of the phase's `**Depends on**:` line, each carrying both `node` (the upstream node id) and `artifact` (that clause's prose, minus the phase reference). |
|
|
597
|
+
| `nodes[].touches` | That phase's **Touch List** entries as plain repo-relative paths — strip the `@` prefix and any markdown link syntax, keep the trailing `/` on a directory. |
|
|
598
|
+
| `nodes[].gates` | The gate ids this phase must pass, in the order they should run. |
|
|
599
|
+
| `nodes[].crew` | The id from this phase's `**Assigned to**:` line. **Required on every node of a staffed workflow** — a half-staffed document is refused, because the executor would be running two staffing rules at once. |
|
|
600
|
+
| `nodes[].model` / `nodes[].harness` | **Omit `model` entirely on a staffed workflow** — the member carries it, and a node setting both is refused. `harness` only when this one phase runs on a different CLI than `defaults`. |
|
|
601
|
+
| `nodes[].max_fix_rounds` | Omit (defaults to 2). Set it higher only on a phase whose review you expect to iterate — a delicate migration, a concurrency protocol. |
|
|
602
|
+
| `nodes[].pipeline` | Omit. A per-phase pipeline is for a phase that genuinely runs a different lifecycle, which is rare enough that needing it is a signal to re-read the plan. |
|
|
603
|
+
| `pipelines` | **Omit.** The executor ships `standard-phase` — see "The pipeline block". |
|
|
604
|
+
|
|
605
|
+
Rules the mapping depends on:
|
|
606
|
+
|
|
607
|
+
- **`artifact` is required on every edge, and it is the clause's own prose.** It is what the implementer's prompt uses to explain what this phase builds on, so the value is what the clause says the phase needs — "the `BookmarkFolder` model and its migration" — never `p1`, never "depends on Phase 1". If the `**Depends on**:` line has no artifact to transcribe, the edge shouldn't exist; see "`**Depends on**:` — one line per phase, always present".
|
|
608
|
+
- **`touches` is what the same-wave overlap check reads.** Executors *warn* on two same-wave nodes declaring the same path rather than refusing, so an incomplete Touch List doesn't fail loudly — it fails at merge. Transcribe every file the phase creates or edits, including tests.
|
|
609
|
+
- **Never invent a model id.** Pick the tier from the rubric under "Staff the plan", then read the id out of [resources/ai-models.yaml](resources/ai-models.yaml). Ids drift; tiers don't.
|
|
610
|
+
- **The roster is the same decision as the Crew table.** `crew` transcribes it: one entry per row, `tier` from the Tier column, `model` from that tier in `ai-models.yaml`. A member the table does not list, or a table row with no `crew` entry, means the two were edited separately.
|
|
611
|
+
- **Gate commands must be commands the repo actually runs today.** Read them out of the project's task runner (`package.json` scripts, `Makefile`, `pyproject.toml`, CI config) rather than guessing a conventional one. A gate that doesn't exist fails every phase identically, and looks like a code problem.
|
|
612
|
+
- **The graph must agree with the Execution graph table.** Same nodes, same edges, same waves — they are two renderings of one set of `**Depends on**:` lines, so derive both from the lines rather than transcribing one from the other. A disagreement means one was hand-edited, and the executor flags it.
|
|
613
|
+
- **`plan_context_refs` is anchors, never prose.** It names sections of the plan; it never restates them. A summary written into the JSON is a second copy that drifts the first time someone edits the plan, and the whole point of the field is that the implementer reads what the plan actually says.
|
|
614
|
+
|
|
615
|
+
### Plan-level context
|
|
616
|
+
|
|
617
|
+
`prompt_ref` gives a phase its own body. It gives it nothing else — and a phase body alone is how an implementer ends up building something the plan explicitly ruled out, or re-deciding a question **Guiding Decisions** already closed. `plan_context_refs` is where the plan hands every phase the two sections that bound all of them:
|
|
618
|
+
|
|
619
|
+
```json
|
|
620
|
+
"plan_context_refs": [
|
|
621
|
+
"ai-plans/2026-03-04-BOOKMARK_FOLDERS_IMPLEMENTATION_PLAN.md#1-goals",
|
|
622
|
+
"ai-plans/2026-03-04-BOOKMARK_FOLDERS_IMPLEMENTATION_PLAN.md#2-guiding-decisions"
|
|
623
|
+
]
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
The executor resolves each reference the same way it resolves a `prompt_ref` — file, then the named heading's section down to the next heading of the same depth — and hands the text to the implementer and the reviewer **verbatim**, under a heading that says it is the plan's and not the phase's.
|
|
627
|
+
|
|
628
|
+
Rules:
|
|
629
|
+
|
|
630
|
+
- **Anchor on the heading as you wrote it.** The **Plan structure** section numbers those headings — `## 1. Goals`, `## 2. Guiding Decisions` — so their anchors are `#1-goals` and `#2-guiding-decisions`. Write the anchor of the heading that is actually in your plan: an anchor that resolves to nothing fails the phase loudly at spawn time, before any code is written.
|
|
631
|
+
- **Goals carries Non-goals.** Non-goals is a bulleted list *inside* the **Goals** section, so one anchor delivers both. That is why `#1-goals` is not optional here — the non-goals are the half that stops scope creep.
|
|
632
|
+
- **Two entries, both of them.** Not **Data Model Changes** (large, and the phase body names the models it touches), not **Risk & Rollout Notes**, not the whole plan file. Every extra section is paid for in every phase's prompt, twice — once for the implementer, once for the reviewer.
|
|
633
|
+
- **Emit it on every plan**, exactly like the file itself. A workflow without it still runs; its phases just each rediscover the boundaries the plan already drew.
|
|
634
|
+
|
|
635
|
+
### The `project` block
|
|
636
|
+
|
|
637
|
+
Every other field in the document is transcribed from the plan. This one is not: nothing in a feature plan says how the project's databases are delivered, and without the block an executor gives each phase a git worktree and nothing else. Every phase's gate then runs against the same database — so a phase that adds a migration changes the schema every other lane is tested against, and a suite that leaves rows behind changes what the next lane sees. A `test-suite` pool does not fix that: a semaphore orders the suites, it does not give them separate data.
|
|
638
|
+
|
|
639
|
+
`project` says what a lane must **fork** to be a working checkout of this repo. It is optional, and omitting it is a real answer: a repo whose tests need no database at all, or whose suite builds an in-memory one per process, has nothing to declare.
|
|
640
|
+
|
|
641
|
+
**Sharing is the absence of a declaration.** There is no `"share"` value and no `"none"` value, for either role. A lane that reads the main checkout's database has no database of its own to describe, so it says nothing — and a declared database is always forked.
|
|
642
|
+
|
|
643
|
+
Two roles, each optional and each declared separately:
|
|
644
|
+
|
|
645
|
+
- **`dev`** — the database the app runs against inside the lane.
|
|
646
|
+
- **`test`** — the database the gate commands run against. This is the one that matters most: declare it whenever a gate touches a database.
|
|
647
|
+
|
|
648
|
+
`migrate_cmd` is the project's own migrate command. It runs **once per template database**, never per lane — that is what makes the Nth lane cost a copy instead of a provision. Read it out of the project's task runner, the same way gate commands are read, rather than guessing a conventional one.
|
|
649
|
+
|
|
650
|
+
Fields per database, by engine:
|
|
651
|
+
|
|
652
|
+
| `engine` | Fields | What they mean |
|
|
653
|
+
|---|---|---|
|
|
654
|
+
| `postgres` | `delivery`, `name`, `server_url`, `connection_url_var` | `delivery: "external"` forks a new database on a server that is already running — the cheap mode, and the one to prefer, because N lanes cost N cheap clones against one server. `delivery: "compose"` boots the lane its own server on its own forked volume: there is no template to clone from, so such a lane is single-use and gets re-provisioned rather than reset. `name` is the **main checkout's** database name; lane names are derived from it. `server_url` is the server *without* the database path segment — `postgres://localhost:5432`. |
|
|
655
|
+
| `sqlite` | `path`, `connection_url_var` | `path` is the repo-relative path of the database file, e.g. `db.sqlite3`. The lane gets its own copy of it. |
|
|
656
|
+
|
|
657
|
+
**Each role names its own database.** A lane's copy is named from `name` (or `path`) and the lane — the role is not part of it — so declaring `dev` and `test` with the same `name` makes both roles resolve to one forked database and one template. Give them the names the project already uses for them: `bookmarks` and `bookmarks_test`, `db.sqlite3` and `db.test.sqlite3`.
|
|
658
|
+
|
|
659
|
+
`connection_url_var` is the **name** of the env var the project already reads its connection string from — `DATABASE_URL`, `TEST_DATABASE_URL`, whatever the settings module names. The executor sets it per lane. Never write a connection string with credentials in it here: this file is committed beside the plan, and `server_url` is a host and port, not a login.
|
|
660
|
+
|
|
661
|
+
**What does not belong in this block.** Everything a worktree's own provisioning discovers and records per worktree: dependency install-or-link strategy, env file copying, `COMPOSE_PROJECT_NAME` and network naming, volume forks, sandbox tier, redis database indices, S3 prefixes, seed commands, and the `reset_cmd` for each forked database. Those are the `prepare-worktree` skill's, are decided when a lane is created, and are read back off the summary it writes per worktree. `project` records only what has to be known *before* any worktree exists.
|
|
662
|
+
|
|
663
|
+
#### Asking for it
|
|
664
|
+
|
|
665
|
+
Read the project first so the questions carry real defaults — the settings module, `.env.example`, `compose.yaml` / `docker-compose.yml`, the migrations directory, and the task runner. If none of that exists, the repo has no database: omit `project` and ask nothing.
|
|
666
|
+
|
|
667
|
+
Otherwise issue **one `AskUserQuestion` call** carrying both questions:
|
|
668
|
+
|
|
669
|
+
1. *"What does a phase lane need its own copy of?"* — options: `Nothing — lanes share the main database`, `Test database only`, `Dev and test databases`, `Dev database only`. Put the default you found in the question header ("this repo's suite reads `TEST_DATABASE_URL` — default: test only").
|
|
670
|
+
2. *"How is that database delivered?"* — options: `Postgres on a server that is already running`, `Postgres started by Docker Compose`, `SQLite file in the repo`. Both questions ride the same call; if the answer to the first is `Nothing`, this answer is discarded rather than asked again.
|
|
671
|
+
|
|
672
|
+
The remaining values — `migrate_cmd`, the database names, the server URL, the env var names — are **read out of the project, not asked**. They already exist in its settings, its compose file and its task runner, and a question whose answer is on disk wastes a turn. Echo what you found in the read-back summary so a wrong guess gets corrected before the file is written, and fall back to a plain-prose question only where the repo genuinely does not say.
|
|
673
|
+
|
|
674
|
+
### The pipeline block
|
|
675
|
+
|
|
676
|
+
`pipelines` describes what happens *within* one phase — implement → review → fix → gate → integrate — as opposed to `nodes`, which describes what happens *between* phases. It is fixed machinery, not a planning decision.
|
|
677
|
+
|
|
678
|
+
**Omit `pipelines` entirely.** Naming `standard-phase` in `defaults.pipeline` is enough: the executor ships that pipeline and supplies it. Do not paste a copy into the plan — a pasted pipeline is a copy that cannot be fixed centrally, so an executor-side correction would never reach a plan already written, and a hand-edited one is how a plan silently stops running its reviewer.
|
|
679
|
+
|
|
680
|
+
Author a `pipelines` block only when a project genuinely needs a *different* lifecycle. That is an executor-configuration decision made once per project, not a per-plan choice, and a declared id shadows the shipped pipeline of the same name.
|
|
681
|
+
|
|
682
|
+
### Worked example
|
|
683
|
+
|
|
684
|
+
A five-phase plan whose `**Depends on**:` lines are:
|
|
685
|
+
|
|
686
|
+
```markdown
|
|
687
|
+
### Phase 1 — BookmarkFolder model + migration
|
|
688
|
+
**Depends on**: nothing — starts from the base branch.
|
|
689
|
+
|
|
690
|
+
### Phase 2 — Folder CRUD endpoints
|
|
691
|
+
**Depends on**: Phase 1 (the `BookmarkFolder` model and its migration).
|
|
692
|
+
|
|
693
|
+
### Phase 3 — Folder tree serializer
|
|
694
|
+
**Depends on**: Phase 1 (the `BookmarkFolder.parent` self-FK the tree is walked over).
|
|
695
|
+
|
|
696
|
+
### Phase 4 — Nested folder listing endpoint
|
|
697
|
+
**Depends on**: Phase 2 (the `/api/folders` viewset this list action is added to), Phase 3 (the `FolderTreeSerializer` payload shape).
|
|
698
|
+
|
|
699
|
+
### Phase 5 — Remove the `bookmark-folders` feature flag
|
|
700
|
+
**Depends on**: Phase 2 (the flag branches the CRUD endpoints added), Phase 3 (the flag branch in the tree serializer), Phase 4 (the flag branch in the nested listing action).
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
staffed by this **Crew** table:
|
|
704
|
+
|
|
705
|
+
```markdown
|
|
706
|
+
| Agent | Role | Tier | Takes | Why this tier |
|
|
707
|
+
|---|---|---|---|---|
|
|
708
|
+
| `junior` | implementer | 1 | Phase 1, Phase 5 | A model plus its migration, and a flag deletion. Exact precedent, both. |
|
|
709
|
+
| `mid-1` | implementer | 2 | Phase 2 | A DRF viewset mirroring the tags viewset almost line for line. |
|
|
710
|
+
| `mid-2` | implementer | 2 | Phase 3, Phase 4 | Tree serializer and the list action that returns it — same shape twice. |
|
|
711
|
+
| `reviewer` | reviewer | 2 | — reviews every phase | Tier 2 is the hardest phase here, so one reviewer covers the plan. |
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
Three implementers for a graph that is never more than **two** phases wide,
|
|
715
|
+
which is the case worth reading closely. The two mids are there for concurrency:
|
|
716
|
+
wave 2 is two Tier 2 phases, and two hands at Tier 2 is the only way to run it
|
|
717
|
+
two-wide — a junior cannot take one of them. `junior` is there for cheapness:
|
|
718
|
+
without them, Phase 1 and Phase 5 would run on a mid's model for work that has
|
|
719
|
+
exact precedent. `junior` is idle in waves 2 and 3 and that is not a defect; a
|
|
720
|
+
fourth implementer would be, because there would be nothing left for them to
|
|
721
|
+
make cheaper and no third phase for them to run alongside.
|
|
722
|
+
|
|
723
|
+
One reviewer at Tier 2 covers every phase, since Tier 2 is the hardest work on
|
|
724
|
+
this plan. A Tier 1 reviewer would be refused — it could never take Phase 2,
|
|
725
|
+
Phase 3 or Phase 4 — and a Tier 4 one would be paying top rate to read a
|
|
726
|
+
migration. It gets no worktree: it reads whichever lane it is reviewing, with
|
|
727
|
+
that phase's changes still uncommitted.
|
|
728
|
+
|
|
729
|
+
and this **Execution graph** table:
|
|
730
|
+
|
|
731
|
+
```markdown
|
|
732
|
+
| Wave | Phases | Agent | Depends on |
|
|
733
|
+
|---|---|---|---|
|
|
734
|
+
| 1 | Phase 1 | `junior` | — |
|
|
735
|
+
| 2 | Phase 2, Phase 3 | `mid-1`, `mid-2` | Phase 1 |
|
|
736
|
+
| 3 | Phase 4 | `mid-2` | Phase 2, Phase 3 |
|
|
737
|
+
| 4 | Phase 5 — remove the `bookmark-folders` flag | `junior` | Phase 2, Phase 3, Phase 4 (deferred — soak-gated) |
|
|
738
|
+
|
|
739
|
+
**Idle:** both mids in wave 1 — the model has to exist before anything reads it.
|
|
740
|
+
`mid-1` in wave 3, `junior` in waves 2 and 3.
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
and this `ai-plans/bookmark-folders.workflow.json`:
|
|
744
|
+
|
|
745
|
+
```json
|
|
746
|
+
{
|
|
747
|
+
"$schema": "https://github.com/vintasoftware/vinta-ai-workflows/schemas/workflow.v1.schema.json",
|
|
748
|
+
"schema_version": 1,
|
|
749
|
+
"id": "bookmark-folders",
|
|
750
|
+
"plan_ref": "ai-plans/2026-03-04-BOOKMARK_FOLDERS_IMPLEMENTATION_PLAN.md",
|
|
751
|
+
"plan_context_refs": [
|
|
752
|
+
"ai-plans/2026-03-04-BOOKMARK_FOLDERS_IMPLEMENTATION_PLAN.md#1-goals",
|
|
753
|
+
"ai-plans/2026-03-04-BOOKMARK_FOLDERS_IMPLEMENTATION_PLAN.md#2-guiding-decisions"
|
|
754
|
+
],
|
|
755
|
+
"base_branch": "main",
|
|
756
|
+
"project": {
|
|
757
|
+
"migrate_cmd": "uv run python manage.py migrate",
|
|
758
|
+
"databases": {
|
|
759
|
+
"dev": {
|
|
760
|
+
"engine": "postgres",
|
|
761
|
+
"delivery": "external",
|
|
762
|
+
"name": "bookmarks",
|
|
763
|
+
"server_url": "postgres://localhost:5432",
|
|
764
|
+
"connection_url_var": "DATABASE_URL"
|
|
765
|
+
},
|
|
766
|
+
"test": {
|
|
767
|
+
"engine": "postgres",
|
|
768
|
+
"delivery": "external",
|
|
769
|
+
"name": "bookmarks_test",
|
|
770
|
+
"server_url": "postgres://localhost:5432",
|
|
771
|
+
"connection_url_var": "TEST_DATABASE_URL"
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
"crew": {
|
|
776
|
+
"junior": {
|
|
777
|
+
"role": "implementer",
|
|
778
|
+
"tier": 1,
|
|
779
|
+
"model": "claude-haiku-4-5",
|
|
780
|
+
"description": "A model plus its migration, and a flag deletion."
|
|
781
|
+
},
|
|
782
|
+
"mid-1": {
|
|
783
|
+
"role": "implementer",
|
|
784
|
+
"tier": 2,
|
|
785
|
+
"model": "claude-sonnet-5",
|
|
786
|
+
"description": "A DRF viewset mirroring the tags viewset."
|
|
787
|
+
},
|
|
788
|
+
"mid-2": {
|
|
789
|
+
"role": "implementer",
|
|
790
|
+
"tier": 2,
|
|
791
|
+
"model": "claude-sonnet-5",
|
|
792
|
+
"description": "Tree serializer and the list action that returns it."
|
|
793
|
+
},
|
|
794
|
+
"reviewer": {
|
|
795
|
+
"role": "reviewer",
|
|
796
|
+
"tier": 2,
|
|
797
|
+
"model": "claude-sonnet-5",
|
|
798
|
+
"description": "Reads every phase; Tier 2 is the hardest work here."
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
"defaults": {
|
|
802
|
+
"harness": "claude-code",
|
|
803
|
+
"model": "claude-sonnet-5",
|
|
804
|
+
"pipeline": "standard-phase"
|
|
805
|
+
},
|
|
806
|
+
"resources": {
|
|
807
|
+
"lane": {
|
|
808
|
+
"capacity": 3,
|
|
809
|
+
"kind": "worktree",
|
|
810
|
+
"description": "One desk per implementer, kept for the whole run."
|
|
811
|
+
},
|
|
812
|
+
"test-suite": {
|
|
813
|
+
"capacity": 1,
|
|
814
|
+
"kind": "semaphore",
|
|
815
|
+
"description": "The suite runs against a forked database; two at once race on the same fixtures."
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
"gates": {
|
|
819
|
+
"types": {
|
|
820
|
+
"cmd": "uv run mypy apps/",
|
|
821
|
+
"timeout_s": 300
|
|
822
|
+
},
|
|
823
|
+
"unit": {
|
|
824
|
+
"cmd": "uv run pytest",
|
|
825
|
+
"requires": [
|
|
826
|
+
"test-suite"
|
|
827
|
+
],
|
|
828
|
+
"timeout_s": 1800
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
"nodes": [
|
|
832
|
+
{
|
|
833
|
+
"id": "p1",
|
|
834
|
+
"name": "BookmarkFolder model + migration",
|
|
835
|
+
"depends_on": [],
|
|
836
|
+
"prompt_ref": "ai-plans/2026-03-04-BOOKMARK_FOLDERS_IMPLEMENTATION_PLAN.md#phase-1",
|
|
837
|
+
"touches": [
|
|
838
|
+
"apps/bookmarks/models.py",
|
|
839
|
+
"apps/bookmarks/migrations/",
|
|
840
|
+
"tests/bookmarks/test_models.py"
|
|
841
|
+
],
|
|
842
|
+
"gates": [
|
|
843
|
+
"types",
|
|
844
|
+
"unit"
|
|
845
|
+
],
|
|
846
|
+
"crew": "junior"
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
"id": "p2",
|
|
850
|
+
"name": "Folder CRUD endpoints",
|
|
851
|
+
"depends_on": [
|
|
852
|
+
{
|
|
853
|
+
"node": "p1",
|
|
854
|
+
"artifact": "the `BookmarkFolder` model and its migration"
|
|
855
|
+
}
|
|
856
|
+
],
|
|
857
|
+
"prompt_ref": "ai-plans/2026-03-04-BOOKMARK_FOLDERS_IMPLEMENTATION_PLAN.md#phase-2",
|
|
858
|
+
"touches": [
|
|
859
|
+
"apps/bookmarks/api/views.py",
|
|
860
|
+
"apps/bookmarks/api/urls.py",
|
|
861
|
+
"tests/bookmarks/test_api_crud.py"
|
|
862
|
+
],
|
|
863
|
+
"gates": [
|
|
864
|
+
"types",
|
|
865
|
+
"unit"
|
|
866
|
+
],
|
|
867
|
+
"crew": "mid-1"
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
"id": "p3",
|
|
871
|
+
"name": "Folder tree serializer",
|
|
872
|
+
"depends_on": [
|
|
873
|
+
{
|
|
874
|
+
"node": "p1",
|
|
875
|
+
"artifact": "the `BookmarkFolder.parent` self-FK the tree is walked over"
|
|
876
|
+
}
|
|
877
|
+
],
|
|
878
|
+
"prompt_ref": "ai-plans/2026-03-04-BOOKMARK_FOLDERS_IMPLEMENTATION_PLAN.md#phase-3",
|
|
879
|
+
"touches": [
|
|
880
|
+
"apps/bookmarks/api/serializers.py",
|
|
881
|
+
"tests/bookmarks/test_serializers.py"
|
|
882
|
+
],
|
|
883
|
+
"gates": [
|
|
884
|
+
"types",
|
|
885
|
+
"unit"
|
|
886
|
+
],
|
|
887
|
+
"crew": "mid-2"
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
"id": "p4",
|
|
891
|
+
"name": "Nested folder listing endpoint",
|
|
892
|
+
"depends_on": [
|
|
893
|
+
{
|
|
894
|
+
"node": "p2",
|
|
895
|
+
"artifact": "the `/api/folders` viewset this list action is added to"
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
"node": "p3",
|
|
899
|
+
"artifact": "the `FolderTreeSerializer` payload shape"
|
|
900
|
+
}
|
|
901
|
+
],
|
|
902
|
+
"prompt_ref": "ai-plans/2026-03-04-BOOKMARK_FOLDERS_IMPLEMENTATION_PLAN.md#phase-4",
|
|
903
|
+
"touches": [
|
|
904
|
+
"apps/bookmarks/api/views.py",
|
|
905
|
+
"apps/bookmarks/use_cases/list_folder_tree.py",
|
|
906
|
+
"tests/bookmarks/test_api_tree.py"
|
|
907
|
+
],
|
|
908
|
+
"gates": [
|
|
909
|
+
"types",
|
|
910
|
+
"unit"
|
|
911
|
+
],
|
|
912
|
+
"crew": "mid-2"
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
"id": "p5",
|
|
916
|
+
"name": "Remove the bookmark-folders feature flag",
|
|
917
|
+
"depends_on": [
|
|
918
|
+
{
|
|
919
|
+
"node": "p2",
|
|
920
|
+
"artifact": "the flag branches the CRUD endpoints added"
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
"node": "p3",
|
|
924
|
+
"artifact": "the flag branch in the tree serializer"
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
"node": "p4",
|
|
928
|
+
"artifact": "the flag branch in the nested listing action"
|
|
929
|
+
}
|
|
930
|
+
],
|
|
931
|
+
"prompt_ref": "ai-plans/2026-03-04-BOOKMARK_FOLDERS_IMPLEMENTATION_PLAN.md#phase-5",
|
|
932
|
+
"touches": [
|
|
933
|
+
"apps/core/feature_flags.py",
|
|
934
|
+
"apps/bookmarks/api/views.py",
|
|
935
|
+
"apps/bookmarks/api/serializers.py",
|
|
936
|
+
"tests/bookmarks/test_api_crud.py",
|
|
937
|
+
"tests/bookmarks/test_api_tree.py"
|
|
938
|
+
],
|
|
939
|
+
"gates": [
|
|
940
|
+
"types",
|
|
941
|
+
"unit"
|
|
942
|
+
],
|
|
943
|
+
"crew": "junior"
|
|
944
|
+
}
|
|
945
|
+
]
|
|
946
|
+
}
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
Read the three renderings against each other: `p2` and `p3` both name only `p1`, so they sit in wave 2 and run at once; `p4` names both, so it is wave 3; `p5` names every gated phase, so it is wave 4 and alone there. `p2` and `p4` both touch `apps/bookmarks/api/views.py` — allowed, because the edge between them puts them in different waves; had they been same-wave, that overlap is what "Same-wave phases must not fight over the same files" is about.
|
|
950
|
+
|
|
951
|
+
No node carries a `model`. `p1` and `p5` are the Tier 1 phases and run on `junior`'s model because that is who took them — the roster says it once instead of two nodes repeating an id that goes stale on the next model bump.
|
|
952
|
+
|
|
953
|
+
`mid-2` takes `p3` and then `p4`, which is the pairing worth seeing: the same agent writes the tree serializer and the action that returns it, in the same worktree, **continuing the same session**. Its second phase does not pay to work out where the serializers live or how the suite is run — it did that in `p3`. What it *is* told, because the tree moved underneath it, is that it is now on `p4`'s branch, that `p3`'s work is in this tree (`p4` depends on it), and exactly which files differ from what it last saw.
|
|
954
|
+
|
|
955
|
+
No node names a reviewer. Every phase is read by `reviewer`, the only member on the roster whose role is to read them — so no agent ever reads its own diff. Its session carries only between reviews that land in the same lane, because it goes where the work is rather than keeping a tree of its own.
|
|
956
|
+
|
|
957
|
+
`plan_context_refs` points at the same plan file the `prompt_ref`s do, at its **Goals** and **Guiding Decisions** headings. Each of the five phases is handed those two sections whole, so the implementer of `p3` knows that the tree serializer is deliberately not paginated if the plan's Non-goals said so, and the reviewer of `p3` can call a paginated one scope creep instead of a bonus.
|
|
958
|
+
|
|
959
|
+
The lane pool is **three**: one desk per implementer, kept for the whole run so each one's session has a directory to come back to. Only two are ever busy at once — the graph is never wider than that — and the third idle desk is the price of `junior`'s session. `reviewer` has no desk at all; it reads whichever lane it is reviewing, changes still uncommitted, which is what lets a finding be fixed before the commit rather than after it. The `project` block is what lets those worktrees exist at once. `bookmarks_test` is forked per lane from a template that `uv run python manage.py migrate` builds once, so `p2` and `p3` run `uv run pytest` against separate rows instead of the same ones; `test-suite` stays at capacity 1 because three suites at once melt the machine, not because they would corrupt each other. `dev` and `test` name two different databases, which is what keeps their forks from being the same database under two roles.
|
|
960
|
+
|
|
399
961
|
## What to avoid
|
|
400
962
|
|
|
401
963
|
- **No `§N` shorthand for section references — anywhere in the plan body.** Use section names: `Goals + Non-goals`, `Guiding Decisions`, `Data Model Changes`, `API Design`, `Phased Rollout`, `Risk & Rollout Notes`, `Open Questions`, `Touch List`. Readers shouldn't have to count headings to follow a cross-reference, and section numbering shifts when the spec/plan evolves. Same rule applies to citing SPEC sections (`Use-cases`, `Acceptance scenarios`, etc.) — name them.
|
|
@@ -403,6 +965,14 @@ Don't mix styles within one sentence. In **Touch List**, use `@path` for new fil
|
|
|
403
965
|
- **No vibes-based guarantees** ("should be straightforward", "trivial", "easy lift").
|
|
404
966
|
- **No skipped non-goals section.**
|
|
405
967
|
- **No phase that breaks build if merged alone.** Each independently mergeable AND independently reversible.
|
|
968
|
+
- **No `**Depends on**:` edge you can't justify with an artifact.** "It's later in the list" is not a dependency; it's a chain that costs the team a week of wall-clock for nothing.
|
|
969
|
+
- **No two same-wave phases rewriting the same file.** Either add the edge or split differently.
|
|
970
|
+
- **No phase assigned below the tier its work implies.** A cheap model on work above its tier does not fail cleanly; it fails review two rounds later, and by then nothing points at the staffing.
|
|
971
|
+
- **No implementer who takes no phase**, and no reviewer below every phase on the plan. Both are agents budgeted for that can never be picked, and the executor refuses both.
|
|
972
|
+
- **No phase assigned to a reviewer, and no reviewer that also implements.** The roles are disjoint so that an agent reading its own diff is not something the document can say.
|
|
973
|
+
- **No `model` on a node of a staffed workflow.** The member carries it; a node with both is refused rather than one quietly outranking the other.
|
|
974
|
+
- **No repeating a defect a post-mortem already recorded.** A `wave_conflicts` entry on those paths, or a `missing_dependencies` entry between those layers, means the last run already paid for the lesson; drawing the same graph again wastes it.
|
|
975
|
+
- **No plan without its `.workflow.json` sibling, and no sibling that disagrees with the plan.** Different nodes, different edges, different waves, a `prompt_ref` pointing at a phase that was renumbered — all of them mean the two files were edited separately instead of derived from the same `**Depends on**:` lines.
|
|
406
976
|
- **No phase requiring manual `kubectl` / SSH / "remember to run X"** without Risk & Rollout Notes checklist.
|
|
407
977
|
- **No assuming user wants what they asked for.** Watch for "wait, also…" + update plan.
|
|
408
978
|
|
|
@@ -424,8 +994,19 @@ When in doubt, model the plan after a recent example in `ai-plans/` — look for
|
|
|
424
994
|
- [ ] Phases MR-sized (≤1500 LoC) + independently mergeable.
|
|
425
995
|
- [ ] Phase numbering uses numbers + letters consistently.
|
|
426
996
|
- [ ] **Phase granularity matches the Step 0 answer.** Default (one-use-case-per-phase): at least one phase per spec use-case, no phase implements two use-cases. If bundling was chosen: grouped phases stay MR-sized, one concern, independently mergeable. Cross-cutting scaffolding is its own foundation phase either way.
|
|
427
|
-
- [ ] Each phase has Goal / Spec use-case / Feature flag (or explicit waiver) / Changes / Tests /
|
|
428
|
-
- [ ] `**
|
|
997
|
+
- [ ] Each phase has Goal / **Depends on** / Spec use-case / Feature flag (or explicit waiver) / Changes / Tests / **Assigned to** / Reusable skills / Acceptance.
|
|
998
|
+
- [ ] Every `**Depends on**:` entry names the artifact it needs (model, symbol, migration, endpoint) — no bare phase ids, no "comes first" edges.
|
|
999
|
+
- [ ] **Execution graph** table is the first thing under **Phased Rollout**, and its waves match what the `**Depends on**:` lines imply.
|
|
1000
|
+
- [ ] Graph is acyclic; the flag-removal phase depends on every gated phase.
|
|
1001
|
+
- [ ] Same-wave phases checked against the **Touch List** for file overlap; real overlaps either serialized with an edge or called out explicitly under the graph table.
|
|
1002
|
+
- [ ] Post-mortems from previous runs (`.vinta-ai-maestro/runs/*/postmortem.json`, plus any committed beside a plan) read **before** the graph was drawn; every finding either changed an edge, a wave or a split, or was consciously dismissed as not applying to this feature.
|
|
1003
|
+
- [ ] Slow-moving / cross-repo work sits in wave 1, and no in-repo phase depends on a cross-repo phase when it only needs the contract.
|
|
1004
|
+
- [ ] **Crew** table is the first thing under **Phased Rollout**: one row per agent with its **Role**, every implementer taking at least one phase, every `Takes` cell agreeing with that phase's `**Assigned to**:` line.
|
|
1005
|
+
- [ ] **At least one reviewer on the roster**, at or above the plan's hardest phase tier. Implementers and reviewers are disjoint — no phase is assigned to a reviewer.
|
|
1006
|
+
- [ ] **Every member earns their place** — by concurrency (a wave genuinely needs that many hands *at or above* those phases' tiers) or by cheapness (they take work a dearer member would otherwise do). Check it wave by wave: sort the wave's phase tiers against the roster's and confirm the roster covers them one for one.
|
|
1007
|
+
- [ ] Every phase is assigned to a member whose tier is **at or above** the rubric tier its work implies — no phase handed down a tier to save money.
|
|
1008
|
+
- [ ] **Execution graph** carries the Agent column, and an **Idle** note naming who has nothing to do in which wave. A wave that serializes because the roster is smaller than it is called out rather than left to be discovered at run time.
|
|
1009
|
+
- [ ] `**Review models**:` appears **only** on phases needing a review above the tier-above-the-author default (not on every phase); each such line names a tier + why.
|
|
429
1010
|
<!-- e2e:start -->
|
|
430
1011
|
- [ ] **If e2e coverage was opted into at Step 0:** every phase introducing a new UI flow has an **E2E happy-path test** in its Tests block, with screenshot output to `pr-screenshots/`. If it was not opted into (default), **no phase carries an e2e spec** and there is no `QA_USE_CASES.md` / `pr-screenshots/` reference.
|
|
431
1012
|
<!-- e2e:end -->
|
|
@@ -437,4 +1018,13 @@ When in doubt, model the plan after a recent example in `ai-plans/` — look for
|
|
|
437
1018
|
- [ ] Risk & Rollout Notes covers locks, partitions, backfills, rollback.
|
|
438
1019
|
- [ ] Open Questions lists what couldn't resolve, with recommended default.
|
|
439
1020
|
- [ ] Touch List groups files by phase.
|
|
440
|
-
- [ ] All file references use `@path/to/file.py` or `[name](relative-path#Lline)`.
|
|
1021
|
+
- [ ] All file references use `@path/to/file.py` or `[name](relative-path#Lline)`.
|
|
1022
|
+
- [ ] **`ai-plans/<feature-kebab>.workflow.json` written** — every plan, no exceptions — with `$schema` set to the canonical URL and `schema_version: 1`.
|
|
1023
|
+
- [ ] Workflow graph matches the **Execution graph** table: one node per phase, one `depends_on` entry per `**Depends on**:` clause carrying both the node id and the artifact, same waves.
|
|
1024
|
+
- [ ] Every node has `prompt_ref` (`<plan_ref>#phase-<number>`), `touches` from its **Touch List** block, and the `gates` it must pass.
|
|
1025
|
+
- [ ] **`plan_context_refs` names the Goals and Guiding Decisions anchors** (`<plan_ref>#1-goals`, `<plan_ref>#2-guiding-decisions`), matching the headings as written — references, never a summary of them. Every phase's implementer and reviewer read them; a phase that doesn't know the non-goals is a phase that scope-creeps.
|
|
1026
|
+
- [ ] `resources` declares a `lane` pool at the **number of implementers** — one worktree each, kept for the whole run; reviewers have none and read the lane under review. Every gate that contends for something shared names its pool in `requires`.
|
|
1027
|
+
- [ ] **`project` decided, not defaulted** — asked via `AskUserQuestion`, then either written (roles `dev` / `test`, each naming its own database, engine fields filled from the project, `migrate_cmd` read out of its task runner) or deliberately omitted because lanes share the main checkout's database. No `reset_cmd`, no compose project name, no seed command, no env-file strategy — those are the worktree's, not the plan's.
|
|
1028
|
+
- [ ] No credential anywhere in the workflow file: `connection_url_var` is a variable name, and `server_url` is a host and port.
|
|
1029
|
+
- [ ] Model ids come from [resources/ai-models.yaml](resources/ai-models.yaml) and appear **only** in the `crew` block — no node carries a `model`, and `crew` transcribes the Crew table row for row.
|
|
1030
|
+
- [ ] `pipelines` is omitted — `defaults.pipeline: standard-phase` is enough, and the executor supplies it.
|