@huseynsnmz/pi-board 0.15.1 → 0.16.0

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 (215) hide show
  1. package/CHANGELOG.md +26 -4
  2. package/CONTRIBUTING.md +7 -0
  3. package/README.md +53 -419
  4. package/SECURITY.md +19 -0
  5. package/docs/how-it-works.md +31 -0
  6. package/docs/reference.md +276 -0
  7. package/package.json +18 -19
  8. package/src/index.ts +9 -1
  9. package/src/runtime/service.mjs +12 -0
  10. package/src/ui/dashboard.ts +14 -8
  11. package/src/ui/double-left.ts +5 -1
  12. package/src/ui/focus.ts +7 -1
  13. package/src/ui/waiting.ts +10 -5
  14. package/IMPLEMENTATION_PLAN.md +0 -920
  15. package/PRD.md +0 -484
  16. package/PROGRESS.md +0 -142
  17. package/VERIFY.md +0 -115
  18. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  19. package/docs/EXPLORATION.md +0 -187
  20. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  21. package/docs/RELEASE.md +0 -148
  22. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  23. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  24. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  25. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  26. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  27. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  28. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  29. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  30. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  31. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  32. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  33. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  34. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  35. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  36. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  37. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  38. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  39. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  40. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  41. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  42. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  43. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  44. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  45. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  46. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  47. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  48. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  49. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  50. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  51. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  52. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  53. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  54. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  55. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  56. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  57. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  58. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  59. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  60. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  61. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  62. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  63. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  64. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  65. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  66. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  67. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  68. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  69. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  70. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  71. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  72. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  73. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  74. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  75. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  76. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  77. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  78. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  79. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  80. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  81. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  82. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  83. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  84. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  85. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  86. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  87. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  88. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  89. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  90. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  91. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  92. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  93. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  94. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  95. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  96. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  97. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  98. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  99. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  100. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  101. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  102. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  103. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  104. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  105. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  106. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  107. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  108. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  109. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  110. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  111. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  112. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  113. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  114. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  115. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  116. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  117. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  118. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  119. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  120. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  121. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  122. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  123. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  124. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  125. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  126. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  127. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  128. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  129. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  130. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  131. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  132. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  133. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  134. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  135. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  136. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  137. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  138. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  139. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  140. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  141. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  142. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  143. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  144. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  145. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  146. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  147. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  148. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  149. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  150. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  151. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  152. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  153. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  154. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  155. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  156. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  157. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  158. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  159. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  160. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  161. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  162. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  163. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  164. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  165. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  166. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  167. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  168. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  169. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  170. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  171. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  172. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  173. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  174. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  175. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  176. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  177. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  178. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  179. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  180. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  181. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  182. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  183. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  184. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  185. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  186. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  187. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  188. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  189. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  190. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  191. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  192. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  193. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  194. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  195. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  196. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  197. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  198. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  199. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  200. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  201. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  202. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  203. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  204. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  205. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  206. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  207. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  208. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  209. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  210. package/scripts/budget-audit.mjs +0 -135
  211. package/scripts/patch-vulns.mjs +0 -59
  212. package/scripts/release.mjs +0 -446
  213. package/scripts/release_helper.mjs +0 -309
  214. package/scripts/run-perf-gate.mjs +0 -40
  215. package/scripts/run-tests.mjs +0 -375
