vinta-ai-workflows 0.7.0-alpha36 → 0.7.0-alpha38

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-alpha38 on 2026-10-22 -->
11
+ <!-- pre-release: 0.7.0-alpha37 on 2026-10-20 -->
10
12
  <!-- pre-release: 0.7.0-alpha36 on 2026-10-18 -->
11
13
  <!-- pre-release: 0.7.0-alpha35 on 2026-10-16 -->
12
14
  <!-- pre-release: 0.7.0-alpha34 on 2026-10-16 -->
@@ -36,6 +38,83 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
36
38
 
37
39
  ### Added
38
40
 
41
+ - **`vinta-ai-maestro` UI: run analytics and the end-of-run report.** A new
42
+ `#/runs/<run>/analytics` page shows where the run's time is going while
43
+ it runs — working time by pipeline step (implementation, gates, review,
44
+ polish, integration), filtered by phase, wave, harness, crew member or
45
+ model, a breakdown grouped by any of those, what each gate and each chore
46
+ cost, and the critical path — with open activities timed live. Once the
47
+ run has ended the same page leads with the post-mortem drawn as a report
48
+ with a short diagnosis. The run view carries a "Where the time goes"
49
+ summary and a header button that reads Analytics while the run goes and
50
+ Report after. Two new reads back them: `GET /api/runs/:id/analytics` and
51
+ `GET /api/runs/:id/postmortem`.
52
+ - **`vinta-ai-maestro` UI: the integration worktree's record.** The run
53
+ view's worktree panel lists every step every phase took there, newest
54
+ first, each linked to its phase (`GET /api/runs/:id/integration`).
55
+ - **`vinta-ai-maestro` UI: the coordinator is a sidebar.** The run
56
+ coordinator's conversation moved out of the run view's grid into a
57
+ full-height sidebar on the right — from under the top bar to the bottom of
58
+ the window on a wide screen, a non-modal sheet otherwise, and on the phase
59
+ page too — opened from a header button and remembered across visits. The
60
+ conversation fills it and the question box sits at the bottom.
61
+ - **`vinta-ai-maestro` UI: every page fits a phone.** The top bar keeps to
62
+ one row (the mark alone stands for the brand below the `sm` breakpoint),
63
+ every two-column layout stacks, and every table scrolls inside its card
64
+ instead of widening the page.
65
+ - **`vinta-ai-maestro` UI: the run view's panels are a tiled grid.** Every
66
+ panel is one or two row units tall, fills its cell and scrolls inside it,
67
+ and short panels pack into the holes tall ones leave — no more band of
68
+ empty page under a short card beside a long one. The roster, "Decided
69
+ without you", the integration record and the phase page's integration
70
+ timeline show their newest five rows (newest first) with the whole record
71
+ in a paged dialog; the resource pools show the two in use (fullest first)
72
+ with the rest in a dialog. Selecting a node on the canvas scrolls its
73
+ roster row into view.
74
+ - **`vinta-dag-editor`: phase ids on the cards.** Each node card carries its
75
+ id (`p1`, `p2`, …) above its name, so the canvas names phases the way the
76
+ roster, the transcript and the plan do.
77
+ - **`vinta-dag-editor`: tooltips stay inside the canvas.** A tooltip is kept
78
+ whole within the viewport — pushed in from the edges and flipped above its
79
+ source when there is no room below — so the toolbar's and the edge cards'
80
+ tooltips no longer open into the clipped strip outside the graph.
81
+ - **`vinta-ai-maestro` UI: the graph goes full screen.** A button on the
82
+ canvas (run view, replay and plan review) takes it over the whole window
83
+ and frames the graph again for the larger box; Escape or the same button
84
+ brings it back.
85
+ - **`vinta-ai-maestro`: integration gate logs are live.** The integrator
86
+ records one `verify` step per gate, with the log's path, when the gate
87
+ starts rather than when the whole suite ends — so the phase page tails a
88
+ merged-tree gate's output while it runs. The step's wording is now
89
+ "gate `<id>` on the merged tree", with the outcome mark saying whether it
90
+ went red.
91
+ - **`vinta-ai-maestro`: `post_merge`.** A workflow (or `maestro.post_merge`)
92
+ may declare a command that runs in the integration worktree after git
93
+ merges a branch by itself, with `{files}` set to the files both sides
94
+ changed (for example `py_compile` plus `ruff check --fix` and
95
+ `ruff format`). Its changes are committed on top of the merge. If it fails,
96
+ the merge stays as git made it and the merged tree's gates judge it. Each
97
+ run is journalled as `post_merge_result`.
98
+ - **`vinta-ai-maestro`: `ci_only` records CI checks deliberately left to
99
+ CI.** It lives in a plan or in `maestro.ci_only` in
100
+ `.vinta-ai-workflows.yaml`. Each entry is a CI command with the reason no
101
+ gate runs it. `doctor`'s CI-coverage check stops warning about those
102
+ commands and counts them as decisions.
103
+ - **`vinta-ai-maestro`: `queue_timeout_s`, and the pool queue in `status`.**
104
+ A gate's `timeout_s` counts only its command's runtime, and a regression
105
+ test now pins that down. The optional `queue_timeout_s` bounds the wait
106
+ for the gate's pools: a gate that waits too long fails with how long it
107
+ waited and who held the pool. `status` shows each pool's queue in order,
108
+ for example `test-suite: p3b (holds 4m) → p5b (waits 22m) → p5c (waits 3m)`.
109
+ - **`vinta-ai-maestro`: a gate can run differently in a lane with no scope
110
+ cache.** A command gate may declare `scope_cache` (for example
111
+ `apps/api/.testmondata`) and `cold_cmd`. When a cache file is missing, the
112
+ phase gate runs `cold_cmd` (default `cmd`) instead of a scoped form that
113
+ would run every test serially. `gate_started` now records which form ran
114
+ and why. The new `project.lane_seed` option copies such ignored caches into
115
+ a lane from its dependency's lane, the integration worktree or the main
116
+ checkout, journalled as `lane_seeded`. Both fields are also accepted in
117
+ `maestro.gates.<type>`.
39
118
  - **`amend-plan` amends a live `vinta-ai-maestro` run instead of rewriting
40
119
  its branches.** When a run of the plan is running, paused, interrupted or
41
120
  failed, the skill asks whether to amend it, then edits the plan and the
@@ -842,6 +921,36 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
842
921
 
843
922
  ### Changed
844
923
 
924
+ - **`vinta-ai-maestro`: the coordinator's wakes are more specific.**
925
+ - The `gate_cost` wake lists uncached minutes per phase and flags any
926
+ single run of 20 minutes or more as probably a cold lane. Runs of a
927
+ gate's `cold_cmd` no longer count toward the total.
928
+ - A failed attempt does not wake the coordinator when it already acted on
929
+ that phase during the attempt.
930
+ - A session that ends with shell calls that never returned is journalled
931
+ as `tool_calls_unfinished` and wakes the coordinator.
932
+ - `amend` lists the affected phases that are running or waiting, which pick
933
+ up the change only once they stop.
934
+ - **Bootstrap asks about maestro's test gate in lanes.** In the maestro
935
+ defaults (C.11), bootstrap now asks:
936
+ - whether a cache-based scoped run (`pytest --testmon`) should fall back to
937
+ the full parallel suite in a lane with no cache. This emits
938
+ `scope_cache`, `cold_cmd` and `project.lane_seed`, and proposes a
939
+ `test_changed` target that falls back when `.testmondata` is missing.
940
+ It also explains that `--testmon` with `-n` trades correctness for speed.
941
+ - whether a suite that reuses its database should get a fresh-database form
942
+ for the integration worktree. This emits `fresh_cmd` and
943
+ `tuning.allowed_flags: ['--create-db']`.
944
+ - for each CI check with no gate, whether to add a gate or record it under
945
+ `maestro.ci_only` with a reason.
946
+ - **Django stack notes** cover database reuse and testmon in lanes, keeping
947
+ the virtual environment out of a bind-mounted worktree
948
+ (`UV_PROJECT_ENVIRONMENT=/opt/venv`, `user: "${UID}:${GID}"`), registries
949
+ every phase appends to, and drf-spectacular `ENUM_NAME_OVERRIDES` for new
950
+ choice fields.
951
+ - **`plan-feature`** names the enum for same-named choice fields that two
952
+ phases expose, lists the usual append-only registries, and records CI
953
+ checks that cannot run locally under `ci_only`.
845
954
  - **`plan-feature`: a rename of a shared symbol is an interface change.** A
