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