@yemi33/minions 0.1.2448 → 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 +58 -4
  7. package/dashboard/js/detail-panel.js +36 -0
  8. package/dashboard/js/memory-panel.js +59 -12
  9. package/dashboard/js/qa.js +179 -20
  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 +13 -2
  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
package/minions.js CHANGED
@@ -16,10 +16,10 @@ const fs = require('fs');
16
16
  const path = require('path');
17
17
  const readline = require('readline');
18
18
  const { execSync } = require('child_process');
19
- const shared = require('./engine/shared');
19
+ const shared = require('./engine/core/shared');
20
20
  const { ENGINE_DEFAULTS, DEFAULT_AGENTS } = shared;
21
- const projectDiscovery = require('./engine/project-discovery');
22
- const scheduleBootstrap = require('./engine/schedule-bootstrap');
21
+ const projectDiscovery = require('./engine/planning/project-discovery');
22
+ const scheduleBootstrap = require('./engine/orchestration/schedule-bootstrap');
23
23
 
24
24
  const MINIONS_HOME = process.env.MINIONS_HOME ? path.resolve(process.env.MINIONS_HOME) : __dirname;
25
25
  const CONFIG_PATH = path.join(MINIONS_HOME, 'config.json');
@@ -41,8 +41,34 @@ function cleanupPlaceholderProjects(config) {
41
41
  return removed;
42
42
  }
43
43
 
