vinta-ai-workflows 0.7.0-alpha9 → 0.7.0
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 +473 -621
- package/README.md +33 -19
- package/package.json +4 -1
- package/schemas/README.md +11 -3
- package/schemas/ai-models.v1.schema.json +5 -0
- package/schemas/interview-answers.v1.schema.json +122 -0
- package/schemas/interview-round.v1.schema.json +346 -0
- package/schemas/plan-review.v1.schema.json +342 -0
- package/schemas/postmortem.v1.schema.json +848 -3
- package/schemas/prs-context-comments.v1.schema.json +1 -1
- package/schemas/prs-context-frontmatter.v1.schema.json +18 -7
- package/schemas/vinta-ai-workflows-config.v1.schema.json +677 -18
- package/schemas/workflow.v1.schema.json +616 -26
- package/skills/vinta-analyze-codebase/SKILL.md +5 -0
- package/skills/vinta-bootstrap-ai-tools/SKILL.md +154 -31
- package/skills/vinta-bootstrap-ai-tools/resources/stacks/README.md +1 -1
- package/skills/vinta-bootstrap-ai-tools/resources/stacks/django/notes.md +36 -1
- package/skills/vinta-bootstrap-ai-tools/resources/worktree-command/README.md +85 -0
- package/skills/vinta-bootstrap-ai-tools/resources/worktree-command/lib-template.sh +160 -0
- package/skills/vinta-bootstrap-ai-tools/resources/worktree-command/prepare-template.sh +250 -0
- package/skills/vinta-bootstrap-ai-tools/resources/worktree-command/teardown-template.sh +96 -0
- package/skills/vinta-derive-skills/SKILL.md +23 -15
- package/skills/vinta-derive-skills/resources/dependency-license-block.md +8 -9
- package/skills/vinta-derive-skills/resources/foundation-skills/add-one-off-script/SKILL.md +1 -1
- package/skills/vinta-derive-skills/resources/foundation-skills/create-qa-use-cases/SKILL.md +3 -3
- package/skills/vinta-derive-skills/resources/foundation-skills/create-spec/SKILL.md +21 -2
- package/skills/vinta-derive-skills/resources/foundation-skills/handoff/SKILL.md +3 -3
- package/skills/vinta-derive-skills/resources/foundation-skills/interview-ui/SKILL.md +121 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/interview-ui/resources/examples/docs/D4-concurrency.md +28 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/interview-ui/resources/examples/round-01.json +60 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/open-pr-from-context/SKILL.md +1 -1
- package/skills/vinta-derive-skills/resources/foundation-skills/open-pr-from-context/scripts/open-pr.sh +34 -7
- package/skills/vinta-derive-skills/resources/foundation-skills/plan-feature/SKILL.md +363 -142
- package/skills/vinta-derive-skills/resources/foundation-skills/plan-feature/resources/ai-models.yaml +19 -13
- package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/SKILL.md +39 -21
- package/skills/vinta-derive-skills/resources/foundation-skills/thermo-nuclear-code-quality-review/SKILL.md +8 -6
- package/skills/vinta-derive-skills/resources/foundation-skills/thermo-nuclear-review-loop/SKILL.md +181 -0
- package/skills/vinta-derive-skills/resources/plan-execution/README.md +40 -7
- package/skills/vinta-derive-skills/resources/plan-execution/partials/agent-models.md +14 -35
- package/skills/vinta-derive-skills/resources/plan-execution/partials/commit-strategy/stacked.md +21 -0
- package/skills/vinta-derive-skills/resources/plan-execution/partials/dispatched-agent.md +31 -0
- package/skills/vinta-derive-skills/resources/plan-execution/partials/implementer-prompt.md +67 -6
- package/skills/vinta-derive-skills/resources/plan-execution/partials/maestro-live-run.md +44 -0
- package/skills/vinta-derive-skills/resources/plan-execution/partials/model-pick.md +9 -3
- package/skills/vinta-derive-skills/resources/plan-execution/partials/parallel-lanes.md +95 -50
- package/skills/vinta-derive-skills/resources/plan-execution/partials/pr-context.md +4 -2
- package/skills/vinta-derive-skills/resources/plan-execution/partials/relay-questions.md +13 -0
- package/skills/vinta-derive-skills/resources/plan-execution/partials/review-loop.md +81 -0
- package/skills/vinta-derive-skills/resources/plan-execution/partials/worktree-seam.md +86 -17
- package/skills/vinta-derive-skills/resources/plan-execution/shell/amend-plan-template.md +35 -16
- package/skills/vinta-derive-skills/resources/plan-execution/shell/implement-phase-template.md +12 -1
- package/skills/vinta-derive-skills/resources/plan-execution/shell/implement-plan-template.md +49 -33
- package/skills/vinta-derive-skills/resources/plan-execution/shell/integrate-phase-template.md +2 -1
- package/skills/vinta-derive-skills/resources/plan-execution/shell/review-phase-template.md +19 -27
- package/skills/vinta-derive-skills/resources/prs-context-template.md +5 -4
- package/skills/vinta-derive-skills/resources/systematic-debugging-mcp-tools.md +1 -1
- package/skills/vinta-derive-skills/resources/systematic-debugging-template.md +8 -8
- package/skills/vinta-derive-skills/resources/write-unit-test-packs/stacks/django.md +4 -1
- package/skills/vinta-derive-skills/resources/write-unit-test-template.md +3 -0
- package/skills/vinta-derive-subagents/SKILL.md +25 -9
- package/skills/vinta-install-ai-tools-setup/SKILL.md +29 -2
- package/skills/vinta-migrate-plans-specs/SKILL.md +7 -7
- package/skills/vinta-sync-ai-tools/SKILL.md +19 -2
- package/skills/vinta-update-project-skills/SKILL.md +1 -0
- package/skills/vinta-write-agents-md/SKILL.md +10 -6
- package/skills/vinta-write-agents-md/resources/asking-the-human.md +56 -0
- package/skills/vinta-derive-skills/resources/plan-execution/partials/review-layers.md +0 -101
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://github.com/vintasoftware/vinta-ai-workflows/schemas/workflow.v1.schema.json",
|
|
4
4
|
"title": "vinta-ai-maestro workflow",
|
|
5
|
-
"description": "An executable plan: the phase DAG, the pipelines phases run through, the capacity pools they contend for, and the gates they must pass. Lives at `ai-plans/<feature-kebab>.workflow.json` in a target project, emitted by `plan-feature` alongside the human-readable plan and executed by `vinta-ai-maestro`. GENERATED from packages/vinta-ai-maestro/src/types.ts — edit there, not here. See schemas/README.md for versioning rules.",
|
|
5
|
+
"description": "An executable plan: the phase DAG, the pipelines phases run through, the capacity pools they contend for, and the gates they must pass. Layered over the project’s own configuration (`.vinta-ai-workflows.yaml`) when it is loaded. Lives at `ai-plans/<feature-kebab>.workflow.json` in a target project, emitted by `plan-feature` alongside the human-readable plan and executed by `vinta-ai-maestro`. GENERATED from packages/vinta-ai-maestro/src/types.ts — edit there, not here. See schemas/README.md for versioning rules.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
8
|
"$schema": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"plan_context_refs": {
|
|
28
28
|
"default": [],
|
|
29
|
-
"description": "Sections of the plan that bound every phase rather than any one of them — its Goals + Non-goals and its Guiding Decisions. Each is a file-and-anchor reference in the same form as `prompt_ref` (`ai-plans/PLAN.md#1-goals`), resolved by the same resolver and handed to the implementer
|
|
29
|
+
"description": "Sections of the plan that bound every phase rather than any one of them — its Goals + Non-goals and its Guiding Decisions. Each is a file-and-anchor reference in the same form as `prompt_ref` (`ai-plans/PLAN.md#1-goals`), resolved by the same resolver and handed to the implementer verbatim, marked as plan-level. Empty — the default — means the prompts carry the phase brief alone.",
|
|
30
30
|
"type": "array",
|
|
31
31
|
"items": {
|
|
32
32
|
"type": "string",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"base_branch": {
|
|
37
|
+
"description": "What dependency-free nodes branch from, and what the plan PR targets. Absent takes `project.default_branch` from `.vinta-ai-workflows.yaml`.",
|
|
37
38
|
"type": "string",
|
|
38
|
-
"minLength": 1
|
|
39
|
-
"description": "What dependency-free nodes branch from."
|
|
39
|
+
"minLength": 1
|
|
40
40
|
},
|
|
41
41
|
"project": {
|
|
42
42
|
"type": "object",
|
|
@@ -106,6 +106,16 @@
|
|
|
106
106
|
"connection_url_var": {
|
|
107
107
|
"type": "string",
|
|
108
108
|
"minLength": 1
|
|
109
|
+
},
|
|
110
|
+
"createdb_cmd": {
|
|
111
|
+
"description": "Replaces the `createdb` binary name (default `createdb`) in every command that creates a lane database. A trusted command prefix, unquoted, like `migrate_cmd`; the `-h`/`-p`/`-U` flags derived from `server_url` still follow it. Only meaningful for `external`. For a Postgres in a container whose password the host cannot supply: `docker compose exec -T db createdb` — the official image trusts connections made from inside the container, so no password is needed there.",
|
|
112
|
+
"type": "string",
|
|
113
|
+
"minLength": 1
|
|
114
|
+
},
|
|
115
|
+
"dropdb_cmd": {
|
|
116
|
+
"description": "Replaces the `dropdb` binary name (default `dropdb`), exactly as `createdb_cmd` does for `createdb`. Set both together: `docker compose exec -T db dropdb`.",
|
|
117
|
+
"type": "string",
|
|
118
|
+
"minLength": 1
|
|
109
119
|
}
|
|
110
120
|
},
|
|
111
121
|
"required": [
|
|
@@ -175,6 +185,16 @@
|
|
|
175
185
|
"connection_url_var": {
|
|
176
186
|
"type": "string",
|
|
177
187
|
"minLength": 1
|
|
188
|
+
},
|
|
189
|
+
"createdb_cmd": {
|
|
190
|
+
"description": "Replaces the `createdb` binary name (default `createdb`) in every command that creates a lane database. A trusted command prefix, unquoted, like `migrate_cmd`; the `-h`/`-p`/`-U` flags derived from `server_url` still follow it. Only meaningful for `external`. For a Postgres in a container whose password the host cannot supply: `docker compose exec -T db createdb` — the official image trusts connections made from inside the container, so no password is needed there.",
|
|
191
|
+
"type": "string",
|
|
192
|
+
"minLength": 1
|
|
193
|
+
},
|
|
194
|
+
"dropdb_cmd": {
|
|
195
|
+
"description": "Replaces the `dropdb` binary name (default `dropdb`), exactly as `createdb_cmd` does for `createdb`. Set both together: `docker compose exec -T db dropdb`.",
|
|
196
|
+
"type": "string",
|
|
197
|
+
"minLength": 1
|
|
178
198
|
}
|
|
179
199
|
},
|
|
180
200
|
"required": [
|
|
@@ -231,6 +251,15 @@
|
|
|
231
251
|
"additionalProperties": false,
|
|
232
252
|
"description": "The project’s own command lines, handed to every agent verbatim. A fixed vocabulary rather than free-form, because the prompt has to be able to say which one step 3 of the working instructions means. Declaring these is how an agent stops guessing: a project whose suite only runs inside a container, or only against a lane-specific database, has no way of telling one otherwise, and `pytest` is a plausible guess that is wrong in exactly that project."
|
|
233
253
|
},
|
|
254
|
+
"hooks": {
|
|
255
|
+
"default": "run",
|
|
256
|
+
"description": "Whether the repository’s git hooks run on commits made inside a lane. `run` is the default and is what a developer’s own checkout does. `skip` points the lane at an empty `core.hooksPath`, for the case this exists for: a `language: system` pre-commit chain that, in a worktree that has never been committed in, builds a virtualenv per lane before it will let a commit through — observed as four failed commit attempts, one of them a two-minute timeout, and a 510 MB `.venv` per lane. The gates still run; this only stops each lane paying a whole-environment install to make a commit. The integration worktree follows the same setting: the merge commits maestro makes there — a resolved conflict, a wave — run the hooks under `run` and skip them under `skip`, exactly as a lane’s commits do. A hook that refuses one of those commits is asked about (commit, commit --no-verify, abort) rather than left mid-merge; a hook that needs a gitignored file needs that file in `env_files`.",
|
|
257
|
+
"type": "string",
|
|
258
|
+
"enum": [
|
|
259
|
+
"run",
|
|
260
|
+
"skip"
|
|
261
|
+
]
|
|
262
|
+
},
|
|
234
263
|
"services": {
|
|
235
264
|
"default": {},
|
|
236
265
|
"description": "Shared servers each lane gets its own namespace inside, by id. One postgres, one redis, one rabbit on the machine, with a database / db index / vhost per lane — which is the difference between six lanes being viable on a laptop and not. The alternative a project falls into without this is booting a server per lane, or sharing one with no isolation at all.",
|
|
@@ -322,10 +351,38 @@
|
|
|
322
351
|
"additionalProperties": false,
|
|
323
352
|
"description": "How a lane’s compose stack is isolated past `COMPOSE_PROJECT_NAME`."
|
|
324
353
|
},
|
|
354
|
+
"prepare_cmd": {
|
|
355
|
+
"description": "Run in the repository root **before anything else a run does** — before the preflight, before the template databases, before the first worktree. Its job is to make the shared servers this project’s lanes connect to reachable: `docker compose up -d --wait db redis` for a stack in the root checkout, `brew services start postgresql` for a host install, nothing at all for a project whose lanes boot their own. `setup_cmd` cannot do this — it runs per lane, long after the template database has been created on a server that had to be up already. MUST be idempotent: it runs again before every lane recycle, which is what restores a server that died mid-run. Note that `--wait` only waits for services that declare a `healthcheck`; without one it returns as soon as the container is started, and `createdb` races the server.",
|
|
356
|
+
"type": "string",
|
|
357
|
+
"minLength": 1
|
|
358
|
+
},
|
|
325
359
|
"setup_cmd": {
|
|
326
360
|
"description": "The project’s own lane-setup command, run inside each freshly provisioned lane with that lane’s environment applied. The escape hatch for whatever a project needs that no field here describes. It MUST be idempotent: it runs again every time the lane is recycled for another phase.",
|
|
327
361
|
"type": "string",
|
|
328
362
|
"minLength": 1
|
|
363
|
+
},
|
|
364
|
+
"reset_cmd": {
|
|
365
|
+
"description": "Run inside a lane when it is recycled for another phase, and only then — never on a freshly provisioned lane. Runs with the lane’s environment applied, after maestro has reset the worktree, the env files, the databases and the services, and before `setup_cmd`. For what a phase leaves behind that the clean does not reach: a cache outside the worktree, a `recycle_keep` directory that needs pruning rather than deleting.",
|
|
366
|
+
"type": "string",
|
|
367
|
+
"minLength": 1
|
|
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
|
+
},
|
|
378
|
+
"recycle_keep": {
|
|
379
|
+
"default": [],
|
|
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.",
|
|
381
|
+
"type": "array",
|
|
382
|
+
"items": {
|
|
383
|
+
"type": "string",
|
|
384
|
+
"minLength": 1
|
|
385
|
+
}
|
|
329
386
|
}
|
|
330
387
|
},
|
|
331
388
|
"required": [
|
|
@@ -338,6 +395,7 @@
|
|
|
338
395
|
"type": "object",
|
|
339
396
|
"properties": {
|
|
340
397
|
"harness": {
|
|
398
|
+
"default": "claude-code",
|
|
341
399
|
"type": "string",
|
|
342
400
|
"enum": [
|
|
343
401
|
"claude-code",
|
|
@@ -350,10 +408,155 @@
|
|
|
350
408
|
"minLength": 1
|
|
351
409
|
},
|
|
352
410
|
"pipeline": {
|
|
411
|
+
"default": "standard-phase",
|
|
353
412
|
"type": "string",
|
|
354
413
|
"minLength": 1,
|
|
355
414
|
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
356
415
|
},
|
|
416
|
+
"gates": {
|
|
417
|
+
"description": "The gates every phase must pass unless it names its own. Resolved into each node when the workflow is loaded, with the same override rule as `chores`: a node’s own list — `[]` included — replaces this one.",
|
|
418
|
+
"type": "array",
|
|
419
|
+
"items": {
|
|
420
|
+
"type": "string",
|
|
421
|
+
"minLength": 1,
|
|
422
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
"allow_ungated_phases": {
|
|
426
|
+
"default": false,
|
|
427
|
+
"description": "Whether a phase that resolves to no gates at all is acceptable. Off — the default — `validate` refuses the workflow and `doctor` warns, because a phase with no gates passes its gate step vacuously: an observed run finished twelve phases without a single check because every node carried `gates: []` where its author meant \"the project’s\". Set it only for a plan whose phases genuinely have nothing to check.",
|
|
428
|
+
"type": "boolean"
|
|
429
|
+
},
|
|
430
|
+
"gate_scope": {
|
|
431
|
+
"default": "scoped",
|
|
432
|
+
"description": "`scoped` — the default — runs a gate’s `scoped_cmd` on a phase’s gates up to and during its review, and its full `cmd` once the review approves (`verify`) and on every merged tree in the integration worktree. `full` runs `cmd` everywhere.",
|
|
433
|
+
"type": "string",
|
|
434
|
+
"enum": [
|
|
435
|
+
"scoped",
|
|
436
|
+
"full"
|
|
437
|
+
]
|
|
438
|
+
},
|
|
439
|
+
"substitution": {
|
|
440
|
+
"default": "up_one_tier",
|
|
441
|
+
"description": "How far above the member the plan named a phase may be moved when that member is busy or another holds a warm session. `same_tier` — only a peer at the same tier; a phase waits rather than run dearer. `up_one_tier` — the default — also the next tier up the roster has, when nobody at the planned tier is free; a warm session never moves a phase up. `any` — any tier above, and a warm session above the plan may take it to avoid a cold start. One observed run moved 23 phases from Tier 3 to a Tier 4 member whose credits then ran out, and nothing showed it.",
|
|
442
|
+
"type": "string",
|
|
443
|
+
"enum": [
|
|
444
|
+
"same_tier",
|
|
445
|
+
"up_one_tier",
|
|
446
|
+
"any"
|
|
447
|
+
]
|
|
448
|
+
},
|
|
449
|
+
"escalate_after_rejections": {
|
|
450
|
+
"default": 3,
|
|
451
|
+
"description": "Review rejections a phase may take on one desk before it moves up a crew tier. Every review that does not approve counts, across attempts; at the Nth the scheduler amends the run, moving the phase to the cheapest member of the next tier up, journalled as a `workflow_amended` row by `scheduler` with the reason and the count. The new desk takes the phase from its next attempt, and the count starts again there. Nothing happens at the top tier, or in a workflow with no roster. `0` turns it off. One observed run left a Tier 1 phase rejected 16 times over six hours before a person moved it.",
|
|
452
|
+
"type": "integer",
|
|
453
|
+
"minimum": 0,
|
|
454
|
+
"maximum": 9007199254740991
|
|
455
|
+
},
|
|
456
|
+
"pull_requests": {
|
|
457
|
+
"default": "phase",
|
|
458
|
+
"description": "Which pull requests a run opens. `phase` — the default — one per phase, one per integration branch, and the plan PR that lands them all: the phase PRs are review units stacked in merge order. `plan` opens the plan PR alone, from the last wave branch, which already holds every phase and every conflict resolution; review happens there. One observed ten-phase refactor opened fifteen PRs that nobody was going to merge. Under `plan` a phase has no PR of its own, so its `after_pr` chores are skipped.",
|
|
459
|
+
"type": "string",
|
|
460
|
+
"enum": [
|
|
461
|
+
"phase",
|
|
462
|
+
"plan"
|
|
463
|
+
]
|
|
464
|
+
},
|
|
465
|
+
"max_spare_crew": {
|
|
466
|
+
"default": 2,
|
|
467
|
+
"description": "Crew members a run may take on beyond the roster. When the member a phase was planned for is busy, the phase does not borrow a member another pending phase is planned for, and does not wait: it gets a new member at the planned member’s tier and model, with a worktree of its own provisioned there and then. A member nobody plans for is borrowed as before. The spare stays on the roster for the rest of the run, is never planned for, and covers later phases at its tier. `retry with <member>` takes a spare in that member’s image the same way when the member is needed elsewhere. A worktree, its databases and its compose stack each, so this is bounded; `0` keeps the roster to the plan’s and restores waiting and covering. One observed run left a phase on the critical path idle for 17 minutes while a retry held the member it was planned for.",
|
|
468
|
+
"type": "integer",
|
|
469
|
+
"minimum": 0,
|
|
470
|
+
"maximum": 9007199254740991
|
|
471
|
+
},
|
|
472
|
+
"verify_gates": {
|
|
473
|
+
"default": "full",
|
|
474
|
+
"description": "What `standard-phase`’s `verify` runs once the review approves. `full` — the default — runs every gate’s whole `cmd` on the phase branch, so a PR never carries a suite only a scoped run saw; a red full run goes back to a fixer and the review. `scoped` keeps the scoped form there too, for a suite too slow to run once per phase — the merged-tree gates are then the first full run. No effect under `gate_scope: full`.",
|
|
475
|
+
"type": "string",
|
|
476
|
+
"enum": [
|
|
477
|
+
"full",
|
|
478
|
+
"scoped"
|
|
479
|
+
]
|
|
480
|
+
},
|
|
481
|
+
"base_gates": {
|
|
482
|
+
"default": "full",
|
|
483
|
+
"description": "Whether a multi-dependency phase’s base (`integ-<id>`, its dependencies merged) is gated before the phase starts on it. `full` — the default — runs every command gate its dependencies declare, whole `cmd`, on the merged base, cached on its tree; red fails the phase’s setup. `off` starts the phase on the merged base unchecked.",
|
|
484
|
+
"type": "string",
|
|
485
|
+
"enum": [
|
|
486
|
+
"full",
|
|
487
|
+
"off"
|
|
488
|
+
]
|
|
489
|
+
},
|
|
490
|
+
"wave_gates": {
|
|
491
|
+
"default": "every",
|
|
492
|
+
"description": "When the phases’ gates run in full on a merged wave, in the integration worktree. Each phase passes its gates on its own branch, and two parallel phases can each be green and disagree with each other — a sender and a receiver of one signal, say — which only a merged tree shows. `every` — the default — runs the wave’s phases’ gates on every wave, so the next wave builds on a passing tree and a disagreement is caught at the wave that introduced it, and every gate of the plan once on the last wave before the plan PR opens. It costs one full suite per wave. `final` runs the last wave’s and any wave whose merge needed a conflict resolution only. `off` runs none of these. Under `gate_scope: scoped` the full form of a narrowed gate still runs on each wave whatever this says: that is where the suite the phases skipped is paid back.",
|
|
493
|
+
"type": "string",
|
|
494
|
+
"enum": [
|
|
495
|
+
"off",
|
|
496
|
+
"final",
|
|
497
|
+
"every"
|
|
498
|
+
]
|
|
499
|
+
},
|
|
500
|
+
"chores": {
|
|
501
|
+
"default": [],
|
|
502
|
+
"description": "The chores every phase runs unless it names its own. Most chores are run-wide — a comment pass, a changelog entry — and repeating them on every node is how one phase ends up quietly missing one.",
|
|
503
|
+
"type": "array",
|
|
504
|
+
"items": {
|
|
505
|
+
"type": "string",
|
|
506
|
+
"minLength": 1,
|
|
507
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
"model_fallbacks": {
|
|
511
|
+
"default": {},
|
|
512
|
+
"description": "Model id → the model to run instead once the first is out of quota (§6.1). A `quota` refusal on a model listed here does not park the harness: the spawn is retried at once on the fallback, and every later spawn of that model on the same harness goes straight to the fallback until the vendor’s stated reset, or for the rest of the run when it stated none. Chains are followed; a fallback refused for quota too means the account is out, not the model, and the harness parks as it would have.",
|
|
513
|
+
"type": "object",
|
|
514
|
+
"propertyNames": {
|
|
515
|
+
"type": "string",
|
|
516
|
+
"minLength": 1
|
|
517
|
+
},
|
|
518
|
+
"additionalProperties": {
|
|
519
|
+
"type": "string",
|
|
520
|
+
"minLength": 1
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
"harness_fallbacks": {
|
|
524
|
+
"default": {},
|
|
525
|
+
"description": "Harness id → where the run moves once that harness is out of quota and has no model fallback left (§6.1). Instead of parking every phase on it until the window resets, the run switches: the turn that was refused runs again at once on the fallback, at the same pipeline step, and every later turn that would have run on the harness runs there — phases in a turn move when it ends. A moved phase starts a fresh session with a handoff (the previous agent’s last report, its work in the lane, the red gate). One switch per harness; a fallback that runs out too parks as it would have. One observed run moved five phases off an account out of credits by aborting and retrying each by hand.",
|
|
526
|
+
"type": "object",
|
|
527
|
+
"propertyNames": {
|
|
528
|
+
"type": "string",
|
|
529
|
+
"enum": [
|
|
530
|
+
"claude-code",
|
|
531
|
+
"codex",
|
|
532
|
+
"opencode"
|
|
533
|
+
]
|
|
534
|
+
},
|
|
535
|
+
"additionalProperties": {
|
|
536
|
+
"type": "object",
|
|
537
|
+
"properties": {
|
|
538
|
+
"harness": {
|
|
539
|
+
"type": "string",
|
|
540
|
+
"enum": [
|
|
541
|
+
"claude-code",
|
|
542
|
+
"codex",
|
|
543
|
+
"opencode"
|
|
544
|
+
],
|
|
545
|
+
"description": "The harness the run moves to."
|
|
546
|
+
},
|
|
547
|
+
"model": {
|
|
548
|
+
"type": "string",
|
|
549
|
+
"minLength": 1,
|
|
550
|
+
"description": "The model every moved turn runs on there."
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
"required": [
|
|
554
|
+
"harness",
|
|
555
|
+
"model"
|
|
556
|
+
],
|
|
557
|
+
"additionalProperties": false
|
|
558
|
+
}
|
|
559
|
+
},
|
|
357
560
|
"max_session_turns": {
|
|
358
561
|
"default": 12,
|
|
359
562
|
"description": "Turns one reused session slot may take before the next spawn starts fresh (§15.5). A shared session only ever grows; without a ceiling a long node eventually dies on a context-window error that reads as a broken harness.",
|
|
@@ -363,9 +566,7 @@
|
|
|
363
566
|
}
|
|
364
567
|
},
|
|
365
568
|
"required": [
|
|
366
|
-
"
|
|
367
|
-
"model",
|
|
368
|
-
"pipeline"
|
|
569
|
+
"model"
|
|
369
570
|
],
|
|
370
571
|
"additionalProperties": false
|
|
371
572
|
},
|
|
@@ -381,15 +582,6 @@
|
|
|
381
582
|
"additionalProperties": {
|
|
382
583
|
"type": "object",
|
|
383
584
|
"properties": {
|
|
384
|
-
"role": {
|
|
385
|
-
"default": "implementer",
|
|
386
|
-
"description": "Implementers take phases; reviewers read them. No member does both, which is what makes self-review unrepresentable rather than merely unlikely.",
|
|
387
|
-
"type": "string",
|
|
388
|
-
"enum": [
|
|
389
|
-
"implementer",
|
|
390
|
-
"reviewer"
|
|
391
|
-
]
|
|
392
|
-
},
|
|
393
585
|
"tier": {
|
|
394
586
|
"type": "integer",
|
|
395
587
|
"minimum": 1,
|
|
@@ -446,6 +638,22 @@
|
|
|
446
638
|
],
|
|
447
639
|
"description": "`worktree` pools are provisioned lanes; `semaphore` pools are pure counters."
|
|
448
640
|
},
|
|
641
|
+
"scope": {
|
|
642
|
+
"description": "`run` — the default — counts only this run’s holders. `host` also counts every other run on this machine holding a pool of the same name, through lock files under `~/.vinta-ai-maestro/host-leases/` (`VINTA_AI_MAESTRO_HOST_LEASES` moves them), so two runs on one laptop take turns at the full suite instead of running two at once. Only meaningful on a `semaphore` pool.",
|
|
643
|
+
"type": "string",
|
|
644
|
+
"enum": [
|
|
645
|
+
"run",
|
|
646
|
+
"host"
|
|
647
|
+
]
|
|
648
|
+
},
|
|
649
|
+
"match": {
|
|
650
|
+
"description": "Command patterns that need this pool — `pytest*`, `pnpm test*`. `*` matches any run of characters and the rest is literal, compared against the whole command line with whitespace collapsed. On a harness that can enforce it, an agent running a matching command without `vinta-ai-maestro with <pool> --` in front of it is refused and told to take the lease. A guardrail against habit, not a sandbox: `bash -c` or a script that runs the same suite is not seen. Only meaningful on a `semaphore` pool.",
|
|
651
|
+
"type": "array",
|
|
652
|
+
"items": {
|
|
653
|
+
"type": "string",
|
|
654
|
+
"minLength": 1
|
|
655
|
+
}
|
|
656
|
+
},
|
|
449
657
|
"description": {
|
|
450
658
|
"type": "string"
|
|
451
659
|
}
|
|
@@ -460,6 +668,280 @@
|
|
|
460
668
|
},
|
|
461
669
|
"gates": {
|
|
462
670
|
"default": {},
|
|
671
|
+
"description": "Gates by id. A command gate with a `type` may omit `cmd` and inherit it from the project. Gates the project configures by type (`maestro.gates.<type>`, or a `commands.*` line) are also available under the type’s name without being declared here, and so is every custom gate the project declares (`maestro.gates.<id>`) under its id. An entry here of the same id replaces the project’s whole.",
|
|
672
|
+
"type": "object",
|
|
673
|
+
"propertyNames": {
|
|
674
|
+
"type": "string",
|
|
675
|
+
"minLength": 1,
|
|
676
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
677
|
+
},
|
|
678
|
+
"additionalProperties": {
|
|
679
|
+
"anyOf": [
|
|
680
|
+
{
|
|
681
|
+
"type": "object",
|
|
682
|
+
"properties": {
|
|
683
|
+
"type": {
|
|
684
|
+
"description": "What kind of check this is. A typed gate takes any field it does not set — `cmd`, `scoped_cmd`, `requires`, `timeout_s` — from the project: `maestro.gates.<type>` in `.vinta-ai-workflows.yaml`, then the matching `commands.*` line there (`test` → `test_unit`, `lint` → `lint`, `typecheck` → `build`, `e2e` → `e2e`). A field set here wins over both.",
|
|
685
|
+
"type": "string",
|
|
686
|
+
"enum": [
|
|
687
|
+
"test",
|
|
688
|
+
"lint",
|
|
689
|
+
"typecheck",
|
|
690
|
+
"e2e"
|
|
691
|
+
]
|
|
692
|
+
},
|
|
693
|
+
"cmd": {
|
|
694
|
+
"type": "string",
|
|
695
|
+
"minLength": 1,
|
|
696
|
+
"description": "Shell command run in the node’s lane. Not an agent. The full check."
|
|
697
|
+
},
|
|
698
|
+
"scoped_cmd": {
|
|
699
|
+
"description": "The same check narrowed to what the phase changed, run by a phase gate under `defaults.gate_scope: scoped`. `{changed_files}` is the files the lane changed against the phase’s base and `{touches}` the node’s Touch List, each substituted shell-quoted and space-separated. The command must accept any path it is given — `vitest related {changed_files} --run`, `jest --findRelatedTests {changed_files}` — because a source file with no tests is a normal member of the list. When a placeholder would substitute nothing, `cmd` runs instead.",
|
|
700
|
+
"type": "string",
|
|
701
|
+
"minLength": 1
|
|
702
|
+
},
|
|
703
|
+
"cold_cmd": {
|
|
704
|
+
"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.",
|
|
705
|
+
"type": "string",
|
|
706
|
+
"minLength": 1
|
|
707
|
+
},
|
|
708
|
+
"scope_cache": {
|
|
709
|
+
"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.",
|
|
710
|
+
"type": "array",
|
|
711
|
+
"items": {
|
|
712
|
+
"type": "string",
|
|
713
|
+
"minLength": 1
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
"fresh_cmd": {
|
|
717
|
+
"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.",
|
|
718
|
+
"type": "string",
|
|
719
|
+
"minLength": 1
|
|
720
|
+
},
|
|
721
|
+
"fresh_when": {
|
|
722
|
+
"description": "Globs, relative to the repository root, of the files the state `fresh_cmd` recreates is built from — the migrations, typically (`apps/*/migrations/*.py`). When a file that matched at the gate’s last run in the integration worktree is gone or different now, the gate runs `fresh_cmd` even though the tree moved forward: a reused database keeps a migration that was rewritten or renamed under it. Files added since do not count. Ignored without `fresh_cmd`.",
|
|
723
|
+
"type": "array",
|
|
724
|
+
"items": {
|
|
725
|
+
"type": "string",
|
|
726
|
+
"minLength": 1
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
"requires": {
|
|
730
|
+
"default": [],
|
|
731
|
+
"description": "Resource pool ids acquired before the gate runs, in canonical order.",
|
|
732
|
+
"type": "array",
|
|
733
|
+
"items": {
|
|
734
|
+
"type": "string",
|
|
735
|
+
"minLength": 1,
|
|
736
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
"timeout_s": {
|
|
740
|
+
"default": 1800,
|
|
741
|
+
"description": "How long the command may run, counted from when it starts — never the time it queued for `requires`.",
|
|
742
|
+
"type": "integer",
|
|
743
|
+
"minimum": 1,
|
|
744
|
+
"maximum": 9007199254740991
|
|
745
|
+
},
|
|
746
|
+
"queue_timeout_s": {
|
|
747
|
+
"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.",
|
|
748
|
+
"type": "integer",
|
|
749
|
+
"minimum": 1,
|
|
750
|
+
"maximum": 9007199254740991
|
|
751
|
+
},
|
|
752
|
+
"tuning": {
|
|
753
|
+
"type": "object",
|
|
754
|
+
"properties": {
|
|
755
|
+
"allowed_flags": {
|
|
756
|
+
"minItems": 1,
|
|
757
|
+
"type": "array",
|
|
758
|
+
"items": {
|
|
759
|
+
"type": "string",
|
|
760
|
+
"minLength": 1
|
|
761
|
+
},
|
|
762
|
+
"description": "Flags the monitor may ADD to this gate’s command, verbatim and whole. Matched as exact argv tokens, so `--reuse-db` permits `--reuse-db` and nothing else — not `--reuse-db-and-more`, and not a flag that merely starts the same way. A flag taking a separate value (`-n auto`) is written as the two tokens it is. Removing or rewriting an existing token is never permitted, whatever is listed here: the proposed command must be this one’s tokens, in order, plus additions."
|
|
763
|
+
}
|
|
764
|
+
},
|
|
765
|
+
"required": [
|
|
766
|
+
"allowed_flags"
|
|
767
|
+
],
|
|
768
|
+
"additionalProperties": false,
|
|
769
|
+
"description": "What an autonomous retune of this gate may do. Absent — the default — means the command is not the monitor’s to touch."
|
|
770
|
+
},
|
|
771
|
+
"description": {
|
|
772
|
+
"type": "string"
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
"additionalProperties": false
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
"type": "object",
|
|
779
|
+
"properties": {
|
|
780
|
+
"judge": {
|
|
781
|
+
"type": "object",
|
|
782
|
+
"properties": {
|
|
783
|
+
"question": {
|
|
784
|
+
"description": "The question, inline. Exactly one of `question` and `question_ref` is required.",
|
|
785
|
+
"type": "string",
|
|
786
|
+
"minLength": 1
|
|
787
|
+
},
|
|
788
|
+
"question_ref": {
|
|
789
|
+
"description": "Where the question lives, in `prompt_ref` form (`ai-plans/PLAN.md#no-phi-in-logs`), resolved off the lane like a phase brief.",
|
|
790
|
+
"type": "string",
|
|
791
|
+
"minLength": 1
|
|
792
|
+
},
|
|
793
|
+
"labels": {
|
|
794
|
+
"default": [
|
|
795
|
+
"yes",
|
|
796
|
+
"no"
|
|
797
|
+
],
|
|
798
|
+
"description": "The closed set the classifier chooses among. The default makes it a yes/no question.",
|
|
799
|
+
"minItems": 2,
|
|
800
|
+
"type": "array",
|
|
801
|
+
"items": {
|
|
802
|
+
"type": "string",
|
|
803
|
+
"minLength": 1
|
|
804
|
+
}
|
|
805
|
+
},
|
|
806
|
+
"fail_on": {
|
|
807
|
+
"minItems": 1,
|
|
808
|
+
"type": "array",
|
|
809
|
+
"items": {
|
|
810
|
+
"type": "string",
|
|
811
|
+
"minLength": 1
|
|
812
|
+
},
|
|
813
|
+
"description": "The labels that fail the gate. Their combined score is compared against `threshold`, so `[\"architectural\", \"schema\"]` fails on the two together and not on either alone."
|
|
814
|
+
},
|
|
815
|
+
"threshold": {
|
|
816
|
+
"default": 0.5,
|
|
817
|
+
"description": "The gate fails when the `fail_on` labels together score at least this much.",
|
|
818
|
+
"type": "number",
|
|
819
|
+
"minimum": 0,
|
|
820
|
+
"maximum": 1
|
|
821
|
+
},
|
|
822
|
+
"input": {
|
|
823
|
+
"default": "diff",
|
|
824
|
+
"description": "What is judged. `diff` is the lane against the phase’s base, uncommitted work included.",
|
|
825
|
+
"type": "string",
|
|
826
|
+
"enum": [
|
|
827
|
+
"diff"
|
|
828
|
+
]
|
|
829
|
+
},
|
|
830
|
+
"max_input_bytes": {
|
|
831
|
+
"default": 204800,
|
|
832
|
+
"description": "A larger input is not sent. The gate then counts as unavailable — a truncated diff would be judged on the part that happened to fit.",
|
|
833
|
+
"type": "integer",
|
|
834
|
+
"minimum": 1024,
|
|
835
|
+
"maximum": 9007199254740991
|
|
836
|
+
},
|
|
837
|
+
"on_unavailable": {
|
|
838
|
+
"default": "pass",
|
|
839
|
+
"description": "What a question nobody answered means: no classifier configured, unreachable, or an oversized input. `pass` — the default — keeps a judge gate advisory; `fail` is for a check the phase must not merge without.",
|
|
840
|
+
"type": "string",
|
|
841
|
+
"enum": [
|
|
842
|
+
"pass",
|
|
843
|
+
"fail"
|
|
844
|
+
]
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
"required": [
|
|
848
|
+
"fail_on"
|
|
849
|
+
],
|
|
850
|
+
"additionalProperties": false
|
|
851
|
+
},
|
|
852
|
+
"requires": {
|
|
853
|
+
"default": [],
|
|
854
|
+
"description": "Resource pool ids acquired before the gate runs. Rarely needed: a judge holds no lane resource.",
|
|
855
|
+
"type": "array",
|
|
856
|
+
"items": {
|
|
857
|
+
"type": "string",
|
|
858
|
+
"minLength": 1,
|
|
859
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
"description": {
|
|
863
|
+
"type": "string"
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
"required": [
|
|
867
|
+
"judge"
|
|
868
|
+
],
|
|
869
|
+
"additionalProperties": false
|
|
870
|
+
}
|
|
871
|
+
]
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
"chores": {
|
|
875
|
+
"default": {},
|
|
876
|
+
"description": "Agent turns a phase runs beside its gates, by id. A gate judges and a chore changes the tree, so they are separate registries — see ChoreSchema.",
|
|
877
|
+
"type": "object",
|
|
878
|
+
"propertyNames": {
|
|
879
|
+
"type": "string",
|
|
880
|
+
"minLength": 1,
|
|
881
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
882
|
+
},
|
|
883
|
+
"additionalProperties": {
|
|
884
|
+
"type": "object",
|
|
885
|
+
"properties": {
|
|
886
|
+
"prompt": {
|
|
887
|
+
"description": "The instruction, inline. Exactly one of `prompt` and `prompt_ref` is required.",
|
|
888
|
+
"type": "string",
|
|
889
|
+
"minLength": 1
|
|
890
|
+
},
|
|
891
|
+
"prompt_ref": {
|
|
892
|
+
"description": "Where the instruction lives, in `prompt_ref` form (`ai-plans/PLAN.md#deslop`), resolved by the same resolver as a phase brief. Prefer it over `prompt` for anything longer than a sentence: it keeps the text reviewable in the plan rather than in a JSON string.",
|
|
893
|
+
"type": "string",
|
|
894
|
+
"minLength": 1
|
|
895
|
+
},
|
|
896
|
+
"skill": {
|
|
897
|
+
"description": "A skill the agent should use, named in the prompt rather than passed as a harness flag — codex and opencode have no skills, and a chore that only works on one harness is a chore that silently does nothing on the other two.",
|
|
898
|
+
"type": "string",
|
|
899
|
+
"minLength": 1
|
|
900
|
+
},
|
|
901
|
+
"session": {
|
|
902
|
+
"default": "main",
|
|
903
|
+
"description": "The session slot this turn continues (§15). `main` — the default — is the implementer’s: the agent that wrote the phase already holds the brief, the diff and its own reasons, and that is most of what makes a chore cheap. A slot no other effect names starts a fresh session every time and pays for a cold context.",
|
|
904
|
+
"type": "string",
|
|
905
|
+
"minLength": 1
|
|
906
|
+
},
|
|
907
|
+
"model": {
|
|
908
|
+
"description": "Overrides the model the phase’s implementer would otherwise run at. A mechanical pass does not need the tier the phase was staffed at.",
|
|
909
|
+
"type": "string",
|
|
910
|
+
"minLength": 1
|
|
911
|
+
},
|
|
912
|
+
"when": {
|
|
913
|
+
"default": "after_review",
|
|
914
|
+
"description": "When the chore runs. `review` runs it once the phase’s gates are green, as the phase’s code review: the turn ends with a `VERDICT: pass` or `VERDICT: fail` line, and the phase moves on only when every review chore passed. `after_review` — the default — runs it once the review approved, before the gates run again on the final tree. `after_pr` runs it once the phase is merged, pushed and its pull request is open, for work about the PR rather than the diff (a review canvas, a PR comment). An `after_pr` chore must not edit the tree, is skipped when no PR opened, and cannot be `on_failure: fail`.",
|
|
915
|
+
"type": "string",
|
|
916
|
+
"enum": [
|
|
917
|
+
"review",
|
|
918
|
+
"after_review",
|
|
919
|
+
"after_pr"
|
|
920
|
+
]
|
|
921
|
+
},
|
|
922
|
+
"background": {
|
|
923
|
+
"description": "`false` tells this chore’s turns to run every command in the foreground and wait for it, even on a harness that keeps a session alive for background work. For a review whose turns keep ending on \"waiting on the tests\" with no verdict. Absent, the harness decides.",
|
|
924
|
+
"type": "boolean"
|
|
925
|
+
},
|
|
926
|
+
"on_failure": {
|
|
927
|
+
"default": "continue",
|
|
928
|
+
"description": "`continue` — the default — journals a failed chore and moves on. A chore is polish, and losing an implemented phase to one that timed out is the worse trade. `fail` is for a chore whose output the phase is not correct without. A `review` chore ignores it: a review that did not approve, for any reason, is put to the operator.",
|
|
929
|
+
"type": "string",
|
|
930
|
+
"enum": [
|
|
931
|
+
"continue",
|
|
932
|
+
"fail"
|
|
933
|
+
]
|
|
934
|
+
},
|
|
935
|
+
"description": {
|
|
936
|
+
"type": "string"
|
|
937
|
+
}
|
|
938
|
+
},
|
|
939
|
+
"additionalProperties": false
|
|
940
|
+
}
|
|
941
|
+
},
|
|
942
|
+
"codegen": {
|
|
943
|
+
"default": {},
|
|
944
|
+
"description": "Deterministic commands that write declared files, by id — see CodegenStepSchema. They run in every lane before its gates and in the integration worktree around every merge, in `after` order. A failing step is a red gate.",
|
|
463
945
|
"type": "object",
|
|
464
946
|
"propertyNames": {
|
|
465
947
|
"type": "string",
|
|
@@ -472,11 +954,39 @@
|
|
|
472
954
|
"cmd": {
|
|
473
955
|
"type": "string",
|
|
474
956
|
"minLength": 1,
|
|
475
|
-
"description": "Shell command run in the
|
|
957
|
+
"description": "Shell command, run in the worktree with its environment — the lane’s or the integration worktree’s. Must be deterministic: the same inputs produce byte-identical outputs (`doctor --codegen` checks). A `format` step’s command takes `{changed_files}`."
|
|
958
|
+
},
|
|
959
|
+
"outputs": {
|
|
960
|
+
"minItems": 1,
|
|
961
|
+
"type": "array",
|
|
962
|
+
"items": {
|
|
963
|
+
"type": "string",
|
|
964
|
+
"minLength": 1
|
|
965
|
+
},
|
|
966
|
+
"description": "The files the command writes, as git glob pathspecs relative to the repository root (`schema.yml`, `frontend/src/api-client/**`, `**/*.gen.ts`). What it changes among them is committed; anything else it changes is left alone. Agents are told these are not to be edited by hand, and a merge conflict confined to them is settled by running the step instead of by a fixer."
|
|
967
|
+
},
|
|
968
|
+
"inputs": {
|
|
969
|
+
"default": [],
|
|
970
|
+
"description": "The files the outputs are a function of, as git glob pathspecs. When the step last ran on exactly these contents and its outputs are still what it wrote, it is skipped, as a gate is on a tree the cache has seen. Empty — the default — runs it every time.",
|
|
971
|
+
"type": "array",
|
|
972
|
+
"items": {
|
|
973
|
+
"type": "string",
|
|
974
|
+
"minLength": 1
|
|
975
|
+
}
|
|
976
|
+
},
|
|
977
|
+
"after": {
|
|
978
|
+
"default": [],
|
|
979
|
+
"description": "Steps that run before this one: the schema before the client generated from it, the client before whatever reads it.",
|
|
980
|
+
"type": "array",
|
|
981
|
+
"items": {
|
|
982
|
+
"type": "string",
|
|
983
|
+
"minLength": 1,
|
|
984
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
985
|
+
}
|
|
476
986
|
},
|
|
477
987
|
"requires": {
|
|
478
988
|
"default": [],
|
|
479
|
-
"description": "Resource pool ids
|
|
989
|
+
"description": "Resource pool ids held while the step runs, as a gate’s are. Never `lane`.",
|
|
480
990
|
"type": "array",
|
|
481
991
|
"items": {
|
|
482
992
|
"type": "string",
|
|
@@ -485,21 +995,82 @@
|
|
|
485
995
|
}
|
|
486
996
|
},
|
|
487
997
|
"timeout_s": {
|
|
488
|
-
"default":
|
|
998
|
+
"default": 600,
|
|
489
999
|
"type": "integer",
|
|
490
1000
|
"minimum": 1,
|
|
491
1001
|
"maximum": 9007199254740991
|
|
492
1002
|
},
|
|
1003
|
+
"format": {
|
|
1004
|
+
"default": false,
|
|
1005
|
+
"description": "A formatter rather than a generator. `{changed_files}` in `cmd` is the files the phase changed that match `outputs`, shell-quoted; with none, the step is skipped. Formatting the whole repository makes diffs in files the phase never touched. A format step’s outputs are ordinary source, so they are not listed as hand-edit-free and a conflict in them still goes to the fixer.",
|
|
1006
|
+
"type": "boolean"
|
|
1007
|
+
},
|
|
493
1008
|
"description": {
|
|
494
1009
|
"type": "string"
|
|
495
1010
|
}
|
|
496
1011
|
},
|
|
497
1012
|
"required": [
|
|
498
|
-
"cmd"
|
|
1013
|
+
"cmd",
|
|
1014
|
+
"outputs"
|
|
499
1015
|
],
|
|
500
1016
|
"additionalProperties": false
|
|
501
1017
|
}
|
|
502
1018
|
},
|
|
1019
|
+
"post_merge": {
|
|
1020
|
+
"description": "A normalizing command for files a clean merge combined — see PostMergeSchema.",
|
|
1021
|
+
"type": "object",
|
|
1022
|
+
"properties": {
|
|
1023
|
+
"cmd": {
|
|
1024
|
+
"type": "string",
|
|
1025
|
+
"minLength": 1,
|
|
1026
|
+
"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."
|
|
1027
|
+
},
|
|
1028
|
+
"timeout_s": {
|
|
1029
|
+
"default": 300,
|
|
1030
|
+
"type": "integer",
|
|
1031
|
+
"minimum": 1,
|
|
1032
|
+
"maximum": 9007199254740991
|
|
1033
|
+
}
|
|
1034
|
+
},
|
|
1035
|
+
"required": [
|
|
1036
|
+
"cmd"
|
|
1037
|
+
],
|
|
1038
|
+
"additionalProperties": false
|
|
1039
|
+
},
|
|
1040
|
+
"ci_only": {
|
|
1041
|
+
"default": [],
|
|
1042
|
+
"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.",
|
|
1043
|
+
"type": "array",
|
|
1044
|
+
"items": {
|
|
1045
|
+
"type": "object",
|
|
1046
|
+
"properties": {
|
|
1047
|
+
"command": {
|
|
1048
|
+
"type": "string",
|
|
1049
|
+
"minLength": 1,
|
|
1050
|
+
"description": "The CI command, as CI runs it (wrappers like `uv run` are ignored when matching)."
|
|
1051
|
+
},
|
|
1052
|
+
"reason": {
|
|
1053
|
+
"type": "string",
|
|
1054
|
+
"minLength": 1,
|
|
1055
|
+
"description": "Why no gate runs it: it needs credentials, a deploy, a hosted service."
|
|
1056
|
+
}
|
|
1057
|
+
},
|
|
1058
|
+
"required": [
|
|
1059
|
+
"command",
|
|
1060
|
+
"reason"
|
|
1061
|
+
],
|
|
1062
|
+
"additionalProperties": false
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
"append_only": {
|
|
1066
|
+
"default": [],
|
|
1067
|
+
"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.",
|
|
1068
|
+
"type": "array",
|
|
1069
|
+
"items": {
|
|
1070
|
+
"type": "string",
|
|
1071
|
+
"minLength": 1
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
503
1074
|
"nodes": {
|
|
504
1075
|
"minItems": 1,
|
|
505
1076
|
"type": "array",
|
|
@@ -569,6 +1140,15 @@
|
|
|
569
1140
|
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
570
1141
|
}
|
|
571
1142
|
},
|
|
1143
|
+
"chores": {
|
|
1144
|
+
"description": "The chores this phase runs, in order. Optional rather than defaulted, and that is the whole of its override rule: absent takes `defaults.chores`, and a list — `[]` included — replaces it. Merging instead would leave no way to skip a run-wide chore on the one phase it makes no sense for.",
|
|
1145
|
+
"type": "array",
|
|
1146
|
+
"items": {
|
|
1147
|
+
"type": "string",
|
|
1148
|
+
"minLength": 1,
|
|
1149
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
1150
|
+
}
|
|
1151
|
+
},
|
|
572
1152
|
"harness": {
|
|
573
1153
|
"description": "Overrides defaults.harness.",
|
|
574
1154
|
"type": "string",
|
|
@@ -583,13 +1163,18 @@
|
|
|
583
1163
|
"type": "string"
|
|
584
1164
|
},
|
|
585
1165
|
"crew": {
|
|
586
|
-
"description": "The
|
|
1166
|
+
"description": "The member this phase is assigned to. Their tier is the floor for it: a busier roster may hand the phase to a free member at that tier or above, never below.",
|
|
587
1167
|
"type": "string",
|
|
588
1168
|
"minLength": 1,
|
|
589
1169
|
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
590
1170
|
},
|
|
1171
|
+
"deferred": {
|
|
1172
|
+
"description": "Why this phase must not start on its own — \"deferred until the flag has soaked two weeks at 100% in production\". A deferred phase is not dispatched when its dependencies are done: it parks on a question naming this reason, and the operator answers `start` (now) or `stop` (fail it, blocking what depends on it). Without it the scheduler would run a soak-gated flag removal the moment its dependencies merged.",
|
|
1173
|
+
"type": "string",
|
|
1174
|
+
"minLength": 1
|
|
1175
|
+
},
|
|
591
1176
|
"max_fix_rounds": {
|
|
592
|
-
"
|
|
1177
|
+
"description": "Fix rounds a phase may spend on red gates, and unsuccessful iterations each review turn may run, before the operator is asked whether to keep going (§16). Absent — the default — sets no limit: the fixer works until the gates are green and the review loop until the reviewer approves. A fix round is a fixer turn; a review iteration is one pass of the review loop that returned blockers. When a set budget runs out the shipped pipeline asks rather than fails: `continue` grants the same budget again, `stop` fails the phase and hands it to `--on-failure`.",
|
|
593
1178
|
"type": "integer",
|
|
594
1179
|
"minimum": 0,
|
|
595
1180
|
"maximum": 9007199254740991
|
|
@@ -665,13 +1250,15 @@
|
|
|
665
1250
|
"enum": [
|
|
666
1251
|
"spawn_agent",
|
|
667
1252
|
"run_gate",
|
|
1253
|
+
"run_chore",
|
|
668
1254
|
"git_branch",
|
|
669
1255
|
"git_merge",
|
|
670
1256
|
"git_push",
|
|
671
1257
|
"open_pr",
|
|
672
1258
|
"write_tracking",
|
|
673
1259
|
"await_human",
|
|
674
|
-
"notify"
|
|
1260
|
+
"notify",
|
|
1261
|
+
"grant_fix_rounds"
|
|
675
1262
|
],
|
|
676
1263
|
"description": "Which catalog verb this effect invokes."
|
|
677
1264
|
},
|
|
@@ -729,13 +1316,15 @@
|
|
|
729
1316
|
"enum": [
|
|
730
1317
|
"spawn_agent",
|
|
731
1318
|
"run_gate",
|
|
1319
|
+
"run_chore",
|
|
732
1320
|
"git_branch",
|
|
733
1321
|
"git_merge",
|
|
734
1322
|
"git_push",
|
|
735
1323
|
"open_pr",
|
|
736
1324
|
"write_tracking",
|
|
737
1325
|
"await_human",
|
|
738
|
-
"notify"
|
|
1326
|
+
"notify",
|
|
1327
|
+
"grant_fix_rounds"
|
|
739
1328
|
],
|
|
740
1329
|
"description": "Which catalog verb this effect invokes."
|
|
741
1330
|
},
|
|
@@ -849,13 +1438,15 @@
|
|
|
849
1438
|
"enum": [
|
|
850
1439
|
"spawn_agent",
|
|
851
1440
|
"run_gate",
|
|
1441
|
+
"run_chore",
|
|
852
1442
|
"git_branch",
|
|
853
1443
|
"git_merge",
|
|
854
1444
|
"git_push",
|
|
855
1445
|
"open_pr",
|
|
856
1446
|
"write_tracking",
|
|
857
1447
|
"await_human",
|
|
858
|
-
"notify"
|
|
1448
|
+
"notify",
|
|
1449
|
+
"grant_fix_rounds"
|
|
859
1450
|
],
|
|
860
1451
|
"description": "Which catalog verb this effect invokes."
|
|
861
1452
|
},
|
|
@@ -959,7 +1550,6 @@
|
|
|
959
1550
|
"required": [
|
|
960
1551
|
"schema_version",
|
|
961
1552
|
"id",
|
|
962
|
-
"base_branch",
|
|
963
1553
|
"defaults",
|
|
964
1554
|
"resources",
|
|
965
1555
|
"nodes"
|