@huseynsnmz/pi-board 0.15.3 → 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 (211) hide show
  1. package/CHANGELOG.md +13 -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 +5 -11
  8. package/src/index.ts +1 -1
  9. package/src/runtime/service.mjs +12 -0
  10. package/src/ui/dashboard.ts +14 -8
  11. package/IMPLEMENTATION_PLAN.md +0 -920
  12. package/PRD.md +0 -484
  13. package/PROGRESS.md +0 -142
  14. package/VERIFY.md +0 -115
  15. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  16. package/docs/EXPLORATION.md +0 -187
  17. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  18. package/docs/RELEASE.md +0 -148
  19. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  20. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  21. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  22. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  23. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  24. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  25. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  26. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  27. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  28. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  29. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  30. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  31. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  32. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  33. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  34. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  35. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  36. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  37. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  38. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  39. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  40. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  41. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  42. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  43. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  44. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  45. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  46. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  47. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  48. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  49. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  50. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  51. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  52. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  53. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  54. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  55. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  56. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  57. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  58. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  59. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  60. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  61. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  62. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  63. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  64. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  65. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  66. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  67. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  68. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  69. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  70. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  71. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  72. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  73. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  74. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  75. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  76. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  77. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  78. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  79. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  80. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  81. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  82. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  83. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  84. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  85. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  86. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  87. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  88. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  89. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  90. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  91. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  92. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  93. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  94. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  95. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  96. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  97. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  98. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  99. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  100. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  101. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  102. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  103. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  104. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  105. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  106. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  107. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  108. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  109. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  110. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  111. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  112. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  113. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  114. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  115. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  116. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  117. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  118. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  119. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  120. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  121. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  122. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  123. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  124. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  125. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  126. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  127. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  128. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  129. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  130. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  131. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  132. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  133. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  134. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  135. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  136. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  137. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  138. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  139. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  140. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  141. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  142. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  143. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  144. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  145. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  146. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  147. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  148. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  149. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  150. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  151. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  152. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  153. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  154. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  155. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  156. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  157. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  158. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  159. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  160. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  161. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  162. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  163. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  164. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  165. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  166. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  167. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  168. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  169. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  170. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  171. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  172. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  173. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  174. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  175. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  176. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  177. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  178. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  179. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  180. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  181. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  182. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  183. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  184. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  185. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  186. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  187. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  188. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  189. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  190. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  191. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  192. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  193. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  194. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  195. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  196. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  197. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  198. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  199. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  200. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  201. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  202. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  203. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  204. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  205. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  206. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  207. package/scripts/budget-audit.mjs +0 -135
  208. package/scripts/release.mjs +0 -446
  209. package/scripts/release_helper.mjs +0 -309
  210. package/scripts/run-perf-gate.mjs +0 -40
  211. 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.