44
- function saveConfig(config) {
45
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
44
+ const EMPTY_CONFIG = { projects: [], engine: {}, claude: {}, agents: {} };
45
+ function emptyConfig() { return JSON.parse(JSON.stringify(EMPTY_CONFIG)); }
46
+
47
+ /**
48
+ * Persist a config.json change through the shared file lock, applying the
49
+ * mutation to the FRESH on-disk snapshot (W-ms811bme000n475a).
50
+ *
51
+ * The previous `loadConfig()` … whole-config `fs.writeFileSync` pattern wrote a
52
+ * stale config object: anything another writer committed between the read and the
53
+ * write was silently clobbered. `minions init` runs on every `minions update`
54
+ * and rewrites `projects[]` it never intended to touch, so a project removed
55
+ * from the dashboard in that window came straight back. This mirrors the same
56
+ * fix already applied to removeProject (P-bf05-config-write-race).
57
+ *
58
+ * The mutator MUST stay synchronous and fast — it runs while the config lock is
59
+ * held. Do prompts, runtime detection, and filesystem setup outside it. It may
60
+ * also be re-run if the lock is compromised, so keep it idempotent.
61
+ */
62
+ function mutateConfig(mutator) {
63
+ return shared.mutateJsonFileLocked(CONFIG_PATH, (config) => {
64
+ if (!config || typeof config !== 'object' || Array.isArray(config)) config = emptyConfig();
65
+ const next = mutator(config);
66
+ return next === undefined ? config : next;
67
+ }, { defaultValue: emptyConfig() });
68
+ }
69
+
70
+ function _sameLocalPath(entry, resolvedTarget) {
71
+ return !!(entry && entry.localPath) && path.resolve(entry.localPath) === resolvedTarget;
46
72
  }
47
73
 
48
74
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -99,16 +125,13 @@ async function addProject(targetDir) {
99
125
  process.exit(1);
100
126
  }
101
127
 
102
- const config = loadConfig();
103
- if (!config.projects) config.projects = [];
104
-
105
- // Check if already linked
106
- const existing = config.projects.find(p => path.resolve(p.localPath) === target);
128
+ // Read-only probe for the "already linked" prompt. The authoritative
129
+ // read-modify-write happens under the config lock further down.
130
+ const existing = (loadConfig().projects || []).find(p => _sameLocalPath(p, target));
107
131
  if (existing) {
108
132
  console.log(` "${existing.name}" is already linked at ${target}`);
109
133
  const update = await ask('Update its configuration? (y/N)', 'N');
110
134
  if (update.toLowerCase() !== 'y') { rl.close(); return; }
111
- config.projects = config.projects.filter(p => path.resolve(p.localPath) !== target);
112
135
  }
113
136
 
114
137
  console.log('\n Link Project to Minions');
@@ -154,7 +177,7 @@ async function addProject(targetDir) {
154
177
  // missing fields up front rather than letting the CLI silently persist a
155
178
  // broken project.
156
179
  try {
157
- require('./engine/projects').assertProjectLinkOk(projectEntry);
180
+ require('./engine/planning/projects').assertProjectLinkOk(projectEntry);
158
181
  } catch (e) {
159
182
  console.log(`\n Error: ${e.message}`);
160
183
  if (Array.isArray(e.missing) && e.missing.length > 0) {
@@ -164,18 +187,28 @@ async function addProject(targetDir) {
164
187
  }
165
188
 
166
189
  shared.initializeProjectState(projectEntry);
167
- if (existing && String(existing.name || '').toLowerCase() !== String(projectEntry.name || '').toLowerCase()) {
168
- scheduleBootstrap.disableProjectGitFetchSchedules(config, existing.name);
169
- }
170
- config.projects.push(projectEntry);
171
- scheduleBootstrap.applyScheduleBootstrap(config, {
172
- projectNames: [projectEntry.name],
173
- includeHarnessSweep: true,
190
+ // Re-apply against the FRESH on-disk config: the interactive prompts above
191
+ // can be open for minutes, and a dashboard/CLI project removal that landed in
192
+ // that window must not be undone by this write (W-ms811bme000n475a).
193
+ let totalProjects = 0;
194
+ mutateConfig(cfg => {
195
+ if (!Array.isArray(cfg.projects)) cfg.projects = [];
196
+ const prior = cfg.projects.find(p => _sameLocalPath(p, target));
197
+ cfg.projects = cfg.projects.filter(p => !_sameLocalPath(p, target));
198
+ if (prior && String(prior.name || '').toLowerCase() !== String(projectEntry.name || '').toLowerCase()) {
199
+ scheduleBootstrap.disableProjectGitFetchSchedules(cfg, prior.name);
200
+ }
201
+ cfg.projects.push(projectEntry);
202
+ scheduleBootstrap.applyScheduleBootstrap(cfg, {
203
+ projectNames: [projectEntry.name],
204
+ includeHarnessSweep: true,
205
+ });
206
+ totalProjects = cfg.projects.length;
207
+ return cfg;
174
208
  });
175
- saveConfig(config);
176
209
 
177
210
  console.log(`\n Linked "${name}" (${target})`);
178
- console.log(` Total projects: ${config.projects.length}`);
211
+ console.log(` Total projects: ${totalProjects}`);
179
212
  console.log(`\n Start the minions from anywhere:`);
180
213
  console.log(` node ${MINIONS_HOME}/engine.js # Engine`);
181
214
  console.log(` node ${MINIONS_HOME}/dashboard.js # Dashboard`);
@@ -183,7 +216,7 @@ async function addProject(targetDir) {
183
216
  }
184
217
 
185
218
  function removeProject(target, options = {}) {
186
- const { removeProject: doRemove } = require('./engine/projects');
219
+ const { removeProject: doRemove } = require('./engine/planning/projects');
187
220
  // CLI accepts a path; project lookup also matches by name as a convenience
188
221
  const result = doRemove(path.resolve(target), options);
189
222
  if (!result.ok) {
@@ -315,8 +348,10 @@ async function scanAndAdd({ root, depth } = {}) {
315
348
  return;
316
349
  }
317
350
 
318
- const config = loadConfig();
319
- const linkedPaths = new Set((config.projects || []).map(p => path.resolve(p.localPath)));
351
+ // Read-only snapshot used to tag "already linked" in the listing. The
352
+ // authoritative read-modify-write happens under the config lock further down.
353
+ const linkedPaths = new Set(
354
+ (loadConfig().projects || []).filter(p => p && p.localPath).map(p => path.resolve(p.localPath)));
320
355
 
321
356
  // Enrich repos with auto-discovered metadata
322
357
  const enriched = repos.map(repoPath => {
@@ -376,34 +411,57 @@ async function scanAndAdd({ root, depth } = {}) {
376
411
 
377
412
  console.log(`\n Adding ${toAdd.length} project(s)...\n`);
378
413
 
379
- const existingNames = new Set((config.projects || []).map(p => p.name));
414
+ // Re-apply against the FRESH on-disk config — the selection prompt above can
415
+ // be open for minutes (W-ms811bme000n475a). Name/path dedupe therefore runs
416
+ // inside the lock, and initializeProjectState (filesystem work) runs after it.
380
417
  const addedProjectNames = [];
381
- for (const repo of toAdd) {
382
- // Deduplicate names — append -2, -3 etc. if name already taken
383
- let name = repo.name;
384
- if (existingNames.has(name)) {
385
- let i = 2;
386
- while (existingNames.has(name + '-' + i)) i++;
387
- name = name + '-' + i;
418
+ const addedEntries = [];
419
+ let totalProjects = 0;
420
+ mutateConfig(cfg => {
421
+ if (!Array.isArray(cfg.projects)) cfg.projects = [];
422
+ // The mutator can be re-run when the lock is compromised — reset accumulators.
423
+ addedProjectNames.length = 0;
424
+ addedEntries.length = 0;
425
+ const existingNames = new Set(cfg.projects.map(p => p && p.name).filter(Boolean));
426
+ const existingPaths = new Set(
427
+ cfg.projects.filter(p => p && p.localPath).map(p => path.resolve(p.localPath)));
428
+ for (const repo of toAdd) {
429
+ // Another writer may have linked this path since the scan listing.
430
+ if (existingPaths.has(path.resolve(repo.path))) continue;
431
+ // Deduplicate names — append -2, -3 etc. if name already taken
432
+ let name = repo.name;
433
+ if (existingNames.has(name)) {
434
+ let i = 2;
435
+ while (existingNames.has(name + '-' + i)) i++;
436
+ name = name + '-' + i;
437
+ }
438
+ existingNames.add(name);
439
+ existingPaths.add(path.resolve(repo.path));
440
+ const projectEntry = buildProjectEntry({
441
+ name, description: repo.description, localPath: repo.path,
442
+ repoHost: repo.host, repositoryId: repo.repositoryId, org: repo.org, project: repo.project,
443
+ repoName: repo.repoName, mainBranch: repo.mainBranch, prUrlBase: repo.prUrlBase,
444
+ });
445
+ cfg.projects.push(projectEntry);
446
+ addedProjectNames.push(projectEntry.name);
447
+ addedEntries.push(projectEntry);
388
448
  }
389
- existingNames.add(name);
390
- const projectEntry = buildProjectEntry({
391
- name, description: repo.description, localPath: repo.path,
392
- repoHost: repo.host, repositoryId: repo.repositoryId, org: repo.org, project: repo.project,
393
- repoName: repo.repoName, mainBranch: repo.mainBranch, prUrlBase: repo.prUrlBase,
394
- });
395
- shared.initializeProjectState(projectEntry);
396
- config.projects.push(projectEntry);
397
- addedProjectNames.push(projectEntry.name);
398
- console.log(` + ${name} (${repo.path})`);
449
+ if (addedProjectNames.length > 0) {
450
+ scheduleBootstrap.applyScheduleBootstrap(cfg, {
451
+ projectNames: addedProjectNames,
452
+ includeHarnessSweep: true,
453
+ });
454
+ }
455
+ totalProjects = cfg.projects.length;
456
+ return cfg;
457
+ });
458
+
459
+ for (const entry of addedEntries) {
460
+ shared.initializeProjectState(entry);
461
+ console.log(` + ${entry.name} (${entry.localPath})`);
399
462
  }
400
463
 
401
- scheduleBootstrap.applyScheduleBootstrap(config, {
402
- projectNames: addedProjectNames,
403
- includeHarnessSweep: true,
404
- });
405
- saveConfig(config);
406
- console.log(`\n Done. ${config.projects.length} total project(s) linked.`);
464
+ console.log(`\n Done. ${totalProjects} total project(s) linked.`);
407
465
  console.log(` Run "minions list" to verify.\n`);
408
466
  rl.close();
409
467
  }
@@ -413,39 +471,51 @@ async function scanAndAdd({ root, depth } = {}) {
413
471
  const [cmd, ...rest] = process.argv.slice(2);
414
472
 
415
473
  async function initMinions({ skipScan = false, scanRoot, scanDepth } = {}) {
416
- const config = loadConfig();
417
- if (!config.projects) config.projects = [];
418
- const removedPlaceholders = cleanupPlaceholderProjects(config);
419
- const hadConfiguredDefaultCli = config.engine?.defaultCli !== undefined;
420
- // Merge defaults — fills in new fields from upgrades while preserving user customizations
421
- if (!config.engine) config.engine = {};
422
- for (const [k, v] of Object.entries(ENGINE_DEFAULTS)) {
423
- if (k === 'defaultCli') continue;
424
- if (config.engine[k] === undefined) config.engine[k] = v;
425
- }
426
- shared.pruneDefaultClaudeConfig(config);
427
- if (!config.agents || Object.keys(config.agents).length === 0) {
428
- config.agents = { ...DEFAULT_AGENTS };
429
- }
474
+ // Runtime detection shells out (`where` / `which`), so it must run OUTSIDE the
475
+ // config lock — lock callbacks stay synchronous and fast.
476
+ const hadConfiguredDefaultCli = loadConfig().engine?.defaultCli !== undefined;
477
+ const detected = hadConfiguredDefaultCli ? [] : _detectAvailableRuntimes();
478
+
479
+ // W-ms811bme000n475a: merge into the FRESH on-disk config under the lock.
480
+ // `minions init` runs on every `minions update`; the previous stale-snapshot
481
+ // rewrite resurrected projects that were unlinked while it was running, even
482
+ // though init never intends to touch `projects[]`.
483
+ let removedPlaceholders = 0;
484
+ let pinnedDefaultCli = null;
485
+ mutateConfig(config => {
486
+ removedPlaceholders = 0;
487
+ pinnedDefaultCli = null;
488
+ if (!config.projects) config.projects = [];
489
+ removedPlaceholders = cleanupPlaceholderProjects(config);
490
+ // Merge defaults — fills in new fields from upgrades while preserving user customizations
491
+ if (!config.engine) config.engine = {};
492
+ for (const [k, v] of Object.entries(ENGINE_DEFAULTS)) {
493
+ if (k === 'defaultCli') continue;
494
+ if (config.engine[k] === undefined) config.engine[k] = v;
495
+ }
496
+ shared.pruneDefaultClaudeConfig(config);
497
+ if (!config.agents || Object.keys(config.agents).length === 0) {
498
+ config.agents = { ...DEFAULT_AGENTS };
499
+ }
430
500
 
431
- // Auto-detect available runtime CLIs and pin engine.defaultCli to whichever
432
- // is installed. Only set if the user hasn't already configured one — never
433
- // overwrite an explicit choice on `init --force` upgrades.
434
- if (!hadConfiguredDefaultCli) {
435
- const detected = _detectAvailableRuntimes();
436
- if (detected.length === 1) {
437
- config.engine.defaultCli = detected[0];
438
- console.log(`\n ✓ Detected ${detected[0]} CLI — set as fleet default runtime`);
439
- } else if (detected.length > 1) {
440
- // Prefer the canonical fleet default when it is installed.
441
- config.engine.defaultCli = detected.includes(ENGINE_DEFAULTS.defaultCli)
442
- ? ENGINE_DEFAULTS.defaultCli
443
- : detected[0];
444
- console.log(`\n ✓ Detected ${detected.join(' + ')} — fleet default set to ${config.engine.defaultCli}`);
501
+ // Auto-detect available runtime CLIs and pin engine.defaultCli to whichever
502
+ // is installed. Only set if the user hasn't already configured one — never
503
+ // overwrite an explicit choice on `init --force` upgrades. Re-checked here
504
+ // because another writer may have pinned it since the pre-lock probe.
505
+ if (config.engine.defaultCli === undefined && detected.length > 0) {
506
+ config.engine.defaultCli = detected.length === 1
507
+ ? detected[0]
508
+ : (detected.includes(ENGINE_DEFAULTS.defaultCli) ? ENGINE_DEFAULTS.defaultCli : detected[0]);
509
+ pinnedDefaultCli = config.engine.defaultCli;
445
510
  }
446
511
  // If nothing is detected, leave defaultCli unset so the engine uses its canonical default.
512
+ return config;
513
+ });
514
+ if (pinnedDefaultCli) {
515
+ console.log(detected.length === 1
516
+ ? `\n ✓ Detected ${detected[0]} CLI — set as fleet default runtime`
517
+ : `\n ✓ Detected ${detected.join(' + ')} — fleet default set to ${pinnedDefaultCli}`);
447
518
  }
448
- saveConfig(config);
449
519
  console.log(`\n Minions initialized at ${MINIONS_HOME}`);
450
520
  console.log(` Config, agents, and engine defaults created.\n`);
451
521
 
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2448",
3
+ "version": "0.1.2449",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"
7
7
  },
8
8
  "scripts": {
9
- "package:check": "node tools/check-package-boundary.js public",
10
- "package:prepare:public": "node tools/prepare-package.js public",
11
- "package:prepare:internal": "node tools/prepare-package.js internal",
9
+ "package:check": "node scripts/check-package-boundary.js public",
10
+ "package:prepare:public": "node scripts/prepare-package.js public",
11
+ "package:prepare:internal": "node scripts/prepare-package.js internal",
12
+ "package:drift:check": "node scripts/reconcile-publish-drift.js --check",
13
+ "package:drift:apply": "node scripts/reconcile-publish-drift.js --apply",
12
14
  "dev": "node bin/minions.js --dev restart",
13
15
  "dev:start": "node bin/minions.js --dev start",
14
16
  "dev:stop": "node bin/minions.js --dev stop",
@@ -50,6 +52,7 @@
50
52
  "dashboard-build.js",
51
53
  "dashboard.js",
52
54
  "docs/",
55
+ "engine/README.md",
53
56
  "engine/**/*.js",
54
57
  "engine.js",
55
58
  "knowledge/agents/*.md",
@@ -57,7 +60,8 @@
57
60
  "playbooks/",
58
61
  "prompts/",
59
62
  "routing.md",
60
- "skills/"
63
+ "skills/",
64
+ "watch-plugins/"
61
65
  ],
62
66
  "repository": {
63
67
  "type": "git",
@@ -69,7 +73,7 @@
69
73
  },
70
74
  "devDependencies": {
71
75
  "@playwright/test": "^1.58.2",
72
- "eslint": "^9.39.4",
76
+ "eslint": "^10.8.0",
73
77
  "eslint-plugin-no-unsanitized": "^4.1.5"
74
78
  },
75
79
  "publishConfig": {
@@ -1,7 +1,7 @@
1
1
  # PR description audit + screenshot refresh (shared fragment)
2
2
 
3
3
  > **Naming convention.** Files named `_*.md` under `playbooks/` are **shared
4
- > fragments**. The engine renderer (`engine/playbook.js`) does not (yet)
4
+ > fragments**. The engine renderer (`engine/agents/playbook.js`) does not (yet)
5
5
  > implement an `<include>` directive, so each consuming playbook inlines this
6
6
  > fragment verbatim and the two copies are kept in sync by hand. If you change
7
7
  > this file, also update the inlined copies in `playbooks/fix.md` and
@@ -65,108 +65,137 @@ When you skip, still report it: set `meta.descriptionAudit.ran = false` and
65
65
  6. **GET-verify the description post-patch** by re-running the fetch from
66
66
  step 1 and confirming the body matches what you sent.
67
67
 
68
- ## Screenshot handling (introduce AND refresh for visual changes)
69
-
70
- For a PR that contains a **meaningful visual/UI change**, proactively CAPTURE
71
- screenshots and EMBED them directly in the PR description — do **not** rely only
72
- on the OPG `Visual evidence capture` CI bot (that bot stays as-is; this is
73
- additive PR-body evidence). When the existing description already embeds
74
- `![alt](url)` refs for a view this dispatch touched, refresh them the same way.
68
+ ## Visual evidence handling — screenshots + recordings (introduce AND refresh for visual changes)
69
+
70
+ Visual evidence is a **default behavior for UI changes on ANY project**, not a
71
+ minions-only nicety. For a PR that contains a **meaningful visual/UI change** —
72
+ web front-ends, dashboards, mobile webviews, or any rendered surface a user sees
73
+ — proactively CAPTURE evidence and EMBED it directly in the PR description; do
74
+ **not** rely only on the OPG `Visual evidence capture` CI bot (that bot stays
75
+ as-is and is minions-specific; this is additive, cross-project PR-body evidence).
76
+ When the existing description already embeds `![alt](url)` refs for a view this
77
+ dispatch touched, refresh them the same way. Decide "is this a visual change?"
78
+ from whether the diff changes a **rendered UI surface**, NOT from repo-specific
79
+ paths.
80
+
81
+ Capture **both** where applicable:
82
+
83
+ - **Screenshots** — a BEFORE/AFTER pair for visual FIXES; an AFTER-only shot for
84
+ NEW UI.
85
+ - **Screen recordings / video** — for interactive, animated, or multi-step flows
86
+ (hover/expand, drag, wizard steps, transitions). Playwright records video per
87
+ browser context; save the resulting file alongside the screenshots.
88
+
89
+ **How to capture — resolution order (harness first, then the minions skill).**
90
+
91
+ 1. **Harness first.** Consult the TARGET repo's own instructions for how it runs
92
+ its UI and captures evidence before inventing a command: `AGENTS.md`,
93
+ `CLAUDE.md`, `README`/`CONTRIBUTING`, a project-local playbook override under
94
+ `projects/<name>/playbooks/`, or a project-embedded skill under
95
+ `.claude/skills` / `.github/skills` / `.agents/skills`. The engine already
96
+ surfaces this harness plumbing read-only — `engine/agents/harness-context.js` and
97
+ `GET /api/harness/diagnostics` — so ground your capture on what the repo
98
+ actually declares; don't guess.
99
+ 2. **Fall back to the minions Playwright skill.** If the target repo has no
100
+ visual-evidence instructions, use Minions' own project-agnostic
101
+ `capture-web-evidence` skill (`.claude/skills/capture-web-evidence/SKILL.md`),
102
+ which drives Playwright (Playwright MCP when available, else the Playwright
103
+ CLI) against any web or mobile-webview UI.
75
104
 
76
105
  **MANDATORY GATE — UI-surface diffs REQUIRE screenshots.** Screenshot capture +
77
106
  embed is a **required completion gate**, not a nicety, for any dispatch whose diff
78
107
  modifies a **rendered UI surface** — e.g. files under `dashboard/`,
79
108
  `dashboard/slim/`, `dashboard/pages/`, `dashboard/js/`, `dashboard/styles.css`, or
80
109
  ANY change that alters what a dashboard page/route renders
81
- (layout/CSS/markup/new UI). For such a diff you **MUST** capture and embed AFTER
82
- screenshots (BEFORE/AFTER for visual FIXES, AFTER-only for NEW UI) for the PR to be
83
- considered complete. Omitting screenshots on a UI-surface diff **without a recorded
84
- hard-failure reason makes the PR incomplete** — "I judged it not visual enough" is
85
- **NOT** a valid skip when the diff touches those paths. The ONLY legitimate skips are
86
- genuine hard failures: the dev server will not start after a real attempt, Playwright
87
- MCP is unavailable, the route 404s, or upload/attachment auth fails. Record any such
88
- skip in `meta.descriptionAudit.result` as `screenshots-skipped (<specific-reason>)`.
89
-
90
- **Scope guard — which changes get screenshots.** Only meaningful visual/UI
110
+ (layout/CSS/markup/new UI). This is the minions-dashboard instance of the
111
+ cross-project default above and is **not** weakened by it. For such a diff you
112
+ **MUST** capture and embed AFTER screenshots (BEFORE/AFTER for visual FIXES,
113
+ AFTER-only for NEW UI) for the PR to be considered complete. Omitting screenshots
114
+ on a UI-surface diff **without a recorded hard-failure reason makes the PR
115
+ incomplete** — "I judged it not visual enough" is **NOT** a valid skip when the
116
+ diff touches those paths. The ONLY legitimate skips are genuine hard failures: the
117
+ dev server will not start after a real attempt, Playwright MCP is unavailable, the
118
+ route 404s, or upload/attachment auth fails. Record any such skip in
119
+ `meta.descriptionAudit.result` as `screenshots-skipped (<specific-reason>)`.
120
+
121
+ **Scope guard — which changes get visual evidence.** Only meaningful visual/UI
91
122
  changes: dashboard pages, slim-ux, Settings, work-item/PR/plan views, badges,
92
- layout/CSS. Skip backend-only, engine-logic, text/prose-only, analysis, and
93
- docs changes — those get NO screenshots (they add no signal). Use a BEFORE/AFTER
94
- pair for layout/visual FIXES; an AFTER-only shot for NEW UI.
95
-
96
- **Capture.** If the project has a runnable dev server (detect via
97
- `package.json` scripts named `dev`, `start`, or `serve`) AND Playwright MCP is
98
- available:
99
-
100
- 1. Spin up the dev server with a detached handoff (see `shared-rules.md` →
101
- "Long-Running Commands"). Record PID + log path + URL + stop command.
102
- 2. Drive Playwright MCP to the changed route at a normal viewport and
103
- screenshot the section that changed. Save raw PNGs to
104
- `agents/<id>/screenshots/` ONLY. **NEVER `git add` a PNG** — committing a
105
- screenshot is a release-blocker bug.
123
+ layout/CSS, and the equivalent rendered surfaces of other projects.
124
+ Skip backend-only, engine-logic, text/prose-only, analysis, and docs changes —
125
+ those get NO screenshots or recordings (they add no signal). Use a BEFORE/AFTER
126
+ pair for layout/visual FIXES; an AFTER-only shot for NEW UI; add a recording only
127
+ for interactive/animated flows.
128
+
129
+ **Capture.** Detect the project's run command generically — from the repo harness
130
+ (step 1 above) or from a `package.json` script named `dev`, `start`, or `serve`
131
+ (or the repo's documented preview command). Do **not** hardcode a per-repo build
132
+ command. With Playwright available:
133
+
134
+ 1. Spin up the project's dev/preview server with a detached handoff (see
135
+ `shared-rules.md` → "Long-Running Commands"). Record PID + log path + URL +
136
+ stop command.
137
+ 2. Drive Playwright to the changed route/view at a **normal desktop viewport**;
138
+ for **mobile webviews**, also capture at a mobile device-emulation viewport
139
+ (e.g. Playwright's `devices['iPhone 13']` or an explicit small viewport) and
140
+ screenshot the section that changed. For interactive flows, enable video
141
+ recording, drive the interaction, then save the video. Save raw PNGs/videos to
142
+ `agents/<id>/screenshots/` ONLY (recordings under a `recordings/` subdir).
143
+ **NEVER `git add` a PNG** or a video — committing captured evidence is a
144
+ release-blocker bug.
106
145
  3. Stop the dev server using the recorded stop command.
107
146
 
108
147
  **Upload + embed — GitHub (release-asset CDN recipe).** GitHub has no
109
148
  documented REST endpoint for PR-comment image uploads, so publish the PNGs as
110
149
  assets on a dedicated per-PR prerelease and embed the asset CDN URLs. This works
111
- on PRIVATE repos and never commits PNGs to the source branch. Resolve the
112
- correct account token for the repo owner and build a per-command `GH_TOKEN` —
113
- **NEVER run `gh auth switch`** (`opg-microsoft` → `yemishin_microsoft`,
114
- `yemi33` → `yemi33`).
115
-
116
- bash:
117
-
118
- ```bash
119
- OWNER=opg-microsoft; REPO=minions; PR=<num>; TAG="pr-${PR}-visual"
120
- GH_TOKEN=$(gh auth token --user yemishin_microsoft --hostname github.com)
121
- # Create the per-PR prerelease once; on re-dispatch, upload --clobber instead.
122
- GH_TOKEN=$GH_TOKEN gh release create "$TAG" agents/<id>/screenshots/*.png \
123
- --repo "$OWNER/$REPO" --title "PR #${PR} visual evidence" --prerelease \
124
- --notes "Visual evidence for PR #${PR}" \
125
- || GH_TOKEN=$GH_TOKEN gh release upload "$TAG" agents/<id>/screenshots/*.png \
126
- --repo "$OWNER/$REPO" --clobber
127
- # Resolve each asset's public CDN url:
128
- URL=$(GH_TOKEN=$GH_TOKEN gh api "repos/$OWNER/$REPO/releases/tags/$TAG" \
129
- --jq '.assets[]|select(.name=="after.png")|.browser_download_url')
130
- ```
131
-
132
- PowerShell:
133
-
134
- ```powershell
135
- $OWNER='opg-microsoft'; $REPO='minions'; $PR=<num>; $TAG="pr-$PR-visual"
136
- $env:GH_TOKEN = (gh auth token --user yemishin_microsoft --hostname github.com)
137
- $pngs = Get-ChildItem agents/<id>/screenshots/*.png
138
- gh release create $TAG $pngs --repo "$OWNER/$REPO" --title "PR #$PR visual evidence" --prerelease --notes "Visual evidence for PR #$PR"
139
- if ($LASTEXITCODE -ne 0) { gh release upload $TAG $pngs --repo "$OWNER/$REPO" --clobber }
140
- $URL = (gh api "repos/$OWNER/$REPO/releases/tags/$TAG" --jq '.assets[]|select(.name=="after.png")|.browser_download_url')
141
- ```
150
+ on private repositories and never commits PNGs to the source branch.
151
+
152
+ 1. Resolve `<owner>/<repo>` from the current PR and GET-verify that exact slug.
153
+ 2. Enumerate configured `gh` accounts without changing global auth. For each,
154
+ build a per-command `GH_TOKEN` and probe the exact slug; select only an
155
+ account whose repository response grants push permission. Fail closed when
156
+ none does. **NEVER run `gh auth switch` or hardcode an operator login.**
157
+ 3. Set `TAG=pr-<num>-visual` and explicitly list this dispatch's PNG (and, for
158
+ interactive flows, video) files.
159
+ 4. Query `gh release view "$TAG" --repo <owner>/<repo>` first. If the exact tag
160
+ exists, run `gh release upload "$TAG" <files> --clobber`; if it returns a
161
+ verified not-found response, run `gh release create`. Do not reinterpret an
162
+ arbitrary create failure as permission to overwrite assets.
163
+ 5. Resolve each asset with `gh api
164
+ repos/<owner>/<repo>/releases/tags/$TAG --jq
165
+ '.assets[]|select(.name=="after.png")|.browser_download_url'`.
142
166
 
143
167
  Then embed each asset in the PR body as `![<view> (after)](<url>)` (BEFORE/AFTER
144
168
  pair for fixes, AFTER-only for new UI) and patch the description with
145
- `gh pr edit <num> --repo <owner>/<repo> --body-file <file>`.
169
+ `gh pr edit <num> --repo <owner>/<repo> --body-file <file>`. Recordings upload as
170
+ assets on the same release; embed them as a plain markdown link (video files do
171
+ not render inline) — `[<view> (recording)](<url>)`.
146
172
 
147
173
  **Upload + embed — Azure DevOps.** ADO exposes a scriptable attachment API:
148
- `PUT /pullRequests/<id>/attachments/<filename>` with the PNG bytes, then
149
- reference the returned URL in the description. This now applies to
150
- newly-introduced screenshots too, not just refreshes.
174
+ `PUT /pullRequests/<id>/attachments/<filename>` with the PNG (or video) bytes,
175
+ then reference the returned URL in the description. This now applies to
176
+ newly-introduced screenshots and recordings too, not just refreshes.
151
177
 
152
178
  **Degrade gracefully.** If the dev server won't start, the route 404s,
153
179
  Playwright is unavailable, or the upload fails → **SKIP** without failing the
154
180
  work item and record the skip + reason in `meta.descriptionAudit` (e.g.
155
- `result: "screenshots-skipped (dev-server-unavailable)"`). Screenshots NEVER
156
- land in the repo — `agents/<id>/screenshots/` only; never `git add` a PNG.
181
+ `result: "screenshots-skipped (dev-server-unavailable)"`). Screenshots and
182
+ recordings NEVER land in the repo — `agents/<id>/screenshots/` only (videos under
183
+ a `recordings/` subdir); never `git add` a PNG or a video.
157
184
 
158
185
  ## Out-of-scope guardrails
159
186
 
160
- - Only screenshot **meaningful visual/UI changes** (see the scope guard above);
161
- skip backend/engine/text/analysis/docs changes — they add no signal.
187
+ - Only capture visual evidence for **meaningful visual/UI changes** (see the
188
+ scope guard above); skip backend/engine/text/analysis/docs changes — they add
189
+ no signal.
162
190
  - Don't rewrite description prose beyond the targeted stale edits.
163
191
  - Don't modify the PR title.
164
192
  - Don't toggle draft state, close/reopen the PR, or post a separate PR
165
193
  comment narrating the description change.
166
- - Don't fail the work item on a screenshot or upload/attachment failure —
167
- degrade gracefully and record the skip.
168
- - Screenshots **never** land in the repo. Save raw PNGs only under
169
- `agents/<id>/screenshots/`. No `git add` of any PNG, ever.
194
+ - Don't fail the work item on a screenshot, recording, or upload/attachment
195
+ failure — degrade gracefully and record the skip.
196
+ - Screenshots and recordings **never** land in the repo. Save raw PNGs only under
197
+ `agents/<id>/screenshots/` (videos under a `recordings/` subdir). No `git add`
198
+ of any PNG or video, ever.
170
199
  - Never run `gh auth switch`; build a per-command `GH_TOKEN` for the repo
171
200
  owner's account.
172
201
 
@@ -193,9 +222,12 @@ this block in the JSON completion report (see
193
222
  When you **introduce** screenshots for a visual change, set
194
223
  `result: "screenshots-introduced"` (or
195
224
  `"description-patched+screenshots-introduced"` when you also patched prose),
196
- and list the embedded asset URLs in `newScreenshots`. Use
197
- `"screenshots-refreshed"` when you only swapped pre-existing image refs, and a
198
- `"screenshots-skipped (<reason>)"` shape when capture/upload was skipped.
225
+ and list the embedded asset URLs in `newScreenshots`. When you also (or only)
226
+ attach a **screen recording**, use `result: "recordings-introduced"` (or combine,
227
+ e.g. `"screenshots-introduced+recordings-introduced"`) and list the video asset
228
+ URLs in `newScreenshots` alongside the images. Use `"screenshots-refreshed"` when
229
+ you only swapped pre-existing image refs, and a `"screenshots-skipped (<reason>)"`
230
+ shape when capture/upload was skipped.
199
231
 
200
232
  Omit `meta.descriptionAudit` entirely when the audit did not run (no
201
233
  commits pushed, no-op completion, etc.). When `meta.skipDescriptionAudit`
@@ -78,6 +78,8 @@ Use subagents only for genuinely parallel, independent tasks (e.g., editing file
78
78
 
79
79
  Before starting work, run `git status` and verify the worktree is clean and on the expected branch (`{{pr_branch}}`). If the worktree is dirty or on the wrong branch, report the issue and stop.
80
80
 
81
+ An untracked root-level `.minions-worktree` entry is expected Minions orchestration metadata, not source dirt. Do not delete or modify the marker. Any other status entry, including staged, modified, deleted, or other untracked files, means the worktree is dirty.
82
+
81
83
  ## Validation
82
84
 
83
85
  Before pushing, prove the fix did not introduce regressions: