@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,450 +0,0 @@
1
- # Monotonic Foreground State Rebuild 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:** Stop a stale `needs_input` projection from resurrecting the "Needs answer" group after a foreground question is answered.
6
-
7
- **Architecture:** A new module-level, pure projection cache stores the freshest status fields with a strictly increasing per-view stamp. `writeForegroundState` stamps and remembers; `syncRowEvent` adopts the snapshot when it is strictly newer than the disk rebuild (`statusFromRow` now carries `updatedAt`).
8
-
9
- **Tech Stack:** Node 22, `node:test` + `assert/strict`, plain ESM `.mjs`, no build step.
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md`
12
-
13
- ## Global Constraints
14
-
15
- - Tabs for indentation; English comments and commit messages.
16
- - Conventional commit subjects; the PR number is appended at merge time.
17
- - Run `npm run verify` before claiming completion (`npm test` alone skips the perf gate and coverage thresholds).
18
- - Do not change the reducer, `foreground-preview-cache.mjs`, the coordinator, or manual-verdict writes.
19
- - Cache keys are view ids; a view id is never reused (ids are generated), so leftover entries are only a memory concern.
20
-
21
- ## Review Focus
22
-
23
- - **Same-millisecond bursts**: start/end of a question inside one millisecond must still order correctly (`nextStamp`, not `Date.now`).
24
- - **Manual verdicts**: a `completed` / `holding` row (`autoState === null`) must never be overwritten by a cached projection.
25
- - **Clone isolation**: mutating `status.pendingQuestions` after `remember`/`adopt` must not change the cache.
26
- - **Other views**: stamps and entries are per view id; one view's events must not order another's.
27
- - **Non-question fields untouched**: `review` / `diagnostics` / `autoState` / `lastVisitedAt` are not copied by adoption.
28
-
29
- ---
30
-
31
- ### Task 1: `foreground-projection-cache` module (A1–A3, A6-unit)
32
-
33
- **Files:**
34
- - Create: `src/core/foreground-projection-cache.mjs`
35
- - Test: `test/foreground-projection-cache.test.mjs` (new file)
36
-
37
- **Interfaces:**
38
- - Produces: `createForegroundProjectionCache()` → `{ nextStamp(viewId), remember(viewId, status, stamp), adopt(viewId, status), forget(viewId), clear(), size() }` and the module-level singleton `foregroundProjectionCache`.
39
-
40
- - [ ] **Step 1: Write the failing tests**
41
-
42
- Create `test/foreground-projection-cache.test.mjs`:
43
-
44
- ```js
45
- import assert from "node:assert/strict";
46
- import { test } from "node:test";
47
- import { createForegroundProjectionCache } from "../src/core/foreground-projection-cache.mjs";
48
-
49
- function status(overrides = {}) {
50
- return {
51
- semanticState: "working",
52
- processState: "alive",
53
- summary: "Working",
54
- currentTool: null,
55
- question: null,
56
- pendingQuestions: [],
57
- error: null,
58
- lastActivityAt: 100,
59
- latestAssistantPreview: "",
60
- lastAgentActivityAt: null,
61
- updatedAt: 100,
62
- ...overrides,
63
- };
64
- }
65
-
66
- test("adopt copies the snapshot only when the cached stamp is strictly newer", () => {
67
- const cache = createForegroundProjectionCache();
68
- cache.remember("v1", status({ semanticState: "working", summary: "Done" }), 200);
69
- const stale = status({ semanticState: "needs_input", question: "Q?", pendingQuestions: [{ toolCallId: "q1", question: "Q?" }] });
70
- assert.equal(cache.adopt("v1", stale), true);
71
- assert.equal(stale.semanticState, "working");
72
- assert.equal(stale.summary, "Done");
73
- assert.equal(stale.question, null);
74
- assert.deepEqual(stale.pendingQuestions, []);
75
- assert.equal(stale.updatedAt, 200);
76
-
77
- const equal = status({ semanticState: "needs_input" });
78
- equal.updatedAt = 200;
79
- assert.equal(cache.adopt("v1", equal), false);
80
- assert.equal(equal.semanticState, "needs_input");
81
-
82
- const newer = status({ semanticState: "completed" });
83
- newer.updatedAt = 300;
84
- assert.equal(cache.adopt("v1", newer), false);
85
- assert.equal(newer.semanticState, "completed");
86
- });
87
-
88
- test("non-whitelisted fields are never copied", () => {
89
- const cache = createForegroundProjectionCache();
90
- cache.remember("v1", status({ summary: "cached" }), 200);
91
- const live = status({ summary: "disk", updatedAt: 100 });
92
- live.review = { ready: true };
93
- live.diagnostics = { code: "keep" };
94
- live.autoState = { state: "keep" };
95
- cache.adopt("v1", live);
96
- assert.equal(live.summary, "cached");
97
- assert.deepEqual(live.review, { ready: true });
98
- assert.deepEqual(live.diagnostics, { code: "keep" });
99
- assert.deepEqual(live.autoState, { state: "keep" });
100
- });
101
-
102
- test("remember and adopt clone nested values in both directions", () => {
103
- const cache = createForegroundProjectionCache();
104
- const source = status({ currentTool: { name: "bash", path: null }, pendingQuestions: [{ toolCallId: "q1", question: "Q?" }] });
105
- cache.remember("v1", source, 200);
106
- source.pendingQuestions[0].question = "mutated";
107
- source.currentTool.name = "mutated";
108
-
109
- const target = status({ updatedAt: 100 });
110
- assert.equal(cache.adopt("v1", target), true);
111
- assert.equal(target.pendingQuestions[0].question, "Q?");
112
- assert.equal(target.currentTool.name, "bash");
113
- target.pendingQuestions[0].question = "mutated again";
114
- const again = status({ updatedAt: 100 });
115
- cache.adopt("v1", again);
116
- assert.equal(again.pendingQuestions[0].question, "Q?");
117
- });
118
-
119
- test("nextStamp is strictly increasing per view even when the clock is frozen", () => {
120
- const cache = createForegroundProjectionCache();
121
- const realNow = Date.now;
122
- Date.now = () => 1_700_000_000_000;
123
- try {
124
- const a1 = cache.nextStamp("v1");
125
- const a2 = cache.nextStamp("v1");
126
- const b1 = cache.nextStamp("v2");
127
- assert.ok(a2 > a1);
128
- assert.equal(a1, 1_700_000_000_000);
129
- assert.equal(b1, 1_700_000_000_000);
130
- assert.ok(a2 === b1 || a2 > b1);
131
- } finally {
132
- Date.now = realNow;
133
- }
134
- });
135
-
136
- test("forget drops the entry", () => {
137
- const cache = createForegroundProjectionCache();
138
- cache.remember("v1", status(), 200);
139
- assert.equal(cache.size(), 1);
140
- assert.equal(cache.forget("v1"), true);
141
- assert.equal(cache.size(), 0);
142
- const stale = status({ updatedAt: 100 });
143
- assert.equal(cache.adopt("v1", stale), false);
144
- });
145
- ```
146
-
147
- - [ ] **Step 2: Run the tests to verify they fail**
148
-
149
- Run: `node --test test/foreground-projection-cache.test.mjs`
150
- Expected: FAIL — `Cannot find module '../src/core/foreground-projection-cache.mjs'`.
151
-
152
- - [ ] **Step 3: Implement the module**
153
-
154
- Create `src/core/foreground-projection-cache.mjs`:
155
-
156
- ```js
157
- /**
158
- * Process-local read-your-writes cache for foreground status projections
159
- * (issue #225).
160
- *
161
- * `syncRowEvent` rebuilds its status from `state.json` on every event, while
162
- * `writeForegroundState` is fire-and-forget: two events can have two
163
- * `sync_foreground` commands in flight, and an older projection can materialize
164
- * last. Because the rebuilt status carries no freshness signal, the next event
165
- * reduces onto the stale state and writes it back — a cleared question comes
166
- * back as `needs_input` until a terminal event lands.
167
- *
168
- * This cache stores the freshest whitelisted status fields with a strictly
169
- * increasing per-view stamp (`nextStamp`), so a disk rebuild that is older than
170
- * the last write is corrected before the new event is reduced.
171
- *
172
- * Module-level by necessity: `serviceFor()` creates a new service instance per
173
- * call (src/index.ts), so a per-instance cache would be discarded between
174
- * events.
175
- */
176
-
177
- /** Fields a cached snapshot may restore on a stale rebuild. */
178
- const CACHED_FIELDS = [
179
- "semanticState",
180
- "processState",
181
- "summary",
182
- "currentTool",
183
- "question",
184
- "pendingQuestions",
185
- "error",
186
- "lastActivityAt",
187
- "latestAssistantPreview",
188
- "lastAgentActivityAt",
189
- ];
190
-
191
- /** @param {any} value */
192
- function cloneValue(value) {
193
- if (value === null || typeof value !== "object") return value;
194
- return JSON.parse(JSON.stringify(value));
195
- }
196
-
197
- /** @param {any} status */
198
- function snapshot(status) {
199
- /** @type {Record<string, unknown>} */
200
- const fields = {};
201
- for (const key of CACHED_FIELDS) fields[key] = cloneValue(status?.[key]);
202
- return fields;
203
- }
204
-
205
- export function createForegroundProjectionCache() {
206
- /** @type {Map<string, { stamp: number, fields: Record<string, unknown> }>} */
207
- const known = new Map();
208
- /** @type {Map<string, number>} */
209
- const stamps = new Map();
210
-
211
- function nextStamp(viewId) {
212
- const stamp = Math.max(Date.now(), (stamps.get(viewId) ?? 0) + 1);
213
- stamps.set(viewId, stamp);
214
- return stamp;
215
- }
216
-
217
- function remember(viewId, status, stamp) {
218
- if (!viewId || !status || typeof stamp !== "number" || !Number.isFinite(stamp)) return;
219
- known.set(viewId, { stamp, fields: snapshot(status) });
220
- }
221
-
222
- function adopt(viewId, status) {
223
- if (!viewId || !status) return false;
224
- const entry = known.get(viewId);
225
- if (!entry) return false;
226
- const at = typeof status.updatedAt === "number" ? status.updatedAt : 0;
227
- if (entry.stamp <= at) return false;
228
- for (const key of CACHED_FIELDS) status[key] = cloneValue(entry.fields[key]);
229
- status.updatedAt = entry.stamp;
230
- return true;
231
- }
232
-
233
- function forget(viewId) {
234
- stamps.delete(viewId);
235
- return known.delete(viewId);
236
- }
237
-
238
- function clear() {
239
- known.clear();
240
- stamps.clear();
241
- }
242
-
243
- function size() {
244
- return known.size;
245
- }
246
-
247
- return { nextStamp, remember, adopt, forget, clear, size };
248
- }
249
-
250
- /** Shared cache used by the runtime service (module-level: see module doc). */
251
- export const foregroundProjectionCache = createForegroundProjectionCache();
252
- ```
253
-
254
- - [ ] **Step 4: Run the tests to verify they pass**
255
-
256
- Run: `node --test test/foreground-projection-cache.test.mjs`
257
- Expected: PASS (5 tests).
258
-
259
- - [ ] **Step 5: Commit**
260
-
261
- ```bash
262
- git add src/core/foreground-projection-cache.mjs test/foreground-projection-cache.test.mjs
263
- git commit -m "feat: foreground projection cache with monotonic per-view stamps (#225)"
264
- ```
265
-
266
- ---
267
-
268
- ### Task 2: Wire the cache into the service (A4–A7, A8)
269
-
270
- **Files:**
271
- - Modify: `src/runtime/service.mjs` (`statusFromRow` :814, `writeForegroundState` :854, `syncRowEvent` :1540, eviction `:775` and `:2131`)
272
- - Modify: `src/core/types.mjs` (`RunStatus` typedef)
273
- - Test: `test/service.test.mjs` (append after the existing foreground-sync tests)
274
-
275
- **Interfaces:**
276
- - Consumes: `foregroundProjectionCache` from Task 1 (`nextStamp` / `remember` / `adopt` / `forget`).
277
- - Produces: no public API change; `RunStatus` gains optional `updatedAt`.
278
-
279
- - [ ] **Step 1: Write the failing integration tests**
280
-
281
- Append to `test/service.test.mjs`:
282
-
283
- ```js
284
- test("a stale needs_input snapshot cannot resurrect a cleared question (issue #225)", async () => {
285
- const root = freshRoot();
286
- const prevCoordinator = setEnv("AGENT_BOARD_COORDINATOR", "off");
287
- const realNow = Date.now;
288
- Date.now = () => 1_700_000_000_000;
289
- try {
290
- createView(root, { id: "v1", name: "a", cwd: "/r" });
291
- const svc = service(root);
292
- assert.equal(await svc.syncHostedEvent("v1", {
293
- type: "tool_execution_start",
294
- toolCallId: "q1",
295
- toolName: "ask_questions",
296
- args: { questions: [{ question: "Choose a mode?" }] },
297
- }), true);
298
- const stale = readState(root, "v1");
299
- assert.equal(stale.semanticState, "needs_input");
300
-
301
- assert.equal(await svc.syncHostedEvent("v1", { type: "tool_execution_end", toolCallId: "q1", toolName: "ask_questions", isError: false }), true);
302
- // Simulate the older projection materializing after the newer one.
303
- writeState(root, stale);
304
-
305
- assert.equal(await svc.syncHostedEvent("v1", { type: "tool_execution_start", toolCallId: "t1", toolName: "bash", args: { command: "ls" } }), true);
306
- const next = readState(root, "v1");
307
- assert.equal(next.semanticState, "working");
308
- assert.equal(next.needsInput, false);
309
- assert.deepEqual(next.pendingQuestions, []);
310
- } finally {
311
- Date.now = realNow;
312
- setEnv("AGENT_BOARD_COORDINATOR", prevCoordinator);
313
- rmSync(root, { recursive: true, force: true });
314
- }
315
- });
316
-
317
- test("a manual verdict survives a foreground tool event (issue #225)", async () => {
318
- const root = freshRoot();
319
- try {
320
- createView(root, { id: "v1", name: "a", cwd: "/r" });
321
- const done = readState(root, "v1");
322
- done.semanticState = "completed";
323
- done.processState = "exited";
324
- done.autoState = null;
325
- done.summary = "Done";
326
- done.updatedAt = Date.now() - 1000;
327
- writeState(root, done);
328
-
329
- const svc = service(root, { sendStateCommand: async () => ({ status: "rejected", reason: "manual_fence" }) });
330
- await svc.syncHostedEvent("v1", { type: "tool_execution_start", toolCallId: "t1", toolName: "bash", args: { command: "ls" } });
331
- const next = readState(root, "v1");
332
- assert.equal(next.semanticState, "completed");
333
- assert.equal(next.autoState, null);
334
- } finally {
335
- rmSync(root, { recursive: true, force: true });
336
- }
337
- });
338
- ```
339
-
340
- - [ ] **Step 2: Run them to verify the first fails**
341
-
342
- Run: `node --test --test-name-pattern="issue #225" test/service.test.mjs`
343
- Expected: the stale-snapshot test FAILS with `actual: 'needs_input'`, `expected: 'working'` (pre-fix the disk rebuild wins); the manual-verdict test passes (it is a regression guard).
344
-
345
- - [ ] **Step 3: Add `updatedAt` to `RunStatus`**
346
-
347
- In `src/core/types.mjs`, inside the `RunStatus` typedef, after `lastEventAt`:
348
-
349
- ```js
350
- * @property {number} [updatedAt] In-memory foreground rebuild freshness (issue #225);
351
- * not persisted on run status files.
352
- ```
353
-
354
- - [ ] **Step 4: Wire `statusFromRow`**
355
-
356
- In `statusFromRow`, after `lastActivityAt: s.lastActivityAt,` add:
357
-
358
- ```js
359
- updatedAt: typeof s.updatedAt === "number" ? s.updatedAt : 0,
360
- ```
361
-
362
- - [ ] **Step 5: Stamp and remember in `writeForegroundState`**
363
-
364
- Replace the first two statements of `writeForegroundState`:
365
-
366
- ```js
367
- async function writeForegroundState(row, status, opts = {}) {
368
- const projected = projectViewState(status, Date.now(), readState(root, row.meta.id) ?? row.state ?? null);
369
- ```
370
-
371
- with:
372
-
373
- ```js
374
- async function writeForegroundState(row, status, opts = {}) {
375
- // Issue #225: a strictly increasing stamp makes the background rebuild able
376
- // to tell a stale disk snapshot from this write, even within one millisecond.
377
- const stamp = foregroundProjectionCache.nextStamp(row.meta.id);
378
- const projected = projectViewState(status, stamp, readState(root, row.meta.id) ?? row.state ?? null);
379
- foregroundProjectionCache.remember(row.meta.id, status, stamp);
380
- ```
381
-
382
- - [ ] **Step 6: Adopt in `syncRowEvent`**
383
-
384
- After `foregroundPreviewCache.backfill(row.meta.id, status);` add:
385
-
386
- ```js
387
- // Issue #225: correct a stale disk rebuild before the new event is reduced.
388
- foregroundProjectionCache.adopt(row.meta.id, status);
389
- ```
390
-
391
- - [ ] **Step 7: Evict alongside the preview cache**
392
-
393
- At `archiveView` (next to `foregroundPreviewCache.forget(viewId);`) and at `archiveByState` (next to `foregroundPreviewCache.forget(row.meta.id);`) add the matching call:
394
-
395
- ```js
396
- foregroundProjectionCache.forget(viewId);
397
- ```
398
-
399
- ```js
400
- foregroundProjectionCache.forget(row.meta.id);
401
- ```
402
-
403
- - [ ] **Step 8: Add the import**
404
-
405
- At the top of `src/runtime/service.mjs`, next to the preview-cache import:
406
-
407
- ```js
408
- import { foregroundProjectionCache } from "../core/foreground-projection-cache.mjs";
409
- ```
410
-
411
- - [ ] **Step 9: Run the tests**
412
-
413
- Run: `node --test --test-name-pattern="issue #225" test/service.test.mjs`
414
- Expected: PASS (both).
415
-
416
- Then run: `npm test`
417
- Expected: PASS — all existing foreground-sync, preview-cache, archive, and reducer tests stay green. (A7)
418
-
419
- - [ ] **Step 10: Run the full gate**
420
-
421
- Run: `npm run verify`
422
- Expected: PASS — typecheck, perf gate, tests, coverage thresholds (85/80/70), pack dry-run. (A8)
423
-
424
- - [ ] **Step 11: Commit**
425
-
426
- ```bash
427
- git add src/runtime/service.mjs src/core/types.mjs test/service.test.mjs
428
- git commit -m "fix: keep foreground rows monotonic across out-of-order state writes (#225)"
429
- ```
430
-
431
- ---
432
-
433
- ### Task 3: Post-merge live verification (manual, not a code task)
434
-
435
- **Files:** none.
436
-
437
- - [ ] **Step 1: Update the board clone the running process loads**
438
-
439
- ```bash
440
- cd ~/.pi/agent/git/github.com/zhuxixi/pi-agent-board
441
- git checkout -- package-lock.json
442
- git pull
443
- git log -1 --oneline
444
- ```
445
-
446
- - [ ] **Step 2: Restart Pi / the board service**, open the dashboard.
447
-
448
- - [ ] **Step 3: Trigger a question** (`ask_user_question` or `question`), answer it, and keep watching.
449
-
450
- Expected: the row returns to **RUNNING** within about a second, not at turn end. (U1)