@huseynsnmz/pi-board 0.15.1 → 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 (215) hide show
  1. package/CHANGELOG.md +26 -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 +18 -19
  8. package/src/index.ts +9 -1
  9. package/src/runtime/service.mjs +12 -0
  10. package/src/ui/dashboard.ts +14 -8
  11. package/src/ui/double-left.ts +5 -1
  12. package/src/ui/focus.ts +7 -1
  13. package/src/ui/waiting.ts +10 -5
  14. package/IMPLEMENTATION_PLAN.md +0 -920
  15. package/PRD.md +0 -484
  16. package/PROGRESS.md +0 -142
  17. package/VERIFY.md +0 -115
  18. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  19. package/docs/EXPLORATION.md +0 -187
  20. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  21. package/docs/RELEASE.md +0 -148
  22. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  23. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  24. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  25. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  26. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  27. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  28. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  29. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  30. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  31. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  32. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  33. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  34. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  35. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  36. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  37. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  38. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  39. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  40. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  41. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  42. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  43. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  44. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  45. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  46. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  47. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  48. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  49. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  50. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  51. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  52. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  53. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  54. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  55. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  56. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  57. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  58. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  59. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  60. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  61. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  62. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  63. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  64. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  65. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  66. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  67. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  68. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  69. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  70. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  71. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  72. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  73. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  74. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  75. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  76. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  77. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  78. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  79. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  80. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  81. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  82. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  83. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  84. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  85. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  86. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  87. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  88. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  89. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  90. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  91. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  92. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  93. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  94. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  95. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  96. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  97. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  98. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  99. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  100. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  101. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  102. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  103. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  104. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  105. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  106. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  107. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  108. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  109. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  110. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  111. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  112. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  113. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  114. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  115. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  116. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  117. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  118. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  119. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  120. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  121. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  122. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  123. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  124. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  125. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  126. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  127. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  128. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  129. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  130. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  131. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  132. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  133. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  134. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  135. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  136. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  137. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  138. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  139. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  140. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  141. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  142. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  143. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  144. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  145. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  146. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  147. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  148. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  149. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  150. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  151. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  152. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  153. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  154. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  155. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  156. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  157. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  158. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  159. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  160. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  161. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  162. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  163. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  164. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  165. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  166. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  167. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  168. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  169. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  170. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  171. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  172. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  173. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  174. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  175. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  176. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  177. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  178. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  179. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  180. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  181. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  182. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  183. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  184. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  185. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  186. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  187. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  188. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  189. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  190. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  191. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  192. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  193. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  194. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  195. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  196. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  197. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  198. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  199. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  200. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  201. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  202. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  203. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  204. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  205. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  206. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  207. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  208. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  209. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  210. package/scripts/budget-audit.mjs +0 -135
  211. package/scripts/patch-vulns.mjs +0 -59
  212. package/scripts/release.mjs +0 -446
  213. package/scripts/release_helper.mjs +0 -309
  214. package/scripts/run-perf-gate.mjs +0 -40
  215. package/scripts/run-tests.mjs +0 -375
