@huseynsnmz/pi-board 0.15.3 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/CHANGELOG.md +24 -4
  2. package/CONTRIBUTING.md +7 -0
  3. package/README.md +55 -419
  4. package/SECURITY.md +19 -0
  5. package/docs/how-it-works.md +31 -0
  6. package/docs/reference.md +277 -0
  7. package/package.json +5 -11
  8. package/src/commands/agent-board.ts +26 -2
  9. package/src/commands/attach-flow.ts +4 -0
  10. package/src/core/double-press.mjs +6 -2
  11. package/src/core/slash-commands.mjs +52 -0
  12. package/src/index.ts +1 -1
  13. package/src/runtime/service.mjs +12 -0
  14. package/src/ui/dashboard.ts +59 -25
  15. package/src/ui/pty-attach.ts +8 -3
  16. package/IMPLEMENTATION_PLAN.md +0 -920
  17. package/PRD.md +0 -484
  18. package/PROGRESS.md +0 -142
  19. package/VERIFY.md +0 -115
  20. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  21. package/docs/EXPLORATION.md +0 -187
  22. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  23. package/docs/RELEASE.md +0 -148
  24. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  25. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  26. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  27. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  28. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  29. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  30. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  31. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  32. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  33. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  34. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  35. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  36. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  37. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  38. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  39. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  40. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  41. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  42. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  43. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  44. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  45. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  46. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  47. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  48. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  49. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  50. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  51. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  52. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  53. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  54. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  55. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  56. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  57. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  58. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  59. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  60. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  61. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  62. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  63. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  64. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  65. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  66. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  67. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  68. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  69. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  70. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  71. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  72. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  73. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  74. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  75. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  76. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  77. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  78. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  79. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  80. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  81. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  82. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  83. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  84. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  85. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  86. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  87. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  88. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  89. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  90. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  91. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  92. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  93. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  94. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  95. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  96. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  97. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  98. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  99. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  100. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  101. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  102. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  103. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  104. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  105. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  106. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  107. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  108. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  109. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  110. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  111. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  112. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  113. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  114. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  115. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  116. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  117. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  118. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  119. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  120. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  121. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  122. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  123. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  124. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  125. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  126. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  127. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  128. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  129. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  130. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  131. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  132. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  133. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  134. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  135. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  136. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  137. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  138. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  139. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  140. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  141. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  142. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  143. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  144. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  145. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  146. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  147. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  148. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  149. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  150. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  151. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  152. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  153. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  154. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  155. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  156. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  157. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  158. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  159. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  160. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  161. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  162. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  163. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  164. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  165. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  166. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  167. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  168. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  169. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  170. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  171. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  172. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  173. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  174. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  175. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  176. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  177. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  178. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  179. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  180. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  181. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  182. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  183. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  184. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  185. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  186. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  187. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  188. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  189. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  190. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  191. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  192. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  193. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  194. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  195. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  196. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  197. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  198. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  199. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  200. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  201. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  202. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  203. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  204. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  205. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  206. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  207. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  208. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  209. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  210. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  211. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  212. package/scripts/budget-audit.mjs +0 -135
  213. package/scripts/release.mjs +0 -446
  214. package/scripts/release_helper.mjs +0 -309
  215. package/scripts/run-perf-gate.mjs +0 -40
  216. package/scripts/run-tests.mjs +0 -375
@@ -1,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));