vinta-ai-workflows 0.7.0-alpha2 → 0.7.0-alpha21

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +1704 -3
  2. package/README.md +16 -0
  3. package/package.json +11 -9
  4. package/schemas/README.md +9 -2
  5. package/schemas/intervention.v1.schema.json +170 -0
  6. package/schemas/postmortem.v1.schema.json +268 -3
  7. package/schemas/prs-context-frontmatter.v1.schema.json +18 -7
  8. package/schemas/vinta-ai-workflows-config.v1.schema.json +12 -0
  9. package/schemas/workflow.v1.schema.json +388 -24
  10. package/skills/vinta-analyze-codebase/SKILL.md +1 -0
  11. package/skills/vinta-bootstrap-ai-tools/SKILL.md +18 -2
  12. package/skills/vinta-derive-skills/SKILL.md +5 -0
  13. package/skills/vinta-derive-skills/resources/foundation-skills/open-pr-from-context/scripts/open-pr.sh +0 -0
  14. package/skills/vinta-derive-skills/resources/foundation-skills/plan-feature/SKILL.md +100 -50
  15. package/skills/vinta-derive-skills/resources/foundation-skills/plan-feature/resources/ai-models.yaml +9 -10
  16. package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/SKILL.md +10 -2
  17. package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/scripts/claude-worktree-write-guard.py +0 -0
  18. package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/scripts/gen-claude-sandbox-settings.sh +0 -0
  19. package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/scripts/gen-compose-worktree-override.sh +0 -0
  20. package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/scripts/sandbox-run.sh +0 -0
  21. package/skills/vinta-derive-skills/resources/plan-execution/README.md +11 -0
  22. package/skills/vinta-derive-skills/resources/plan-execution/partials/commit-strategy/stacked.md +18 -0
  23. package/skills/vinta-derive-skills/resources/plan-execution/partials/dispatched-agent.md +31 -0
  24. package/skills/vinta-derive-skills/resources/plan-execution/partials/implementer-prompt.md +2 -0
  25. package/skills/vinta-derive-skills/resources/plan-execution/partials/parallel-lanes.md +10 -3
  26. package/skills/vinta-derive-skills/resources/plan-execution/partials/pr-context.md +3 -1
  27. package/skills/vinta-derive-skills/resources/plan-execution/shell/amend-plan-template.md +3 -0
  28. package/skills/vinta-derive-skills/resources/plan-execution/shell/implement-phase-template.md +3 -0
  29. package/skills/vinta-derive-skills/resources/plan-execution/shell/implement-plan-template.md +7 -3
  30. package/skills/vinta-derive-skills/resources/plan-execution/shell/integrate-phase-template.md +2 -1
  31. package/skills/vinta-derive-skills/resources/plan-execution/shell/review-phase-template.md +3 -0
  32. package/skills/vinta-derive-skills/resources/prs-context-template.md +5 -4
  33. package/skills/vinta-install-ai-tools-setup/SKILL.md +28 -1
  34. package/skills/vinta-install-ai-tools-setup/resources/setup-ai-tools.mjs +0 -0
  35. package/skills/vinta-sync-ai-tools/SKILL.md +2 -1
  36. package/skills/vinta-update-project-skills/SKILL.md +1 -0
@@ -191,6 +191,155 @@
191
191
  }
192
192
  },
193
193
  "additionalProperties": false
