@yemi33/minions 0.1.2447 → 0.1.2449

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 (315) hide show
  1. package/bin/cli-api-client.js +1 -1
  2. package/bin/install-internal-minions.js +1382 -44
  3. package/bin/install-layout.js +150 -0
  4. package/bin/minions.js +460 -167
  5. package/dashboard/docs/typography.md +65 -12
  6. package/dashboard/js/command-center.js +66 -7
  7. package/dashboard/js/detail-panel.js +36 -0
  8. package/dashboard/js/memory-panel.js +59 -12
  9. package/dashboard/js/qa.js +186 -45
  10. package/dashboard/js/refresh.js +148 -12
  11. package/dashboard/js/render-dispatch.js +3 -4
  12. package/dashboard/js/render-inbox.js +2 -2
  13. package/dashboard/js/render-other.js +3 -3
  14. package/dashboard/js/render-pipelines.js +14 -0
  15. package/dashboard/js/render-plans.js +57 -9
  16. package/dashboard/js/render-prd.js +132 -23
  17. package/dashboard/js/render-prs.js +195 -166
  18. package/dashboard/js/render-schedules.js +63 -3
  19. package/dashboard/js/render-utils.js +3 -3
  20. package/dashboard/js/render-watches.js +19 -3
  21. package/dashboard/js/render-work-items.js +238 -30
  22. package/dashboard/js/settings.js +205 -54
  23. package/dashboard/js/utils.js +51 -1
  24. package/dashboard/pages/home.html +1 -1
  25. package/dashboard/pages/qa.html +1 -16
  26. package/dashboard/pages/work.html +40 -0
  27. package/dashboard/shared/cc-limits.js +79 -0
  28. package/dashboard/shared/pr-filters.js +21 -38
  29. package/dashboard/shared/project-git-summary.js +1 -1
  30. package/dashboard/shared/record-filters.js +169 -0
  31. package/dashboard/shared/watches-source.js +1 -1
  32. package/dashboard/shared/welcome-popup.js +1 -1
  33. package/dashboard/shared/wi-filters.js +302 -0
  34. package/dashboard/slim/body.html +1 -0
  35. package/dashboard/slim/js/command-send.js +26 -0
  36. package/dashboard/slim/js/modals-tiles.js +380 -39
  37. package/dashboard/slim/js/status.js +13 -21
  38. package/dashboard/slim/layout.html +1 -0
  39. package/dashboard/slim/panel-bootstrap.js +6 -2
  40. package/dashboard/slim/styles.css +38 -0
  41. package/dashboard/styles.css +159 -55
  42. package/dashboard-build.js +52 -6
  43. package/dashboard.js +956 -423
  44. package/docs/README.md +11 -6
  45. package/docs/api-errors.md +2 -2
  46. package/docs/architecture-review-2026-07-09.md +1 -1
  47. package/docs/architecture.excalidraw +2 -2
  48. package/docs/auto-discovery.md +18 -9
  49. package/docs/branch-derivation.md +4 -4
  50. package/docs/capture-demos.js +39 -2
  51. package/docs/ci-runner-canary.md +123 -0
  52. package/docs/claude-md-propagation.md +2 -2
  53. package/docs/cloud-agent-dispatch.md +204 -0
  54. package/docs/command-center.md +7 -7
  55. package/docs/completion-reports.md +43 -20
  56. package/docs/constants.md +10 -3
  57. package/docs/constellation-bridge.md +134 -6
  58. package/docs/constellation-style-telemetry.md +4 -4
  59. package/docs/contracts/capability-protocol.v1.json +165 -0
  60. package/docs/cooldown-merge-semantics.md +12 -12
  61. package/docs/copilot-cli-schema.md +7 -7
  62. package/docs/cross-repo-plans.md +10 -10
  63. package/docs/dead-code-audit-retractions.md +5 -5
  64. package/docs/default-branch-ci.md +173 -0
  65. package/docs/deprecated.json +31 -31
  66. package/docs/design-inbox-entries-schema.md +3 -3
  67. package/docs/design-language.md +1051 -0
  68. package/docs/design-state-storage.md +11 -11
  69. package/docs/diagnostics-crash-reports.md +9 -9
  70. package/docs/diagnostics-memory.md +5 -5
  71. package/docs/documentation-audit-2026-07-09.md +7 -7
  72. package/docs/engine-restart.md +90 -5
  73. package/docs/harness-mode.md +1 -1
  74. package/docs/internal-install.md +338 -39
  75. package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
  76. package/docs/kb-pr3223-cascade-archiving.md +1 -1
  77. package/docs/kb-pr696-merge-conflict-docs.md +6 -6
  78. package/docs/kb-sweep.md +35 -35
  79. package/docs/keep-processes.md +1 -1
  80. package/docs/live-checkout-mode.md +30 -30
  81. package/docs/managed-spawn.md +18 -14
  82. package/docs/named-agents.md +7 -7
  83. package/docs/plan-lifecycle.md +69 -2
  84. package/docs/pr-author-identity.md +114 -0
  85. package/docs/pr-auto-fix-dispatch.md +19 -4
  86. package/docs/pr-comment-followup.md +6 -6
  87. package/docs/pr-review-fix-loop.md +59 -10
  88. package/docs/process-termination.md +40 -0
  89. package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
  90. package/docs/qa-runbook-lifecycle.md +367 -17
  91. package/docs/qa-runbooks.md +3 -3
  92. package/docs/rfc-completion-json.md +18 -18
  93. package/docs/runtime-adapters.md +26 -21
  94. package/docs/security.md +6 -6
  95. package/docs/self-improvement.md +4 -4
  96. package/docs/shared-lifecycle-module-map.md +473 -472
  97. package/docs/skills.md +52 -3
  98. package/docs/slim-ux/concepts.md +121 -116
  99. package/docs/specs/agent-configurability.md +18 -18
  100. package/docs/specs/agent-rename.md +18 -18
  101. package/docs/team-memory.md +38 -21
  102. package/docs/timeouts-and-liveness.md +118 -10
  103. package/docs/tutorials/01-install-and-connect.md +1 -1
  104. package/docs/watches.md +40 -39
  105. package/docs/workspace-manifests.md +4 -4
  106. package/docs/worktree-lifecycle.md +293 -14
  107. package/engine/README.md +46 -0
  108. package/engine/{ado-comment.js → ado/comment.js} +8 -8
  109. package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
  110. package/engine/{ado.js → ado/index.js} +417 -63
  111. package/engine/{ado-status.js → ado/status.js} +6 -8
  112. package/engine/{ado-token.js → ado/token.js} +1 -1
  113. package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
  114. package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
  115. package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
  116. package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
  117. package/engine/{harness-context.js → agents/harness-context.js} +5 -5
  118. package/engine/{harness.js → agents/harness.js} +3 -3
  119. package/engine/{llm.js → agents/llm.js} +18 -14
  120. package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
  121. package/engine/{playbook.js → agents/playbook.js} +155 -22
  122. package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
  123. package/engine/{preflight.js → agents/preflight.js} +29 -10
  124. package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
  125. package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
  126. package/engine/{steering.js → agents/steering.js} +5 -5
  127. package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
  128. package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
  129. package/engine/{api-validation.js → api/api-validation.js} +1 -1
  130. package/engine/api/bridge.js +787 -0
  131. package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
  132. package/engine/api/companion.js +560 -0
  133. package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
  134. package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
  135. package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
  136. package/engine/api-contracts/agent-content.js +4 -4
  137. package/engine/api-contracts/capability-manifest.js +236 -0
  138. package/engine/api-contracts/capability-protocol.js +333 -0
  139. package/engine/api-contracts/cc-ops.js +1 -1
  140. package/engine/api-contracts/config-runtime.js +5 -0
  141. package/engine/api-contracts/core.js +28 -1
  142. package/engine/api-contracts/index.js +100 -0
  143. package/engine/api-contracts/orchestration.js +18 -5
  144. package/engine/api-contracts/pull-requests.js +37 -6
  145. package/engine/api-contracts/qa-process.js +29 -6
  146. package/engine/api-contracts/work-plan-prd.js +21 -1
  147. package/engine/cloud/contract.js +212 -0
  148. package/engine/cloud/index.js +159 -0
  149. package/engine/{execution-model.js → core/execution-model.js} +1 -1
  150. package/engine/{features.js → core/features.js} +4 -4
  151. package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
  152. package/engine/{queries.js → core/queries.js} +201 -36
  153. package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
  154. package/engine/{shared.js → core/shared.js} +1637 -175
  155. package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
  156. package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
  157. package/engine/db/index.js +11 -2
  158. package/engine/db/migrations/002-dispatches.js +3 -3
  159. package/engine/db/migrations/003-work-items.js +1 -1
  160. package/engine/db/migrations/004-pull-requests.js +1 -1
  161. package/engine/db/migrations/006-metrics.js +1 -1
  162. package/engine/db/migrations/007-watches.js +2 -2
  163. package/engine/db/migrations/008-small-state.js +1 -1
  164. package/engine/db/migrations/009-qa.js +1 -1
  165. package/engine/db/migrations/010-pr-links.js +1 -1
  166. package/engine/db/migrations/011-remaining-state.js +1 -1
  167. package/engine/db/migrations/012-steering-deliveries.js +2 -2
  168. package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
  169. package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
  170. package/engine/db/migrations/015-plans-prds.js +0 -0
  171. package/engine/db/migrations/018-sql-only-cutover.js +2 -2
  172. package/engine/db/migrations/021-archived-work-items.js +1 -1
  173. package/engine/db/migrations/022-global-cc-session.js +1 -1
  174. package/engine/db/migrations/023-engine-state.js +1 -1
  175. package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
  176. package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
  177. package/engine/db/migrations/029-repair-reused-versions.js +20 -0
  178. package/engine/db/migrations/031-pr-author-identity.js +137 -0
  179. package/engine/{consolidation.js → memory/consolidation.js} +6 -6
  180. package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
  181. package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
  182. package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
  183. package/engine/{memory-store.js → memory/memory-store.js} +3 -3
  184. package/engine/{promotion.js → memory/promotion.js} +3 -3
  185. package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
  186. package/engine/{review-learning.js → memory/review-learning.js} +10 -5
  187. package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
  188. package/engine/{logs-store.js → observability/logs-store.js} +5 -5
  189. package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
  190. package/engine/{check-status.js → operations/check-status.js} +3 -3
  191. package/engine/{cli.js → operations/cli.js} +271 -113
  192. package/engine/{distribution.js → operations/distribution.js} +5 -6
  193. package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
  194. package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
  195. package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
  196. package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
  197. package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
  198. package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
  199. package/engine/{meeting.js → orchestration/meeting.js} +6 -16
  200. package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
  201. package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
  202. package/engine/{routing.js → orchestration/routing.js} +3 -3
  203. package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
  204. package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
  205. package/engine/{timeout.js → orchestration/timeout.js} +158 -109
  206. package/engine/{db-events.js → persistence/db-events.js} +2 -2
  207. package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
  208. package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
  209. package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
  210. package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
  211. package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
  212. package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
  213. package/engine/persistence/state-operations.js +350 -0
  214. package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
  215. package/engine/{issues.js → planning/issues.js} +2 -2
  216. package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
  217. package/engine/planning/prd-result-sidecar.js +190 -0
  218. package/engine/{prd-store.js → planning/prd-store.js} +17 -17
  219. package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
  220. package/engine/{projects.js → planning/projects.js} +10 -10
  221. package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
  222. package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
  223. package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
  224. package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
  225. package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
  226. package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
  227. package/engine/{process-utils.js → processes/process-utils.js} +599 -55
  228. package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
  229. package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
  230. package/engine/{comment-format.js → providers/comment-format.js} +5 -5
  231. package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
  232. package/engine/{gh-token.js → providers/gh-token.js} +4 -4
  233. package/engine/{github.js → providers/github.js} +131 -54
  234. package/engine/{pr-action.js → providers/pr-action.js} +13 -12
  235. package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
  236. package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
  237. package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
  238. package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
  239. package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
  240. package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
  241. package/engine/{pr-track.js → providers/pr-track.js} +11 -13
  242. package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
  243. package/engine/qa/auto-prd-qa.js +313 -0
  244. package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
  245. package/engine/qa/prd-session.js +240 -0
  246. package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
  247. package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
  248. package/engine/{qa-runs.js → qa/runs.js} +286 -15
  249. package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
  250. package/engine/qa/visual-journey.js +654 -0
  251. package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
  252. package/engine/qa-runners/maestro.js +3 -3
  253. package/engine/qa-runners/playwright.js +2 -2
  254. package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
  255. package/engine/recovery/stop-stack.js +607 -0
  256. package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
  257. package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
  258. package/engine/runtimes/claude.js +14 -12
  259. package/engine/runtimes/codex.js +8 -6
  260. package/engine/runtimes/copilot.js +17 -16
  261. package/engine/{watch-actions.js → watches/actions.js} +13 -13
  262. package/engine/{watches.js → watches/index.js} +43 -32
  263. package/engine/{watches-store.js → watches/store.js} +4 -4
  264. package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
  265. package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
  266. package/engine/worktrees/inventory.js +671 -0
  267. package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
  268. package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
  269. package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
  270. package/engine/worktrees/quarantine-refs.js +173 -0
  271. package/engine.js +1137 -208
  272. package/minions.js +147 -77
  273. package/package.json +10 -6
  274. package/playbooks/_pr-description-audit.md +110 -78
  275. package/playbooks/build-fix-complex.md +2 -0
  276. package/playbooks/fix.md +16 -12
  277. package/playbooks/implement-shared.md +2 -0
  278. package/playbooks/implement.md +19 -20
  279. package/playbooks/plan-to-prd.md +18 -3
  280. package/playbooks/qa-session-draft.md +136 -1
  281. package/playbooks/qa-session-execute.md +80 -2
  282. package/playbooks/qa-session-setup.md +17 -1
  283. package/playbooks/qa-validate.md +1 -1
  284. package/playbooks/setup.md +2 -0
  285. package/playbooks/shared-rules.md +25 -32
  286. package/playbooks/templates/followup-dispatch.md +4 -3
  287. package/playbooks/verify.md +1 -1
  288. package/prompts/cc-system.md +19 -27
  289. package/watch-plugins/README.md +92 -0
  290. package/watch-plugins/ado-author-prs.js +336 -0
  291. package/watch-plugins/gh-author-prs.js +375 -0
  292. package/watch-plugins/http.js +474 -0
  293. package/watch-plugins/teams-channel.js +869 -0
  294. package/docs/dev-composite-workflow.md +0 -101
  295. package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
  296. package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
  297. package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
  298. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
  299. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
  300. package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
  301. package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
  302. package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
  303. package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
  304. package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
  305. package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
  306. package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
  307. package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
  308. package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
  309. package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
  310. package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
  311. package/docs/visual-evidence-ci.md +0 -103
  312. package/engine/bridge.js +0 -379
  313. package/engine/quarantine-refs.js +0 -103
  314. package/engine/state-operations.js +0 -178
  315. /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
