vinta-ai-workflows 0.7.0-alpha33 → 0.7.0-alpha35

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 CHANGED
@@ -7,6 +7,8 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## [0.7.0] — YYYY-MM-DD
9
9
 
10
+ <!-- pre-release: 0.7.0-alpha35 on 2026-10-16 -->
11
+ <!-- pre-release: 0.7.0-alpha34 on 2026-10-16 -->
10
12
  <!-- pre-release: 0.7.0-alpha33 on 2026-10-16 -->
11
13
  <!-- pre-release: 0.7.0-alpha31 on 2026-10-09 -->
12
14
  <!-- pre-release: 0.7.0-alpha30 on 2026-10-09 -->
@@ -33,6 +35,54 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
33
35
 
34
36
  ### Added
35
37
 
38
+ - **`vinta-ai-maestro`: integration is visible while it happens.** A phase in
39
+ `integrate` — a merge, a suite on the merged tree, a push, none with an
40
+ agent — showed `running` over a silent transcript, and one queued behind it
41
+ looked the same. Now:
42
+ - The phase view has an **Integration** timeline: each merge, conflict fix
43
+ round, `verify`, wave gate, push and PR of this attempt, a live clock on
44
+ the one in progress, and the log of every step that has one.
45
+ - The run view has an **Integration worktree** panel: who holds it, the step
46
+ it is on and for how long, and who is queued behind it. The roster and
47
+ the phase header show each running phase's pipeline step.
48
+ - `status` prints the step beside each running phase and an `integration`
49
+ line (`a · wave 1 gate unit · 6m 13s · 1 queued (b)`); `status --json`
50
+ carries `nodes[].step` and `integration`.
51
+ - A conflict fixer's turn is also written to the transcript of the phase
52
+ being based or building the wave, not only to the incoming phase's.
53
+
54
+ Two additive journal events back it: `node_step` (the pipeline state an
55
+ attempt entered) and `integration_step` (a step started or finished in the
56
+ worktree, identifiers and a log path only). Older runs have neither and
57
+ show no timeline.
58
+ - **`vinta-ai-maestro`: `accept` on an unapproved review.** The review-budget
59
+ question offered `continue` or `stop`, and operators answered `stop` meaning
60
+ "take it as it is" — which fails the attempt and, under `--on-failure
61
+ retry`, starts the phase over. It now offers `continue`, `accept` and `stop`
62
+ and says what each does. `accept` ends the review loop and keeps the work:
63
+ the polish chores, the gates and integration still run, and the answer is
64
+ journalled. The unattended answer is unchanged (`continue` once, then
65
+ `stop`); the coordinator cannot answer `accept`.
66
+ - **`vinta-ai-maestro validate` warns about parallel phases adding migrations
67
+ to one app**, and `plan-feature` treats them as real overlap. Two phases
68
+ that neither depends on the other, each adding a migration under the same
69
+ directory (Django `<app>/migrations/`, Alembic `…/versions/`, Rails
70
+ `db/migrate/`, Prisma `prisma/migrations/`), start from the same latest
71
+ migration; merged, the graph has two leaves and every gate on the merged
72
+ tree fails. Their Touch Lists name different files, so nothing flagged it.
73
+ `validate --json` gains a `warnings` list, printed as `warning:` lines in
74
+ text mode, and the review page shows them in their own panel; neither
75
+ changes the exit code or `valid`. `plan-feature` now says to add the edge,
76
+ or to name the phase whose integration writes the merge migration.
77
+ - **`vinta-ai-maestro node resume <run> <phase>`**, the undo of `node pause`:
78
+ it withdraws a pause not yet taken, releases a phase parked on the pause, or
79
+ gives a held phase its `--retry-after` timer back. It used to take
80
+ answering an undocumented "The operator paused this node. Resume it?"
81
+ question, or answering a held phase's question by hand.
82
+ - **`vinta-ai-maestro status` shows the question each phase is asking**, with
83
+ its choices, and `status --json` carries it per node as `question`
84
+ (`effectId`, `question`, `kind`, `choices`, `askedAt`). `asking: true` alone
85
+ sent operators into `flow.db` to read it.
36
86
  - **`vinta-ai-maestro`: ways past a stale gate verdict.** `gate <id>
37
87
  --no-cache` runs a gate past its cached result and keeps the new one in its
