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