@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,367 +0,0 @@
1
- # Issue #217 — Precise Host Identity Corroboration Implementation Plan
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** On platforms where `captureStartToken()` is `null` (Windows, macOS), refine an `unknown` host-role observation with deterministic evidence — role-specific command-line match, falling back to process creation time vs `*SpawnedAt` — so a reused pid no longer strands a `stopping` host, and `recovery_pending` is never silent.
6
-
7
- **Architecture:** The decision is a pure function in `src/core/host-coordination.mjs`. A new I/O shell `src/core/process-identity-probe.mjs` runs one async `execFile` per pid (Windows CIM JSON, macOS `ps`) behind injectable seams. `service.mjs` keeps all wiring: `observeHostRole` becomes async, receives `{ sessionFile, memo }`, and only probes when the fast token path returns `unknown`. Diagnostics use a new `appendDiagnosticOnce`.
8
-
9
- **Tech Stack:** Node ESM `.mjs`, `node:test` + `assert/strict`, `node:child_process.execFile` (async, `windowsHide: true`), PowerShell `Get-CimInstance Win32_Process`, POSIX `ps -p <pid> -o etime=,command=`.
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md`
12
-
13
- **Worktree:** `C:/Users/27499/proj/opensource/pi-agent-board/.pi/worktrees/issue-217-windows-pid-reuse-stopping` — every path below is relative to it; never edit the main checkout.
14
-
15
- ## Global Constraints
16
-
17
- - Tabs for indentation; English comments; conventional commit subjects (`(#217)` lands on the PR, not on local commits).
18
- - `host-coordination.mjs` stays pure: no fs/net/process I/O, no `resolveTestMs` import.
19
- - `probe == null` MUST return `unknown` — never `foreign`. This is the #70 contract.
20
- - Role-specific fragment: child → `meta.sessionFile`, runner → host `configPath`. Never use `sessionFile` for the runner.
21
- - Do not change `processIdentityState`, `captureStartToken` (all copies), lock identity, or `serviceIdentity()`.
22
- - `recoverHost(viewId, expectedInstanceId, sessionFile?)` — third parameter optional so existing two-arg calls keep working.
23
- - Probe memo is created by the caller and shared across the loop and the final re-check; the resolver stopping branch creates its own local memo.
24
- - Every spawn/exec uses `windowsHide: true` and a `timeout`.
25
- - `git add <explicit files>` only.
26
- - Gate: `npm run typecheck` + the listed test files; `npm run verify` if the local node-pty environment allows it (known Windows `AttachConsole failed` limitation).
27
-
28
- ## Review Focus
29
-
30
- 1. **Process dies between `isAlive` and the probe** — `probe == null` must stay `unknown` for that round; the next poll's `defaultObserveProcess` classifies `dead`. → A8 (Task 3).
31
- 2. **Path prefix trap** (`session.jsonl` inside `session-2.jsonl`, `view_abc` inside `view_abcd`) — matching must be boundary-aware, not a raw `includes`. → A1 (Task 1).
32
- 3. **Mixed slash/case/quoting in command lines** — Windows paths arrive as `C:\…` inside `"…"`; comparison normalizes slashes and case. → A1 (Task 1).
33
- 4. **Command line unreadable (permission / system process)** — fall back to the creation-time window; a missing `spawnedAt` yields `unknown`. → A2 (Task 1).
34
- 5. **Clock step between spawn and recovery** — creation-time fallback uses a 5 s tolerance and only applies when the command-line signal is unavailable. → A2/A15 (Task 1).
35
-
36
- ---
37
-
38
- ### Task 1: Pure decision functions + tolerance knob
39
-
40
- **Files:**
41
- - Modify: `src/core/host-coordination.mjs` (append after `processIdentityState`)
42
- - Modify: `src/runtime/service.mjs` (add the constant + reader next to `resolveHostRecoveryPollMs` at :90)
43
- - Test: `test/host-coordination.test.mjs`, `test/test-knobs.test.mjs`
44
-
45
- **Interfaces:**
46
- - Produces:
47
- - `export function commandLineMentions(commandLine: string, fragment: string): boolean`
48
- - `export function classifyUnverifiableProcess({ spawnedAt, probe, expectedCommandFragment, toleranceMs }): "owned" | "foreign" | "unknown"`
49
- - `export const HOST_IDENTITY_CREATION_TOLERANCE_MS = 5_000` (in `service.mjs`)
50
- - `export function resolveIdentityCreationToleranceMs(env = process.env): number` (in `service.mjs`)
51
- - `probe` shape: `{ creationTimeMs: number|null, commandLine: string|null } | null`.
52
-
53
- - [ ] **Step 1: Write the failing truth-table tests**
54
-
55
- In `test/host-coordination.test.mjs`, add:
56
-
57
- ```js
58
- test("commandLineMentions normalizes slashes/case and respects path boundaries", () => {
59
- assert.equal(commandLineMentions('node C:\\x\\cli.js --session "C:\\a b\\s.jsonl"', "C:/a b/s.jsonl"), true);
60
- assert.equal(commandLineMentions("NODE --session c:\\A\\S.JSONL", "C:\\a\\s.jsonl"), true);
61
- // prefix trap: view_abc must not match view_abcd
62
- assert.equal(commandLineMentions("node x --session C:\\v\\view_abcd.jsonl", "C:\\v\\view_abc.jsonl"), false);
63
- assert.equal(commandLineMentions("node x --session C:\\v\\session-2.jsonl", "C:\\v\\session.jsonl"), false);
64
- assert.equal(commandLineMentions("", "C:\\v\\s.jsonl"), false);
65
- });
66
-
67
- test("classifyUnverifiableProcess truth table", () => {
68
- const probe = (creationTimeMs, commandLine) => ({ creationTimeMs, commandLine });
69
- const base = { spawnedAt: 1_000_000, expectedCommandFragment: "C:\\v\\s.jsonl", toleranceMs: 5_000 };
70
- assert.equal(classifyUnverifiableProcess({ ...base, probe: null }), "unknown");
71
- assert.equal(classifyUnverifiableProcess({ ...base, probe: probe(9_999_999, "node --session C:\\v\\s.jsonl") }), "owned");
72
- assert.equal(classifyUnverifiableProcess({ ...base, probe: probe(9_999_999, "node other.js") }), "foreign");
73
- assert.equal(classifyUnverifiableProcess({ ...base, probe: probe(1_002_000, null) }), "owned");
74
- assert.equal(classifyUnverifiableProcess({ ...base, probe: probe(900_000, null) }), "foreign");
75
- assert.equal(classifyUnverifiableProcess({ ...base, probe: probe(1_020_000, null) }), "foreign");
76
- // command line wins over a null spawnedAt
77
- assert.equal(classifyUnverifiableProcess({ ...base, spawnedAt: null, probe: probe(5, "node --session C:\\v\\s.jsonl") }), "owned");
78
- // no signal at all
79
- assert.equal(classifyUnverifiableProcess({ ...base, spawnedAt: null, probe: probe(1_000_000, null) }), "unknown");
80
- assert.equal(classifyUnverifiableProcess({ ...base, expectedCommandFragment: null, probe: probe(1_000_000, "node x") }), "owned");
81
- });
82
- ```
83
-
84
- In `test/test-knobs.test.mjs`, extend both reader tests:
85
-
86
- ```js
87
- assert.equal(HOST_IDENTITY_CREATION_TOLERANCE_MS, 5_000);
88
- assert.equal(resolveIdentityCreationToleranceMs({}), 5_000);
89
- assert.equal(resolveIdentityCreationToleranceMs({ AGENT_BOARD_TEST_IDENTITY_CREATION_TOLERANCE_MS: "25" }), 25);
90
- ```
91
-
92
- - [ ] **Step 2: Run to verify they fail**
93
-
94
- Run: `node --test test/host-coordination.test.mjs test/test-knobs.test.mjs`
95
- Expected: FAIL — `commandLineMentions is not a function` / import error for the new reader.
96
-
97
- - [ ] **Step 3: Implement the pure functions**
98
-
99
- `host-coordination.mjs`: `commandLineMentions` normalizes `\`→`/` and lowercases both sides, then scans every occurrence and accepts only when both neighbours are start/end or whitespace/quote (boundary-aware). `classifyUnverifiableProcess` follows the spec D1 pseudo-code exactly: `probe == null → unknown`; command line + fragment → `owned`/`foreign`; else creation window `|creation - spawnedAt| <= tolerance → owned`, otherwise `foreign`; missing `spawnedAt`/`creation` → `unknown`.
100
-
101
- `service.mjs`: add the constant and reader (uses `resolveTestMs`) beside the existing readers.
102
-
103
- - [ ] **Step 4: Run to verify they pass**
104
-
105
- Run: `node --test test/host-coordination.test.mjs test/test-knobs.test.mjs`
106
- Expected: PASS.
107
-
108
- - [ ] **Step 5: Commit**
109
-
110
- ```bash
111
- git add src/core/host-coordination.mjs src/runtime/service.mjs test/host-coordination.test.mjs test/test-knobs.test.mjs
112
- git commit -m "feat(core): add identity corroboration pure functions and tolerance knob (issue #217)"
113
- ```
114
-
115
- **Acceptance:** A1, A2, A15.
116
-
117
- ---
118
-
119
- ### Task 2: Process identity probe module
120
-
121
- **Files:**
122
- - Create: `src/core/process-identity-probe.mjs`
123
- - Create: `test/process-identity-probe.test.mjs`
124
-
125
- **Interfaces:**
126
- - Produces:
127
- - `export const PROBE_TIMEOUT_MS = 3_000`
128
- - `export function parseUnixMillis(text: string): number|null`
129
- - `export function parseProbeJson(text: string): {creationTimeMs:number|null, commandLine:string|null}|null`
130
- - `export function parsePsElapsedMs(text: string): number|null`
131
- - `export function parsePsProbeLine(text: string, nowMs: number): {creationTimeMs:number|null, commandLine:string|null}|null`
132
- - `export function resolveProbeCommand(platform: string, pid: number): {command:string, args:string[]}|null`
133
- - `export async function readProcessIdentityProbe(pid, { platform = process.platform, execFileFn = execFile, timeoutMs = PROBE_TIMEOUT_MS, nowMs = Date.now } = {}): Promise<{creationTimeMs:number|null, commandLine:string|null}|null>`
134
- - Consumes nothing from Task 1.
135
-
136
- - [ ] **Step 1: Write the failing parser/command tests**
137
-
138
- In `test/process-identity-probe.test.mjs`:
139
-
140
- ```js
141
- test("parseUnixMillis accepts positive integers only", () => {
142
- assert.equal(parseUnixMillis("1792718500139"), 1_792_718_500_139);
143
- assert.equal(parseUnixMillis(""), null);
144
- assert.equal(parseUnixMillis("abc"), null);
145
- assert.equal(parseUnixMillis("-5"), null);
146
- });
147
-
148
- test("parseProbeJson parses the CIM object and rejects junk", () => {
149
- assert.deepEqual(parseProbeJson('{"creation":1792718500139,"cmd":"node x"}'),
150
- { creationTimeMs: 1_792_718_500_139, commandLine: "node x" });
151
- assert.deepEqual(parseProbeJson('{"creation":null,"cmd":null}'), { creationTimeMs: null, commandLine: null });
152
- assert.equal(parseProbeJson("not json"), null);
153
- });
154
-
155
- test("parsePsElapsedMs handles every etime shape", () => {
156
- assert.equal(parsePsElapsedMs("05:03"), 303_000);
157
- assert.equal(parsePsElapsedMs("01:02:03"), 3_723_000);
158
- assert.equal(parsePsElapsedMs("2-01:02:03"), 176_523_000);
159
- assert.equal(parsePsElapsedMs("nope"), null);
160
- });
161
-
162
- test("parsePsProbeLine splits elapsed from the command", () => {
163
- assert.deepEqual(parsePsProbeLine("05:03 node /x --session y z", 1_000_000),
164
- { creationTimeMs: 697_000, commandLine: "node /x --session y z" });
165
- });
166
-
167
- test("resolveProbeCommand is platform- and pid-aware", () => {
168
- assert.match(resolveProbeCommand("win32", 123).args.at(-1), /ProcessId=123/);
169
- assert.deepEqual(resolveProbeCommand("darwin", 123), { command: "ps", args: ["-p", "123", "-o", "etime=,command="] });
170
- assert.equal(resolveProbeCommand("linux", 123), null);
171
- assert.equal(resolveProbeCommand("win32", 0), null);
172
- assert.equal(resolveProbeCommand("win32", 1.5), null);
173
- });
174
- ```
175
-
176
- - [ ] **Step 2: Run to verify they fail**
177
-
178
- Run: `node --test test/process-identity-probe.test.mjs`
179
- Expected: FAIL — module not found.
180
-
181
- - [ ] **Step 3: Implement the parsers and command resolver**
182
-
183
- Windows command: `powershell.exe -NoProfile -NonInteractive -Command "$p = Get-CimInstance Win32_Process -Filter \"ProcessId=<pid>\" -ErrorAction SilentlyContinue; if ($p) { [pscustomobject]@{creation=[long]([datetimeoffset]$p.CreationDate).ToUnixTimeMilliseconds(); cmd=$p.CommandLine} | ConvertTo-Json -Compress }"`. macOS: `ps -p <pid> -o etime=,command=`. Other platforms: `null`.
184
-
185
- - [ ] **Step 4: Write the failing execution-layer tests**
186
-
187
- ```js
188
- test("readProcessIdentityProbe parses stdout and honors exec options", async () => {
189
- const calls = [];
190
- const execFileFn = (cmd, args, opts, cb) => {
191
- calls.push({ cmd, args, opts });
192
- cb(null, '{"creation":1792718500139,"cmd":"node x --session y"}\n', "");
193
- };
194
- const probe = await readProcessIdentityProbe(123, { platform: "win32", execFileFn });
195
- assert.deepEqual(probe, { creationTimeMs: 1_792_718_500_139, commandLine: "node x --session y" });
196
- assert.equal(calls[0].opts.windowsHide, true);
197
- assert.equal(calls[0].opts.timeout, 3_000);
198
- });
199
-
200
- test("readProcessIdentityProbe returns null on failure/empty output", async () => { /* non-zero exit, timeout, empty stdout → null */ });
201
- ```
202
-
203
- - [ ] **Step 5: Run to verify they fail, then implement the I/O shell**
204
-
205
- Run: `node --test test/process-identity-probe.test.mjs` → FAIL.
206
- Implement `readProcessIdentityProbe` with `execFile(command, args, { timeout, windowsHide: true, maxBuffer: 64 * 1024 }, cb)` wrapped in a promise; non-zero exit / error / empty trimmed stdout → `null`.
207
-
208
- - [ ] **Step 6: Run to verify they pass + commit**
209
-
210
- Run: `node --test test/process-identity-probe.test.mjs` → PASS.
211
-
212
- ```bash
213
- git add src/core/process-identity-probe.mjs test/process-identity-probe.test.mjs
214
- git commit -m "feat(core): add cross-platform process identity probe (issue #217)"
215
- ```
216
-
217
- **Acceptance:** A3, A4, A5.
218
-
219
- ---
220
-
221
- ### Task 3: Wire the probe into recovery
222
-
223
- **Files:**
224
- - Modify: `src/runtime/service.mjs` (`createService` opts at :100-160; `observeHostRole` :961; `recoverHost` :982-1050; resolver stopping branch :1304-1318; final re-check :1046)
225
- - Test: `test/host-recovery.test.mjs`, `test/host-resolver.test.mjs`
226
-
227
- **Interfaces:**
228
- - Consumes: `classifyUnverifiableProcess`, `resolveIdentityCreationToleranceMs` (Task 1); `readProcessIdentityProbe` (Task 2).
229
- - Produces: `createService({ probeProcessIdentity })` injection point; `observeHostRole(host, role, { sessionFile, memo })` async.
230
-
231
- - [ ] **Step 1: Update existing unknown→pending tests to inject a null probe**
232
-
233
- In `test/host-recovery.test.mjs:179` and `test/host-resolver.test.mjs:350`, add `probeProcessIdentity: async () => null` to the `service(...)` overrides. Run both files; the existing assertions must still pass (they will fail after Step 3 without this).
234
-
235
- - [ ] **Step 2: Write the failing recovery tests**
236
-
237
- Add to `test/host-recovery.test.mjs`:
238
-
239
- ```js
240
- test("recoverHost releases an unverifiable reused pid when the command line is foreign", async () => {
241
- // host record: stopping, runnerIdentity/childIdentity startToken null, runnerSpawnedAt/childSpawnedAt set
242
- // observeProcess: () => "unknown"; probeProcessIdentity: async () => ({ creationTimeMs: 9, commandLine: "node other.js" })
243
- // expect: res.ok === true, res.recovered === true, signals.length === 0, new instance claimed
244
- });
245
-
246
- test("recoverHost signals an unverifiable owned pid when the command line matches", async () => {
247
- // same record, probe commandLine includes the view's sessionFile
248
- // expect: exactly one SIGTERM to the owned identity, replacement claimed
249
- });
250
-
251
- test("recoverHost probes at most twice per call (shared memo)", async () => {
252
- // probeProcessIdentity counts calls; runner+child both unknown
253
- // expect probeCalls <= 2
254
- });
255
- ```
256
-
257
- Add to `test/host-resolver.test.mjs`:
258
-
259
- ```js
260
- test("resolver advances a stranded stopping host when the child command line is foreign (issue #217)", async () => {
261
- // stopping host, staleStop true, observeProcess unknown, probe foreign, probeHost scripted ["missing","ready"]
262
- // expect result.kind === "pty" (no recovery_pending)
263
- });
264
- ```
265
-
266
- - [ ] **Step 3: Run to verify they fail**
267
-
268
- Run: `node --test test/host-recovery.test.mjs test/host-resolver.test.mjs`
269
- Expected: FAIL — the foreign cases still return `recovery_pending`; memo count unbounded.
270
-
271
- - [ ] **Step 4: Implement the wiring**
272
-
273
- Follow spec D3 exactly:
274
- - `const probeProcessIdentityImpl = opts.probeProcessIdentity ?? readProcessIdentityProbe;`
275
- - `async function observeHostRole(host, role, { sessionFile, memo })` — fast path unchanged; on `unknown` with a pid, `memo.set(pid, await probeProcessIdentityImpl(pid))` once, then `classifyUnverifiableProcess({ spawnedAt, probe, expectedCommandFragment: role === "child" ? sessionFile : host?.configPath ?? null, toleranceMs: resolveIdentityCreationToleranceMs() })`.
276
- - `recoverHost(viewId, expectedInstanceId, sessionFile = loadRow(root, viewId)?.meta.sessionFile ?? null)` creates `const probeMemo = new Map()` and awaits every `observeHostRole(..., { sessionFile, memo: probeMemo })` (loop + final re-check).
277
- - Resolver stopping branch creates its own local memo and passes its existing `sessionFile`; the resolver's `recoverHost` calls also pass `sessionFile`.
278
-
279
- - [ ] **Step 5: Run to verify they pass**
280
-
281
- Run: `node --test test/host-recovery.test.mjs test/host-resolver.test.mjs`
282
- Expected: PASS (including the pre-existing unknown→pending assertions).
283
-
284
- - [ ] **Step 6: Commit**
285
-
286
- ```bash
287
- git add src/runtime/service.mjs test/host-recovery.test.mjs test/host-resolver.test.mjs
288
- git commit -m "fix(host): corroborate unverifiable process identity before stranding recovery (issue #217)"
289
- ```
290
-
291
- **Acceptance:** A6, A7, A8, A9, A11, A13.
292
-
293
- ---
294
-
295
- ### Task 4: Recovery-pending diagnostics
296
-
297
- **Files:**
298
- - Modify: `src/core/diagnostics.mjs` (add `appendDiagnosticOnce`)
299
- - Modify: `src/runtime/service.mjs` (`:1018`, `:1047`, `:1308`)
300
- - Test: `test/diagnostics.test.mjs`, `test/host-recovery.test.mjs`
301
-
302
- **Interfaces:**
303
- - Produces: `export function appendDiagnosticOnce(root, viewId, patch, opts = {}): DiagnosticEvent|null` — `opts.match` (array of `details` keys, default `[]`), `opts.limit` (default 20); returns `null` when a matching recent event exists.
304
-
305
- - [ ] **Step 1: Write the failing unit test**
306
-
307
- ```js
308
- test("appendDiagnosticOnce writes once per match key", () => {
309
- const root = mkdtempSync(join(tmpdir(), "agentview-diag-"));
310
- createView(root, { id: "v1", name: "a", cwd: "/r" });
311
- const patch = { source: "service", level: "warn", code: "host_recovery_pending", details: { instanceId: "i1", role: "child" } };
312
- assert.ok(appendDiagnosticOnce(root, "v1", patch, { match: ["instanceId", "role"] }));
313
- assert.equal(appendDiagnosticOnce(root, "v1", patch, { match: ["instanceId", "role"] }), null);
314
- assert.ok(appendDiagnosticOnce(root, "v1", { ...patch, details: { instanceId: "i2", role: "child" } }, { match: ["instanceId", "role"] }));
315
- });
316
- ```
317
-
318
- - [ ] **Step 2: Run to verify it fails**
319
-
320
- Run: `node --test test/diagnostics.test.mjs` → FAIL (no export).
321
-
322
- - [ ] **Step 3: Implement and wire**
323
-
324
- `appendDiagnosticOnce` reads the tail (default 20) via `readJsonlTail` + `normalizeDiagnostic`, returns `null` on a same-code same-key match, otherwise appends. In `service.mjs`, before the three producing returns, call it with `code: "host_recovery_pending"`, `details: { instanceId, role, observation, probe: probe ? "available" : "unavailable", path: "recover" }` (or `"attach"` at :1308), `{ match: ["instanceId", "role"] }`. Do not add it to the two relay returns (:1315, :1383).
325
-
326
- - [ ] **Step 4: Write the failing integration assertion**
327
-
328
- In `test/host-recovery.test.mjs`, extend the unknown→pending test: assert `readDiagnostics(root, "v1").some((e) => e.code === "host_recovery_pending")`, then call `recoverHost` again and assert the count did not grow.
329
-
330
- - [ ] **Step 5: Run to verify they pass + commit**
331
-
332
- Run: `node --test test/diagnostics.test.mjs test/host-recovery.test.mjs` → PASS.
333
-
334
- ```bash
335
- git add src/core/diagnostics.mjs src/runtime/service.mjs test/diagnostics.test.mjs test/host-recovery.test.mjs
336
- git commit -m "feat(diagnostics): record host recovery pending with dedupe (issue #217)"
337
- ```
338
-
339
- **Acceptance:** A10.
340
-
341
- ---
342
-
343
- ### Task 5: Verification and manual-verification record
344
-
345
- **Files:** none (verification only)
346
-
347
- - [ ] **Step 1: Typecheck**
348
-
349
- Run: `npm run typecheck` → 0 errors.
350
-
351
- - [ ] **Step 2: Run the targeted suites**
352
-
353
- Run: `node --test test/host-recovery.test.mjs test/host-resolver.test.mjs test/host-coordination.test.mjs test/process-identity-probe.test.mjs test/diagnostics.test.mjs test/test-knobs.test.mjs` → all pass.
354
-
355
- - [ ] **Step 3: Attempt the full gate and record the environment verdict**
356
-
357
- Run: `npm run verify`. If it fails only on the known Windows `AttachConsole failed` / node-pty paths (JFox `202609051440498495`), record the failing files and the reason in the PR body; otherwise fix.
358
-
359
- - [ ] **Step 4: Record U1/U2 status honestly**
360
-
361
- Write the U1/U2 results (or `pending` with the exact blocker) into the PR body. Do not claim manual verification without observed evidence.
362
-
363
- - [ ] **Step 5: Commit any verification-only documentation, if needed**
364
-
365
- `git add <explicit files> && git commit -m "test: record issue #217 verification results"` only if a file changed.
366
-
367
- **Acceptance:** A12, A14, U1, U2.