846
955
  phase that renames something a same-wave sibling also uses does it in the
847
956
  foundation phase or keeps the old name as an alias until the wave lands.
@@ -1275,6 +1384,88 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1275
1384
 
1276
1385
  ### Fixed
1277
1386
 
1387
+ - **`vinta-ai-maestro` on Windows: a cancelled `exec` ends.** Cancelling a
1388
+ command run through `vinta-ai-maestro exec` sent the polite tree kill,
1389
+ which on Windows closes the shell and orphans the command; the orphan
1390
+ kept the output pipes open and the cancel hung. The kill is forceful
1391
+ there, as it already was for a gate.
1392
+ - **`vinta-ai-maestro`: a failing database command names its cause from
1393
+ stdout too.** `cmd.exe`'s own commands report why they failed on stdout,
1394
+ so a lane reset whose template was missing said only "exited 1". The tail
1395
+ of stdout is the cause when stderr said nothing.
1396
+ - **`vinta-ai-maestro`: `doctor` labels workflow files with forward
1397
+ slashes on every platform** (`.github/workflows/ci.yml`, never
1398
+ `.github\workflows\ci.yml`).
1399
+ - **`vinta-ai-maestro`: a review chore can no longer leave the lane dirty.**
1400
+ Whatever a review leaves uncommitted, such as a mutation experiment's
1401
+ reverted migration or a generated file, is set aside under a
1402
+ `refs/vinta-ai-maestro/wip/` ref. The lane is then reset to `HEAD` before
1403
+ the gates run, and `review_left_lane_dirty` is journalled. The implementer
1404
+ and conflict-fixer prompts now say to run one test command at a time per
1405
+ lane, to check `docker ps` for leftover run containers, and to treat a
1406
+ single test file that runs past two minutes as a lock or hang.
1407
+ - **Plan-execution prompts and the review-canvas chore.** The implementer
1408
+ prompt adds the same shared-test-resource rules. The conflict fixer
1409
+ reports `ENVIRONMENT_STALE` (and changes no code) when the merged tree's
1410
+ failures are columns or tables no model defines, and syntax-checks the
1411
+ files git auto-merged. The reviewer leaves the worktree as it found it:
1412
+ mutation experiments run in a throwaway worktree. `plan-feature`'s review
1413
+ chore prompt says so too. Its review-canvas chore passes
1414
+ `--data-dir .pr-review` inside the lane, and setup ignores `.pr-review/`.
1415
+ - **`vinta-ai-maestro`: conflicts in generated files no longer fail a
1416
+ merge.** After each conflict-fixer round, codegen outputs that the round
1417
+ left conflicted, or staged with conflict markers, are settled again and
1418
+ regenerated, even when the fixer aborted and redid the merge. The fixer's
1419
+ prompt now matches: with codegen declared, generated files are the
1420
+ integrator's; with none declared, every conflicted file is the fixer's. An
1421
+ unresolved conflict over files that look generated suggests declaring a
1422
+ `codegen` step.
1423
+ - **`vinta-ai-maestro`: `exec` streams, accepts `--reason`, and can be
1424
+ cancelled.** The daemon used to read each answer whole before sending
1425
+ it, so an `exec` queued behind the integration worktree sent nothing, and
1426
+ the client said the run was "not live" while the command still ran later.
1427
+ The answer now streams. The client says who it is queued behind, and
1428
+ ending the client cancels the command, whether it is queued or running.
1429
+ `--reason` is journalled. `exec --detach`, `exec list <run>` and
1430
+ `exec cancel <run> <id>` are new. Every release of the integration
1431
+ worktree lets the next waiter in.
1432
+ - **`vinta-ai-maestro`: a paused phase no longer takes the integration
1433
+ worktree.** A paused node waiting in the integration queue keeps its place
1434
+ but is passed over, and is let in once it is resumed. This is journalled
1435
+ as `node_wait` with state `paused`. `status` shows a node as paused as soon
1436
+ as the pause is sent, not only once it takes effect.
1437
+ - **`vinta-ai-maestro`: a lane that cannot be recycled no longer fails the
1438
+ phase handed to it.** A recycle that fails (outside the `preserve` stage)
1439
+ takes that lane out of the run and provisions a fresh one, `<lane>-r<N>`,
1440
+ and the phase runs there with nothing charged. The old lane stays on disk
1441
+ and is journalled as `lane_unhealthy`, with any paths git could not remove
1442
+ and their owner's uid. `status` shows it.
1443
+ - **`vinta-ai-maestro`: a provider or network outage no longer fails
1444
+ phases.** A turn that ends on an infrastructure error (DNS or connection
1445
+ failures, `upstream-unavailable`, HTTP 5xx or 529, an expired or rejected
1446
+ login) parks the harness with backoff and re-runs the phase when the
1447
+ harness answers. Nothing is charged to its attempts, retries or
1448
+ "N failures in a row" count. A lapsed login is asked once per harness, not
1449
+ once per phase. Each outage is journalled as `outage_started` /
1450
+ `outage_ended`, and `status` shows a line for every open one. The
1451
+ coordinator's digest no longer lists error lines from before a phase's
1452
+ last session that ended well.
1453
+ - **`vinta-ai-maestro`: integration gates no longer run against a test
1454
+ database left by another branch.** A command gate may declare `fresh_cmd`
1455
+ (also in `maestro.gates.<type>`), the same check from a fresh database (for
1456
+ example `pytest --create-db`). In the integration worktree it runs instead
1457
+ of `cmd` in four cases: when the tree moved anywhere but forward since the
1458
+ gate last ran there, on the gate's first run there, once more after a red
1459
+ run whose output shows missing or extra columns or relations, and after a
1460
+ conflict fixer ends its turn on `ENVIRONMENT_STALE`. That last round is
1461
+ given back. Each fresh run is journalled as `integration_fresh_state`.
1462
+ - **`vinta-ai-maestro`: a killed gate no longer leaves its Docker container
1463
+ running.** A timed-out or cancelled gate, `with` command or `exec` also
1464
+ removes the one-off `docker compose run` containers its lane's compose
1465
+ project started since the command began, before the pools, lease or
1466
+ integration worktree it held are released. A sweep every five minutes
1467
+ removes one-off containers in lanes no phase or `exec` is using, journalled
1468
+ as `orphan_containers_stopped`. Without docker, nothing changes.
1278
1469
  - **`vinta-ai-maestro`: the integrator no longer stages untracked files into
1279
1470
  a merge resolution.** It stages tracked paths and the merge's own paths
1280
1471
  instead of `git add --all`, so a gate's leftovers in the integration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vinta-ai-workflows",
3
- "version": "0.7.0-alpha36",
3
+ "version": "0.7.0-alpha38",
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": {
@@ -465,6 +465,21 @@
465
465
  "type": "string",
466
466
  "minLength": 1
467
467
  },
