vinta-ai-maestro 0.7.0-alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/README.md +323 -0
  2. package/SPEC.md +720 -0
  3. package/package.json +56 -0
  4. package/scripts/fix-node-pty.mjs +53 -0
  5. package/src/admission/admission.ts +422 -0
  6. package/src/admission/clock.ts +28 -0
  7. package/src/admission/waits.ts +87 -0
  8. package/src/amend/amend.ts +438 -0
  9. package/src/amend/diff.ts +212 -0
  10. package/src/amend/index.ts +13 -0
  11. package/src/amend/rebase.ts +120 -0
  12. package/src/analytics/analytics.ts +885 -0
  13. package/src/cli/bin.ts +29 -0
  14. package/src/cli/doctor.ts +65 -0
  15. package/src/cli/index.ts +101 -0
  16. package/src/cli/io.ts +90 -0
  17. package/src/cli/paths.ts +20 -0
  18. package/src/cli/purge.ts +145 -0
  19. package/src/cli/run.ts +584 -0
  20. package/src/cli/serve.ts +163 -0
  21. package/src/cli/simulate.ts +68 -0
  22. package/src/daemon/api.ts +655 -0
  23. package/src/daemon/auth.ts +63 -0
  24. package/src/daemon/control.ts +133 -0
  25. package/src/daemon/harnesses.ts +39 -0
  26. package/src/daemon/index.ts +64 -0
  27. package/src/daemon/pty-frames.ts +94 -0
  28. package/src/daemon/pty.ts +241 -0
  29. package/src/daemon/schemas.ts +556 -0
  30. package/src/daemon/server.ts +219 -0
  31. package/src/daemon/static.ts +193 -0
  32. package/src/daemon/stream.ts +147 -0
  33. package/src/daemon/workflows.ts +133 -0
  34. package/src/doctor/index.ts +380 -0
  35. package/src/doctor/report.ts +33 -0
  36. package/src/executor/executor.ts +548 -0
  37. package/src/executor/index.ts +28 -0
  38. package/src/executor/notify.ts +103 -0
  39. package/src/executor/tracking.ts +185 -0
  40. package/src/gates/cache.ts +207 -0
  41. package/src/gates/runner.ts +132 -0
  42. package/src/graph.ts +111 -0
  43. package/src/harness/adapter.ts +285 -0
  44. package/src/harness/claude-code.ts +598 -0
  45. package/src/harness/codex.ts +625 -0
  46. package/src/harness/contract.ts +413 -0
  47. package/src/harness/mock.ts +279 -0
  48. package/src/harness/opencode.ts +1149 -0
  49. package/src/harness/pty.ts +171 -0
  50. package/src/harness/shared.ts +469 -0
  51. package/src/integration/fixer.ts +78 -0
  52. package/src/integration/git.ts +36 -0
  53. package/src/integration/index.ts +12 -0
  54. package/src/integration/integrator.ts +361 -0
  55. package/src/integration/pr.ts +75 -0
  56. package/src/journal/events.ts +306 -0
  57. package/src/journal/journal.ts +576 -0
  58. package/src/lanes/database.ts +208 -0
  59. package/src/lanes/disk.ts +67 -0
  60. package/src/lanes/pool.ts +422 -0
  61. package/src/lanes/summary.ts +73 -0
  62. package/src/pipeline/effects.ts +155 -0
  63. package/src/pipeline/guard.ts +378 -0
  64. package/src/pipeline/index.ts +31 -0
  65. package/src/pipeline/interpreter.ts +300 -0
  66. package/src/pipeline/standard.ts +179 -0
  67. package/src/platform/platform.ts +353 -0
  68. package/src/postmortem/generate.ts +40 -0
  69. package/src/postmortem/postmortem.ts +682 -0
  70. package/src/postmortem/schema.ts +41 -0
  71. package/src/prompts/index.ts +15 -0
  72. package/src/prompts/prompts.ts +856 -0
  73. package/src/resources/pools.ts +155 -0
  74. package/src/scheduler/crew.ts +216 -0
  75. package/src/scheduler/index.ts +8 -0
  76. package/src/scheduler/scheduler.ts +1736 -0
  77. package/src/scheduler/sessions.ts +194 -0
  78. package/src/schema/build.ts +33 -0
  79. package/src/schema/generate.ts +40 -0
  80. package/src/simulate/clock.ts +69 -0
  81. package/src/simulate/format.ts +113 -0
  82. package/src/simulate/index.ts +14 -0
  83. package/src/simulate/pools.ts +123 -0
  84. package/src/simulate/simulate.ts +445 -0
  85. package/src/types.ts +372 -0
  86. package/src/usage/crew.ts +118 -0
  87. package/src/usage/reuse.ts +166 -0
  88. package/src/usage/usage.ts +553 -0
  89. package/src/validate.ts +253 -0
  90. package/tests/admission.test.ts +358 -0
  91. package/tests/amend.test.ts +807 -0
  92. package/tests/analytics.test.ts +684 -0
  93. package/tests/cli.test.ts +1190 -0
  94. package/tests/crew-rollup.test.ts +133 -0
  95. package/tests/crew.test.ts +209 -0
  96. package/tests/daemon.test.ts +1860 -0
  97. package/tests/doctor.test.ts +379 -0
  98. package/tests/executor.test.ts +982 -0
  99. package/tests/fixtures/golden-workflow.json +244 -0
  100. package/tests/fixtures/plan-feature-example.workflow.json +201 -0
  101. package/tests/fixtures/repo/migrations/001_widgets.sql +4 -0
  102. package/tests/fixtures/repo/package.json +9 -0
  103. package/tests/fixtures/repo/scripts/migrate.mjs +32 -0
  104. package/tests/fixtures/repo/tests/widgets.mjs +19 -0
  105. package/tests/fixtures-support.test.ts +340 -0
  106. package/tests/gate-cache.test.ts +227 -0
  107. package/tests/gates.test.ts +168 -0
  108. package/tests/harness-claude-code.test.ts +623 -0
  109. package/tests/harness-codex.test.ts +702 -0
  110. package/tests/harness-opencode.test.ts +1296 -0
  111. package/tests/harness-pty.test.ts +946 -0
  112. package/tests/harness.test.ts +172 -0
  113. package/tests/integration.test.ts +555 -0
  114. package/tests/journal.test.ts +431 -0
  115. package/tests/lanes.test.ts +400 -0
  116. package/tests/pipeline.test.ts +603 -0
  117. package/tests/plan-feature-example.test.ts +288 -0
  118. package/tests/platform.test.ts +457 -0
  119. package/tests/postmortem.test.ts +571 -0
  120. package/tests/prompts.test.ts +1110 -0
  121. package/tests/resources.test.ts +284 -0
  122. package/tests/reuse.test.ts +173 -0
  123. package/tests/scheduler.test.ts +2099 -0
  124. package/tests/sessions.test.ts +218 -0
  125. package/tests/simulate.test.ts +475 -0
  126. package/tests/support/fake-cli.ts +176 -0
  127. package/tests/support/gate-script.ts +154 -0
  128. package/tests/support/platform.ts +39 -0
  129. package/tests/usage.test.ts +414 -0
  130. package/tests/workflow.test.ts +464 -0
  131. package/tsconfig.json +13 -0
  132. package/ui/index.html +12 -0
  133. package/ui/src/App.tsx +187 -0
  134. package/ui/src/Chip.tsx +45 -0
  135. package/ui/src/Dag.tsx +78 -0
  136. package/ui/src/Editor.tsx +740 -0
  137. package/ui/src/EditorDag.tsx +83 -0
  138. package/ui/src/EditorPipeline.tsx +90 -0
  139. package/ui/src/Live.tsx +28 -0
  140. package/ui/src/Node.tsx +698 -0
  141. package/ui/src/Notifications.tsx +77 -0
  142. package/ui/src/Panel.tsx +71 -0
  143. package/ui/src/Replay.tsx +310 -0
  144. package/ui/src/Run.tsx +545 -0
  145. package/ui/src/Runs.tsx +121 -0
  146. package/ui/src/Terminal.tsx +170 -0
  147. package/ui/src/Transcript.tsx +111 -0
  148. package/ui/src/app.css +100 -0
  149. package/ui/src/client.ts +212 -0
  150. package/ui/src/editor-client.ts +104 -0
  151. package/ui/src/editor-model.ts +397 -0
  152. package/ui/src/main.tsx +21 -0
  153. package/ui/src/notifications.ts +355 -0
  154. package/ui/src/projection.ts +67 -0
  155. package/ui/src/pty-link.ts +72 -0
  156. package/ui/src/replay-client.ts +54 -0
  157. package/ui/src/replay.ts +245 -0
  158. package/ui/src/status.ts +51 -0
  159. package/ui/src/theme.tsx +67 -0
  160. package/ui/src/time.ts +26 -0
  161. package/ui/src/transcript.ts +165 -0
  162. package/ui/src/useRun.ts +172 -0
  163. package/ui/tests/app-css.test.ts +83 -0
  164. package/ui/tests/contract.test.tsx +59 -0
  165. package/ui/tests/editor-model.test.tsx +189 -0
  166. package/ui/tests/editor-view.test.tsx +287 -0
  167. package/ui/tests/fake-notification.ts +70 -0
  168. package/ui/tests/fixtures.ts +161 -0
  169. package/ui/tests/node-view.test.tsx +581 -0
  170. package/ui/tests/notifications.test.ts +185 -0
  171. package/ui/tests/notify-view.test.tsx +136 -0
  172. package/ui/tests/projection.test.ts +73 -0
  173. package/ui/tests/render-app.tsx +39 -0
  174. package/ui/tests/replay-view.test.tsx +346 -0
  175. package/ui/tests/resume.test.tsx +143 -0
  176. package/ui/tests/run-view.test.tsx +304 -0
  177. package/ui/tests/runs-view.test.tsx +51 -0
  178. package/ui/tests/setup.ts +36 -0
  179. package/ui/tests/stub-daemon.ts +426 -0
  180. package/ui/tests/terminal.test.tsx +238 -0
  181. package/ui/tsconfig.json +16 -0
  182. package/ui/vite.config.ts +25 -0
  183. package/vitest.config.ts +64 -0