194
+ },
195
+ "env_files": {
196
+ "default": [],
197
+ "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.",
198
+ "type": "array",
199
+ "items": {
200
+ "type": "string",
201
+ "minLength": 1
202
+ }
203
+ },
204
+ "commands": {
205
+ "default": {},
206
+ "type": "object",
207
+ "properties": {
208
+ "lint": {
209
+ "type": "string",
210
+ "minLength": 1
211
+ },
212
+ "typecheck": {
213
+ "type": "string",
214
+ "minLength": 1
215
+ },
216
+ "test": {
217
+ "description": "The whole suite.",
218
+ "type": "string",
219
+ "minLength": 1
220
+ },
221
+ "test_one": {
222
+ "description": "A single test or a subtree. The agent appends the target, so end it accordingly.",
223
+ "type": "string",
224
+ "minLength": 1
225
+ },
226
+ "migrate": {
227
+ "type": "string",
228
+ "minLength": 1
229
+ }
230
+ },
231
+ "additionalProperties": false,
232
+ "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
+ },
234
+ "hooks": {
235
+ "default": "run",
236
+ "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.",
237
+ "type": "string",
238
+ "enum": [
239
+ "run",
240
+ "skip"
241
+ ]
242
+ },
243
+ "services": {
244
+ "default": {},
245
+ "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.",
246
+ "type": "object",
247
+ "propertyNames": {
248
+ "type": "string",
249
+ "minLength": 1,
250
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
251
+ },
252
+ "additionalProperties": {
253
+ "type": "object",
254
+ "properties": {
255
+ "namespace": {
256
+ "type": "string",
257
+ "enum": [
258
+ "index",
259
+ "name"
260
+ ],
261
+ "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."
262
+ },
263
+ "url": {
264
+ "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.",
265
+ "type": "string",
266
+ "minLength": 1
267
+ },
268
+ "url_var": {
269
+ "type": "string",
270
+ "minLength": 1,
271
+ "description": "Env var the lane reads this service’s address from."
272
+ },
273
+ "capacity": {
274
+ "default": 16,
275
+ "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.",
276
+ "type": "integer",
277
+ "minimum": 1,
278
+ "maximum": 9007199254740991
279
+ },
280
+ "create_cmd": {
281
+ "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.",
282
+ "type": "string",
283
+ "minLength": 1
284
+ },
285
+ "reset_cmd": {
286
+ "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.",
287
+ "type": "string",
288
+ "minLength": 1
289
+ }
290
+ },
291
+ "required": [
292
+ "namespace",
293
+ "url_var"
294
+ ],
295
+ "additionalProperties": false,
296
+ "description": "A shared server each lane gets its own namespace inside."
297
+ }
298
+ },
299
+ "compose": {
300
+ "default": {
301
+ "enabled": true,
302
+ "publish": [],
303
+ "shared_volumes": []
304
+ },
305
+ "type": "object",
306
+ "properties": {
307
+ "enabled": {
308
+ "default": true,
309
+ "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.",
310
+ "type": "boolean"
311
+ },
312
+ "publish": {
313
+ "default": [],
314
+ "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>`.",
315
+ "type": "array",
316
+ "items": {
317
+ "type": "string",
318
+ "minLength": 1
319
+ }
320
+ },
321
+ "shared_volumes": {
322
+ "default": [],
323
+ "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.",
324
+ "type": "array",
325
+ "items": {
326
+ "type": "string",
327
+ "minLength": 1
328
+ }
329
+ }
330
+ },
331
+ "additionalProperties": false,
332
+ "description": "How a lane’s compose stack is isolated past `COMPOSE_PROJECT_NAME`."
333
+ },
334
+ "prepare_cmd": {
335
+ "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.",
336
+ "type": "string",
337
+ "minLength": 1
338
+ },
339
+ "setup_cmd": {
340
+ "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.",
341
+ "type": "string",
342
+ "minLength": 1
194
343
  }
195
344
  },
196
345
  "required": [
@@ -219,6 +368,21 @@
219
368
  "minLength": 1,
220
369
  "pattern": "^[a-z0-9][a-z0-9-]*$"
221
370
  },
371
+ "chores": {
372
+ "default": [],
373
+ "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.",
374
+ "type": "array",
375
+ "items": {
376
+ "type": "string",
377
+ "minLength": 1,
378
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
379
+ }
380
+ },
381
+ "reviewer_model": {
382
+ "description": "The model review turns run on when no reviewer is staffed on the roster (§16.4). A review is the step standing between a plan and its merge, so it is not tied to the tier the phase was written at. Absent takes the harness’s own top tier where it names one, and the node’s model where it does not.",
383
+ "type": "string",
384
+ "minLength": 1
385
+ },
222
386
  "max_session_turns": {
223
387
  "default": 12,
224
388
  "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.",
@@ -331,37 +495,218 @@
331
495
  "minLength": 1,
332
496
  "pattern": "^[a-z0-9][a-z0-9-]*$"
333
497
  },