468
+ "fresh_cmd": {
469
+ "description": "The same check run from fresh state (the test database recreated, e.g. `pytest --create-db`). Run in place of `cmd` in the integration worktree whenever the state an earlier run left there may belong to another tree: the tree moved anywhere but forward, the gate has not run there yet in this process, its red output reads as a stale schema, or a conflict fixer reported `ENVIRONMENT_STALE`. Must check at least what `cmd` does.",
470
+ "type": "string",
471
+ "minLength": 1
472
+ },
473
+ "cold_cmd": {
474
+ "description": "What a phase gate runs in place of `scoped_cmd` when the lane lacks a `scope_cache` file (e.g. `make test`, the suite in parallel, instead of a `--testmon` run that without `.testmondata` runs every test serially). Defaults to `cmd`. Must check at least what `scoped_cmd` does.",
475
+ "type": "string",
476
+ "minLength": 1
477
+ },
478
+ "scope_cache": {
479
+ "description": "Files `scoped_cmd` narrows by, relative to the lane (e.g. `apps/api/.testmondata`). When any is missing, a phase gate runs `cold_cmd` (or `cmd`) instead and journals why.",
480
+ "type": "array",
481
+ "items": { "type": "string", "minLength": 1 }
482
+ },
468
483
  "requires": {
469
484
  "description": "Resource pools the gate holds while it runs, e.g. `[test-suite]`.",
470
485
  "type": "array",
@@ -477,6 +492,11 @@
477
492
  "type": "integer",
478
493
  "minimum": 1
479
494
  },
495
+ "queue_timeout_s": {
496
+ "description": "How long a phase gate may wait for its `requires` pools before the attempt fails, naming who held them. `timeout_s` counts only the command's own runtime. Absent, it waits as long as it takes.",
497
+ "type": "integer",
498
+ "minimum": 1
499
+ },
480
500
  "tuning": {
481
501
  "description": "Flags the monitor may add to `cmd` when it retunes the gate mid-run. Same shape as a workflow gate's `tuning`.",
482
502
  "type": "object",
@@ -512,6 +532,21 @@
512
532
  "type": "string",
513
533
  "minLength": 1
514
534
  },
535
+ "fresh_cmd": {
536
+ "description": "The same check run from fresh state (the test database recreated, e.g. `pytest --create-db`). Run in place of `cmd` in the integration worktree whenever the state an earlier run left there may belong to another tree: the tree moved anywhere but forward, the gate has not run there yet in this process, its red output reads as a stale schema, or a conflict fixer reported `ENVIRONMENT_STALE`. Must check at least what `cmd` does.",
537
+ "type": "string",
538
+ "minLength": 1
539
+ },
540
+ "cold_cmd": {
541
+ "description": "What a phase gate runs in place of `scoped_cmd` when the lane lacks a `scope_cache` file (e.g. `make test`, the suite in parallel, instead of a `--testmon` run that without `.testmondata` runs every test serially). Defaults to `cmd`. Must check at least what `scoped_cmd` does.",
542
+ "type": "string",
543
+ "minLength": 1
544
+ },
545
+ "scope_cache": {
546
+ "description": "Files `scoped_cmd` narrows by, relative to the lane (e.g. `apps/api/.testmondata`). When any is missing, a phase gate runs `cold_cmd` (or `cmd`) instead and journals why.",
547
+ "type": "array",
548
+ "items": { "type": "string", "minLength": 1 }
549
+ },
515
550
  "requires": {
516
551
  "description": "Resource pools the gate holds while it runs, e.g. `[test-suite]`.",
517
552
  "type": "array",
@@ -524,6 +559,11 @@
524
559
  "type": "integer",
525
560
  "minimum": 1
526
561
  },
562
+ "queue_timeout_s": {
563
+ "description": "How long a phase gate may wait for its `requires` pools before the attempt fails, naming who held them. `timeout_s` counts only the command's own runtime. Absent, it waits as long as it takes.",
564
+ "type": "integer",
565
+ "minimum": 1
566
+ },
527
567
  "tuning": {
528
568
  "description": "Flags the monitor may add to `cmd` when it retunes the gate mid-run. Same shape as a workflow gate's `tuning`.",
529
569
  "type": "object",
@@ -559,6 +599,21 @@
559
599
  "type": "string",
560
600
  "minLength": 1
561
601
  },
602
+ "fresh_cmd": {
603
+ "description": "The same check run from fresh state (the test database recreated, e.g. `pytest --create-db`). Run in place of `cmd` in the integration worktree whenever the state an earlier run left there may belong to another tree: the tree moved anywhere but forward, the gate has not run there yet in this process, its red output reads as a stale schema, or a conflict fixer reported `ENVIRONMENT_STALE`. Must check at least what `cmd` does.",
604
+ "type": "string",
605
+ "minLength": 1
606
+ },
607
+ "cold_cmd": {
608
+ "description": "What a phase gate runs in place of `scoped_cmd` when the lane lacks a `scope_cache` file (e.g. `make test`, the suite in parallel, instead of a `--testmon` run that without `.testmondata` runs every test serially). Defaults to `cmd`. Must check at least what `scoped_cmd` does.",
609
+ "type": "string",
610
+ "minLength": 1
611
+ },
612
+ "scope_cache": {
613
+ "description": "Files `scoped_cmd` narrows by, relative to the lane (e.g. `apps/api/.testmondata`). When any is missing, a phase gate runs `cold_cmd` (or `cmd`) instead and journals why.",
614
+ "type": "array",
615
+ "items": { "type": "string", "minLength": 1 }
616
+ },
562
617
  "requires": {
563
618
  "description": "Resource pools the gate holds while it runs, e.g. `[test-suite]`.",
564
619
  "type": "array",
@@ -571,6 +626,11 @@
571
626
  "type": "integer",
572
627
  "minimum": 1
573
628
  },
629
+ "queue_timeout_s": {
630
+ "description": "How long a phase gate may wait for its `requires` pools before the attempt fails, naming who held them. `timeout_s` counts only the command's own runtime. Absent, it waits as long as it takes.",
631
+ "type": "integer",
632
+ "minimum": 1
633
+ },
574
634
  "tuning": {
575
635
  "description": "Flags the monitor may add to `cmd` when it retunes the gate mid-run. Same shape as a workflow gate's `tuning`.",
576
636
  "type": "object",
@@ -606,6 +666,21 @@
606
666
  "type": "string",
607
667
  "minLength": 1
608
668
  },
669
+ "fresh_cmd": {
670
+ "description": "The same check run from fresh state (the test database recreated, e.g. `pytest --create-db`). Run in place of `cmd` in the integration worktree whenever the state an earlier run left there may belong to another tree: the tree moved anywhere but forward, the gate has not run there yet in this process, its red output reads as a stale schema, or a conflict fixer reported `ENVIRONMENT_STALE`. Must check at least what `cmd` does.",
671
+ "type": "string",
672
+ "minLength": 1
673
+ },
674
+ "cold_cmd": {
675
+ "description": "What a phase gate runs in place of `scoped_cmd` when the lane lacks a `scope_cache` file (e.g. `make test`, the suite in parallel, instead of a `--testmon` run that without `.testmondata` runs every test serially). Defaults to `cmd`. Must check at least what `scoped_cmd` does.",
676
+ "type": "string",
677
+ "minLength": 1
678
+ },
679
+ "scope_cache": {
680
+ "description": "Files `scoped_cmd` narrows by, relative to the lane (e.g. `apps/api/.testmondata`). When any is missing, a phase gate runs `cold_cmd` (or `cmd`) instead and journals why.",
681
+ "type": "array",
682
+ "items": { "type": "string", "minLength": 1 }
683
+ },
609
684
  "requires": {
610
685
  "description": "Resource pools the gate holds while it runs, e.g. `[test-suite]`.",
611
686
  "type": "array",
@@ -618,6 +693,11 @@
618
693
  "type": "integer",
619
694
  "minimum": 1
620
695
  },