38
88
  place; a cached red now says so. `gate-cache invalidate <run-id> <gate-id>
@@ -609,6 +659,30 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
609
659
 
610
660
  ### Changed
611
661
 
662
+ - **`vinta-ai-maestro`: the full suite runs before a PR, and on every merged
663
+ tree.** Under `gate_scope: scoped`, a phase's gates ran their scoped form
664
+ everywhere, so a PR could carry a suite only a scoped run had seen, and most
665
+ phases were `done` before any full suite touched their code.
666
+ - A phase's gates run scoped until its review approves; `verify`, after the
667
+ review, runs every gate's full `cmd` on the phase branch. A red full run
668
+ goes back to a fixer and through the review again — more review rounds,
669
+ no failing suite in a PR. The fixer is told the gate failed in full, and
670
+ `vinta-ai-maestro gate <id> --full` runs the whole command for an agent.
671
+ - `defaults.wave_gates` now defaults to `every`: each wave's gates run in
672
+ full on the merged tree, so the next wave builds on a passing one. Set
673
+ `final` for the previous behaviour.
674
+ - A multi-dependency phase's `integ-<id>` base runs its dependencies' gates
675
+ in full before the phase starts, cached on the merged tree; red fails the
676
+ phase's setup with `BaseGateError`. A clean base merge used to run nothing.
677
+ - Wave and base gate failures now reach the journal with their gate and log,
678
+ not just their error name.
679
+ - Each full run can be skipped in a plan's `defaults` or the project's
680
+ `maestro.defaults` (`.vinta-ai-workflows.yaml`): `verify_gates: scoped`
681
+ keeps `verify` on the scoped form, `base_gates: off` leaves merged bases
682
+ unchecked, and `wave_gates: final` / `off` as before. Both new fields are
683
+ in `vinta-ai-workflows-config.v1.schema.json`, and the bootstrap asks when
684
+ the full suite should run. The run coordinator may tighten them, never
685
+ loosen them.
612
686
  - **`vinta-ai-maestro`: a recycled lane no longer keeps the last phase's
613
687
  ignored files.** A lane handed to the next phase kept everything gitignored,
614
688
  and one kept `apps/api/.mypy_cache` and a deleted package's `__pycache__`,
@@ -957,6 +1031,51 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
957
1031
 
958
1032
  ### Fixed
959
1033
 
1034
+ - **`vinta-ai-maestro`: a claude-code turn the CLI calls "blocked" no longer
1035
+ fails unless a tool was refused in it.** The CLI's own turn summary marks a
1036
+ turn `blocked` whenever its last message names a next step for a person —
1037
+ a review ending "Nothing was pushed." over `VERDICT: pass`, an implementer
1038
+ listing an open point — and each such turn was recorded as failed
1039
+ ("claude-code: the turn ended blocked"), an approving review included. One
1040
+ observed phase had 43 of them. The summary now fails a turn only alongside a
1041
+ `permission_denied` in the same turn, the case it was added for; whether
1042
+ the rest did their job is for verdicts, gates and the agent's questions. A
1043
+ turn's errors no longer outlive it in a session held open for background
1044
+ work.
1045
+ - **`vinta-ai-maestro`: a merge git stopped with nothing unmerged no longer
1046
+ starts a fixer.** `rerere.autoupdate` replaying a recorded resolution, or a
1047
+ `pre-merge-commit` / `commit-msg` hook refusing the merged code, leaves
1048
+ `git merge` non-zero with no unmerged path, and the integrator handed the
1049
+ conflict fixer an empty file list — nineteen rounds in one observed phase,
1050
+ each told to resolve nothing. With no conflict markers left, the merge now
1051
+ goes to `verify` and is committed, recorded as resolved in 0 rounds; a hook
1052
+ that refuses that commit gets the refused-commit question (commit, commit
1053
+ past the hooks, or abort), worded for a clean merge. Only a red gate sends it
1054
+ to the fixer. A merge git refused before merging anything (an untracked file
1055
+ in the way) fails with git's own error.
1056
+ - **`vinta-ai-maestro`: a merge finished by hand survives the next attempt.**
1057
+ A conflict the fixer could not settle fails the phase with "finish the merge
1058
+ by hand … and commit it", and the next attempt's `mergeWave` cut
1059
+ `wave-<N>` again from `wave-<N-1>` and re-merged every member, throwing the
1060
+ resolution away and hitting the same conflict on every attempt. A wave or
1061
+ `integ-<id>` branch that still contains its starting point, and whose merges
1062
+ all brought in work still in the members' current tips, is now kept, and only
1063
+ the members it lacks are merged. A member redone after it was merged still
1064
+ forces a rebuild. The error now says the branch is kept.
1065
+ - **`vinta-ai-maestro`: a merged tree that fails its gates is no longer
1066
+ reported as an unresolved conflict with no paths.** Two sibling phases that
1067
+ each added a Django migration on the same parent merged cleanly and failed
1068
+ `verify` together, and the phase failed "unresolved conflict: p4a both
1069
+ changed , …". Each fix round is now told which gate the merged tree failed
1070
+ and where its log is, and the rounds end in `IntegrationVerifyError`, naming
1071
+ the gate and its log path (`nodes/_integration/gates/<gate>.log`).
1072
+ - **`vinta-ai-maestro`: `exec` accepts a lane's short name or its phase, and
1073
+ `with` / `gate` work inside it.** `exec <run> crew-3-tier2-2` and
1074
+ `exec <run> p1` were refused `unknown_target`; only the full
1075
+ `<run-id>-crew-3-tier2-2` worked. Both now resolve, and a refusal lists the
1076
+ run's targets. Inside `exec`, `with test-suite -- make test` said "no live
1077
+ run is available" while the run was live: the command now holds leases as the
1078
+ phase running in the lane, or as itself on an idle one, until it exits.
960
1079
  - **`vinta-ai-maestro`: a fix loop that changes nothing now stops and asks.**
961
1080
  A typecheck red because of ignored files left in a lane kept coming back
962
1081
  from the gate cache after they were gone, and twenty fixers in a row found
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vinta-ai-workflows",
3
- "version": "0.7.0-alpha33",
3
+ "version": "0.7.0-alpha35",
4
4
  "description": "Bootstrap AI tooling (AGENTS.md, skills, sub-agents) across Claude Code, Codex, Cursor, and VS Code + GitHub Copilot.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -339,7 +339,7 @@
339
339
  "additionalProperties": false,
340
340
  "properties": {
341
341
  "defaults": {
342
- "description": "Run defaults a plan's `defaults` overrides field by field: `harness`, `model`, `pipeline`, `chores`, `gates` (the gate ids every phase runs unless it names its own), `gate_scope` (`scoped` | `full`), `wave_gates` (`off` | `final` | `every`), `substitution` (`same_tier` | `up_one_tier` | `any`), `allow_ungated_phases`, `model_fallbacks`, `max_session_turns`, `max_spare_crew`, `pull_requests` (`phase` | `plan`).",
342
+ "description": "Run defaults a plan's `defaults` overrides field by field: `harness`, `model`, `pipeline`, `chores`, `gates` (the gate ids every phase runs unless it names its own), `gate_scope` (`scoped` | `full`), `verify_gates` (`full` | `scoped`), `base_gates` (`full` | `off`), `wave_gates` (`off` | `final` | `every`), `substitution` (`same_tier` | `up_one_tier` | `any`), `allow_ungated_phases`, `model_fallbacks`, `max_session_turns`, `max_spare_crew`, `pull_requests` (`phase` | `plan`).",
343
343
  "type": "object",
344
344
  "properties": {
345
345
  "harness": {
@@ -379,6 +379,22 @@
379
379
  "full"
380
380
  ]
381
381
  },
382
+ "verify_gates": {
383
+ "type": "string",
384
+ "enum": [
385
+ "full",
386
+ "scoped"
387
+ ],
388
+ "description": "What a phase's `verify` runs once its review approves. `full` (the default): every gate's whole command on the phase branch, so a PR never carries a suite only a scoped run saw. `scoped`: the scoped form there too, for a suite too slow to run once per phase; the merged-tree gates are then the first full run. No effect under `gate_scope: full`."
389
+ },
390
+ "base_gates": {
391
+ "type": "string",
392
+ "enum": [
393
+ "full",
394
+ "off"
395
+ ],
396
+ "description": "Whether a multi-dependency phase's merged base is gated before the phase starts on it. `full` (the default): every command gate its dependencies declare, whole command, on the merged base; red fails the phase's setup. `off`: the phase starts on the merged base unchecked."
397
+ },
382
398
  "substitution": {
383
399
  "type": "string",
384
400
  "enum": [
@@ -395,7 +411,7 @@
395
411
  "final",
396
412
  "every"
397
413
  ],
398
- "description": "When the phases' gates run in full on a merged wave, in the integration worktree. `final` (the default): every gate of the plan on the last wave before the plan PR, and a wave's gates on any wave whose merge needed a conflict resolution. `every`: on every wave as well. `off`: none of these (the full form of a `scoped_cmd` gate still runs per wave under `gate_scope: scoped`)."
414
+ "description": "When the phases' gates run in full on a merged wave, in the integration worktree. `every` (the default): a wave's gates on every wave, so the next wave builds on a passing tree, and every gate of the plan on the last wave before the plan PR. `final`: the last wave's, and a wave's gates only on a wave whose merge needed a conflict resolution. `off`: none of these (the full form of a `scoped_cmd` gate still runs per wave under `gate_scope: scoped`)."
399
415
  },
400
416
  "allow_ungated_phases": {
401
417
  "type": "boolean",
@@ -420,7 +420,7 @@
420
420
  },
421
421
  "gate_scope": {
422
422
  "default": "scoped",
423
- "description": "`scoped` — the default — runs a gate’s `scoped_cmd` on phase gates and its full `cmd` on the gate that re-runs after a wave merge. `full` runs `cmd` everywhere.",
423
+ "description": "`scoped` — the default — runs a gate’s `scoped_cmd` on a phase’s gates up to and during its review, and its full `cmd` once the review approves (`verify`) and on every merged tree in the integration worktree. `full` runs `cmd` everywhere.",
424
424
  "type": "string",
425
425
  "enum": [
426
426
  "scoped",
@@ -453,9 +453,27 @@
453
453
  "minimum": 0,
454
454
  "maximum": 9007199254740991
455
455
  },
456
+ "verify_gates": {
457
+ "default": "full",
458
+ "description": "What `standard-phase`’s `verify` runs once the review approves. `full` — the default — runs every gate’s whole `cmd` on the phase branch, so a PR never carries a suite only a scoped run saw; a red full run goes back to a fixer and the review. `scoped` keeps the scoped form there too, for a suite too slow to run once per phase — the merged-tree gates are then the first full run. No effect under `gate_scope: full`.",
459
+ "type": "string",
460
+ "enum": [
461
+ "full",
462
+ "scoped"
463
+ ]
464
+ },
465
+ "base_gates": {
466
+ "default": "full",
467
+ "description": "Whether a multi-dependency phase’s base (`integ-<id>`, its dependencies merged) is gated before the phase starts on it. `full` — the default — runs every command gate its dependencies declare, whole `cmd`, on the merged base, cached on its tree; red fails the phase’s setup. `off` starts the phase on the merged base unchecked.",
468
+ "type": "string",
469
+ "enum": [
470
+ "full",
471
+ "off"
472
+ ]
473
+ },
456
474
  "wave_gates": {
457
- "default": "final",
458
- "description": "When the phases’ gates run in full on a merged wave, in the integration worktree. Each phase passes its gates on its own branch, and two parallel phases can each be green and disagree with each other — a sender and a receiver of one signal, say — which only a merged tree shows. `final` — the default — runs every gate of the plan once on the last wave before the plan PR opens, and the wave’s phases’ gates on any wave whose merge needed a conflict resolution. `every` runs the wave’s phases’ gates on every wave as well, which costs one full suite per wave and catches a disagreement at the wave that introduced it. `off` runs none of these. Under `gate_scope: scoped` the full form of a narrowed gate still runs on each wave whatever this says: that is where the suite the phases skipped is paid back.",
475
+ "default": "every",
476
+ "description": "When the phases’ gates run in full on a merged wave, in the integration worktree. Each phase passes its gates on its own branch, and two parallel phases can each be green and disagree with each other — a sender and a receiver of one signal, say — which only a merged tree shows. `every` — the default — runs the wave’s phases’ gates on every wave, so the next wave builds on a passing tree and a disagreement is caught at the wave that introduced it, and every gate of the plan once on the last wave before the plan PR opens. It costs one full suite per wave. `final` runs the last wave’s and any wave whose merge needed a conflict resolution only. `off` runs none of these. Under `gate_scope: scoped` the full form of a narrowed gate still runs on each wave whatever this says: that is where the suite the phases skipped is paid back.",
459
477
  "type": "string",
460
478
  "enum": [
461
479
  "off",
@@ -101,7 +101,8 @@ These bleed across sub-skills, so capture once now:
101
101
  On `Set project defaults`, capture:
102
102
  - **Gates every phase runs.** Multi-select `AskUserQuestion` over the types the commands above give a command for: `typecheck` (from `commands.build`), `lint`, `test` (from `commands.test_unit`), and `e2e` only when `add-e2e-test` is enabled. → `maestro.defaults.gates`.
103
103
  - **A different command for maestro.** `AskUserQuestion` per selected type: `Same as commands.* (Recommended)`, `Different for maestro (I'll give it)`. Teams pick a different one when lanes need a flag the inner loop does not, e.g. `pytest --reuse-db -n auto`. → `maestro.gates.<type>.cmd`. `implement-plan` keeps running `commands.*`.
104
- - **Scoped gates.** Only when `test` (or `lint`) is selected: should a phase's gate run narrowed to the files the phase changed, with the full command run once per merged wave? `AskUserQuestion`: `Yes — scoped per phase, full per wave (Recommended)`, `No — full command every phase`. On `Yes`, propose a template with `{changed_files}` for the detected runner (`vitest related {changed_files} --run`, `jest --findRelatedTests {changed_files}`, `pytest --testmon {changed_files}`, `ruff check {changed_files}`) and confirm it. It lands in `commands.test_unit_scoped` / `commands.lint_scoped`, where `implement-plan` reads it too. `No` sets `maestro.defaults.gate_scope: full`.
104
+ - **Scoped gates.** Only when `test` (or `lint`) is selected: should a phase's gate run narrowed to the files the phase changed until its review approves, with the full command run after the review and on every merged tree? `AskUserQuestion`: `Yes — scoped until review, full after (Recommended)`, `No — full command every time`. On `Yes`, propose a template with `{changed_files}` for the detected runner (`vitest related {changed_files} --run`, `jest --findRelatedTests {changed_files}`, `pytest --testmon {changed_files}`, `ruff check {changed_files}`) and confirm it. It lands in `commands.test_unit_scoped` / `commands.lint_scoped`, where `implement-plan` reads it too. `No` sets `maestro.defaults.gate_scope: full`.
105
+ - **When the full suite runs.** Only on `Yes` above. `AskUserQuestion`: `After each review and on every merged tree (Recommended)`, `Only on merged trees`, `Only on merged waves`. The first is the default and emits nothing. `Only on merged trees` skips the per-phase full run after review (`maestro.defaults.verify_gates: scoped`), for a suite too slow to run once per phase. `Only on merged waves` also leaves multi-dependency bases unchecked (`verify_gates: scoped` and `base_gates: off`).
105
106
  - **A test-suite pool.** `AskUserQuestion`: `One suite at a time (Recommended for DB-backed suites)`, `Two at a time`, `No limit`. A limit emits `maestro.resources.test-suite` (`kind: semaphore`, `capacity` 1 or 2) and `maestro.gates.test.requires: [test-suite]`. Then offer the runner's command patterns as the pool's `match` (`pytest*`, `uv run pytest*`, `pnpm test*`), which makes agents take the lease before running tests by hand.
106
107
  - **Files git does not carry.** A maestro lane is a worktree and has only what git tracks. Read `.gitignore` for entries with a tracked `*.example` / `*.sample` / `*.template` sibling, the compose file's `env_file:` lines, and the gitignored settings module the framework imports (`DJANGO_SETTINGS_MODULE`'s local module, typically). Propose the list in a multi-select `AskUserQuestion` (header `Env files`), each path pre-selected; the user confirms or drops entries. → `maestro.project.env_files`. Every hook, gate and `manage.py` call in a lane depends on these, and a plan that omits them fails in ways that look like code problems. Nothing found → omit the key and say so.
