vinta-ai-workflows 0.7.0-alpha8 → 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 +472 -595
- 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 +751 -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": [
|
|
@@ -191,6 +211,178 @@
|
|
|
191
211
|
}
|
|
192
212
|
},
|
|
193
213
|
"additionalProperties": false
|
|
214
|
+
},
|
|
215
|
+
"env_files": {
|
|
216
|
+
"default": [],
|
|
217
|
+
"description": "Repo-relative ignored files every lane needs its own COPY of — `.env`, `.env.docker`, `.envrc`. Copied, never symlinked: lane provisioning appends to them (a connection string, a compose override path), and a symlink would write those lane-specific lines back into the main checkout. A declared file that is missing fails provisioning rather than producing a lane whose stack cannot boot.",
|
|
218
|
+
"type": "array",
|
|
219
|
+
"items": {
|
|
220
|
+
"type": "string",
|
|
221
|
+
"minLength": 1
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
"commands": {
|
|
225
|
+
"default": {},
|
|
226
|
+
"type": "object",
|
|
227
|
+
"properties": {
|
|
228
|
+
"lint": {
|
|
229
|
+
"type": "string",
|
|
230
|
+
"minLength": 1
|
|
231
|
+
},
|
|
232
|
+
"typecheck": {
|
|
233
|
+
"type": "string",
|
|
234
|
+
"minLength": 1
|
|
235
|
+
},
|
|
236
|
+
"test": {
|
|
237
|
+
"description": "The whole suite.",
|
|
238
|
+
"type": "string",
|
|
239
|
+
"minLength": 1
|
|
240
|
+
},
|
|
241
|
+
"test_one": {
|
|
242
|
+
"description": "A single test or a subtree. The agent appends the target, so end it accordingly.",
|
|
243
|
+
"type": "string",
|
|
244
|
+
"minLength": 1
|
|
245
|
+
},
|
|
246
|
+
"migrate": {
|
|
247
|
+
"type": "string",
|
|
248
|
+
"minLength": 1
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
"additionalProperties": false,
|
|
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."
|
|
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
|
+
},
|
|
263
|
+
"services": {
|
|
264
|
+
"default": {},
|
|
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.",
|
|
266
|
+
"type": "object",
|
|
267
|
+
"propertyNames": {
|
|
268
|
+
"type": "string",
|
|
269
|
+
"minLength": 1,
|
|
270
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
271
|
+
},
|
|
272
|
+
"additionalProperties": {
|
|
273
|
+
"type": "object",
|
|
274
|
+
"properties": {
|
|
275
|
+
"namespace": {
|
|
276
|
+
"type": "string",
|
|
277
|
+
"enum": [
|
|
278
|
+
"index",
|
|
279
|
+
"name"
|
|
280
|
+
],
|
|
281
|
+
"description": "How this lane’s namespace inside the shared server is derived. `index` is a small integer, for a server with a fixed number of slots — redis’s sixteen databases are the case it exists for. `name` is a token derived from the lane, for a server that names things freely: a vhost, a bucket prefix, a schema."
|
|
282
|
+
},
|
|
283
|
+
"url": {
|
|
284
|
+
"description": "The shared server, without the per-lane segment — `redis://localhost:6379`. Omit it where the namespace *is* the value: an object-storage prefix has no URL to hang off, and the variable then carries the bare token.",
|
|
285
|
+
"type": "string",
|
|
286
|
+
"minLength": 1
|
|
287
|
+
},
|
|
288
|
+
"url_var": {
|
|
289
|
+
"type": "string",
|
|
290
|
+
"minLength": 1,
|
|
291
|
+
"description": "Env var the lane reads this service’s address from."
|
|
292
|
+
},
|
|
293
|
+
"capacity": {
|
|
294
|
+
"default": 16,
|
|
295
|
+
"description": "How many distinct namespaces the server has. Only meaningful for `index`, where a pool larger than this is refused before anything is created — two lanes on one redis database is the bug this exists to prevent, and reaching it by arithmetic is no better than reaching it by neglect.",
|
|
296
|
+
"type": "integer",
|
|
297
|
+
"minimum": 1,
|
|
298
|
+
"maximum": 9007199254740991
|
|
299
|
+
},
|
|
300
|
+
"create_cmd": {
|
|
301
|
+
"description": "Run once per lane, in the lane. `{namespace}`, `{url}` and `{lane}` are substituted. Declaring a vhost is a `rabbitmqadmin` invocation this package has no business knowing; a project that needs none declares none.",
|
|
302
|
+
"type": "string",
|
|
303
|
+
"minLength": 1
|
|
304
|
+
},
|
|
305
|
+
"reset_cmd": {
|
|
306
|
+
"description": "Empties this lane’s namespace when the lane is handed to the next phase. Same substitutions. A service without one keeps whatever the last phase left in it, which for a cache is usually right and for a queue usually is not — declaring this is how a project says which of those it has.",
|
|
307
|
+
"type": "string",
|
|
308
|
+
"minLength": 1
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
"required": [
|
|
312
|
+
"namespace",
|
|
313
|
+
"url_var"
|
|
314
|
+
],
|
|
315
|
+
"additionalProperties": false,
|
|
316
|
+
"description": "A shared server each lane gets its own namespace inside."
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
"compose": {
|
|
320
|
+
"default": {
|
|
321
|
+
"enabled": true,
|
|
322
|
+
"publish": [],
|
|
323
|
+
"shared_volumes": []
|
|
324
|
+
},
|
|
325
|
+
"type": "object",
|
|
326
|
+
"properties": {
|
|
327
|
+
"enabled": {
|
|
328
|
+
"default": true,
|
|
329
|
+
"description": "Whether lane provisioning generates a compose override. On by default, and it does nothing at all in a project with no compose file. Where there is one and docker cannot answer, provisioning fails rather than quietly producing lanes that share a data volume — set this to false to say that is genuinely what you want.",
|
|
330
|
+
"type": "boolean"
|
|
331
|
+
},
|
|
332
|
+
"publish": {
|
|
333
|
+
"default": [],
|
|
334
|
+
"description": "Services that must keep a reachable host port, republished on one this lane was granted instead of the one the project pinned. Empty is the default and is usually right outright: where the project’s own test command runs inside compose, services reach each other by container DNS on the lane’s own network and a published port buys nothing but a collision. The granted port reaches the lane as `LANE_PORT_<SERVICE>_<CONTAINER_PORT>`.",
|
|
335
|
+
"type": "array",
|
|
336
|
+
"items": {
|
|
337
|
+
"type": "string",
|
|
338
|
+
"minLength": 1
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
"shared_volumes": {
|
|
342
|
+
"default": [],
|
|
343
|
+
"description": "Volume keys that leak past the project name and are to be left leaking. The escape hatch for the one decision this cannot make for you: re-pinning a shared dependency volume is correct, and it costs every lane the install that sharing it was avoiding. A read-only cache with no dependency churn in the plan is safe here. A data volume never is — that is two servers on one data directory, which is the bug this exists to prevent.",
|
|
344
|
+
"type": "array",
|
|
345
|
+
"items": {
|
|
346
|
+
"type": "string",
|
|
347
|
+
"minLength": 1
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"additionalProperties": false,
|
|
352
|
+
"description": "How a lane’s compose stack is isolated past `COMPOSE_PROJECT_NAME`."
|
|
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
|
+
},
|
|
359
|
+
"setup_cmd": {
|
|
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.",
|
|
361
|
+
"type": "string",
|
|
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
|
+
}
|
|
194
386
|
}
|
|
195
387
|
},
|
|
196
388
|
"required": [
|
|
@@ -203,6 +395,7 @@
|
|
|
203
395
|
"type": "object",
|
|
204
396
|
"properties": {
|
|
205
397
|
"harness": {
|
|
398
|
+
"default": "claude-code",
|
|
206
399
|
"type": "string",
|
|
207
400
|
"enum": [
|
|
208
401
|
"claude-code",
|
|
@@ -215,10 +408,155 @@
|
|
|
215
408
|
"minLength": 1
|
|
216
409
|
},
|
|
217
410
|
"pipeline": {
|
|
411
|
+
"default": "standard-phase",
|
|
218
412
|
"type": "string",
|
|
219
413
|
"minLength": 1,
|
|
220
414
|
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
221
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
|
+
},
|
|
222
560
|
"max_session_turns": {
|
|
223
561
|
"default": 12,
|
|
224
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.",
|
|
@@ -228,9 +566,7 @@
|
|
|
228
566
|
}
|
|
229
567
|
},
|
|
230
568
|
"required": [
|
|
231
|
-
"
|
|
232
|
-
"model",
|
|
233
|
-
"pipeline"
|
|
569
|
+
"model"
|
|
234
570
|
],
|
|
235
571
|
"additionalProperties": false
|
|
236
572
|
},
|
|
@@ -246,15 +582,6 @@
|
|
|
246
582
|
"additionalProperties": {
|
|
247
583
|
"type": "object",
|
|
248
584
|
"properties": {
|
|
249
|
-
"role": {
|
|
250
|
-
"default": "implementer",
|
|
251
|
-
"description": "Implementers take phases; reviewers read them. No member does both, which is what makes self-review unrepresentable rather than merely unlikely.",
|
|
252
|
-
"type": "string",
|
|
253
|
-
"enum": [
|
|
254
|
-
"implementer",
|
|
255
|
-
"reviewer"
|
|
256
|
-
]
|
|
257
|
-
},
|
|
258
585
|
"tier": {
|
|
259
586
|
"type": "integer",
|
|
260
587
|
"minimum": 1,
|
|
@@ -311,6 +638,22 @@
|
|
|
311
638
|
],
|
|
312
639
|
"description": "`worktree` pools are provisioned lanes; `semaphore` pools are pure counters."
|
|
313
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
|
+
},
|
|
314
657
|
"description": {
|
|
315
658
|
"type": "string"
|
|
316
659
|
}
|
|
@@ -325,6 +668,280 @@
|
|
|
325
668
|
},
|
|
326
669
|
"gates": {
|
|
327
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.",
|
|
328
945
|
"type": "object",
|
|
329
946
|
"propertyNames": {
|
|
330
947
|
"type": "string",
|
|
@@ -337,11 +954,39 @@
|
|
|
337
954
|
"cmd": {
|
|
338
955
|
"type": "string",
|
|
339
956
|
"minLength": 1,
|
|
340
|
-
"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
|
+
}
|
|
341
986
|
},
|
|
342
987
|
"requires": {
|
|
343
988
|
"default": [],
|
|
344
|
-
"description": "Resource pool ids
|
|
989
|
+
"description": "Resource pool ids held while the step runs, as a gate’s are. Never `lane`.",
|
|
345
990
|
"type": "array",
|
|
346
991
|
"items": {
|
|
347
992
|
"type": "string",
|
|
@@ -350,21 +995,82 @@
|
|
|
350
995
|
}
|
|
351
996
|
},
|
|
352
997
|
"timeout_s": {
|
|
353
|
-
"default":
|
|
998
|
+
"default": 600,
|
|
354
999
|
"type": "integer",
|
|
355
1000
|
"minimum": 1,
|
|
356
1001
|
"maximum": 9007199254740991
|
|
357
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
|
+
},
|
|
358
1008
|
"description": {
|
|
359
1009
|
"type": "string"
|
|
360
1010
|
}
|
|
361
1011
|
},
|
|
362
1012
|
"required": [
|
|
363
|
-
"cmd"
|
|
1013
|
+
"cmd",
|
|
1014
|
+
"outputs"
|
|
364
1015
|
],
|
|
365
1016
|
"additionalProperties": false
|
|
366
1017
|
}
|
|
367
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
|
+
},
|
|
368
1074
|
"nodes": {
|
|
369
1075
|
"minItems": 1,
|
|
370
1076
|
"type": "array",
|
|
@@ -434,6 +1140,15 @@
|
|
|
434
1140
|
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
435
1141
|
}
|
|
436
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
|
+
},
|
|
437
1152
|
"harness": {
|
|
438
1153
|
"description": "Overrides defaults.harness.",
|
|
439
1154
|
"type": "string",
|
|
@@ -448,13 +1163,18 @@
|
|
|
448
1163
|
"type": "string"
|
|
449
1164
|
},
|
|
450
1165
|
"crew": {
|
|
451
|
-
"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.",
|
|
452
1167
|
"type": "string",
|
|
453
1168
|
"minLength": 1,
|
|
454
1169
|
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
455
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
|
+
},
|
|
456
1176
|
"max_fix_rounds": {
|
|
457
|
-
"
|
|
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`.",
|
|
458
1178
|
"type": "integer",
|
|
459
1179
|
"minimum": 0,
|
|
460
1180
|
"maximum": 9007199254740991
|
|
@@ -530,13 +1250,15 @@
|
|
|
530
1250
|
"enum": [
|
|
531
1251
|
"spawn_agent",
|
|
532
1252
|
"run_gate",
|
|
1253
|
+
"run_chore",
|
|
533
1254
|
"git_branch",
|
|
534
1255
|
"git_merge",
|
|
535
1256
|
"git_push",
|
|
536
1257
|
"open_pr",
|
|
537
1258
|
"write_tracking",
|
|
538
1259
|
"await_human",
|
|
539
|
-
"notify"
|
|
1260
|
+
"notify",
|
|
1261
|
+
"grant_fix_rounds"
|
|
540
1262
|
],
|
|
541
1263
|
"description": "Which catalog verb this effect invokes."
|
|
542
1264
|
},
|
|
@@ -594,13 +1316,15 @@
|
|
|
594
1316
|
"enum": [
|
|
595
1317
|
"spawn_agent",
|
|
596
1318
|
"run_gate",
|
|
1319
|
+
"run_chore",
|
|
597
1320
|
"git_branch",
|
|
598
1321
|
"git_merge",
|
|
599
1322
|
"git_push",
|
|
600
1323
|
"open_pr",
|
|
601
1324
|
"write_tracking",
|
|
602
1325
|
"await_human",
|
|
603
|
-
"notify"
|
|
1326
|
+
"notify",
|
|
1327
|
+
"grant_fix_rounds"
|
|
604
1328
|
],
|
|
605
1329
|
"description": "Which catalog verb this effect invokes."
|
|
606
1330
|
},
|
|
@@ -714,13 +1438,15 @@
|
|
|
714
1438
|
"enum": [
|
|
715
1439
|
"spawn_agent",
|
|
716
1440
|
"run_gate",
|
|
1441
|
+
"run_chore",
|
|
717
1442
|
"git_branch",
|
|
718
1443
|
"git_merge",
|
|
719
1444
|
"git_push",
|
|
720
1445
|
"open_pr",
|
|
721
1446
|
"write_tracking",
|
|
722
1447
|
"await_human",
|
|
723
|
-
"notify"
|
|
1448
|
+
"notify",
|
|
1449
|
+
"grant_fix_rounds"
|
|
724
1450
|
],
|
|
725
1451
|
"description": "Which catalog verb this effect invokes."
|
|
726
1452
|
},
|
|
@@ -824,7 +1550,6 @@
|
|
|
824
1550
|
"required": [
|
|
825
1551
|
"schema_version",
|
|
826
1552
|
"id",
|
|
827
|
-
"base_branch",
|
|
828
1553
|
"defaults",
|
|
829
1554
|
"resources",
|
|
830
1555
|
"nodes"
|