696
+ "queue_timeout_s": {
697
+ "description": "How long a phase gate may wait for its `requires` pools before the attempt fails, naming who held them. `timeout_s` counts only the command's own runtime. Absent, it waits as long as it takes.",
698
+ "type": "integer",
699
+ "minimum": 1
700
+ },
621
701
  "tuning": {
622
702
  "description": "Flags the monitor may add to `cmd` when it retunes the gate mid-run. Same shape as a workflow gate's `tuning`.",
623
703
  "type": "object",
@@ -656,6 +736,21 @@
656
736
  "type": "string",
657
737
  "minLength": 1
658
738
  },
739
+ "fresh_cmd": {
740
+ "description": "The same check run from fresh state (the test database recreated, e.g. `pytest --create-db`). Run in place of `cmd` in the integration worktree whenever the state an earlier run left there may belong to another tree: the tree moved anywhere but forward, the gate has not run there yet in this process, its red output reads as a stale schema, or a conflict fixer reported `ENVIRONMENT_STALE`. Must check at least what `cmd` does.",
741
+ "type": "string",
742
+ "minLength": 1
743
+ },
744
+ "cold_cmd": {
745
+ "description": "What a phase gate runs in place of `scoped_cmd` when the lane lacks a `scope_cache` file (e.g. `make test`, the suite in parallel, instead of a `--testmon` run that without `.testmondata` runs every test serially). Defaults to `cmd`. Must check at least what `scoped_cmd` does.",
746
+ "type": "string",
747
+ "minLength": 1
748
+ },
749
+ "scope_cache": {
750
+ "description": "Files `scoped_cmd` narrows by, relative to the lane (e.g. `apps/api/.testmondata`). When any is missing, a phase gate runs `cold_cmd` (or `cmd`) instead and journals why.",
751
+ "type": "array",
752
+ "items": { "type": "string", "minLength": 1 }
753
+ },
659
754
  "requires": {
660
755
  "description": "Resource pools the gate holds while it runs, e.g. `[test-suite]`.",
661
756
  "type": "array",
@@ -668,6 +763,11 @@
668
763
  "type": "integer",
669
764
  "minimum": 1
670
765
  },
766
+ "queue_timeout_s": {
767
+ "description": "How long a phase gate may wait for its `requires` pools before the attempt fails, naming who held them. `timeout_s` counts only the command's own runtime. Absent, it waits as long as it takes.",
768
+ "type": "integer",
769
+ "minimum": 1
770
+ },
671
771
  "tuning": {
672
772
  "description": "Flags the monitor may add to `cmd` when it retunes the gate mid-run. Same shape as a workflow gate's `tuning`.",
673
773
  "type": "object",
@@ -777,6 +877,29 @@
777
877
  "minLength": 1
778
878
  }
779
879
  },
880
+ "post_merge": {
881
+ "description": "A command run in maestro's integration worktree after git merges a branch by itself, with `{files}` replaced by the files both sides changed — e.g. `python -m py_compile {files} && ruff check --fix --select I,F811,F401 {files} && ruff format {files}`. What it changes is committed on top of the merge; a non-zero exit is journalled and the merged tree's gates judge it. A plan's own `post_merge` replaces this one.",
882
+ "type": "object",
883
+ "additionalProperties": false,
884
+ "required": ["cmd"],
885
+ "properties": {
886
+ "cmd": { "type": "string", "minLength": 1 },
887
+ "timeout_s": { "type": "integer", "minimum": 1 }
888
+ }
889
+ },
890
+ "ci_only": {
891
+ "description": "CI checks deliberately left to CI, each with its reason (it needs credentials, a deploy, a hosted service). `vinta-ai-maestro doctor` warns about every CI command no gate runs; one listed here is a decision, not a gap. A plan's own `ci_only` replaces this list.",
892
+ "type": "array",
893
+ "items": {
894
+ "type": "object",
895
+ "additionalProperties": false,
896
+ "required": ["command", "reason"],
897
+ "properties": {
898
+ "command": { "type": "string", "minLength": 1, "description": "The CI command as CI runs it; wrappers like `uv run` are ignored when matching." },
899
+ "reason": { "type": "string", "minLength": 1 }
900
+ }
901
+ }
902
+ },
780
903
  "project": {
781
904
  "description": "The workflow schema's `project` block (`migrate_cmd`, `databases`, `env_files`, `commands`, `hooks`, `services`, `compose`, `prepare_cmd`, `setup_cmd`), merged under a plan's own field by field. `project.commands` additionally inherits `lint`, `typecheck` (from `commands.build`) and `test` (from `commands.test_unit`).",
782
905
  "type": "object"
@@ -366,6 +366,15 @@
366
366
  "type": "string",
367
367
  "minLength": 1
368
368
  },
369
+ "lane_seed": {
370
+ "default": [],
371
+ "description": "Ignored cache files a lane is given when it is handed to a phase and lacks them, relative to the lane (`apps/api/.testmondata`): copied from the lane of the phase’s first dependency, else the integration worktree, else the main checkout, whichever has it first. Only paths git ignores are copied, and never over one the lane has. For a scope cache (`gates.<id>.scope_cache`) that is expensive to build from nothing.",
372
+ "type": "array",
373
+ "items": {
374
+ "type": "string",
375
+ "minLength": 1
376
+ }
377
+ },
369
378
  "recycle_keep": {
370
379
  "default": [],
371
380
  "description": "Ignored paths a recycled lane keeps. A recycle removes every untracked and ignored file the previous phase left — caches such as `.mypy_cache` and `__pycache__` included, which once kept a deleted package importable and failed a type check on clean code — except the copied dependency tree, the `env_files` and what is listed here. Each entry is a gitignore pattern handed to `git clean -e`: `.venv` keeps one at any depth, `/apps/api/.venv` only that one. For what is expensive to rebuild; anything not listed is rebuilt by `setup_cmd`, which runs on every recycle.",
@@ -683,6 +692,24 @@
683
692
  "type": "string",
684
693
  "minLength": 1
685
694
  },
695
+ "cold_cmd": {
696
+ "description": "What a phase gate runs in place of `scoped_cmd` when the lane has no scope cache yet (`scope_cache`): `make test` (the suite in parallel) instead of a `--testmon` run that, without `.testmondata`, runs every test serially. Defaults to `cmd`. It must check at least what `scoped_cmd` does.",
697
+ "type": "string",
698
+ "minLength": 1
699
+ },
700
+ "scope_cache": {
701
+ "description": "Files `scoped_cmd` narrows by, relative to the lane (`apps/api/.testmondata`). When any is missing, the scoped form would run everything slowly, so a phase gate runs `cold_cmd` (or `cmd`) instead, and journals why. `project.lane_seed` can copy them into new lanes.",
702
+ "type": "array",
703
+ "items": {
704
+ "type": "string",
705
+ "minLength": 1
706
+ }
707
+ },
708
+ "fresh_cmd": {
709
+ "description": "The same check run from fresh state — the test database recreated (`pytest --create-db`, `make test_reset test`) — run in place of `cmd` in the integration worktree whenever the state an earlier run left there may belong to another tree: the tree moved anywhere but forward since the gate last ran there, the gate has not run there in this process, its red output reads as a stale schema (missing or extra columns or relations), or a conflict fixer reported `ENVIRONMENT_STALE`. It must check at least what `cmd` does.",
710
+ "type": "string",
711
+ "minLength": 1
712
+ },
686
713
  "requires": {
687
714
  "default": [],
688
715
  "description": "Resource pool ids acquired before the gate runs, in canonical order.",
@@ -695,6 +722,13 @@
695
722
  },
696
723
  "timeout_s": {
697
724
  "default": 1800,
725
+ "description": "How long the command may run, counted from when it starts — never the time it queued for `requires`.",
726
+ "type": "integer",
727
+ "minimum": 1,
728
+ "maximum": 9007199254740991
729
+ },
730
+ "queue_timeout_s": {
731
+ "description": "How long a phase gate may wait for its `requires` pools before the attempt fails, naming who held them and for how long. Absent, it waits as long as it takes.",
698
732
  "type": "integer",
699
733
  "minimum": 1,
700
734
  "maximum": 9007199254740991
@@ -962,6 +996,52 @@
962
996
  "additionalProperties": false
963
997
  }