498
+ "additionalProperties": {
499
+ "anyOf": [
500
+ {
501
+ "type": "object",
502
+ "properties": {
503
+ "cmd": {
504
+ "type": "string",
505
+ "minLength": 1,
506
+ "description": "Shell command run in the node’s lane. Not an agent."
507
+ },
508
+ "requires": {
509
+ "default": [],
510
+ "description": "Resource pool ids acquired before the gate runs, in canonical order.",
511
+ "type": "array",
512
+ "items": {
513
+ "type": "string",
514
+ "minLength": 1,
515
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
516
+ }
517
+ },
518
+ "timeout_s": {
519
+ "default": 1800,
520
+ "type": "integer",
521
+ "minimum": 1,
522
+ "maximum": 9007199254740991
523
+ },
524
+ "tuning": {
525
+ "type": "object",
526
+ "properties": {
527
+ "allowed_flags": {
528
+ "minItems": 1,
529
+ "type": "array",
530
+ "items": {
531
+ "type": "string",
532
+ "minLength": 1
533
+ },
534
+ "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."
535
+ }
536
+ },
537
+ "required": [
538
+ "allowed_flags"
539
+ ],
540
+ "additionalProperties": false,
541
+ "description": "What an autonomous retune of this gate may do. Absent — the default — means the command is not the monitor’s to touch."
542
+ },
543
+ "description": {
544
+ "type": "string"
545
+ }
546
+ },
547
+ "required": [
548
+ "cmd"
549
+ ],
550
+ "additionalProperties": false
551
+ },
552
+ {
553
+ "type": "object",
554
+ "properties": {
555
+ "judge": {
556
+ "type": "object",
557
+ "properties": {
558
+ "question": {
559
+ "description": "The question, inline. Exactly one of `question` and `question_ref` is required.",
560
+ "type": "string",
561
+ "minLength": 1
562
+ },
563
+ "question_ref": {
564
+ "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.",
565
+ "type": "string",
566
+ "minLength": 1
567
+ },
568
+ "labels": {
569
+ "default": [
570
+ "yes",
571
+ "no"
572
+ ],
573
+ "description": "The closed set the classifier chooses among. The default makes it a yes/no question.",
574
+ "minItems": 2,
575
+ "type": "array",
576
+ "items": {
577
+ "type": "string",
578
+ "minLength": 1
579
+ }
580
+ },
581
+ "fail_on": {
582
+ "minItems": 1,
583
+ "type": "array",
584
+ "items": {
585
+ "type": "string",
586
+ "minLength": 1
587
+ },
588
+ "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."
589
+ },
590
+ "threshold": {
591
+ "default": 0.5,
592
+ "description": "The gate fails when the `fail_on` labels together score at least this much.",
593
+ "type": "number",
594
+ "minimum": 0,
595
+ "maximum": 1
596
+ },
597
+ "input": {
598
+ "default": "diff",
599
+ "description": "What is judged. `diff` is the lane against the phase’s base, uncommitted work included.",
600
+ "type": "string",
601
+ "enum": [
602
+ "diff"
603
+ ]
604
+ },
605
+ "max_input_bytes": {
606
+ "default": 204800,
607
+ "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.",
608
+ "type": "integer",
609
+ "minimum": 1024,
610
+ "maximum": 9007199254740991
611
+ },
612
+ "on_unavailable": {
613
+ "default": "pass",
614
+ "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.",
615
+ "type": "string",
616
+ "enum": [
617
+ "pass",
618
+ "fail"
619
+ ]
620
+ }
621
+ },
622
+ "required": [
623
+ "fail_on"
624
+ ],
625
+ "additionalProperties": false
626
+ },
627
+ "requires": {
628
+ "default": [],
629
+ "description": "Resource pool ids acquired before the gate runs. Rarely needed: a judge holds no lane resource.",
630
+ "type": "array",
631
+ "items": {
632
+ "type": "string",
633
+ "minLength": 1,
634
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
635
+ }
636
+ },
637
+ "description": {
638
+ "type": "string"
639
+ }
640
+ },
641
+ "required": [
642
+ "judge"
643
+ ],
644
+ "additionalProperties": false
645
+ }
646
+ ],
647
+ "description": "A command gate (`cmd`) or a judge gate (`judge`)."
648
+ }
649
+ },
650
+ "chores": {
651
+ "default": {},
652
+ "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.",
653
+ "type": "object",
654
+ "propertyNames": {
655
+ "type": "string",
656
+ "minLength": 1,
657
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
658
+ },
334
659
  "additionalProperties": {
335
660
  "type": "object",
336
661
  "properties": {
337
- "cmd": {
662
+ "prompt": {
663
+ "description": "The instruction, inline. Exactly one of `prompt` and `prompt_ref` is required.",
338
664
  "type": "string",
339
- "minLength": 1,
340
- "description": "Shell command run in the node’s lane. Not an agent."
665
+ "minLength": 1
341
666
  },
342
- "requires": {
343
- "default": [],
344
- "description": "Resource pool ids acquired before the gate runs, in canonical order.",
345
- "type": "array",
346
- "items": {
347
- "type": "string",
348
- "minLength": 1,
349
- "pattern": "^[a-z0-9][a-z0-9-]*$"
350
- }
667
+ "prompt_ref": {
668
+ "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.",
669
+ "type": "string",
670
+ "minLength": 1
351
671
  },
352
- "timeout_s": {
353
- "default": 1800,
354
- "type": "integer",
355
- "minimum": 1,
356
- "maximum": 9007199254740991
672
+ "skill": {
673
+ "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.",
674
+ "type": "string",
675
+ "minLength": 1
676
+ },
677
+ "session": {
678
+ "default": "main",
679
+ "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.",
680
+ "type": "string",
681
+ "minLength": 1
682
+ },
683
+ "model": {
684
+ "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.",
685
+ "type": "string",
686
+ "minLength": 1
687
+ },
688
+ "when": {
689
+ "default": "before_gate",
690
+ "description": "When the chore runs. `before_gate` — the default — runs it between a passing review and the gates, on the diff that is about to merge. `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`.",
691
+ "type": "string",
692
+ "enum": [
693
+ "before_gate",
694
+ "after_pr"
695
+ ]
696
+ },
697
+ "on_failure": {
698
+ "default": "continue",
699
+ "description": "`continue` — the default — journals a failed chore and moves on to the gates. 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.",
700
+ "type": "string",
701
+ "enum": [
702
+ "continue",
703
+ "fail"
704
+ ]
357
705
  },
358
706
  "description": {
359
707
  "type": "string"
360
708
  }
361
709
  },
362
- "required": [
363
- "cmd"
364
- ],
365
710
  "additionalProperties": false
366
711
  }
367
712
  },
@@ -434,6 +779,15 @@
434
779
  "pattern": "^[a-z0-9][a-z0-9-]*$"
435
780
  }
436
781
  },
782
+ "chores": {
783
+ "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.",
784
+ "type": "array",
785
+ "items": {
786
+ "type": "string",
787
+ "minLength": 1,
788
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
789
+ }
790
+ },
437
791
  "harness": {
438
792
  "description": "Overrides defaults.harness.",
439
793
  "type": "string",
@@ -454,7 +808,8 @@
454
808
  "pattern": "^[a-z0-9][a-z0-9-]*$"
455
809
  },
456
810
  "max_fix_rounds": {
457
- "default": 2,
811
+ "default": 4,
812
+ "description": "Fix rounds a phase may spend before the operator is asked whether to keep going (§16). A round is a fixer turn — one answer to a review that returned blockers or a gate that went red. It is measured in *rounds* rather than findings, so a first review raising four legitimate blockers spends one. When it 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
813
  "type": "integer",
459
814
  "minimum": 0,
460
815
  "maximum": 9007199254740991
@@ -530,13 +885,16 @@
530
885
  "enum": [
531
886
  "spawn_agent",
532
887
  "run_gate",
888
+ "run_chore",
533
889
  "git_branch",
534
890
  "git_merge",
535
891
  "git_push",
536
892
  "open_pr",
537
893
  "write_tracking",
538
894
  "await_human",
539
- "notify"
895
+ "notify",
896
+ "record_decision",
897
+ "grant_fix_rounds"
540
898
  ],
541
899
  "description": "Which catalog verb this effect invokes."
542
900
  },
@@ -594,13 +952,16 @@
594
952
  "enum": [
595
953
  "spawn_agent",
596
954
  "run_gate",
955
+ "run_chore",
597
956
  "git_branch",
598
957
  "git_merge",
599
958
  "git_push",
600
959
  "open_pr",
601
960
  "write_tracking",
602
961
  "await_human",
603
- "notify"
962
+ "notify",
963
+ "record_decision",
964
+ "grant_fix_rounds"
604
965
  ],
605
966
  "description": "Which catalog verb this effect invokes."
606
967
  },
