@huseynsnmz/pi-board 0.15.3 → 0.16.0

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