964
998
  },
999
+ "post_merge": {
1000
+ "description": "A normalizing command for files a clean merge combined — see PostMergeSchema.",
1001
+ "type": "object",
1002
+ "properties": {
1003
+ "cmd": {
1004
+ "type": "string",
1005
+ "minLength": 1,
1006
+ "description": "Shell command run in the integration worktree after git merges a branch by itself, with `{files}` replaced by the files both sides changed (shell-quoted): `python -m py_compile {files} && ruff check --fix --select I,F811,F401 {files} && ruff format {files}`. What it changes is committed on top of the merge; a non-zero exit is journalled and the merged tree’s gates judge it."
1007
+ },
1008
+ "timeout_s": {
1009
+ "default": 300,
1010
+ "type": "integer",
1011
+ "minimum": 1,
1012
+ "maximum": 9007199254740991
1013
+ }
1014
+ },
1015
+ "required": [
1016
+ "cmd"
1017
+ ],
1018
+ "additionalProperties": false
1019
+ },
1020
+ "ci_only": {
1021
+ "default": [],
1022
+ "description": "CI checks deliberately left to CI, each with its reason. `doctor` warns about every CI command no gate runs; one listed here is reported as a decision instead of a gap.",
1023
+ "type": "array",
1024
+ "items": {
1025
+ "type": "object",
1026
+ "properties": {
1027
+ "command": {
1028
+ "type": "string",
1029
+ "minLength": 1,
1030
+ "description": "The CI command, as CI runs it (wrappers like `uv run` are ignored when matching)."
1031
+ },
1032
+ "reason": {
1033
+ "type": "string",
1034
+ "minLength": 1,
1035
+ "description": "Why no gate runs it: it needs credentials, a deploy, a hosted service."
1036
+ }
1037
+ },
1038
+ "required": [
1039
+ "command",
1040
+ "reason"
1041
+ ],
1042
+ "additionalProperties": false
1043
+ }
1044
+ },
965
1045
  "append_only": {
966
1046
  "default": [],
967
1047
  "description": "Files every phase only appends to — a DI container, a URL route list, a registry of notification kinds, a beat schedule — as gitattributes patterns. Merged in the integration worktree with git’s `union` driver, which keeps both sides’ lines, so two phases each adding an entry is not a conflict. The merged tree is gated as any other is; a union that reads wrong (a duplicate key, a missing comma) is a red gate there, not a silent pass.",
@@ -102,11 +102,21 @@ These bleed across sub-skills, so capture once now:
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
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
+ - **Cold lanes.** Only when the scoped template narrows by a cache, as `pytest --testmon` does with `.testmondata`. A lane has no cache until a run builds one, and a testmon run without one runs **every** test, serially: one observed run spent 30–40 minutes per phase gate that way. Say so, and that `--testmon` together with `-n` (xdist) trades correctness for speed — testmon cannot see which tests a worker ran, so the project's scoped line normally runs without `-n`. `AskUserQuestion` (header `Cold lanes`): `Run the full suite in parallel until a lane has a cache (Recommended)`, `Keep the scoped command — it handles a missing cache itself`. On the first, emit `maestro.gates.test.scope_cache` (the cache path, relative to the repo, e.g. `apps/api/.testmondata`), `maestro.gates.test.cold_cmd` (the parallel full suite, e.g. `make test`), and `maestro.project.lane_seed` with the same paths, so a lane is handed a copy from its dependency's lane. When the scoped line is a task-runner target (`make test_changed`), also propose the fallback in the target itself, so `implement-plan` and a person get it too:
106
+
107
+ ```make
108
+ test_changed:
109
+ @if [ ! -f .testmondata ]; then $(MAKE) test; else <the testmon command>; fi
110
+ ```
111
+
112
+ Show it as a proposed edit and apply it only on the user's yes.
113
+ - **Fresh database in the integration worktree.** Only when the test command keeps its database between runs (`--reuse-db`, `--keepdb`, a fixture that skips migrations on an existing schema). Maestro's integration worktree runs merges for different branches one after another, and a reused database applies each tree's migrations on top of the last tree's — one observed run had three merges fail on columns and tables that belonged to another branch. `AskUserQuestion` (header `Fresh DB`): `Recreate the database there when the tree changes (Recommended)`, `Keep reusing it`. On the first, propose the fresh form of the command (`--create-db` in place of `--reuse-db`, or `make test_reset test`), confirm it, and emit it as `maestro.gates.test.fresh_cmd`, plus `tuning.allowed_flags: ['--create-db']` when the flag form fits, so the run coordinator can add the flag without being refused for rewriting the command.
105
114
  - **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`).
106
115
  - **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.
107
116
  - **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.
108
117
 
109
118
  - **Test-tool caches git must not carry.** Gates in a lane write cache and report files into the worktree, and an agent's `git add -A` there commits whatever is not ignored. Read `.gitignore` for the outputs of the runners and gates selected above: `.testmondata*` (pytest-testmon's SQLite database, plus its `-shm` / `-wal` siblings — tens of MB), `.pytest_cache/`, `junit/` or `junit*.xml`, `coverage/`, `.coverage`, `.coverage.*`, `htmlcov/`, `coverage.xml`, `.nyc_output/`, `.vitest/` and `.jest-cache/` where those runners are in use. Then read `.pre-commit-config.yaml` (or the project's hook runner) for `check-added-large-files`: that hook checks staged files, so a testmon database that is not ignored fails every lane commit and every maestro merge commit (`project.hooks: run`), and looks like a hook problem rather than a missing ignore rule. Propose the missing entries in a multi-select `AskUserQuestion` (header `Cache ignores`), each pre-selected, with `Add the selected entries (Recommended)` meaning append them to `.gitignore` under a `# test-tool caches` comment. When one is already tracked, say so: ignoring it does not untrack it, and the human runs `git rm --cached <path>` themselves. Nothing missing → say so and add nothing.
119
+ - **CI checks.** Read the CI config (`.github/workflows/*.yml`, `.gitlab-ci.yml`) for each check it runs — tests, lint, type check, `makemigrations --check --dry-run`, `check --deploy --fail-level WARNING`, a "generated client is current" diff — and compare with the gates chosen above. For each check no gate runs, `AskUserQuestion` (header `CI check`): `Add it as a project gate (Recommended)`, `Leave it to CI — it needs CI (I'll say why)`. A gate lands as `maestro.gates.<id>.cmd` with its id added to `maestro.defaults.gates`. A check left to CI lands under `maestro.ci_only` with its command and the reason, which `vinta-ai-maestro doctor` reports as a decision rather than warning about it on every run. Never drop one silently: an unrecorded gap is a warning in every run's log that people learn to skip.
110
120
  - **Generated files and append-only registries.** Read the project for generators — an API schema command (`spectacular`, an `openapi.json` dump), a generated client (`openapi-ts`, `orval`, `openapi-generator`, `graphql-codegen`; `*.gen.ts`), a route tree (`routeTree.gen.ts`) — and for file-modifying hooks in `.pre-commit-config.yaml` / `lefthook.yml` / `lint-staged` (a schema hook, `ruff format`, `prettier --write`). Propose each as a codegen step in a multi-select `AskUserQuestion` (header `Codegen`), pre-selected, showing its command, inputs and outputs; a formatter is `format: true` with `{changed_files}` in its command. → `maestro.codegen`. Then propose append-only registries — DI containers, URL route lists, notification-kind registries, beat schedules — in a second multi-select (header `Registries`). → `maestro.append_only`. Nothing found → omit both keys and say so.
111
121
 
112
122
  Everything here is a default under every plan. A plan's own `.workflow.json` overrides any of it, and a commit to this file on a running plan's branch (`plan/<workflow-id>/base`) is applied to that run.
@@ -376,6 +386,11 @@ maestro:
376
386
  gates:
377
387
  test:
378
388
  cmd: <C.11 → maestro-only test command; omit to use commands.test_unit>
389
+ fresh_cmd: <C.11 "Fresh database in the integration worktree" → the same command from a fresh test database, e.g. pytest --create-db -n auto; omit when the suite keeps no database between runs>
390
+ tuning:
391
+ allowed_flags: <C.11 → ['--create-db'] alongside fresh_cmd, so the coordinator may add it; omit otherwise>
392
+ cold_cmd: <C.11 "Cold lanes" → what runs while a lane has no scope cache, e.g. the parallel full suite; omit when the scoped template needs no cache>
393
+ scope_cache: <C.11 "Cold lanes" → the cache files the scoped template narrows by, e.g. [apps/api/.testmondata]; omit otherwise>
379
394
  requires: [test-suite] # only with a test-suite pool
380
395
  resources:
381
396
  test-suite: # only on a pool limit
@@ -390,8 +405,12 @@ maestro:
390
405
  after: <steps that run first, e.g. [schema]; omit when none>
391
406
  format: <true for a formatter whose cmd takes {changed_files}; omit otherwise>
392
407
  append_only: <C.11 → registries phases only append to, e.g. [app/containers.py, app/urls.py]; omit when none>
408
+ ci_only: # C.11 "CI checks" → each CI command deliberately left to CI; omit when every one has a gate
409
+ - command: <the CI command as CI runs it>
410
+ reason: <why no gate runs it: credentials, a deploy, a hosted service>
393
411
  project:
394
412
  env_files: <C.11 → gitignored files every lane needs its own copy of, e.g. [.env, .env.docker, app/settings/local.py]; omit when none>
413
+ lane_seed: <C.11 "Cold lanes" → the same scope-cache paths, so a new lane gets a copy; omit otherwise>
395
414
 
396
415
  skills:
397
416
  # Only emit this block when foundation_skills.systematic-debugging = enabled.
@@ -25,7 +25,7 @@ If no multi-tenancy signals → single-tenant Django; the team's templates may s
25
25
  - **Create-postgres-function** (if the project ships custom `CREATE FUNCTION` / aggregates) — versioned function migrations.
26
26
  - **Create-data-import / create-data-export** — async import/export commands feeding integrations.
27
27
  - **GraphQL-public-query** (if the project uses Strawberry or Graphene) — public-API mutation/query patterns.
28
- - **REST-endpoint** (if the project uses Django REST Framework or django-ninja) - REST API endpoint patterns.
28
+ - **REST-endpoint** (if the project uses Django REST Framework or django-ninja) - REST API endpoint patterns. With drf-spectacular, ask whether the team's endpoint template requires every new `choices` field the API exposes to add its `ENUM_NAME_OVERRIDES` entry in the same change. Parallel phases that each add a `status` field with different choices otherwise collide on the generated enum name (`StatusEnum`), and the collision only shows at integration, in the schema test.
29
29
  - **Run-one-off-script-django** — sister to the universal `add-one-off-script` foundation skill. Authors the *runner* artefact in the per-script folder (Jupyter notebook in `notebooks/` is the typical Vinta default; a thin `BaseCommand` under `<app>/management/commands/<name>.py` is the alternative for headless / cron / CI invocation) and ships the matching `Runtime` adapter at `<scripts_dir>/_runtime_django.py` (`JupyterRuntime` and/or `DjangoMgmtRuntime`). Adapter responsibilities: hook `BaseCommand.handle()` into `BaseOneOffScript.execute()`; respect `python manage.py <name> --apply` / `--resume` / `--status` / `--restore` flags; load Django settings + DB connection before the engine starts iterating; for the Jupyter variant, swap signal handlers for kernel-interrupt and skip the PID-file lease (notebook = single instance by construction). README in the per-script folder spells out `python manage.py shell < script.py`, `jupyter lab notebooks/<name>/runner.ipynb`, and `python manage.py <name> --apply` as the three legitimate entry points.
30
30
 
31
31
  ## Agent categories typically needed
@@ -55,6 +55,27 @@ Ask which of these CI runs, and suggest each one it runs as a gate, so a phase c
55
55
 
56
56
  Placeholders to ask about: the settings module CI runs checks with, and the schema/client generate command and its output paths.
57
57
 
58
+ ## Test commands in lanes
59
+
60
+ Ask how the suite runs, because maestro runs it in many worktrees at once, one merged tree after another in its integration worktree:
61
+
62
+ - **Database reuse.** With `pytest --reuse-db` (or `--keepdb`), a database a test run left behind applies forward migrations and never removes ones the current tree lacks. The integration worktree switches branches between merges, so its test gate needs a fresh-database form (`--create-db`, `make test_reset test`) — bootstrap emits it as `maestro.gates.test.fresh_cmd`.
63
+ - **Scoped runs with testmon.** `pytest --testmon` without a `.testmondata` runs every test, serially. A `test_changed` target should fall back to the full parallel run when the file is missing, and bootstrap declares the cache as `scope_cache` / `lane_seed`. `--testmon` with `-n` trades correctness for speed: testmon cannot attribute what an xdist worker ran.
64
+ - **One run at a time per lane.** A DB-backed suite shares its lane's test databases with any other run in that lane. Ask about the test-suite pool, and tell agents to check `docker ps` for a leftover `<lane>-api-run-*` container before starting another run.
65
+
66
+ Placeholders to ask about: the fresh-database form of the test command, the testmon cache path, the full parallel command.
67
+
68
+ ## The dev container and the worktree
69
+
70
+ When `docker-compose.yml` bind-mounts the repository into the dev container, anything the container writes into the mount belongs to the container's user — root by default — and a lane whose `apps/api/.venv` is root-owned cannot be cleaned or recycled from the host (one observed phase failed six times on such a lane). Ask whether the compose file already keeps these out of the mount, and suggest, as changes the team makes to its own compose file:
71
+
72
+ - the virtual environment outside the mounted tree: `UV_PROJECT_ENVIRONMENT=/opt/venv` (uv), or a named volume over the `.venv` path;
73
+ - the dev service running as the host user: `user: "${UID}:${GID}"`, with `UID`/`GID` exported in the project's env file.
74
+
75
+ ## Registries every phase appends to
76
+
77
+ URL route lists (`routes.py`), DI containers (`containers.py`), task packages (`tasks/__init__.py`) and drf-spectacular's `ENUM_NAME_OVERRIDES` are edited by nearly every phase of a plan, and they cause most of its merge work — including auto-merges git accepts that leave two list openers or a duplicated import. Ask which the project has, and suggest either a registry per feature module (`routes/<feature>.py` gathered by a loader, tasks found by autodiscovery), or listing them under `maestro.append_only` so maestro merges them with git's union driver. Either way the merged tree is gated.
78
+
58
79
  ## Test determinism
59
80
 
60
81
  Tests that touch dates freeze the clock and set the timezone explicitly (`freezegun` / `time-machine` + `override_settings(TIME_ZONE=...)`), and schema generation reads no host state — timezone choices come from a pinned `tzdata`, not the OS tz database. Ask whether the project already has `freezegun`/`time-machine` and `tzdata` in its dev/lock dependencies; the `write-unit-test` Django pack assumes them.
@@ -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
+ - **Two phases exposing a field of the same name with different choices: name the enum in the plan.** A schema generator names an enum after the field (`status` → `StatusEnum`), so two parallel phases that each add a `status` choice field with different values collide on that name, and the collision shows only at integration, in the schema test. When two phases in the plan expose same-named choice fields, say in each phase's body which enum name its field gets (and, with drf-spectacular, that the phase adds its `ENUM_NAME_OVERRIDES` entry in the same change). That entry list is a registry every such phase appends to: see `append_only`.
257
258
  - **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.
258
259
 
259
260
  ### Design *for* parallelism when it's cheap
@@ -638,7 +639,7 @@ Rules the mapping depends on:
638
639
  - **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.
639
640
  - **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 row with no `crew` entry, means the two were edited separately.
640
641
  - **A gate is a command; a chore is an agent.** Both run per phase and that is where the resemblance stops. A gate is a shell line that says pass or fail and decides whether the phase merges. A chore is a turn that *changes* the diff — the review loop's fixes, the comment pass, a changelog entry — and it runs only once the phase's gates are green. Only the `review` chore stands between a phase and its merge; any other chore that fails is recorded and the phase carries on to its final gate run. Anything you can express as a command belongs in `gates`, where it is cached and queued and costs no model time.
641
- - **Gates cover what CI runs.** A check CI runs and no gate runs is a check every phase passes and the plan PR fails. Read the CI config (`.github/workflows/`, `.gitlab-ci.yml`, `.circleci/`, …) next to the task runner, and give every CI job that can run locally a gate — or say in the plan why it cannot (it needs a secret, a deploy, a hosted service). The ones a plan most often misses are not the test suite: a framework's deploy or system check run at warning level, a migrations-are-complete check, a "generated client / schema / lockfile is up to date" check that regenerates and diffs, a docs build. One observed run had a phase add routes that passed every gate and failed CI's deploy check. These are project-wide checks: they belong in `.vinta-ai-workflows.yaml` as custom gates (`maestro.gates.<id>` with a `cmd`, the id listed in `maestro.defaults.gates`), which every plan then runs without declaring them. When the project's `maestro.gates` lacks such a check, declare it here for this plan and tell the human it belongs in the project file — and give the exact `maestro.gates.<id>` entry to add.
642
+ - **Gates cover what CI runs.** A check CI runs and no gate runs is a check every phase passes and the plan PR fails. Read the CI config (`.github/workflows/`, `.gitlab-ci.yml`, `.circleci/`, …) next to the task runner, and give every CI job that can run locally a gate — or say in the plan why it cannot (it needs a secret, a deploy, a hosted service). The ones a plan most often misses are not the test suite: a framework's deploy or system check run at warning level, a migrations-are-complete check, a "generated client / schema / lockfile is up to date" check that regenerates and diffs, a docs build. One observed run had a phase add routes that passed every gate and failed CI's deploy check. These are project-wide checks: they belong in `.vinta-ai-workflows.yaml` as custom gates (`maestro.gates.<id>` with a `cmd`, the id listed in `maestro.defaults.gates`), which every plan then runs without declaring them. When the project's `maestro.gates` lacks such a check, declare it here for this plan and tell the human it belongs in the project file — and give the exact `maestro.gates.<id>` entry to add. A check that cannot run locally is recorded, not dropped: under `ci_only` (or the project's `maestro.ci_only`), each entry a `command` as CI runs it and the `reason`, so `vinta-ai-maestro doctor` reports it as a decision instead of a gap.
642
643
  - **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. A command that goes through `docker compose run` or `exec` needs `-T`: a gate runs with no terminal, and without it compose fails with "the input device is not a TTY" before the command starts (`doctor` warns about it).