@@ -714,13 +1075,16 @@
714
1075
  "enum": [
715
1076
  "spawn_agent",
716
1077
  "run_gate",
1078
+ "run_chore",
717
1079
  "git_branch",
718
1080
  "git_merge",
719
1081
  "git_push",
720
1082
  "open_pr",
721
1083
  "write_tracking",
722
1084
  "await_human",
723
- "notify"
1085
+ "notify",
1086
+ "record_decision",
1087
+ "grant_fix_rounds"
724
1088
  ],
725
1089
  "description": "Which catalog verb this effect invokes."
726
1090
  },
@@ -164,6 +164,7 @@ For each skill, also classify:
164
164
 
165
165
  - `vinta-managed` — directory name starts with `vinta-` (installed by `vinta-ai-workflows`; can be left alone or refreshed via that CLI).
166
166
  - `foundation-shape` — name matches the Vinta foundation set (`plan-feature`, `create-spec`, `create-qa-use-cases`, `implement-plan`, `implement-phase`, `review-phase`, `integrate-phase`, `amend-plan`, `add-e2e-test`, `add-env-var`, `write-unit-test`). `implement-phase` / `review-phase` / `integrate-phase` are the plan-execution sub-skills co-shipped with `implement-plan`.
167
+ - `integration` — installed by an external tool that the bootstrap's **Integrations** group knows about. The directory carries that tool's marker, such as `pr-review-canvas/` with a `.pr-review-install` file (installed by `pr-review install-skill`). The tool owns it. Record it, and also record `pr-review.config.yml` at the repo root when present, so the bootstrap can default the integration question to `Yes`.
167
168
  - `project-custom` — anything else; written by the team.
