@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,111 +0,0 @@
1
- # Spec:attach jiggle 协议改造 — shrink-and-hold(issue #25)
2
-
3
- > **历史设计,部分内容已被 issue #42 superseded。** 当前实现保留 G1–G5,
4
- > 并在首帧快速 restore 后增加 G6 post-restore verify:900ms 内没有 clear
5
- > 时重新 shrink;attach detach 只使用空输入时的 `←`,`ctrl+]` 透传给 Pi。
6
-
7
- ## 日期
8
- 2026-08-22
9
-
10
- ## 问题
11
-
12
- #10 修复后冷启动 attach 自愈延迟 10-25s(re-arm 生效但后续 ±1/200ms 脉冲对被启动风暴合并成净零尺寸变化,子端渲染时看不到宽度变化 → 不触发 fullRender 全清)。目标:冷启动 ≤3s 自愈,热 attach ≤300ms 不劣化,且不破坏任何现有功能。
13
-
14
- ## 协议设计(核心状态机)
15
-
16
- **旧协议**:connect → shrink(−1) → [200ms] → restore → 每次重试重复脉冲对 → 见全清停。
17
- **新协议**:
18
-
19
- ```
20
- connect:
21
- 1. sendResize(W×H) # 原始尺寸(保持现有语义)
22
- 2. shrink 到 (W−1)×(H−1) 并 HOLD # "armed":与子端基线之间存在待兑现的宽度差
23
- 3. 启动安全计时器(见"守卫 G1/G2")
24
-
25
- 子端输出处理(沿用 feed):
26
- - 见 \x1b[2J(全清)→ restore(W×H),链停 # 子端渲染时读到窄宽 → fullRender → 我们恢复
27
- - 见首个 \x1b[?2026h(re-arm)→ restore(W×H) 并停在那里
28
- # 冷启动死锁解法:TUI 若在 shrink 后才启动,首帧把窄宽当基线;
29
- # restore 让"正在渲染的子端"下一帧看到宽 1 列 → 必然 fullRender → 走上面的全清分支
30
-
31
- 守卫:
32
- G1 无帧兜底: connect 后 6s 内无任何 TUI 帧 → restore(W×H)(没有渲染器可触发,继续缩无意义)
33
- G2 预算兜底: 全部退避预算(56.12s)走完无全清 → restore(W×H)(非 pi 子进程/死 session)
34
- G3 close/detach: close() 时若 hold 生效 → 先 restore 再断(socket 尚可用)
35
- G4 真实 resize: resizeIfNeeded(新宽) 进入时若 hold 生效 → 取消 hold 状态、按新尺寸 sendResize、
36
- 之后由下一次 attach 语义重启链(hold 的"原尺寸"取新值)
37
- G5 reconnect: start() 重置前若 hold 生效 → 先 restore(socket 刚连上)
38
- ```
39
-
40
- **为什么合并不再重要**:旧协议的净零来自"缩"与"恢复"互相抵消;新协议在见到全清前根本不存在"恢复",任何一侧(外层 timer / 子端 SIGWINCH / 渲染节流)的合并最多推迟信号到达,不能消除"宽度与基线不同"这个事实。子端只要渲染任何一帧,全清必然发生。
41
-
42
- ## 模块改动
43
-
44
- ### 1. `src/core/pty-attach-jiggle-retry.mjs`(纯逻辑,微调)
45
- - 不变:退避表、`hasTuiFrameStart`/`hasFullClearSequence`、carry=7。
46
- - `JiggleRetryState` 增加 `held: boolean`(是否正缩着)与 `originalCols/originalRows`。由控制器维护,状态机保持零副作用。
47
-
48
- ### 2. `src/core/pty-attach-jiggle-controller.mjs`(协议主体)
49
- - deps 增加:`sendResize(cols, rows)`(发送任意尺寸;sendJiggle 语义被 hold 协议取代,删除或保留为内部组合)。
50
- - `start()`:若前次 hold 生效 → 先 `sendResize(original)`(G5);随后 `sendResize(W,H)`、`sendResize(W−1,H−1)` 置 `held=true`(armed);启动 G1 计时器;预算链照常排(作 G2 计时用,重试回调在 hold 下为 no-op——不发脉冲)。
51
- - `feed()`:见全清 → `restoreIfHeld()` + 停链 + 置 clearDetected;见首帧(re-arm)→ `restoreIfHeld()`(G-rearm),**预算链不重置不重排**(hold 协议下 re-arm 后无需再脉冲;若全清一直不来,由 G1/G2 兜底)。G1 计时器见帧后取消。
52
- - `stop()`:restoreIfHeld 不在此做(组件 close 语义不同——见 G3,由组件在 socket 可用时显式调用 `restoreAndStop()`)。
53
- - 新增 API:`restoreAndStop()`(G3)、`notifyExternalResize(cols, rows)`(G4:取消 hold/计时器、更新 original、可选重启链)。
54
- - 计时器仍全部走注入的 setTimeoutFn/clearTimeoutFn,unref。
55
-
56
- ### 3. `src/ui/pty-attach.ts`(薄胶水)
57
- - `forceChildRedraw` 语义替换:connect 处理器改调 `jiggleRetry.start()`(内含 armed shrink);`JIGGLE_RESTORE_MS` 删除(无脉冲)。
58
- - `resizeIfNeeded`:尺寸变化时调 `jiggleRetry.notifyExternalResize(newCols, newRows)`,再照常 `term.resize + sendResize`。
59
- - `close()`:`jiggleRetry.restoreAndStop()`。
60
- - `checkClearSequence` → `jiggleRetry.feed(data)` 不变。
61
-
62
- ### 4. 测试
63
- - 单测(controller):armed 后见全清→restore+停;见首帧→restore 且不重排;G1 6s 无帧 restore;G2 预算耗尽 restore;close restoreAndStop;notifyExternalResize 取消 hold 并更新原尺寸;重复 start 先 restore 旧 hold;所有 restore 只发一次(幂等)。
64
- - E2E 重写(stub 语义改为 hold 协议):
65
- - 冷启动:stub 延迟 8s 启动 TUI;收到 shrink 不动作;**收到 restore(原尺寸) 且 TUI 已启动** → 发全清。断言全清 ≤3s 内到达(re-arm 后 ~一帧间隔),且 PTY 终态=原尺寸。
66
- - shell 型子进程(永不发全清):断言 G1 兜底 6s 后收到 restore(原尺寸)。
67
- - (可选)hold 中途外部 resize:模拟 notifyExternalResize,断言不发旧尺寸。
68
- - 既有 208 测试适配(controller 单测大改、retry 状态机测试微调)。
69
-
70
- ## 决策表
71
-
72
- | 决策点 | 选择 | 理由 |
73
- |---|---|---|
74
- | hold 的宽度 | cols−1 且 rows−1 | 沿用现有 jiggle 尺寸语义;宽高都变确保 heightChanged 路径也可触发 |
75
- | re-arm 后行为 | 只 restore,不再脉冲 | 子端已在渲染,restore 即待兑现差值;脉冲回归旧脆弱性 |
76
- | G1 时长 | 6s | > 最慢正常 boot 出首帧(实测 ~5.2s)+ 余量;< G2 预算 |
77
- | G2 | 退避表走完(56.12s) | 与 #10 验收窗口一致;hold 下重试回调 no-op,表仅作计时 |
78
- | restore 幂等 | 只发一次 | 防止 close/reconnect/兜底叠加多发 |
79
- | `JIGGLE_RESTORE_MS` | 删除 | 无脉冲对 |
80
-
81
- ## 风险面 → 守卫映射(用户重点确认区)
82
-
83
- | # | 风险面 | 守卫 | 验证 |
84
- |---|---|---|---|
85
- | R1 | 非 pi 子进程/死 session | G1(6s)+G2(56s) restore | E2E shell stub |
86
- | R2 | detach/close 中途 | G3 close 先 restore | 单测 |
87
- | R3 | 真实 resize 打架 | G4 中断+取新值 | 单测 |
88
- | R4 | 子端窄 1 列渲染 | 瞬态,restore 全清重绘 | 人工+E2E 终态尺寸断言 |
89
- | R5 | 本地投影 1 列差 | 流内自洽,无影响 | 人工 |
90
- | R6 | reconnect 残留 | G5 start 先 restore | 单测 |
91
- | R7 | settle 后 UX | 无脉冲(频次低于现状) | 人工 |
92
- | R8 | 测试有效性 | stub 改 hold 语义+兜底用例 | E2E |
93
-
94
- ## 降级
95
-
96
- - 若 pi-tui 改掉 `\x1b[?2026h`:re-arm 不触发,退化为 G1(6s) restore——**比 #10 修复前更好**(6s 内恢复正确尺寸,画面可能仍脏但不卡窄宽)。
97
- - 若全清信号消失:同上走 G1/G2,终态尺寸正确。
98
-
99
- ## 非目标
100
-
101
- - 失同步渲染检测(#11 范围,若 hold 后仍有可见脏窗再评估)。
102
- - runner / pi-tui 侧改动。
103
- - screen.log 重放锚点问题。
104
-
105
- ## 验收标准
106
-
107
- 1. 冷启动 E2E:全清 ≤3s 到达且终态=原尺寸(旧协议同场景 >10s,反证有效)。
108
- 2. shell stub E2E:G1 兜底 6s 后 restore 原尺寸。
109
- 3. 8 个风险面守卫全部有单测/E2E 覆盖。
110
- 4. 全套测试通过、typecheck 干净、CR 收敛。
111
- 5. 人工实机:home 目录冷启动 attach ≤3s 自愈;热 attach 无可感知劣化。
@@ -1,114 +0,0 @@
1
- # Spec: Recognize pi `question`/`questionnaire` tools as pending-question sources
2
-
3
- - **Issue**: zhuxixi/pi-agent-board#26
4
- - **Date**: 2026-08-22
5
- - **Status**: draft — awaiting user confirmation before implementation
6
- - **Type**: bug fix (event reducer + arg extraction), no data-model change
7
-
8
- ## 1. Problem
9
-
10
- A pi session blocked on the built-in `question` (or `questionnaire`) tool shows a
11
- "Question" summary but stays in the **RUNNING** group instead of **NEEDS ANSWER**.
12
-
13
- Observed 2026-08-22 01:26 on view `view_e809b1a3a2` ("jfox moc密度PR审查与监控"):
14
- row summary "Question", `semanticState=working`, `pendingQuestions=[]`.
15
-
16
- ## 2. Root cause
17
-
18
- The reducer only recognizes ONE question-tool name — `ask_questions`
19
- (`src/core/events.mjs` L83/L97). Pi's actual question tools are named
20
- `question` and `questionnaire` (user extensions:
21
- `~/.pi/agent/extensions/question.ts`, `questionnaire.ts`).
22
-
23
- When `tool_execution_start` arrives with `toolName: "question"`:
24
- 1. Name check fails → `else if (pendingQuestions.length === 0)` branch runs →
25
- `semanticState = "working"`, `currentTool.summary = toolSummary("question")` =
26
- `capitalize("question")` = `"Question"` (heuristics.mjs default branch).
27
- 2. `preservePendingQuestion` finds no pending question → no-op.
28
- 3. Result: row grouped by `semanticState` = "working" → RUNNING, summary "Question".
29
-
30
- Correction to issue body: the "design gap B" claim (pending questions don't affect
31
- grouping) is **wrong** — `preservePendingQuestion` already sets
32
- `semanticState = "needs_input"` when `pendingQuestions` is non-empty. The ONLY gap is
33
- tool-name recognition (plus arg-shape extraction). See research/root-cause.md.
34
-
35
- ## 3. Design
36
-
37
- All changes in `src/core/events.mjs` + tests. No store/schema/type changes.
38
-
39
- ### 3.1 Question-tool name set
40
-
41
- ```js
42
- const QUESTION_TOOL_NAMES = new Set(["ask_questions", "question", "questionnaire"]);
43
- ```
44
-
45
- Replace both `name === "ask_questions"` checks (tool_execution_start L83,
46
- tool_execution_end L97) with `QUESTION_TOOL_NAMES.has(name)` /
47
- `QUESTION_TOOL_NAMES.has(event.toolName ?? "")`.
48
-
49
- Rationale: static set matches the existing hardcoded-name precedent; all three names
50
- are in the family of pi/agent question tools. A configurable list is deferred (4.2).
51
-
52
- ### 3.2 `questionFromArgs` — support pi arg shapes
53
-
54
- Current: reads `args.questions[].question` (ask_questions shape).
55
-
56
- Extend to:
57
-
58
- | Tool | Args shape | Extract from |
59
- |---|---|---|
60
- | `ask_questions` | `{ questions: [{ question }] }` | `item.question` |
61
- | `question` (pi) | `{ question: string, options: [...] }` | `args.question` |
62
- | `questionnaire` (pi) | `{ questions: [{ prompt, options }] }` | `item.prompt`, fallback `item.question` |
63
-
64
- Priority in `questions[]` items: `item.question ?? item.prompt`. Keep the
65
- "Answer the pending question" fallback.
66
-
67
- ### 3.3 Downstream flow (already correct — verify, don't change)
68
-
69
- - `tool_execution_start` (interactive, question tool): `upsertPendingQuestion` →
70
- `preservePendingQuestion` → `semanticState = "needs_input"`, `currentTool = null`,
71
- `question = first.question`. Row moves to NEEDS ANSWER group; summary shows the
72
- question text (deriveSummary: needs_input + question).
73
- - `tool_execution_end` (interactive, question tool): `removePendingQuestion`,
74
- `semanticState = "working"`, `question = null` — back to RUNNING after the answer.
75
- - Non-interactive (detached worker) path: `opts.interactive` gate unchanged →
76
- question tools keep the old generic behavior (no pending-question tracking).
77
-
78
- ### 3.4 Tests (`test/events.test.mjs`)
79
-
80
- Mirror the existing `ask_questions` coverage (L109-160):
81
-
82
- 1. `tool_execution_start` with `toolName: "question"` (args `{ question: "Approve?" }`),
83
- interactive → `pendingQuestions = [{ toolCallId, question: "Approve?" }]`,
84
- `semanticState === "needs_input"`, `currentTool === null`, summary = question text.
85
- 2. Same for `toolName: "questionnaire"` with args `{ questions: [{ prompt: "Pick scope" }] }`
86
- → extracted question "Pick scope".
87
- 3. `tool_execution_end` with `toolName: "question"` → pendingQuestions empty,
88
- `semanticState === "working"`.
89
- 4. Detached (no `interactive`) `question` start → unchanged legacy behavior
90
- (`currentTool.name === "question"`, `pendingQuestions = []`).
91
-
92
- ## 4. Non-goals / deferred
93
-
94
- 1. No configurable question-tool list (env/config escape hatch). Add later only if a
95
- harness with a different question-tool name appears. Keeping the set static avoids
96
- new config surface.
97
- 2. No changes to `finalizeRun`, `projectViewState`, `deriveSummary`, or grouping logic.
98
- 3. Non-interactive path behavior unchanged.
99
- 4. No i18n / label changes.
100
-
101
- ## 5. Edge cases
102
-
103
- - Two concurrent question tool calls: keyed by `toolCallId`; first pending question wins
104
- display. Unchanged from `ask_questions` behavior.
105
- - Non-TUI pi question tool returns an error result immediately → start/end pair clears
106
- the pending question within the same cycle; transient needs_input is harmless.
107
- - `agent_start` / `input` events already clear `pendingQuestions` in the foreground
108
- path (service.mjs L524-535) — no interaction.
109
-
110
- ## 6. Verification
111
-
112
- - `npm run verify` (project gate) with the new events tests.
113
- - Manual: start a pi session under the board, have it call the `question` tool →
114
- row shows the question text in NEEDS ANSWER group; answer it → row returns to RUNNING.
@@ -1,80 +0,0 @@
1
- # Spec: Rename state labels — needs_input → "Needs answer", idle → "Needs instructions"
2
-
3
- - **Issue**: zhuxixi/pi-agent-board#15
4
- - **Date**: 2026-08-22
5
- - **Status**: approved (2026-08-22) — implemented on branch issue-15-rename-state-labels
6
- - **Type**: display-only rename, no data-model change
7
-
8
- ## 1. Problem
9
-
10
- Dashboard semantic-state labels mislead. `idle` shows as "In Progress" but means *run ended, nothing asked, not confidently done* — the opposite of active work implied next to "Running". `needs_input` shows as "Needs input" but its distinct user action is *answering an explicit question*. The header collides lexically: needs_input counts read "awaiting input" while idle rows group under "In Progress".
11
-
12
- History (research): upstream `5c67518` renamed idle "Idle" → "In Progress" and introduced the legacy-text normalization mechanism; `99691e1` later made idle the auto-state classifier's "ended but not done" bucket, at which point the label started systematically misleading. Issue #14 (open) will make idle the default terminal bucket, increasing exposure.
13
-
14
- ## 2. Goals
15
-
16
- - Each waiting state's label names the user action it waits for:
17
- - `needs_input` → **"Needs answer"** — agent asked a question; it needs your reply.
18
- - `idle` → **"Needs instructions"** — run ended cleanly; it needs your next directive (follow-up, new task, or mark-done).
19
- - Header needs_input stage reads "needs answer" (compact "answer") — no "awaiting" wording for needs_input, so "awaiting"-family ambiguity disappears.
20
- - Persisted rows display the new labels with zero migration.
21
-
22
- ## 3. Non-goals
23
-
24
- - No changes to `SEMANTIC_STATES` internal names, store schemas (`meta.json`/`state.json`/`status.json`), state transitions, or the auto-state classifier internals — that is issue #14's scope.
25
- - No changes to historical docs (`PRD.md`, `IMPLEMENTATION_PLAN.md`) — they are point-in-time records.
26
- - No i18n layer.
27
-
28
- ## 4. Design
29
-
30
- ### 4.1 Label sources (code changes)
31
-
32
- | File | Change |
33
- |---|---|
34
- | `src/core/types.mjs` | `GROUP_LABELS.needs_input: "Needs input"` → `"Needs answer"`; `GROUP_LABELS.idle: "In Progress"` → `"Needs instructions"` |
35
- | `src/core/derive.mjs` | `fallbackStatusText("needs_input")` → `"Needs answer"`; `fallbackStatusText("idle")` → `"Needs instructions"` |
36
- | `src/runtime/service.mjs` (~L961) | reconciled-host summary `"In Progress"` → `"Needs instructions"` |
37
- | `src/ui/dashboard.ts` (~L926) | placeholder row summary `"In Progress"` → `"Needs instructions"` |
38
- | `src/ui/dashboard.ts` (~L1755) | headerStageSummary needs_input part: `"awaiting input"`/`"awaiting"` → `"needs answer"`/`"answer"` |
39
- | `README.md` (L65) | state list: `Needs answer`, `Needs instructions` |
40
-
41
- Stage headers render via `renderStageHeader` → `label.toUpperCase()`: displays "NEEDS ANSWER" / "NEEDS INSTRUCTIONS".
42
-
43
- ### 4.2 Backward compatibility (reuse the 5c67518 pattern)
44
-
45
- `GENERIC_STATUS_TEXT` in `derive.mjs` is the recognized-legacy set consumed by `normalizeGenericStatusText(state, text)`, which `rowView()` (`rows.mjs`) applies at render time. Extend the sets; never shrink them:
46
-
47
- ```js
48
- needs_input: new Set(["Needs input", "Needs answer"]),
49
- idle: new Set(["Idle", "In Progress", "Needs instructions"]),
50
- ```
51
-
52
- Effect: rows persisted with any historical generic summary ("Idle", "In Progress", "Needs input") auto-display the current label. Non-generic summaries (real question text, error text) pass through untouched. No data migration, no store write.
53
-
54
- ### 4.3 Auto-following surfaces (verify only)
55
-
56
- - Delete-confirm prompt and notices use `GROUP_LABELS[state].toLowerCase()` → "delete 3 needs instructions sessions?" (grammar acceptable).
57
- - Filter aliases (`rows.mjs`) = `[stateName, GROUP_LABELS[state]]` → "needs answer", "idle", "needs instructions" match; "in progress" stops matching (accepted; state name "idle" still matches).
58
- - Peek panel and header counts render derived values only.
59
-
60
- ### 4.4 Tests
61
-
62
- Update string expectations:
63
- - `test/derive.test.mjs:96-97` — fallbackStatusText for both states.
64
- - `test/rows.test.mjs:87` — rowView normalization (legacy "Idle" → "Needs instructions").
65
- - `test/service.test.mjs` (~L334) — summary assertions touching "Needs input".
66
-
67
- Add normalization coverage:
68
- - legacy "In Progress" → "Needs instructions"; legacy "Needs input" → "Needs answer" (render-level, via rowView).
69
- - New fallback values appear for fresh runs.
70
-
71
- ## 5. Error handling / edge cases
72
-
73
- - Old dashboard process + new code reading same store: summaries normalize on read; safe.
74
- - Rows whose summary was manually set to a non-generic string: untouched (pass-through preserved).
75
- - Classifier (`auto-state.mjs`) and steering prompts use internal kind names (`needs_input`/`in_progress`/`done`), not display labels — unaffected.
76
-
77
- ## 6. Verification
78
-
79
- - `npm run verify` (project's standard gate) with updated tests.
80
- - Manual: open dashboard with pre-existing rows carrying old summaries → group headers and row summaries show new labels; header shows "needs answer".
@@ -1,97 +0,0 @@
1
- # Spec: 修复 locks.mjs acquireLock 无眠死循环(issue #33)
2
-
3
- > Draft 状态:待用户确认设计后进 worktree 实现(github-issue-driven 步 4 暂停点)。
4
-
5
- ## 问题
6
-
7
- `src/core/locks.mjs` `acquireLock` 在锁持续不可得时(根目录被删 / 只读 / 锁状态损坏),30s 等待窗过期后退化为零睡眠忙等循环:100% 单核 CPU、事件循环冻死、定时器全灭、进程永远不退出。现网两个 job-runner 僵尸进程分别空转 10.5 天 / 4.4 天(#33 现场证据)。
8
-
9
- ## 根因(两处叠加)
10
-
11
- ```js
12
- while (true) {
13
- try {
14
- mkdirSync(lockPath);
15
- writeFileSync(path.join(lockPath, "owner.json"), ...);
16
- return;
17
- } catch (err) {
18
- // 缺陷 1:睡眠只在初始窗口内生效,窗口一过永不睡眠
19
- if (!isLockStale(lockPath, staleMs) && Date.now() - started < Math.max(250, staleMs)) {
20
- Atomics.wait(...20ms);
21
- continue;
22
- }
23
- // 缺陷 2:releaseLock 静默吞错,循环无条件继续 → 无眠紧循环
24
- releaseLock(lockPath);
25
- }
26
- }
27
- ```
28
-
29
- **触发机制(现场还原)**:teardown 的 `rmSync(root, {recursive})` 与 runner 收尾链在时间上系统性重叠(runner 快速收尾链 ~10-50ms 到达锁 vs 测试 waitFor 轮询 ~50ms + 断言后才 rmSync ~50-150ms)。目录树遍历删掉 `ensureDir` 刚验证过的父目录后,`mkdirSync` 从此永远 ENOENT(**ensureDir 只在循环外跑一次,循环内永不重建父目录**)→ 30s 窗口后零睡眠死循环。另两类等价失败:owner.json 写失败(半成品锁被误判 stale → 删了重建无限循环)、锁目录删不掉(rmSync 失败被吞)。
30
-
31
- ## 设计决策
32
-
33
- ### 决策表
34
-
35
- | # | 决策点 | 选择 | 理由 |
36
- |---|---|---|---|
37
- | D1 | 强夺失败后的行为 | **有界尝试后抛错**(`Error: lock timeout: <path>`) | 锁不可得属环境故障,忙等无意义;抛错让调用层决定降级 |
38
- | D2 | 强夺(窗口后偷锁)语义 | **保留**:窗口过期 → releaseLock → 立即重试一次 | 现有测试「fresh lock 等窗口后强夺」固化此语义,改动会破坏契约 |
39
- | D3 | 重试上限 | 等待窗内无限重试(带睡眠,窗口 = `max(250, staleMs)`,保留现有 floor);窗口后**最多 2 次强夺**(含 owner.json 写失败路径),仍失败即抛 | 覆盖 rm 失败 / mkdir 仍失败 / 写失败三类;有界即无死循环 |
40
- | D4 | 睡眠策略 | 保留 `Atomics.wait(20ms)`;循环内任何 continue 前必有睡眠或已抛错 | 反证 D1 的失败模式,杜绝任何无眠路径 |
41
- | D4b | **循环内自愈**:每次 catch 后重跑 `ensureDir(dirname)`(ensureDir 自身失败计为一次失败尝试) | 现场最高频竞态(teardown rmSync 删掉父目录)从「等窗口后抛错」升级为「瞬时自愈、正常拿锁退出」;有界性不变 |
42
- | D5 | fs 注入 | 仿 `screen-log.mjs` `defaultScreenLogFs` 先例,加 `locksFs` 参数(默认 node:fs) | 现有测试无注入,注入后才能确定性复现「mkdir 永败」等场景 |
43
- | D6 | 队列层错误传播 | follow-up-queue.mjs 5 个入口 try/catch **catch-all**(含 fn 内 writeFollowUpQueue 的 fs 错误,非仅锁错误)→ `{ok:false, error}` | 保持 {ok} 返回值约定,service.mjs 无需改动;已确认 follow-up-queue.test.mjs 无 throw 断言,catch-all 安全 |
44
- | D7 | job-runner 兜底 | `.finally` 链里 `finalizeSteeringIfNeeded` + `drainQueuedFollowUp` 各自 try/catch | 锁层抛错永远不会阻止 `process.exit`——僵尸进程防线最后一道 |
45
- | D8 | 默认 staleMs | 不变(30s) | 现有测试与调用方依赖 |
46
- | D9 | 测试 harness 清理 | 全部 7 处 launchRun 都捕获 pid(现有 4 处丢弃,含出过僵尸的 dash 测试)→ finally 里 TERM → 短等待 → KILL → 再 rmSync(root) | 现场两个僵尸的直接源头是 teardown 只删目录不杀 detached runner;这层保证测试不再产出孤儿(launch.test.mjs 的 detached fake-pi 已确认自然退出,无需处理) |
47
-
48
- ### 数据流(修复后)
49
-
50
- ```
51
- claimNextFollowUp(root, viewId)
52
- → withViewLockSync(root, viewId, "queue", fn)
53
- → acquireLock: [wait loop w/ sleep] → 窗口过期 → steal attempt ×2 → 失败 → throw
54
- → catch → { ok: false, error: "follow-up queue lock unavailable: ..." }
55
- → job-runner drainQueuedFollowUp: claimNextFollowUp 返回 {ok:false} → 直接 return(不进 launch)
56
- → .finally → process.exit 必达
57
- ```
58
-
59
- ### 组件契约
60
-
61
- - `withFileLockSync` / `withViewLockSync`:成功返回 fn 结果;**新行为**——锁超时抛 `Error`(message 含 lockPath 与耗时)。
62
- - follow-up-queue 5 个导出(enqueue/claim/complete/release/remove/clear):任何锁失败 → `{ok:false, error}`,不再抛出。
63
- - service.mjs:零改动(已按 {ok} 消费)。
64
- - job-runner.mjs:收尾链不因锁失败挂起。
65
-
66
- ### 降级行为
67
-
68
- - 锁失败时队列操作静默失败并写 diagnostics(job-runner 用 appendDiagnostic;service 层已有该模式),用户可感知但系统不挂。
69
- - 不引入锁重试队列、不引入跨进程 watchdog——超出本 issue 范围。
70
-
71
- ## 非目标
72
-
73
- - 不改锁的 mkdir 实现(不换 flock/其他机制)
74
- - 不处理「锁持有者崩溃残留」之外的竞争语义
75
- - 不改 30s staleMs 默认值
76
- - 不引入异步锁
77
-
78
- ## 测试计划(red-green)
79
-
80
- test/locks.test.mjs 现有 5 测试全绿;新增(全部带 `{ timeout: 5000 }` 防挂):
81
-
82
- 1. **mkdir 永败**(注入 fs):`withFileLockSync(..., { staleMs: 50 })` 在 ~250ms 窗口(`max(250, staleMs)` floor)后抛错,不在 5s 内挂起。
83
- 2. **写 owner.json 永败**(注入 fs):mkdir 成功但 writeFileSync 抛 → 有界强夺后抛错。
84
- 3. **rmSync 永败**(注入 fs):窗口后强夺 rm 失败 → 抛错。
85
- 4. **争用正常恢复**:注入 fs 模拟「前 N 次 mkdir EEXIST、之后成功」→ 等待窗内获取成功(保语义 1)。
86
- 4b. **父目录被删后自愈**(D4b):真实 fs,锁获取前删掉父目录 → ensureDir 在循环内重建 → 正常拿锁(验证现场最高频竞态透明自愈)。
87
- 5. **窗口后强夺仍成功**:复用现有测试 4(不回归)。
88
- 6. follow-up-queue 层:锁失败 → `{ok:false, error}`(用注入 fs 或真实坏路径)。
89
- 7. job-runner 收尾:若可低成本导出/集成测试则覆盖「锁坏时 drainQueuedFollowUp 不挂起、process 退出」;否则以代码评审 + 手动验证为准。
90
- 8. 集成测试 teardown:runner 被杀后进程表里不再残留 job-runner(现有集成测试全绿即可证明 kill 生效)。
91
-
92
- ## 验收
93
-
94
- - `npm test`(或 `npm run verify`)全绿
95
- - 35s 复现脚本(/sys 只读路径)修复后应立即抛错而非空转
96
- - 跑完集成测试后 `ps` 无残留 job-runner
97
- - 代码评审通过后 PR + Zima CR
@@ -1,35 +0,0 @@
1
- # Issue #34 — CI flaky: pty-runner integration test times out waiting for pre-connect output
2
-
3
- ## Root cause
4
-
5
- `test/pty-runner.integration.test.mjs` (test "pty-runner creates host socket, broadcasts output, forwards input, finalizes") waits for the boot banner `fake pi ready` **over the control socket**. But the socket only carries *live* output: `broadcast()` iterates `clients`, which is empty until a client connects. If the child emits `fake pi ready` before the test's socket connects (CI runners start the child fast), the output is broadcast to zero clients and lost — the 3s `waitFor` times out.
6
-
7
- History output is intentionally NOT replayed over the socket; the UI attach path replays it from the screen log file (`src/ui/pty-attach.ts` `replayScreenLog`). The test's assumption contradicts the protocol design.
8
-
9
- ### Evidence
10
-
11
- - CI run 32554867604 (main, #32): `not ok 181 ... error: 'timed out waiting'` at `test/pty-runner.integration.test.mjs:65`, both Node 22 and Node 24.
12
- - PR branch run 32554619699 failed on a *different* test (`runner.integration.test.mjs:195`, auto-done idle), passed on rerun — separate timing-sensitive spot, out of scope.
13
- - Reproduced deterministically locally with a forced "output before connect" script: 3/3 timeouts. Same test passes 5/5 under normal timing.
14
- - Local runs after fix: 6/6 pass; late-connect scenario: 3/3 pass; full suite: 313/313 pass.
15
-
16
- ## Fix design
17
-
18
- Only the test changes; no product code change (socket protocol behavior is by design).
19
-
20
- | Step | File | Change |
21
- |------|------|--------|
22
- | 1 | `test/pty-runner.integration.test.mjs` | Replace the socket wait for `fake pi ready` with a screen-log read wait (`P.screenLogPath(root, "v1")` contains `fake pi ready`) — mirrors UI attach replay semantics |
23
-
24
- Assertions that remain on the socket (post-connect realtime events, timing-safe):
25
- - `echo:hello` output (live broadcast + input forwarding)
26
- - resize → `readHost().cols === 100`
27
- - `exit` → `endedAt` set, state `exited`
28
-
29
- Screen-log assertions (file-based, timing-safe):
30
- - boot banner `fake pi ready` present (already asserted at test end via `assert.match`)
31
-
32
- ## Non-goals
33
-
34
- - No change to `runner/pty-runner.mjs` socket protocol.
35
- - No change to `runner.integration.test.mjs` flaky spot (tracked separately if it recurs).
@@ -1,128 +0,0 @@
1
- # Spec: 行内展示 session 关联的 issue / PR 编号(code-refs)
2
-
3
- - Issue: https://github.com/zhuxixi/pi-agent-board/issues/40
4
- - 日期:2026-08-29
5
- - 状态:待用户确认
6
-
7
- ## 1. 背景与目标
8
-
9
- Dashboard 每行(一个后台 pi session)目前只有 name + summary + age,看不到「这行在处理哪个 issue、它提交了哪个 PR」。本特性在行内徽章区展示这两个编号,peek 视图给完整信息。
10
-
11
- **平台无关是硬约束**:GitHub、GitLab、公司内网代码平台都有 issue/PR(MR)概念但 CLI 与 URL 不同。平台差异全部做成正则规则数据,提取引擎保持通用。
12
-
13
- ### 目标(Goals)
14
-
15
- 1. 行内徽章显示最近一个 issue 编号 + 最近一个 PR 编号(如 `#40 ▸#45`),前缀随平台规则(GitLab MR 用 `!`)。
16
- 2. peek 视图显示完整引用列表(编号 + 置信度 + 平台链接)。
17
- 3. 平台规则可由用户配置扩展(内网平台 = 加一段 JSON,引擎零改动)。
18
- 4. 纯本地提取,零网络调用;实时性 = 事件驱动(session 跑到相关命令时徽章即出现)。
19
-
20
- ### 非目标(Non-goals)
21
-
22
- - 不做网络补全(`gh pr list --head` 查分支对应 PR、抓取 issue/PR 标题与状态)——列为 v2 候选,本期不做。
23
- - 不做 PR 状态着色(open/merged)、不做 `refs:has` 过滤器——v2 候选。
24
- - 不修 `outputPreview` 的 `[object Object]` bug——拆独立 issue #41。
25
- - 不识别「无编号」的平台对象(如纯分支名)。
26
-
27
- ## 2. 决策表(已与用户对齐)
28
-
29
- | # | 决策点 | 结论 |
30
- |---|---|---|
31
- | D1 | 评分规则 | 信号分四级(认领/worktree 命名/PR 回链 = 最强;动作 = 强;查看 = 中;裸引用 = 弱兜底),平局时最近的最强信号赢 |
32
- | D2 | 多候选显示 | 行内只显示最近一个 issue + 一个 PR;完整列表进 peek |
33
- | D3 | 用户规则与内置同名 provider 冲突 | 按规则追加(用户规则优先匹配,其后是内置规则) |
34
- | D4 | outputPreview bug | 拆独立 issue #41,本特性不含 |
35
- | D5 | 隔离测试 | `PI_CODING_AGENT_DIR` + `AGENT_BOARD_ROOT` 双变量隔离,不动 `~/.pi`;软链开发方式不用 |
36
-
37
- ## 3. 架构
38
-
39
- 复刻仓库既有「artifact → summarize → 合并进 Row → renderRow 徽章」模式(evidence/diagnostics/followUps/steering 同构)。新增一个纯函数引擎模块、一个 per-view artifact、五处写入钩子、两处渲染改动。
40
-
41
- ```
42
- 事件流 → reduceEvidence → evidence.json ──┐
43
- ├─→ extractCodeRefs() → github.json → Row.github → 徽章/peek
44
- providers.json(用户规则)+ 内置规则 ──────┘ ▲
45
- repo.mjs remoteHost()(带缓存)───────────────────┘
46
- ```
47
-
48
- ### 组件契约
49
-
50
- **C1 `src/core/code-refs.mjs`(新,纯函数,零 I/O,主测试对象)**
51
-
52
- - `extractCodeRefs(input, providerSet) → CodeRefsResult`
53
- - `input`: `{ commands: EvidenceCommand[], assistantTexts: string[], worktreePath: string|null, branch: string|null }`
54
- - `providerSet`: 解析后的规则包列表(已按 host 选好 + 用户规则已合并)
55
- - 输出: `{ issue: {number, confidence, source} | null, pr: {number, confidence, source} | null, repoUrl: string|null }`
56
- - `loadProviders(builtIns, userConfig) → provider 列表`(实现 D3 追加语义:同名 provider 时用户规则排在内置规则前面)
57
- - `matchProvider(providers, remoteHost) → provider | null`(host 匹配;无匹配返回 null,调用方走兜底规则)
58
- - 信号强度枚举:`claim > action > view > mention`,每级带置信度 high/medium/low。
59
-
60
- **C2 信号来源与评分规则(D1 落地)**
61
-
62
- | 强度 | 信号 | 检测方式 |
63
- |---|---|---|
64
- | claim(最强) | 认领命令:provider 规则里 `strength: "claim"` 的命令模式(GitHub 内置:`gh issue edit N --add-assignee`) | commands 正则 |
65
- | claim | worktree 命名:`issue-<N>-<slug>`(issue-driven 工作流强制规范) | worktreePath / branch 结构化解析(非正则配置,引擎内置) |
66
- | claim | PR 回链(按证据上下文拆分,issue #65):`gh pr create` body 中的 `Closes #N` / `fixes issue #N` / 兼容裸 `issue #N`;后续 assistant 文本仅认 canonical `Closes/Fixes/Resolves #N`(带单词边界与 7 位编号边界);仅在恰好一个 PR create 命令时扫描 assistant,后续 command 不参与回链 | create 命令自身 + assistantTexts |
67
- | action(强) | `issue comment/edit/close N`、`pr checkout/view/merge N`、`pr create`(编号从 outputUrl 或后续 URL 反查,见 D4 限制) | commands 正则 |
68
- | view(中) | `issue view N` / `pr view N` | commands 正则,要求频次 ≥2,取最近一次 |
69
- | mention(弱,兜底) | 裸 `#N` | assistantTexts,要求频次显著最高(≥3 且 ≥ 第二名的 2 倍) |
70
-
71
- 平局:命令序列中位置最靠后的最高强度信号赢(commands 有序,按数组下标比较,不用时间戳)。置信度:claim/action → high;view → medium;mention → low。渲染时 low 用 dim 色(宁可不显示也不错显示——mention 级仅在无任何更强信号时出现)。
72
-
73
- **C3 `providers.json` 规则 schema(用户配置)**
74
-
75
- - 位置:`$AGENT_BOARD_ROOT/providers.json`(随 store root 隔离,E2E 天然不污染真实配置)。
76
- - 结构:`{ providers: [{ name, hosts[], issuePrefix, prPrefix, urlTemplates: { issue, pr }, rules[] }] }`;每条 rule = `{ pattern, kind: "issue"|"pr", strength: "claim"|"action"|"view", numberFrom?: "capture"|"outputUrl" }`。urlTemplates 用占位符拼链接,如 `"https://{host}/{owner}/{repo}/-/issues/{number}"`(owner/repo 从 remote URL 解析)。
77
- - 内置默认:GitHub + GitLab 两份(含 hosts、URL 正则、CLI 正则、前后缀、链接模板)。
78
- - 加载失败(JSON 语法错 / 单条正则非法):跳过该条并记 diagnostics(`code_refs_config` 码),不炸 dashboard。
79
-
80
- **C4 `repo.mjs` 增补**
81
-
82
- - `gitRemoteHost(repoRoot) → string|null`:`git remote get-url origin` 解析 host,支持 ssh(`git@host:path`)与 https 两种形式;结果按 repoRoot 缓存在模块级 Map(一个仓库只查一次,失败也缓存 null)。
83
-
84
- **C5 artifact `github.json`(per-view)**
85
-
86
- - `paths.mjs` 加 `codeRefsPath(root, viewId)` → `views/<id>/github.json`。
87
- - 内容:`{ version: 1, viewId, updatedAt, provider: string|null, issue: {...}|null, pr: {...}|null, allRefs: [...](peek 用,最多 10 条) }`。
88
- - 读写走 `atomicWriteJson`(并发写者多,KB 已有教训)。
89
- - `readViewArtifactSummaries` 增加 `codeRefs:` 汇总;`Row`/`RowView` 加 `codeRefs` 字段。
90
-
91
- **C6 写入钩子(5 处,`writeEvidence` 的全部调用点)**
92
-
93
- `runner/job-runner.mjs` ×2(共用 persist())、`src/runtime/service.mjs` ×2、`runner/state-runner.mjs` ×1。统一收敛为一个 helper:`updateCodeRefsFromEvidence(root, viewId, evidence, meta)`——增量不重算:引擎输入只取 evidence 的 commands + 最近若干条 assistantTexts + meta.worktreePath/branch,纯正则,实测成本微秒级;每次 evidence 写入后顺带调用。失败只记 diagnostics,不影响主流程。
94
-
95
- **C7 渲染**
96
-
97
- - `rows.mjs` `rowView`:透传 `codeRefs`。
98
- - `dashboard.ts` `renderRow`:statusBadges 追加 `issuePrefix+number`(issue)与 `prPrefix+number`(PR),low 置信度用 `dim` 色;宽度沿用现有「从 name 预算扣」机制。
99
- - peek 视图:新增 "Refs" 段(复刻 Auto-state 段模式):provider 名、issue/PR 编号 + 置信度 + 由 `urlTemplate` 拼出的终端超链接、allRefs 完整列表。
100
-
101
- ## 4. 错误处理与降级
102
-
103
- - 无 git 仓库 / 无 remote / host 不认识 → 只用「通用 URL 兜底规则」(匹配任意 host 的 `/issues/N`、`/pull/N`、`/-/issues/N`、`/-/merge_requests/N`)+ worktree 命名解析;都没有则不显示徽章。
104
- - 用户 providers.json 损坏 → 内置规则仍生效,diagnostics 记一条 warn。
105
- - 提取过程任何异常 → catch 后记 diagnostics,evidence 主流程不受影响(与既有 artifact 容错一致)。
106
- - 无任何引用 → `github.json` 写空结果(`issue: null, pr: null`),渲染跳过徽章,不留 stale 数据。
107
-
108
- ## 5. 测试策略(四层,详见 issue 评论)
109
-
110
- 1. **单元**:`code-refs.mjs` 全分支覆盖——每级信号命中、强度排序、平局规则、mention 兜底阈值、provider 追加合并(D3)、host 匹配、损坏配置容错。
111
- 2. **真实数据回测**:脱敏后的真实 evidence.json 命令序列做 fixture(`moc 439` 行的多引用歧义场景是核心用例)。
112
- 3. **集成**:fake-pi.mjs 注入含 `gh issue edit 40 --add-assignee` / `gh pr create` 的事件流 → 断言 `github.json` 内容与渲染徽章字符串。
113
- 4. **手工 E2E**:`PI_CODING_AGENT_DIR` + `AGENT_BOARD_ROOT` 隔离环境;scratch 仓库换 remote host 验证 provider 匹配;PR 编号用 `echo <url>` 模拟(全程零真实 GitHub 变更)。
114
- 5. 验收:`npm run verify` 全绿(typecheck + test + c8 行 85%/分支 70% + pack dry-run)。
115
-
116
- ## 6. 分阶段
117
-
118
- - **v1(本 issue)**:C1–C7 全部(纯本地提取 + 渲染 + 配置)。
119
- - **v2(另开 issue,不在本期)**:网络补全(`pr list --head`、标题/状态)、PR 状态着色、`refs:has` 过滤器、outputPreview 修复后的 `outputUrl` 反查增强(依赖 #41)。
120
-
121
- ## 7. 实现顺序(供 plan 参考)
122
-
123
- 1. `repo.mjs` `gitRemoteHost` + 缓存(含单测)
124
- 2. `code-refs.mjs` 引擎 + 内置规则 + schema 校验(含单测,覆盖率大头)
125
- 3. `github.json` artifact 读写 + `readViewArtifactSummaries` 汇总 + Row/RowView 字段
126
- 4. 5 处写入钩子
127
- 5. 渲染:徽章 + peek Refs 段
128
- 6. 四层测试补齐 + `npm run verify`
@@ -1,47 +0,0 @@
1
- # Spec: dashboard 列表方向键导航循环回绕(issue #52)
2
-
3
- 日期:2026-08-30 · 状态:draft(等用户确认)
4
-
5
- ## 根因报告
6
-
7
- **现象**:dashboard 主列表 ↑/↓ 移动选中项,到达首/尾边界后按键无效,无法循环。
8
-
9
- **根因**(已通过代码直读确认,systematic-debugging Phase 1 完成):
10
- `src/ui/dashboard.ts` `moveSelection()`(L250-258)用 `Math.max(0, Math.min(len-1, ...))` 钳制新索引,而非取模回绕。该写法来自 MVP commit `72c0d8f`,非有意设计。
11
-
12
- **调用链**:`handleListKey`(L334-335,normal 模式)与 `handleSelectKey`(L370-371,multi-select 模式)→ `moveSelection(±1)`。
13
-
14
- ## 设计
15
-
16
- ### 改动点
17
-
18
- | # | 位置 | 改动 |
19
- |---|------|------|
20
- | 1 | `moveSelection()` L250-258 | 钳制 → 取模回绕:`next = ((base + delta) % len + len) % len` |
21
- | 2 | `peekStep()` L1094-1101 | 同上,保持与主列表一致 |
22
-
23
- ### 决策表
24
-
25
- | 决策点 | 决定 | 理由 |
26
- |--------|------|------|
27
- | `cur < 0`(selectedId 不在列表中) | 取模语义:按 ↓ 得 index 1(第二条,与旧实现一致);按 ↑ 得 index len-1(最后一条,**与旧实现不同**——旧钳制得第一条)。有意为之:与回绕语义一致;该路径实际不可达(`refresh()` 不变量保证按键处理时 selectedId ∈ orderedIds,或 selectedId=null 走 base 0) | 修正记录(Zima CR round 1):旧版写"保持现状语义"不属实,↑ 方向兜底行为确有变化 |
28
- | 单条列表(len=1) | 取模后索引恒为 0,`nextId === selectedId` early return 挡掉无效更新 | 现有兜底继续生效,无需特判 |
29
- | 空列表 | 现有 `length === 0` early return 保留 | 不变 |
30
- | 滚动跟随 | **不改**——`windowBody()` 渲染时强制选中行可见,方向无关,回绕自动跟随 | 已验证 |
31
- | 按键热路径性能 | 改动只涉及一次取模运算,不影响 #9/PR #12 的 prewarm debounce 设计 | 调研结论 |
32
- | launch picker(cwd/model/thinking,L539-547) | **本 issue 不改**(非目标),另开 issue 跟踪 | 弹窗内短列表,回绕收益低;避免一次 PR 混两个行为变更 |
33
-
34
- ### 非目标
35
-
36
- - launch 对话框 picker 的回绕(另议)
37
- - 任何渲染层、滚动层改动
38
- - 其他模式的按键行为
39
-
40
- ### 测试
41
-
42
- 目前 `moveSelection`/`peekStep` 无测试覆盖。补一个针对 Dashboard 的轻量测试(参考 `test/ui-smoke.test.mjs` 的实例化方式):构造 3 条 orderedIds,断言 尾→↓→首、首→↑→尾 的回绕行为,以及单条/空列表不炸。
43
-
44
- ## 验证
45
-
46
- 1. `npm test` 全绿(含新增用例)
47
- 2. 手动跑 dashboard:多 session 列表,尾按 ↓ 回首、首按 ↑ 回尾,peek 模式同样验证