643
644
  - **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.
644
645
  - **`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.
@@ -672,7 +673,7 @@ Most merge conflicts between parallel phases are in two kinds of file, and neith
672
673
 
673
674
  Each step must be **deterministic**: the same inputs give byte-identical outputs. A generator that stamps the date, sorts by a hash, or embeds the machine's timezone (one observed schema's timezone enum differed by machine) conflicts on every merge whatever maestro does; fix its configuration first, and run `vinta-ai-maestro doctor <workflow.json> --codegen` to check. Name every file a step reads in its `inputs`, or leave `inputs` empty to run it every time. The phase briefs need no instruction about these files: maestro tells every agent which paths are generated and how to regenerate them.
674
675
 
675
- **Append-only registries → `append_only`.** A file where each phase adds its own entries and never edits another's — a dependency-injection container, a URL `urlpatterns` list, a registry of notification kinds or event types, a Celery beat schedule, a `periodic_tasks.py` — is merged with git's `union` driver, which keeps both sides' lines. Find them in the Touch Lists: a file two or more phases of different waves both touch, and only by adding lines, is one. The merged tree is still gated, so a union that reads wrong (two entries with one key, a missing comma between list items) is caught there; prefer registries whose entries are one line each, ending in a comma. Never list a file where phases edit existing lines — union keeps both versions of the line.
676
+ **Append-only registries → `append_only`.** A file where each phase adds its own entries and never edits another's — a dependency-injection container, a URL `urlpatterns` list, a registry of notification kinds or event types, a Celery beat schedule, a `periodic_tasks.py` — is merged with git's `union` driver, which keeps both sides' lines. Find them in the Touch Lists: a file two or more phases of different waves both touch, and only by adding lines, is one — route lists, DI containers, a task package's `__init__.py` and a settings list such as `ENUM_NAME_OVERRIDES` are the usual ones, and a plan where nearly every phase touches one of them spends most of its merge work there. The merged tree is still gated, so a union that reads wrong (two entries with one key, a missing comma between list items) is caught there; prefer registries whose entries are one line each, ending in a comma. Never list a file where phases edit existing lines — union keeps both versions of the line.
676
677
 
677
678
  ### Plan-level context
678
679
 
@@ -760,7 +761,7 @@ Emit this chore on every plan unless `maestro.chores.review` already declares it
760
761
  "review": {
761
762
  "skill": "thermo-nuclear-review-loop",
762
763
  "when": "review",
763
- "prompt": "Run the thermo-nuclear-review-loop skill over this phase's diff until its reviewer approves it.",
764
+ "prompt": "Run the thermo-nuclear-review-loop skill over this phase's diff until its reviewer approves it. Leave the lane as you found it apart from committed fixes: a mutation experiment runs in a throwaway worktree (git worktree add), never here, and `git status --porcelain` is empty before you state the verdict.",
764
765
  "description": "The phase's code review: one reviewer sub-agent, the implementer fixing and answering it."
765
766
  }
766
767
  ```
