vinta-ai-workflows 0.7.0-alpha36 → 0.7.0-alpha37
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 +128 -0
- package/package.json +1 -1
- package/schemas/vinta-ai-workflows-config.v1.schema.json +123 -0
- package/schemas/workflow.v1.schema.json +80 -0
- package/skills/vinta-bootstrap-ai-tools/SKILL.md +19 -0
- package/skills/vinta-bootstrap-ai-tools/resources/stacks/django/notes.md +22 -1
- package/skills/vinta-derive-skills/resources/foundation-skills/plan-feature/SKILL.md +7 -6
- package/skills/vinta-derive-skills/resources/plan-execution/partials/implementer-prompt.md +1 -0
- package/skills/vinta-derive-skills/resources/plan-execution/partials/parallel-lanes.md +3 -1
- package/skills/vinta-derive-skills/resources/plan-execution/partials/review-loop.md +1 -1
- package/skills/vinta-install-ai-tools-setup/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,7 @@ 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-alpha37 on 2026-10-20 -->
|
|
10
11
|
<!-- pre-release: 0.7.0-alpha36 on 2026-10-18 -->
|
|
11
12
|
<!-- pre-release: 0.7.0-alpha35 on 2026-10-16 -->
|
|
12
13
|
<!-- pre-release: 0.7.0-alpha34 on 2026-10-16 -->
|
|
@@ -36,6 +37,33 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
36
37
|
|
|
37
38
|
### Added
|
|
38
39
|
|
|
40
|
+
- **`vinta-ai-maestro`: `post_merge`.** A workflow (or `maestro.post_merge`)
|
|
41
|
+
may declare a command that runs in the integration worktree after git
|
|
42
|
+
merges a branch by itself, with `{files}` set to the files both sides
|
|
43
|
+
changed (for example `py_compile` plus `ruff check --fix` and
|
|
44
|
+
`ruff format`). Its changes are committed on top of the merge. If it fails,
|
|
45
|
+
the merge stays as git made it and the merged tree's gates judge it. Each
|
|
46
|
+
run is journalled as `post_merge_result`.
|
|
47
|
+
- **`vinta-ai-maestro`: `ci_only` records CI checks deliberately left to
|
|
48
|
+
CI.** It lives in a plan or in `maestro.ci_only` in
|
|
49
|
+
`.vinta-ai-workflows.yaml`. Each entry is a CI command with the reason no
|
|
50
|
+
gate runs it. `doctor`'s CI-coverage check stops warning about those
|
|
51
|
+
commands and counts them as decisions.
|
|
52
|
+
- **`vinta-ai-maestro`: `queue_timeout_s`, and the pool queue in `status`.**
|
|
53
|
+
A gate's `timeout_s` counts only its command's runtime, and a regression
|
|
54
|
+
test now pins that down. The optional `queue_timeout_s` bounds the wait
|
|
55
|
+
for the gate's pools: a gate that waits too long fails with how long it
|
|
56
|
+
waited and who held the pool. `status` shows each pool's queue in order,
|
|
57
|
+
for example `test-suite: p3b (holds 4m) → p5b (waits 22m) → p5c (waits 3m)`.
|
|
58
|
+
- **`vinta-ai-maestro`: a gate can run differently in a lane with no scope
|
|
59
|
+
cache.** A command gate may declare `scope_cache` (for example
|
|
60
|
+
`apps/api/.testmondata`) and `cold_cmd`. When a cache file is missing, the
|
|
61
|
+
phase gate runs `cold_cmd` (default `cmd`) instead of a scoped form that
|
|
62
|
+
would run every test serially. `gate_started` now records which form ran
|
|
63
|
+
and why. The new `project.lane_seed` option copies such ignored caches into
|
|
64
|
+
a lane from its dependency's lane, the integration worktree or the main
|
|
65
|
+
checkout, journalled as `lane_seeded`. Both fields are also accepted in
|
|
66
|
+
`maestro.gates.<type>`.
|
|
39
67
|
- **`amend-plan` amends a live `vinta-ai-maestro` run instead of rewriting
|
|
40
68
|
its branches.** When a run of the plan is running, paused, interrupted or
|
|
41
69
|
failed, the skill asks whether to amend it, then edits the plan and the
|
|
@@ -842,6 +870,36 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
842
870
|
|
|
843
871
|
### Changed
|
|
844
872
|
|
|
873
|
+
- **`vinta-ai-maestro`: the coordinator's wakes are more specific.**
|
|
874
|
+
- The `gate_cost` wake lists uncached minutes per phase and flags any
|
|
875
|
+
single run of 20 minutes or more as probably a cold lane. Runs of a
|
|
876
|
+
gate's `cold_cmd` no longer count toward the total.
|
|
877
|
+
- A failed attempt does not wake the coordinator when it already acted on
|
|
878
|
+
that phase during the attempt.
|
|
879
|
+
- A session that ends with shell calls that never returned is journalled
|
|
880
|
+
as `tool_calls_unfinished` and wakes the coordinator.
|
|
881
|
+
- `amend` lists the affected phases that are running or waiting, which pick
|
|
882
|
+
up the change only once they stop.
|
|
883
|
+
- **Bootstrap asks about maestro's test gate in lanes.** In the maestro
|
|
884
|
+
defaults (C.11), bootstrap now asks:
|
|
885
|
+
- whether a cache-based scoped run (`pytest --testmon`) should fall back to
|
|
886
|
+
the full parallel suite in a lane with no cache. This emits
|
|
887
|
+
`scope_cache`, `cold_cmd` and `project.lane_seed`, and proposes a
|
|
888
|
+
`test_changed` target that falls back when `.testmondata` is missing.
|
|
889
|
+
It also explains that `--testmon` with `-n` trades correctness for speed.
|
|
890
|
+
- whether a suite that reuses its database should get a fresh-database form
|
|
891
|
+
for the integration worktree. This emits `fresh_cmd` and
|
|
892
|
+
`tuning.allowed_flags: ['--create-db']`.
|
|
893
|
+
- for each CI check with no gate, whether to add a gate or record it under
|
|
894
|
+
`maestro.ci_only` with a reason.
|
|
895
|
+
- **Django stack notes** cover database reuse and testmon in lanes, keeping
|
|
896
|
+
the virtual environment out of a bind-mounted worktree
|
|
897
|
+
(`UV_PROJECT_ENVIRONMENT=/opt/venv`, `user: "${UID}:${GID}"`), registries
|
|
898
|
+
every phase appends to, and drf-spectacular `ENUM_NAME_OVERRIDES` for new
|
|
899
|
+
choice fields.
|
|
900
|
+
- **`plan-feature`** names the enum for same-named choice fields that two
|
|
901
|
+
phases expose, lists the usual append-only registries, and records CI
|
|
902
|
+
checks that cannot run locally under `ci_only`.
|
|
845
903
|
- **`plan-feature`: a rename of a shared symbol is an interface change.** A
|
|
846
904
|
phase that renames something a same-wave sibling also uses does it in the
|
|
847
905
|
foundation phase or keeps the old name as an alias until the wave lands.
|
|
@@ -1275,6 +1333,76 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1275
1333
|
|
|
1276
1334
|
### Fixed
|
|
1277
1335
|
|
|
1336
|
+
- **`vinta-ai-maestro`: a review chore can no longer leave the lane dirty.**
|
|
1337
|
+
Whatever a review leaves uncommitted, such as a mutation experiment's
|
|
1338
|
+
reverted migration or a generated file, is set aside under a
|
|
1339
|
+
`refs/vinta-ai-maestro/wip/` ref. The lane is then reset to `HEAD` before
|
|
1340
|
+
the gates run, and `review_left_lane_dirty` is journalled. The implementer
|
|
1341
|
+
and conflict-fixer prompts now say to run one test command at a time per
|
|
1342
|
+
lane, to check `docker ps` for leftover run containers, and to treat a
|
|
1343
|
+
single test file that runs past two minutes as a lock or hang.
|
|
1344
|
+
- **Plan-execution prompts and the review-canvas chore.** The implementer
|
|
1345
|
+
prompt adds the same shared-test-resource rules. The conflict fixer
|
|
1346
|
+
reports `ENVIRONMENT_STALE` (and changes no code) when the merged tree's
|
|
1347
|
+
failures are columns or tables no model defines, and syntax-checks the
|
|
1348
|
+
files git auto-merged. The reviewer leaves the worktree as it found it:
|
|
1349
|
+
mutation experiments run in a throwaway worktree. `plan-feature`'s review
|
|
1350
|
+
chore prompt says so too. Its review-canvas chore passes
|
|
1351
|
+
`--data-dir .pr-review` inside the lane, and setup ignores `.pr-review/`.
|
|
1352
|
+
- **`vinta-ai-maestro`: conflicts in generated files no longer fail a
|
|
1353
|
+
merge.** After each conflict-fixer round, codegen outputs that the round
|
|
1354
|
+
left conflicted, or staged with conflict markers, are settled again and
|
|
1355
|
+
regenerated, even when the fixer aborted and redid the merge. The fixer's
|
|
1356
|
+
prompt now matches: with codegen declared, generated files are the
|
|
1357
|
+
integrator's; with none declared, every conflicted file is the fixer's. An
|
|
1358
|
+
unresolved conflict over files that look generated suggests declaring a
|
|
1359
|
+
`codegen` step.
|
|
1360
|
+
- **`vinta-ai-maestro`: `exec` streams, accepts `--reason`, and can be
|
|
1361
|
+
cancelled.** The daemon used to read each answer whole before sending
|
|
1362
|
+
it, so an `exec` queued behind the integration worktree sent nothing, and
|
|
1363
|
+
the client said the run was "not live" while the command still ran later.
|
|
1364
|
+
The answer now streams. The client says who it is queued behind, and
|
|
1365
|
+
ending the client cancels the command, whether it is queued or running.
|
|
1366
|
+
`--reason` is journalled. `exec --detach`, `exec list <run>` and
|
|
1367
|
+
`exec cancel <run> <id>` are new. Every release of the integration
|
|
1368
|
+
worktree lets the next waiter in.
|
|
1369
|
+
- **`vinta-ai-maestro`: a paused phase no longer takes the integration
|
|
1370
|
+
worktree.** A paused node waiting in the integration queue keeps its place
|
|
1371
|
+
but is passed over, and is let in once it is resumed. This is journalled
|
|
1372
|
+
as `node_wait` with state `paused`. `status` shows a node as paused as soon
|
|
1373
|
+
as the pause is sent, not only once it takes effect.
|
|
1374
|
+
- **`vinta-ai-maestro`: a lane that cannot be recycled no longer fails the
|
|
1375
|
+
phase handed to it.** A recycle that fails (outside the `preserve` stage)
|
|
1376
|
+
takes that lane out of the run and provisions a fresh one, `<lane>-r<N>`,
|
|
1377
|
+
and the phase runs there with nothing charged. The old lane stays on disk
|
|
1378
|
+
and is journalled as `lane_unhealthy`, with any paths git could not remove
|
|
1379
|
+
and their owner's uid. `status` shows it.
|
|
1380
|
+
- **`vinta-ai-maestro`: a provider or network outage no longer fails
|
|
1381
|
+
phases.** A turn that ends on an infrastructure error (DNS or connection
|
|
1382
|
+
failures, `upstream-unavailable`, HTTP 5xx or 529, an expired or rejected
|
|
1383
|
+
login) parks the harness with backoff and re-runs the phase when the
|
|
1384
|
+
harness answers. Nothing is charged to its attempts, retries or
|
|
1385
|
+
"N failures in a row" count. A lapsed login is asked once per harness, not
|
|
1386
|
+
once per phase. Each outage is journalled as `outage_started` /
|
|
1387
|
+
`outage_ended`, and `status` shows a line for every open one. The
|
|
1388
|
+
coordinator's digest no longer lists error lines from before a phase's
|
|
1389
|
+
last session that ended well.
|
|
1390
|
+
- **`vinta-ai-maestro`: integration gates no longer run against a test
|
|
1391
|
+
database left by another branch.** A command gate may declare `fresh_cmd`
|
|
1392
|
+
(also in `maestro.gates.<type>`), the same check from a fresh database (for
|
|
1393
|
+
example `pytest --create-db`). In the integration worktree it runs instead
|
|
1394
|
+
of `cmd` in four cases: when the tree moved anywhere but forward since the
|
|
1395
|
+
gate last ran there, on the gate's first run there, once more after a red
|
|
1396
|
+
run whose output shows missing or extra columns or relations, and after a
|
|
1397
|
+
conflict fixer ends its turn on `ENVIRONMENT_STALE`. That last round is
|
|
1398
|
+
given back. Each fresh run is journalled as `integration_fresh_state`.
|
|
1399
|
+
- **`vinta-ai-maestro`: a killed gate no longer leaves its Docker container
|
|
1400
|
+
running.** A timed-out or cancelled gate, `with` command or `exec` also
|
|
1401
|
+
removes the one-off `docker compose run` containers its lane's compose
|
|
1402
|
+
project started since the command began, before the pools, lease or
|
|
1403
|
+
integration worktree it held are released. A sweep every five minutes
|
|
1404
|
+
removes one-off containers in lanes no phase or `exec` is using, journalled
|
|
1405
|
+
as `orphan_containers_stopped`. Without docker, nothing changes.
|
|
1278
1406
|
- **`vinta-ai-maestro`: the integrator no longer stages untracked files into
|
|
1279
1407
|
a merge resolution.** It stages tracked paths and the merge's own paths
|
|
1280
1408
|
instead of `git add --all`, so a gate's leftovers in the integration
|
package/package.json
CHANGED
|
@@ -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
|
|
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.
|