107
108
 
@@ -366,7 +367,9 @@ run_options:
366
367
  maestro:
367
368
  defaults:
368
369
  gates: <C.11 → gates every phase runs, e.g. [typecheck, lint, test]>
369
- gate_scope: <C.11 → omit for "scoped" (the default); `full` on "No — full command every phase">
370
+ gate_scope: <C.11 → omit for "scoped" (the default); `full` on "No — full command every time">
371
+ verify_gates: <C.11 → omit (the default, `full`); `scoped` on "Only on merged trees" or "Only on merged waves">
372
+ base_gates: <C.11 → omit (the default, `full`); `off` on "Only on merged waves">
370
373
  gates:
371
374
  test:
372
375
  cmd: <C.11 → maestro-only test command; omit to use commands.test_unit>
@@ -254,6 +254,7 @@ Before publishing the plan, cross-check the **Touch List**: for every pair of ph
254
254
 
255
255
  - **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.
256
256
  - **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.
257
+ - **Migrations in one app count as real overlap, even in different files.** Two phases that run in parallel and each add a migration to the same app both start from the same latest migration. Each is green alone; merged, the migration graph has two leaves (Django) or two heads (Alembic), and every gate on the merged tree fails. Their Touch Lists name two different new files, so path overlap never shows it. Prefer the edge — usually by folding both schema changes into the foundation phase — or, when the two must stay parallel, say in the later phase's body that the integration step writes the merge migration. `vinta-ai-maestro validate` warns about such a pair; put the migrations directory in each phase's Touch List so it can.
257
258
 