@@ -775,12 +776,12 @@ Emit this chore only when `.vinta-ai-workflows.yaml` has `integrations.pr-review
775
776
  "review-canvas": {
776
777
  "skill": "pr-review-canvas",
777
778
  "when": "after_pr",
778
- "prompt": "Generate the review canvas for this phase's pull request: run `/pr-review-canvas` with the PR number named above. Report the local review URL and the PR comment link it returns. Do not change any file in the repository.",
779
+ "prompt": "Generate the review canvas for this phase's pull request: run `/pr-review-canvas` with the PR number named above, passing `--data-dir .pr-review` so its data lands inside this worktree (the directory is gitignored; the main checkout's is outside what you may write). Report the local review URL and the PR comment link it returns. Do not change any tracked file.",
779
780
  "description": "A topic-grouped review canvas on each phase PR, for the human reviewer."
780
781
  }
781
782
  ```
782
783
 
783
- `"when": "after_pr"` is what makes this work. Maestro runs the chore at the end of `integrate`, once the PR is open, and tells the agent the PR's URL and number. When no PR opened, it skips the chore. Do not give it `on_failure: "fail"`; maestro refuses that for an `after_pr` chore, because the phase is already merged. The integration never changes how the plan's phases are written, so the worked example below leaves this chore out.
784
+ `--data-dir .pr-review` is relative to the lane: a lane agent may write only inside its own worktree, and the tool's default data directory resolves to the main checkout, so every phase's canvas chore was refused and fell back to a scratch directory. `"when": "after_pr"` is what makes this work. Maestro runs the chore at the end of `integrate`, once the PR is open, and tells the agent the PR's URL and number. When no PR opened, it skips the chore. Do not give it `on_failure: "fail"`; maestro refuses that for an `after_pr` chore, because the phase is already merged. The integration never changes how the plan's phases are written, so the worked example below leaves this chore out.
784
785
 
