@yemi33/minions 0.1.2379 → 0.1.2381

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 (100) hide show
  1. package/bin/minions.js +19 -9
  2. package/dashboard/js/refresh.js +3 -4
  3. package/dashboard/js/render-other.js +43 -23
  4. package/dashboard/js/render-prd.js +1 -1
  5. package/dashboard/js/render-work-items.js +13 -6
  6. package/dashboard/js/settings.js +40 -17
  7. package/dashboard.js +435 -768
  8. package/docs/architecture-review-2026-07-09.md +2 -4
  9. package/docs/auto-discovery.md +36 -49
  10. package/docs/blog-first-successful-dispatch.md +1 -1
  11. package/docs/branch-derivation.md +2 -2
  12. package/docs/command-center.md +1 -1
  13. package/docs/completion-reports.md +14 -4
  14. package/docs/constellation-bridge.md +59 -10
  15. package/docs/constellation-style-telemetry.md +6 -6
  16. package/docs/cooldown-merge-semantics.md +4 -0
  17. package/docs/copilot-cli-schema.md +3 -3
  18. package/docs/cross-repo-plans.md +17 -17
  19. package/docs/deprecated.json +2 -2
  20. package/docs/design-state-storage.md +1 -1
  21. package/docs/documentation-audit-2026-07-09.md +2 -2
  22. package/docs/engine-restart.md +20 -8
  23. package/docs/harness-mode.md +1 -1
  24. package/docs/live-checkout-mode.md +45 -26
  25. package/docs/managed-spawn.md +4 -4
  26. package/docs/onboarding.md +1 -2
  27. package/docs/pr-comment-followup.md +3 -3
  28. package/docs/pr-review-fix-loop.md +1 -1
  29. package/docs/proposals/repo-pool-for-live-checkout.md +1 -2
  30. package/docs/qa-runbook-lifecycle.md +4 -4
  31. package/docs/qa-runbooks.md +2 -2
  32. package/docs/rfc-completion-json.md +4 -1
  33. package/docs/runtime-adapters.md +1 -1
  34. package/docs/self-improvement.md +4 -5
  35. package/docs/shared-lifecycle-module-map.md +3 -1
  36. package/docs/slim-ux/architecture-suggestions.md +5 -6
  37. package/docs/slim-ux/concepts.md +23 -25
  38. package/docs/watches.md +7 -7
  39. package/docs/workspace-manifests.md +1 -1
  40. package/docs/worktree-lifecycle.md +1 -1
  41. package/engine/abandoned-pr-reconciliation.js +4 -5
  42. package/engine/ado-status.js +5 -5
  43. package/engine/ado.js +20 -25
  44. package/engine/agent-worker-pool.js +58 -1
  45. package/engine/bridge.js +260 -5
  46. package/engine/cleanup.js +48 -131
  47. package/engine/cli.js +125 -83
  48. package/engine/cooldown.js +9 -16
  49. package/engine/db/index.js +22 -9
  50. package/engine/db/migrations/009-qa.js +1 -1
  51. package/engine/db/migrations/020-qa-session-scopes.js +23 -0
  52. package/engine/db/migrations/021-archived-work-items.js +72 -0
  53. package/engine/db/migrations/022-global-cc-session.js +31 -0
  54. package/engine/db/migrations/023-engine-state.js +30 -0
  55. package/engine/db/migrations/024-prd-ghost-collisions.js +53 -0
  56. package/engine/db/migrations/025-malformed-work-item-phantoms.js +33 -0
  57. package/engine/dispatch-store.js +2 -7
  58. package/engine/dispatch.js +39 -44
  59. package/engine/github.js +20 -27
  60. package/engine/lifecycle.js +279 -354
  61. package/engine/live-checkout.js +193 -149
  62. package/engine/llm.js +1 -1
  63. package/engine/logs-store.js +2 -2
  64. package/engine/managed-spawn.js +2 -23
  65. package/engine/meeting.js +6 -6
  66. package/engine/metrics-store.js +2 -2
  67. package/engine/note-link-backfill.js +6 -11
  68. package/engine/pipeline.js +18 -36
  69. package/engine/playbook.js +13 -16
  70. package/engine/prd-store.js +73 -54
  71. package/engine/preflight.js +2 -5
  72. package/engine/projects.js +15 -62
  73. package/engine/pull-requests-store.js +0 -17
  74. package/engine/qa-runbooks.js +2 -2
  75. package/engine/qa-runs.js +1 -8
  76. package/engine/qa-sessions.js +41 -64
  77. package/engine/queries.js +120 -219
  78. package/engine/routing.js +4 -6
  79. package/engine/scheduler.js +0 -4
  80. package/engine/shared-branch-pr-reconcile.js +2 -3
  81. package/engine/shared.js +268 -699
  82. package/engine/small-state-store.js +89 -10
  83. package/engine/state-operations.js +16 -4
  84. package/engine/stdio-timestamps.js +1 -1
  85. package/engine/timeout.js +5 -12
  86. package/engine/watch-actions.js +20 -22
  87. package/engine/watches-store.js +1 -1
  88. package/engine/watches.js +6 -10
  89. package/engine/work-item-validation.js +52 -0
  90. package/engine/work-items-store.js +127 -29
  91. package/engine/worktree-gc.js +2 -2
  92. package/engine/worktree-pool.js +8 -18
  93. package/engine.js +197 -358
  94. package/minions.js +2 -2
  95. package/package.json +1 -1
  96. package/playbooks/plan-to-prd.md +2 -2
  97. package/playbooks/shared-rules.md +3 -3
  98. package/playbooks/templates/followup-dispatch.md +1 -1
  99. package/playbooks/verify.md +1 -1
  100. package/prompts/cc-system.md +9 -9
@@ -61,10 +61,8 @@ cancellation.
61
61
 
62
62
  These require separate focused changes rather than expanding this PR:
63
63
 
64
- 1. **Dispatch SQL/JSON empty-state resurrection (high).** A process that has not
65
- written SQL can still adopt stale `dispatch.json` when the SQL table is empty.
66
- The durable fix is a mirror-generation/hash marker or removal of the legacy
67
- fallback, not moving filesystem writes inside a SQLite transaction.
64
+ 1. **Dispatch empty-state resurrection (resolved).** The SQL-only cutover removed
65
+ the legacy fallback; an empty dispatch table now stays empty.
68
66
  2. **Pipeline task terminal-state model (high).** Task stages use a boolean
69
67
  completion check and promote failed work items to a completed pipeline stage;
70
68
  cancelled/review-blocked work can wedge indefinitely. Replace the boolean
@@ -46,11 +46,11 @@ tick()
46
46
 
47
47
  `discoverWork()` iterates every project in `config.projects[]` and runs four core discovery sources: pull requests, per-project work items, central work items, and pipeline/scheduled tasks. Results are prioritized: fixes > reviews > implements > work-items.
48
48
 