258
259
  ### Design *for* parallelism when it's cheap
259
260
 
@@ -607,7 +608,8 @@ First key in the file is `"$schema"`, pointing at `https://github.com/vintasoftw
607
608
  | `chores.review-canvas` | **Emit only when `.vinta-ai-workflows.yaml` has `integrations.pr-review-canvas: enabled`.** An `after_pr` chore: maestro runs it once each phase's PR is open, and it posts a review canvas on that PR. Emit it exactly as shown under "The review-canvas chore" below. Never emit it when the integration is `disabled` or absent. The skill it names is installed only when the integration is enabled. |
608
609
  | `defaults.chores` | `["review", "deslop"]`, plus `"review-canvas"` when that chore is emitted — or **omit the key** when `maestro.defaults.chores` already names the same list. The chores every phase runs unless it names its own. **Never write `"chores": []` to mean "the project's"**: an empty list *replaces* the project's list with nothing, and the run then runs no chore at all. |
609
610
  | `defaults.substitution` | **Omit** — it defaults to `up_one_tier`: a busy member's phase may go to the next tier up the roster has, and a warm session never moves a phase up. Set `same_tier` when the top tier is a limited credit allowance that must not absorb other tiers' work. |
610
- | `defaults.wave_gates` | **Omit** — it defaults to `final`: every gate of the plan runs once on the merged tree before the plan PR opens, plus a wave's gates on any wave whose merge needed a conflict resolution. Set `every` for a plan with many parallel phases on shared interfaces, where finding a cross-phase break at the wave that introduced it is worth one full suite per wave. Never `off` unless the project's suite cannot run in the integration worktree. |
611
+ | `defaults.wave_gates` | **Omit** — it defaults to `every`: each wave's gates run in full on the merged tree, so the next wave builds on a passing one, and every gate of the plan once more before the plan PR opens. Set `final` only for a plan whose suite is too slow to run once per wave. Never `off` unless the project's suite cannot run in the integration worktree. |
612
+ | `defaults.verify_gates` / `defaults.base_gates` | **Omit** — both default to `full`: a phase runs its gates' full commands once its review approves, and a multi-dependency phase's merged base is gated before it starts. Set `verify_gates: scoped` or `base_gates: off` only when the human says the suite is too slow for it, and say so in the plan. |
611
613
  | `defaults.gates` | The gate ids every phase runs. **Omit the key** when `maestro.defaults.gates` is already the list this plan wants; set it when most phases share a list the project does not name. A node with no `gates` key takes it. **Never `[]`**: that is a run with no gates. |
612
614
  | `nodes[].chores` | **Omit the key** on almost every phase — absent means the run-wide default. Name a list only to give one phase a different set, and `[]` only to deliberately opt one phase out. A list *replaces* the default rather than adding to it; `[]` is "no chores", never "the default". |
613
615
  | `nodes[]` | One per phase, in plan order. |
@@ -640,7 +642,7 @@ Rules the mapping depends on:
640
642
  Read `.vinta-ai-workflows.yaml` before writing the document. Its `commands` and its `maestro:` section are laid **under** this file when maestro loads it: every value the project already supplies reaches this plan without being written here, and a value written here overrides it for this plan alone. So the document says what is particular to this plan and nothing else. A copy of a project value is not harmless — it freezes the value into the plan, and a later change to the project's file never reaches it.
641
643
 
642
644
  - **Gates by type.** A gate typed `test`, `lint`, `typecheck` or `e2e` takes its command from the project — `maestro.gates.<type>`, else `commands.test_unit` / `lint` / `build` / `e2e`. Each type the project has a command for is available under the type's own name, so a node can list `"gates": ["typecheck", "test"]` with no `gates` table at all. Write an entry only to change something for this plan: `{ "type": "test", "timeout_s": 3600 }` keeps the project's command and extends its timeout; `{ "cmd": "…" }` (no type) replaces it outright.
643
- - **`scoped_cmd`** is the same check narrowed to what a phase changed, with `{changed_files}` / `{touches}` in it. The project normally supplies it (`commands.test_unit_scoped`, `maestro.gates.<type>.scoped_cmd`). Phase gates run it, and the full command runs once on each merged wave. Set one here only for a plan whose phases need a different narrowing.
645
+ - **`scoped_cmd`** is the same check narrowed to what a phase changed, with `{changed_files}` / `{touches}` in it. The project normally supplies it (`commands.test_unit_scoped`, `maestro.gates.<type>.scoped_cmd`). A phase's gates run it until its review approves; the full command runs after that, on the phase branch, and on every merged tree — each wave and each multi-dependency base. Set one here only for a plan whose phases need a different narrowing.
644
646
  - **Pools, chores and the `project` block** merge per id: an entry here replaces the project's entry of the same id whole. Declare `resources.lane` (it is this plan's — one desk per implementer), and a pool, chore or database only when the project has none or this plan needs a different one.
645
647
  - **`defaults`** merge field by field: omit what `maestro.defaults` already says.
646
648
  - **Omitting a key inherits; writing `[]` replaces with nothing.** `nodes[].gates`, `defaults.gates`, `defaults.chores` and `nodes[].chores` are lists, and lists replace rather than merge. The only way to take the project's list is to leave the key out. `"gates": []` on a node is a phase with no gates, `"chores": []` in `defaults` is a run with no review and no comment pass — both silent at run time, and both exactly what the observed run did. When in doubt, omit the key and read what `validate --json` resolved.
@@ -1071,6 +1073,8 @@ Fix each issue by its `source`:
1071
1073
  - **`workflow`**: the document's shape or graph, such as an unknown node, a cycle, an undeclared pool, a crew member that cannot take its phase, a gate entry with neither `cmd` nor `type`, a phase with no gates, or an id that does not match the filename. Re-derive the field from the plan. A gate `"has no cmd"` is an override that forgot its `"type"`. A phase that `"has no gates"` is a node carrying `"gates": []` — remove the key so it takes `defaults.gates`, name its gates, or, for a plan whose phases genuinely have nothing to run, set `defaults.allow_ungated_phases: true`. If the plan itself is wrong (a cycle in its `**Depends on**:` lines), fix the plan first.
1072
1074
  - **`config`**: `.vinta-ai-workflows.yaml` does not parse. That file is the project's, not the plan's. Do not edit it to make the plan pass. Tell the human what is wrong and where.
1073
1075
 
1076
+ **`warnings` do not change the exit code, and each still gets an answer.** Today the one warning is two phases that run in parallel and both add migrations to the same app (see "Same-wave phases must not fight over the same files"). Add the dependency edge, or keep the pair parallel and say in the later phase's body that the merge migration is written at integration. Either way, name the choice when you hand the plan over. An older maestro prints no `warnings` key.
1077
+
1074
1078
  ## Review the plan with the human
1075
1079
 
1076
1080
  A plan is cheapest to change before anything runs. Once both files are written and the workflow validates, hand the plan over for review. Ask with `AskUserQuestion`: header `Review`, question "The plan and its workflow are written — `<plan path>` and `<workflow path>`. How do you want to review it?"