168
169
 
169
170
  **Sub-agents** — list every agent file with its name + description:
@@ -23,7 +23,7 @@ This orchestrator runs six sub-skills in order. Each is its own SKILL.md so it c
23
23
  2. [vinta-write-agents-md](../vinta-write-agents-md/SKILL.md) — synthesize the root `AGENTS.md` from the inventory + a focused interview for what the analysis can't see.
24
24
  3. [vinta-derive-subagents](../vinta-derive-subagents/SKILL.md) — author `ai-tools/agents/*.yaml`. Always emits the foundation trio (`implementer`, `reviewer`, `fixer`); adds stack-specific specialists when the user supplies a template for a matched stack.
25
25
  4. [vinta-derive-skills](../vinta-derive-skills/SKILL.md) — author `ai-tools/skills/*/SKILL.md`. Always copies the project-agnostic foundation set (`plan-feature`, `create-spec`, `create-qa-use-cases`) verbatim from its bundled resources. Generates `implement-plan` from a parameterized template using project specifics. Asks the user whether the optional `add-e2e-test` and `add-env-var` skills are needed. Asks for stack-specific templates per matched stack.
26
- 5. [vinta-install-ai-tools-setup](../vinta-install-ai-tools-setup/SKILL.md) — copy the canonical `setup-ai-tools.mjs` into `ai-tools/scripts/`, wire the package script alias, run setup, verify all vendor paths resolve.
26
+ 5. [vinta-install-ai-tools-setup](../vinta-install-ai-tools-setup/SKILL.md) — copy the canonical `setup-ai-tools.mjs` into `ai-tools/scripts/`, wire the package script alias, run setup, verify all vendor paths resolve. Then install the skills of any enabled integration (D.2) through that tool's own CLI.
27
27
  6. [vinta-migrate-plans-specs](../vinta-migrate-plans-specs/SKILL.md) — find any pre-existing implementation plans / feature specs scattered across the repo (`docs/`, `specs/`, `plans/`, root markdown, etc.) and propose moving them to the canonical layout `ai-plans/YYYY-MM-DD-{FEATURE_NAME}_{PLAN|SPEC}.md`. Read-only by default; every rename is gated on per-file user approval. Skipped automatically when the analysis finds no candidates.
28
28
 
29
29
  Each sub-skill returns a short status report. Don't run the next sub-skill until the previous finished cleanly. If a sub-skill fails or surfaces ambiguity, surface that to the user and resolve before continuing.
@@ -144,6 +144,16 @@ Seven skills are part of the foundation set but aren't always needed. Ask explic
144
144
 
145
145
  If the user answers "No" to any of the seven, that skill won't ship. If the user answers "Yes" to `add-e2e-test` / `add-env-var` but doesn't have a template, derive-skills drafts one via interview. `systematic-debugging` is always template-rendered — no per-project drafting interview, just the MCP-server inventory above. `add-one-off-script` is copied verbatim from [vinta-derive-skills/resources/foundation-skills/add-one-off-script/](../vinta-derive-skills/resources/foundation-skills/add-one-off-script/) — its body is project-agnostic; the per-project variability lives in the `skills.add-one-off-script.*` config block above and in env vars consumed at runtime. `prepare-worktree` is also copied verbatim from [vinta-derive-skills/resources/foundation-skills/prepare-worktree/](../vinta-derive-skills/resources/foundation-skills/prepare-worktree/) — same pattern: project-agnostic body, per-project defaults under `skills.prepare-worktree.*`. `thermo-nuclear-code-quality-review` is copied verbatim from [vinta-derive-skills/resources/foundation-skills/thermo-nuclear-code-quality-review/](../vinta-derive-skills/resources/foundation-skills/thermo-nuclear-code-quality-review/) with no follow-up config — its body is fully project-agnostic. `handoff-to-client` is always template-rendered from [vinta-derive-skills/resources/handoff-to-client-template.md](../vinta-derive-skills/resources/handoff-to-client-template.md) — no per-project drafting interview, just the client-handoff config follow-up above.
146
146
 
147
+ ### D.2 Integrations (external tools)
148
+
149
+ Integrations are external tools that ship their own skill. They are not foundation skills: this package bundles none of their content. When one is enabled, the tool's own CLI installs its skill, and this flow only wires that skill in. Ask once per integration:
150
+
151
+ 1. **`pr-review-canvas`** ([PR Review Canvas](https://github.com/vintasoftware/pr-review-canvas), CLI `pr-review`). Should agents generate a review canvas for every PR they open? A canvas is the PR's diff grouped by topic, with attention points for the reviewer. It is shared as a PR comment and read in a local review app (`pr-review serve`). Ask **only when** `project.code_host` is `github` or `gitlab` **and** the `pr_creation` policy (C.4) is `agents-create`. Otherwise set `disabled` without asking: the tool supports only those two hosts, and with `branches-only` no agent opens a PR to attach a canvas to. `AskUserQuestion` options: `Yes — enable`, `No — skip`. Recommend `Yes` for teams that review agent-written PRs, where a large multi-phase diff is the normal case. Default to `Yes` when [vinta-analyze-codebase](../vinta-analyze-codebase/SKILL.md) already found the tool in use: a `pr-review-canvas` skill classified `integration`, or a `pr-review.config.yml`.
152
+
153
+ Mention the requirements in the question: Node.js 22+, a logged-in `gh` or `glab`, and Claude Code or Codex as the agent. Do **not** install the CLI. Like `open-pr.sh`'s `yq` / `jq` / `gh`, it is a machine-level dependency the user installs. [vinta-install-ai-tools-setup](../vinta-install-ai-tools-setup/SKILL.md) checks for it and prints `npm install -g @vintasoftware/pr-review-canvas` when it is missing.
154
+
155
+ No follow-up config. The tool's own settings (sharing on/off, high-risk paths, prompt overrides) live in its `pr-review.config.yml` at the repo root, and the tool owns that file.
156
+
147
157
  ### E. Existing AI artifacts (per-artifact disposition)
148
158
 
149
159
  [vinta-analyze-codebase](../vinta-analyze-codebase/SKILL.md#11-existing-ai-tooling-artifacts)'s **Existing AI-tooling artifacts** scan produces an inventory of **every** AI-tooling artifact already in the repo: instruction docs (AGENTS.md / CLAUDE.md / .cursorrules / .github/copilot-instructions.md / ...), skills under `.claude/skills/`, `.cursor/skills/`, `.codex/skills/`, `.github/skills/`, `.agents/skills/`, `ai-tools/skills/`, and sub-agent files under `.claude/agents/`, `.cursor/agents/`, `.codex/agents/`, `.github/agents/`, `ai-tools/agents/`.
@@ -162,6 +172,8 @@ For each artifact, read it (frontmatter + body), then ask the user via `AskUserQ
162
172
  - `Keep in current vendor path, don't touch` — leaves it where it is. AGENTS.md may reference it; downstream skill setup won't manage it.
163
173
  - `Drop` — delete (rare; usually the user wants to migrate).
164
174
 
175
+ **Integration-owned skills** (name matches an integration under D.2 — today `pr-review-canvas`) get no question. Leave them where they are. Their tool owns them through its own marker file (`.pr-review-install`), and its `doctor` / `upgrade` commands manage them. When the integration is `enabled` and the existing copy sits in a real `.claude/skills/` or `.agents/skills/` directory rather than in `ai-tools/skills/`, tell the user once. [vinta-install-ai-tools-setup](../vinta-install-ai-tools-setup/SKILL.md) installs a fresh copy into `ai-tools/skills/`, so the old copy becomes redundant once the vendor directory is a symlink.
176
+
165
177
  Foundation-shape skills (name matches `plan-feature`, `create-spec`, `create-qa-use-cases`, `implement-plan`, `implement-phase`, `review-phase`, `integrate-phase`, `amend-plan`, `add-e2e-test`, `add-env-var`, `add-one-off-script`, `prepare-worktree`, `thermo-nuclear-code-quality-review`, `deslop-comments`, `handoff`, `handoff-to-client`, `write-unit-test`) get an extra option: `Replace with Vinta foundation version` — overwrites with the canonical foundation content, preserving the user's name. Useful when the existing version is stale. (`implement-phase` / `review-phase` / `integrate-phase` are the plan-execution sub-skills co-shipped with `implement-plan`; replace them as a unit.)
166
178
 
167
179
  - **Sub-agents** (each under any vendor `agents/` dir):
@@ -220,7 +232,7 @@ Captured interview state lands in **one** YAML file at the repo root: `.vinta-ai
220
232
 
221
233
  This file is the **only** source of truth for project-wide settings. Every downstream sub-skill (4 → 6 below) reads from it instead of receiving values via in-conversation state. Every meta-skill ([vinta-sync-ai-tools](../vinta-sync-ai-tools/SKILL.md), [vinta-update-project-skills](../vinta-update-project-skills/SKILL.md)) reads + rewrites it.
222
234
 
223
- Write the file now (before any sub-skill runs). Populate from the interview groups **Scope**, **Stack detection**, **Project conventions**, **Optional foundation skills**, and **Existing AI artifacts**:
235
+ Write the file now (before any sub-skill runs). Populate from the interview groups **Scope**, **Stack detection**, **Project conventions**, **Optional foundation skills**, **Integrations**, and **Existing AI artifacts**:
224
236
 
225
237
  ```yaml
226
238
  # yaml-language-server: $schema=./node_modules/vinta-ai-workflows/schemas/vinta-ai-workflows-config.v1.schema.json
@@ -291,6 +303,9 @@ foundation_agents:
291
303
  reviewer: enabled
292
304
  fixer: enabled
293
305
 
306
+ integrations:
307
+ pr-review-canvas: <Integrations → pr-review-canvas answer → enabled | disabled; disabled without asking when code_host ∉ {github, gitlab} or pr_creation = branches-only>
308
+
294
309
  # Only emit the roles the user chose in the "Agent model tiers" question (C.10).
295
310
  # Omit the whole block on "Leave unset"; omit any individual role left unset.
296
311
  # Each value is a tier (1–4) into ai-tools/skills/plan-feature/resources/ai-models.yaml.
@@ -434,6 +449,7 @@ ai-tools/
434
449
  │ │ └── one_off_script_base.ts
435
450
  │ ├── prepare-worktree/SKILL.md ← optional — only if user opts in (verbatim copy; project-agnostic body, defaults under skills.prepare-worktree.*)
436
451
  │ ├── thermo-nuclear-code-quality-review/SKILL.md ← optional — only if user opts in (verbatim copy; deep structural-maintainability audit, escalation target for review-phase Layer 3)
452
+ │ ├── pr-review-canvas/ ← integration — only when integrations.pr-review-canvas = enabled and `pr-review` is installed. Written by `pr-review install-skill`, never by this flow; owned through its `.pr-review-install` marker, refreshed by `pr-review upgrade`
437
453
  │ ├── run-one-off-script-django/ ← optional sister skill — only when stack matches + user supplies template (authors Jupyter notebook / mgmt command runner + JupyterRuntime / DjangoMgmtRuntime adapter in the per-script folder)
438
454
  │ ├── run-one-off-script-medplum/ ← optional sister skill — only when stack matches + user supplies template (authors Medplum bot + MedplumBotRuntime adapter in the per-script folder)
439
455
  │ └── <stack-specific skills>/SKILL.md ← only if user supplied templates
@@ -138,8 +138,12 @@ Shells under `plan-execution/shell/` are thin — most content lives in `plan-ex
138
138
 
139
139
  **The parallel-lanes seam.** `partials/parallel-lanes.md` carries the DAG scheduler: the graph parse (`DAG_PARSE`), the worktree pool (`LANE_WORKTREE_POOL`), the dependency-derived branch topology (`LANE_TOPOLOGY`), the dispatch loop (`LANE_SCHEDULER`), the tracking-directory schema (`TRACKING_DIR`), and the sibling-lane write guard (`SIBLING_LANE_ISOLATION`, included by `worktree-seam.md#STRAY_WRITE_CHECK`). All six blocks are project-agnostic and carry no new `{{...}}` placeholders of their own beyond ones the shells already substitute (`{{PLAN_DIR}}`, `{{BUILD_CMD}}`, `{{E2E_RUN_OPTION_TRACKING}}`, `{{TRACKING_BRANCH_FIELD}}`, `{{TRACKING_PHASE_BRANCH_FIELD}}`).
140
140
 
141
+ **The dispatched-agent seam.** `partials/dispatched-agent.md` carries the one rule that spans both directions of the unit: the agent doing a phase never dispatches. `CONDUCTOR_ENTRY_GUARD` goes near the top of all four conductors (`implement-plan`, `implement-phase`, `review-phase`, `amend-plan`) and stops an agent that was itself handed one phase — by another conductor, or by [vinta-ai-maestro](https://github.com/vintasoftware/vinta-ai-workflows/tree/main/packages/vinta-ai-maestro) — from re-entering the orchestrator one level down. `NO_NESTED_DISPATCH` is prompt text, spliced into `implementer-prompt.md#INNER_OUTER_LOOP`, so every composed implementer prompt (forward and amend) carries it. Both blocks are project-agnostic and take no `{{...}}` placeholders.
142
+
141
143
  **When `foundation_skills.prepare-worktree` is `disabled`**, parallel execution is impossible (several agents cannot share one working tree). Render the plan-execution unit sequential-only: strip the Step 0 (e) question and the `{If run_options.parallel_phases = true:}` gate from the implementer prompt, and keep the `parallel-lanes` blocks — the scheduler with `max_parallel_lanes = 1` **is** the sequential path, and the DAG still orders the phases correctly. Only the pool provisioning collapses to the single-worktree branch of `WORKROOT_RESOLUTION`.
142
144
 
145
+ **The review-canvas gate stays, whatever the config says.** The PR-context block carries a `{If integrations.pr-review-canvas = enabled in .vinta-ai-workflows.yaml:}` paragraph. Render it verbatim, including the gate. It is a runtime gate that the integrating agent reads, so a team that enables the integration later through [vinta-sync-ai-tools](../vinta-sync-ai-tools/SKILL.md) needs no re-derive. Strip it only when `policies.pr_creation = branches-only`, because then no agent ever runs `open-pr.sh`.
146
+
143
147
  **`ask` → two `integrate-phase` variants, no dual-render.** When `policies.commit_strategy = ask`, render `integrate-phase-template.md` **twice**:
144
148
  - `ai-tools/skills/integrate-phase-stacked/SKILL.md` — `<RESOLVED>` = `stacked`, `{{INTEGRATE_PHASE_NAME}}` = `integrate-phase-stacked`.
145
149
  - `ai-tools/skills/integrate-phase-modular/SKILL.md` — `<RESOLVED>` = `modular`, `{{INTEGRATE_PHASE_NAME}}` = `integrate-phase-modular`.
@@ -343,6 +347,7 @@ Length: 100–300 lines. Shorter = under-specified. Longer = should probably spl
343
347
  - **Foundation set is a unit.** Always copy `plan-feature` + `create-spec` + `open-pr-from-context` together — they reference each other. `create-qa-use-cases` joins only when `add-e2e-test` is enabled; when it's not, strip `plan-feature`'s e2e regions (the `<!-- e2e:start/end -->` pass) so no cross-link dangles.
344
348
  - **The plan-execution unit is generated, not copied.** `implement-plan` + `implement-phase` + `review-phase` + `integrate-phase` + `amend-plan` all have too much project-specific content for verbatim shipping. They are separate files but a **co-shipped unit** — never emit the conductors without the three sub-skills; a conductor whose `[implement-phase]` / `[review-phase]` / integrate link dangles is a broken skill.
345
349
  - **Optional skills (`add-e2e-test`, `add-env-var`, `add-one-off-script`, `prepare-worktree`, `thermo-nuclear-code-quality-review`, `handoff-to-client`) are gated by user answer.** Don't ship them by default. `handoff-to-client` is additionally template-rendered (bucket B) — its config lives under `skills.handoff-to-client.*`.
350
+ - **Integration skills are not yours.** `ai-tools/skills/pr-review-canvas/` (and any other skill an `integrations.*` tool installs) is written and refreshed by that tool's CLI. It carries the tool's own marker. Never copy, render, scrub or delete it here. The tool's `doctor` treats any edit as a stale copy.
346
351
  - **Each skill solves one job.** Two unrelated checklists → split.
347
352
  - **Reference real files in the target.** Skill links must point to existing paths.
348
353
  - **Skills auto-load by description.** Specific triggers, specific outcomes.