@@ -1,106 +0,0 @@
1
- # Spec: cwd 常用目录在全量历史里搜索(issue #262)
2
-
3
- Drafted: deepseek/deepseek-flash (selected) · deepseek/deepseek-flash (physical) @ 2026-11-03T08:21:00+08:00
4
- Revised: deepseek/deepseek-flash (selected) · deepseek/deepseek-flash (physical) @ 2026-11-03T08:30:00+08:00
5
-
6
- ## 1. 问题陈述
7
-
8
- cwd picker 的 favorites 候选池在**打开时**就被截断到 8 条(`src/ui/dashboard.ts:755`),后续 `filterCwdCandidates`(`src/core/launch-options.mjs:358`)只在这 8 条上做子串过滤。结果是:`cwd-stats.json` 里已记录、但排名 > 8 的目录,输入其名字片段完全搜不到;过滤为空还会回落 `browse`,browse 以 `launch.cwd` 为基准解析片段,必然找不到,界面只显示 `no matches`,用户误以为项目从未被记录。
9
-
10
- 排名是「使用强度」排序,不是「用户会去找」的排序;历史越长越容易命中这个窗口。
11
-
12
- ## 2. 根因(已对真实代码逐行核对)
13
-
14
- 1. `src/ui/dashboard.ts:755`:`launch.cwdRanked = existingCwdCandidates(rankedCwdCandidates(this.deps.root, 8))` —— 前置截断。
15
- 2. `src/core/cwd-stats.mjs:98`:`rankedCwdCandidates(root, limit = 8)` —— 默认值同源,`rows.slice(0, Math.max(1, limit))` 后才追加 home 兜底。
16
- 3. `src/core/launch-options.mjs:409`:`nextCwdPickerState` 在传入列表上过滤;无命中即 `browse`(`:413` 之后的 fallback),browse 的 `listDirectorySuggestions`(函数头 `:302`,以 `launch.cwd` 为基准解析在 `:303-305`)解析项目名片段,得到 `[]`。
17
- 4. 渲染层 `renderLaunchSuggestions`(`src/ui/dashboard.ts:1685`)本就只画 6 行 + `↓ N more`,候选数组变长不会撑高对话框——**不是**需要截断的原因。
18
-
19
- ## 3. 方案选择
20
-
21
- | 方案 | 内容 | 结论 |
22
- | --- | --- | --- |
23
- | A(选定) | 打开 picker 时加载**全量** ranked 历史;`rankedCwdCandidates` 默认不截断 | 一次性消除「记录存在但搜不到」,渲染层零改动 |
24
- | B | 8 → 20(或任意更大常量) | 只把窗口推后,记录继续增长会复发;且两处常量需同步,易改漏 |
25
- | C | 在 `nextCwdPickerState` 里先过滤再 slice | 状态机拿不到被截掉的条目,等同 B;且会破坏 `↓ N more` 的语义(slice 后总数失真) |
26
-
27
- 选定 A。`limit` 参数保留(默认 `Number.POSITIVE_INFINITY`),仅供测试或未来明确需要显式封顶的调用方使用;当前唯一生产调用方 dashboard 不传 limit。
28
-
29
- ## 4. 数据流与契约
30
-
31
- ```
32
- readCwdStats(root) // 全量 entries(已有)
33
- → rankedCwdCandidates(root) // 全量排序:count desc → lastUsed desc,末尾补 home(若缺)
34
- → existingCwdCandidates(...) // 丢弃已不存在的目录(已有,O(N) statSync)
35
- → launch.cwdRanked // picker 的搜索池
36
- → nextCwdPickerState(query, pool, cwd) // 纯函数:命中 → favorites;空/无命中 → browse(已有语义)
37
- ```
38
-
39
- 改动点:
40
-
41
- - `src/core/cwd-stats.mjs`:`rankedCwdCandidates(root, limit = Number.POSITIVE_INFINITY)`;更新 JSDoc,写明默认不封顶、显式 limit 仅用于调用方需要的封顶。
42
- - `src/ui/dashboard.ts:755`:去掉 `, 8`,改为 `existingCwdCandidates(rankedCwdCandidates(this.deps.root))`。
43
- - 其余(过滤、状态机、browse、渲染)**一行不改**。
44
-
45
- 行为契约(改后):
46
-
47
- | 输入 | 模式 | 候选 |
48
- | --- | --- | --- |
49
- | 空 query | favorites | 全量 ranked(按 count/lastUsed 排序,home 兜底) |
50
- | 非空 query,历史中任意一条路径含该子串(大小写不敏感) | favorites | 全部命中项,保持排名序 |
51
- | 非空 query,历史中无任何命中 | browse | 与现状一致(以 `launch.cwd` 为基准的文件系统浏览) |
52
- | stats 读取失败 | browse | 与现状一致(`openLaunchPicker` 的 try/catch 置空池) |
53
- | 池中目录全部已删除 | browse | `existingCwdCandidates` 过滤后为空 → 现状 fallback |
54
-
55
- ## 5. 非目标
56
-
57
- - 不修 browse 模式的软链目录枚举缺陷(`src/core/launch-options.mjs:317-318`,`Dirent.isDirectory()` 不跟随软链)——独立 issue 跟踪。
58
- - 不改渲染窗口(6 行)、不改 `↑/↓` 钳制语义、不加 fuzzy 匹配、不引入手动收藏、不改 `cwd-stats.json` 格式与排名规则。
59
- - 不改历史设计文档 `docs/superpowers/specs/2026-08-22-cwd-favorites-design.md` 中的 “Top 8” 记录(历史留档,由本 spec 取代其行为约定)。
60
-
61
- ## 6. 已知边界与风险
62
-
63
- - **O(N) statSync**:打开 picker 时对 N 条历史各做一次 `statSync`(`existingCwdCandidates`)。N = 记录目录数,本机 15、issue 机器 28,量级为微秒到毫秒;perf gate(A11)不覆盖此路径,且打开动作是每 picker 会话一次而非每帧。若未来 N 达到数千,需要另设懒校验/缓存方案,不在本 issue。
64
- - **home 兜底的滚动距离**:home 不在 stats 时仍追加在列表末尾;记录越多,空 query 下按 ↓ 到 home 的距离越长(旧实现固定在 9 位)。home 在真实使用中几乎总是排名第一(本机 187×),故接受;不额外改变排序语义。
65
- - **候选总数上界 = 历史条目数**:无人为 cap,故不存在「再次写死一个数字」的复发点。
66
-
67
- ## 7. 可测性拆分设计(必答)
68
-
69
- 保持既有分层,**不新增生产 helper、不在 dashboard 里加逻辑**:
70
-
71
- | 单元 | 位置 | 形态 | 测试层级 |
72
- | --- | --- | --- | --- |
73
- | 排名 + home 兜底 | `rankedCwdCandidates`(`src/core/cwd-stats.mjs`) | 读 stats(I/O 壳)+ 纯排序;不碰 fs 目录校验 | unit(临时 root 写 `cwd-stats.json`) |
74
- | stale 目录过滤 | `existingCwdCandidates`(`src/core/launch-options.mjs`) | 纯函数 + `statSync` 谓词 | unit(既有测试覆盖) |
75
- | 子串过滤 | `filterCwdCandidates` | 纯函数 | unit(既有) |
76
- | 模式状态机 | `nextCwdPickerState` | 纯函数 | unit(新增「>8 条列表、命中第 10 条」用例) |
77
- | dashboard 接线 | `openLaunchPicker`(`src/ui/dashboard.ts:749-762`) | 薄 I/O 壳:加载池 → 调状态机 | integration(TS 探针 + `handleInput`,沿用 `test-support/cwd-browse-tab-probe.ts` 模式) |
78
-
79
- 测试边界:排名/过滤/状态机的语义全部由纯函数单测钉住;「打开 picker 时池子是否被截断」只能由 dashboard 探针钉住(单测对传入列表一视同仁,无法发现调用方的 `, 8`)。渲染层不新增逻辑,故不加渲染断言。
80
-
81
- ## 8. 验收矩阵(必答)
82
-
83
- | ID | 功能点 | 验收方式 | 具体验证 | 通过标准 |
84
- | --- | --- | --- | --- | --- |
85
- | A1 | 排名默认返回全量历史 | 自动化验证(unit) | `node --test test/cwd-stats.test.mjs`:临时 root 写 10 条 stats,调 `rankedCwdCandidates(root)`(不传 limit) | 返回 10 条按 count desc / lastUsed desc 排列 + home 兜底;不出现 8 条截断 |
86
- | A2 | picker 能搜到排名 > 8 的已记录目录(issue 主场景) | 自动化验证(integration,探针) | `node --test test/dashboard-cwd-favorites-pool.test.mjs`:临时 root 建 10 个真实目录并写入 stats(目标为第 10 名);先 `dash.input = "go"`(`openLaunchDialog` 在空 prompt 时直接回 list,`launch` 为 null,见 `dashboard.ts:676-678`、`:750-751`),再 `openLaunchDialog()` → `openLaunchPicker("cwd", "")` → 逐字符 `handleInput("a"/"l"/"e"/"r")` | `cwdPickerMode === "favorites"`,`cwdSuggestions` 含目标路径(修复前为 `browse` + `[]`) |
87
- | A3 | 状态机在超 8 条列表上仍按子串匹配 | 自动化验证(unit) | `node --test test/launch-options.test.mjs`:12 条 ranked,query 命中第 10 条 | `mode === "favorites"`,suggestions 含该条且保持排名序 |
88
- | A4 | 既有行为不回归(空 query、无命中回 browse、stale 过滤、browse/favorites Tab) | 自动化验证(unit + integration) | `node --test test/launch-options.test.mjs test/cwd-stats.test.mjs test/dashboard-cwd-tab.test.mjs` | 全绿;空 query 仍 favorites、无命中仍 browse、Tab 语义不变 |
89
- | A5 | 类型与套件门禁 | 自动化验证(static + build) | `npm run typecheck`、`npm run verify` | 零类型错误;套件、覆盖率、perf gate、pack:dry 全过 |
90
- | A6 | 失败分支:stats 不可读 / 目录全失效 | 自动化验证(unit) | `node --test test/cwd-stats.test.mjs test/launch-options.test.mjs`(既有 corrupt stats 用例 + `existingCwdCandidates` + 空池回 browse) | 读取失败降级为空/仅 home;池为空时仍回落 browse,不抛异常 |
91
- | U1 | 真实 TUI 手感(本机 15 条 stats,目标排名第 11) | 用户实测 | 前置:更新 pi 的 git 安装副本并重启 pi——`git -C ~/.pi/agent/git/github.com/zhuxixi/pi-agent-board pull --ff-only origin main`,再 `git -C ~/.pi/agent/git/github.com/zhuxixi/pi-agent-board log -1 --oneline` 确认含修复 commit(若 pi 有自身更新机制如 `pi install`/update,以它为准)。步骤:重启后 board → `i` 输入 prompt → `Enter` → `cwd` 字段 `Enter` → 输入 `commit` | favorites 模式出现 `~/git-repo/github/pi-commit-lint 1×`(第 11 名);再输 `github-watching` 出现 `~/workspace/proj/github-watching/pi-agent-board 2×`(第 10 名);空 query 榜单顺序不变 |
92
-
93
- U1 可执行时机:合并 + 安装副本更新后。合并前以 A2 的探针作为等价自动化证据;U1 在合并后由用户执行,未执行前记为 `pending`,不得宣称全部验收完成。
94
-
95
- ## 9. 文件清单
96
-
97
- | 文件 | 改动 |
98
- | --- | --- |
99
- | `src/core/cwd-stats.mjs` | `rankedCwdCandidates` 默认 limit 改为 `Number.POSITIVE_INFINITY`;JSDoc 更新 |
100
- | `src/ui/dashboard.ts` | `openLaunchPicker` 去掉 `, 8`(仅 1 行) |
101
- | `test/cwd-stats.test.mjs` | 新增 A1 全量用例 |
102
- | `test/launch-options.test.mjs` | 新增 A3「>8 条列表命中第 10 条」用例 |
103
- | `test-support/cwd-favorites-pool-probe.ts` | 新增 A2 探针(沿用 #127 探针模式) |
104
- | `test/dashboard-cwd-favorites-pool.test.mjs` | 新增 A2 集成测试 |
105
-
106
- 命名遵循仓内约定:测试文件 kebab-case,探针放 `test-support/`,commit 用 conventional commits 并在 subject 末尾带 `(#<PR>)`。
@@ -1,135 +0,0 @@
1
- /**
2
- * Budget audit for test files (issue #95 F2).
3
- *
4
- * Mechanical discipline: a test's declared timeout must be able to contain
5
- * its waits (rule a), and an app-level deadline a test drives must be covered
6
- * by the test's own budget unless explicitly escaped (rule b). Pure functions
7
- * only — the gate lives in test/budget-audit.test.mjs, which runs these over
8
- * fixture sources AND the real test tree.
9
- */
10
-
11
- /** Strip escaped lines (`// budget: <reason>`) from a source. @param {string} source @returns {string} */
12
- export function stripEscapedLines(source) {
13
- return source.split("\n").filter((line) => !line.includes("// budget:")).join("\n");
14
- }
15
-
16
- /** @param {string} source @returns {number | null} */
17
- export function parseFileDefaultWaitMs(source) {
18
- // The default lives in the parameter list: `async function waitFor(predicate, timeoutMs = 15_000, ...) {`.
19
- const m = /async function waitFor\([^)]*?timeoutMs\s*=\s*([0-9_]+)[^)]*\)\s*\{/.exec(source);
20
- return m ? Number(m[1].replace(/_/g, "")) : null;
21
- }
22
-
23
- /** Balanced-paren argument text for the call whose `(` sits at fromIndex. @param {string} text @param {number} fromIndex @returns {string | null} */
24
- function callArgs(text, fromIndex) {
25
- let depth = 0;
26
- for (let i = fromIndex; i < text.length; i++) {
27
- if (text[i] === "(") depth++;
28
- else if (text[i] === ")") {
29
- depth--;
30
- if (depth === 0) return text.slice(fromIndex + 1, i);
31
- }
32
- }
33
- return null;
34
- }
35
-
36
- /** Top-level (depth-0) comma-separated arguments of a call's argument text. @param {string} args @returns {string[]} */
37
- function splitTopLevelArgs(args) {
38
- const parts = [];
39
- let depth = 0;
40
- let current = "";
41
- for (const ch of args) {
42
- if (ch === "(" || ch === "[" || ch === "{") depth += 1;
43
- else if (ch === ")" || ch === "]" || ch === "}") depth -= 1;
44
- if (ch === "," && depth === 0) {
45
- parts.push(current);
46
- current = "";
47
- } else current += ch;
48
- }
49
- parts.push(current);
50
- return parts;
51
- }
52
-
53
- /**
54
- * Distinct numeric budget literals a `waitFor(...)` call declares: the trailing
55
- * numeric argument (`waitFor(pred, 30_000)` / `waitFor(pred, 30_000, 50)`) plus
56
- * the first top-level numeric argument, which covers
57
- * `waitFor(pred, 30_000, capture)` shapes where the trailing regex only ever
58
- * sees the capture thunk — and the `(pred, timeout, interval)` shape, whose
59
- * trailing number is the interval, not the budget. Empty when the call omits
60
- * the budget and inherits the file default.
61
- * @param {string} args @returns {number[]}
62
- */
63
- function waitLiteralsIn(args) {
64
- const out = [];
65
- const add = (raw) => {
66
- const value = Number(raw.replace(/_/g, ""));
67
- if (!out.includes(value)) out.push(value);
68
- };
69
- const trailing = /,\s*([0-9_]+)\s*,?\s*$/.exec(args.trim());
70
- if (trailing) add(trailing[1]);
71
- const first = splitTopLevelArgs(args).map((a) => a.trim()).find((a) => /^[0-9_]+$/.test(a));
72
- if (first) add(first);
73
- return out;
74
- }
75
-
76
- /**
77
- * @param {string} source
78
- * @returns {{ fileDefaultWaitMs: number | null, tests: Array<{ name: string, declaredTimeoutMs: number | null, waitLiterals: number[], hasDefaultWaitCall: boolean, deadlineLiterals: number[] }> }}
79
- */
80
- export function parseTestBudgets(source) {
81
- const clean = stripEscapedLines(source);
82
- const fileDefaultWaitMs = parseFileDefaultWaitMs(clean);
83
- const tests = [];
84
- const starts = [];
85
- const re = /^(?:export )?test\(/gm;
86
- for (let m = re.exec(clean); m; m = re.exec(clean)) starts.push(m.index);
87
- for (let i = 0; i < starts.length; i++) {
88
- const chunk = clean.slice(starts[i], starts[i + 1] ?? clean.length);
89
- const nameM = /^(?:export )?test\("([^"]+)"/.exec(chunk);
90
- const toM = /timeout:\s*([0-9_]+)/.exec(chunk.slice(0, 400));
91
- const waitLiterals = [];
92
- let hasDefaultWaitCall = false;
93
- const waitRe = /waitFor\(/g;
94
- for (let w = waitRe.exec(chunk); w; w = waitRe.exec(chunk)) {
95
- const args = callArgs(chunk, w.index + "waitFor".length);
96
- if (args == null) continue;
97
- const literals = waitLiteralsIn(args);
98
- if (literals.length > 0) waitLiterals.push(...literals);
99
- else hasDefaultWaitCall = true;
100
- }
101
- const deadlineLiterals = [];
102
- const dlRe = /timeoutMs:\s*([0-9_]+)/g;
103
- for (let d = dlRe.exec(chunk); d; d = dlRe.exec(chunk)) deadlineLiterals.push(Number(d[1].replace(/_/g, "")));
104
- tests.push({ name: nameM ? nameM[1] : `<test ${i + 1}>`, declaredTimeoutMs: toM ? Number(toM[1].replace(/_/g, "")) : null, waitLiterals, hasDefaultWaitCall, deadlineLiterals });
105
- }
106
- return { fileDefaultWaitMs, tests };
107
- }
108
-
109
- /**
110
- * Rule (a): declared timeout >= max(single explicit wait, file default when a
111
- * call omits it) + margin, margin = max(5000, wait / 3).
112
- * Rule (b): an app deadline >= 30s needs a declared timeout >= it (escape via
113
- * `// budget:` lines, stripped upstream).
114
- * @param {Array<{ path: string, source: string }>} sources
115
- * @returns {Array<{ path: string, test: string, rule: string, message: string }>}
116
- */
117
- export function auditBudgets(sources) {
118
- const violations = [];
119
- for (const { path, source } of sources) {
120
- const parsed = parseTestBudgets(source);
121
- for (const t of parsed.tests) {
122
- const waits = [...t.waitLiterals];
123
- if (t.hasDefaultWaitCall && parsed.fileDefaultWaitMs != null) waits.push(parsed.fileDefaultWaitMs);
124
- if (t.declaredTimeoutMs != null && waits.length > 0) {
125
- const maxWait = Math.max(...waits);
126
- const margin = Math.max(5000, Math.round(maxWait / 3));
127
- if (t.declaredTimeoutMs < maxWait + margin) violations.push({ path, test: t.name, rule: "rule-a", message: `declared timeout ${t.declaredTimeoutMs}ms cannot contain max wait ${maxWait}ms + margin ${margin}ms` });
128
- }
129
- for (const d of t.deadlineLiterals) {
130
- if (d >= 30000 && (t.declaredTimeoutMs == null || t.declaredTimeoutMs < d)) violations.push({ path, test: t.name, rule: "rule-b", message: `app deadline ${d}ms needs a declared test timeout >= it (or a // budget: escape)` });
131
- }
132
- }
133
- }
134
- return violations;
135
- }
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * postinstall: patch vulnerable transitive dependencies that are locked behind
4
- * @earendil-works/pi-coding-agent's npm-shrinkwrap.json (which prevents npm
5
- * "overrides" from taking effect).
6
- *
7
- * Vulnerabilities patched:
8
- * - brace-expansion <=5.0.7 → 5.0.8 (GHSA-3jxr-9vmj-r5cp, GHSA-mh99-v99m-4gvg)
9
- * - protobufjs 7.5.0–7.6.4 → 7.6.5 (GHSA-j3f2-48v5-ccww)
10
- */
11
- import { execSync } from "node:child_process";
12
- import { existsSync, rmSync, mkdirSync } from "node:fs";
13
- import { join, dirname } from "node:path";
14
- import { fileURLToPath } from "node:url";
15
-
16
- const root = join(dirname(fileURLToPath(import.meta.url)), "..");
17
- const nested = join(
18
- root,
19
- "node_modules",
20
- "@earendil-works",
21
- "pi-coding-agent",
22
- "node_modules",
23
- );
24
-
25
- const patches = [
26
- { name: "brace-expansion", version: "5.0.8" },
27
- { name: "protobufjs", version: "7.6.5" },
28
- ];
29
-
30
- for (const { name, version } of patches) {
31
- const target = join(nested, name);
32
- if (!existsSync(target)) {
33
- console.log(`[patch-vulns] ${name} not found nested, skipping`);
34
- continue;
35
- }
36
-
37
- console.log(`[patch-vulns] patching ${name} → ${version}`);
38
- rmSync(target, { recursive: true, force: true });
39
- mkdirSync(target, { recursive: true });
40
-
41
- // Install the patched version into a temp dir, then move it into place
42
- const tmp = join(root, `.patch-tmp-${name}`);
43
- rmSync(tmp, { recursive: true, force: true });
44
- mkdirSync(tmp, { recursive: true });
45
-
46
- try {
47
- execSync(`npm install ${name}@${version} --prefix "${tmp}" --ignore-scripts`, {
48
- stdio: "pipe",
49
- cwd: root,
50
- });
51
- const installed = join(tmp, "node_modules", name);
52
- rmSync(target, { recursive: true, force: true });
53
- execSync(`mv "${installed}" "${target}"`);
54
- } finally {
55
- rmSync(tmp, { recursive: true, force: true });
56
- }
57
- }
58
-
59
- console.log("[patch-vulns] done");