vinta-ai-workflows 0.6.1 → 0.7.0-alpha10
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 +714 -0
- package/README.md +21 -13
- package/package.json +8 -8
- package/schemas/README.md +5 -1
- package/schemas/postmortem.v1.schema.json +339 -0
- package/schemas/vinta-ai-workflows-config.v1.schema.json +13 -1
- package/schemas/workflow.v1.schema.json +978 -0
- package/skills/vinta-bootstrap-ai-tools/SKILL.md +9 -1
- package/skills/vinta-derive-skills/SKILL.md +6 -2
- package/skills/vinta-derive-skills/resources/foundation-skills/open-pr-from-context/scripts/open-pr.sh +0 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/plan-feature/SKILL.md +622 -32
- package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/SKILL.md +34 -4
- package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/scripts/claude-worktree-write-guard.py +0 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/scripts/gen-claude-sandbox-settings.sh +0 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/scripts/gen-compose-worktree-override.sh +0 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/scripts/sandbox-run.sh +0 -0
- package/skills/vinta-derive-skills/resources/plan-execution/README.md +21 -4
- package/skills/vinta-derive-skills/resources/plan-execution/partials/agent-models.md +30 -3
- package/skills/vinta-derive-skills/resources/plan-execution/partials/commit-strategy/modular.md +27 -14
- package/skills/vinta-derive-skills/resources/plan-execution/partials/commit-strategy/stacked.md +12 -18
- package/skills/vinta-derive-skills/resources/plan-execution/partials/implementer-prompt.md +19 -9
- package/skills/vinta-derive-skills/resources/plan-execution/partials/model-pick.md +25 -9
- package/skills/vinta-derive-skills/resources/plan-execution/partials/parallel-lanes.md +240 -0
- package/skills/vinta-derive-skills/resources/plan-execution/partials/review-layers.md +56 -5
- package/skills/vinta-derive-skills/resources/plan-execution/partials/worktree-seam.md +13 -6
- package/skills/vinta-derive-skills/resources/plan-execution/shell/amend-plan-template.md +17 -12
- package/skills/vinta-derive-skills/resources/plan-execution/shell/implement-phase-template.md +5 -3
- package/skills/vinta-derive-skills/resources/plan-execution/shell/implement-plan-template.md +94 -57
- package/skills/vinta-derive-skills/resources/plan-execution/shell/integrate-phase-template.md +2 -1
- package/skills/vinta-derive-skills/resources/plan-execution/shell/review-phase-template.md +16 -5
- package/skills/vinta-install-ai-tools-setup/resources/setup-ai-tools.mjs +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,720 @@ All notable changes to `vinta-ai-workflows` are documented in this file.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
6
6
|
the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.0] — YYYY-MM-DD
|
|
9
|
+
|
|
10
|
+
<!-- pre-release: 0.7.0-alpha10 on 2026-09-15 -->
|
|
11
|
+
<!-- pre-release: 0.7.0-alpha9 on 2026-09-14 -->
|
|
12
|
+
<!-- pre-release: 0.7.0-alpha8 on 2026-09-14 -->
|
|
13
|
+
<!-- pre-release: 0.7.0-alpha7 on 2026-09-14 -->
|
|
14
|
+
<!-- pre-release: 0.7.0-alpha6 on 2026-09-13 -->
|
|
15
|
+
<!-- pre-release: 0.7.0-alpha5 on 2026-09-13 -->
|
|
16
|
+
<!-- pre-release: 0.7.0-alpha4 on 2026-09-13 -->
|
|
17
|
+
<!-- pre-release: 0.7.0-alpha3 on 2026-09-12 -->
|
|
18
|
+
<!-- pre-release: 0.7.0-alpha2 on 2026-09-12 -->
|
|
19
|
+
<!-- pre-release: 0.7.0-alpha1 on 2026-09-11 -->
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **A project declares what makes a lane runnable.** `project.env_files` copies
|
|
24
|
+
ignored configuration into each worktree, `project.setup_cmd` runs an
|
|
25
|
+
idempotent setup hook on provisioning and recycle, and the fixed
|
|
26
|
+
`project.commands` vocabulary tells implementers, reviewers and fixers the
|
|
27
|
+
project's real lint, typecheck, test and migration entry points.
|
|
28
|
+
|
|
29
|
+
- **Compose-backed lanes are isolated past `COMPOSE_PROJECT_NAME`.** The daemon
|
|
30
|
+
now generates an out-of-tree Compose override per lane, strips fixed host
|
|
31
|
+
ports, re-pins fixed and external volumes, and carries the complete lane
|
|
32
|
+
environment into agent and takeover processes as well as gates. Explicitly
|
|
33
|
+
published services receive probed per-lane ports recorded in the teardown
|
|
34
|
+
summary.
|
|
35
|
+
|
|
36
|
+
- **Shared services get one namespace per lane.** `project.services` models a
|
|
37
|
+
fixed index or lane-derived name inside one Redis, RabbitMQ, object-storage,
|
|
38
|
+
or similar server, with project-owned create/reset commands and capacity
|
|
39
|
+
validation before provisioning creates templates or worktrees.
|
|
40
|
+
|
|
41
|
+
- **Agents can take the semaphore a workflow declares.**
|
|
42
|
+
`vinta-ai-maestro with <resource> -- <cmd>` waits on the run's existing
|
|
43
|
+
`ResourcePools`, runs the heavy inner-loop command, and releases on exit.
|
|
44
|
+
Renewable leases expire when their client disappears, are visible in the
|
|
45
|
+
live journal, and are surfaced in implementer, reviewer and fixer prompts.
|
|
46
|
+
|
|
47
|
+
- **A failed phase retries itself, and then asks.** `--on-failure` defaults to
|
|
48
|
+
`retry`: one cold re-attempt (`--retries <n>`, 0–5), and then the node parks
|
|
49
|
+
on a question — retry, retry with another member of the crew, or stop. The
|
|
50
|
+
failures this system produces are overwhelmingly environmental and are gone by
|
|
51
|
+
the second attempt; the ones that survive it are the ones worth a person.
|
|
52
|
+
`ask` skips the automatic attempt. `stop` is the old behaviour and remains the
|
|
53
|
+
right choice for CI, because everything else eventually **waits**.
|
|
54
|
+
|
|
55
|
+
A failure that survives its retries says so: the reason carries
|
|
56
|
+
`(after N attempts)`, which no event otherwise records.
|
|
57
|
+
|
|
58
|
+
- **A failed phase can be retried by hand.** A **Retry phase** button, and
|
|
59
|
+
`POST /api/runs/:runId/nodes/:nodeId/retry`. It returns the node to the ready
|
|
60
|
+
set and unblocks the subtree its failure blocked — a phase that died of
|
|
61
|
+
something environmental holds up work that was never broken. It needs the run
|
|
62
|
+
to still be in flight, which with the new default it usually is; a run that
|
|
63
|
+
has genuinely ended is re-run, not retried, and says so rather than doing
|
|
64
|
+
nothing.
|
|
65
|
+
|
|
66
|
+
- **The monitor can go and look.** It was given a digest and nothing else, and
|
|
67
|
+
read exactly as thin as that sounds — it could say a phase had failed and
|
|
68
|
+
never say what the phase had written. It is briefed as a technical project
|
|
69
|
+
manager now, with the map: the plan document, each phase's brief, each lane's
|
|
70
|
+
worktree path, each branch and its base, and where the journal and transcripts
|
|
71
|
+
live. It has a shell and its working directory is the repository, so it can
|
|
72
|
+
read the diff before drawing a conclusion. The digest stays as the index —
|
|
73
|
+
bounded, cheap, enough to know which phase is worth a closer look.
|
|
74
|
+
|
|
75
|
+
- **The monitor conversation is kept.** It lived in component state, so a reload
|
|
76
|
+
threw away every question and every answer — a worse record than the run it
|
|
77
|
+
describes. It is written to the transcript store under a reserved node id and
|
|
78
|
+
read back on load, so it outlives the tab, the daemon and the run.
|
|
79
|
+
|
|
80
|
+
- **Panels expand.** Anything holding content that is only nominally
|
|
81
|
+
summarisable — an agent's transcript, a gate log, the monitor conversation,
|
|
82
|
+
the steering box — takes the full container width and about a screen of
|
|
83
|
+
height on demand. The height travels as a CSS variable, because the element
|
|
84
|
+
that has to grow is a scroller several components below the panel.
|
|
85
|
+
|
|
86
|
+
- **A run has a spokesperson you can ask.** `serve` grows a **Monitor** panel on
|
|
87
|
+
the run view: one agent that reads the journal and answers in prose — what is
|
|
88
|
+
blocked, why a phase failed, what it would take to move on. It runs on the
|
|
89
|
+
dearest tier on the crew, because it is read by a person deciding what to do
|
|
90
|
+
about a failing run, and it is asked a handful of times rather than hundreds.
|
|
91
|
+
|
|
92
|
+
Three things it deliberately is not.
|
|
93
|
+
|
|
94
|
+
**It is not in the permission path.** The obvious thought, once an agent has
|
|
95
|
+
been refused something, is to put a smarter agent in front of the refusals —
|
|
96
|
+
but a phase makes hundreds of tool calls (one node in one real run made 179)
|
|
97
|
+
and a model turn before each is latency and cost spent on questions like "may
|
|
98
|
+
I run ruff", which should never have been questions. Permissions are settled
|
|
99
|
+
structurally, once per spawn, for free.
|
|
100
|
+
|
|
101
|
+
**It does not read the transcripts.** Piping every agent's output into a
|
|
102
|
+
second agent would make the monitor the most expensive thing in the run and
|
|
103
|
+
tie its cost to the work rather than to the questions. It reads a bounded
|
|
104
|
+
digest — statuses, failure reasons, pending questions, the last few refusals
|
|
105
|
+
with their sentences, one trimmed last word per phase — so a one-hour run and
|
|
106
|
+
a one-day run cost about the same to ask about.
|
|
107
|
+
|
|
108
|
+
**It has no authority.** It can explain a §9.1 pause; it cannot answer one.
|
|
109
|
+
An agent that could quietly approve its colleagues' work would turn a
|
|
110
|
+
checkpoint into a formality, and the checkpoint is the point.
|
|
111
|
+
|
|
112
|
+
It answers about **finished runs too**, which is when "why did this fail" is
|
|
113
|
+
usually asked, and it needs no lane: it writes nothing.
|
|
114
|
+
|
|
115
|
+
- **`serve` reads `--permission`.** The flag has been accepted and ignored since
|
|
116
|
+
it existed. It decides how the monitor is spawned, so it is read now.
|
|
117
|
+
|
|
118
|
+
- **A plan staffs a team, instead of picking a model per phase.** Choosing a tier
|
|
119
|
+
phase by phase answers "what runs this one" ten times and never adds it up, so the
|
|
120
|
+
two questions that decide what a feature costs go unasked: how many agents does
|
|
121
|
+
this need at once, and is any of them too junior for what it was handed.
|
|
122
|
+
- `plan-feature` opens **Phased Rollout** with a **Crew** table — one row per
|
|
123
|
+
agent, carrying its tier and the phases it takes — and every phase carries an
|
|
124
|
+
**`**Assigned to**:`** line naming one of them. It replaces
|
|
125
|
+
`**Suggested AI model**:`; a plan that still has the old line keeps working, and
|
|
126
|
+
the executor reads the tier straight off it.
|
|
127
|
+
- The **Execution graph** table gains an **Agent** column and an **Idle** note.
|
|
128
|
+
Reading across a row says who is working that wave; reading down a column says
|
|
129
|
+
who is not. A wave that will serialize because the roster cannot staff it is now
|
|
130
|
+
stated in the plan rather than discovered as a slow run.
|
|
131
|
+
- **Every member has to earn their place**, by concurrency (a wave genuinely needs
|
|
132
|
+
that many hands *at or above* those phases' tiers) or by cheapness (they take
|
|
133
|
+
work a dearer member would otherwise do). The roster is therefore **not** capped
|
|
134
|
+
at the widest wave — a junior who runs the migration and the flag deletion is
|
|
135
|
+
worth having in a graph that never runs two phases at once — but the lane pool
|
|
136
|
+
still is, because lanes are bought with concurrency and crew are not.
|
|
137
|
+
- **Reviewers are a role on the crew, and implementers and reviewers are
|
|
138
|
+
disjoint.** A member has `role: implementer` or `role: reviewer`; a phase cannot
|
|
139
|
+
be assigned to a reviewer and a reviewer never writes code, so an agent reading
|
|
140
|
+
its own diff is not something a plan can express. A phase is read by the
|
|
141
|
+
cheapest reviewer at or above its tier. A roster with no reviewer still runs and
|
|
142
|
+
falls back to `agent_models.reviewer`, cold, one session per phase.
|
|
143
|
+
- **A reviewer reads the lane it is reviewing**, with the phase's changes still
|
|
144
|
+
uncommitted in it, and has no worktree of its own. That is what puts review
|
|
145
|
+
before the commit: a finding is fixed in the working tree rather than
|
|
146
|
+
recorded as a mistake on the branch plus a correction after it. The cost is
|
|
147
|
+
that a reviewer's directory follows the work, so its session carries only
|
|
148
|
+
between reviews that land in the same lane.
|
|
149
|
+
- **A crew member is an agent that lives for the whole run.** Each one keeps its
|
|
150
|
+
own worktree and its own session across every phase it takes, so the agent that
|
|
151
|
+
takes Phase 4 still knows what it learned about the codebase in Phase 1 —
|
|
152
|
+
which is most of what a cold agent's first turn of a phase is spent
|
|
153
|
+
rediscovering.
|
|
154
|
+
- This works because the *directory* stops moving. Lanes used to be anonymous
|
|
155
|
+
slots handed out by a free list, so a member landed somewhere different each
|
|
156
|
+
phase and its context described paths it was not standing in. Pinning each
|
|
157
|
+
member to one worktree leaves a far smaller question — which files changed —
|
|
158
|
+
and `git diff --name-only` answers it exactly.
|
|
159
|
+
- A cross-phase continuation therefore gets the new phase's **full brief** (it
|
|
160
|
+
is new work, not a delta) behind a re-orientation: same agent, same directory,
|
|
161
|
+
everything you learned still holds; the tree is on a different branch; these
|
|
162
|
+
files differ; and — the sentence that matters most — whether the previous
|
|
163
|
+
phase's own work is in this tree at all.
|
|
164
|
+
- A delta that cannot be computed is reported as **unknown**, never as empty.
|
|
165
|
+
"Nothing changed" is the one wording that would stop an agent re-reading.
|
|
166
|
+
- Two things still start cold: a member whose previous phase **failed**, because
|
|
167
|
+
its session is the context that failed with it, and the final fix round.
|
|
168
|
+
- The cost, stated plainly: **one worktree and one set of forked databases per
|
|
169
|
+
implementer**, so `resources.lane.capacity` is now the implementer count
|
|
170
|
+
rather than the widest wave, and adding a cheaper implementer is a trade of
|
|
171
|
+
disk against money rather than free.
|
|
172
|
+
- The workflow document gained a top-level **`crew`** block and **`nodes[].crew`**.
|
|
173
|
+
A staffed node carries no `model` — the member has one — and a document that is
|
|
174
|
+
half-staffed, names a member nobody declared, assigns a phase to a reviewer,
|
|
175
|
+
declares an implementer nobody is assigned to, or staffs a reviewer below every
|
|
176
|
+
phase on the plan is refused before the run starts.
|
|
177
|
+
- `vinta-ai-maestro` claims an agent **before** the lane, prefers the member the plan
|
|
178
|
+
named, covers with the **cheapest** qualified free peer when they are busy, and
|
|
179
|
+
**waits rather than handing a phase below its tier** — even with a lane free. A
|
|
180
|
+
lane is disk; a phase run by too junior an agent does not fail cleanly, it fails
|
|
181
|
+
review two rounds later with nothing pointing back at the staffing. A reviewer is
|
|
182
|
+
claimed per review turn rather than per phase — it has one session ledger and
|
|
183
|
+
two concurrent reviews would collide over it — so one reviewer on a three-lane
|
|
184
|
+
plan is a queue at the review step and not a serialised run.
|
|
185
|
+
- The run view reports who actually worked against who the plan said would.
|
|
186
|
+
`substituted` is the figure to read beside a cost that overran: every
|
|
187
|
+
substitution ran at or above the budgeted tier, so a run can be entirely green
|
|
188
|
+
and still have been staffed dearer than planned.
|
|
189
|
+
|
|
190
|
+
- **`implement-plan` runs independent phases in parallel.** The plan now carries a
|
|
191
|
+
dependency graph and the conductor schedules against it: a phase starts as soon as
|
|
192
|
+
every phase it depends on is green and a worktree lane is free, instead of waiting
|
|
193
|
+
for its turn in plan order. A chain-shaped plan behaves exactly as before —
|
|
194
|
+
sequential execution is the one-lane case of the same scheduler.
|
|
195
|
+
- `plan-feature` gives every phase a **`**Depends on**:`** line naming the artifact
|
|
196
|
+
it needs from each upstream phase, and opens **Phased Rollout** with an
|
|
197
|
+
**Execution graph** table derived from those lines. It also checks same-wave
|
|
198
|
+
phases against the **Touch List** for file overlap and tells you to add an edge
|
|
199
|
+
when two phases would fight over the same file.
|
|
200
|
+
- New shared partial `plan-execution/partials/parallel-lanes.md` carries the graph
|
|
201
|
+
parse, the worktree lane pool, the dependency-derived branch topology, the
|
|
202
|
+
dispatch loop, the tracking-directory schema, and the sibling-lane write guard.
|
|
203
|
+
- `prepare-worktree` documents provisioning a **pool** of lanes for one plan and
|
|
204
|
+
records a `reset_cmd` per forked DB so a lane can be reused across phases without
|
|
205
|
+
carrying the previous phase's migrations into the next one's test run.
|
|
206
|
+
- Two new config fields: `run_options.implement-plan.parallel_phases` (default
|
|
207
|
+
`true`) and `run_options.implement-plan.max_parallel_lanes` (default `3`), asked
|
|
208
|
+
at bootstrap alongside the existing `prepare-worktree` follow-ups.
|
|
209
|
+
|
|
210
|
+
- **`plan-feature` emits an executable `ai-plans/<feature-kebab>.workflow.json`
|
|
211
|
+
beside every plan.** The markdown stays the document humans review; the JSON is
|
|
212
|
+
the same phase graph in the form an orchestrator runs — one node per phase, one
|
|
213
|
+
`depends_on` entry per `**Depends on**:` clause carrying both the upstream phase
|
|
214
|
+
and the artifact it provides, plus the phase's Touch List, gates, capacity pools
|
|
215
|
+
and model tier. Validated by
|
|
216
|
+
[`schemas/workflow.v1.schema.json`](schemas/workflow.v1.schema.json), which the
|
|
217
|
+
emitted file references from its `$schema` key so editors validate it as it is
|
|
218
|
+
written. **Unconditional** — no config field, no bootstrap question, no opt-in:
|
|
219
|
+
a project with no orchestrator carries a file nothing reads, and a project that
|
|
220
|
+
adopts one later finds its plans already executable.
|
|
221
|
+
|
|
222
|
+
- **Review findings go back to the phase's own implementer, not to a fresh fixer.**
|
|
223
|
+
The agent that wrote the code still holds the phase brief, the plan's bounds, the
|
|
224
|
+
dependency context and its own reasoning; a fresh fixer held a quoted finding and
|
|
225
|
+
had to rediscover the rest — slower, dearer, and likelier to "fix" a symptom by
|
|
226
|
+
undoing something the phase chose deliberately. The fix message is now a delta —
|
|
227
|
+
the findings, nothing else — because re-sending the brief invites a
|
|
228
|
+
re-implementation rather than a fix.
|
|
229
|
+
|
|
230
|
+
Three rules keep that from costing what it buys. **The reviewer is never the
|
|
231
|
+
implementer**: continuing the reviewer across rounds is fine, but the review must
|
|
232
|
+
come from an agent that did not write the code. **The last round before giving up
|
|
233
|
+
goes to a fresh fixer, always** — reuse means the agent that wrote the bug is
|
|
234
|
+
fixing it, which is usually the point and occasionally exactly wrong, because that
|
|
235
|
+
assumption *was* the bug. And where a runtime cannot continue a finished
|
|
236
|
+
sub-agent, the old cold hand-off still happens and is recorded as one, so a slow
|
|
237
|
+
phase can be read later without guessing.
|
|
238
|
+
|
|
239
|
+
One consequence worth knowing: a continued implementer fixes at its own crew
|
|
240
|
+
member's tier, so `agent_models.fixer` now governs only the cold cases. A project
|
|
241
|
+
that set `fixer` cheap to save money is saving it on fewer rounds.
|
|
242
|
+
|
|
243
|
+
- **The workflow schema gained `defaults.max_session_turns`.** Optional, default
|
|
244
|
+
12. An orchestrator that reuses one agent session across a phase's implement
|
|
245
|
+
and fix turns needs a ceiling on how long that session may grow before the next
|
|
246
|
+
turn starts cold; without one a long phase eventually dies on a context-window
|
|
247
|
+
error that reads as a broken harness. `plan-feature` omits the field — it is a
|
|
248
|
+
safety limit, not something a plan tunes — and its field table says so.
|
|
249
|
+
|
|
250
|
+
- **`plan-feature` asks about the project's databases and emits the workflow's
|
|
251
|
+
`project` block.** The block records what a phase lane must **fork** to be a
|
|
252
|
+
working checkout — the `dev` and `test` databases, and the project's own
|
|
253
|
+
migrate command, which runs once per template database rather than once per
|
|
254
|
+
lane. Without it every lane got a git worktree and nothing else, so two phases
|
|
255
|
+
running concurrently pointed their test gate at the same rows. Asked at
|
|
256
|
+
emission time with one `AskUserQuestion` call ("what does a phase lane need its
|
|
257
|
+
own copy of", "how is that database delivered"); the rest — the migrate
|
|
258
|
+
command, the database names, the env var names — is read out of the project's
|
|
259
|
+
settings and task runner rather than asked.
|
|
260
|
+
|
|
261
|
+
Two rules the skill is explicit about, because both are easy to get wrong:
|
|
262
|
+
**sharing is the absence of a declaration** (there is no `share` value, and a
|
|
263
|
+
lane that reads the main checkout's database has nothing to describe), and
|
|
264
|
+
**each role names its own database** (a lane's fork is named from `name` and
|
|
265
|
+
the lane, not the role, so `dev` and `test` sharing one `name` collapse to one
|
|
266
|
+
forked database). Everything `prepare-worktree` discovers per worktree —
|
|
267
|
+
`reset_cmd`, compose project names, volume forks, seed commands, env-file
|
|
268
|
+
strategy — stays out of the block by design.
|
|
269
|
+
|
|
270
|
+
- **New schema
|
|
271
|
+
[`postmortem.v1.schema.json`](schemas/postmortem.v1.schema.json)** — the
|
|
272
|
+
structured plan post-mortem an orchestrator writes at
|
|
273
|
+
`.vinta-ai-maestro/runs/<run-id>/postmortem.json` once a run has ended: dependencies
|
|
274
|
+
that were declared but never used, dependencies discovered at gate time,
|
|
275
|
+
same-wave phases that actually conflicted, and phases whose real duration
|
|
276
|
+
diverged from their wave placement. `plan-feature` reads it before drawing the
|
|
277
|
+
next feature's Execution graph, which is what stops a plan from repeating a
|
|
278
|
+
defect the last run already paid for. Like
|
|
279
|
+
[`workflow.v1.schema.json`](schemas/workflow.v1.schema.json) it is
|
|
280
|
+
**generated** from a zod source rather than hand-written — see
|
|
281
|
+
[`schemas/README.md`](schemas/README.md).
|
|
282
|
+
|
|
283
|
+
### Changed
|
|
284
|
+
|
|
285
|
+
- **Phase branches base on their dependencies, not on the previous phase.** A phase
|
|
286
|
+
with no dependencies cuts from the default branch; one with a single dependency
|
|
287
|
+
cuts from that phase's branch; one with several cuts from a
|
|
288
|
+
`plan/{plan-id}/integ-{phase-id}` branch that merges them. Each wave is then merged
|
|
289
|
+
into a `plan/{plan-id}/wave-{N}` integration branch, which is what a resume anchors
|
|
290
|
+
on and what the final report points at. The PR `base` follows the same value —
|
|
291
|
+
a stacked plan produces exactly the stack it did before.
|
|
292
|
+
- **Tracking is a directory, not a file.** `{{PLAN_DIR}}/TRACKING_{plan-id}/` holds
|
|
293
|
+
`run.md` (conductor-owned), one `phase-{id}.md` per phase (written only by the lane
|
|
294
|
+
that ran it, committed on that phase's own branch), and `waves/wave-{N}.md`. No two
|
|
295
|
+
branches write the same path, so wave merges no longer conflict on tracking. A run
|
|
296
|
+
started under the old single-file layout is migrated on resume.
|
|
297
|
+
- **An implementer is told about its dependencies only.** Prior-phase context passed
|
|
298
|
+
into a phase prompt is now that phase's transitive dependency closure rather than
|
|
299
|
+
"everything finished so far" — a sibling lane's work is not in the phase's base
|
|
300
|
+
branch, and describing it as done made the implementer code against files it could
|
|
301
|
+
not see.
|
|
302
|
+
- **The stray-write guard covers sibling lanes.** The sandbox denies the whole
|
|
303
|
+
worktree pool root and allows back only the running lane; the review-phase backstop
|
|
304
|
+
checks the main checkout and every sibling workroot after each implementer and
|
|
305
|
+
fixer. A write into a lane that is mid-implementation is worse than a stray
|
|
306
|
+
main-checkout write.
|
|
307
|
+
- **`amend-plan` cascades along the dependency graph.** A rewrite touches the
|
|
308
|
+
amended phase's dependent closure instead of "every phase with a higher number",
|
|
309
|
+
rebases in topological order, rebuilds `integ-` bases before rebasing a
|
|
310
|
+
multi-dependency phase onto them, and refuses to run while any phase is still in
|
|
311
|
+
flight. It gained a `dependency-change` amendment kind for edits to a
|
|
312
|
+
`**Depends on**:` line.
|
|
313
|
+
- **`implement-plan` refuses rather than degrading** when parallel execution is asked
|
|
314
|
+
for but worktrees are unavailable — several agents cannot share one working tree,
|
|
315
|
+
and silently falling back to sequential would discard the schedule the user
|
|
316
|
+
approved.
|
|
317
|
+
- **`modular-commits` supports parallel runs**: lanes commit their atomic units on
|
|
318
|
+
`plan/{plan-id}/lane-{phase-id}` branches, merged `--no-ff` into the single plan
|
|
319
|
+
branch at each wave boundary, so the unit commits survive.
|
|
320
|
+
|
|
321
|
+
### Fixed
|
|
322
|
+
|
|
323
|
+
- **A retried phase keeps the commits the attempt before it made.** A failed
|
|
324
|
+
phase re-enters its pipeline at the initial state, which runs `git_branch`
|
|
325
|
+
again — and `checkout -B` moves an existing branch unconditionally, so attempt
|
|
326
|
+
2 began by resetting attempt 1's work to base. The files left the worktree,
|
|
327
|
+
the next reviewer correctly reported "phase not implemented at all", and the
|
|
328
|
+
fix budget burned re-implementing from nothing until the operator was asked. A
|
|
329
|
+
phase that produced real commits was guaranteed to lose them.
|
|
330
|
+
|
|
331
|
+
"Cold" was always meant to describe the agent's *session*, not its branch —
|
|
332
|
+
and the default policy targets environmental failures, which is exactly when
|
|
333
|
+
the previous attempt's code is worth keeping. A resumed branch is checked out,
|
|
334
|
+
never rebased onto a base that moved: a conflict there would fail the retry
|
|
335
|
+
during its setup, before the agent that might resolve it has run. A branch
|
|
336
|
+
that committed nothing is still moved up. Whether this is a second attempt is
|
|
337
|
+
read from the journal rather than from the ref, because a phase branch's name
|
|
338
|
+
carries the plan id and not the run id — an identically named branch left by
|
|
339
|
+
an earlier run must still be cut fresh. `node_assigned` now records
|
|
340
|
+
`previous_head`, so a reset that does happen is visible in the run rather than
|
|
341
|
+
only in a reflog.
|
|
342
|
+
|
|
343
|
+
- **A phase is judged on commits, and the prompts finally say so.** An
|
|
344
|
+
implementer ran four sessions, reported `SUCCESS` with a green inner loop, and
|
|
345
|
+
never ran `git commit`: its deliverables were untracked files. The reviewer
|
|
346
|
+
reads the committed diff, so it saw an empty one and reported "not implemented
|
|
347
|
+
at all"; the fixer re-implemented, also without committing; the rounds ran out
|
|
348
|
+
and the lane was recycled over the files. Every instruction that agent had was
|
|
349
|
+
*about* committing — "never commit while a gate is red" — and none of them
|
|
350
|
+
said it had to.
|
|
351
|
+
|
|
352
|
+
Implementers and fixers, cold and continued, are now told that the phase is
|
|
353
|
+
reviewed and merged from commits on its branch, that an uncommitted file does
|
|
354
|
+
not exist as far as the run is concerned, and that the turn is not over until
|
|
355
|
+
`git status --porcelain` is empty of their work — staged by explicit path,
|
|
356
|
+
never `git add -A`, because a lane holds local files that are not theirs to
|
|
357
|
+
commit. The reviewer reads the working tree as well as the diff: a full tree
|
|
358
|
+
with an empty diff is a real failure and is *not* "not implemented", and the
|
|
359
|
+
difference decides whether the fixer writes the code again or simply commits
|
|
360
|
+
it.
|
|
361
|
+
|
|
362
|
+
- **A recycled lane no longer deletes work nobody committed.** Both recycle
|
|
363
|
+
paths throw the working tree away. The tree is now committed first — with
|
|
364
|
+
plumbing, and **off the branch**, under `refs/vinta-ai-maestro/wip/`. On the
|
|
365
|
+
branch was the first implementation, and it was wrong: a lane's dirty tree
|
|
366
|
+
holds gate artifacts as often as deliverables, and a phase branch is the base
|
|
367
|
+
of its dependents, so one gate's scratch file reached the next phase and
|
|
368
|
+
failed a gate for a reason nothing in that phase caused. Nothing merges,
|
|
369
|
+
nothing reaches a dependent, and one `git restore --source <ref>` gets a file
|
|
370
|
+
back. Where the rescue itself fails, the recycle refuses and names the paths.
|
|
371
|
+
|
|
372
|
+
The retry question says how much is at stake: commits are kept, and *N*
|
|
373
|
+
uncommitted files in the lane will be set aside.
|
|
374
|
+
|
|
375
|
+
- **`project.hooks: skip`.** Committing is not optional any more, and a
|
|
376
|
+
`language: system` pre-commit chain reads a never-committed-in worktree as a
|
|
377
|
+
fresh machine — one project's hooks built a 510 MB virtualenv before allowing
|
|
378
|
+
a first commit, per lane, after four attempts and a two-minute timeout. Set
|
|
379
|
+
per worktree, so the operator's own checkout keeps its hooks, and opt-in,
|
|
380
|
+
because hooks are usually there for a reason.
|
|
381
|
+
|
|
382
|
+
- **`doctor` knows when a project needs docker compose.** It assembled its
|
|
383
|
+
options without a `project`, so `needsCompose` was handed `undefined` on every
|
|
384
|
+
invocation and reported "not required by this project" for a
|
|
385
|
+
compose-delivered database — and `run`'s own preflight did the same. A check
|
|
386
|
+
nothing can reach is worse than an absent one: it reads as a pass.
|
|
387
|
+
|
|
388
|
+
- **A failing `setup_cmd` says why.** It carried a lane name and nothing else,
|
|
389
|
+
so the operator saw "could not provision the lane pool under …" and had to
|
|
390
|
+
replay the command by hand with the lane environment rebuilt to find a missing
|
|
391
|
+
settings module. It carries the exit code and a bounded stderr tail now — the
|
|
392
|
+
same class of thing as a harness refusal's own explanation, which this package
|
|
393
|
+
already decided to carry after an afternoon lost to the same silence.
|
|
394
|
+
|
|
395
|
+
- **A lane the executor was not given is refused, not guessed.** It fell back to
|
|
396
|
+
the right directory with an *empty environment* — a gate running against the
|
|
397
|
+
wrong compose project and no forked connection string, with nothing anywhere
|
|
398
|
+
saying a lane was missing.
|
|
399
|
+
|
|
400
|
+
- **The monitor reads, and does not run.** Its shell has no lane environment, so
|
|
401
|
+
the project's own commands run from it contend with the lanes instead of
|
|
402
|
+
observing them. One reported "the final gate fails on a port conflict" when no
|
|
403
|
+
gate had run at all, and the operator believed it. Gate results come from the
|
|
404
|
+
journal and the gate logs.
|
|
405
|
+
|
|
406
|
+
- **`serve --host 0.0.0.0` prints a URL another machine can open.** Binding
|
|
407
|
+
every interface made the server report the wildcard back, so the line printed
|
|
408
|
+
for the operator to open — and share — was `http://0.0.0.0:<port>`, which
|
|
409
|
+
resolves for nobody. The bind is unchanged; the printed host is now this
|
|
410
|
+
machine's LAN address. The daemon still warns, because the token in that URL
|
|
411
|
+
is the only thing between the run and anyone who can reach the port.
|
|
412
|
+
|
|
413
|
+
- **An agent can run a shell command.** `auto` mapped to the vendor's
|
|
414
|
+
`acceptEdits`, which accepts file *edits* and nothing else — a shell command
|
|
415
|
+
still goes to a permission prompt, and in `-p` there is nobody to answer one.
|
|
416
|
+
A run of eight phases died of it: the agents wrote their code and were then
|
|
417
|
+
refused `ruff`, `pytest`, `git add` and `docker compose`, **69 denials across
|
|
418
|
+
two lanes**, every gate failing on work that was never allowed to be checked.
|
|
419
|
+
`auto` now allows `Bash` in the policy file it already writes.
|
|
420
|
+
|
|
421
|
+
What that costs is worth stating plainly: **`Bash` was never confined to the
|
|
422
|
+
lane.** The deny list covers the file-editing tools and a shell redirection
|
|
423
|
+
walks through it, so allowing the shell allows commands on the machine.
|
|
424
|
+
`auto` has always promised exactly this in words — "the agent works unattended
|
|
425
|
+
inside its lane" — and this is the first version where the words are true.
|
|
426
|
+
`ask` still does not allow it: that mode exists for a human at the browser,
|
|
427
|
+
and the prompt is its purpose.
|
|
428
|
+
|
|
429
|
+
- **A refusal says why, in the words the harness used.** `permission_denied`
|
|
430
|
+
carried a decision token and deliberately dropped the sentence beside it, on
|
|
431
|
+
§11 grounds. Then a run failed with forty-five denials reading
|
|
432
|
+
`reason: "other"`, and finding out what that meant cost an afternoon and a
|
|
433
|
+
rebuilt reproduction — the sentence said "this Bash command contains multiple
|
|
434
|
+
operations; the following parts require approval". §11 keeps repository
|
|
435
|
+
*contents* out of the record; a refusal's own explanation is not contents, and
|
|
436
|
+
the `tool_use` row above it already carries the path and the command verbatim.
|
|
437
|
+
Withholding it protected nothing and hid the one fact worth having.
|
|
438
|
+
|
|
439
|
+
- **`serve` opens a run that has already finished.** The API resolved a run from
|
|
440
|
+
the in-memory registry *and* the journal and required both, so a daemon
|
|
441
|
+
started with nothing running listed the operator's history — the list reads
|
|
442
|
+
the journal — and then answered 404 for every run in it, under a message
|
|
443
|
+
saying the daemon was not running. Reads now need only the journal, which is
|
|
444
|
+
where a run's whole record lives and which outlives the daemon by design
|
|
445
|
+
(§5.3). Steering a finished run is still refused, with `409` rather than
|
|
446
|
+
`404`: the run exists, and saying it does not sends the operator hunting for a
|
|
447
|
+
typo instead of reading the status in front of them.
|
|
448
|
+
|
|
449
|
+
- **Four places the UI asked the operator to work around it.** The transcript
|
|
450
|
+
opened at the *oldest* row it held, so a live agent's newest line — the one
|
|
451
|
+
the panel was opened to read — sat below the fold and every arriving entry
|
|
452
|
+
pushed it further down; it now opens at the newest and stays there, but only
|
|
453
|
+
while the operator is already at the bottom, because scrolling up is reading.
|
|
454
|
+
The `git diff` command overflowed its panel, uncopyable without selecting
|
|
455
|
+
blind, and now wraps. The shell's bounded column went from 1280px to 1600px:
|
|
456
|
+
1280 is a reading measure, and the Nodes table's columns are ids, lane names
|
|
457
|
+
and branch names, none of which can be abbreviated without losing what
|
|
458
|
+
identifies them. And the notification interval said "Remind every 5 min"
|
|
459
|
+
without ever saying what it would remind anyone about — it repeats the
|
|
460
|
+
notification for a phase that stopped to ask the operator something, until
|
|
461
|
+
they answer, which is what the options say now.
|
|
462
|
+
|
|
463
|
+
- **A killed terminal releases its directory on Windows.** Two PTY tests failed
|
|
464
|
+
teardown with `EBUSY: rmdir`: killing a process there is not synchronous with
|
|
465
|
+
releasing what it held, so the conpty and its background child are gone as far
|
|
466
|
+
as the test is concerned while the OS still has the working directory open.
|
|
467
|
+
|
|
468
|
+
- **A failed phase can be retried instead of ending the run.** `run` takes
|
|
469
|
+
`--on-failure <stop|ask>`. `stop` is the default and is what every run has
|
|
470
|
+
always done — the phase fails, its dependents block, the run finishes. `ask`
|
|
471
|
+
parks the node on a question instead: retry, retry with another member of the
|
|
472
|
+
crew, or stop.
|
|
473
|
+
|
|
474
|
+
It exists because the failures worth retrying are overwhelmingly
|
|
475
|
+
environmental — a permission wall, a grant nobody made, a gate whose service
|
|
476
|
+
was not up — and the only recovery was to start the whole plan again, running
|
|
477
|
+
every phase that had already succeeded a second time. Only pass `ask` when
|
|
478
|
+
somebody is watching: the run waits, which is exactly why it is not the
|
|
479
|
+
default.
|
|
480
|
+
|
|
481
|
+
The alternatives offered are **members of the crew, not models**. A staffed
|
|
482
|
+
run has no free-floating models in it — a phase is taken by a member, and the
|
|
483
|
+
tier floor that decides who may take it is the assigned member's own — so a
|
|
484
|
+
model is something the plan cannot express and nobody could be found to hold.
|
|
485
|
+
A member below the phase's tier is never offered: an operator answering a
|
|
486
|
+
question is not a reason to hand a phase to somebody the plan judged too
|
|
487
|
+
junior for it.
|
|
488
|
+
|
|
489
|
+
The retry starts **cold**, and everything the node held goes back before it
|
|
490
|
+
waits (§6.1) — an operator at lunch must not be holding a lane another phase
|
|
491
|
+
could use.
|
|
492
|
+
|
|
493
|
+
- **A refusal nobody was asked about is now said out loud.** The adapter knew
|
|
494
|
+
about `permission_request`, a question waiting for an answer, and nothing
|
|
495
|
+
else. The refusal an operator actually hits is not a question: a read outside
|
|
496
|
+
the working directory is decided by the CLI and announced as
|
|
497
|
+
`permission_denied`, with no request to reply to. That frame was dropped at
|
|
498
|
+
the first `switch` — no event, no journal row, no transcript line. The new
|
|
499
|
+
`permission_denied` event carries the decision token (`workingDir`, `mode`)
|
|
500
|
+
and never the vendor's prose, which names the file being read (§11).
|
|
501
|
+
|
|
502
|
+
- **A turn that ended blocked is no longer a turn that succeeded.** The CLI
|
|
503
|
+
says `status_category: "blocked"` one frame before reporting
|
|
504
|
+
`is_error: false`, and both are true from its side: it was asked for
|
|
505
|
+
something it could not do and said so. Taking the second at its word is how a
|
|
506
|
+
phase passed having written no code and then failed two steps later under
|
|
507
|
+
another name. A session that reported an error no longer ends `ok` — which
|
|
508
|
+
also makes a reviewer fail closed, since its verdict is read back out of a
|
|
509
|
+
transcript whose session did not end cleanly.
|
|
510
|
+
|
|
511
|
+
- **An agent still could not write in its own lane.** The entry below passed
|
|
512
|
+
claude-code `--permission-mode auto`, which matched our own vocabulary for
|
|
513
|
+
"works unattended" and is not what the vendor means by the word: `auto` still
|
|
514
|
+
routes a write to a permission prompt, and in `-p` there is nobody to answer
|
|
515
|
+
one. Run against the CLI, a `Write` to the agent's *own working directory*
|
|
516
|
+
comes back denied with no reason attached — the same dead end, wearing the
|
|
517
|
+
word that made it look closed. `acceptEdits` is the mode under which that
|
|
518
|
+
write succeeds, and is what `auto` now maps to. The test covering this
|
|
519
|
+
asserted the word rather than the behaviour, so it pinned the bug in place.
|
|
520
|
+
|
|
521
|
+
- **A phase can read the operator's checkout.** A lane is a worktree cut from a
|
|
522
|
+
branch, so anything uncommitted — a plan written this morning, a spec that
|
|
523
|
+
never leaves the operator's machine — is present where they are and absent
|
|
524
|
+
from every lane. Reaching for it was refused before the model saw a byte, with
|
|
525
|
+
a message that reads like a prompt awaiting an answer ("Claude requested
|
|
526
|
+
permissions to read from …, but you haven't granted it yet") when in fact
|
|
527
|
+
nothing is asking: the CLI denies it outright, emits no permission request,
|
|
528
|
+
and the session then ends *successfully*, having written nothing.
|
|
529
|
+
|
|
530
|
+
The repository root is now granted to every claude-code agent for **reading**.
|
|
531
|
+
Granting a directory also grants writing in it, and the lane sits inside the
|
|
532
|
+
directory being granted, so the grant is paired with a deny list that keeps
|
|
533
|
+
the lane the only writable place under it — the operator's source and every
|
|
534
|
+
sibling lane stay refused. That list names the *siblings* at each level down
|
|
535
|
+
to the lane rather than the root itself, because the vendor resolves deny
|
|
536
|
+
before allow with no carve-out: `deny: <repo>/**` plus `allow: <lane>/**`
|
|
537
|
+
refuses the lane too. Each of those facts was established by running the CLI.
|
|
538
|
+
|
|
539
|
+
**This covers the file-editing tools and not `Bash`.** A shell redirection was
|
|
540
|
+
never confined to the working directory and is not confined now; that boundary
|
|
541
|
+
is the OS's, and the vendor's sandbox that enforces it also switches off the
|
|
542
|
+
network a phase needs to install anything.
|
|
543
|
+
|
|
544
|
+
codex needed nothing: under `--approve-for-me` it already reads anywhere on
|
|
545
|
+
disk, which was confirmed the same way.
|
|
546
|
+
|
|
547
|
+
- **An agent can write in its own lane.** Every adapter declared
|
|
548
|
+
`permissionControl: true` and passed no policy at all: claude-code was spawned
|
|
549
|
+
as `-p --output-format stream-json …` with no `--permission-mode`, and codex as
|
|
550
|
+
`exec --json` with no sandbox and no approval flag, under a comment saying it
|
|
551
|
+
took both on the command line. So a headless run asked before its first write,
|
|
552
|
+
emitted a `permission_request`, rendered it in the transcript — and nothing
|
|
553
|
+
anywhere answered. The phase failed reporting a permission system it could not
|
|
554
|
+
see, having written nothing.
|
|
555
|
+
|
|
556
|
+
`run` and `serve` now take `--permission <ask|auto|full>`, defaulting to
|
|
557
|
+
`auto`. **The operator sets it, never the workflow document**: the document is
|
|
558
|
+
committed and shared, and a file in a repository should not be able to tell
|
|
559
|
+
someone else's machine to run agents without approvals.
|
|
560
|
+
|
|
561
|
+
Both CLIs refuse combinations that looked reasonable, and both were found by
|
|
562
|
+
running them rather than reading them: codex rejects `--sandbox` alongside
|
|
563
|
+
`--approve-for-me`, and `codex exec resume` accepts neither — passing the
|
|
564
|
+
fresh-spawn policy there turned a `stale_session` refusal, which the scheduler
|
|
565
|
+
retries cold, into a `fatal` one that fails the node.
|
|
566
|
+
|
|
567
|
+
This reverses a documented decision. The README said the daemon passed no
|
|
568
|
+
permission flags and that a committed `.claude/settings.json` was what made a
|
|
569
|
+
run able to write; that is now the narrowing layer on top of a mode, not the
|
|
570
|
+
only thing standing between an agent and a blocked lane.
|
|
571
|
+
- **`purge --lanes --branches` clears what a failed run leaves behind.** A run's
|
|
572
|
+
lane worktrees are not under `runs/`, so no purge ever reached them: three
|
|
573
|
+
failed attempts at one plan left twelve worktrees, twelve summaries, and a set
|
|
574
|
+
of `plan/<id>/phase-*` branches still checked out — which is what makes the
|
|
575
|
+
*next* run of that plan impossible, since git refuses to check out a branch a
|
|
576
|
+
second worktree holds.
|
|
577
|
+
|
|
578
|
+
One rule decides both: **never delete the only copy of work.** A phase can
|
|
579
|
+
leave work committed on its branch or uncommitted in its worktree, and both
|
|
580
|
+
count — so a branch carrying commits no other branch has is kept, a lane with
|
|
581
|
+
a dirty tree is kept, and each is listed with the command to remove it by
|
|
582
|
+
hand. Untracked files count as work: a phase that wrote three new modules and
|
|
583
|
+
never committed them is exactly the case worth protecting.
|
|
584
|
+
|
|
585
|
+
Emptiness is measured as "carries no commit another branch does not already
|
|
586
|
+
have", not as "merged into HEAD". The latter is relative to wherever the
|
|
587
|
+
operator is standing, so on a feature branch — the checkout this is most often
|
|
588
|
+
run from — every empty phase branch looked unmerged and nothing was ever
|
|
589
|
+
cleaned up.
|
|
590
|
+
|
|
591
|
+
`--dry-run` and `--yes` work as they already did, and a worktree that will not
|
|
592
|
+
go is named rather than counted.
|
|
593
|
+
|
|
594
|
+
- **`doctor` now catches, before a lane exists, the failures that used to take a
|
|
595
|
+
run each.** Four consecutive runs of one plan failed four different ways, and
|
|
596
|
+
every one of them was knowable at minute zero from the workflow and the repo.
|
|
597
|
+
- **Every `prompt_ref` and `plan_context_ref` is resolved against
|
|
598
|
+
`base_branch`**, with `git cat-file -e`, which reads the branch's tree and
|
|
599
|
+
so cannot be satisfied by a working-tree copy — the working tree being
|
|
600
|
+
exactly what a lane will not have. It distinguishes the three cases, because
|
|
601
|
+
they have three different fixes: never committed, **staged but never
|
|
602
|
+
committed** (`git add` alone does not put a file in a branch), and committed
|
|
603
|
+
on a different branch. The last offers both routes out — merge it, or point
|
|
604
|
+
`base_branch` at the branch that has it — since only the operator knows
|
|
605
|
+
which they want.
|
|
606
|
+
- **Phase branches held by another worktree** are reported with the directory
|
|
607
|
+
to remove. Lane directories are named per run and phase branches per
|
|
608
|
+
workflow, so a failed run leaves worktrees holding the branch names the next
|
|
609
|
+
run will try to cut, and git refuses to check out a branch twice. Nothing
|
|
610
|
+
cleans those up: `purge` deletes run state under `.vinta-ai-maestro/runs/`,
|
|
611
|
+
and lane worktrees are not there. `doctor` does not delete them either — a
|
|
612
|
+
failed lane's worktree is the only place its state survives — but it now
|
|
613
|
+
names each one and the exact command.
|
|
614
|
+
|
|
615
|
+
- **A failed node records why.** `node_status` carried `{"status":"failed"}` and
|
|
616
|
+
nothing else, so two runs failing for two unrelated reasons produced identical
|
|
617
|
+
journal rows and the cause survived only in the operator's terminal. The
|
|
618
|
+
reason is now persisted — **sanitized**, not raw: errors the package builds
|
|
619
|
+
itself are identifiers by construction and are kept verbatim, and anything
|
|
620
|
+
else is reduced to its kind, the rule `recycleStage` already followed. The
|
|
621
|
+
journal is durable and API-served, and an exception message from a dependency
|
|
622
|
+
is exactly how repository content gets into one. Blocked dependents record
|
|
623
|
+
which node blocked them.
|
|
624
|
+
|
|
625
|
+
- **Two diagnostics that named the symptom and hid the cause.** Both came out of
|
|
626
|
+
one real run, and neither was a wrong answer — just an unusable one.
|
|
627
|
+
- **A phase whose plan is not committed** failed with `prompt_ref "…" names no
|
|
628
|
+
readable file`, against a path that was spelled correctly. A lane is a fresh
|
|
629
|
+
worktree of the base branch, so a plan sitting untracked in the operator's
|
|
630
|
+
checkout is in the one place the run cannot look. The message now names the
|
|
631
|
+
directory it resolved against and says why a plan is often not in it.
|
|
632
|
+
- **A lane summary the daemon could not accept** reported `summary unreadable`
|
|
633
|
+
and advised deleting the lane — which takes its forked databases with it,
|
|
634
|
+
over what turned out to be two wrong fields. `doctor` now names the fields
|
|
635
|
+
and what was expected, and offers repair before deletion. A file that does
|
|
636
|
+
not parse at all still reports as unreadable, because there is no field to
|
|
637
|
+
name. Neither message carries the offending *value*: a summary holds
|
|
638
|
+
database names and connection variables, and the value that failed
|
|
639
|
+
validation is the likeliest thing in it to be one.
|
|
640
|
+
|
|
641
|
+
`prepare-worktree` gained the rule that would have prevented the summary in
|
|
642
|
+
the first place — its `|` alternatives are closed sets read by machine, `null`
|
|
643
|
+
is how you say "none", and a strategy outside the set should be the closest
|
|
644
|
+
one plus a `note:` rather than a fourth word.
|
|
645
|
+
|
|
646
|
+
- **`npx vinta-ai-maestro` now runs.** The published package pointed its `bin` at
|
|
647
|
+
`src/cli/bin.ts` and relied on Node stripping the types at startup — which Node
|
|
648
|
+
refuses to do anywhere under `node_modules`, unconditionally and with no flag to
|
|
649
|
+
override. So the CLI worked from a checkout and died on first run once installed,
|
|
650
|
+
with `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`.
|
|
651
|
+
|
|
652
|
+
`packages/vinta-ai-maestro` now builds: `tsc` emits `dist/` with
|
|
653
|
+
`rewriteRelativeImportExtensions` (the source imports `.ts` specifiers, which
|
|
654
|
+
emitted JS cannot keep), vite writes the daemon's UI into `dist/ui`, and the
|
|
655
|
+
shebang is rewritten from the source's `--experimental-transform-types` form to
|
|
656
|
+
a plain `#!/usr/bin/env node` — a compiled entry point has no types left to
|
|
657
|
+
transform, and should not ask Node for a flag it is free to retire. `prepack`
|
|
658
|
+
runs the build, so a tarball can never be cut from a stale `dist/`.
|
|
659
|
+
|
|
660
|
+
Three packaging faults surfaced with it, each of which broke an install on its
|
|
661
|
+
own:
|
|
662
|
+
- **The UI was never in the tarball.** `serve` resolves its static root as
|
|
663
|
+
`dist/ui` and refuses to read outside it, so it would have booted and answered
|
|
664
|
+
every page with "build the UI first". The build now asserts `dist/ui/index.html`
|
|
665
|
+
exists rather than trusting a vite config that writes outside its own root.
|
|
666
|
+
- **`tailwindcss` and `tw-animate-css` were imported but never declared.** They
|
|
667
|
+
resolved only through pnpm's `.bin` shim setting `NODE_PATH`; building by any
|
|
668
|
+
other path failed to resolve them. Both are now devDependencies.
|
|
669
|
+
- **Everything the UI imports was a runtime dependency.** React, xterm,
|
|
670
|
+
lucide-react and the three workspace packages are bundled into `dist/ui` by
|
|
671
|
+
vite and cannot be loaded at runtime — yet a consumer installed all of them,
|
|
672
|
+
and two carried `workspace:*` specifiers that no registry understands. Moved to
|
|
673
|
+
devDependencies; the published package now declares six runtime dependencies
|
|
674
|
+
instead of fourteen.
|
|
675
|
+
|
|
676
|
+
CI packs the tarball, installs it into a throwaway project and runs the binary
|
|
677
|
+
from `node_modules`. Every previous check ran from a checkout, which is the one
|
|
678
|
+
place this class of bug cannot appear.
|
|
679
|
+
|
|
680
|
+
- **Lane provisioning serializes `git worktree add`, not only the database
|
|
681
|
+
template.** `prepare-worktree` previously named one serialization point; there
|
|
682
|
+
are two. Git rewrites `.git/worktrees/` metadata on every add, and concurrent
|
|
683
|
+
adds against one repository clobber each other's entries — a reproducible
|
|
684
|
+
corruption, not a theoretical race. Worktrees are added one at a time; the
|
|
685
|
+
expensive per-lane work (dependency linking, database cloning, summary
|
|
686
|
+
writing) still overlaps around it.
|
|
687
|
+
|
|
688
|
+
- **The conflict-fixer round budget is an integration-level setting, not a
|
|
689
|
+
phase's `max_fix_rounds`.** `parallel-lanes.md` now says so, and defaults it to
|
|
690
|
+
two. A merge conflict belongs to a *pair* of phases, so deriving the budget
|
|
691
|
+
from one of them made the answer depend on which phase happened to merge
|
|
692
|
+
second.
|
|
693
|
+
|
|
694
|
+
- **A resolved merge conflict is confirmed by scanning the files, never by asking
|
|
695
|
+
git.** `git add` clears a path's unmerged flag whether or not `<<<<<<<` is
|
|
696
|
+
still sitting in it, so git's index cannot answer "did the fixer actually fix
|
|
697
|
+
it". `parallel-lanes.md` now requires scanning the conflicted paths for
|
|
698
|
+
conflict markers before the merge is committed. Without it, a fixer that did
|
|
699
|
+
nothing produces a merge commit full of markers that passes into the wave
|
|
700
|
+
branch unnoticed.
|
|
701
|
+
|
|
702
|
+
> The repository also gained `packages/vinta-ai-maestro/`, the workspace package
|
|
703
|
+
> that executes these workflows. It is published as its own npm package,
|
|
704
|
+
> `vinta-ai-maestro`, carrying the same version as `vinta-ai-workflows`, and is
|
|
705
|
+
> run with `npx vinta-ai-maestro`. It is not part of the `vinta-ai-workflows`
|
|
706
|
+
> tarball — the root `files` whitelist excludes `packages/`, so nothing in it is
|
|
707
|
+
> installed by `npx vinta-ai-workflows install`. What ships from that work into
|
|
708
|
+
> the skills package is the two generated schemas above and the skill changes
|
|
709
|
+
> that produce and consume them.
|
|
710
|
+
>
|
|
711
|
+
> Its browser UI is now built on `packages/design-system/` (`vinta-design-system`),
|
|
712
|
+
> a second workspace package: oklch tokens in three layers mirroring
|
|
713
|
+
> `vinta-schedule-design-system`, six run-status **tones** (`idle`, `active`,
|
|
714
|
+
> `wait`, `attention`, `ok`, `error` — waiting is amber and failure is red, never
|
|
715
|
+
> the same hue), shadcn/ui components on Tailwind CSS v4, a small layout kit, and
|
|
716
|
+
> a light/dark/system theme. The run graph and the pipeline editor are re-skinned
|
|
717
|
+
> through their own custom properties, so the canvas and the badges beside it
|
|
718
|
+
> share one palette. It and `vinta-dag-editor` publish on their own cadence
|
|
719
|
+
> (`0.1.0`) as dependencies of `vinta-ai-maestro`; nothing in either reaches
|
|
720
|
+
> `npx vinta-ai-workflows install`.
|
|
721
|
+
|
|
8
722
|
## [0.6.1] — 2026-08-17
|
|
9
723
|
|
|
10
724
|
### Fixed
|