49
- Before scanning, the engine materializes plans and specs into project work items (side-effect writes to `work-items.json`), so they're picked up by the work items source below.
49
+ Before scanning, the engine materializes plans and specs into project-scoped SQL work items, so they're picked up by the work-items source below.
50
50
 
51
51
  ### Source 1: Pull Requests (`discoverFromPrs`)
52
52
 
53
- **Reads:** `~/.minions/projects/<project>/pull-requests.json`
53
+ **Reads:** project-scoped `pull_requests` rows in `engine/state.db`
54
54
 
55
55
  | PR State | Action | Dispatch Type |
56
56
  |----------|--------|---------------|
@@ -92,8 +92,8 @@ Flip via Dashboard → Settings → Polling → "Granular per-poller controls" c
92
92
  | Flag | Default | Phase inside `discoverWork()` |
93
93
  |------|---------|--------------------------------|
94
94
  | `prDiscoveryEnabled` | `true` | Per-project `discoverFromPrs(config, project)` — PR-derived fix / review / build-test work |
95
- | `workItemsDiscoveryEnabled` | `true` | Per-project `discoverFromWorkItems(config, project)` — project-local `work-items.json` scan |
96
- | `centralWorkDiscoveryEnabled` | `true` | `discoverCentralWorkItems(config)` — top-level project-agnostic `work-items.json` scan |
95
+ | `workItemsDiscoveryEnabled` | `true` | Per-project `discoverFromWorkItems(config, project)` — project SQL scope |
96
+ | `centralWorkDiscoveryEnabled` | `true` | `discoverCentralWorkItems(config)` — central SQL scope |
97
97
  | `scheduledWorkDiscoveryEnabled` | `true` | `discoverScheduledWork(config)` — cron-style scheduled tasks + scheduled meetings |
98
98
  | `planMaterializationEnabled` | `true` | `reconcilePrdStatuses(config)` + `materializePlansAsWorkItems(config)` (paired side-effect passes) |
99
99
 
@@ -116,7 +116,7 @@ PRD items flow through `materializePlansAsWorkItems()`, which queries SQL PRDs f
116
116
 
117
117
  ### Source 3: Per-Project Work Items (`discoverFromWorkItems`)
118
118
 
119
- **Reads:** `~/.minions/projects/<project>/work-items.json`
119
+ **Reads:** the project's `work_items` SQL scope
120
120
 
121
121
  | Item State | Action | Dispatch Type |
122
122
  |------------|--------|---------------|
@@ -126,7 +126,7 @@ After dispatching, the engine writes `status: "dispatched"` back to the item. Th
126
126
 
127
127
  ### Source 4: Central Work Items (`discoverCentralWorkItems`)
128
128
 
129
- **Reads:** `~/.minions/work-items.json` (central, project-agnostic)
129
+ **Reads:** the central `work_items` SQL scope
130
130
 
131
131
  These are tasks where the agent decides which project to work in. The engine builds a prompt that includes a list of all linked projects with their paths and repo config. The agent then navigates to the appropriate project directory based on the task.
132
132
 
@@ -163,7 +163,7 @@ This means a single work item like "Add telemetry to the document creation pipel
163
163
  **Adding central work items:**
164
164
  - Dashboard Command Center → type your intent (no `#project` = central queue)
165
165
  - CLI: `node engine.js work "task title"` (defaults to central queue)
166
- - Direct edit: `~/.minions/work-items.json`
166
+ - API: `POST /api/work-items` with no project
167
167
 
168
168
  ### Materialization: Specs and Plans → Work Items
169
169
 
@@ -184,11 +184,11 @@ priority: high
184
184
 
185
185
  **Plans** (`materializePlansAsWorkItems`): Queries SQL PRDs for `missing`/`planned` items. Creates work items in the target project's queue with `createdBy: 'engine:plan-discovery'`. Work item ID = PRD item ID (e.g. `P-43e5ac28`). Deduped by `id`.
186
186
 
187
- Both write to `work-items.json` and are picked up by Source 3 on the same or next tick.
187
+ Both write to the SQL work-item store and are picked up by Source 3 on the same or next tick.
188
188
 
189
189
  ## PR Status Polling (`pollPrStatus`)
190
190
 
191
- **Runs:** On a wall-clock cadence derived from `prPollStatusEvery × engine.tickInterval` (default 72 × 10s, ≈ 12 minutes), independently of work discovery or file-change wakeups. ADO polling lives in `engine/ado.js`; GitHub polling lives in `engine/github.js` — both run in parallel each cycle (`Promise.allSettled`) and write to the same per-project `pull-requests.json` schema. Replaces the retired agent-based `pr-sync`.
191
+ **Runs:** On a wall-clock cadence derived from `prPollStatusEvery × engine.tickInterval` (default 72 × 10s, ≈ 12 minutes), independently of work discovery. ADO polling lives in `engine/ado.js`; GitHub polling lives in `engine/github.js` — both run in parallel each cycle (`Promise.allSettled`) and write the shared SQL PR schema. Replaces the retired agent-based `pr-sync`.
192
192
 
193
193
  The engine directly polls the host REST API for **all** PR metadata: build/CI status, human review votes, and completion state. Two API calls per PR — no agent dispatch needed.
194
194
 
@@ -196,7 +196,7 @@ The engine directly polls the host REST API for **all** PR metadata: build/CI st
196
196
  1. `GET pullrequests/{id}` → `status` (active/completed/abandoned), `mergeStatus`, `reviewers[].vote`
197
197
  2. `GET pullrequests/{id}/statuses` → CI pipeline results
198
198
 
199
- **Fields updated in `pull-requests.json`:**
199
+ **Fields updated in the PR store:**
200
200
 
201
201
  | Field | Source | Values |
202
202
  |-------|--------|--------|
@@ -259,7 +259,7 @@ Resolution order:
259
259
 
260
260
  ## Dispatch Queue
261
261
 
262
- Discovered items land in `engine/dispatch.json`:
262
+ Discovered items land in the SQL dispatch store, exposed as:
263
263
 