@@ -1,461 +0,0 @@
1
- # Coordinator root-vanished self-exit — Implementation Plan
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use subagent-driven-development (recommended) or executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** Make a detached `runner/state-coordinator.mjs` exit on its own when its board root disappears, make throwaway roots deletable on win32 while it serves them, and make the test suite fail loudly if it leaks a coordinator.
6
-
7
- **Architecture:** (1) `launchCoordinator` passes an absolute root argv and spawns with `cwd = dirname(root)` so the process no longer pins the root; (2) the coordinator checks `statSync(root)` on its existing supervised heartbeat tick and steps down on `ENOENT`; (3) a new `scripts/run-tests.mjs` wraps `node --test`, snapshots this checkout's coordinator pids before the run, polls a grace window after it, and kills + fails on survivors. No protocol bump, no new dependencies, no new timers.
8
-
9
- **Tech Stack:** Node ESM (`.mjs`), `node:test` + `assert/strict`, `node:child_process` detached spawn, `net` JSONL control sockets, Windows CIM / `ps` for process enumeration.
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md` (read §3, §5, §6 before starting; the acceptance IDs A1–A8/U1 used below come from §5).
12
-
13
- ## Global Constraints
14
-
15
- - Tabs for indentation; English comments and commit messages (repo `AGENTS.md`).
16
- - No new dependencies. Wrapper uses only Node built-ins.
17
- - Tests: `node:test` + `assert/strict`; every filesystem fixture under a temp dir — never the host repo.
18
- - Node >= 20 (CI matrix 22/24, ubuntu). Nothing may depend on Node 21+ test-runner flags.
19
- - No protocol version bump (`COORDINATOR_PROTOCOL_VERSION` unchanged); lease/journal formats unchanged.
20
- - Detection cadence reuses `AGENT_BOARD_TEST_COORDINATOR_HEARTBEAT_MS` (default 1000 ms); do not add a second knob for the root check.
21
- - Work from the worktree only: `WT=C:\Users\27499\proj\opensource\pi-agent-board\.pi\worktrees\issue-241-coordinator-self-exit`. Never edit the main checkout.
22
- - `npm run verify` is the completion gate (typecheck + perf gate + tests + coverage + pack:dry); `npm test` itself now goes through the new wrapper.
23
- - Conventional commit subjects with the issue ref: `fix(coordinator): … (issue #241)`.
24
-
25
- ## Review Focus
26
-
27
- - **Relative / trailing-slash / dot-segment root** passed to `launchCoordinator` must resolve to the same absolute root and the same coordinator endpoint (issue #124 contract). Owning task: Task 1 (A1 + existing `test/coordinator-client.test.mjs` #124 case).
28
- - **Root already gone at boot** (deleted before the coordinator starts): it must exit 0 through the existing lease-unavailable path, not crash and not linger. Owning task: Task 2 (A3b).
29
- - **Host without process enumeration** (powershell/ps missing or failing): the wrapper must warn and skip the residue check, never turn an environment issue into a red suite. Owning task: Task 3 (A6 branch E).
30
- - **Pre-existing orphans** (from older builds like the 26 on the reporter's box) must be snapshot-only: warn, never fail the run, never kill someone else's process. Owning task: Task 3 (A6 branch C).
31
- - **win32 command-line spellings** (quoted paths, forward slashes, case differences) must still select the coordinator; production roots (`~/.pi/...`, `$AGENT_BOARD_ROOT`) must never be selected. Owning task: Task 3 (A5).
32
-
33
- ---
34
-
35
- ## Task 1: `launchCoordinator` spawn plan (A1, A2)
36
-
37
- **Files:**
38
- - Modify: `src/core/launch.mjs` (imports at `:8-12`; `launchCoordinator` at `:124-137`)
39
- - Test: `test/coordinator-lifecycle.test.mjs` (create)
40
-
41
- **Interfaces:**
42
- - Consumes: nothing new.
43
- - Produces: `coordinatorSpawnPlan(root: string): { root: string, cwd: string }` exported from `src/core/launch.mjs`; `launchCoordinator(root, opts)` keeps its existing signature/return (`{ pid: number|null }`).
44
-
45
- - [ ] **Step 1: Write the failing A1 unit test**
46
-
47
- ```js
48
- import assert from "node:assert/strict";
49
- import { isAbsolute, dirname, join, resolve } from "node:path";
50
- import { tmpdir } from "node:os";
51
- import { test } from "node:test";
52
- import { coordinatorSpawnPlan } from "../src/core/launch.mjs";
53
-
54
- test("A1 coordinatorSpawnPlan: absolute root argv, parent-dir cwd", () => {
55
- const base = join(tmpdir(), "agentview-c241-plan", "board root");
56
- for (const input of [base, `${base}/`, join(base, "views", "..")]) {
57
- const plan = coordinatorSpawnPlan(input);
58
- assert.ok(isAbsolute(plan.root), `root must be absolute for ${input}`);
59
- assert.equal(plan.root, resolve(input));
60
- assert.equal(plan.cwd, dirname(resolve(input)));
61
- }
62
- });
63
- ```
64
-
65
- - [ ] **Step 2: Run it to verify it fails**
66
-
67
- Run: `node --test test/coordinator-lifecycle.test.mjs`
68
- Expected: FAIL — `SyntaxError: The requested module '../src/core/launch.mjs' does not provide an export named 'coordinatorSpawnPlan'`.
69
-
70
- - [ ] **Step 3: Add `coordinatorSpawnPlan` (no wiring yet)**
71
-
72
- In `src/core/launch.mjs`, add `import { dirname, resolve } from "node:path";` next to the other imports and export:
73
-
74
- ```js
75
- /**
76
- * Compute the detached coordinator spawn plan (issue #241): an absolute root
77
- * argv and a cwd OUTSIDE the root. The coordinator derives all paths from the
78
- * root argument and never uses cwd, so keeping cwd off the root releases the
79
- * win32 directory handle that made throwaway roots undeletable.
80
- * @param {string} root
81
- * @returns {{ root: string, cwd: string }}
82
- */
83
- export function coordinatorSpawnPlan(root) {
84
- const absoluteRoot = resolve(root);
85
- return { root: absoluteRoot, cwd: dirname(absoluteRoot) };
86
- }
87
- ```
88
-
89
- - [ ] **Step 4: Run A1 to verify it passes** — `node --test test/coordinator-lifecycle.test.mjs` → PASS.
90
-
91
- - [ ] **Step 5: Write the failing A2 integration test (append to the file)**
92
-
93
- ```js
94
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
95
- import { launchCoordinator } from "../src/core/launch.mjs";
96
- import { isAlive } from "../src/core/pid.mjs";
97
- import { sleep, waitFor } from "./helpers-c241.mjs"; // inline helpers instead if preferred
98
- ```
99
-
100
- Fake runner body (written under the temp root by the test):
101
-
102
- ```js
103
- import { writeFileSync } from "node:fs";
104
- import { join } from "node:path";
105
- writeFileSync(join(process.argv[2], "spawn-probe.json"), JSON.stringify({ cwd: process.cwd(), arg: process.argv[2] }));
106
- ```
107
-
108
- Test body:
109
-
110
- ```js
111
- test("A2 launchCoordinator spawns with absolute root argv and parent-dir cwd", async () => {
112
- const root = mkdtempSync(join(tmpdir(), "agentview-c241-spawn-"));
113
- try {
114
- const fake = join(root, "fake-runner.mjs");
115
- writeFileSync(fake, FAKE_RUNNER, "utf8");
116
- const { pid } = launchCoordinator(root, { runnerScript: fake });
117
- assert.ok(pid, "detached child has a pid");
118
- const probePath = join(root, "spawn-probe.json");
119
- const raw = await waitFor(() => (existsSync(probePath) ? readFileSync(probePath, "utf8") : null), 5000);
120
- assert.ok(raw, "fake runner must write its probe file");
121
- const probe = JSON.parse(raw);
122
- const norm = (p) => (process.platform === "win32" ? resolve(p).toLowerCase() : resolve(p));
123
- assert.equal(norm(probe.arg), norm(root), "argv[2] is the absolute root");
124
- assert.equal(norm(probe.cwd), norm(dirname(root)), "cwd is the root's parent, not the root");
125
- } finally {
126
- rmSync(root, { recursive: true, force: true });
127
- }
128
- });
129
- ```
130
-
131
- - [ ] **Step 6: Run A2 to verify it fails** — `node --test test/coordinator-lifecycle.test.mjs` → A2 FAIL at `cwd ... not the root` (launchCoordinator still spawns with `cwd: root`); A1 passes.
132
-
133
- - [ ] **Step 7: Wire `launchCoordinator` to the plan**
134
-
135
- ```js
136
- export function launchCoordinator(root, opts) {
137
- const node = opts.node ?? resolveNode();
138
- const plan = coordinatorSpawnPlan(root);
139
- const child = spawnDetached(node, [opts.runnerScript, plan.root], plan.cwd);
140
- return { pid: child.pid ?? null };
141
- }
142
- ```
143
-
144
- Update the JSDoc above it with one sentence: "The child's cwd is the root's PARENT (issue #241): the coordinator derives paths from argv and cwd must not pin the root on win32."
145
-
146
- - [ ] **Step 8: Run both A1 and A2 to verify they pass** — `node --test test/coordinator-lifecycle.test.mjs` → PASS.
147
-
148
- - [ ] **Step 9: Run the coordinator endpoint-normalization regression**
149
-
150
- Run: `node --test test/coordinator-client.test.mjs`
151
- Expected: PASS, including the `#124` alternate-spelling test (spawn argv now canonical; endpoint hash already normalizes via `path.win32.resolve`).
152
-
153
- - [ ] **Step 10: Commit**
154
-
155
- ```bash
156
- git add src/core/launch.mjs test/coordinator-lifecycle.test.mjs
157
- git commit -m "fix(coordinator): spawn with absolute root and parent-dir cwd (issue #241)"
158
- ```
159
-
160
- ---
161
-
162
- ## Task 2: root-vanished self-exit (A3, A3b, A4)
163
-
164
- **Files:**
165
- - Modify: `runner/state-coordinator.mjs` (`statSync` already imported at `:39`; supervised heartbeat at `:237-272`; `shutdown` at `:278-293`)
166
- - Test: `test/coordinator-lifecycle.test.mjs` (append)
167
-
168
- **Interfaces:**
169
- - Consumes: `launchCoordinator` from Task 1 (A3 spawns the real coordinator through it).
170
- - Produces: coordinator behavior only — no exported API. Exit path is the existing `shutdown(0)`.
171
-
172
- - [ ] **Step 1: Write the failing A3 self-exit test (append)**
173
-
174
- ```js
175
- const COORDINATOR_SCRIPT = fileURLToPath(new URL("../runner/state-coordinator.mjs", import.meta.url));
176
-
177
- test("A3 deleting the root makes the coordinator exit within the grace window", async () => {
178
- const root = mkdtempSync(join(tmpdir(), "agentview-c241-exit-"));
179
- const prev = process.env.AGENT_BOARD_TEST_COORDINATOR_HEARTBEAT_MS;
180
- process.env.AGENT_BOARD_TEST_COORDINATOR_HEARTBEAT_MS = "100";
181
- try {
182
- const { pid } = launchCoordinator(root, { runnerScript: COORDINATOR_SCRIPT });
183
- assert.ok(pid);
184
- assert.ok(await waitForCoordinatorReady(root, 10_000), "coordinator binds its endpoint");
185
- const lease = join(P.viewLockPath(root, "_coordinator", "state-coordinator"), "owner.json");
186
- assert.ok(existsSync(lease), "lease exists BEFORE the delete (anti-vacuous precondition)");
187
- assert.ok(isAlive(pid), "coordinator is alive before the delete");
188
- rmSync(root, { recursive: true, force: true }); // must not throw: cwd no longer pins the root
189
- assert.equal(existsSync(root), false);
190
- await waitForPidGone(pid, 5000);
191
- assert.equal(isAlive(pid), false, "coordinator exits after its root vanished");
192
- } finally {
193
- if (prev === undefined) delete process.env.AGENT_BOARD_TEST_COORDINATOR_HEARTBEAT_MS;
194
- else process.env.AGENT_BOARD_TEST_COORDINATOR_HEARTBEAT_MS = prev;
195
- rmSync(root, { recursive: true, force: true });
196
- }
197
- });
198
- ```
199
-
200
- Helpers in the same file (not exported elsewhere): `waitForCoordinatorReady(root, ms)` polls `createConnection(P.coordinatorEndpointPathFor(process.platform, root))` until connect succeeds; `waitForPidGone(pid, ms)` polls `isAlive` every 25 ms; `isAlive` is imported from `../src/core/pid.mjs` (`src/core/pid.mjs` exports `isAlive`).
201
-
202
- - [ ] **Step 2: Run A3 to verify it fails** — `node --test test/coordinator-lifecycle.test.mjs` → A3 FAIL: `rmSync` now succeeds (Task 1) but `coordinator exits after its root vanished` fails (pid stays alive); lease precondition passes.
203
-
204
- - [ ] **Step 3: Add `rootVanished()` and hook it into the supervised interval**
205
-
206
- ```js
207
- /** True only when the root is provably gone (ENOENT). A transient access
208
- * failure (EACCES/EBUSY) must not kill a healthy coordinator. */
209
- function rootVanished() {
210
- try { statSync(root); return false; }
211
- catch (err) { return err?.code === "ENOENT"; }
212
- }
213
- ```
214
-
215
- Inside the `setInterval` callback started in `server.listen` (`runner/state-coordinator.mjs:248`), before the existing `lease.touch()` logic:
216
-
217
- ```js
218
- heartbeatTimer = setInterval(() => {
219
- if (rootVanished()) {
220
- // No diagnostic: appendLine ensureDirs its parent and would recreate
221
- // <root>/views/_coordinator, leaving the skeleton dir U1 measures
222
- // (root-missing step-down is attributed on stderr only).
223
- process.stderr.write(`state-coordinator: board root missing (${root}); stepping down\n`);
224
- shutdown(0);
225
- return;
226
- }
227
- // ...existing touch/failure logic unchanged...
228
- }, heartbeatMs);
229
- ```
230
-
231
- - [ ] **Step 4: Run A3 to verify it passes** — `node --test test/coordinator-lifecycle.test.mjs` → PASS.
232
-
233
- - [ ] **Step 5: Write A3b (boot with an already-missing root) and A4 (no false positive)**
234
-
235
- A3b: create a root with `mkdtempSync`, `rmSync` it, then spawn the runner directly (`spawn(process.execPath, [COORDINATOR_SCRIPT, plan.root], { cwd: plan.cwd, stdio: ["ignore", "pipe", "pipe"] })` with `plan = coordinatorSpawnPlan(root)`); await exit and assert `code === 0`, `signal === null`, stderr matches `/board root missing/`, and the root still does not exist. (The boot guard exists because lease acquisition `ensureDir`s and would recreate the root — "fails lease acquisition" is not a viable boot path.)
236
-
237
- A4: with the same 100 ms knob, spawn the runner the same way with piped stderr on an existing root, wait ≥ 3 ticks (`await new Promise((r) => setTimeout(r, 500))`), then assert the child is alive and a raw `ping`/`pong` round-trip on the endpoint succeeds; assert stderr contains **no** `board root missing` line (the step-down's only observable — it writes no diagnostic). Finally kill it via the SIGTERM → poll → SIGKILL ladder.
238
-
239
- - [ ] **Step 6: Run A3, A3b, A4** — all PASS. If A4 fails because the coordinator was killed by the test's own knob, restore the env var before spawning (the knob is read at coordinator `main()` start).
240
-
241
- - [ ] **Step 7: Run the existing coordinator suites**
242
-
243
- Run: `node --test test/state-coordinator.integration.test.mjs test/coordinator-client.test.mjs`
244
- Expected: PASS (heartbeat supervision, lease reclaim, A14 endpoint cleanup unchanged).
245
-
246
- - [ ] **Step 8: Commit**
247
-
248
- ```bash
249
- git add runner/state-coordinator.mjs test/coordinator-lifecycle.test.mjs
250
- git commit -m "fix(coordinator): step down when the board root disappears (issue #241)"
251
- ```
252
-
253
- ---
254
-
255
- ## Task 3: suite gate wrapper (A5, A6, A7 wiring)
256
-
257
- **Files:**
258
- - Create: `scripts/run-tests.mjs`
259
- - Create: `test/run-tests-wrapper.test.mjs`
260
- - Modify: `package.json` (`:56-57` test scripts)
261
- - Modify: `.c8rc.json` (exclude array, `:4-9`)
262
-
263
- **Interfaces:**
264
- - Consumes: nothing from earlier tasks for its own tests; its real run expects Tasks 1–2 to stop leaks (A7).
265
- - Produces: `parseProcessList(raw, platform)`, `selectOrphanCoordinators({processes, repoRoot, tmpRoot, platform})`, `runSuite(deps)`, `main()`; `package.json` `"test": "node scripts/run-tests.mjs"`, `"test:coverage": "c8 node scripts/run-tests.mjs"`.
266
-
267
- - [ ] **Step 1: Write the failing A5 parse tests**
268
-
269
- In `test/run-tests-wrapper.test.mjs`, `import { parseProcessList, selectOrphanCoordinators } from "../scripts/run-tests.mjs";` and assert:
270
-
271
- ```js
272
- const CIM_ARRAY = JSON.stringify([
273
- { ProcessId: 123, CommandLine: "node C:\\repo\\runner\\state-coordinator.mjs C:\\Temp\\agentview-pty-a" },
274
- ]);
275
- const CIM_SINGLE = JSON.stringify({ ProcessId: 456, CommandLine: "node other.mjs" });
276
- test("A5 parseProcessList: CIM array and single object", () => {
277
- assert.deepEqual(parseProcessList(CIM_ARRAY, "win32"), [{ pid: 123, commandLine: "node C:\\repo\\runner\\state-coordinator.mjs C:\\Temp\\agentview-pty-a" }]);
278
- assert.deepEqual(parseProcessList(CIM_SINGLE, "win32"), [{ pid: 456, commandLine: "node other.mjs" }]);
279
- });
280
- test("A5 parseProcessList: ps lines incl. long command line", () => {
281
- const long = "/usr/bin/node /home/u/repo/runner/state-coordinator.mjs /tmp/agentview-" + "x".repeat(200);
282
- const raw = ` 123 ${long}\n 456 node other.mjs\nnot-a-line\n`;
283
- const parsed = parseProcessList(raw, "linux");
284
- assert.equal(parsed.length, 2);
285
- assert.equal(parsed[0].pid, 123);
286
- assert.ok(parsed[0].commandLine.includes("x".repeat(200)), "long command line survives parsing");
287
- assert.equal(parseProcessList("", "linux").length, 0);
288
- });
289
- ```
290
-
291
- - [ ] **Step 2: Run to verify it fails** — `node --test test/run-tests-wrapper.test.mjs` → FAIL (module not found).
292
-
293
- - [ ] **Step 3: Implement `parseProcessList` in `scripts/run-tests.mjs`**
294
-
295
- Top of file: shebang `#!/usr/bin/env node`, module JSDoc describing the gate, imports (`readdirSync`, `execFileSync`/`spawn`, `readFileSync` if needed, `tmpdir`, `dirname`, `resolve`, `sep`, `fileURLToPath`, `pathToFileURL`). Pure function:
296
-
297
- ```js
298
- export function parseProcessList(raw, platform) {
299
- const text = String(raw ?? "").trim();
300
- if (!text) return [];
301
- if (platform === "win32") {
302
- let data;
303
- try { data = JSON.parse(text); } catch { return []; }
304
- const rows = Array.isArray(data) ? data : [data];
305
- return rows
306
- .map((row) => ({ pid: Number(row?.ProcessId ?? 0), commandLine: String(row?.CommandLine ?? "") }))
307
- .filter((row) => Number.isFinite(row.pid) && row.pid > 0 && row.commandLine);
308
- }
309
- const out = [];
310
- for (const line of text.split(/\r?\n/)) {
311
- const m = /^\s*(\d+)\s+(.+)$/.exec(line);
312
- if (m) out.push({ pid: Number(m[1]), commandLine: m[2] });
313
- }
314
- return out;
315
- }
316
- ```
317
-
318
- - [ ] **Step 4: Run A5 parse tests to verify they pass.**
319
-
320
- - [ ] **Step 5: Write the failing A5 selection tests**
321
-
322
- ```js
323
- const REPO = "C:\\repo";
324
- const TMP = "C:\\Users\\u\\AppData\\Local\\Temp";
325
- const SCRIPT = `${REPO}\\runner\\state-coordinator.mjs`;
326
- function proc(pid, root, { script = SCRIPT, platform = "win32" } = {}) {
327
- const cmd = platform === "win32"
328
- ? `"C:\\Program Files\\nodejs\\node.exe" "${script}" "${root}"`
329
- : `/usr/bin/node ${script} ${root}`;
330
- return { pid, commandLine: cmd };
331
- }
332
- test("A5 selection: this checkout + temp root only; production/other-checkout excluded", () => {
333
- const processes = [
334
- proc(1, `${TMP}\\agentview-pty-abc`), // selected
335
- proc(2, "C:\\Users\\u\\.pi\\agent\\agent-board"), // production root
336
- proc(3, `${TMP}\\agentview-pty-def`, { script: "C:\\other\\runner\\state-coordinator.mjs" }), // other checkout
337
- proc(4, `${TMP}\\agentview-pty-ghi`, { script: SCRIPT.replace(/\\/g, "/") }), // slash variant
338
- { pid: 5, commandLine: "node unrelated.mjs" },
339
- ];
340
- const got = selectOrphanCoordinators({ processes, repoRoot: REPO, tmpRoot: TMP, platform: "win32" });
341
- assert.deepEqual(got.map((x) => x.pid), [1, 4]);
342
- assert.ok(got.every((x) => x.root.startsWith(TMP)));
343
- });
344
- test("A5 selection: POSIX", () => {
345
- const processes = [
346
- { pid: 10, commandLine: `/usr/bin/node /home/u/repo/runner/state-coordinator.mjs /tmp/agentview-pty-abc` },
347
- { pid: 11, commandLine: `/usr/bin/node /home/u/repo/runner/state-coordinator.mjs /home/u/.pi/agent/agent-board` },
348
- ];
349
- const got = selectOrphanCoordinators({ processes, repoRoot: "/home/u/repo", tmpRoot: "/tmp", platform: "linux" });
350
- assert.deepEqual(got.map((x) => x.pid), [10]);
351
- });
352
- ```
353
-
354
- - [ ] **Step 6: Run to verify selection fails** (not implemented), then implement:
355
-
356
- Rules: normalize separators (`/`→`\` on win32) and lowercase both sides on win32; require the command line to contain `<repoRoot>/runner/state-coordinator.mjs`; extract the root as the token after the script path (strip surrounding quotes); require the root to resolve under `tmpRoot` (`resolve(root).toLowerCase().startsWith(resolve(tmpRoot).toLowerCase() + sep)` on win32, `startsWith(resolve(tmpRoot) + "/")` on POSIX). Return `[{pid, root}]` sorted by pid.
357
-
358
- - [ ] **Step 7: Run A5 selection tests to verify they pass.**
359
-
360
- - [ ] **Step 8: Write the failing A6 `runSuite` tests (injected fakes)**
361
-
362
- `runSuite(deps)` contract: `deps = { spawnTest, listProcesses, kill, sleep, log, repoRoot, tmpRoot, platform, graceMs = 10_000, pollMs = 100, log }`; `spawnTest()` resolves `{ exitCode: number|null, signal?: string|null }`; `listProcesses()` returns `[{pid, commandLine}]`; `kill(pid, signal)`; `sleep(ms)`; `log(line)`; returns `{ exitCode, survivors: number[] }`.
363
-
364
- Cases:
365
- - clean: before `[]`, after `[]`, `spawnTest` → `{exitCode: 0}` ⇒ `{exitCode: 0, survivors: []}`, `kill` never called.
366
- - survivor: before `[]`; after list returns a selected coordinator forever; `spawnTest` → `{exitCode: 0}` ⇒ `exitCode: 1`, `kill` called with `SIGTERM` then `SIGKILL`, survivor reported.
367
- - snapshot-only survivor: same pid present in before AND after ⇒ not killed, `exitCode: 0`, `log` contains a pre-existing warning with count 1.
368
- - signal exit: `spawnTest` → `{exitCode: null, signal: "SIGINT"}` ⇒ `exitCode: 1`.
369
- - enumeration failure: `listProcesses` throws ⇒ `exitCode` equals the test exit code (0), `kill` not called, `log` contains a warning.
370
- - import safety: importing the module twice resolves the same namespace and does not start anything (a spurious `main()` would run the suite recursively here).
371
-
372
- - [ ] **Step 9: Run to verify A6 fails, then implement `runSuite` + `main()` + main-guard**
373
-
374
- Main guard:
375
-
376
- ```js
377
- if (import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
378
- await main();
379
- }
380
- ```
381
-
382
- `main()`: `files = readdirSync("test").filter((f) => f.endsWith(".test.mjs")).sort().map((f) => join("test", f))`; `spawnTest = () => new Promise((res) => { const child = spawn(process.execPath, ["--test", ...files], { stdio: "inherit" }); child.on("exit", (code, signal) => res({ exitCode: code, signal })); })`; `listProcesses = () => parseProcessList(execFileSync(enumCmd, enumArgs, { encoding: "utf8" }), process.platform)` where the enum command is `pwsh/powershell` CIM on win32 and `ps -e -ww -o pid=,args=` otherwise; `kill = (pid, sig) => { try { process.kill(pid, sig); } catch {} }`; grader: poll `pollMs` until `graceMs` for snapshot-subtracted pids to vanish, then kill survivors (`SIGTERM` → 2 s → `SIGKILL`) and `process.exitCode = 1`. Print `run-tests: no leaked coordinators` on success. On enumeration error: warn via `log`, skip residue check.
383
-
384
- - [ ] **Step 10: Run A6 tests to verify they pass.**
385
-
386
- - [ ] **Step 11: Wire `package.json` and `.c8rc.json`**
387
-
388
- - `package.json`: `"test": "node scripts/run-tests.mjs"`, `"test:coverage": "c8 node scripts/run-tests.mjs"`.
389
- - `.c8rc.json`: add `"scripts/run-tests.mjs"` to `exclude`.
390
-
391
- - [ ] **Step 12: Run the wrapper over the full suite**
392
-
393
- Run: `npm test`
394
- Expected: the suite runs, exits 0, and prints `run-tests: no leaked coordinators`. If survivors are reported, record pid + root (Task 4 owns fixing exposed tests) — do not loosen the gate.
395
-
396
- - [ ] **Step 13: Commit**
397
-
398
- ```bash
399
- git add scripts/run-tests.mjs test/run-tests-wrapper.test.mjs package.json .c8rc.json
400
- git commit -m "test: fail the suite when it leaks a state-coordinator (issue #241)"
401
- ```
402
-
403
- ---
404
-
405
- ## Task 4: full verification, exposed-leak fixes, coverage (A7, A8, U1)
406
-
407
- **Files:**
408
- - Modify: any test file the gate exposes as not cleaning up its root (expected: none, but the gate is the source of truth)
409
- - No product code changes expected.
410
-
411
- **Interfaces:**
412
- - Consumes: Tasks 1–3 complete.
413
-
414
- - [ ] **Step 1: Typecheck** — `npm run typecheck` → 0 errors.
415
-
416
- - [ ] **Step 2: Run the suite twice through the wrapper** — `npm test` twice → both exit 0 with no survivors. If a test file leaves a coordinator, its root or its cleanup is fixed in that test file (attach the pid/root from the gate output to the commit message).
417
-
418
- - [ ] **Step 3: Coverage gate** — `npm run test:coverage` → wrapper runs under c8, thresholds hold (lines 85 / funcs 80 / branches 70).
419
-
420
- - [ ] **Step 4: Perf gate** — `npm run test:perf` → pass 3 / skipped 0 (the wrapper must not affect it).
421
-
422
- - [ ] **Step 5: Full verify** — `npm run verify` → typecheck + perf + tests + coverage + pack:dry all green.
423
-
424
- - [ ] **Step 6: U1 measurement on the reporter's box (Windows)**
425
-
426
- Record before/after `npm test`: count `Get-CimInstance Win32_Process -Filter "Name='node.exe'"` filtered to `state-coordinator` and count `%TEMP%\agentview-pty-*` dirs. One-time cleanup of pre-existing orphans is allowed before the baseline (document the exact pids/dirs removed). Report both counts and the delta in the task summary; the post-merge U1 entry points at this measurement.
427
-
428
- - [ ] **Step 7: Commit any fixes** (test cleanup only; message `test: clean up <file> coordinator root (issue #241)`).
429
-
430
- ---
431
-
432
- ## Task 5: pre-existing win32 `launch.test.mjs` cleanup failures (gate exposure)
433
-
434
- **Why:** `node --test test/launch.test.mjs` fails 4/5 on win32 on `main` @2815469 (verified before this branch). Its fixtures build `config.cwd = root` and spawn `test-support/fake-pi.mjs` as a detached runner but never kill it, so `rmSync(root)` fails `EPERM` at cleanup — the same root-pinning and fake-runner-orphan class this issue fixes. A7 requires `npm test` green on Windows, so this must be repaired in the same PR (spec §7 "a test that never deletes its root: fix that test's cleanup in the same PR").
435
-
436
- **Files:**
437
- - Modify: `test/launch.test.mjs` only (no product code, per the Why).
438
-
439
- - [ ] **Step 1: Reproduce** — `node --test test/launch.test.mjs` → 4 failures with `EPERM ... agentview-launch-*`.
440
-
441
- - [ ] **Step 2: Add a local `killAndRemove(pid, dir)` helper** at the bottom of the file: SIGTERM the pid (ignore errors), poll `isAlive(pid)` every 25 ms up to 2 s, SIGKILL if still alive, then `rmSync(dir, { recursive: true, force: true })` retried every 50 ms up to 40 attempts (rethrow the last error). `isAlive` can be a local `try { process.kill(pid, 0); return true; } catch { return false; }`.
442
-
443
- - [ ] **Step 3: Use it in the four failing tests' `finally`** — replace `rmSync(root, ...)` with `await killAndRemove(res.pid, root)` (hoist `res` above the `try`). Keep every assertion unchanged.
444
-
445
- - [ ] **Step 4: Verify** — `node --test test/launch.test.mjs` → 5/5 pass; run it twice and confirm no `agentview-launch-*` dirs and no lingering `fake-pi.mjs` processes are left behind.
446
-
447
- - [ ] **Step 5: Commit** — `test: kill launched fake runners before removing launch roots (issue #241)`.
448
-
449
- Execution order note (ledger): Task 5 runs after Tasks 1–3 and **before Task 4**, because Task 4's gate must see a green suite.
450
-
451
- ## Self-Review
452
-
453
- **Spec coverage:** §3.1 → Task 1 (A1, A2); §3.2 → Task 2 (A3, A3b, A4); §3.3 → Task 3 (A5, A6, A7); §3.4 ordering honored (wrapper wired after 1–2); §5 A8 + U1 → Task 4; §7 risks mapped to Review Focus + tests; §8 deployment unchanged by design (no bump, no migration).
454
-
455
- **Step scan:** every step names the file, the exact function/assertion or command, and the expected failure/pass; no "handle edge cases" or TBD lines.
456
-
457
- **Type consistency:** `coordinatorSpawnPlan` (Task 1) is not referenced by later tasks except through `launchCoordinator`; `parseProcessList`/`selectOrphanCoordinators`/`runSuite` names and signatures are identical between Task 3 steps and its tests; `isAlive` comes from `src/core/pid.mjs`; `P.viewLockPath` from `src/core/paths.mjs`.
458
-
459
- **Review Focus coverage:** relative/trailing/dot root → A1 + #124 suite (Task 1); boot with missing root → A3b (Task 2); no-enumeration host → A6 branch E (Task 3); pre-existing orphans → A6 branch C (Task 3); win32 spellings/production roots → A5 (Task 3).
460
-
461
- **Proportion:** plan tasks are code-light; the wrapper's pure functions are the only non-trivial bodies and their behavior is fixed by the tests above.