vinta-ai-workflows 0.7.0-alpha20 → 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.
- package/CHANGELOG.md +263 -0
- package/README.md +16 -0
- package/package.json +4 -1
- package/schemas/README.md +4 -0
- package/schemas/prs-context-frontmatter.v1.schema.json +18 -7
- package/schemas/vinta-ai-workflows-config.v1.schema.json +12 -0
- package/schemas/workflow.v1.schema.json +161 -42
- package/skills/vinta-analyze-codebase/SKILL.md +1 -0
- package/skills/vinta-bootstrap-ai-tools/SKILL.md +18 -2
- package/skills/vinta-derive-skills/SKILL.md +3 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/plan-feature/SKILL.md +19 -2
- package/skills/vinta-derive-skills/resources/plan-execution/partials/commit-strategy/stacked.md +18 -0
- package/skills/vinta-derive-skills/resources/plan-execution/partials/parallel-lanes.md +8 -1
- package/skills/vinta-derive-skills/resources/plan-execution/partials/pr-context.md +3 -1
- package/skills/vinta-derive-skills/resources/plan-execution/shell/implement-plan-template.md +4 -3
- package/skills/vinta-derive-skills/resources/plan-execution/shell/integrate-phase-template.md +1 -1
- package/skills/vinta-derive-skills/resources/prs-context-template.md +5 -4
- package/skills/vinta-install-ai-tools-setup/SKILL.md +28 -1
- package/skills/vinta-sync-ai-tools/SKILL.md +2 -1
- package/skills/vinta-update-project-skills/SKILL.md +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,202 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
30
30
|
|
|
31
31
|
### Added
|
|
32
32
|
|
|
33
|
+
- **PR Review Canvas integration (`integrations.pr-review-canvas`).** The
|
|
34
|
+
bootstrap now offers [PR Review Canvas](https://github.com/vintasoftware/pr-review-canvas)
|
|
35
|
+
as its first *integration*: an external tool that installs its own skill.
|
|
36
|
+
Nothing from it is bundled here. A new optional top-level `integrations`
|
|
37
|
+
object in `vinta-ai-workflows-config.v1.schema.json` records the opt-in
|
|
38
|
+
(`enabled` / `disabled`). It is additive, so no schema major bump. The
|
|
39
|
+
question is interview group D.2. It is asked only when `code_host` is
|
|
40
|
+
GitHub or GitLab and `pr_creation` is `agents-create`.
|
|
41
|
+
When it is enabled:
|
|
42
|
+
- `vinta-install-ai-tools-setup` step 7b runs
|
|
43
|
+
`pr-review install-skill --claude-dir ai-tools/skills --codex-dir ai-tools/skills`.
|
|
44
|
+
It writes one tool-owned copy that every vendor symlink reaches, then
|
|
45
|
+
runs `pr-review doctor`. The CLI is never auto-installed; the step prints
|
|
46
|
+
`npm install -g @vintasoftware/pr-review-canvas` instead.
|
|
47
|
+
- The `integrate-phase` PR step runs `/pr-review-canvas <n>` after
|
|
48
|
+
`open-pr.sh` publishes. This is a runtime gate in `partials/pr-context.md`,
|
|
49
|
+
so enabling the integration later needs no re-derive.
|
|
50
|
+
- `plan-feature` adds an `after_pr` `review-canvas` chore to the maestro
|
|
51
|
+
workflow it writes.
|
|
52
|
+
|
|
53
|
+
`vinta-analyze-codebase`, `vinta-derive-skills` and
|
|
54
|
+
`vinta-update-project-skills` treat an integration-owned skill (marker
|
|
55
|
+
`.pr-review-install`) as the tool's and never diff or edit it. Refresh it
|
|
56
|
+
with `pr-review upgrade`. `vinta-sync-ai-tools` offers the integration to
|
|
57
|
+
existing projects as an `opt-in-offer`.
|
|
58
|
+
- **Maestro: System One classifiers (SPEC §17).** A run can now consult a
|
|
59
|
+
fast classifier: one that answers yes/no or scores a fixed set of labels.
|
|
60
|
+
Three things use it.
|
|
61
|
+
- **Configuration.** `run`, `serve` and `doctor` take
|
|
62
|
+
`--system-one <config.json>`. The file lives on the operator's machine and
|
|
63
|
+
is never part of the plan. It names the classifier adapter: `http` (POST,
|
|
64
|
+
bearer key read from the env var you name) or `command` (a local process,
|
|
65
|
+
JSON on stdin and stdout). Out-of-tree adapters register through
|
|
66
|
+
`registerSystemOneAdapter`. The file also turns on the built-in judges.
|
|
67
|
+
This is the only API key the package uses; LLM harnesses still run on your
|
|
68
|
+
logged-in CLIs.
|
|
69
|
+
- **Judge gates.** A workflow gate can be
|
|
70
|
+
`{ "judge": { "question" | "question_ref", "labels", "fail_on",
|
|
71
|
+
"threshold", "on_unavailable" } }` instead of `{ "cmd": … }`. It asks the
|
|
72
|
+
plan's question about the lane's diff and reports exit 0 or 1, so
|
|
73
|
+
pipelines, the fix loop and the fixer prompt are unchanged. A judge gate
|
|
74
|
+
can only fail a phase, never pass one that a command gate failed. Agents
|
|
75
|
+
are not told to run judge gates. Without `--system-one`, a judge gate
|
|
76
|
+
falls back to its `on_unavailable` setting (`pass` by default).
|
|
77
|
+
`schemas/workflow.v1.schema.json` is regenerated (`gates` values are now
|
|
78
|
+
`anyOf` command | judge). The change is additive: existing gates are
|
|
79
|
+
unchanged.
|
|
80
|
+
- **Gate triage** (`judges.gate_triage`). When a command gate fails, the
|
|
81
|
+
classifier reads the end of its log. If it calls the failure flaky or
|
|
82
|
+
environmental, the gate is rerun once before a fixer is spent. The result
|
|
83
|
+
is exposed as `gate.triage`.
|
|
84
|
+
- **`--permission judged`** (claude-code only). Agents run without vendor
|
|
85
|
+
prompts. A `PreToolUse` hook asks the classifier about each judged tool
|
|
86
|
+
call (default `Bash`) through a new `POST /api/runs/:runId/permission`
|
|
87
|
+
endpoint. It fails closed at every step. It needs `judges.permission` in
|
|
88
|
+
the config. `doctor` refuses it for codex or opencode phases. This is a
|
|
89
|
+
speed trade-off, not a sandbox.
|
|
90
|
+
|
|
91
|
+
Every judgement is journalled as `system_one_judged`, with labels and
|
|
92
|
+
scores only, never the diff, log or command. With a hosted classifier,
|
|
93
|
+
diffs, gate logs and shell commands leave the machine. Use the `command`
|
|
94
|
+
adapter with a local model where that is not acceptable.
|
|
95
|
+
- **Maestro: chores that run after the PR opens (`when: "after_pr"`).** A
|
|
96
|
+
chore can now declare `when`. The default, `before_gate`, keeps today's
|
|
97
|
+
`polish` timing. `after_pr` runs the chore at the end of `standard-phase`'s
|
|
98
|
+
`integrate` state, after `open_pr`, for work about the PR rather than the
|
|
99
|
+
diff, such as a review canvas. `open_pr` now states `pr.opened` / `pr.url` /
|
|
100
|
+
`pr.number` as facts, so a guard can read them, and the `pr` root joins the
|
|
101
|
+
guard context. An `after_pr` chore's prompt names the PR and forbids
|
|
102
|
+
editing, committing or pushing. It is skipped when no PR opened. The
|
|
103
|
+
validator refuses `after_pr` with `on_failure: "fail"`, because the phase is
|
|
104
|
+
already merged. `run_chore` takes a `when` param. Custom pipelines that
|
|
105
|
+
call it without one keep running the `before_gate` chores.
|
|
106
|
+
`schemas/workflow.v1.schema.json` is regenerated, and the change is
|
|
107
|
+
additive.
|
|
108
|
+
- **Maestro: the node view links the phase's PR.** `GET
|
|
109
|
+
/api/runs/:runId/nodes/:nodeId` now carries `pullRequest` (`opened`, `url`,
|
|
110
|
+
`number`, `reason`), read from the latest `node_pr` event. The **Changes**
|
|
111
|
+
card shows `#<n>` as a link, or says why no PR opened (`gh` missing or
|
|
112
|
+
failed). Only an `https://` URL becomes a link. The link carries
|
|
113
|
+
`rel="noreferrer noopener"` because the page URL holds the daemon token.
|
|
114
|
+
- **The node view shows what a phase changed, and the whole diff on request.**
|
|
115
|
+
The *Diff* panel used to print `git diff base...branch` for the operator to
|
|
116
|
+
paste into a terminal. It is now a *Changes* card: how many files changed
|
|
117
|
+
and how many lines were added and removed, then each file — up to ten, the
|
|
118
|
+
rest as a count — with its status mark (`A`, `M`, `D`, `R`, `?` for a file
|
|
119
|
+
not yet added to git), its own `+N −M`, and a five-block bar that compares
|
|
120
|
+
the files to each other at a glance. *View full diff* opens a new route,
|
|
121
|
+
`#/runs/<run>/nodes/<node>/changes`, that renders the unified diff with
|
|
122
|
+
syntax highlighting (shiki, GitHub light and dark following the theme),
|
|
123
|
+
old and new line numbers, hunk headers with their function context, a
|
|
124
|
+
sticky file list that scrolls to each file, and a fold on any file past 400
|
|
125
|
+
changed lines. Within a removed line and the added line it pairs with, the
|
|
126
|
+
words that differ are marked, so a flipped operator or a renamed variable
|
|
127
|
+
reads at a glance; a line rewritten rather than edited keeps the row colour
|
|
128
|
+
alone. *Unified* or *Split* — the old file beside the new — is a toggle in
|
|
129
|
+
the header, remembered per browser. The daemon serves it from a new endpoint,
|
|
130
|
+
`GET /api/runs/:runId/nodes/:nodeId/changes`, answered by the git unit: from
|
|
131
|
+
the lane's **working tree** while the lane still holds the phase's branch —
|
|
132
|
+
so an agent's uncommitted edits and untracked files are visible while it is
|
|
133
|
+
working — and from the branch itself once the lane has moved on. The card
|
|
134
|
+
polls the counts on the node view's cadence and never the patch; the diff
|
|
135
|
+
view reads the patch once, and again on a button or when the run moves. A
|
|
136
|
+
patch over 2 MiB is cut at a file boundary and says so; the per-file counts
|
|
137
|
+
are complete whatever its size. A daemon older than this browser gets the
|
|
138
|
+
reference — branch, base, lane — as before. (`vinta-ai-maestro`)
|
|
139
|
+
|
|
140
|
+
- **The transcript reads like the harnesses do.** An agent's prose is
|
|
141
|
+
rendered as the markdown it is — headings, lists, inline code and fenced
|
|
142
|
+
blocks, the blocks highlighted like the diff — instead of as its asterisks;
|
|
143
|
+
raw HTML in it stays text. Tool calls are a verb and a target on one line —
|
|
144
|
+
`Read` and a path, `Shell` and a command, `Edit` and a path with `+N −M`
|
|
145
|
+
beside it — whatever the harness named the tool (`Bash`, `bash`,
|
|
146
|
+
`command_execution` and an MCP server's `read_file` all read as what they
|
|
147
|
+
are), with the result's verdict as a dot at the end of the row because the
|
|
148
|
+
result now sits under the call it answers. Open, an edit is a diff of its
|
|
149
|
+
two sides, a write is the file, a shell call is the command and what it
|
|
150
|
+
printed, a failed one with a red edge. A stretch of consecutive reads,
|
|
151
|
+
searches and listings is one row — *Explored · 3 reads, 2 searches* — that
|
|
152
|
+
opens into the calls. The operator's own message is set apart in a tinted
|
|
153
|
+
block. The transcript panel also takes the height the window leaves it
|
|
154
|
+
rather than a fixed 480px. (`vinta-ai-maestro`)
|
|
155
|
+
|
|
156
|
+
- **A plan's PRs now reach `main`.** Phase PRs alone did not: a phase with
|
|
157
|
+
several dependencies targets its `integ-<id>` branch, and nothing ever
|
|
158
|
+
targeted that branch, so every PR stacked above it was stuck. Conflict
|
|
159
|
+
resolutions from wave merges were on no phase branch at all. maestro and the
|
|
160
|
+
stacked-branches `implement-plan` now open two more kinds of PR:
|
|
161
|
+
- an **integration PR** per `integ-<id>` branch, into `base_branch`, opened
|
|
162
|
+
by that phase just before its own PR;
|
|
163
|
+
- a **plan PR** from the final wave branch into `base_branch`, opened once
|
|
164
|
+
the last wave merges. Its body lists every phase and integration PR in an
|
|
165
|
+
order that merges, and the two ways to land the plan: merge it alone, or
|
|
166
|
+
merge the listed PRs in order and this one last.
|
|
167
|
+
|
|
168
|
+
maestro also pushes every wave branch, journals the new PRs (`node_pr` gains
|
|
169
|
+
`kind`, and a new `run_pr` event), and treats `gh`'s "a pull request already
|
|
170
|
+
exists" as opened, with that PR's URL, so a retry no longer reports a failure.
|
|
171
|
+
`prs-context-frontmatter.v1` gains an optional `kind`
|
|
172
|
+
(`phase` | `integration` | `plan`); `phase_id` and `phase_title` are no
|
|
173
|
+
longer required for `kind: plan`.
|
|
174
|
+
- **The schemas are checked.** `npm run validate-schemas` (and `npm test`)
|
|
175
|
+
compiles every schema under `schemas/` with Ajv in strict Draft 2020-12 mode
|
|
176
|
+
and checks the fixtures in `tests/schema-fixtures/`: `valid/` must pass,
|
|
177
|
+
`invalid/` must fail. Source-side only — `ajv` and `ajv-formats` are root
|
|
178
|
+
devDependencies, and nothing new ships.
|
|
179
|
+
- **The plan graph is readable, and says where you are in it.** The canvas
|
|
180
|
+
`vinta-ai-maestro`'s run view and workflow editor share (`vinta-dag-editor`)
|
|
181
|
+
was a strip of six-pixel cards: a five-wave plan was framed into a 380px box
|
|
182
|
+
at half scale, every dependency label was a bordered box truncated to "the
|
|
183
|
+
BookmarkFolder…" piled on the line it labelled, edges had no direction, and
|
|
184
|
+
nothing changed when a node was picked. Redrawn: edges carry arrowheads and
|
|
185
|
+
the ones touching the selected or hovered node are pulled forward while the
|
|
186
|
+
rest recede; cards show a status dot and label (running pulses; failed and
|
|
187
|
+
awaiting-human tint the whole card) and clamp a long name to two lines;
|
|
188
|
+
labels are quiet pills in the gap between waves and spread apart when two
|
|
189
|
+
share one; the zoom and fit controls are icons with tooltips; an empty canvas
|
|
190
|
+
says so. **Long text is reachable three ways:** an instant tooltip on hover
|
|
191
|
+
or keyboard focus for a clamped name or truncated artifact, a label that
|
|
192
|
+
grows to its full text when hovered or selected, and a details strip under
|
|
193
|
+
the canvas — in the run view too — printing the selected node's name and
|
|
194
|
+
status or the selected dependency as a sentence. Keyboard: `+`/`−`/`0` zoom
|
|
195
|
+
and fit, Enter on the selected node opens it. Double-clicking a node in the
|
|
196
|
+
run view opens its node view (`vinta-dag-node-activate`).
|
|
197
|
+
- **Dependencies are drawn by dragging.** Drag from a node's handle onto
|
|
198
|
+
another node; a dashed preview follows the pointer, the source is ringed, and
|
|
199
|
+
every card says whether it would take the drop — a cycle, a duplicate, or the
|
|
200
|
+
source itself is dimmed and refused before the gesture is spent. Click-then-
|
|
201
|
+
pick and the `e` key still work. A refused dependency now says which rule it
|
|
202
|
+
broke (`vinta-dag-refuse`), and the editor shows that where the gesture
|
|
203
|
+
happened instead of nothing.
|
|
204
|
+
|
|
205
|
+
### Changed
|
|
206
|
+
|
|
207
|
+
- **The canvas no longer swallows the page's scroll.** A bare wheel over the
|
|
208
|
+
graph used to zoom it and block the page from scrolling; it now scrolls the
|
|
209
|
+
page, and zoom is ⌘ or Ctrl + scroll (which is also how a trackpad pinch
|
|
210
|
+
arrives), the buttons, or the keys. Dragging the canvas shows a grab cursor
|
|
211
|
+
and never selects text. The host box follows the window's height instead of
|
|
212
|
+
being fixed at 380px.
|
|
213
|
+
|
|
214
|
+
### Fixed
|
|
215
|
+
|
|
216
|
+
- **The editor's canvas inspector no longer offers fields it then discards.**
|
|
217
|
+
Its Status and Wave fields were folded back into the workflow and silently
|
|
218
|
+
dropped — status is run state and wave is computed from dependencies — and
|
|
219
|
+
Name duplicated the field in the panel beside the graph. They are hidden in
|
|
220
|
+
the editor through the parts the component now exposes, leaving the
|
|
221
|
+
inspector the one thing only it can do: a dependency's artifact and the
|
|
222
|
+
delete actions.
|
|
223
|
+
- **Picking a dependency after a node no longer loses it on the next frame.**
|
|
224
|
+
The host reported an edge selection as "no node", pushed that back into the
|
|
225
|
+
canvas, and cleared the edge the moment it was picked; the edge's inspector
|
|
226
|
+
flashed and vanished. A cleared node selection now leaves an edge selection
|
|
227
|
+
alone.
|
|
228
|
+
- **Pressing on the canvas's toolbar or inspector no longer pans the graph.**
|
|
33
229
|
- **A timed-out gate says whether it hung or was slow.** A `gate_result` with
|
|
34
230
|
`status: timed_out` used to carry only the status, so a run with nine
|
|
35
231
|
timeouts could not say whether any of them was a stuck suite or a host too
|
|
@@ -576,6 +772,58 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
576
772
|
|
|
577
773
|
### Changed
|
|
578
774
|
|
|
775
|
+
- **maestro's review/fix loop is now the thermo-nuclear review loop.** The
|
|
776
|
+
shipped `standard-phase` changes for every run that uses it:
|
|
777
|
+
- **The reviewer** keeps its session across rounds. It is held to a shipped
|
|
778
|
+
Review Standard (evidence bar, priority order, approval bar), or to the
|
|
779
|
+
project's `REVIEW.md`, which replaces that standard when the lane has one.
|
|
780
|
+
`VERDICT: pass` is now an explicit approval. A re-review gets the fixer's
|
|
781
|
+
report, every finding already rejected or settled, and the pass-two rules.
|
|
782
|
+
- **The orchestrator checks that a review turn changed nothing.** If `HEAD`
|
|
783
|
+
or a tracked file moved, the node stops and asks whether to keep the edits
|
|
784
|
+
or stop the phase.
|
|
785
|
+
- **The fixer** (the implementer continuing its session) verifies each
|
|
786
|
+
finding before acting on it and rejects unsupported ones with
|
|
787
|
+
counter-evidence. It sends scope decisions to the operator instead of
|
|
788
|
+
making them: unreachable scenarios, defensive checks, requirements
|
|
789
|
+
ambiguities, destructive operations. The new `consult` state asks about the
|
|
790
|
+
whole batch at once and does not count as a fix round.
|
|
791
|
+
- **The review ledger.** Rejections and the operator's answers are kept in
|
|
792
|
+
`runs/<run-id>/nodes/<node-id>/review-ledger.jsonl` and shown to every
|
|
793
|
+
later review and fix.
|
|
794
|
+
- **The budget.** `max_fix_rounds` now defaults to **4** (was 2). Running out
|
|
795
|
+
asks `continue` / `stop` instead of failing the phase.
|
|
796
|
+
- **Unattended runs.** Under `--retry-after`, a scope question nobody answers
|
|
797
|
+
takes each item's default, and an exhausted budget nobody answers takes
|
|
798
|
+
`stop`, which fails into `--on-failure` as before. Without `--retry-after`
|
|
799
|
+
both questions wait for a person.
|
|
800
|
+
- **The reviewer's model.** An unstaffed review runs on the new optional
|
|
801
|
+
`defaults.reviewer_model`, then the harness's top tier (`opus` on
|
|
802
|
+
claude-code), then the phase's model.
|
|
803
|
+
- **New pieces.** `await_human` gains an optional `unattended_answer`, and
|
|
804
|
+
there are two new effects, `record_decision` and `grant_fix_rounds`; the
|
|
805
|
+
schema stays at `workflow.v1`.
|
|
806
|
+
- **Migration.** A workflow that pins `max_fix_rounds` keeps its number but
|
|
807
|
+
now gets asked at the end of it. One that ships its own `standard-phase`
|
|
808
|
+
shadows the built-in and is unaffected. The skills-path review partials
|
|
809
|
+
are not updated yet (SPEC §16.6). See
|
|
810
|
+
[SPEC §16](packages/vinta-ai-maestro/SPEC.md#16-the-review-loop).
|
|
811
|
+
|
|
812
|
+
- **maestro starts one session at a time per harness, and remembers the
|
|
813
|
+
account's limit.** Sessions used to boot simultaneously up to the harness
|
|
814
|
+
ceiling, and N Claude Code CLIs starting together contend for the same local
|
|
815
|
+
config and OAuth token: a probe against 2.1.274 measured time to `init`
|
|
816
|
+
rising from 1.2 s with two simultaneous starts to 4.8 s with sixteen, against
|
|
817
|
+
a flat 0.7 s when each waited for the previous one. A spawn now waits until
|
|
818
|
+
the one ahead of it has a session or a refusal, so a refusal also parks every
|
|
819
|
+
queued spawn before it spends one. The ceiling AIMD discovers after a
|
|
820
|
+
`concurrency` or `rate_limit` refusal is kept per harness in `flow.db` and a
|
|
821
|
+
run started within six hours opens at it instead of re-learning the limit by
|
|
822
|
+
being refused; clean spawns still probe back up to the configured value, and
|
|
823
|
+
the hint is dropped once the ceiling recovers or goes stale. When a harness
|
|
824
|
+
is throttled, the freed slot goes to the node with the longest chain of work
|
|
825
|
+
still in front of it rather than to whichever queued first.
|
|
826
|
+
|
|
579
827
|
- **Updated the OpenAI and Anthropic models to their latest version.**
|
|
580
828
|
|
|
581
829
|
- **Updated the generated skills to have `disable-model-invocation: true` set.**
|
|
@@ -826,6 +1074,21 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
826
1074
|
|
|
827
1075
|
### Fixed
|
|
828
1076
|
|
|
1077
|
+
- **maestro asks you to log in instead of failing on a logged-out harness.**
|
|
1078
|
+
A CLI that was not logged in (or whose token was rejected) was `fatal`: the
|
|
1079
|
+
node failed and its whole subtree blocked. It now parks in `awaiting_human`
|
|
1080
|
+
on a "not logged in" question answered with `logged in` or `stop`, holding
|
|
1081
|
+
its lane. Nothing answers it for you — no automatic or unattended retry,
|
|
1082
|
+
regardless of `onFailure` — and one `logged in` resumes every node that
|
|
1083
|
+
harness refused. Adapters report it as the new `unauthenticated` refusal
|
|
1084
|
+
kind (Claude Code, Codex and opencode).
|
|
1085
|
+
- **maestro no longer fails a node on an organization spend cap.** Claude
|
|
1086
|
+
Code's `billing_error` "spend limit reached (daily; resets …)", the
|
|
1087
|
+
`org_spend_cap_reached` overage reason and "usage credit limit reached"
|
|
1088
|
+
matched no refusal pattern and were classified `fatal`, failing the node and
|
|
1089
|
+
blocking its subtree. They are now `quota` waits, and the stated
|
|
1090
|
+
`resets YYYY-MM-DD HH:MM UTC` time is honored as the wake time.
|
|
1091
|
+
|
|
829
1092
|
- **A failed Claude Code turn no longer reads as `claude-code result:
|
|
830
1093
|
success`.** When the CLI ends a turn with `is_error: true` beside
|
|
831
1094
|
`subtype: "success"` (how a plan limit arrives, for one), the recorded
|
package/README.md
CHANGED
|
@@ -292,6 +292,22 @@ Land at `ai-tools/agents/<name>.yaml` (canonical YAML; `setup-ai-tools.mjs` emit
|
|
|
292
292
|
|
|
293
293
|
Stack templates may add specialists like `migration-author` (Django) or `deploy-author` (Medplum) — see disclaimer above.
|
|
294
294
|
|
|
295
|
+
### Integrations (external tools)
|
|
296
|
+
|
|
297
|
+
Integrations are external tools that ship their own skill. The bootstrap asks about each one. When one is enabled, the tool's own CLI installs and updates its skill. This package never copies, renders or edits that skill. Enabling one is recorded under `integrations` in `.vinta-ai-workflows.yaml`.
|
|
298
|
+
|
|
299
|
+
| Integration | What it adds |
|
|
300
|
+
|---|---|
|
|
301
|
+
| [`pr-review-canvas`](https://github.com/vintasoftware/pr-review-canvas) | A review canvas for every PR an agent opens: the diff grouped by topic, with attention points for the reviewer, shared as a PR comment and read in a local app (`pr-review serve`). `integrate-phase` runs `/pr-review-canvas <n>` after `open-pr.sh` publishes the PR. `plan-feature` adds an `after_pr` `review-canvas` chore so that [vinta-ai-maestro](packages/vinta-ai-maestro/README.md#chores-about-the-pr--when-after_pr) does the same on each phase PR. Offered only for GitHub / GitLab projects where agents open PRs. |
|
|
302
|
+
|
|
303
|
+
The tool itself is a machine-level install, like `gh`, and the bootstrap never installs it:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
npm install -g @vintasoftware/pr-review-canvas
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
When the CLI is present, the bootstrap runs `pr-review install-skill --claude-dir ai-tools/skills --codex-dir ai-tools/skills`, so one copy in `ai-tools/skills/pr-review-canvas/` serves every vendor. Refresh it with `pr-review upgrade`, and check it with `pr-review doctor`. Do not edit the copy: the tool stamps a hash into it and reports an edited copy as stale. Sharing and other settings live in the tool's own `pr-review.config.yml`.
|
|
310
|
+
|
|
295
311
|
## Staying in sync with upstream
|
|
296
312
|
|
|
297
313
|
Bootstrap is a snapshot. `vinta-ai-workflows` keeps shipping — new foundation skills, refined templates, sharper agent prompts, schema additions, stack support, best-practice updates lifted from real projects. Pulling those into a previously-bootstrapped repo is a first-class flow, not an afterthought. **This is one of the most important capabilities of the package.**
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vinta-ai-workflows",
|
|
3
|
-
"version": "0.7.0-
|
|
3
|
+
"version": "0.7.0-alpha21",
|
|
4
4
|
"description": "Bootstrap AI tooling (AGENTS.md, skills, sub-agents) across Claude Code, Codex, Cursor, and VS Code + GitHub Copilot.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"vinta-ai-workflows": "./vinta-ai-workflows.mjs"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
|
+
"ajv": "^8.20.0",
|
|
11
|
+
"ajv-formats": "^3.0.1",
|
|
10
12
|
"yaml": "^2.4.0"
|
|
11
13
|
},
|
|
12
14
|
"files": [
|
|
@@ -29,6 +31,7 @@
|
|
|
29
31
|
"license": "MIT",
|
|
30
32
|
"scripts": {
|
|
31
33
|
"check-ai-models": "node scripts/check-ai-models.mjs",
|
|
34
|
+
"validate-schemas": "node scripts/validate-schemas.mjs",
|
|
32
35
|
"test": "node --test tests/*.test.mjs"
|
|
33
36
|
}
|
|
34
37
|
}
|
package/schemas/README.md
CHANGED
|
@@ -90,3 +90,7 @@ done
|
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
For PR-context frontmatter, extract the YAML block first (`yq` does this cleanly), then validate.
|
|
93
|
+
|
|
94
|
+
## Checking the schemas themselves (this repo only)
|
|
95
|
+
|
|
96
|
+
`npm run validate-schemas` (also part of `npm test`) compiles every schema here under Ajv's strict Draft 2020-12 mode and checks the fixtures in `tests/schema-fixtures/<schema-file-stem>/`: everything under `valid/` must pass, everything under `invalid/` must fail. A fixture is `.json`, `.yaml` / `.yml`, or a `.md` file whose YAML frontmatter is the payload. **A conditional rule (`if` / `then`) needs an `invalid/` fixture for each branch it enforces** — a lenient validator ignores a broken one silently. Pass file paths instead to validate payloads against the schema their `$schema` key or `yaml-language-server` comment names.
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
"schema_version",
|
|
9
9
|
"plan_id",
|
|
10
10
|
"feature_name",
|
|
11
|
-
"phase_id",
|
|
12
|
-
"phase_title",
|
|
13
11
|
"branch",
|
|
14
12
|
"base",
|
|
15
13
|
"created_at",
|
|
@@ -34,24 +32,30 @@
|
|
|
34
32
|
"pattern": "^[A-Z][A-Z0-9_]*$",
|
|
35
33
|
"minLength": 1
|
|
36
34
|
},
|
|
35
|
+
"kind": {
|
|
36
|
+
"description": "Which PR this file opens. `phase` (the default when absent): one phase's branch into its dependency-derived base. `integration`: a multi-dependency phase's `plan/<plan-id>/integ-<phase-id>` branch into the default branch, so the phase PR stacked on it can land. `plan`: the final wave branch (or, under modular commits, the plan branch) into the default branch — the PR that lands the whole plan.",
|
|
37
|
+
"type": "string",
|
|
38
|
+
"enum": ["phase", "integration", "plan"],
|
|
39
|
+
"default": "phase"
|
|
40
|
+
},
|
|
37
41
|
"phase_id": {
|
|
38
|
-
"description": "Phase id verbatim from the plan's `Phased Rollout` section (e.g. `1`, `4a`, `2.5`).",
|
|
42
|
+
"description": "Phase id verbatim from the plan's `Phased Rollout` section (e.g. `1`, `4a`, `2.5`). Required unless `kind` is `plan`; for `kind: integration` it is the phase whose `integ-` branch this is.",
|
|
39
43
|
"type": "string",
|
|
40
44
|
"pattern": "^[0-9]+(\\.[0-9]+)?[a-z]?$",
|
|
41
45
|
"minLength": 1
|
|
42
46
|
},
|
|
43
47
|
"phase_title": {
|
|
44
|
-
"description": "Phase title verbatim from the plan's `Phased Rollout` section.",
|
|
48
|
+
"description": "Phase title verbatim from the plan's `Phased Rollout` section. Required unless `kind` is `plan`.",
|
|
45
49
|
"type": "string",
|
|
46
50
|
"minLength": 1
|
|
47
51
|
},
|
|
48
52
|
"branch": {
|
|
49
|
-
"description": "Source branch the PR opens from.
|
|
53
|
+
"description": "Source branch the PR opens from. `plan/<plan-id-kebab>/phase-<phase.id>` for a phase, `plan/<plan-id-kebab>/integ-<phase.id>` for an integration PR, the final `plan/<plan-id-kebab>/wave-<N>` (or `plan/<plan-id-kebab>` under modular commits) for the plan PR.",
|
|
50
54
|
"type": "string",
|
|
51
55
|
"minLength": 1
|
|
52
56
|
},
|
|
53
57
|
"base": {
|
|
54
|
-
"description": "Target branch.
|
|
58
|
+
"description": "Target branch. A phase PR targets its dependency-derived base (the default branch, a dependency's phase branch, or its `integ-` branch); integration and plan PRs target the default branch.",
|
|
55
59
|
"type": "string",
|
|
56
60
|
"minLength": 1
|
|
57
61
|
},
|
|
@@ -73,5 +77,12 @@
|
|
|
73
77
|
{ "const": "" }
|
|
74
78
|
]
|
|
75
79
|
}
|
|
76
|
-
}
|
|
80
|
+
},
|
|
81
|
+
"allOf": [
|
|
82
|
+
{
|
|
83
|
+
"description": "Every PR but the plan PR belongs to one phase.",
|
|
84
|
+
"if": { "not": { "properties": { "kind": { "const": "plan" } }, "required": ["kind"] } },
|
|
85
|
+
"then": { "required": ["phase_id", "phase_title"] }
|
|
86
|
+
}
|
|
87
|
+
]
|
|
77
88
|
}
|
|
@@ -250,6 +250,18 @@
|
|
|
250
250
|
"fixer": { "type": "string", "enum": ["enabled", "disabled"] }
|
|
251
251
|
}
|
|
252
252
|
},
|
|
253
|
+
"integrations": {
|
|
254
|
+
"description": "Per-integration opt-in for external tools that ship their own skills, as opposed to foundation skills this package bundles. An integration's skill is installed by that tool's own CLI into `ai-tools/skills/<name>/`, carries the tool's own ownership marker, and is never copied, rendered or refreshed by `vinta-derive-skills` / `vinta-update-project-skills`. Use `enabled` / `disabled`; `disabled` is a sticky opt-out, as for `foundation_skills`. Absent means the integration was never offered (projects bootstrapped before it existed).",
|
|
255
|
+
"type": "object",
|
|
256
|
+
"additionalProperties": { "type": "string", "enum": ["enabled", "disabled"] },
|
|
257
|
+
"properties": {
|
|
258
|
+
"pr-review-canvas": {
|
|
259
|
+
"description": "PR Review Canvas (`@vintasoftware/pr-review-canvas`, CLI `pr-review`): a topic-grouped review canvas for each PR, posted as a PR comment and read in a local review app. When `enabled`: `vinta-install-ai-tools-setup` runs `pr-review install-skill` into `ai-tools/skills/` (the CLI itself is a global install the user does, never auto-installed); the `integrate-phase` PR step runs the `pr-review-canvas` skill after `open-pr.sh` publishes a PR; and `plan-feature` adds an `after_pr` `review-canvas` chore to the maestro workflow it writes. Posting the canvas comment follows the tool's own `sharing.canvasComment` setting in `pr-review.config.yml`.",
|
|
260
|
+
"type": "string",
|
|
261
|
+
"enum": ["enabled", "disabled"]
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
},
|
|
253
265
|
"agent_models": {
|
|
254
266
|
"description": "Per-role / per-task model selection for the plan-execution family, expressed as **tiers** (1–4) into the same table `plan-feature` uses — `ai-tools/skills/plan-feature/resources/ai-models.yaml`. Each consuming skill resolves a tier to a concrete model at runtime: take that tier's `models`, filter to the vendors the runtime exposes, pick the cheapest survivor. This keeps the config free of hard-coded model IDs (which go stale) — the nightly `check-ai-models` job keeps the tier table fresh. Every key is optional; an unset key (or the whole section absent) means the spawn uses the runtime's default model, i.e. today's behavior. These are the **project defaults**: the `reviewer` / `fixer` tiers here can be overridden per phase by a plan's optional `**Review models**:` line (a phase override wins over the value here for that phase only — see `plan-feature`). The per-phase implementer model is NOT here — that stays owned by each plan phase's `**Suggested AI model**:` line. The mechanical steps (`worktree_prep`, `integrate`) are never plan-named.",
|
|
255
267
|
"type": "object",
|
|
@@ -378,6 +378,11 @@
|
|
|
378
378
|
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
379
379
|
}
|
|
380
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
|
+
},
|
|
381
386
|
"max_session_turns": {
|
|
382
387
|
"default": 12,
|
|
383
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.",
|
|
@@ -491,56 +496,155 @@
|
|
|
491
496
|
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
492
497
|
},
|
|
493
498
|
"additionalProperties": {
|
|
494
|
-
"
|
|
495
|
-
|
|
496
|
-
"cmd": {
|
|
497
|
-
"type": "string",
|
|
498
|
-
"minLength": 1,
|
|
499
|
-
"description": "Shell command run in the node’s lane. Not an agent."
|
|
500
|
-
},
|
|
501
|
-
"requires": {
|
|
502
|
-
"default": [],
|
|
503
|
-
"description": "Resource pool ids acquired before the gate runs, in canonical order.",
|
|
504
|
-
"type": "array",
|
|
505
|
-
"items": {
|
|
506
|
-
"type": "string",
|
|
507
|
-
"minLength": 1,
|
|
508
|
-
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
509
|
-
}
|
|
510
|
-
},
|
|
511
|
-
"timeout_s": {
|
|
512
|
-
"default": 1800,
|
|
513
|
-
"type": "integer",
|
|
514
|
-
"minimum": 1,
|
|
515
|
-
"maximum": 9007199254740991
|
|
516
|
-
},
|
|
517
|
-
"tuning": {
|
|
499
|
+
"anyOf": [
|
|
500
|
+
{
|
|
518
501
|
"type": "object",
|
|
519
502
|
"properties": {
|
|
520
|
-
"
|
|
521
|
-
"
|
|
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.",
|
|
522
511
|
"type": "array",
|
|
523
512
|
"items": {
|
|
524
513
|
"type": "string",
|
|
525
|
-
"minLength": 1
|
|
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
|
+
}
|
|
526
536
|
},
|
|
527
|
-
"
|
|
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"
|
|
528
545
|
}
|
|
529
546
|
},
|
|
530
547
|
"required": [
|
|
531
|
-
"
|
|
548
|
+
"cmd"
|
|
532
549
|
],
|
|
533
|
-
"additionalProperties": false
|
|
534
|
-
"description": "What an autonomous retune of this gate may do. Absent — the default — means the command is not the monitor’s to touch."
|
|
550
|
+
"additionalProperties": false
|
|
535
551
|
},
|
|
536
|
-
|
|
537
|
-
"type": "
|
|
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
|
|
538
645
|
}
|
|
539
|
-
},
|
|
540
|
-
"required": [
|
|
541
|
-
"cmd"
|
|
542
646
|
],
|
|
543
|
-
"
|
|
647
|
+
"description": "A command gate (`cmd`) or a judge gate (`judge`)."
|
|
544
648
|
}
|
|
545
649
|
},
|
|
546
650
|
"chores": {
|
|
@@ -581,6 +685,15 @@
|
|
|
581
685
|
"type": "string",
|
|
582
686
|
"minLength": 1
|
|
583
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
|
+
},
|
|
584
697
|
"on_failure": {
|
|
585
698
|
"default": "continue",
|
|
586
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.",
|
|
@@ -695,8 +808,8 @@
|
|
|
695
808
|
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
|
696
809
|
},
|
|
697
810
|
"max_fix_rounds": {
|
|
698
|
-
"default":
|
|
699
|
-
"description": "
|
|
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`.",
|
|
700
813
|
"type": "integer",
|
|
701
814
|
"minimum": 0,
|
|
702
815
|
"maximum": 9007199254740991
|
|
@@ -779,7 +892,9 @@
|
|
|
779
892
|
"open_pr",
|
|
780
893
|
"write_tracking",
|
|
781
894
|
"await_human",
|
|
782
|
-
"notify"
|
|
895
|
+
"notify",
|
|
896
|
+
"record_decision",
|
|
897
|
+
"grant_fix_rounds"
|
|
783
898
|
],
|
|
784
899
|
"description": "Which catalog verb this effect invokes."
|
|
785
900
|
},
|
|
@@ -844,7 +959,9 @@
|
|
|
844
959
|
"open_pr",
|
|
845
960
|
"write_tracking",
|
|
846
961
|
"await_human",
|
|
847
|
-
"notify"
|
|
962
|
+
"notify",
|
|
963
|
+
"record_decision",
|
|
964
|
+
"grant_fix_rounds"
|
|
848
965
|
],
|
|
849
966
|
"description": "Which catalog verb this effect invokes."
|
|
850
967
|
},
|
|
@@ -965,7 +1082,9 @@
|
|
|
965
1082
|
"open_pr",
|
|
966
1083
|
"write_tracking",
|
|
967
1084
|
"await_human",
|
|
968
|
-
"notify"
|
|
1085
|
+
"notify",
|
|
1086
|
+
"record_decision",
|
|
1087
|
+
"grant_fix_rounds"
|
|
969
1088
|
],
|
|
970
1089
|
"description": "Which catalog verb this effect invokes."
|
|
971
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
|
|
@@ -142,6 +142,8 @@ Shells under `plan-execution/shell/` are thin — most content lives in `plan-ex
|
|
|
142
142
|
|
|
143
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`.
|
|
144
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
|
+
|
|
145
147
|
**`ask` → two `integrate-phase` variants, no dual-render.** When `policies.commit_strategy = ask`, render `integrate-phase-template.md` **twice**:
|
|
146
148
|
- `ai-tools/skills/integrate-phase-stacked/SKILL.md` — `<RESOLVED>` = `stacked`, `{{INTEGRATE_PHASE_NAME}}` = `integrate-phase-stacked`.
|
|
147
149
|
- `ai-tools/skills/integrate-phase-modular/SKILL.md` — `<RESOLVED>` = `modular`, `{{INTEGRATE_PHASE_NAME}}` = `integrate-phase-modular`.
|
|
@@ -345,6 +347,7 @@ Length: 100–300 lines. Shorter = under-specified. Longer = should probably spl
|
|
|
345
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.
|
|
346
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.
|
|
347
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.
|
|
348
351
|
- **Each skill solves one job.** Two unrelated checklists → split.
|
|
349
352
|
- **Reference real files in the target.** Skill links must point to existing paths.
|
|
350
353
|
- **Skills auto-load by description.** Specific triggers, specific outcomes.
|
|
@@ -608,7 +608,8 @@ First key in the file is `"$schema"`, pointing at `https://github.com/vintasoftw
|
|
|
608
608
|
| `resources.<pool>` | One `{"kind": "semaphore"}` pool per expensive shared thing a gate contends for — the test database, the e2e browser grid, a staging deploy slot. `capacity: 1` when only one can run at a time. |
|
|
609
609
|
| `gates.<id>` | The checks a phase must pass, as **shell commands run in the phase's lane** — the project's real typecheck / test / lint invocations, not an agent and not prose. Give the slow ones `requires` naming the pool they contend for, and a `timeout_s` that is generous rather than tight. |
|
|
610
610
|
| `chores.<id>` | Agent turns a phase runs beside its gates, for work that changes the diff rather than judging it. **Always emit the `deslop` chore below** — it is the comment-hygiene pass, and without it a maestro run has none. Add another only when the plan genuinely needs one (a changelog entry, a translation extraction); a chore is a model turn per phase, so each one has to earn it. |
|
|
611
|
-
| `
|
|
611
|
+
| `chores.review-canvas` | **Emit only when `.vinta-ai-workflows.yaml` has `integrations.pr-review-canvas: enabled`.** An `after_pr` chore: maestro runs it once each phase's PR is open, and it posts a review canvas on that PR. Emit it exactly as shown under "The review-canvas chore" below. Never emit it when the integration is `disabled` or absent. The skill it names is installed only when the integration is enabled. |
|
|
612
|
+
| `defaults.chores` | `["deslop"]`, plus `"review-canvas"` when that chore is emitted. The chores every phase runs unless it names its own. |
|
|
612
613
|
| `nodes[].chores` | **Omit** on almost every phase — absent means the run-wide default. Name a list only to give one phase a different set, and `[]` to opt one out. A list *replaces* the default rather than adding to it. |
|
|
613
614
|
| `nodes[]` | One per phase, in plan order. |
|
|
614
615
|
| `nodes[].id` | `p` + the phase number, lowercased: `Phase 1` → `p1`, `Phase 4a` → `p4a`, `Phase 1b` → `p1b`. |
|
|
@@ -619,7 +620,7 @@ First key in the file is `"$schema"`, pointing at `https://github.com/vintasoftw
|
|
|
619
620
|
| `nodes[].gates` | The gate ids this phase must pass, in the order they should run. |
|
|
620
621
|
| `nodes[].crew` | The id from this phase's `**Assigned to**:` line. **Required on every node of a staffed workflow** — a half-staffed document is refused, because the executor would be running two staffing rules at once. |
|
|
621
622
|
| `nodes[].model` / `nodes[].harness` | **Omit `model` entirely on a staffed workflow** — the member carries it, and a node setting both is refused. `harness` only when this one phase runs on a different CLI than `defaults`. |
|
|
622
|
-
| `nodes[].max_fix_rounds` | Omit (defaults to
|
|
623
|
+
| `nodes[].max_fix_rounds` | Omit (defaults to 4). When a phase spends it, the executor asks the operator whether to continue rather than failing the phase, so this is how often somebody is asked, not a hard cap. Set it higher only on a phase whose review you expect to iterate — a delicate migration, a concurrency protocol. |
|
|
623
624
|
| `nodes[].pipeline` | Omit. A per-phase pipeline is for a phase that genuinely runs a different lifecycle, which is rare enough that needing it is a signal to re-read the plan. |
|
|
624
625
|
| `pipelines` | **Omit.** The executor ships `standard-phase` — see "The pipeline block". |
|
|
625
626
|
|
|
@@ -701,6 +702,21 @@ The remaining values — `migrate_cmd`, the database names, the server URL, the
|
|
|
701
702
|
|
|
702
703
|
Author a `pipelines` block only when a project genuinely needs a *different* lifecycle. That is an executor-configuration decision made once per project, not a per-plan choice, and a declared id shadows the shipped pipeline of the same name.
|
|
703
704
|
|
|
705
|
+
### The review-canvas chore
|
|
706
|
+
|
|
707
|
+
Emit this chore only when `.vinta-ai-workflows.yaml` has `integrations.pr-review-canvas: enabled`. Then add `"review-canvas"` to `defaults.chores` after `"deslop"`:
|
|
708
|
+
|
|
709
|
+
```json
|
|
710
|
+
"review-canvas": {
|
|
711
|
+
"skill": "pr-review-canvas",
|
|
712
|
+
"when": "after_pr",
|
|
713
|
+
"prompt": "Generate the review canvas for this phase's pull request: run `/pr-review-canvas` with the PR number named above. Report the local review URL and the PR comment link it returns. Do not change any file in the repository.",
|
|
714
|
+
"description": "A topic-grouped review canvas on each phase PR, for the human reviewer."
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
`"when": "after_pr"` is what makes this work. Maestro runs the chore at the end of `integrate`, once the PR is open, and tells the agent the PR's URL and number. When no PR opened, it skips the chore. Do not give it `on_failure: "fail"`; maestro refuses that for an `after_pr` chore, because the phase is already merged. The integration never changes how the plan's phases are written, so the worked example below leaves this chore out.
|
|
719
|
+
|
|
704
720
|
### Worked example
|
|
705
721
|
|
|
706
722
|
A five-phase plan whose `**Depends on**:` lines are:
|
|
@@ -1060,4 +1076,5 @@ When in doubt, model the plan after a recent example in `ai-plans/` — look for
|
|
|
1060
1076
|
- [ ] No credential anywhere in the workflow file: `connection_url_var` is a variable name, and `server_url` is a host and port.
|
|
1061
1077
|
- [ ] Model ids come from [resources/ai-models.yaml](resources/ai-models.yaml) and appear **only** in the `crew` block — no node carries a `model`, and `crew` transcribes the Crew table row for row.
|
|
1062
1078
|
- [ ] `chores` declares `deslop` and `defaults.chores` names it, so every phase's diff gets the comment pass before it is gated. No node carries its own `chores` unless that phase genuinely needs a different set.
|
|
1079
|
+
- [ ] `chores.review-canvas` (with `"when": "after_pr"`) is declared and in `defaults.chores` **if and only if** `.vinta-ai-workflows.yaml` has `integrations.pr-review-canvas: enabled`.
|
|
1063
1080
|
- [ ] `pipelines` is omitted — `defaults.pipeline: standard-phase` is enough, and the executor supplies it.
|
package/skills/vinta-derive-skills/resources/plan-execution/partials/commit-strategy/stacked.md
CHANGED
|
@@ -17,6 +17,18 @@ A chain-shaped plan reproduces the classic stack exactly — each phase depends
|
|
|
17
17
|
**PR base per phase** (the `base` field written into the prs-context frontmatter — this is what `gh pr create --base` / `glab mr create --target-branch` opens the PR against; getting it wrong makes the PR diff include every upstream phase and the review unusable):
|
|
18
18
|
|
|
19
19
|
- `base = <phase.base_branch>`, always. Never `<BASE_BRANCH>` for a phase that has dependencies.
|
|
20
|
+
|
|
21
|
+
**Every branch the plan lands through gets a PR.** Phase PRs alone do not reach `<BASE_BRANCH>`: a phase based on `integ-{phase.id}` targets a branch that nothing else targets, and the conflict resolutions made during wave merges are on no phase branch at all. So a stacked run opens three kinds of PR:
|
|
22
|
+
|
|
23
|
+
| PR | `branch` (head) | `base` | prs-context file | Opened |
|
|
24
|
+
|---|---|---|---|---|
|
|
25
|
+
| Phase | `plan/{plan-id-kebab}/phase-{phase.id}` | `<phase.base_branch>` | `phase-{phase.id}.md` | when the phase passes review |
|
|
26
|
+
| Integration | `plan/{plan-id-kebab}/integ-{phase.id}` | `<BASE_BRANCH>` | `integ-{phase.id}.md` (`kind: integration`) | just before the phase PR, only for a phase with two or more dependencies |
|
|
27
|
+
| Plan | the final `plan/{plan-id-kebab}/wave-{N}` | `<BASE_BRANCH>` | `plan.md` (`kind: plan`) | once, at run end, after the final wave branch is built |
|
|
28
|
+
|
|
29
|
+
The **integration PR** is based on `<BASE_BRANCH>` because the `integ-` branch has several parents and none of them is "below" it. Its diff starts as all of its dependencies and shrinks, as their PRs merge, to the merge commits and any conflict resolution the fixer made. Its description says: merge the dependencies' PRs first, then this one, then retarget the phase PR to `<BASE_BRANCH>`.
|
|
30
|
+
|
|
31
|
+
The **plan PR** is the one branch known to hold the whole plan. Its description lists every phase and integration PR in an order that merges (wave by wave; inside a wave, plan order; each integration PR right before its phase PR) and offers two ways to land: merge the plan PR alone, or merge the listed PRs in order and the plan PR last — by then its diff is only what no other PR carries. Either way, **merge commits, not squash**: a squash makes every PR stacked above it repeat the changes below it.
|
|
20
32
|
<!-- block-end: BRANCH_NAMING -->
|
|
21
33
|
|
|
22
34
|
<!-- block-begin: PER_PHASE_COMMIT -->
|
|
@@ -28,11 +40,17 @@ A chain-shaped plan reproduces the classic stack exactly — each phase depends
|
|
|
28
40
|
|
|
29
41
|
<!-- block-begin: PR_OPEN_TIMING -->
|
|
30
42
|
One PR per phase — the [Open PR via context file](#open-pr-via-context-file) step runs after this phase passes review, writing `.vinta-ai-workflows/prs-context/{feature-kebab}/phase-{phase.id}.md`.
|
|
43
|
+
|
|
44
|
+
**When `<phase.base_branch>` is an `integ-` branch, run that step twice — integration PR first.** Push `plan/{plan-id-kebab}/integ-{phase.id}`, then write `.vinta-ai-workflows/prs-context/{feature-kebab}/integ-{phase.id}.md` with `kind: integration`, `branch: plan/{plan-id-kebab}/integ-{phase.id}`, `base: <BASE_BRANCH>`, a `# Title` of `Integrate <dep ids> for <phase title>`, and a `# Description` naming the dependencies and the merge order (dependencies' PRs → this PR → retarget the phase PR to `<BASE_BRANCH>`). Run `open-pr.sh` on it, then on the phase file. Skip the inline-comment pass for the integration file — its diff is other phases' work.
|
|
45
|
+
|
|
46
|
+
The **plan PR** is not this step's job — the conductor opens it at run end, once the final wave branch exists.
|
|
31
47
|
<!-- block-end: PR_OPEN_TIMING -->
|
|
32
48
|
|
|
33
49
|
<!-- block-begin: CHECKLIST -->
|
|
34
50
|
- [ ] Phase branch created from `<phase.base_branch>` (dependency-derived), not from plan order.
|
|
35
51
|
- [ ] PR `base` in the prs-context frontmatter equals `<phase.base_branch>`.
|
|
52
|
+
- [ ] Phase based on an `integ-` branch: the `integ-` branch pushed and its integration PR (`integ-{phase.id}.md`, `base: <BASE_BRANCH>`) opened before the phase PR.
|
|
53
|
+
- [ ] Run end: plan PR (`plan.md`, final wave branch → `<BASE_BRANCH>`) opened, listing every phase and integration PR in merge order.
|
|
36
54
|
<!-- block-end: CHECKLIST -->
|
|
37
55
|
|
|
38
56
|
<!-- Single-line values (derive-skills substitutes these into the shells directly):
|
|
@@ -124,9 +124,16 @@ The budget is an **integration-level** setting, not either phase's `max_fix_roun
|
|
|
124
124
|
|
|
125
125
|
**Confirming a fix requires reading the files, not asking git.** `git add` clears a path's unmerged flag whether or not `<<<<<<<` is still sitting in it, so git cannot tell you whether the fixer actually resolved anything. Scan the conflicted paths for conflict markers before committing the merge. Skip this and a fixer that did nothing produces a merge commit full of markers that passes straight into the wave branch.
|
|
126
126
|
|
|
127
|
-
###
|
|
127
|
+
### PRs: one per phase, plus what it takes to reach `<BASE_BRANCH>`
|
|
128
128
|
|
|
129
129
|
The PR `base` written into the prs-context frontmatter is the phase's **computed `base_branch`** — `<BASE_BRANCH>`, a single dependency's branch, or the `integ-{P.id}` branch. Never `<BASE_BRANCH>` for a phase that has dependencies; a wrong base makes the PR diff include every upstream phase and the review is unusable.
|
|
130
|
+
|
|
131
|
+
Phase PRs are review units, and on their own they do not land the plan. Under a per-phase PR strategy the conductor also opens:
|
|
132
|
+
|
|
133
|
+
- **An integration PR per `integ-{P.id}` branch**, into `<BASE_BRANCH>`, just before `P`'s own PR. Without it `P`'s PR targets a branch nothing targets, and nothing stacked on `P` can reach `<BASE_BRANCH>`.
|
|
134
|
+
- **A plan PR at run end**, from the final `wave-{N}` branch into `<BASE_BRANCH>`. It is the only branch carrying the conflict resolutions made during wave merges, so it is merged last whichever way the plan lands. Its description lists every phase and integration PR in merge order.
|
|
135
|
+
|
|
136
|
+
The commit strategy's PR-open timing says which files to write and when.
|
|
130
137
|
<!-- block-end: LANE_TOPOLOGY -->
|
|
131
138
|
|
|
132
139
|
<!-- block-begin: LANE_SCHEDULER -->
|
|
@@ -39,7 +39,7 @@ Two project-level signals decide the actual behavior:
|
|
|
39
39
|
|
|
40
40
|
When `generate_inline_comments = false`: skip this step. The file's `# Comments` block stays empty.
|
|
41
41
|
|
|
42
|
-
4. **Write the prs-context file** at {{PRS_CONTEXT_FILE_PATH}}, following [resources/prs-context-template.md](../../prs-context-template.md). Frontmatter: `plan_id`, `feature_name`, `phase_id`, `phase_title`, `branch`, `base`, `created_at`, `status: pending`, empty `pr_url`. **`base` is the branch the PR opens against — resolve it per the commit strategy, never default it to `<BASE_BRANCH>` blindly:** for stacked branches
|
|
42
|
+
4. **Write the prs-context file** at {{PRS_CONTEXT_FILE_PATH}}, following [resources/prs-context-template.md](../../prs-context-template.md). Frontmatter: `plan_id`, `feature_name`, `phase_id`, `phase_title`, `branch`, `base`, `created_at`, `status: pending`, empty `pr_url`. **`base` is the branch the PR opens against — resolve it per the commit strategy, never default it to `<BASE_BRANCH>` blindly:** for stacked branches a phase's PR bases on its dependency-derived `<phase.base_branch>` — `<BASE_BRANCH>` only for a phase with no dependencies (see the PR-base rule under the {{BRANCH_PUSH_HEADING}} step above); an integration PR (`kind: integration`) and the plan PR (`kind: plan`) base on `<BASE_BRANCH>`. For a single plan-level PR (modular / one-PR strategies) `base = <BASE_BRANCH>`. Body sections: `# Title` (single-line PR title), `# Description` (Markdown body — uses the project's PR template structure from step 2 when one exists), `# Comments` (YAML list of `{file, start_line, end_line?, side, body}` — empty list when comments are off).
|
|
43
43
|
|
|
44
44
|
5. **Deslop the prose.** Run the `deslop-comments` skill ([deslop-comments](../deslop-comments/SKILL.md)) over the file you just wrote, passing {{PRS_CONTEXT_FILE_PATH}} as the explicit scope. `# Title`, the `# Description` body, and every `# Comments` `body` must read as Simple English: one idea per sentence, current behavior stated directly instead of "not X" framing, and no AI-slop vocabulary (`gate`, `guard`, `backstop`, `surface`, `leverage`, `plumb`, `canonical`, …). Keep precise domain terms, and keep the project's PR-template structure intact — section headings, `<!-- HTML comment -->` placeholders, and checklist lines stay exactly as step 2 wrote them. This pass rewrites prose only: it never touches the frontmatter, the section layout, or which file and lines a comment targets. Do it before `open-pr.sh` runs, since the published PR body and comments come straight from this file.
|
|
45
45
|
|
|
@@ -59,5 +59,7 @@ Two project-level signals decide the actual behavior:
|
|
|
59
59
|
|
|
60
60
|
When policy = "branches only": **don't run the script.** File stays `status: pending`.
|
|
61
61
|
|
|
62
|
+
{If integrations.pr-review-canvas = enabled in `.vinta-ai-workflows.yaml`:} **Review canvas.** After exit `0` or `1`, the PR is up. Generate its review canvas with the `pr-review-canvas` skill ([pr-review-canvas](../pr-review-canvas/SKILL.md)). Pass the PR number from the end of `pr_url` (`…/pull/<n>` or `…/merge_requests/<n>`), as in `/pr-review-canvas <n>`. Run it again each time this step re-runs `open-pr.sh` on the same PR, for example after each phase under one plan-level PR. The tool updates the canvas incrementally. The skill posts the canvas as a PR comment unless the project's `pr-review.config.yml` turns sharing off. Add its local review URL and the comment link to the user update. A canvas that fails never fails the phase. Report the skill's error, and point at `pr-review doctor` when the error suggests setup. When the `pr-review-canvas` skill is not installed, say so once, point at `vinta-install-ai-tools-setup`, and skip.
|
|
63
|
+
|
|
62
64
|
8. **Skill wrapper** — [open-pr-from-context](../open-pr-from-context/SKILL.md) is available for ad-hoc invocation (after the run, on a different machine, etc.). The orchestrator can call the script directly here; the skill is for humans.
|
|
63
65
|
<!-- block-end: PR_CONTEXT -->
|
package/skills/vinta-derive-skills/resources/plan-execution/shell/implement-plan-template.md
CHANGED
|
@@ -181,9 +181,10 @@ After the scheduler loop exits — every executable phase is `done`, `failed`, o
|
|
|
181
181
|
|
|
182
182
|
1. **Build the final wave branch** if the last completed wave has no branch yet, so one branch carries the whole plan.
|
|
183
183
|
2. **Delete the `TRACKING_{plan-id}/` directory** (`git rm -r`) on that final wave branch, in one commit. The plan file stays.
|
|
184
|
-
3.
|
|
184
|
+
3. **Open the plan PR** (per-phase PR strategies only — skip when the commit strategy already keeps one plan-level PR). Only when every phase is `done`: a plan with failed or blocked phases is not ready to land, so list the gap instead. Push the final wave branch, write `.vinta-ai-workflows/prs-context/{feature-kebab}/plan.md` with `kind: plan`, `branch: plan/{plan-id-kebab}/wave-{N}` (the final wave), `base: <BASE_BRANCH>`, and a `# Description` listing every phase and integration PR in merge order (wave by wave; plan order inside a wave; each integration PR right before its phase PR), with the two ways to land it — merge this PR alone, or merge the listed PRs in order and this one last — and the rule to use merge commits, not squash. Run `open-pr.sh` on it under the same PR policy as the phase PRs. This is the PR that lands the plan; a run that ends without it leaves the user to rebuild the merges by hand.
|
|
185
|
+
4. Send the user a final summary: the **plan PR URL** first (or why it was not opened — failed phases, PR policy, missing deps); {{FINAL_REPORT_BRANCH_SUMMARY}}; the **wave branches** in order, and which phase branches merged into each; phases that **failed** and the dependents each one **blocked**; for UI-flow phases — list of `pr-screenshots/` files (if applicable); deferred phases (cross-repo + flag-removal); next steps for the human. When `run_options.use_worktree = true`: include **every lane's** path + branch + summary file path + teardown command (`git worktree remove <path>` + the per-engine drop-db / `docker compose -p <project> down -v` lines from that lane's `<worktree_summary>`), and the integration worktree's. Do NOT auto-run teardown — the user may still want a lane to debug review feedback or land follow-ups, and a failed phase's lane is the only place its state survives.
|
|
185
186
|
{{FINAL_REPORT_PR_NOTE}}
|
|
186
|
-
|
|
187
|
+
5. Flag-removal phase deferred → end with `/schedule` offer for the dedicated flag-removal skill.
|
|
187
188
|
|
|
188
189
|
## Important rules
|
|
189
190
|
|
|
@@ -232,4 +233,4 @@ After the scheduler loop exits — every executable phase is `done`, `failed`, o
|
|
|
232
233
|
- [ ] Wave branch built + `waves/wave-{N}.md` written when this phase completed its wave; merge conflicts resolved by a fixer, never by the conductor, and the outer gate re-run on the merged tree.
|
|
233
234
|
- [ ] One-paragraph user update sent per phase completion (PR URL or pending-file path included; lane named; what the scheduler picked up next).
|
|
234
235
|
- [ ] If `run_options.pause_between_phases = true`: stopped dispatching, let in-flight lanes drain, prompted user (`Continue` / `Pause` / `Stop`); honored answer. Else: next ready phase dispatched immediately.
|
|
235
|
-
- [ ] On run end: final wave branch built; tracking directory deleted; final summary lists wave + phase branches{{FINAL_CHECKLIST_PR_NOTE}}, failed phases with the dependents they blocked, every lane's teardown command; any `status: pending` PR-context files listed with publish command; `/schedule` offer for flag-removal if applicable.
|
|
236
|
+
- [ ] On run end: final wave branch built; tracking directory deleted; plan PR opened (final wave branch → `<BASE_BRANCH>`, every phase + integration PR listed in merge order) when every phase is done and the strategy opens per-phase PRs; final summary leads with the plan PR URL and lists wave + phase branches{{FINAL_CHECKLIST_PR_NOTE}}, failed phases with the dependents they blocked, every lane's teardown command; any `status: pending` PR-context files listed with publish command; `/schedule` offer for flag-removal if applicable.
|
package/skills/vinta-derive-skills/resources/plan-execution/shell/integrate-phase-template.md
CHANGED
|
@@ -29,4 +29,4 @@ Invoked by [implement-plan](../implement-plan/SKILL.md) after [review-phase](../
|
|
|
29
29
|
|
|
30
30
|
## Output
|
|
31
31
|
|
|
32
|
-
Return to the conductor: the branch pushed, and
|
|
32
|
+
Return to the conductor: the branch pushed (plus the `integ-` branch when one was pushed), and each PR-context file path written — the integration file first when there is one — with its `status` (`published` + `pr_url` when `open-pr.sh` ran; `pending` otherwise) plus the publish command when `pending`.
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
schema_version: 1 # see schemas/prs-context-frontmatter.v1.schema.json
|
|
4
4
|
plan_id: <plan-id> # filename feature portion, kebab-case
|
|
5
5
|
feature_name: <FEATURE_NAME> # UPPERCASE_WITH_UNDERSCORES, matches plan/spec
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
kind: phase # phase (default) | integration (a phase's integ- branch) | plan (the PR that lands the whole plan)
|
|
7
|
+
phase_id: <phase-id> # e.g. "1", "4a". Omit for kind: plan
|
|
8
|
+
phase_title: <phase title> # verbatim from the plan's Phased Rollout section. Omit for kind: plan
|
|
9
|
+
branch: plan/<feature-kebab>/phase-<id> # branch the PR opens from (integ-<id> for integration; final wave-<N> for a stacked plan PR)
|
|
10
|
+
base: <main | plan/<feature-kebab>/phase-<dep-id> | plan/<feature-kebab>/integ-<id>> # PR target branch. Stacked phase: its dependency-derived base (default branch only with no dependencies). Integration, plan, modular / single-PR: default branch.
|
|
10
11
|
created_at: <ISO 8601 timestamp>
|
|
11
12
|
status: pending # `pending` until published; `published` after CLI run
|
|
12
13
|
pr_url: # set by open-pr-from-context after publishing
|
|
@@ -101,10 +101,37 @@ For each vendor in the user's selection:
|
|
|
101
101
|
|
|
102
102
|
Any mismatches → re-run the script. If still wrong → inspect the script's output for warnings (it warns when a target path exists as a non-symlink real file).
|
|
103
103
|
|
|
104
|
+
### 7b. Install integration skills
|
|
105
|
+
|
|
106
|
+
Read `integrations` from `.vinta-ai-workflows.yaml`. Skip this step when nothing there is `enabled`. Run it **after** step 6: the vendor skill paths must already be symlinks into `ai-tools/skills/`, or the tool would create real vendor directories that block those symlinks.
|
|
107
|
+
|
|
108
|
+
**`pr-review-canvas` = enabled:**
|
|
109
|
+
|
|
110
|
+
1. Check for the CLI with `pr-review --version`. If it is missing, print the install command and skip the rest of this integration. **Do not install it yourself.** It is a global, machine-level tool, like `gh`:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm install -g @vintasoftware/pr-review-canvas
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Tell the user to install it, then re-run this step (or run `pr-review install-skill --claude-dir ai-tools/skills --codex-dir ai-tools/skills` themselves).
|
|
117
|
+
2. Install the skill into the canonical directory:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pr-review install-skill --claude-dir ai-tools/skills --codex-dir ai-tools/skills --json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Both flags point at `ai-tools/skills/`, so the tool writes one copy, `ai-tools/skills/pr-review-canvas/`. Every vendor then reaches that copy through its symlink. The tool also adds `.pr-review/settings.yml` to `.gitignore`.
|
|
124
|
+
3. **Never edit `ai-tools/skills/pr-review-canvas/SKILL.md`.** The tool stamps a body hash into its frontmatter, and `pr-review doctor` and `pr-review serve` flag a modified copy as stale. To refresh the skill, run `pr-review upgrade`, not this flow.
|
|
125
|
+
4. Run `pr-review doctor`. Its `skill` check must pass, and it reads the copy through `.agents/skills/`, which step 6 always links. Show any other failed checks to the user with the hints the tool prints, most often `gh auth login` / `glab auth login` or a missing `origin` remote. These do not block the bootstrap.
|
|
126
|
+
5. Do not write `pr-review.config.yml`. Every key in it is optional, and the defaults are correct. Mention that the team can copy the tool's example config to change sharing or mark high-risk paths.
|
|
127
|
+
|
|
128
|
+
On `SKILL_DIR_EXISTS` (exit 1), a hand-made `ai-tools/skills/pr-review-canvas/` without the tool's marker is in the way. Show it to the user and ask before re-running with `--force`. `--force` replaces that directory.
|
|
129
|
+
|
|
104
130
|
### 8. Commit
|
|
105
131
|
|
|
106
132
|
Stage:
|
|
107
|
-
- `ai-tools/` (canonical sources + script)
|
|
133
|
+
- `ai-tools/` (canonical sources + script, plus any integration skill from step 7b)
|
|
134
|
+
- `.gitignore` (when step 7b's `pr-review install-skill` added `.pr-review/settings.yml`)
|
|
108
135
|
- `package.json` (script alias + yaml dep)
|
|
109
136
|
- `pnpm-lock.yaml` / `package-lock.json` / `yarn.lock` (after install)
|
|
110
137
|
- `AGENTS.md` (regular file at the repo root)
|
|
@@ -109,7 +109,7 @@ For each `affects-project` and `opt-in-offer` and `config-schema-change` entry,
|
|
|
109
109
|
|
|
110
110
|
`tooling` changes batch under one prompt: `Apply all tooling changes?` — they're typically idempotent and small.
|
|
111
111
|
|
|
112
|
-
`opt-in-offer` `Skip` writes the corresponding entry to `foundation_skills` (or wherever it lives) as `disabled` so the next sync respects the choice.
|
|
112
|
+
`opt-in-offer` `Skip` writes the corresponding entry to `foundation_skills` (or `integrations`, or wherever it lives) as `disabled` so the next sync respects the choice.
|
|
113
113
|
|
|
114
114
|
Don't batch `affects-project` decisions. Each is a separate decision so the user can keep their hand-tuned wording for one skill while accepting another.
|
|
115
115
|
|
|
@@ -124,6 +124,7 @@ In order:
|
|
|
124
124
|
- Bump `schema_version` if the major changed.
|
|
125
125
|
|
|
126
126
|
2. **`opt-in-offer` accepts** — flip the corresponding entry to `enabled` in the config; copy / render the artifact (foundation skill, agent, etc.).
|
|
127
|
+
- **An integration** (`integrations.<name>` in the config, such as `pr-review-canvas`) has no bundled artifact to copy. Flip it to `enabled`, then run [vinta-install-ai-tools-setup](../vinta-install-ai-tools-setup/SKILL.md)'s **Install integration skills** step, which uses the tool's own CLI. The plan-execution skills and `plan-feature` read the flag at runtime, so nothing needs re-deriving. Offer it only when its preconditions hold. For `pr-review-canvas`, that means `project.code_host` is `github` / `gitlab` and `policies.pr_creation` is `agents-create`. `Skip` writes `disabled` under `integrations`.
|
|
127
128
|
|
|
128
129
|
3. **`affects-project` accepts** — re-render the relevant template (for `implement-plan` / `amend-plan`) or re-copy the foundation skill body (for verbatim ones — delegated to [vinta-update-project-skills](../vinta-update-project-skills/SKILL.md)). Use the values in `.vinta-ai-workflows.yaml` as the substitution source. Validate every `{{PLACEHOLDER}}` is resolved.
|
|
129
130
|
|
|
@@ -65,6 +65,7 @@ For each project skill, decide its bucket:
|
|
|
65
65
|
| `verbatim` | The skill name matches a directory under `vinta-derive-skills/resources/foundation-skills/` (e.g. `plan-feature`, `create-spec`, `create-qa-use-cases`). | Diff against source. |
|
|
66
66
|
| `templated` | The skill name is `implement-plan` (or any other future templated skill listed under `vinta-derive-skills/resources/*-template.md`). | **Don't diff.** Surface as "regenerate via [vinta-derive-skills](../vinta-derive-skills/SKILL.md)". |
|
|
67
67
|
| `stack-derived` | The skill maps to a stack-template prompt under `vinta-bootstrap-ai-tools/resources/stacks/<stack>/notes.md`. | Same as `templated` — surface, don't diff. |
|
|
68
|
+
| `integration` | The skill dir carries an external tool's ownership marker, such as `.pr-review-install` for `pr-review-canvas`. | **Never diff or edit** — the tool stamps a content hash and treats any edit as stale. Surface as "refresh with the tool's own updater" (`pr-review upgrade` for `pr-review-canvas`). |
|
|
68
69
|
| `user-authored` | None of the above. | Skip silently. |
|
|
69
70
|
|
|
70
71
|
For `templated` and `stack-derived`, in-place diffing is misleading because the project version contains interpolated values (commands, branch names, etc.) that won't appear in the template. The right refresh path for those is to re-run [vinta-derive-skills](../vinta-derive-skills/SKILL.md) so the user can re-supply the inputs.
|