264
264
  ```json
265
265
  {
@@ -330,8 +330,8 @@ claude -p --system-prompt-file <sysprompt-file> \
330
330
  - CLAUDECODE env vars stripped to allow nested sessions
331
331
 
332
332
  ### 6. Track State
333
- - Agent status derived from dispatch queue (`engine/dispatch.json`)
334
- - Dispatch item → moved from `pending` to `active` in `dispatch.json`
333
+ - Agent status derived from SQL dispatch state
334
+ - Dispatch item → moved from `pending` to `active` transactionally
335
335
  - Process tracked in `activeProcesses` Map for timeout monitoring
336
336
 
337
337
  ## Post-Completion
@@ -347,10 +347,10 @@ proc.on('close')
347
347
 
348
348
  ├─ Move dispatch item: active → completed
349
349
 
350
- ├─ Sync PRs from output (scan for PR URLs → pull-requests.json)
350
+ ├─ Sync PRs from output (scan for PR URLs → SQL PR store)
351
351
 
352
352
  ├─ Post-completion hooks:
353
- │ review → update PR minionsReview in pull-requests.json, vote on ADO
353
+ │ review → update PR minionsReview in SQL, vote on ADO
354
354
  │ fix → set PR minionsReview back to "waiting"
355
355
  │ build-test → record verification result and findings
356
356
 
@@ -386,39 +386,26 @@ Work type is auto-detected from keywords (fix, explore, test, review → impleme
386
386
 
387
387
  ┌──────────┼──────────┐
388
388
  ▼ ▼ ▼
389
- work-items notes/ plans/
390
- .json inbox/*.md *.md
391
-
392
- Per-project sources: Central engine: Agents:
393
-
394
- work-items.json ──┐
395
- pull-requests.json┤ discoverWork() dispatch.json
396
- docs/**/*.md (specs)(each tick) ┌──────────┐
397
- │ │ pending │
398
- ~/.minions/ │ │ │ active
399
- work-items.json ┤ │ │ completed │
400
- prd/*.json ─────┤ │ └─────┬────┘
401
- plans/*.md ─────┘ ▼
402
- addToDispatch()────────┘
403
-
404
- ADO + GitHub REST ── pollPrStatus() ──► pull-requests.json
405
- (every ~12min) (buildStatus field) │
406
- spawnAgent()
407
-
408
- ┌────────────┼────────────┐
409
- ▼ ▼ ▼
410
- worktree claude CLI dispatch.json
411
- (in project (max 100 (active/completed)
412
- repo dir) turns)
413
-
414
- on exit:
415
-
416
- ┌───────────┬───────┼───────┬──────────┐
417
- ▼ ▼ ▼ ▼ ▼
418
- output.log notes/ PRs work-items localhost
419
- (per agent) inbox/*.md .json .json (if webapp,
420
- │ from build
421
- consolidateInbox() & test)
389
+ SQL work notes/ plans/
390
+ items inbox/*.md *.md
391
+
392
+ Project-scoped SQL ──┐
393
+ Central SQL scope ───┤ discoverWork() ──► SQL dispatch rows
394
+ docs/**/*.md (specs) ┤ │ │
395
+ SQL PRDs ────────────┘ ▼ ▼
396
+ addToDispatch() spawnAgent()
397
+
398
+ ADO + GitHub REST ── pollPrStatus() ──► SQL PR rows
399
+
400
+ worktree + runtime CLI
401
+
402
+ on exit:
403
+
404
+ ┌───────────┬───────────────┼──────────┐
405
+ ▼ ▼ ▼ ▼
406
+ output.log notes/inbox SQL state localhost
407
+ (if webapp)
408
+ consolidateInbox()
422
409
  (at 5+ files)
423
410
 
424
411
 
@@ -451,7 +438,7 @@ Lack of stdout/stderr is not treated as a hang while the engine still has a live
451
438
  | Pull requests | 30 minutes | After dispatching a review, won't re-queue for 30min |
452
439
  | Work items | 0 (immediate) | No cooldown, but item status changes to "dispatched" |
453
440
 
454
- Cooldowns are persisted to `engine/cooldowns.json` and reloaded on engine startup. Entries older than 24 hours are pruned. `isAlreadyDispatched()` still provides an additional guard by checking pending/active and recently completed dispatches in `dispatch.json`.
441
+ Cooldowns are persisted in SQLite and reloaded on engine startup. Entries older than 24 hours are pruned. `isAlreadyDispatched()` also checks pending, active, and recently completed SQL dispatch rows.
455
442
 
456
443
  ## Configuration Reference
457
444
 
@@ -79,7 +79,7 @@ New approach: rely on the tracked process while the engine has a live process ha
79
79
 
80
80
  ### Engine Restart Orphan Problem
81
81
 
82
- When the engine restarts, the in-memory `activeProcesses` Map is lost. Active dispatch items stay in `dispatch.json` but the engine has no process handle. Old stale detection (6h threshold) was too slow to catch this. Stale-orphan detection uses recent `live-output.log` activity and catches abandoned dispatches after the restart grace window.
82
+ When the engine restarts, the in-memory `activeProcesses` Map is lost. Active dispatch rows stay in SQLite but the engine has no process handle. Old stale detection (6h threshold) was too slow to catch this. Stale-orphan detection uses recent `live-output.log` activity and catches abandoned dispatches after the restart grace window.
83
83
 
84
84
  ## The Successful Run
85
85
 
@@ -64,7 +64,7 @@ number namespace, and ADO work items share theirs. Before this guard, a
64
64
  `type: "fix"` WI titled `Fix github:owner/repo#244: …` where **#244 is an
65
65
  issue** got `targetPr` / `pr_id` / `prNumber` stamped by
66
66
  `dashboard.js#copyWorkItemPrFields`, which then (a) minted a phantom