@@ -349,8 +349,8 @@ function renderDispatch(dispatch, opts) {
349
349
  const compStart = _completedPage * COMPLETED_PER_PAGE;
350
350
  const pageCompleted = completed.slice(compStart, compStart + COMPLETED_PER_PAGE);
351
351
 
352
- // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: dispatch id, type, agent, task, result, reason)
353
- completedEl.innerHTML = '<table class="pr-table"><thead><tr><th>ID</th><th>Type</th><th>Agent</th><th>Task</th><th>Result</th><th>Completed</th></tr></thead><tbody>' +
352
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: type, agent, task, result, reason)
353
+ completedEl.innerHTML = '<table class="pr-table"><thead><tr><th>Type</th><th>Agent</th><th>Task</th><th>Result</th><th>Completed</th></tr></thead><tbody>' +
354
354
  pageCompleted.map(d => {
355
355
  const isError = d.result === 'error';
356
356
  const agentId = (d.agent || '').toLowerCase();
@@ -358,10 +358,9 @@ function renderDispatch(dispatch, opts) {
358
358
  ? ' <button class="error-details-btn" data-agent="' + escHtml(agentId) + '" data-reason="' + escHtml(d.reason || 'No reason recorded') + '" data-task="' + escHtml((d.task || '').slice(0, 100)) + '" onclick="showErrorDetails(this.dataset.agent, this.dataset.reason, this.dataset.task)" title="View error details">details</button>'
359
359
  : '';
360
360
  return '<tr>' +
361
- '<td style="font-family:Consolas;font-size:var(--text-sm)" title="' + escHtml(d.id || '') + '">' + escHtml(d.id || '') + '</td>' +
362
361
  '<td><span class="dispatch-type ' + (d.type || '') + '">' + escHtml(d.type || '') + '</span></td>' +
363
362
  '<td>' + escHtml(d.agentName || d.agent || '') + '</td>' +
364
- '<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escHtml((d.task || '').slice(0, 60)) + '</td>' +
363
+ '<td style="width:100%;max-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escHtml((d.task || '').slice(0, 60)) + '</td>' +
365
364
  '<td style="color:' + (d.result === 'success' ? 'var(--green)' : 'var(--red)') + '">' + escHtml(d.result || '') + errorBtn + '</td>' +
366
365
  '<td class="pr-date">' + shortTime(d.completed_at) + '</td>' +
367
366
  '</tr>';
@@ -3,7 +3,7 @@
3
3
  const INBOX_PER_PAGE = 15;
4
4
  let _inboxPage = 0;
5
5
 
6
- // Quick relative-time formatter — mirrors engine/queries.js timeSince so the
6
+ // Quick relative-time formatter — mirrors engine/core/queries.js timeSince so the
7
7
  // inbox-item "age" chip reads the same as it did via /api/status.
8
8
  function _timeSinceMs(mtimeMs) {
9
9
  if (!mtimeMs) return '';
@@ -78,7 +78,7 @@ async function fetchNotesFromDisk() {
78
78
  // Promote a human-readable title to the inbox card heading. Prefers an
79
79
  // explicit `title` field from the API, then a `title:` YAML frontmatter key,
80
80
  // then the first markdown heading, then the first non-empty body line. Mirrors
81
- // the engine's own title derivation (engine/consolidation.js — first `# ` ATX
81
+ // the engine's own title derivation (engine/memory/consolidation.js — first `# ` ATX
82
82
  // heading). Returns '' when nothing readable is extractable so the caller can
83
83
  // fall back to the raw filename.
84
84
  function _extractInboxTitle(item) {
@@ -24,7 +24,7 @@ function renderProjects(projects) {
24
24
  }
25
25
 
26
26
  // Renders the per-project current-branch indicator. Driven by the gitState
27
- // classifier emitted by getProjectGitStatus() in engine/queries.js.
27
+ // classifier emitted by getProjectGitStatus() in engine/core/queries.js.
28
28
  // Adds a title attribute carrying the full branch name so users can still
29
29
  // read it on hover after the CSS ellipsis-truncate (W-mpayac6d000b7d33).
30
30
  //
@@ -78,7 +78,7 @@ function _renderProjectBranch(p) {
78
78
  const detachedSuffix = p.gitDetached ? ' <span class="muted">(detached)</span>' : '';
79
79
  // opg-microsoft/minions#664 — the counts are always vs origin/<comparator>
80
80
  // (configured mainBranch, falling back to remoteDefaultBranch; see
81
- // engine/queries.js _probeProjectGitStatus), never vs the checked-out
81
+ // engine/core/queries.js _probeProjectGitStatus), never vs the checked-out
82
82
  // branch's own upstream. When branchDiffersFromMain the bare "↑N ↓M" chip
83
83
  // reads as ambiguous (looks like it could be vs the current branch's
84
84
  // tracking ref), so append the comparator name to the VISIBLE text too —
@@ -681,7 +681,7 @@ function renderLlmPerf(metrics) {
681
681
  // Excludes:
682
682
  // - 'agent-dispatch' (already counted in per-agent metrics[agentId] totals;
683
683
  // summing both would double-count agent spend in the tile)
684
- // - 'test-*' / '_test*' (matching engine/llm.js trackEngineUsage filter so
684
+ // - 'test-*' / '_test*' (matching engine/agents/llm.js trackEngineUsage filter so
685
685
  // stale test categories don't inflate engineCalls)
686
686
  function _aggregateEngineUsageForTokenTile(engine) {
687
687
  let cost = 0, input = 0, output = 0, cache = 0, calls = 0;
@@ -881,3 +881,17 @@ async function _submitEditPipeline() {
881
881
  }
882
882
 
883
883
  window.MinionsPipelines = { renderPipelines, openPipelineDetail, openCreatePipelineModal, openEditPipelineModal };
884
+
885
+ // ── Slim "+ Add" deep-link (W-ms258yw0000ffd8a) ────────────────────────
886
+ // The Slim Automation tile's "+ Add" picker embeds this classic screen at
887
+ // /pipelines?embed=1#create and relies on this hook to auto-open the Create
888
+ // Pipeline dialog — the classic create flow is reused, not forked (CLAUDE.md
889
+ // slim-vs-classic). Fires only on the /pipelines path with a #create hash; the
890
+ // standalone classic screen and every other embed is otherwise untouched.
891
+ // No isReady predicate: unlike the schedule/watch forms this one has no
892
+ // cmdProjects/cmdAgents-backed selects, so it has nothing to wait for.
893
+ // Helper lives in utils.js#bootCreateDeepLink.
894
+ typeof bootCreateDeepLink === 'function' && bootCreateDeepLink({
895
+ route: '/pipelines',
896
+ resolve: function() { return typeof openCreatePipelineModal === 'function' ? openCreatePipelineModal : null; },
897
+ });
@@ -285,7 +285,11 @@ function renderPlans(plans) {
285
285
  const archivedPlans = plans.filter(p => p.archived && p.format !== 'prd');
286
286
  countEl.textContent = activePlans.length + (archivedPlans.length ? ' + ' + archivedPlans.length + ' archived' : '');
287
287
 
288
- function renderPlanCard(p) {
288
+ // Derive the plan's PRD linkage, archive flag, and canonical lifecycle
289
+ // status. Extracted so the Active/Completed grouping and the card renderer
290
+ // share one source of truth for "completed" (the derived lifecycle status),
291
+ // rather than matching on display text.
292
+ function computePlanStatusContext(p) {
289
293
  const prdFile = planToPrdFile[p.file] || (p.file.endsWith('.json') ? p.file : '');
290
294
  const isArchived = p.archived;
291
295
 
@@ -306,6 +310,11 @@ function renderPlans(plans) {
306
310
 
307
311
  // Single source of truth: derive status from work items
308
312
  const effectiveStatus = isArchived ? 'completed' : derivePlanStatus(prdFile, p.file, prdJsonStatus, allWi);
313
+ return { prdFile, isArchived, effectiveStatus };
314
+ }
315
+
316
+ function renderPlanCard(p, ctx) {
317
+ const { prdFile, isArchived, effectiveStatus } = ctx || computePlanStatusContext(p);
309
318
 
310
319
  const statusLabelsMap = {
311
320
  'completed': 'Completed', 'dispatched': 'In Progress', 'converting': 'Converting to PRD',
@@ -370,6 +379,19 @@ function renderPlans(plans) {
370
379
  const deleteBtn = !isArchived ? '<button class="pr-pager-btn" style="font-size:var(--text-sm);padding:2px 8px;color:var(--red)" ' +
371
380
  'onclick="event.stopPropagation();planDelete(\'' + escapeHtml(p.file) + '\')">Delete</button>' : '';
372
381
 
382
+ // Verify status badges stay inline in the meta line as informational
383
+ // status; the interactive action buttons move to a dedicated row below.
384
+ const verifyBadgesHtml = verifyWis.length >= 2
385
+ ? verifyWis.map(v => _renderVerifyBadge(v, { projectLabel: v.project || '' })).join(' ')
386
+ : (hasVerifyWi ? _renderVerifyBadge(verifyWis[0]) : '');
387
+ const actionButtons = executeBtn + pauseBtn + resumeBtn + verifyBtn + archiveBtn + deleteBtn;
388
+ // Dedicated horizontal action row rendered immediately under the status
389
+ // (meta) line. Skipped entirely when a card has no buttons so empty rows
390
+ // never render.
391
+ const actionRow = actionButtons
392
+ ? '<div class="plan-card-actions plan-card-action-row" onclick="event.stopPropagation()">' + actionButtons + '</div>'
393
+ : '';
394
+
373
395
  const versionBadge = p.version ? ' <span style="font-size:var(--text-sm);font-weight:700;padding:1px 5px;border-radius:3px;background:rgba(56,139,253,0.15);color:var(--blue);vertical-align:middle">v' + p.version + '</span>' : '';
374
396
  const statusColors = { 'completed': 'var(--green)', 'dispatched': 'var(--blue)', 'converting': 'var(--yellow)', 'paused': 'var(--muted)', 'awaiting-approval': 'var(--yellow)', 'approved': 'var(--purple,#a855f7)', 'rejected': 'var(--red)', 'has-failures': 'var(--red)', 'revision-requested': 'var(--purple,#a855f7)', 'active': 'var(--muted)', 'draft': 'var(--muted)' };
375
397
  const cardClass = effectiveStatus === 'dispatched' || effectiveStatus === 'converting' ? 'working' : effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused' ? 'awaiting' : effectiveStatus;
@@ -414,25 +436,51 @@ function renderPlans(plans) {
414
436
  (p.updatedAt ? '<span title="Last updated: ' + p.updatedAt + '">Updated ' + timeAgo(p.updatedAt) + '</span>' : '') +
415
437
  (p.completedAt ? '<span>' + p.completedAt.slice(0, 10) + '</span>' : '') +
416
438
  (p.generatedBy ? '<span>by ' + escapeHtml(p.generatedBy) + '</span>' : '') +
417
- executeBtn + pauseBtn + resumeBtn + verifyBtn + (verifyWis.length >= 2 ? verifyWis.map(v => _renderVerifyBadge(v, { projectLabel: v.project || '' })).join(' ') : (hasVerifyWi ? _renderVerifyBadge(verifyWis[0]) : '')) + archiveReadyBadge + archiveBtn + deleteBtn +
439
+ verifyBadgesHtml + archiveReadyBadge +
418
440
  '</div>' +
441
+ actionRow +
419
442
  '</div>' +
420
443
  '</div>' +
421
444
  actions +
422
445
  '</div>';
423
446
  }
424
447
 
425
- const totalPlanPages = Math.ceil(activePlans.length / PLANS_PER_PAGE);
448
+ // Group displayed plans into Active vs Completed using the canonical derived
449
+ // lifecycle status (computePlanStatusContext → derivePlanStatus), preserving
450
+ // the existing order within each group. The combined ordering is
451
+ // [active…, completed…]; pagination runs over that combined list so a single
452
+ // pager still bounds the page, and section headers are emitted where the
453
+ // group changes within the page.
454
+ const displayEntries = activePlans.map(p => {
455
+ const ctx = computePlanStatusContext(p);
456
+ return { plan: p, ctx, completed: ctx.effectiveStatus === 'completed' };
457
+ });
458
+ const orderedEntries = displayEntries.filter(e => !e.completed)
459
+ .concat(displayEntries.filter(e => e.completed));
460
+
461
+ const planSectionHeader = (group) =>
462
+ '<div class="plan-section-header">' + (group === 'completed' ? 'Completed Plans' : 'Active') + '</div>';
463
+
464
+ const totalPlanPages = Math.ceil(orderedEntries.length / PLANS_PER_PAGE);
426
465
  if (_plansPage >= totalPlanPages) _plansPage = totalPlanPages - 1;
427
466
  if (_plansPage < 0) _plansPage = 0;
428
467
  const plansStart = _plansPage * PLANS_PER_PAGE;
429
- const pagePlans = activePlans.slice(plansStart, plansStart + PLANS_PER_PAGE);
430
-
431
- let html = pagePlans.map(renderPlanCard).join('');
468
+ const pageEntries = orderedEntries.slice(plansStart, plansStart + PLANS_PER_PAGE);
469
+
470
+ let html = '';
471
+ let lastGroup = null;
472
+ for (const entry of pageEntries) {
473
+ const group = entry.completed ? 'completed' : 'active';
474
+ if (group !== lastGroup) {
475
+ html += planSectionHeader(group);
476
+ lastGroup = group;
477
+ }
478
+ html += renderPlanCard(entry.plan, entry.ctx);
479
+ }
432
480
 
433
- if (activePlans.length > PLANS_PER_PAGE) {
481
+ if (orderedEntries.length > PLANS_PER_PAGE) {
434
482
  html += '<div class="pr-pager">' +
435
- '<span class="pr-page-info">' + (plansStart + 1) + '-' + Math.min(plansStart + PLANS_PER_PAGE, activePlans.length) + ' of ' + activePlans.length + '</span>' +
483
+ '<span class="pr-page-info">' + (plansStart + 1) + '-' + Math.min(plansStart + PLANS_PER_PAGE, orderedEntries.length) + ' of ' + orderedEntries.length + '</span>' +
436
484
  '<div class="pr-pager-btns">' +
437
485
  '<button class="pr-pager-btn ' + (_plansPage === 0 ? 'disabled' : '') + '" onclick="_plansPrev()">Prev</button>' +
438
486
  '<button class="pr-pager-btn ' + (_plansPage >= totalPlanPages - 1 ? 'disabled' : '') + '" onclick="_plansNext()">Next</button>' +
@@ -687,7 +735,7 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
687
735
  modalActions += '<span style="' + bs + ';color:var(--blue)">In Progress</span> ';
688
736
  }
689
737
  // Verify / Verified badge — cross-repo plans fan out one verify WI per
690
- // project (engine/lifecycle.js); collect them all and render one badge
738
+ // project (engine/orchestration/lifecycle.js); collect them all and render one badge
691
739
  // per project so operators can see every repo's verify status.
692
740
  const modalVerifyWis = (window._lastWorkItems || []).filter(w => w.itemType === 'verify' && w.sourcePlan === (prdFile || normalizedFile));
693
741
  if (effectiveStatus === 'completed' && prdFile && !isArchived && modalVerifyWis.length === 0) {
@@ -63,6 +63,47 @@ async function _savePrdDesc(source, itemId) {
63
63
  if (saveBtn) { saveBtn.disabled = false; saveBtn.textContent = 'Save'; }
64
64
  }
65
65
 
66
+ // ── PRD item execution-state predicates — single source of truth ────────────
67
+ // engine/core/queries.js#getPrdInfo projects each PRD item's `status` from its
68
+ // materialized work item and maps `pending` -> `missing`, `dispatched` ->
69
+ // `in-progress`, defaulting a blank status to `missing`. So `missing` means the
70
+ // item has NOT STARTED — either no work item was ever materialized, or one
71
+ // exists and is still queued. It is materializability, never execution.
72
+ //
73
+ // Folding `missing` into "active" used to happen independently in the header,
74
+ // the group summary and the item detail modal, so an APPROVED PRD whose items
75
+ // were all `missing` (with zero pending/dispatched work items) rendered as
76
+ // `dispatched` / "In Progress". Every derivation below routes through these
77
+ // predicates so the three paths cannot drift apart again.
78
+ const PRD_ACTIVE_ITEM_STATUSES = ['in-progress'];
79
+ const PRD_NOT_STARTED_ITEM_STATUSES = ['missing'];
80
+ const PRD_ACTIVE_WORK_ITEM_STATUSES = ['pending', 'dispatched'];
81
+
82
+ // Canonical "this PRD item is executing right now".
83
+ function prdItemIsActive(item) {
84
+ return !!item && PRD_ACTIVE_ITEM_STATUSES.indexOf(item.status) !== -1;
85
+ }
86
+
87
+ // Canonical "this PRD item has not started". A blank status counts: the server
88
+ // defaults it to `missing`, so an unset value carries the same meaning.
89
+ function prdItemIsNotStarted(item) {
90
+ if (!item) return false;
91
+ return !item.status || PRD_NOT_STARTED_ITEM_STATUSES.indexOf(item.status) !== -1;
92
+ }
93
+
94
+ // Canonical "this materialized work item is live execution state".
95
+ function prdWorkItemIsActive(wi) {
96
+ return !!wi && PRD_ACTIVE_WORK_ITEM_STATUSES.indexOf(wi.status) !== -1;
97
+ }
98
+
99
+ // A PRD is "In Progress" only from canonical active state: a materialized work
100
+ // item in pending/dispatched, or an `in-progress` item projection (which also
101
+ // covers items whose work-item id differs from the PRD feature id and are
102
+ // therefore missed by the feature-id join the callers do).
103
+ function prdHasActiveExecution(items, workItems) {
104
+ return (workItems || []).some(prdWorkItemIsActive) || (items || []).some(prdItemIsActive);
105
+ }
106
+
66
107
  function renderPrd(prd, prog) {
67
108
  const section = document.getElementById('prd-content');
68
109
  const badge = document.getElementById('prd-badge');
@@ -94,9 +135,9 @@ function renderPrd(prd, prog) {
94
135
  // Single PRD — show status + actions in header
95
136
  const implementItems = allWi.filter(w => prdItems.some(pi => pi.id === w.id));
96
137
  const allDone = implementItems.length > 0 && implementItems.every(w => w.status === 'done');
97
- // Unmaterialized PRD items (status === 'missing') count as active they still need work.
98
- const hasMissing = prdItems.some(i => i.status === 'missing');
99
- const hasActive = hasMissing || implementItems.some(w => w.status === 'pending' || w.status === 'dispatched');
138
+ // Unmaterialized PRD items (status === 'missing') are NOT started, so they
139
+ // never imply execution on their own — see prdHasActiveExecution above.
140
+ const hasActive = prdHasActiveExecution(prdItems, implementItems);
100
141
  const prdFile = existing[0]?.file || '';
101
142
  const prdStatus = existing[0]?.status || '';
102
143
  const effectiveStatus = allDone && !hasActive ? 'completed' : hasActive ? 'dispatched' : prdStatus || 'active';
@@ -128,9 +169,9 @@ function renderPrd(prd, prog) {
128
169
  const items = prdItems.filter(i => i.source === p.file);
129
170
  const wiForPrd = allWi.filter(w => items.some(pi => pi.id === w.id));
130
171
  const allDone = wiForPrd.length > 0 && wiForPrd.every(w => w.status === 'done');
131
- // Unmaterialized PRD items (status === 'missing') count as active they still need work.
132
- const hasMissing = items.some(i => i.status === 'missing');
133
- const hasActive = hasMissing || wiForPrd.some(w => w.status === 'pending' || w.status === 'dispatched');
172
+ // Unmaterialized PRD items (status === 'missing') are NOT started, so they
173
+ // never imply execution on their own — see prdHasActiveExecution above.
174
+ const hasActive = prdHasActiveExecution(items, wiForPrd);
134
175
  const s = allDone && !hasActive ? 'completed' : hasActive ? 'dispatched' : p.status || 'active';
135
176
  counts[s] = (counts[s] || 0) + 1;
136
177
  }
@@ -195,11 +236,11 @@ function renderPrdProgress(prog) {
195
236
  const total = items.length;
196
237
  if (total === 0) return '';
197
238
  const done = items.filter(i => i.status === 'done').length;
198
- const inProgress = items.filter(i => i.status === 'in-progress').length;
239
+ const inProgress = items.filter(prdItemIsActive).length;
199
240
  const failed = items.filter(i => i.status === 'failed').length;
200
241
  const paused = items.filter(i => i.status === 'paused').length;
201
242
  const updated = items.filter(i => i.status === 'updated').length;
202
- const missing = items.filter(i => i.status === 'missing' || !i.status).length;
243
+ const missing = items.filter(prdItemIsNotStarted).length;
203
244
  const pct = (n) => total > 0 ? ((n / total) * 100).toFixed(1) : 0;
204
245
 
205
246
  const stats = '<div class="prd-stats" style="margin:0">' +
@@ -363,10 +404,17 @@ function renderPrdProgress(prog) {
363
404
  };
364
405
 
365
406
  const timings = prog.planTimings || {};
407
+ // W-msa0mrus00mh466f — server-derived PRD revision state (`none` |
408
+ // `in-progress` | `failed`). Derivation lives in
409
+ // engine/core/shared.js#derivePrdRevisionState; this renderer only paints it.
410
+ const revisions = prog.planRevisions || {};
366
411
 
367
412
  const renderGroupHeader = (g) => {
368
413
  const done = g.items.filter(i => i.status === 'done').length;
369
- const wip = g.items.filter(i => i.status === 'in-progress' || i.status === 'missing').length;
414
+ // `missing` is not-started, not work-in-progress: count it separately so the
415
+ // summary never advertises a not-yet-materialized item as "active".
416
+ const wip = g.items.filter(prdItemIsActive).length;
417
+ const notStarted = g.items.filter(prdItemIsNotStarted).length;
370
418
  const summary = (g.summary || '').replace(/^Convert plan to PRD:\s*/i, '').slice(0, 80);
371
419
  const isAwaitingApproval = g.planStatus === 'awaiting-approval';
372
420
  const isPaused = g.planStatus === 'paused';
@@ -388,29 +436,61 @@ function renderPrdProgress(prog) {
388
436
  : isPaused
389
437
  ? '<span style="color:var(--muted);font-weight:600;font-size:var(--text-sm);padding:1px 6px;border:1px solid var(--muted);border-radius:3px">PAUSED</span>'
390
438
  : '';
391
- const showStale = (!isBlocked || isPaused) && g.planStale;
439
+ const revision = revisions[g.file] || null;
440
+ const revisionActive = !!revision && revision.state === 'in-progress';
441
+ const revisionFailed = !!revision && revision.state === 'failed';
442
+ // A live revision owns this PRD: never render the generic stale recovery
443
+ // beside it or the user can launch a competing regenerate/resume path.
444
+ const showStale = (!isBlocked || isPaused) && g.planStale && !revisionActive && !revisionFailed;
392
445
  const staleLabel = showStale
393
446
  ? '<span style="color:var(--orange);font-weight:700;font-size:var(--text-sm);padding:1px 6px;border:1px solid var(--orange);border-radius:3px;background:rgba(210,153,34,0.12)" title="Source plan changed after this PRD was generated">STALE</span>'
394
447
  : '';
448
+ const revisionLabel = revisionActive
449
+ ? '<span style="color:var(--blue);font-weight:700;font-size:var(--text-sm);padding:1px 6px;border:1px solid var(--blue);border-radius:3px;background:rgba(56,139,253,0.12)" title="A revision agent is rewriting this PRD">REVISING</span>'
450
+ : revisionFailed
451
+ ? '<span style="color:var(--red);font-weight:700;font-size:var(--text-sm);padding:1px 6px;border:1px solid var(--red);border-radius:3px;background:rgba(248,81,73,0.12)" title="' + escHtml(revision.failReason || 'Revision did not complete') + '">REVISION FAILED</span>'
452
+ : '';
453
+ const revisionWiChip = (revision && revision.workItemId && typeof renderArtifactLink === 'function')
454
+ ? renderArtifactLink({ type: 'wi', id: revision.workItemId, label: revision.workItemId, title: 'Revision work item' })
455
+ : (revision && revision.workItemId ? '<span class="prd-item-id">' + escHtml(revision.workItemId) + '</span>' : '');
456
+ const reviewPlanBtn = '<span onclick="event.stopPropagation();planView(\'' + escHtml(g.sourcePlan || g.file) + '\')" style="color:var(--blue);cursor:pointer;font-size:var(--text-sm);padding:2px 8px;background:rgba(56,139,253,0.1);border:1px solid rgba(56,139,253,0.3);border-radius:4px" title="Review latest plan changes">Review plan</span>';
457
+ const revisionRecovery = revisionActive
458
+ ? '<div style="width:100%;margin-top:4px;padding:6px 8px;border:1px solid rgba(56,139,253,0.35);border-radius:4px;background:rgba(56,139,253,0.08);display:flex;align-items:center;gap:8px;flex-wrap:wrap">' +
459
+ '<span style="color:var(--blue);font-size:var(--text-sm);font-weight:600">&#x1F504; Revision in progress. An agent is rewriting this PRD.</span>' +
460
+ revisionWiChip +
461
+ (revision.requestedBy ? '<span style="color:var(--muted);font-size:var(--text-sm)">requested by ' + escHtml(revision.requestedBy) + '</span>' : '') +
462
+ reviewPlanBtn +
463
+ '</div>'
464
+ : revisionFailed
465
+ ? '<div style="width:100%;margin-top:4px;padding:6px 8px;border:1px solid rgba(248,81,73,0.35);border-radius:4px;background:rgba(248,81,73,0.08);display:flex;align-items:center;gap:8px;flex-wrap:wrap">' +
466
+ '<span style="color:var(--red);font-size:var(--text-sm);font-weight:600">&#x26A0;&#xFE0F; Revision failed: ' + escHtml(revision.failReason || 'the revision did not complete') + '</span>' +
467
+ revisionWiChip +
468
+ (revision.workItemId
469
+ ? '<span onclick="event.stopPropagation();prdRetryRevision(\'' + escHtml(revision.workItemId) + '\')" style="color:var(--green);cursor:pointer;font-size:var(--text-sm);font-weight:700;padding:2px 8px;background:rgba(63,185,80,0.12);border:1px solid rgba(63,185,80,0.35);border-radius:4px" title="Requeue the revision work item">Retry revision</span>'
470
+ : '<span style="color:var(--muted);font-size:var(--text-sm)">Request a new revision from the Plans page.</span>') +
471
+ reviewPlanBtn +
472
+ '</div>'
473
+ : '';
395
474
  const staleRecovery = showStale
396
475
  ? '<div style="width:100%;margin-top:4px;padding:6px 8px;border:1px solid rgba(210,153,34,0.35);border-radius:4px;background:rgba(210,153,34,0.08);display:flex;align-items:center;gap:8px;flex-wrap:wrap">' +
397
476
  '<span style="color:var(--orange);font-size:var(--text-sm);font-weight:600">&#x26A0;&#xFE0F; Source plan was revised. This PRD may be outdated.</span>' +
398
477
  '<span onclick="event.stopPropagation();prdRegenerate(\'' + escHtml(g.file) + '\')" style="color:var(--green);cursor:pointer;font-size:var(--text-sm);font-weight:700;padding:2px 8px;background:rgba(63,185,80,0.12);border:1px solid rgba(63,185,80,0.35);border-radius:4px" title="Compare revised plan against existing PRD and update items">Regenerate PRD</span>' +
399
478
  '<span onclick="event.stopPropagation();prdResumeWithoutRegen(\'' + escHtml(g.file) + '\')" style="color:var(--muted);cursor:pointer;font-size:var(--text-sm);padding:2px 8px;background:var(--surface);border:1px solid var(--border);border-radius:4px" title="Resume without regenerating — use current PRD as-is">Resume as-is</span>' +
400
- '<span onclick="event.stopPropagation();planView(\'' + escHtml(g.sourcePlan || g.file) + '\')" style="color:var(--blue);cursor:pointer;font-size:var(--text-sm);padding:2px 8px;background:rgba(56,139,253,0.1);border:1px solid rgba(56,139,253,0.3);border-radius:4px" title="Review latest plan changes">Review plan</span>' +
479
+ reviewPlanBtn +
401
480
  '</div>'
402
481
  : '';
403
482
  const isCompleted = done > 0 && done === g.items.length;
404
- // Hide regular action buttons when stale banner is showing — stale banner has its own actions
405
- const isStale = showStale;
406
- const pauseResumeBtn = isStale ? '' : isAwaitingApproval
483
+ // Hide regular action buttons when a recovery banner is showing — the banner
484
+ // owns the recovery actions for that state.
485
+ const suppressGroupActions = showStale || revisionActive || revisionFailed;
486
+ const pauseResumeBtn = suppressGroupActions ? '' : isAwaitingApproval
407
487
  ? '<span onclick="event.stopPropagation();planApprove(\'' + escHtml(g.file) + '\',this)" style="color:var(--green);cursor:pointer;font-size:var(--text-sm);padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Approve</span>'
408
488
  : isPaused
409
489
  ? '<span onclick="event.stopPropagation();planApprove(\'' + escHtml(g.file) + '\',this)" style="color:var(--green);cursor:pointer;font-size:var(--text-sm);padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Resume</span>'
410
490
  : isCompleted && !(window._lastWorkItems || []).some(w => w.itemType === 'verify' && w.sourcePlan === g.file)
411
491
  ? '<span onclick="event.stopPropagation();triggerVerify(\'' + escHtml(g.file) + '\',this)" style="color:var(--green);cursor:pointer;font-size:var(--text-sm);padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Verify</span>'
412
492
  : isCompleted ? '' : '<span onclick="event.stopPropagation();planPause(\'' + escHtml(g.file) + '\',this)" style="color:var(--yellow);cursor:pointer;font-size:var(--text-sm);padding:1px 6px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.3);border-radius:3px">Pause</span>';
413
- const archiveBtn = (!isStale && (isCompleted || isPaused)) ? '<span onclick="event.stopPropagation();planArchive(\'' + escHtml(g.file) + '\',this)" style="color:var(--muted);cursor:pointer;font-size:var(--text-sm);padding:1px 6px;background:rgba(139,148,158,0.1);border:1px solid rgba(139,148,158,0.3);border-radius:3px">Archive</span>' : '';
493
+ const archiveBtn = (!suppressGroupActions && (isCompleted || isPaused)) ? '<span onclick="event.stopPropagation();planArchive(\'' + escHtml(g.file) + '\',this)" style="color:var(--muted);cursor:pointer;font-size:var(--text-sm);padding:1px 6px;background:rgba(139,148,158,0.1);border:1px solid rgba(139,148,158,0.3);border-radius:3px">Archive</span>' : '';
414
494
  const deleteBtn = '<span onclick="event.stopPropagation();planDelete(\'' + escHtml(g.file) + '\')" style="color:var(--red);cursor:pointer;font-size:var(--text-sm);padding:1px 6px;background:rgba(248,81,73,0.1);border:1px solid rgba(248,81,73,0.3);border-radius:3px">Delete</span>';
415
495
  const sourcePlanLink = g.sourcePlan
416
496
  ? '<span onclick="event.stopPropagation();planView(\'' + escHtml(g.sourcePlan) + '\')" style="color:var(--blue);cursor:pointer;font-size:var(--text-sm);padding:1px 6px;background:rgba(56,139,253,0.1);border:1px solid rgba(56,139,253,0.3);border-radius:3px" title="View source plan">&#x1F4C4; Plan</span>'
@@ -424,9 +504,10 @@ function renderPrdProgress(prog) {
424
504
  : '<span style="font-size:var(--text-sm);padding:1px 5px;border-radius:3px;background:rgba(56,139,253,0.12);color:var(--blue);font-weight:400" title="Each item gets its own branch (work/P-xxx) and PR">&#x1F500; parallel branches</span>') +
425
505
  pausedLabel +
426
506
  staleLabel +
507
+ revisionLabel +
427
508
  '<span style="font-weight:700;font-size:var(--text-base);color:' + (done === g.items.length && g.items.length > 0 ? 'var(--green)' : 'var(--text)') + '">' + (g.items.length > 0 ? Math.round((done / g.items.length) * 100) : 0) + '%</span>' +
428
509
  '<span style="color:var(--muted);font-weight:400;font-size:var(--text-sm)">' + g.items.length + ' items' +
429
- (done ? ' · ' + done + ' done' : '') + (wip ? ' · ' + wip + ' active' : '') +
510
+ (done ? ' · ' + done + ' done' : '') + (wip ? ' · ' + wip + ' active' : '') + (notStarted ? ' · ' + notStarted + ' to do' : '') +
430
511
  '</span>' +
431
512
  (g.prdUpdatedAt ? '<span style="color:var(--muted);font-weight:400;font-size:var(--text-sm)" title="PRD last updated: ' + g.prdUpdatedAt + '">PRD updated ' + timeAgo(g.prdUpdatedAt) + '</span>' : '') +
432
513
  runtimeLabel +
@@ -443,6 +524,7 @@ function renderPrdProgress(prog) {
443
524
  deleteBtn +
444
525
  '</span>') +
445
526
  staleRecovery +
527
+ revisionRecovery +
446
528
  '</div>';
447
529
  };
448
530
 
@@ -844,9 +926,14 @@ async function prdItemEdit(source, itemId) {
844
926
  let completionHtml = '';
845
927
  const isDone = item.status === 'done';
846
928
  const isFailed = item.status === 'failed';
847
- const isActive = item.status === 'in-progress' || item.status === 'missing';
848
-
849
- if (isDone || isFailed || isActive) {
929
+ const isActive = prdItemIsActive(item);
930
+ // `missing` is the server's projection for "not materialized, or materialized
931
+ // but still queued" it must render as its truthful not-started state, never
932
+ // as an In Progress completion panel.
933
+ const isNotStarted = prdItemIsNotStarted(item);
934
+ const isQueued = isNotStarted && prdWorkItemIsActive(wi);
935
+
936
+ if (isDone || isFailed || isActive || isNotStarted) {
850
937
  const agent = wi?.dispatched_to || completedEntry?.agent || '';
851
938
  const completedAt = wi?.completedAt || completedEntry?.completed_at || '';
852
939
  const summary = completedEntry?.resultSummary || '';
@@ -857,8 +944,8 @@ async function prdItemEdit(source, itemId) {
857
944
  : '<code>' + escHtml(pr.id) + '</code>';
858
945
  }).join(', ');
859
946
 
860
- const statusColor = isDone ? 'var(--green)' : isFailed ? 'var(--red)' : 'var(--blue)';
861
- const statusLabel = isDone ? 'Completed' : isFailed ? 'Failed' : 'In Progress';
947
+ const statusColor = isDone ? 'var(--green)' : isFailed ? 'var(--red)' : isActive ? 'var(--blue)' : 'var(--muted)';
948
+ const statusLabel = isDone ? 'Completed' : isFailed ? 'Failed' : isActive ? 'In Progress' : isQueued ? 'Queued' : 'Not started';
862
949
 
863
950
  completionHtml = '<div style="background:var(--surface);border:1px solid var(--border);border-left:3px solid ' + statusColor + ';border-radius:4px;padding:10px 12px;margin-bottom:12px">' +
864
951
  '<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">' +
@@ -1034,6 +1121,28 @@ function prdResumeWithoutRegen(prdFile) {
1034
1121
  'Plan resumed (PRD unchanged)');
1035
1122
  }
1036
1123
 
1124
+ // W-msa0mrus00mh466f — recovery for a failed revision: requeue the SAME
1125
+ // revision work item rather than enqueuing a competing operation.
1126
+ async function prdRetryRevision(workItemId) {
1127
+ if (!workItemId) return;
1128
+ try {
1129
+ const res = await fetch('/api/work-items/retry', {
1130
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
1131
+ body: JSON.stringify({ id: workItemId })
1132
+ });
1133
+ if (res.ok) {
1134
+ showToast('cmd-toast', 'Revision ' + workItemId + ' requeued', true);
1135
+ if (typeof wakeEngine === 'function') wakeEngine();
1136
+ if (typeof refresh === 'function') refresh();
1137
+ } else {
1138
+ const d = await res.json().catch(() => ({}));
1139
+ showToast('cmd-toast', 'Retry failed: ' + (d.error || 'unknown'), false);
1140
+ }
1141
+ } catch (e) {
1142
+ showToast('cmd-toast', 'Error: ' + e.message, false);
1143
+ }
1144
+ }
1145
+
1037
1146
  function openArchive(i) {
1038
1147
  const a = archivedPrds[i];
1039
1148
  if (!a) return;
@@ -1109,7 +1218,7 @@ function openArchive(i) {
1109
1218
 
1110
1219
  // "QA this PRD" — one-shot bridge from a completed/approved PRD card to a QA
1111
1220
  // Session. POSTs the PRD file to /api/qa/from-prd (P-b3e9d4a1), which builds the
1112
- // spec via engine/qa-from-prd.js then reuses createSession + queueSetup. On
1221
+ // spec via engine/qa/from-prd.js then reuses createSession + queueSetup. On
1113
1222
  // success we navigate to the QA Sessions page so the operator can watch it run;
1114
1223
  // the endpoint returns a clean 400 (never 500) for any unusable PRD, surfaced
1115
1224
  // here as a toast. Mirrors triggerVerify's button-state + error handling.
@@ -1136,4 +1245,4 @@ async function qaThisPrd(file, btn) {
1136
1245
  }
1137
1246
  }
1138
1247
 
1139
- window.MinionsPrd = { renderPrd, renderPrdProgress, openArchivedPrdModal, showArchivedPrdDetail, prdItemEdit, prdItemSave, prdItemRemove, prdItemRequeue, prdItemReopen, prdRegenerate, openArchive, qaThisPrd, _togglePrdDescEdit, _cancelPrdDescEdit, _savePrdDesc };
1248
+ window.MinionsPrd = { renderPrd, renderPrdProgress, openArchivedPrdModal, showArchivedPrdDetail, prdItemEdit, prdItemSave, prdItemRemove, prdItemRequeue, prdItemReopen, prdRegenerate, prdRetryRevision, openArchive, qaThisPrd, _togglePrdDescEdit, _cancelPrdDescEdit, _savePrdDesc };