package/README.md ADDED
@@ -0,0 +1,323 @@
1
+ # vinta-ai-maestro
2
+
3
+ Runs a `plan-feature` plan as a real, code-orchestrated run: independent phases are scheduled concurrently across git worktree lanes, each phase's branch is cut from its own dependencies, expensive gates queue behind capacity limits instead of stampeding, and a browser UI shows the graph, the transcripts and the queue while it happens.
4
+
5
+ The executable artifact is `ai-plans/<feature>.workflow.json` — the file `plan-feature` writes beside every plan. Its schema is [`schemas/workflow.v1.schema.json`](../../schemas/workflow.v1.schema.json) at the repo root, generated from [`src/types.ts`](src/types.ts).
6
+
7
+ [SPEC.md](SPEC.md) is the authority for everything below. Where this README and the spec disagree, the spec is right and this file is stale.
8
+
9
+ **Status: private workspace package.** `vinta-ai-maestro` is not published to npm and is not part of the `vinta-ai-workflows` package that `npx vinta-ai-workflows install` puts in your project — the root `files` whitelist excludes `packages/`. You get it by cloning this repository. It is developed on **macOS and Linux**; Windows support exists and is described under [Platforms](#platforms).
10
+
11
+ ## It does not replace the skills path
12
+
13
+ The zero-install path still works and is still the default. `implement-plan` — the prompt-shaped orchestrator that ships into projects as a skill — runs the same plan with no daemon installed, and that property is the whole value of `vinta-ai-workflows` in a client repo.
14
+
15
+ `vinta-ai-maestro` is an opt-in upgrade for projects that want a scheduler, a UI and a journal instead of a conductor prompt. The two are readings of one description of the same semantics (the partials under `skills/vinta-derive-skills/resources/plan-execution/partials/`), so a plan written for one runs on the other. Nothing about installing this changes what `plan-feature` emits: it writes `workflow.json` unconditionally, daemon or no daemon.
16
+
17
+ ## Requirements
18
+
19
+ - **Node 22 or newer.**
20
+ - **git 2.17 or newer**, with worktree support. `doctor` checks both.
21
+ - **A harness CLI you are already logged into** — `claude`, `codex` or `opencode`. See [Harnesses](#harnesses).
22
+ - Docker Compose, only if your workflow's `project` block declares a `compose`-delivered database.
23
+
24
+ ## Platforms
25
+
26
+ macOS, Linux and Windows. CI runs the whole suite on all three (`.github/workflows/vinta-ai-maestro.yml`) — the *whole* suite, and it passes: **847 tests on Windows**, where eighty of them used to be skipped for being written in `sh`. What still does not run there is one test, for a stated reason, plus whatever needs a real agent CLI that is not installed on a runner. Fixtures are declared as data and rendered for whichever platform is running, so a stand-in CLI is a shebang script on POSIX and a `.cmd` shim on Windows, exactly as npm installs a real one. The four places the operating systems genuinely disagree are decided in one module — `src/platform/platform.ts` — rather than scattered through the code that depends on them. Every function there takes the platform as an argument, so both answers are asserted from either kind of machine in `tests/platform.test.ts`.
27
+
28
+ What differs, and what you inherit as a consequence:
29
+
30
+ | | macOS / Linux | Windows |
31
+ | --- | --- | --- |
32
+ | A gate's `cmd` runs under | `/bin/sh -c` | `cmd.exe /d /s /c` |
33
+ | A harness CLI is spawned | directly | through `cmd.exe`, because npm installs it as a `.cmd` shim |
34
+ | A timed-out or interrupted process is ended by | one signal to its process group | `taskkill /t` |
35
+ | OS notifications | `osascript` / `notify-send` | none — a documented no-op |
36
+
37
+ **`cmd.exe`, not PowerShell.** A gate is *your* command line, and on Windows your own `package.json` scripts and `.cmd` shims already run under `cmd.exe`. `pnpm test && pnpm run lint` means what you meant there; under PowerShell 5.1 `&&` will not parse, and redirection and `%VAR%` differ under both PowerShell versions. So the shell that matches the rest of your tooling wins.
38
+
39
+ Four Windows caveats worth knowing before you rely on it:
40
+
41
+ - **An interrupt is less gentle.** Windows has no console signal Node can send, so `taskkill` is the whole vocabulary. Where POSIX sends `SIGINT` and gives a CLI a moment to persist its session before the deadline, Windows reaches the same deadline having done nothing in between. Take-over and resume still work; the CLI simply gets less warning.
42
+ - **A grandchild that outlives its parent survives.** A process group holds every descendant; `taskkill /t` reads parent links that a dead parent no longer has. If a gate backgrounds something that then loses its parent, it can stay running and hold the pipe open. There is no fix for this without a native dependency, which this package does not take.
43
+ - **`core.autocrlf`.** Git for Windows enables it by default, which means your gates see CRLF where the same gate on Linux sees LF. Nothing here changes that setting for you — it is your repository's decision — but a formatter or a golden-file test that disagrees across platforms is usually this. CI pins it off so the suite tests the committed bytes.
44
+ - **A lane's `node_modules` is a junction**, not a symlink. Real directory symlinks on Windows need Developer Mode or an elevated shell; junctions need neither and behave the same for this purpose.
45
+
46
+ **What CI does not cover** is two things. One is the shebang. `src/cli/bin.ts` starts with `#!/usr/bin/env -S node --experimental-transform-types`, which Windows never reads: npm rewrites it into a generated `.cmd` shim at install time, and this package is private and therefore never installed. Current `cmd-shim` does parse `env -S` and forward the flags, so this is expected to work — but it is expectation, not evidence. CI starts the entry point through Node directly instead, which covers the flag and the module graph and not the shim.
47
+
48
+ The other is opening a pull request. `openPullRequest` hands `gh` to `execFile` directly rather than through `commandInvocation`, which is right for the real thing — `gh` is `gh.exe` on Windows and needs no shell — but leaves no way to point it at a test fixture there, since Node refuses to spawn a `.cmd` without one. Routing it through the seam like every other spawn would be worse than the gap: one of `gh`'s arguments is the pull request body, and `cmd.exe` cannot escape a `\"` or a `%` inside a quoted region, so a spawn that cannot be broken by its own payload would become one that can. `tests/support/platform.ts` carries the whole argument.
49
+
50
+ ## Install and run
51
+
52
+ ```bash
53
+ git clone https://github.com/vintasoftware/vinta-ai-workflows
54
+ cd vinta-ai-workflows
55
+ pnpm install
56
+ ```
57
+
58
+ There is no build step and no bin link, because the package is private. Run the CLI by its path — the file is executable and its shebang asks Node for the TypeScript flags it needs:
59
+
60
+ ```bash
61
+ /path/to/vinta-ai-workflows/packages/vinta-ai-maestro/src/cli/bin.ts --help
62
+ ```
63
+
64
+ Give it a name you will actually type:
65
+
66
+ ```bash
67
+ alias vinta-ai-maestro=/path/to/vinta-ai-workflows/packages/vinta-ai-maestro/src/cli/bin.ts
68
+ ```
69
+
70
+ `node packages/vinta-ai-maestro/src/cli/bin.ts` does **not** work: that bypasses the shebang, and Node's strip-only TypeScript mode rejects the parameter properties the adapters use.
71
+
72
+ Every command runs against a project checkout — your project, not this one. `--repo <dir>` names it; with no flag it is the current directory.
73
+
74
+ ## The five commands
75
+
76
+ | Command | What it does |
77
+ |---|---|
78
+ | `doctor <workflow.json> [--repo <dir>]` | Preflights every check a run depends on and exits non-zero if a run cannot start. |
79
+ | `simulate <workflow.json>` | Projects the schedule without running it — wall clock, critical path, pool contention. Spawns no agent. |
80
+ | `serve [--repo <dir>] [--host <host>] [--port <n>]` | Starts the daemon and prints the URL to open. Its editor edits `<repo>/ai-plans/*.workflow.json`. |
81
+ | `run <workflow.json> [--repo <dir>] [--host <host>] [--port <n>]` | Starts the daemon *and* executes the workflow. Exits when the run ends. |
82
+ | `purge [run-id] [--repo <dir>] [--yes] [--dry-run]` | Deletes run state under `.vinta-ai-maestro/runs/`. |
83
+
84
+ `--port` defaults to `0`, an OS-assigned port printed with the URL. `--host` defaults to `127.0.0.1` — see [The URL is the credential](#the-url-is-the-credential). `vinta-ai-maestro <command> --help` prints the command's own options.
85
+
86
+ Exit codes are three, so a script can tell the cases apart: `0` success, `1` the command ran and the answer was no, `2` the command line was wrong.
87
+
88
+ ## Walkthrough — two phases in parallel
89
+
90
+ A repository with two phases that depend on nothing, so both belong to wave 1 and both run at once.
91
+
92
+ **1. Have a workflow.** `plan-feature` writes one beside every plan, as `ai-plans/<feature-kebab>.workflow.json` — committed, reviewed with the markdown plan, and the same file every command below is pointed at. By hand, save the smallest one that runs two phases in parallel as `ai-plans/widget-tags.workflow.json`:
93
+
94
+ ```jsonc
95
+ {
96
+ "$schema": "https://github.com/vintasoftware/vinta-ai-workflows/schemas/workflow.v1.schema.json",
97
+ "schema_version": 1,
98
+ "id": "widget-tags",
99
+ "plan_ref": "ai-plans/2026-09-10-WIDGET_TAGS_IMPLEMENTATION_PLAN.md",
100
+ "base_branch": "main",
101
+ "defaults": { "harness": "claude-code", "model": "claude-sonnet-5", "pipeline": "standard-phase" },
102
+ "resources": {
103
+ "lane": { "capacity": 2, "kind": "worktree" },
104
+ "test-suite": { "capacity": 1, "kind": "semaphore" }
105
+ },
106
+ "gates": {
107
+ "types": { "cmd": "npm run typecheck", "timeout_s": 300 },
108
+ "unit": { "cmd": "npm test", "requires": ["test-suite"], "timeout_s": 1800 }
109
+ },
110
+ "nodes": [
111
+ { "id": "p1", "name": "Tag model", "depends_on": [],
112
+ "prompt_ref": "ai-plans/2026-09-10-WIDGET_TAGS_IMPLEMENTATION_PLAN.md#phase-1",
113
+ "touches": ["src/tag.js"], "gates": ["types", "unit"] },
114
+ { "id": "p2", "name": "Tag list endpoint", "depends_on": [],
115
+ "prompt_ref": "ai-plans/2026-09-10-WIDGET_TAGS_IMPLEMENTATION_PLAN.md#phase-2",
116
+ "touches": ["src/list.js"], "gates": ["types", "unit"] }
117
+ ]
118
+ }
119
+ ```
120
+
121
+ **2. Prepare the project.** Two things, both one-time and both easy to discover the hard way:
122
+
123
+ - Add `.vinta-ai-maestro/` to the project's `.gitignore`. Nothing adds it for you, and what lands there holds your repository's contents verbatim — see [What `.vinta-ai-maestro/` holds](#what-vinta-ai-maestro-holds). Add `.vinta-ai-workflows/worktrees/` too: a run writes one summary per lane there, and every one of them is absolute paths and machine-local state.
124
+ - Commit the harness permissions a phase needs. A lane is a worktree of your repository, so committed settings travel into it; without them the agents run headless with nothing able to approve a prompt, and each phase ends its turn asking for permission it will never get. For `claude-code` that is a `.claude/settings.json`:
125
+
126
+ ```json
127
+ { "permissions": { "defaultMode": "acceptEdits", "allow": ["Bash", "Read", "Write", "Edit", "Glob", "Grep"] } }
128
+ ```
129
+
130
+ Scope it to what your phases actually need; this is the permissive end.
131
+
132
+ **3. Review and approve it in the editor.** `serve` opens the daemon with no run attached. Its Editor lists every `ai-plans/*.workflow.json` in the project — the file you just wrote, or the one `plan-feature` wrote — and saving writes back to that same file:
133
+
134
+ ```console
135
+ $ vinta-ai-maestro serve
136
+ vinta-ai-maestro: daemon listening on http://127.0.0.1:52218
137
+ Open this URL. It carries the access token, so treat it as a secret:
138
+ http://127.0.0.1:52218/?token=<the-token-printed-here>
139
+ ```
140
+
141
+ The document's `id` and its filename must agree: `widget-tags` lives in `widget-tags.workflow.json`. The id lands in every branch a run cuts (`plan/widget-tags/phase-p1`), so a file that disagrees with itself is refused rather than quietly opened. A save is a rewrite of a committed file, in place and atomically — review it the way you review the plan beside it, with `git diff`:
142
+
143
+ ```console
144
+ $ git diff ai-plans/widget-tags.workflow.json
145
+ ```
146
+
147
+ A save writes the *validated* document — the one the executor would run — so the first save of a hand-written file also normalizes it: two-space indentation, and the fields the schema defaults made explicit (`plan_context_refs: []`, each node's `depends_on`, `gates`, `touches`). That is a one-time diff; every save after it shows only what you changed.
148
+
149
+ Nothing here is copied into `.vinta-ai-maestro/`. The store holds run state; `ai-plans/` holds the source, and the editor edits the source. Saving *during* a run is a different operation: the run has its own frozen snapshot, so the save goes through the amend path, which refuses while an affected node is in flight and rebases the finished ones whose base moved. The file is written only after the run accepts the change.
150
+
151
+ **4. Preflight.**
152
+
153
+ ```console
154
+ $ vinta-ai-maestro doctor ai-plans/widget-tags.workflow.json
155
+ vinta-ai-maestro doctor
156
+
157
+ PASS harness claude-code: installed and authenticated (2.1.236 (Claude Code))
158
+ PASS git: 2.54.0
159
+ PASS git worktrees: usable
160
+ PASS docker compose: not required by this project
161
+ PASS disk: 2 lanes + 1 integration worktree needs 0 MiB, 28.4 GiB free
162
+ PASS lane summaries: none yet — lanes will be provisioned fresh
163
+
164
+ 0 failed, 0 warned, 6 passed
165
+ A run can start.
166
+ ```
167
+
168
+ Every check runs even after one fails, so one report names everything wrong at minute zero. A `FAIL` blocks the run; a `WARN` means it starts degraded — a lane whose forked database has no `reset_cmd` is the usual one, and it just means the lane is single-use.
169
+
170
+ **5. Project the schedule.** `simulate` drives the real scheduler on a virtual clock against a mock harness. It is the cheapest way to see whether the graph is actually parallel, and whether the lane count or a gate pool is the constraint:
171
+
172
+ ```console
173
+ $ vinta-ai-maestro simulate ai-plans/widget-tags.workflow.json
174
+ Simulated run — projection, not a prediction.
175
+
176
+ Projected wall clock: 1h
177
+
178
+ Critical path
179
+ p2 (wave 1) 0s → 1h work 50m, queued 10m
180
+
181
+ Nodes
182
+ node wave status start finish work queued
183
+ p1 1 done 0s 50m 50m 0s
184
+ p2 1 done 0s 1h 50m test-suite 10m
185
+
186
+ Pools
187
+ pool capacity peak busy saturated queued
188
+ lane 2 2 1h 50m 0s
189
+ test-suite 1 1 20m 20m 10m
190
+ ```
191
+
192
+ Both phases start at `0s` — that is the parallelism the plan claimed, confirmed before a model turn is spent. `p2` finishes ten minutes later only because `test-suite` has capacity 1 and `p1` was holding it.
193
+
194
+ **6. Run it.**
195
+
196
+ ```console
197
+ $ vinta-ai-maestro run ai-plans/widget-tags.workflow.json
198
+ vinta-ai-maestro: daemon listening on http://127.0.0.1:52765
199
+ Open this URL. It carries the access token, so treat it as a secret:
200
+ http://127.0.0.1:52765/?token=<the-token-printed-here>
201
+ vinta-ai-maestro: run widget-tags-mtvodosx started (2 nodes).
202
+ ```
203
+
204
+ The daemon comes up before the first node dispatches, so you can open the URL and watch. Both phases are assigned a lane immediately and implement concurrently, each in its own worktree under `.vinta-ai-maestro/lanes/`, on its own branch cut from `base_branch`:
205
+
206
+ ```console
207
+ $ git branch
208
+ * main
209
+ + plan/widget-tags/phase-p1
210
+ + plan/widget-tags/phase-p2
211
+ + wt/widget-tags-mtvodosx-integ
212
+ wt/widget-tags-mtvodosx-lane-1
213
+ wt/widget-tags-mtvodosx-lane-2
214
+ ```
215
+
216
+ `plan/…/phase-<id>` is the phase's own branch; `wt/…` are the branches the lane and integration worktrees are checked out on. When the run ends, read [What this walkthrough has and has not been run against](#what-this-walkthrough-has-and-has-not-been-run-against) before you read the last two lines it prints.
217
+
218
+ **7. Read what happened, then clean up.** A finished run leaves its worktrees, branches and databases in place on purpose — they are the evidence. The post-mortem is written at the end and is what `plan-feature` reads before drawing the next feature's graph:
219
+
220
+ ```console
221
+ $ cat .vinta-ai-maestro/runs/<run-id>/postmortem.json
222
+ $ vinta-ai-maestro purge <run-id> --dry-run
223
+ $ vinta-ai-maestro purge <run-id>
224
+ ```
225
+
226
+ ### What this walkthrough has and has not been run against
227
+
228
+ Every step above is transcribed from a real run of exactly these commands, and that run predates agent prompt composition — at the time, the scheduler handed the harness `node.prompt_ref` as the entire prompt for every role, so the reviewer was never told to end its turn with `VERDICT: pass`, every node fell back to the fail-closed default, and the run ended with `failed nodes: p1, p2`.
229
+
230
+ **That cause is fixed.** `spawn_agent`'s `prompt_template` now selects a composed, per-role prompt, and the shipped `standard-phase` pipeline is driven to `done` in the test suite against real git worktrees, real branches, real gate commands and real merges — including the fix loop, where a red gate produces a fixer and the next review passes. The verdict protocol the reviewer is asked for and the parser that reads it are one definition, so they cannot drift.
231
+
232
+ What has **not** happened is a live run with real agents since that landed. The mechanism is tested; the numbers in the walkthrough above are from the older run. Treat the failure output it describes as history rather than as current behaviour, and expect to be the first to see a full real-agent run reach a merged wave branch.
233
+
234
+ `simulate` remains the fastest way to size `resources.lane` for a plan, because it answers the scheduling question without spending a model turn.
235
+
236
+ ## What `.vinta-ai-maestro/` holds
237
+
238
+ Everything a run writes lives inside the project, never in a global cache directory, so the project's own retention rules reach it:
239
+
240
+ ```
241
+ .vinta-ai-maestro/
242
+ flow.db # SQLite event log — opaque identifiers only
243
+ gate-cache.db # gate results keyed by (gate id, lane tree hash)
244
+ lanes/<lane>/ # the lane worktrees, and .templates/ for forked DBs
245
+ runs/<run-id>/
246
+ workflow.json # the snapshot frozen at run start
247
+ postmortem.json # written once the run has ended
248
+ nodes/<node-id>/
249
+ transcript.jsonl # normalized agent events
250
+ raw.jsonl # the harness's native stream
251
+ gates/<gate-id>.log # the gate's output
252
+ ```
253
+
254
+ **Transcripts and gate logs contain repository contents verbatim** — files the agent read, diffs it produced, test output. Treat that directory as a copy of your source, because it is one. `flow.db` is different by design: structured log fields carry only run, node and session identifiers, never file contents or record data.
255
+
256
+ Two things follow. **Add `.vinta-ai-maestro/` to `.gitignore`** — the daemon does not do it for you, and an unignored store commits your transcripts. And **purge on a schedule if the repository carries a data-handling obligation.**
257
+
258
+ One directory sits outside the store: a run writes a per-lane summary to `.vinta-ai-workflows/worktrees/<lane>.yaml`, in the layout `prepare-worktree` defines. Those hold absolute paths and lane state rather than repository contents, but they are machine-local and belong in `.gitignore` as well.
259
+
260
+ **Retention default: keep until purged.** Nothing under `.vinta-ai-maestro/` expires, rotates, or is deleted on its own; a run's directory survives until someone removes it. `purge` is the mechanism:
261
+
262
+ ```console
263
+ $ vinta-ai-maestro purge --dry-run # every run, listed, nothing deleted
264
+ $ vinta-ai-maestro purge <run-id> # names every path, then asks
265
+ $ vinta-ai-maestro purge <run-id> --yes # for scripts
266
+ ```
267
+
268
+ It removes the run *directory* — snapshot, transcripts, raw streams, gate logs. It does not touch `flow.db`, which holds the identifiers the post-mortem is built from and no repository contents. A run id is a single name, never a path: anything containing a separator or `..` is refused before anything is unlinked.
269
+
270
+ ## The URL is the credential
271
+
272
+ There is no login, no account and no session. The daemon mints a random token at boot, requires it on every request including the WebSocket upgrade, and prints it exactly once, on stdout, in the URL:
273
+
274
+ ```
275
+ vinta-ai-maestro: daemon listening on http://127.0.0.1:52765
276
+ Open this URL. It carries the access token, so treat it as a secret:
277
+ http://127.0.0.1:52765/?token=<the-token-printed-here>
278
+ ```
279
+
280
+ That line is the only place in this package's output where the token ever appears. It is not in the "listening on" line, not in warnings, not in errors — so pasting a log into a ticket is safe, and pasting *that* URL into a ticket publishes the run.
281
+
282
+ **`--host` is explicit and warned about.** The default bind is `127.0.0.1`. Any other value makes the daemon reachable from other machines, and the daemon prints a warning naming the host — on stderr, where it cannot be mistaken for part of the URL. Anyone who can reach the daemon and holds the token can drive the run: there is no per-user access control, by design. Prefer an SSH port-forward to `--host` for a daemon on a bigger box.
283
+
284
+ ## Harnesses
285
+
286
+ Three adapters: `claude-code`, `codex`, `opencode`. They are process supervisors around a CLI you have already logged into.
287
+
288
+ **Subscription authentication only. The daemon never handles an API key** — it does not read a credential store, never prompts for a secret, and never forwards one. `doctor` *reports* on authentication and never performs it: if a harness is logged out, the check fails and prints the command **you** run to log in.
289
+
290
+ Point the adapter at a specific binary with an environment variable, which overrides the bare name on `PATH`:
291
+
292
+ | Harness | Variable | Default |
293
+ |---|---|---|
294
+ | `claude-code` | `VINTA_AI_MAESTRO_CLAUDE_BIN` | `claude` |
295
+ | `codex` | `VINTA_AI_MAESTRO_CODEX_BIN` | `codex` |
296
+ | `opencode` | `VINTA_AI_MAESTRO_OPENCODE_BIN` | `opencode` |
297
+
298
+ **The harness's own permission configuration governs what an agent may do in a lane.** `vinta-ai-maestro` passes no permission flags and cannot answer a permission prompt: a headless session that stops to ask simply ends its turn having done nothing. A lane is a worktree of your repository, so committed settings travel into it — for `claude-code`, a `.claude/settings.json` that grants the tools your phases need is what makes a run able to write at all.
299
+
300
+ ## Limits worth knowing before you rely on it
301
+
302
+ - **Windows runs the same suite as macOS and Linux**, minus one test that cannot be given a fixture there. See [Platforms](#platforms) for that one, and for the caveats you inherit — a less gentle interrupt, `taskkill` in place of process groups, no OS notifications.
303
+ - **One project, one run at a time** per daemon. The journal is keyed by run id, so this is a boundary rather than a design limit — but it is today's boundary.
304
+ - **A full run with real agents has not been done since prompt composition landed.** The path is covered end to end by tests against real git worktrees, gates and merges, but the walkthrough's transcript is from an older run. See [What this walkthrough has and has not been run against](#what-this-walkthrough-has-and-has-not-been-run-against).
305
+ - **A projection is not a prediction.** `simulate` answers "given these durations, what schedule follows", and three things it cannot know:
306
+ - **It cannot predict an agent's turn length.** The durations are yours; the schedule is its answer to them.
307
+ - **Harness concurrency ceilings are not modelled.** A mock session drains instantly, so admission control never blocks. A run that a vendor would throttle projects as if it were not throttled.
308
+ - **It simulates the clean path.** Every review passes, every gate exits zero, no phase needs a fix round.
309
+
310
+ ## Developing on it
311
+
312
+ From this directory:
313
+
314
+ ```bash
315
+ pnpm run typecheck
316
+ pnpm test
317
+ pnpm run schema:check # workflow.v1 vs src/types.ts
318
+ pnpm run postmortem:schema:check # postmortem.v1 vs src/postmortem/postmortem.ts
319
+ ```
320
+
321
+ `schemas/workflow.v1.schema.json` and `schemas/postmortem.v1.schema.json` are **generated** and drift-checked. Edit the zod source and regenerate with `schema:gen` / `postmortem:schema:gen`; never hand-edit the JSON.
322
+
323
+ The browser UI lives in `ui/` and is built on the workspace's design system, [`packages/design-system`](../design-system/README.md) — its tokens, its shadcn/ui components and its layout kit; the two canvas Web Components are re-skinned through their own custom properties in `ui/src/app.css` so the graph and the badges beside it share one palette. `pnpm run ui:dev` serves it with Vite against a running daemon; `pnpm run ui:build` writes the bundle the daemon serves into `dist/ui`. The UI follows the operating system's light or dark scheme by default; the toggle in the top bar remembers a choice per browser. Fonts ship in the bundle — the page makes no request outside its own origin.