@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,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.