785
786
  ### Worked example
786
787
 
@@ -923,7 +924,7 @@ and this `ai-plans/2026-03-04-bookmark-folders.workflow.json`:
923
924
  "review": {
924
925
  "skill": "thermo-nuclear-review-loop",
925
926
  "when": "review",
926
- "prompt": "Run the thermo-nuclear-review-loop skill over this phase's diff until its reviewer approves it.",
927
+ "prompt": "Run the thermo-nuclear-review-loop skill over this phase's diff until its reviewer approves it. Leave the lane as you found it apart from committed fixes: a mutation experiment runs in a throwaway worktree (git worktree add), never here, and `git status --porcelain` is empty before you state the verdict.",
927
928
  "description": "The phase's code review: one reviewer sub-agent, the implementer fixing and answering it."
928
929
  },
929
930
  "deslop": {
@@ -117,6 +117,7 @@ spawns a new agent) with the answers.
117
117
  {If run_options.full_test_suite = true:} run the **full test suite** `{{TEST_CMD}}` instead of the scoped suite — this phase guards against regressions in untouched code too.
118
118
  {{E2E_OUTER_GATE_LINE}}
119
119
  6. Outer gate fails → return step 2 (fix regression), re-run inner loop, then 5a/5b/5c. **Never** commit, push, or proceed while any gate is red.
120
+ 7. **Shared test resources.** Run one test command at a time in `<WORKROOT>` — parallel tool calls included: every run there shares the worktree's test databases, and two at once block each other. Before starting one, check `docker ps` for a leftover `<compose project>-*-run-*` container from an earlier run. A single test file that runs longer than about two minutes means a lock or a hang: find what holds it, don't rerun it.
120
121
 
121
122
  <!-- include: partials/dispatched-agent.md#NO_NESTED_DISPATCH -->
122
123
  <!-- block-end: INNER_OUTER_LOOP -->
@@ -113,7 +113,9 @@ git -C <integ.workroot> push -u origin plan/{plan-id-kebab}/integ-{P.id}
113
113
  The orchestrator **never edits code**, including merge conflicts. On a conflicted `merge`:
114
114
 
115
115
  1. Capture `git -C <integ.workroot> diff --name-only --diff-filter=U`.
116
- 2. Spawn a **fixer** subagent (the project's `fixer` agent type, at the `agent_models.fixer` model) inside the integration worktree. Its prompt carries: the conflicted paths, both phases' bodies from the plan, both phases' `phase-{id}.md` summaries, and the instruction to resolve for **both** intents — never to `--ours` / `--theirs` a conflict away.
116
+ 2. Spawn a **fixer** subagent (the project's `fixer` agent type, at the `agent_models.fixer` model) inside the integration worktree. Its prompt carries: the conflicted paths, both phases' bodies from the plan, both phases' `phase-{id}.md` summaries, and the instruction to resolve for **both** intents — never to `--ours` / `--theirs` a conflict away. It also carries two rules:
117
+ - **A stale environment is not a merge problem.** When the merged tree's failures are missing or extra columns or tables that no model in the tree defines, the integration worktree's test database belongs to another branch. The fixer changes no code and reports `ENVIRONMENT_STALE`; you recreate the test database there (the test command's fresh-database form) and run the outer gate again, without counting the round.
118
+ - **Check what git merged by itself.** Before the outer gate, syntax-check every file git auto-merged that both sides touched (`python -m py_compile <file>` for Python, the project's type check for the rest). A clean auto-merge of a list both sides appended to has left two openers that never close.
117
119
  3. After the fixer returns, re-run the **outer gate** (`{{BUILD_CMD}}` plus the test scope `run_options.full_test_suite` selects) in the integration worktree. Red → loop back to step 2 with the failure.
118
120
  4. Green → commit the merge, push the branch, and record the conflict + resolution in `waves/wave-{N}.md`.
119
121
 
@@ -27,7 +27,7 @@ Spawn **one** sub-agent at the [resolved model](#resolve-the-reviewer-model), wi
27
27
  - **Use a general-purpose agent, not the project's `reviewer` agent type.** The reviewer applies one standard: the project's `REVIEW.md` when it has one, otherwise the skill's Review Standard. An agent type that carries review rules of its own would hand it two.
28
28
  - **Check that it read the standard**, as the skill says: the verdict opens with a line naming the standard and quoting its first and last lines. Without that line, ask it to read the standard and resend before acting on any finding.
29
29
  - **Keep it for the whole loop**, and continue it on every later pass (for example Claude Code's `SendMessage` to the agent id), so it keeps what it already checked. Where the runtime cannot continue a finished sub-agent, spawn a fresh one each pass and hand it the previous findings, the rejected findings with their counter-evidence, and the settled decisions.
30
- - **It never edits.** Record `git -C <WORKROOT> rev-parse HEAD` and `git -C <WORKROOT> status --porcelain` before each pass and compare them after. A change the reviewer made stops the loop. Ask with `AskUserQuestion` (header `Review`), naming the files it touched: `Stop the review (Recommended)` (leave its changes in place for you to look at, and return `STOPPED`), `Keep the changes and continue` (the implementer verifies them like any other finding).
30
+ - **It never edits, and it leaves the worktree as it found it.** Tell it so in its prompt: a mutation experiment (reverting a file to see a test fail, generating a migration, migrating the lane's database) runs in a throwaway worktree (`git worktree add <tmp> HEAD`, removed afterwards), never in `<WORKROOT>`, and `git -C <WORKROOT> status --porcelain` must be empty before it gives a verdict. Record `git -C <WORKROOT> rev-parse HEAD` and `git -C <WORKROOT> status --porcelain` before each pass and compare them after. A change the reviewer made stops the loop. Ask with `AskUserQuestion` (header `Review`), naming the files it touched: `Stop the review (Recommended)` (leave its changes in place for you to look at, and return `STOPPED`), `Keep the changes and continue` (the implementer verifies them like any other finding).
31
31
 
32
32
  ### 3. Hand the findings to the implementer
33
33
 
@@ -120,7 +120,7 @@ Read `integrations` from `.vinta-ai-workflows.yaml`. Skip this step when nothing
120
120
  pr-review install-skill --claude-dir ai-tools/skills --codex-dir ai-tools/skills --json
121
121
  ```
122
122
 
123
- Both flags point at `ai-tools/skills/`, so the tool writes one copy, `ai-tools/skills/pr-review-canvas/`. Every vendor then reaches that copy through its symlink. The tool also adds `.pr-review/settings.yml` to `.gitignore`.
123
+ Both flags point at `ai-tools/skills/`, so the tool writes one copy, `ai-tools/skills/pr-review-canvas/`. Every vendor then reaches that copy through its symlink. The tool also adds `.pr-review/settings.yml` to `.gitignore`. Then make sure `.gitignore` ignores the whole `.pr-review/` directory, adding the line under the tool's own when it is missing: a maestro lane's canvas chore writes its data there, inside the lane (`--data-dir .pr-review`), because an agent in a lane may not write to the main checkout, and an unignored data directory would land in the phase's commits.
124
124
  3. **Never edit `ai-tools/skills/pr-review-canvas/SKILL.md`.** The tool stamps a body hash into its frontmatter, and `pr-review doctor` and `pr-review serve` flag a modified copy as stale. To refresh the skill, run `pr-review upgrade`, not this flow.
125
125
  4. Run `pr-review doctor`. Its `skill` check must pass, and it reads the copy through `.agents/skills/`, which step 6 always links. Show any other failed checks to the user with the hints the tool prints, most often `gh auth login` / `glab auth login` or a missing `origin` remote. These do not block the bootstrap.
126
126
  5. Do not write `pr-review.config.yml`. Every key in it is optional, and the defaults are correct. Mention that the team can copy the tool's example config to change sharing or mark high-risk paths.