67
- `pull-requests.json` record (`agent: 'human'`, went abandoned, polluted the PR
67
+ PR-store record (`agent: 'human'`, went abandoned, polluted the PR
68
68
  count) via `shared.autoEnrollPrFromFixWorkItem`, and (b) routed the dispatch
69
69
  through the PR-fix path (`targetPr`/`pr_id`/`prNumber`) instead of
70
70
  implement-and-open-a-fresh-PR.
@@ -76,7 +76,7 @@ stamping:
76
76
  / `meta.pr_followup.parent_pr_url` are explicit operator intent and stamp
77
77
  with **no** network call.
78
78
  2. **Loose (description/title-scanned) refs on a fix WI** are confirmed via:
79
- - an already-tracked `pull-requests.json` record for the ref (cheap, no
79
+ - an already-tracked SQL PR record for the ref (cheap, no
80
80
  network), else
81
81
  - a host existence check — `engine/github.js#prExists` (`gh api
82
82
  repos/:owner/:repo/pulls/NNN`; 404 ⇒ issue) or `engine/ado.js#prExists`
@@ -2,7 +2,7 @@
2
2
 
3
3
  The Command Center (CC) is the dashboard's conversational chat panel. It opens from the **CC** button in the top-right header and lets you drive the engine — dispatching work items, saving notes, approving plans, and asking questions about Minions state — through a persistent session on the configured CC runtime.
4
4
 
5
- CC is intentionally a thin wrapper around the runtime CLI: state changes happen via tool-driven calls to the dashboard's own REST API, not via parsed delimiter blocks. The end-to-end flow is `dashboard/js/command-center.js` `_ccDoSend()` → `POST /api/command-center` (or `/api/command-center/stream`) in `dashboard.js` (`handleCommandCenter`) → `engine/llm.js` `callLLM({ direct: true })` → runtime session persisted in `engine/state.db` (`cc_sessions`; `engine/cc-sessions.json` is a passive mirror). Per-turn API mutations are correlated via the `X-CC-Turn-Id` header and surfaced as standalone `role='action'` chips rendered outside the assistant bubble (`_ccActionResultLine` + `addMsg('action', ...)`).
5
+ CC is intentionally a thin wrapper around the runtime CLI: state changes happen via tool-driven calls to the dashboard's own REST API, not via parsed delimiter blocks. The end-to-end flow is `dashboard/js/command-center.js` `_ccDoSend()` → `POST /api/command-center` (or `/api/command-center/stream`) in `dashboard.js` (`handleCommandCenter`) → `engine/llm.js` `callLLM({ direct: true })` → runtime session persisted in `engine/state.db` (`cc_sessions`). Per-turn API mutations are correlated via the `X-CC-Turn-Id` header and surfaced as standalone `role='action'` chips rendered outside the assistant bubble (`_ccActionResultLine` + `addMsg('action', ...)`).
6
6
 
7
7
  For canonical detail (system prompt, session lifecycle, turn-ID surfacing pipeline, doc-chat integration, and CC API contract), read [`CLAUDE.md`](../CLAUDE.md) — see the **CC API Contract** and **Sessions** sections — and the source in [`dashboard/js/command-center.js`](../dashboard/js/command-center.js), [`dashboard.js`](../dashboard.js) (`handleCommandCenter`), and [`prompts/cc-system.md`](../prompts/cc-system.md).
8
8
 
@@ -6,9 +6,11 @@ This document is the canonical schema. Playbooks should cross-link here instead
6
6
 
7
7
  ## Where the report is written
8
8
 
9
- The engine injects the absolute path two ways before spawning each agent:
9
+ The engine delivers the absolute path two ways before spawning each agent:
10
10
 
11
- - `MINIONS_COMPLETION_REPORT` environment variable
11
+ - `MINIONS_COMPLETION_REPORT` in the child environment for cold spawns, or the
12
+ same-named authoritative value in the trusted ACP session context for pooled
13
+ spawns
12
14
  - The same path appears in the rendered playbook prompt
13
15
 
14
16
  Path shape (resolved by `shared.dispatchCompletionReportPath()` in `engine/shared.js`):
@@ -21,7 +23,15 @@ The agent must write the JSON to that exact path before exiting. Any character o
21
23
 
22
24
  ## Trust boundary
23
25
 
24
- Each spawn also receives a per-dispatch cryptographic value via the `MINIONS_COMPLETION_NONCE` environment variable. The engine generates this with `crypto.randomBytes(16).toString('hex')` in `engine.js:spawnAgent()` and stores it on the in-memory active-process record. The agent is required to copy the value verbatim into the report's `nonce` field. On parse, `engine/lifecycle.js:runPostCompletionHooks()` compares `report.nonce` against the in-memory value:
26
+ Each spawn also receives a per-dispatch cryptographic value named
27
+ `MINIONS_COMPLETION_NONCE`. Cold spawns receive it in the child environment;
28
+ pooled ACP spawns receive it in trusted, lease-scoped system context so the
29
+ nonce never enters reusable process state or untrusted task content. The engine
30
+ generates it with `crypto.randomBytes(16).toString('hex')` in
31
+ `engine.js:spawnAgent()` and stores it on the in-memory active-process record.
32
+ The agent is required to copy the value verbatim into the report's `nonce`
33
+ field. On parse, `engine/lifecycle.js:runPostCompletionHooks()` compares
34
+ `report.nonce` against the in-memory value:
25
35
 
26
36
  - **Match** — the report is trusted and processed normally.
27
37
  - **Mismatch** — the report is treated as forged (a prompt-injected agent or a stale process writing into a sibling dispatch's completion path). Every signal it carries — `status`, `pr`, `noop`, `failure_class`, `retryable`, `needs_rerun`, artifacts, follow-ups — is discarded. The dispatch is failed with `failure_class: 'completion-nonce-mismatch'` and the work item is marked failed (no auto-retry honors the agent's `retryable` claim).
@@ -263,7 +273,7 @@ optional `followups` array on its completion report:
263
273
  Engine behavior (`engine/lifecycle.js` `processCompletionFollowups`):
264
274
 
265
275
  - Each entry is logged at `info`: `Followup audit (<agent> wi=<parent>): dispatched <wi_id> — <title> (comment=<id>) — <reason>`.
266
- - If the claimed `wi_id` does not exist in any project's `work-items.json` at parse time, a `warn` is emitted. This catches dispatches that were deduped to a 409 / 200-duplicate response, reverted, or never actually landed.
276
+ - If the claimed `wi_id` does not exist in any project SQL scope at parse time, a `warn` is emitted. This catches dispatches that were deduped to a 409 / 200-duplicate response, reverted, or never actually landed.
267
277
  - The `followups` array is **descriptive only** — the engine does not auto-create or auto-link these WIs from the report. Creation happens at the moment the agent calls `POST /api/work-items`; the report is just the auditable record.
268
278
  - Malformed entries (non-object, missing `wi_id`) are logged and skipped.
269
279
 
@@ -1,14 +1,14 @@
1
1
  # Constellation bridge
2
2
 
3
- Minions ships a small read-only surface that the [Constellation](https://office.visualstudio.com/DefaultCollection/ISS/_git/constellation) dashboard polls to project Minions engine state (agents, dispatch queue, PR pipeline) into its HUD. This page documents the Minions-side of that contract.
3
+ Minions ships a versioned read-only surface that the [Constellation](https://office.visualstudio.com/DefaultCollection/ISS/_git/constellation) agent polls to mirror a locally-running Minions instance into its cloud HUD. Minions remains the execution and state authority on the DevBox; Constellation stores only the bounded metadata projection documented here.
4
4
 
5
- > **The bridge polling logic itself lives in the Constellation repo** (`packages/agent/src/bridges/`). Minions only owns the on/off flag, the marker-file contract, and the `minions bridge` subcommand for local debugging.
5
+ > **The bridge polling logic itself lives in the Constellation repo** (`packages/agent/src/orchestrator-bridge/`). Minions owns the snapshot contract, the on/off flag, the marker-file contract, and the `minions bridge` subcommand for local debugging.
6
6
 
7
7
  ## Quick start
8
8
 
9
9
  ```bash
10
10
  minions bridge status # Show enabled flag + last-seen Constellation agent
11
- minions bridge health # Probe http://127.0.0.1:7331/api/status and print the projection
11
+ minions bridge health # Probe /api/constellation/v1/snapshot and print its metadata summary
12
12
  minions bridge enable # Set engine.constellationBridge.enabled = true
13
13
  minions bridge disable # Set engine.constellationBridge.enabled = false
14
14
  ```
@@ -38,6 +38,52 @@ minions bridge disable
38
38
 
39
39
  Both subcommands write `~/.minions/config.json` atomically via `mutateJsonFileLocked`, so concurrent edits from the engine/dashboard cannot tear the file.
40
40
 
41
+ ## Versioned snapshot contract
42
+
43
+ The Constellation agent polls:
44
+
45
+ ```text
46
+ GET http://127.0.0.1:7331/api/constellation/v1/snapshot
47
+ ```
48
+
49
+ The endpoint is loopback-only because the Minions dashboard binds `127.0.0.1`. It supports `ETag` and `If-None-Match`; unchanged state returns `304` with no body. A `200` response is a **complete replacement snapshot**, so consumers must remove previously mirrored entities that are absent from the latest response.
50
+
51
+ ```json
52
+ {
53
+ "protocol": "minions.constellation.snapshot",
54
+ "protocolVersion": 1,
55
+ "complete": true,
56
+ "generatedAt": "2026-07-13T08:00:00.000Z",
57
+ "minionsVersion": "0.1.2203",
58
+ "engine": { "state": "running", "pid": 1234 },
59
+ "entities": {
60
+ "work_item": [],
61
+ "pr": [],
62
+ "plan": [],
63
+ "schedule": [],
64
+ "dispatch": [],
65
+ "agent_status": [],
66
+ "device_summary": []
67
+ }
68
+ }
69
+ ```
70
+
71
+ `protocolVersion` changes only for a breaking schema change. Additive optional fields do not require a version bump.
72
+
73
+ ### Privacy boundary
74
+
75
+ Every entity is rebuilt from an explicit allowlist in `engine/bridge.js`; raw dashboard records are never forwarded. The snapshot excludes task descriptions, prompts, acceptance criteria, completion reports, diffs, comments, human feedback, notes, logs, filesystem paths, environment variables, tokens, and secrets. Strings are length-capped to the matching Constellation schema limits.
76
+
77
+ The entity shapes match `packages/shared/src/orchestrator-messages.ts` in the Constellation repository:
78
+
79
+ - `work_item`: identifiers, title, type/priority/status, project/agent, lifecycle timestamps.
80
+ - `pr`: identifiers, title/status/review status, branch/URL, merge metadata and timestamps.
81
+ - `plan`: identifier, title/status, project/agent and timestamps.
82
+ - `schedule`: identifier, title/type/status, project/agent and run timestamps.
83
+ - `dispatch`: identifier, agent/status/type/project, linked WI/PR and lifecycle timestamps.
84
+ - `agent_status`: agent/status/project, current task identifiers/title and update time.
85
+ - `device_summary`: aggregate counts plus protocol, Minions version and engine health.
86
+
41
87
  ## Marker-file contract
42
88
 
43
89
  The Constellation agent's bridge writes a marker file on every successful poll. Minions reads it with `minions bridge status` so a local operator can verify the bridge is alive and Constellation is talking to it.
@@ -67,21 +113,24 @@ Writers MUST use an atomic-replace pattern (`write to tmp + rename`) so a partia
67
113
 
68
114
  ## `minions bridge health`
69
115
 
70
- `bridge health` performs a synchronous probe of the Minions dashboard's `GET /api/status` endpoint and prints a **curated subset** — the same fields the Constellation bridge would project into its own data model. This is intentionally small: full `/api/status` is large, unstable, and may expose unrelated local state.
116
+ `bridge health` performs a synchronous probe of the versioned snapshot endpoint and prints its aggregate metadata. This verifies both dashboard reachability and protocol compatibility.
71
117
 
72
118
  Sample output:
73
119
 
74
120
  ```text
75
121
  bridge: dashboard reachable on http://127.0.0.1:7331
76
- projection (same fields the Constellation bridge would read):
122
+ snapshot (same metadata the Constellation bridge mirrors):
123
+ protocolVersion: 1
124
+ minionsVersion: 0.1.2203
77
125
  engineState: running
78
126
  enginePid: 1234
79
- minionsVersion: 0.1.1984
80
127
  agentCount: 5
81
128
  activeAgentCount: 2
82
- dispatchPending: 1
83
- dispatchActive: 2
84
- dispatchCompleted: 14
129
+ workItemCount: 18
130
+ pullRequestCount: 4
131
+ planCount: 2
132
+ scheduleCount: 3
133
+ dispatchCount: 14
85
134
  projectCount: 3
86
135
  ```
87
136
 
@@ -89,6 +138,6 @@ If the dashboard is not listening, `bridge health` prints `dashboard not running
89
138
 
90
139
  ## Cross-repo coordination
91
140
 
92
- The Constellation-side PR ([P-wi1-bridge-readonly](https://office.visualstudio.com/DefaultCollection/ISS/_git/constellation)) lands independently of this Minions PR. The Minions side merges first with the default `enabled: false`, then the Constellation side lights up bridge polling. Operators flip the flag to `true` only after both sides are deployed.
141
+ The Minions producer lands before the Constellation consumer. During rollout, Constellation may fall back to the legacy per-endpoint projection only when the versioned endpoint returns `404`; a malformed versioned response must fail closed rather than silently downgrading.
93
142
 
94
143
  The Constellation agent's bridge reads `~/.minions/config.json` directly (no Minions HTTP API call) so config edits propagate without waiting for the engine to restart.
@@ -62,12 +62,12 @@ Key design properties:
62
62
 
63
63
  | Constellation concept | Minions equivalent today |
64
64
  |---|---|
65
- | `metrics_daily` rollups | `metrics.json._daily.<YYYY-MM-DD>` → `{costUsd, inputTokens, outputTokens, cacheRead, tasks}` |
65
+ | `metrics_daily` rollups | SQL metrics `_daily.<YYYY-MM-DD>` → `{costUsd, inputTokens, outputTokens, cacheRead, tasks}` |
66
66
  | Per-entity aggregates | Per-agent counters: `tasksCompleted/Errored`, `prsCreated/Approved/Rejected/Merged`, `reviewsDone`, `totalCostUsd`, `totalInputTokens/OutputTokens/CacheRead`, `totalRuntimeMs`, `model`, `timedTasks` |
67
- | App/engine analytics | `metrics.json._engine`: `command-center`, `agent-dispatch`, `consolidation`, `agent_memory_reconcile` |
67
+ | App/engine analytics | SQL metrics `_engine`: `command-center`, `agent-dispatch`, `consolidation`, `agent_memory_reconcile` |
68
68
  | Emit helpers | `llm.trackEngineUsage()`, `trackReviewMetric()`, `updateMetrics()` (`engine/lifecycle.js`, `engine/llm.js`, `engine/shared.js`) |
69
69
  | Structured error taxonomy | `FAILURE_CLASS` constants (recorded per work-item; not yet aggregated as a metric) |
70
- | Diagnostic stream | `log.json` ring buffer (2500, secret-redacted via `redactSecrets()`) + `engine/dashboard-diagnostics.log` |
70
+ | Diagnostic stream | SQL log tail (secret-redacted via `redactSecrets()`) + `engine/dashboard-diagnostics.log` |
71
71
  | Admin overview query | `getMetrics()` (`engine/queries.js`) surfaced via `/api/status` |
72
72
 
73
73
  ## The gap (the 40% worth building)
@@ -109,9 +109,9 @@ review verdicts, CC turns. Gitignored runtime file (add to the State Files list
109
109
 
110
110
  ### Phase 2 — Rollup + retention (small, ~½ day)
111
111
  Extend the existing daily-rollup path so it also folds event counts + failure-category tallies into
112
- `metrics.json._daily`. Prune `events.jsonl` lines older than `telemetryRetentionDays` **after** the
112
+ SQL metrics `_daily`. Prune `events.jsonl` lines older than `telemetryRetentionDays` **after** the
113
113
  rollup write succeeds (Constellation's invariant). Slots into an existing periodic tick phase.
114
- All `metrics.json` writes stay inside `mutateJsonFileLocked()`.
114
+ All metric writes go through the SQL metrics store.
115
115
 
116
116
  ### Phase 3 — Usage dashboard page (medium, ~1–1.5 days)
117
117
  New sidebar page reading `getMetrics()` + `_daily`: cost/token trend, dispatches-by-type,
@@ -156,6 +156,6 @@ the other HTML entry points; counters from an in-memory registry, no client libr
156
156
  `packages/server/src/api/prism.ts`, `packages/dashboard/src/prism/prism-telemetry.ts`
157
157
  - Constellation taxonomy/privacy: `packages/shared/src/constants.ts`,
158
158
  `packages/shared/src/privacy.ts`
159
- - Minions today: `engine/metrics.json`, `getMetrics()` (`engine/queries.js`),
159
+ - Minions today: `engine/state.db` metrics, `getMetrics()` (`engine/queries.js`),
160
160
  `trackEngineUsage()` (`engine/llm.js`), `FAILURE_CLASS` (`engine/shared.js`),
161
161
  related design doc `design-state-storage.md`.
@@ -1,5 +1,9 @@
1
1
  # `saveCooldowns` merge semantics
2
2
 
3
+ > **Historical design record:** this predates the SQL-only cutover. Cooldowns now
4
+ > persist through `engine/small-state-store.js`; file and lock references below
5
+ > describe the superseded implementation evaluated by the original PRD.
6
+
3
7
  Scoping deliverable for PRD item **P-bfa3a-scope-cooldown-merge** (Wave 3, PR10
4
8
  pre-work) of the 2026-05-27 weekly bug-audit plan. Decides the merge strategy
5
9
  the implementation PR (**P-bfa3b-cooldown-lost-update**) will apply at
@@ -31,7 +31,7 @@
31
31
  | Default | Value |
32
32
  |---|---|
33
33
  | `copilotStreamMode` (default config field) | `'on'` — preserves incremental UX; the adapter parser tolerates either mode. |
34
- | `copilotDisableBuiltinMcps` | `true` — github-mcp-server bypasses Minions' `pull-requests.json` tracking; default OFF. |
34
+ | `copilotDisableBuiltinMcps` | `true` — github-mcp-server bypasses Minions' SQL PR tracking; default OFF. |
35
35
  | `copilotReasoningSummaries` | `false` — opt-in; only some models honor it. |
36
36
 
37
37
  ---
@@ -513,13 +513,13 @@ that default.
513
513
  server is still *registered* (Copilot inventories it for diagnostics), but the
514
514
  agent cannot use its tools. This is the desired behavior — Minions wants the
515
515
  server invisible to the agent so all GitHub mutations route through the
516
- project's `pull-requests.json` tracker rather than spawning ghost PRs.
516
+ project's PR store rather than spawning ghost PRs.
517
517
 
518
518
  > **Tooltip text for the dashboard `copilotDisableBuiltinMcps` toggle**
519
519
  > (per P-7a5c1f8e):
520
520
  >
521
521
  > > When OFF, agents can autonomously create PRs / labels / comments via the
522
- > > github-mcp-server, bypassing Minions' `pull-requests.json` tracking. Leave
522
+ > > github-mcp-server, bypassing Minions' PR tracking. Leave
523
523
  > > this ON unless you have a specific reason to expose the server.
524
524
 
525
525
  ---
@@ -1,6 +1,6 @@
1
1
  # Cross-repo plans
2
2
 
3
- > A cross-repo plan is a single Minions plan whose work items ship into **two or more configured projects**. The plan has no owning project; each `missing_features` item declares its own `project`, the materializer fans the items into per-project `work-items.json` files, and verify completion fans out into one verify work item per touched repo.
3
+ > A cross-repo plan is a single Minions plan whose work items ship into **two or more configured projects**. The plan has no owning project; each `missing_features` item declares its own `project`, the materializer fans items into per-project SQL scopes, and verify completion fans out into one verify work item per touched repo.
4
4
  >
5
5
  > Shipped by `PL-cross-repo-coordination` (`prd/minions-opg-2026-06-10-3.json`, items `P-7a3f1c08` … `P-8b5d3e07`). Default plan shape is unchanged for single-project plans — everything in this doc only activates when the plan markdown declares more than one project.
6
6
 
@@ -77,22 +77,22 @@ The top-level `project` is empty. Each item names its own repo. `depends_on` may
77
77
 
78
78
  ## Per-item project routing
79
79
 
80
- `engine.js#materializePlansAsWorkItems` (~line 5151) groups PRD `missing_features` by `item.project` into a per-project map, then writes each group to `shared.projectWorkItemsPath(project)` i.e. `<MINIONS_DIR>/projects/<name>/work-items.json`. The routing rules:
80
+ `engine.js#materializePlansAsWorkItems` groups PRD `missing_features` by `item.project`, then writes each group transactionally to that project's SQL work-item scope. The routing rules:
81
81
 
82
82
  | `item.project` value | Where it lands |
83
83
  | --- | --- |
84
- | Names a configured project | That project's `projects/<name>/work-items.json` |
84
+ | Names a configured project | That project's SQL scope |
85
85
  | Names an unknown project | Skipped; PRD item stamped with `_invalidProject: <error>` (see `formatUnknownProjectError`) |
86
86
  | Unset, and central WIs disabled | Falls back to the plan-level `defaultProject` (or skipped if neither is resolvable) |
87
- | Unset, and central WIs enabled | Lands in central `work-items.json` (legacy single-project plans) |
87
+ | Unset, and central WIs enabled | Lands in the central SQL scope |
88
88
 
89
- The materializer enforces a single per-project lock per write (`mutateWorkItems(wiPath, …)`), so per-project writes never contend. Cross-project re-opens (e.g. a `missing` PRD item whose previous WI lives in a sibling project) are queued as `deferredReopens` and executed outside the current project's lock (see `engine.js:5297` for the pattern).
89
+ The materializer uses one transaction per project scope (`mutateWorkItems(project, …)`). Cross-project re-opens are queued as `deferredReopens` and executed outside the current project's transaction.
90
90
 
91
- **Reconciliation, PRD-removal sync, and `i.projects[]` rollup** all happen per-project under the same loop. Per-item state is owned by exactly one project's file at a time, so reading `projectWorkItemsPath(project)` always gives a consistent slice for that repo.
91
+ **Reconciliation, PRD-removal sync, and `i.projects[]` rollup** all happen per-project under the same loop. Per-item state is owned by exactly one SQL scope at a time.
92
92
 
93
93
  ## Cycle detection (runs ONCE over the whole plan)
94
94
 
95
- `detectDependencyCycles(plan.missing_features)` (`engine.js:4638`) runs once over the full PRD `missing_features` set **before** the per-project grouping loop, so a cyclic chain that crosses repos (e.g. `P-aaa1` in `minions` → `P-bbb2` in `minions-opg` → `P-aaa1`) is detected and every cyclic item is skipped at materialization time — neither project's `work-items.json` receives the cyclic items.
95
+ `detectDependencyCycles(plan.missing_features)` (`engine.js:4638`) runs once over the full PRD `missing_features` set **before** the per-project grouping loop, so a cyclic chain that crosses repos (e.g. `P-aaa1` in `minions` → `P-bbb2` in `minions-opg` → `P-aaa1`) is detected and every cyclic item is skipped at materialization time — neither project's scope receives the cyclic items.
96
96
 
97
97
  Caveat: `detectDependencyCycles` is a partial DFS — 2-cycles (`A → B → A`) are fully detected, but N-cycles ≥ 3 with distinct nodes only flag the back-edge target and recursing parent. Both legs of a 2-cycle and the canonical back-edge of a longer cycle reliably get skipped. Fixing the partial detector is tracked separately; cross-repo plans inherit the same behaviour as single-project plans.
98
98
 
@@ -126,7 +126,7 @@ for each project P in itemsByProject (excluding the `_central` bucket):
126
126
  git push -u origin <branch> # no-op if origin already has the tip
127
127
  ```
128
128
 
129
- The block is fully idempotent — `rev-parse --verify` guards the local `git branch` create, and `git push -u origin <branch>` is a no-op when origin already has the tip. Per-project failure isolation is explicit: a broken `localPath` or git config in one project logs a warn and continues to the next. The `_central` pseudo-project bucket (legacy central work-items.json) is skipped because it has no project root.
129
+ The block is fully idempotent — `rev-parse --verify` guards the local `git branch` create, and `git push -u origin <branch>` is a no-op when origin already has the tip. Per-project failure isolation is explicit: a broken `localPath` or git config in one project logs a warn and continues to the next. The `_central` pseudo-project bucket is skipped because it has no project root.
130
130
 
131
131
  This is what makes the per-project dispatcher's later `git worktree add ... origin/<branch>` work — each project's origin already has the branch pointing at its own main, and parallel dispatches across repos can each commit + push to the same branch name in their own repo independently.
132
132
 
@@ -134,7 +134,7 @@ This is what makes the per-project dispatcher's later `git worktree add ... orig
134
134
 
135
135
  ## Per-project dispatch (read-side)
136
136
 
137
- `engine.js#discoverFromWorkItems(config, project)` (~line 6671) reads exactly one project's `projectWorkItemsPath(project)` per call, routes pending items via `routing.md`, and returns `newWork[]` entries for the dispatcher. Cross-repo plans don't change this loop the per-project file is the source of truth for what each project's dispatcher sees, and the engine iterates all configured projects per tick.
137
+ `engine.js#discoverFromWorkItems(config, project)` reads exactly one project SQL scope per call, routes pending items via `routing.md`, and returns `newWork[]` entries for the dispatcher. Cross-repo plans don't change this loop; the engine iterates all configured projects per tick.
138
138
 
139
139
  Module-level `_claimedAgents` in `engine/routing.js` persists across in-process calls but is cleared per tick (`routing.resetClaims()`), so two items in different projects routed to the same agent in the same tick contend correctly for the single agent slot.
140
140
 
@@ -145,7 +145,7 @@ Module-level `_claimedAgents` in `engine/routing.js` persists across in-process
145
145
  1. Every PRD `missing_features` item has been materialized into a WI.
146
146
  2. Every materialized WI is in a terminal state (`DONE_STATUSES` or `cancelled`).
147
147
 
148
- When both gates pass, the function groups **active PRs** by project reading each project's `pull-requests.json` via `shared.projectPrPath(p)`, joining to the plan's done items via `engine/pr-links.json` (`getPrLinks()`). The result is a `touchedProjects` array: every project that has at least one active PR linked to one of the plan's done items.
148
+ When both gates pass, the function groups **active PRs** by project from the SQL PR store and joins them to done items through the `pr_links` table. The result is a `touchedProjects` array: every project that has at least one active PR linked to one of the plan's done items.
149
149
 
150
150
  For each touched project the fan-out (lines 245–415):
151
151
 
@@ -155,7 +155,7 @@ For each touched project the fan-out (lines 245–415):
155
155
  - **Shared-branch plans** reuse `feature_branch` directly — the verify agent checks out the branch in-place in the project's localPath.
156
156
  - **Parallel-branch plans** merge each linked PR branch into a per-project worktree at `<localPath>/../worktrees/verify-<projName>-<planSlug>`.
157
157
  - Validates every branch ref through `shared.validateGitRef` before splicing into the bash setup commands (P-f3-verify-prompt — defense in depth against shell injection from a crafted `feature_branch` or PR `branch` field).
158
- - Writes a new verify WI into the touched project's `projectWorkItemsPath(p)` with `itemType: 'verify'`, `type: 'verify'`, `priority: 'high'`, `status: 'pending'`, and `project: <projName>`. The title is project-prefixed (`Verify plan (minions-opg): …`) when `touchedProjects.length > 1` to disambiguate the dashboard listing.
158
+ - Writes a new verify WI into the touched project's SQL scope with `itemType: 'verify'`, `type: 'verify'`, `priority: 'high'`, `status: 'pending'`, and `project: <projName>`. The title is project-prefixed (`Verify plan (minions-opg): …`) when `touchedProjects.length > 1` to disambiguate the dashboard listing.
159
159
 
160
160
  **Fallback:** when no PRs were ever linked (e.g. items completed without PR records, or legacy state), the fan-out collapses to a single verify WI on the **primary project** (the project with the most completed items) so the plan still gets verified.
161
161
 
@@ -240,13 +240,13 @@ Operator approves the plan. Plan-to-prd dispatches against the `minions` project
240
240
 
241
241
  Engine ticks once:
242
242
 
243
- 1. `materializePlansAsWorkItems` groups by project: `{ minions: [P-001], minions-opg: [P-002] }`. Cycle scan runs once over both items, finds nothing. Writes `projects/minions/work-items.json` with P-001 and `projects/minions-opg/work-items.json` with P-002. Branch-strategy is `parallel`, so no shared-branch pre-create runs.
243
+ 1. `materializePlansAsWorkItems` groups by project: `{ minions: [P-001], minions-opg: [P-002] }`. Cycle scan runs once over both items, finds nothing. Writes P-001 and P-002 to their respective SQL scopes. Branch-strategy is `parallel`, so no shared-branch pre-create runs.
244
244
  2. `discoverFromWorkItems(config, minions)` picks up P-001, routes it via `routing.md`, dispatches `implement` agent. Worktree created on `work/P-001` off `minions`' main.
245
245
  3. `discoverFromWorkItems(config, minions-opg)` picks up P-002. Its `depends_on: ["P-001"]` resolves to a same-project dep? **No** — P-001 lives in the `minions` project, so it's a **cross-repo dep**. The dispatcher fetches P-001's branch into the `minions` rootDir, transfer-fetches it into the `minions-opg` worktree under `refs/cross-repo-deps/work/P-001`, computes a short SHA + changed-file list, and appends a `## Cross-repo dependencies` section to the prompt. The agent inspects via `git show` / `git log` and ships matching code in `minions-opg`.
246
- 4. Both items complete with PRs. `engine/pr-links.json` links each PR to its WI.
246
+ 4. Both items complete with PRs. SQL `pr_links` rows link each PR to its WI.
247
247
  5. Next tick `lifecycle.checkPlanCompletion` runs. Gates pass (every item materialized + done). Groups active PRs by project: `{ minions: [PR for P-001], minions-opg: [PR for P-002] }`. Creates two verify WIs:
248
- - `(minions)` verify WI in `projects/minions/work-items.json` with `project: 'minions'`.
249
- - `(minions-opg)` verify WI in `projects/minions-opg/work-items.json` with `project: 'minions-opg'`.
248
+ - `(minions)` verify WI in the `minions` scope with `project: 'minions'`.
249
+ - `(minions-opg)` verify WI in the `minions-opg` scope with `project: 'minions-opg'`.
250
250
  6. Both verify agents dispatch in parallel. Each builds + tests its own repo. Each writes `prd/guides/verify-cross-repo-launch.md` in its own repo's worktree.
251
251
  7. Dashboard plan card shows `minions 2/2 · minions-opg 2/2` rollup pills and two labelled verify badges. Operator archives the plan via `POST /api/plans/archive` when both verifies complete.
252
252
 
@@ -267,7 +267,7 @@ When you need to change the cross-repo path, the load-bearing seams are:
267
267
  | Shared-branch pre-create per project | `engine.js:5318–5356` | Idempotent `rev-parse` → `branch` → `push -u`; per-project failure isolation. |
268
268
  | Cross-repo dep partitioning + advisory fetch | `engine.js:2495–2682` (`spawnAgent` dep loop) | `isCrossRepo` split; transfer-fetch via filesystem-path remote into `refs/cross-repo-deps/<branch>`. |
269
269
  | Cross-repo dep prompt section | `engine.js:662–710` (`buildCrossRepoDepsSection`) | Renders one section per dep with repo, branch, SHA, files, local ref. |
270
- | Per-project verify fan-out | `engine/lifecycle.js:217–274` (`checkPlanCompletion`) | Group active PRs by project via `engine/pr-links.json`; one verify WI per touched project. |
270
+ | Per-project verify fan-out | `engine/lifecycle.js` (`checkPlanCompletion`) | Group active PRs by project via SQL links; one verify WI per touched project. |
271
271
  | Verify WI creation | `engine/lifecycle.js:387–411` | `itemType:'verify'`, `project: projName`, project-prefixed title when `touchedProjects.length > 1`. |
272
272
  | Per-project rollup pills | `dashboard.js#handlePlansList` (P-66b1faec) | Gate `_perProjectProgress.length >= 2`. |
273
273
  | Per-project verify badges | `dashboard/js/render-plans.js` (P-d7e592b1) | Gate `verifyWis.length >= 2`. |
@@ -277,7 +277,7 @@ When you need to change the cross-repo path, the load-bearing seams are:
277
277
 
278
278
  `test/integration/cross-repo-plan-e2e.test.js` (P-8b5d3e07) drives the four load-bearing seams against the real engine code in an isolated `MINIONS_TEST_DIR` (no live state touched, no agents spawned):
279
279
 
280
- 1. PRD with per-item `project` fans into per-project `work-items.json` files (alpha vs beta).
280
+ 1. PRD with per-item `project` fans into per-project SQL scopes (alpha vs beta).
281
281
  2. `discoverFromWorkItems(config, project)` returns only the queried project's items.
282
282
  3. `checkPlanCompletion` creates one verify WI per touched project when each project has at least one active PR linked to a done WI.
283
283
  4. A 3-feature cyclic chain that crosses project boundaries (alpha → beta → alpha) is detected and none of the cyclic items are written.
@@ -43,10 +43,10 @@
43
43
  "LEGACY_DONE_ALIASES",
44
44
  "LEGACY_NEEDS_REVIEW_STATUS"
45
45
  ],
46
- "reason": "Read-side tolerance: cleanup sweep auto-migrates four obsolete work-item / PRD status strings ('in-pr', 'implemented', 'complete', 'needs-human-review') to the canonical 'done' / 'failed' values. The aliases are no longer written anywhere in the engine; the constants exist only to repair stale on-disk values from old engine versions.",
46
+ "reason": "Read-side tolerance: cleanup sweep auto-migrates four obsolete work-item / PRD status strings ('in-pr', 'implemented', 'complete', 'needs-human-review') to the canonical 'done' / 'failed' values. The aliases are no longer written anywhere in the engine; the constants exist only to repair stale SQL records imported from old engine versions.",
47
47
  "status": "active",
48
48
  "targetRemovalDate": null,
49
- "notes": "Keep indefinitely until telemetry / a sweep log shows zero migrations performed for 30 consecutive days across all known projects (work-items.json + prd/*.json). At that point the constants and both _migrateLegacyItem branches in engine/cleanup.js (definitions at :1165-1166; usage at :1168-1183 for work items and :1269-1272 for PRD missing_features) can be deleted. Total cost on disk today: 4 strings."
49
+ "notes": "Keep indefinitely until telemetry shows zero migrations for 30 consecutive days across all project scopes and PRDs. At that point the constants and both _migrateLegacyItem branches in engine/cleanup.js can be deleted."
50
50
  },
51
51
  {
52
52
  "id": "config-claude-binary-override",
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Author: Rebecca (Architect) | Date: 2026-04-07 | Status: **Implemented**
4
4
 
5
- > **Implementation status (as of 2026-06):** Migrations 001–018 implement the full SQLite cutover. `engine/state.db` is the sole runtime-state authority; migration 018 validates the database, records a durable cutover marker, and retires known legacy mirrors. Historical migrations retain one-time JSON import support. Normal runtime never reads or writes retired mirrors. `minions state check`, `backup`, and `export` provide operations support; explicit offline recovery import is available through `minions state import-legacy-json <dir> --confirm`. Sections 1–3 below are retained as historical design rationale.
5
+ > **Implementation status:** Migrations 001–024 implement the full SQLite cutover and final compatibility cleanup. `engine/state.db` is the sole runtime-state authority; migration 018 records the cutover, migrations 020–024 absorb the remaining scoped/archive/session state and one-time PRD collision repair. Historical migrations retain one-time JSON import support. Normal runtime never references retired mirrors. `minions state check`, `backup`, and `export` provide operations support; explicit offline recovery import is available through `minions state import-legacy-json <dir> --confirm`. Sections 1–3 below are retained as historical design rationale.
6
6
 
7
7
  ## Executive Summary
8
8