@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,309 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Conventional-commit driven CHANGELOG helper (port of the jfox release
4
- * helper, adapted to this repo's Node stack and `vX.Y.Z` tags).
5
- *
6
- * Modes:
7
- * node scripts/release_helper.mjs [--dry-run] [patch|minor|major|X.Y.Z]
8
- * Compute the next version (default patch) from package.json, parse
9
- * conventional commits since the last `v*` tag, and print a JSON result
10
- * with a `changelog_preview`. Without --dry-run, also insert the section
11
- * at the top of CHANGELOG.md.
12
- * node scripts/release_helper.mjs verify
13
- * Check that every functional commit (feat/fix/refactor/docs/perf) in
14
- * `last v* tag..HEAD` has its trailing (#N) present in the CHANGELOG top
15
- * section. Exits 1 with a missing list when the changelog has drifted
16
- * (e.g. a PR merged after the section was generated).
17
- *
18
- * The pure functions below are exported for unit tests; the CLI layer is the
19
- * only place that touches git or the filesystem.
20
- */
21
- import { execFileSync } from "node:child_process";
22
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
23
- import { dirname, join } from "node:path";
24
- import { fileURLToPath } from "node:url";
25
-
26
- const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
27
- const PACKAGE_JSON = join(REPO_ROOT, "package.json");
28
- const CHANGELOG_MD = join(REPO_ROOT, "CHANGELOG.md");
29
- const REPO_URL = "https://github.com/zhuxixi/pi-agent-board";
30
- const COMPARE_BASE = `${REPO_URL}/compare`;
31
-
32
- /** Conventional commit subject: type[(scope)][!]: message [(#N)] */
33
- const CONVENTIONAL_RE =
34
- /^(feat|fix|perf|refactor|docs|chore|test)(?:\(([^)]*)\))?!?:\s+(.+?)(?:\s+\(#(\d+)\))?$/;
35
- /** Version-squash subjects produced by `npm version` PRs, e.g. `0.5.2 (#74)`. */
36
- const VERSION_SQUASH_RE = /^\d+\.\d+\.\d+(?:[-.][\w.]+)?\s*\(#\d+\)$/;
37
- /** Functional whitelist for verify (CHANGELOG-worthy types). */
38
- const FUNCTIONAL_TYPES = new Set(["feat", "fix", "refactor", "docs", "perf"]);
39
- /** A PR ref sitting at the very end of a subject, e.g. the `(#208)` in `... (#208)`. */
40
- const TRAILING_PR_RE = /\s*\(#(\d+)\)\s*$/;
41
-
42
- /**
43
- * Strip trailing PR refs from a subject, keeping the rightmost as the PR.
44
- *
45
- * GitHub merge/squash subjects can carry the number twice (`... (#208) (#208)`),
46
- * and the conventional regex above captures only the last one, leaving the
47
- * earlier copy inside the message where the renderer appends a ref again
48
- * (issue #218). Refs in the middle of a subject (`... (issue #189) (#196)`) are
49
- * not tail refs and stay verbatim.
50
- * @param {string} subject
51
- * @returns {{message: string, pr: number|null}}
52
- */
53
- export function stripTrailingPrRefs(subject) {
54
- let text = String(subject ?? "").trim();
55
- let pr = null;
56
- for (let m = text.match(TRAILING_PR_RE); m; m = text.match(TRAILING_PR_RE)) {
57
- // Rightmost ref wins: GitHub appends its own number after any that the
58
- // author already wrote, so the first match is the authoritative one.
59
- if (pr === null) pr = Number(m[1]);
60
- text = text.slice(0, m.index).trimEnd();
61
- }
62
- return { message: text, pr };
63
- }
64
-
65
- /**
66
- * Parse commit subject lines into changelog entries.
67
- * @param {Iterable<string>} lines
68
- * @returns {Array<{type: string, scope: string, message: string, pr: number|null}>}
69
- */
70
- export function parseCommitLines(lines) {
71
- const entries = [];
72
- const seen = new Set();
73
- for (const raw of lines) {
74
- const s = String(raw ?? "").trim();
75
- if (!s) continue;
76
- if (VERSION_SQUASH_RE.test(s)) continue;
77
- if (/bump\s+version/i.test(s)) continue;
78
- if (s.startsWith("Merge ")) continue;
79
- const { message: normalized, pr: tailPr } = stripTrailingPrRefs(s);
80
- const m = normalized.match(CONVENTIONAL_RE);
81
- const entry = m
82
- ? { type: m[1], scope: m[2] || "", message: m[3].trim(), pr: tailPr ?? (m[4] ? Number(m[4]) : null) }
83
- : { type: "other", scope: "", message: normalized, pr: tailPr };
84
- // PR-aware key: two commits with the same text merged under different
85
- // PRs must both keep their number (issue #218).
86
- const key = `${entry.type}|${entry.scope}|${entry.message}|${entry.pr ?? ""}`;
87
- if (seen.has(key)) continue;
88
- seen.add(key);
89
- entries.push(entry);
90
- }
91
- return entries;
92
- }
93
-
94
- /**
95
- * Render one changelog section for a version.
96
- * @param {{version: string, date: string, entries: Array<{type: string, scope: string, message: string, pr: number|null}>, prevTag?: string|null}} opts
97
- * @returns {string}
98
- */
99
- export function generateChangelog({ version, date, entries, prevTag = null }) {
100
- const groups = [
101
- ["Features", (e) => e.type === "feat"],
102
- ["Fixes", (e) => e.type === "fix"],
103
- ["Performance", (e) => e.type === "perf"],
104
- ["Changes", (e) => e.type !== "feat" && e.type !== "fix" && e.type !== "perf"],
105
- ];
106
- const lines = [`## [${version}] - ${date}`, ""];
107
- for (const [title, keep] of groups) {
108
- const group = entries.filter(keep);
109
- if (group.length === 0) continue;
110
- lines.push(`### ${title}`, "");
111
- for (const e of group) {
112
- const scope = e.scope ? `**${e.scope}**: ` : "";
113
- // Render defensively: an entry handed in with the ref already in its
114
- // message (parse-time normalization missed, hand-authored fixtures)
115
- // must still emit the number exactly once (issue #218).
116
- const { message, pr: embeddedPr } = stripTrailingPrRefs(e.message);
117
- const prNumber = e.pr ?? embeddedPr;
118
- const pr = prNumber ? ` (#${prNumber})` : "";
119
- lines.push(`- ${scope}${message}${pr}`);
120
- }
121
- lines.push("");
122
- }
123
- const prev = prevTag ? prevTag.replace(/^v/, "") : null;
124
- if (prev) lines.push(`[${version}]: ${COMPARE_BASE}/v${prev}...v${version}`);
125
- return lines.join("\n");
126
- }
127
-
128
- /**
129
- * PR numbers referenced inside the first `## [...]` section of a CHANGELOG.
130
- * @param {string} text
131
- * @returns {Set<number>}
132
- */
133
- export function changelogTopPrs(text) {
134
- // Split before each `## [` heading and take the first real section — the
135
- // JS port of jfox's regex uses split because Python's \Z (absolute end)
136
- // has no JS equivalent and a `$` lookahead matches zero-width.
137
- const sections = String(text ?? "").split(/(?=^##\s*\[)/m);
138
- const first = sections.find((s) => /^##\s*\[/.test(s));
139
- const section = first ?? String(text ?? "");
140
- return new Set([...section.matchAll(/\(#(\d+)\)/g)].map((x) => Number(x[1])));
141
- }
142
-
143
- /**
144
- * Filter subject lines down to functional (CHANGELOG-worthy) commits:
145
- * conventional whitelist types, skipping merges, version squashes, and
146
- * `docs(changelog)` maintenance commits (their PRs fix the changelog itself
147
- * and must not feed back into verify — infinite loop guard, jfox #333).
148
- * @param {Iterable<string>} lines
149
- * @returns {string[]}
150
- */
151
- export function functionalLines(lines) {
152
- const out = [];
153
- for (const raw of lines) {
154
- const s = String(raw ?? "").trim();
155
- if (!s || VERSION_SQUASH_RE.test(s) || /bump\s+version/i.test(s)) continue;
156
- if (s.startsWith("Merge ")) continue;
157
- const m = s.match(/^(\w+)(?:\(([^)]*)\))?!?:/);
158
- if (!m) continue;
159
- if (!FUNCTIONAL_TYPES.has(m[1].toLowerCase())) continue;
160
- if (m[1].toLowerCase() === "docs" && (m[2] || "").toLowerCase().includes("changelog")) continue;
161
- out.push(s);
162
- }
163
- return out;
164
- }
165
-
166
- /**
167
- * Pure verify: functional commit lines vs CHANGELOG text.
168
- * @param {{lines: Iterable<string>, changelogText: string}} input
169
- * @returns {{ok: boolean, missing: number[], extra: number[], functionalCommits: number}}
170
- */
171
- export function verifyFrom({ lines, changelogText }) {
172
- const funcPrs = new Set();
173
- for (const s of functionalLines(lines)) {
174
- const m = s.match(/\(#(\d+)\)\s*$/);
175
- if (m) funcPrs.add(Number(m[1]));
176
- }
177
- const clPrs = changelogTopPrs(changelogText);
178
- const missing = [...funcPrs].filter((n) => !clPrs.has(n)).sort((a, b) => a - b);
179
- const extra = [...clPrs].filter((n) => !funcPrs.has(n)).sort((a, b) => a - b);
180
- return { ok: missing.length === 0, missing, extra, functionalCommits: funcPrs.size };
181
- }
182
-
183
- /**
184
- * Whether the CHANGELOG already has a section for this exact version
185
- * (apply idempotency guard — a re-apply must not duplicate the heading).
186
- * @param {string} existing
187
- * @param {string} version
188
- * @returns {boolean}
189
- */
190
- export function hasSection(existing, version) {
191
- const escaped = version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
192
- return new RegExp(`^##\\s*\\[${escaped}]`, "m").test(String(existing ?? ""));
193
- }
194
-
195
- /**
196
- * Insert a rendered section above the first existing `## ` heading (or append
197
- * after the header when none exists yet).
198
- * @param {string} existing
199
- * @param {string} section
200
- * @returns {string}
201
- */
202
- export function insertSection(existing, section) {
203
- const content = String(existing ?? "");
204
- const insertAt = content.indexOf("\n## ");
205
- if (insertAt === -1) return `${content.replace(/\s*$/, "")}\n\n${section}\n`;
206
- return `${content.slice(0, insertAt + 1)}${section}\n\n${content.slice(insertAt + 1)}`;
207
- }
208
-
209
- /** @returns {string|null} latest `v*` tag, null when the repo has none, throws on git errors (fail-closed) */
210
- function lastTag(root = REPO_ROOT) {
211
- const tag = gitOut(["tag", "--list", "v*"], root);
212
- const tags = tag.split("\n").map((s) => s.trim()).filter(Boolean);
213
- if (tags.length === 0) return null;
214
- return gitOut(["describe", "--tags", "--abbrev=0", "--match", "v*"], root).trim() || null;
215
- }
216
-
217
- /** @param {string[]} args @param {string} root @returns {string} */
218
- function gitOut(args, root) {
219
- return execFileSync("git", args, { cwd: root, encoding: "utf8" });
220
- }
221
-
222
- /** @returns {string[]} commit subjects in `lastTag..HEAD` (or all when no tag); throws on git errors */
223
- function commitSubjects(tag, root = REPO_ROOT) {
224
- const range = tag ? `${tag}..HEAD` : "HEAD";
225
- return execFileSync("git", ["log", range, "--format=%s"], { cwd: root, encoding: "utf8" })
226
- .split("\n")
227
- .map((s) => s.trim())
228
- .filter(Boolean);
229
- }
230
-
231
- /** @returns {string} current version from package.json */
232
- function currentVersion() {
233
- const pkg = JSON.parse(readFileSync(PACKAGE_JSON, "utf8"));
234
- if (!/^\d+\.\d+\.\d+$/.test(pkg.version ?? "")) throw new Error(`package.json has no semver version: ${pkg.version}`);
235
- return pkg.version;
236
- }
237
-
238
- /** @param {string} arg @param {string} current */
239
- export function nextVersion(arg, current) {
240
- const cur = current.split(".").map(Number);
241
- if (arg === "major") return `${cur[0] + 1}.0.0`;
242
- if (arg === "minor") return `${cur[0]}.${cur[1] + 1}.0`;
243
- if (arg === "patch") return `${cur[0]}.${cur[1]}.${cur[2] + 1}`;
244
- if (/^\d+\.\d+\.\d+$/.test(arg)) {
245
- // Component-wise compare — lexicographic join(".") misorders "0.10.0" vs "0.5.2".
246
- const parts = arg.split(".").map(Number);
247
- for (let i = 0; i < 3; i++) {
248
- if (parts[i] !== cur[i]) {
249
- if (parts[i] < cur[i]) throw new Error(`version ${arg} is not greater than current ${current}`);
250
- break;
251
- }
252
- }
253
- if (arg === current) throw new Error(`version ${arg} is not greater than current ${current}`);
254
- return arg;
255
- }
256
- throw new Error(`invalid version or bump type: ${arg} (expected patch/minor/major or X.Y.Z)`);
257
- }
258
-
259
- function main(argv) {
260
- if (argv[0] === "verify") {
261
- let result;
262
- try {
263
- const tag = lastTag();
264
- const lines = tag ? commitSubjects(tag) : [];
265
- const changelogText = existsSync(CHANGELOG_MD) ? readFileSync(CHANGELOG_MD, "utf8") : "";
266
- result = verifyFrom({ lines, changelogText });
267
- result.lastTag = tag;
268
- } catch (err) {
269
- // Fail-closed: a git/filesystem failure must block the release, not pass it.
270
- result = { ok: false, error: String(err?.message ?? err), missing: [], extra: [], functionalCommits: 0, lastTag: null };
271
- }
272
- console.log(JSON.stringify(result));
273
- process.exitCode = result.ok ? 0 : 1;
274
- return;
275
- }
276
- const dryRun = argv.includes("--dry-run");
277
- const versionArg = argv.find((a) => !a.startsWith("--")) ?? "patch";
278
- try {
279
- const current = currentVersion();
280
- const version = nextVersion(versionArg, current);
281
- const tag = lastTag();
282
- const entries = parseCommitLines(tag ? commitSubjects(tag) : commitSubjects(null));
283
- const date = new Date().toISOString().slice(0, 10);
284
- const changelogPreview = generateChangelog({ version, date, entries, prevTag: tag });
285
- const result = {
286
- current_version: current,
287
- new_version: version,
288
- last_tag: tag,
289
- changelog_summary: `${entries.filter((e) => e.type === "feat").length} features, ${entries.filter((e) => e.type === "fix").length} fixes, ${entries.filter((e) => !["feat", "fix"].includes(e.type)).length} changes`,
290
- changelog_preview: changelogPreview,
291
- };
292
- if (!dryRun) {
293
- const existing = existsSync(CHANGELOG_MD) ? readFileSync(CHANGELOG_MD, "utf8") : "# Changelog\n";
294
- if (hasSection(existing, version)) {
295
- throw new Error(`CHANGELOG.md already has a [${version}] section; remove the stale section before re-applying`);
296
- }
297
- writeFileSync(CHANGELOG_MD, insertSection(existing, changelogPreview));
298
- result.files_modified = ["CHANGELOG.md"];
299
- }
300
- console.log(JSON.stringify(result, null, 1));
301
- } catch (err) {
302
- console.log(JSON.stringify({ error: String(err?.message ?? err) }));
303
- process.exitCode = 1;
304
- }
305
- }
306
-
307
- if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
308
- main(process.argv.slice(2));
309
- }
@@ -1,40 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Authoritative entry for the A11 perf gate (issue #121).
4
- *
5
- * Loudly refuses under coverage instrumentation: c8 sets NODE_V8_COVERAGE
6
- * for its whole process tree, and instrumented latency measurements are
7
- * invalid (2.5–6× inflation). Silently skipping here would manufacture a
8
- * fake-green gate, so refusal is a nonzero exit with an explicit message.
9
- *
10
- * This script is the side-effect layer only — no decision logic, no
11
- * measurement. The pure gate decision lives in test-support/perf-gate.mjs
12
- * and serves the test file; do not merge the layers (spec §3.2/§4).
13
- */
14
- import { spawn } from "node:child_process";
15
- import { fileURLToPath } from "node:url";
16
-
17
- if (process.env.NODE_V8_COVERAGE !== undefined) {
18
- console.error(
19
- "run-perf-gate: refusing to measure under coverage instrumentation " +
20
- "(NODE_V8_COVERAGE is set). Perf assertions are only valid without c8; " +
21
- "run `npm run test:perf` directly, outside any coverage wrapper.",
22
- );
23
- process.exit(1);
24
- }
25
-
26
- const perfTest = fileURLToPath(new URL("../test/terminal-model-perf.test.mjs", import.meta.url));
27
- // No --test-concurrency=1: the flag requires Node 21+ but package.json
28
- // declares engines >=20, and it is a no-op while the gate runs a single
29
- // file (spec D3, revised 2026-09-20). Re-add it when a second perf file
30
- // lands — parallel measurement across files must stay impossible.
31
- const child = spawn(
32
- process.execPath,
33
- ["--test", perfTest],
34
- { stdio: "inherit", env: { ...process.env, AGENT_BOARD_PERF_GATE: "1" } },
35
- );
36
- child.on("error", (err) => {
37
- console.error(`run-perf-gate: failed to spawn the perf suite: ${err.message}`);
38
- process.exit(1);
39
- });
40
- child.on("exit", (code) => process.exit(code ?? 1));