heyspex 0.1.0 → 0.2.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 (322) hide show
  1. package/README.md +21 -0
  2. package/bin/hs.mjs +931 -98
  3. package/bin/spexcode.mjs +262 -0
  4. package/dev-factory/engine/worktree.mjs +740 -0
  5. package/dev-factory/lib/code-repo-root.mjs +67 -0
  6. package/dev-factory/lib/context-budget.mjs +84 -0
  7. package/dev-factory/lib/epic-detect.mjs +190 -0
  8. package/dev-factory/lib/fleet-queue-file.mjs +846 -0
  9. package/dev-factory/lib/fleet-queue.mjs +426 -0
  10. package/dev-factory/lib/fleet-state.mjs +14 -18
  11. package/dev-factory/lib/model-catalog.mjs +338 -0
  12. package/dev-factory/lib/seat-context.mjs +199 -0
  13. package/dev-factory/lib/spec-io.mjs +93 -0
  14. package/dev-factory/lib/sufficiency-score.mjs +8 -2
  15. package/dev-factory/lib/worktree-allocator.mjs +744 -0
  16. package/dev-factory/model-catalog.json +222 -0
  17. package/dist/cli/hs-check.mjs +1085 -103
  18. package/dist/cli/hs-claim.mjs +936 -63
  19. package/dist/cli/hs-connect.mjs +9786 -1910
  20. package/dist/cli/hs-defer.mjs +5267 -3053
  21. package/dist/cli/hs-dev.mjs +564 -0
  22. package/dist/cli/hs-env.mjs +385 -299
  23. package/dist/cli/hs-index-rebuild.mjs +7129 -0
  24. package/dist/cli/hs-index-reconcile.mjs +7852 -0
  25. package/dist/cli/hs-init.mjs +777 -138
  26. package/dist/cli/hs-install.mjs +6583 -485
  27. package/dist/cli/hs-join.mjs +10137 -2368
  28. package/dist/cli/hs-vault-pull.mjs +4659 -5882
  29. package/dist/cli/hs-vault.mjs +3793 -1537
  30. package/dist/cli/hs-workspace.mjs +2550 -524
  31. package/dist/cli-templates/customer-vault/specs/entity-model.md +594 -601
  32. package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +110 -177
  33. package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -0
  34. package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +13 -15
  35. package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +91 -0
  36. package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +150 -168
  37. package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -0
  38. package/dist/cli-templates/harness-commands/claude/hs-auto.md +36 -0
  39. package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -0
  40. package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -0
  41. package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -0
  42. package/dist/cli-templates/harness-commands/claude/hs-dev.md +20 -0
  43. package/dist/cli-templates/harness-commands/claude/hs-doctor.md +32 -0
  44. package/dist/cli-templates/harness-commands/claude/hs-ground.md +28 -0
  45. package/dist/cli-templates/harness-commands/claude/hs-handoff.md +33 -0
  46. package/dist/cli-templates/harness-commands/claude/hs-log.md +27 -0
  47. package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -0
  48. package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -0
  49. package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -0
  50. package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -0
  51. package/dist/cli-templates/harness-commands/claude/hs-res.md +63 -0
  52. package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -0
  53. package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -0
  54. package/dist/cli-templates/harness-commands/claude/hs-view.md +26 -0
  55. package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -0
  56. package/dist/cli-templates/harness-commands/claude/prototype.md +91 -0
  57. package/dist/cli-templates/harness-commands/cursor/hs-auto.md +40 -0
  58. package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +31 -0
  59. package/dist/cli-templates/harness-commands/cursor/hs-capture.md +65 -0
  60. package/dist/cli-templates/harness-commands/cursor/hs-defer.md +32 -0
  61. package/dist/cli-templates/harness-commands/cursor/hs-dev.md +24 -0
  62. package/dist/cli-templates/harness-commands/cursor/hs-doctor.md +36 -0
  63. package/dist/cli-templates/harness-commands/cursor/hs-ground.md +31 -0
  64. package/dist/cli-templates/harness-commands/cursor/hs-handoff.md +37 -0
  65. package/dist/cli-templates/harness-commands/cursor/hs-log.md +31 -0
  66. package/dist/cli-templates/harness-commands/cursor/hs-next.md +32 -0
  67. package/dist/cli-templates/harness-commands/cursor/hs-promote.md +73 -0
  68. package/dist/cli-templates/harness-commands/cursor/hs-push.md +38 -0
  69. package/dist/cli-templates/harness-commands/cursor/hs-qc.md +30 -0
  70. package/dist/cli-templates/harness-commands/cursor/hs-res.md +67 -0
  71. package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +26 -0
  72. package/dist/cli-templates/harness-commands/cursor/hs-review.md +26 -0
  73. package/dist/cli-templates/harness-commands/cursor/hs-view.md +30 -0
  74. package/dist/cli-templates/harness-commands/cursor/kill-ports.md +35 -0
  75. package/dist/cli-templates/harness-commands/cursor/prototype.md +95 -0
  76. package/docs/mcp-client-config.md +259 -6
  77. package/package.json +231 -124
  78. package/plugins/heyspex/.claude-plugin/plugin.json +12 -0
  79. package/plugins/heyspex/.cursor-plugin/plugin.json +15 -0
  80. package/plugins/heyspex/.mcp.json +12 -0
  81. package/plugins/heyspex/bin/launch-mcp.mjs +95 -0
  82. package/plugins/heyspex/bin/run-hook.mjs +62 -0
  83. package/plugins/heyspex/hooks/cursor-hooks.json +227 -0
  84. package/plugins/heyspex/hooks/hooks.json +361 -0
  85. package/plugins/heyspex/mcp.json +13 -0
  86. package/scripts/check-mcp-freshness.mjs +17 -9
  87. package/scripts/claude-hooks/_env-vault-events.mjs +68 -0
  88. package/scripts/claude-hooks/_guardrail-events.mjs +96 -0
  89. package/scripts/claude-hooks/_guardrail-loader.mjs +494 -0
  90. package/scripts/claude-hooks/_intent-classifier-router.mjs +410 -0
  91. package/scripts/claude-hooks/_intent-classifier-semantic.mjs +351 -0
  92. package/scripts/claude-hooks/_run.mjs +120 -0
  93. package/scripts/claude-hooks/_transcript-text.mjs +60 -0
  94. package/scripts/claude-hooks/_vault-retrieval.mjs +322 -0
  95. package/scripts/claude-hooks/agent-spawn-delegation-gate.mjs +133 -0
  96. package/scripts/claude-hooks/capture-workflow-gate.mjs +11 -0
  97. package/scripts/claude-hooks/check-auto-memory-budget.mjs +396 -0
  98. package/scripts/claude-hooks/check-claude-md-budget.mjs +327 -0
  99. package/scripts/claude-hooks/check-mcp-freshness-hook.mjs +14 -0
  100. package/scripts/claude-hooks/claim-before-code-gate.mjs +11 -0
  101. package/scripts/claude-hooks/claim-liveness-shared.mjs +575 -0
  102. package/scripts/claude-hooks/close-terminal-on-ship.mjs +64 -0
  103. package/scripts/claude-hooks/coo-denylist-gate.mjs +11 -0
  104. package/scripts/claude-hooks/coo-guard-check.mjs +11 -0
  105. package/scripts/claude-hooks/deep-research-cost-gate.mjs +11 -0
  106. package/scripts/claude-hooks/dev-server-task-reminder.mjs +15 -0
  107. package/scripts/claude-hooks/emit-claim-status.mjs +41 -0
  108. package/scripts/claude-hooks/enforce-session-sequence.mjs +11 -0
  109. package/scripts/claude-hooks/enforce-vault-naming.mjs +11 -0
  110. package/scripts/claude-hooks/env-vault-guard.mjs +11 -0
  111. package/scripts/claude-hooks/epic-milestone-phrasing-gate.mjs +11 -0
  112. package/scripts/claude-hooks/git-identity-guard.mjs +11 -0
  113. package/scripts/claude-hooks/git-reflog-audit.mjs +15 -0
  114. package/scripts/claude-hooks/git-stash-gate.mjs +11 -0
  115. package/scripts/claude-hooks/guard-auto-memory-writes.mjs +137 -0
  116. package/scripts/claude-hooks/id-display-format-gate.mjs +11 -0
  117. package/scripts/claude-hooks/inject-communication-style.mjs +66 -0
  118. package/scripts/claude-hooks/inject-coo-seat.mjs +78 -0
  119. package/scripts/claude-hooks/inject-dev-server-awareness.mjs +11 -0
  120. package/scripts/claude-hooks/inject-doctrine.mjs +13 -0
  121. package/scripts/claude-hooks/inject-vault-context.mjs +14 -0
  122. package/scripts/claude-hooks/intent-classifier-trigger.mjs +247 -0
  123. package/scripts/claude-hooks/log-session-activity.mjs +341 -0
  124. package/scripts/claude-hooks/naughty-list-gate.mjs +11 -0
  125. package/scripts/claude-hooks/notify-status-flip.mjs +72 -0
  126. package/scripts/claude-hooks/plan-exit-ingest-gate.mjs +170 -0
  127. package/scripts/claude-hooks/plan-ingest-close-reminder.mjs +66 -0
  128. package/scripts/claude-hooks/plan-review-gate.mjs +11 -0
  129. package/scripts/claude-hooks/preflight-reminder.mjs +262 -0
  130. package/scripts/claude-hooks/protected-action-intent.mjs +221 -0
  131. package/scripts/claude-hooks/research-method-guard.mjs +398 -0
  132. package/scripts/claude-hooks/research-protocol-gate.mjs +11 -0
  133. package/scripts/claude-hooks/session-end.mjs +449 -0
  134. package/scripts/claude-hooks/settings-write-gate.mjs +11 -0
  135. package/scripts/claude-hooks/settlement-check-gate.mjs +39 -0
  136. package/scripts/claude-hooks/status-flip-detect.mjs +17 -0
  137. package/scripts/claude-hooks/terminal-close-shared.mjs +426 -0
  138. package/scripts/claude-hooks/terminal-close-watcher.mjs +122 -0
  139. package/scripts/claude-hooks/wip-checkpoint.mjs +298 -0
  140. package/scripts/cursor-hooks/manifest.mjs +235 -0
  141. package/scripts/dev-server-status.mjs +34 -13
  142. package/scripts/dev-server-stop.mjs +313 -0
  143. package/scripts/heyspex-mode.mjs +268 -0
  144. package/scripts/hs-auto-resolve.mjs +240 -0
  145. package/scripts/hs-auto-source.mjs +398 -0
  146. package/scripts/hs-auto.mjs +134 -0
  147. package/scripts/hs-capture-cli.mjs +326 -0
  148. package/scripts/hs-classifier.mjs +136 -0
  149. package/scripts/hs-companion.mjs +88 -0
  150. package/scripts/hs-gateway.mjs +115 -0
  151. package/scripts/hs-mode.mjs +125 -0
  152. package/scripts/hs-next.mjs +441 -0
  153. package/scripts/hs-settings.mjs +338 -0
  154. package/scripts/hs-view.mjs +333 -0
  155. package/scripts/hs-worktree.mjs +201 -0
  156. package/scripts/install-claude-code-hooks.mjs +270 -0
  157. package/scripts/install-cursor-hooks.mjs +471 -0
  158. package/scripts/lib/agent-env-markers.mjs +38 -0
  159. package/scripts/lib/console-url-resolver.mjs +197 -60
  160. package/scripts/lib/customer-kit.mjs +886 -0
  161. package/scripts/lib/dev-mode.mjs +244 -28
  162. package/scripts/lib/heyspex-home.mjs +72 -0
  163. package/scripts/lib/hs-command.mjs +277 -0
  164. package/scripts/lib/hs-companion-settings.mjs +472 -0
  165. package/scripts/lib/hs-source-commit.mjs +59 -5
  166. package/scripts/lib/hs-source-identity.mjs +526 -0
  167. package/scripts/lib/hs-source-log.mjs +11 -21
  168. package/scripts/lib/hs-source-push.mjs +107 -58
  169. package/scripts/lib/hs-worktree-args.mjs +115 -0
  170. package/scripts/lib/shared-path-helpers.mjs +134 -0
  171. package/scripts/lib/standalone-doctor.mjs +540 -3
  172. package/scripts/lib/subagent-git-lock.mjs +109 -0
  173. package/scripts/lib/template-pin.mjs +34 -0
  174. package/scripts/provenance-lib.mjs +91 -0
  175. package/scripts/stamp-provenance-on-edit.mjs +222 -0
  176. package/scripts/wip-ledger-health.mjs +653 -0
  177. package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -0
  178. package/spexharness/src/adapters/audit.mjs +197 -0
  179. package/spexharness/src/adapters/claude-code.mjs +441 -0
  180. package/spexharness/src/chat/tui/ansi.mjs +448 -0
  181. package/spexharness/src/chat/tui/palette.mjs +22 -0
  182. package/spexharness/src/chat/tui/select.mjs +208 -0
  183. package/spexharness/src/config-root.mjs +54 -0
  184. package/spexharness/src/context-budget.mjs +141 -0
  185. package/spexharness/src/dev-server-control.mjs +389 -0
  186. package/spexharness/src/doctrine/behaviour.mjs +371 -0
  187. package/spexharness/src/doctrine/corpus.mjs +733 -0
  188. package/spexharness/src/doctrine/inject.mjs +338 -0
  189. package/spexharness/src/doctrine/merge.mjs +184 -0
  190. package/spexharness/src/doctrine/sources.mjs +252 -0
  191. package/spexharness/src/doctrine/synthesize.mjs +401 -0
  192. package/spexharness/src/doctrine/vault-source.mjs +574 -0
  193. package/spexharness/src/doctrine/writer.mjs +176 -0
  194. package/spexharness/src/events.mjs +84 -0
  195. package/spexharness/src/gates/capture-workflow-gate.mjs +202 -0
  196. package/spexharness/src/gates/claim-before-code-gate.decide.mjs +33 -0
  197. package/spexharness/src/gates/claim-before-code-gate.mjs +129 -0
  198. package/spexharness/src/gates/deep-research-cost-gate.mjs +274 -0
  199. package/spexharness/src/gates/enforce-session-sequence.mjs +895 -0
  200. package/spexharness/src/gates/enforce-vault-naming.mjs +215 -0
  201. package/spexharness/src/gates/epic-milestone-phrasing-gate.mjs +233 -0
  202. package/spexharness/src/gates/git-identity-guard.decide.mjs +436 -0
  203. package/spexharness/src/gates/git-identity-guard.mjs +61 -0
  204. package/spexharness/src/gates/git-reflog-audit.mjs +151 -0
  205. package/spexharness/src/gates/git-stash-gate.decide.mjs +361 -0
  206. package/spexharness/src/gates/git-stash-gate.mjs +159 -0
  207. package/spexharness/src/gates/id-display-format-gate.mjs +190 -0
  208. package/spexharness/src/gates/inject-doctrine-gate.mjs +89 -0
  209. package/spexharness/src/gates/inject-vault-context.mjs +239 -0
  210. package/spexharness/src/gates/naughty-list-gate.mjs +213 -0
  211. package/spexharness/src/gates/plan-review-gate.mjs +233 -0
  212. package/spexharness/src/gates/preflight-reminder-gate.mjs +265 -0
  213. package/spexharness/src/gates/research-protocol-gate.mjs +175 -0
  214. package/spexharness/src/gates/settings-write-gate.decide.mjs +96 -0
  215. package/spexharness/src/gates/settings-write-gate.mjs +104 -0
  216. package/spexharness/src/git-reflog-audit.mjs +294 -0
  217. package/spexharness/src/intent-classifier.mjs +194 -0
  218. package/spexharness/src/intent-directive.mjs +541 -0
  219. package/spexharness/src/profile/learn.mjs +551 -0
  220. package/spexharness/src/profile/proposals.mjs +357 -0
  221. package/spexharness/src/profile/vault-inclusion.mjs +386 -0
  222. package/spexharness/src/redact.mjs +151 -0
  223. package/spexharness/src/rule-table.mjs +819 -0
  224. package/spexharness/src/seat-context.mjs +122 -0
  225. package/spexharness/src/session-bootstrap.mjs +512 -0
  226. package/spexharness/src/session-record.mjs +815 -0
  227. package/spexharness/src/token-inventory.mjs +207 -0
  228. package/vendor/heyspex-mcp/account-switch.mjs +95 -0
  229. package/vendor/heyspex-mcp/alignment-gate.mjs +315 -1
  230. package/vendor/heyspex-mcp/blocker-resolver.mjs +37 -2
  231. package/vendor/heyspex-mcp/breakdown-ladder.mjs +119 -4
  232. package/vendor/heyspex-mcp/breakdown-source.mjs +271 -0
  233. package/vendor/heyspex-mcp/breakdown-writer.mjs +23 -3
  234. package/vendor/heyspex-mcp/capture-buffer-patches.mjs +46 -5
  235. package/vendor/heyspex-mcp/capture-channel.mjs +55 -5
  236. package/vendor/heyspex-mcp/capture-console-client.mjs +80 -1
  237. package/vendor/heyspex-mcp/capture-focus.mjs +1035 -29
  238. package/vendor/heyspex-mcp/capture-outbox.mjs +5 -1
  239. package/vendor/heyspex-mcp/capture-tools.mjs +1219 -0
  240. package/vendor/heyspex-mcp/claim-tools.mjs +1124 -0
  241. package/vendor/heyspex-mcp/cli-verbs.mjs +2076 -0
  242. package/vendor/heyspex-mcp/cli.mjs +201 -15524
  243. package/vendor/heyspex-mcp/cloud-tool-surface.mjs +128 -0
  244. package/vendor/heyspex-mcp/cloud-vault-writes.mjs +412 -0
  245. package/vendor/heyspex-mcp/config-seed.yaml +15 -0
  246. package/vendor/heyspex-mcp/config-settings.mjs +290 -7
  247. package/vendor/heyspex-mcp/console-fetch.mjs +657 -16
  248. package/vendor/heyspex-mcp/console-transport.mjs +166 -0
  249. package/vendor/heyspex-mcp/credentials.mjs +584 -0
  250. package/vendor/heyspex-mcp/defer-spec.mjs +44 -4
  251. package/vendor/heyspex-mcp/deferred-reclaim.mjs +29 -3
  252. package/vendor/heyspex-mcp/design-handlers.mjs +31 -0
  253. package/vendor/heyspex-mcp/design-tools.mjs +4 -1
  254. package/vendor/heyspex-mcp/dev-factory-bridge.mjs +4 -4
  255. package/vendor/heyspex-mcp/dev-mode-core.mjs +45 -0
  256. package/vendor/heyspex-mcp/dev-tools.mjs +124 -0
  257. package/vendor/heyspex-mcp/diagnose-handlers.mjs +54 -0
  258. package/vendor/heyspex-mcp/events-tools.mjs +207 -0
  259. package/vendor/heyspex-mcp/filing-tools.mjs +688 -0
  260. package/vendor/heyspex-mcp/fleet-handlers.mjs +37 -0
  261. package/vendor/heyspex-mcp/fleet-queue-tools.mjs +69 -14
  262. package/vendor/heyspex-mcp/focus-tools.mjs +190 -0
  263. package/vendor/heyspex-mcp/folder-trust-store.mjs +59 -0
  264. package/vendor/heyspex-mcp/http-transport.mjs +300 -0
  265. package/vendor/heyspex-mcp/list-tools.mjs +257 -0
  266. package/vendor/heyspex-mcp/login-handoff.mjs +15 -1
  267. package/vendor/heyspex-mcp/mcp-config.mjs +94 -0
  268. package/vendor/heyspex-mcp/memory-tools.mjs +523 -0
  269. package/vendor/heyspex-mcp/mvp-mode.mjs +53 -6
  270. package/vendor/heyspex-mcp/occupied-keys.mjs +141 -0
  271. package/vendor/heyspex-mcp/on-deck-brief.mjs +3360 -0
  272. package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -0
  273. package/vendor/heyspex-mcp/package-root.mjs +32 -0
  274. package/vendor/heyspex-mcp/peer-presence.mjs +8 -1
  275. package/vendor/heyspex-mcp/plan-ground.mjs +621 -652
  276. package/vendor/heyspex-mcp/plan-push.mjs +906 -21
  277. package/vendor/heyspex-mcp/plan-review.mjs +124 -30
  278. package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
  279. package/vendor/heyspex-mcp/procedure-handlers.mjs +325 -0
  280. package/vendor/heyspex-mcp/procedure-tools.mjs +540 -351
  281. package/vendor/heyspex-mcp/promote-capture.mjs +386 -69
  282. package/vendor/heyspex-mcp/promote-diagnose.mjs +74 -0
  283. package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +9 -2
  284. package/vendor/heyspex-mcp/promote-research.mjs +660 -53
  285. package/vendor/heyspex-mcp/promotion-helpers.mjs +251 -0
  286. package/vendor/heyspex-mcp/prompts.mjs +1460 -0
  287. package/vendor/heyspex-mcp/read-path.mjs +29 -9
  288. package/vendor/heyspex-mcp/receipt-shape.mjs +146 -0
  289. package/vendor/heyspex-mcp/recipe-handlers.mjs +117 -0
  290. package/vendor/heyspex-mcp/regen-tasks.mjs +210 -3
  291. package/vendor/heyspex-mcp/register-spec.mjs +266 -54
  292. package/vendor/heyspex-mcp/request-context.mjs +59 -0
  293. package/vendor/heyspex-mcp/research-focus.mjs +207 -4
  294. package/vendor/heyspex-mcp/research-handlers.mjs +623 -0
  295. package/vendor/heyspex-mcp/research-tools.mjs +4 -4
  296. package/vendor/heyspex-mcp/response-budget.mjs +348 -0
  297. package/vendor/heyspex-mcp/review-focus.mjs +25 -1
  298. package/vendor/heyspex-mcp/search-specs.mjs +280 -0
  299. package/vendor/heyspex-mcp/seat-block-ledger.mjs +113 -0
  300. package/vendor/heyspex-mcp/seat-identity.mjs +2 -6
  301. package/vendor/heyspex-mcp/session-owner.mjs +108 -0
  302. package/vendor/heyspex-mcp/session-tools.mjs +152 -0
  303. package/vendor/heyspex-mcp/settings-registry.mjs +232 -0
  304. package/vendor/heyspex-mcp/settings-tools.mjs +85 -12
  305. package/vendor/heyspex-mcp/spec-edit-tools.mjs +446 -0
  306. package/vendor/heyspex-mcp/spec-id-lib.mjs +93 -17
  307. package/vendor/heyspex-mcp/split-research.mjs +301 -56
  308. package/vendor/heyspex-mcp/stdio-rpc.mjs +22 -0
  309. package/vendor/heyspex-mcp/submit-for-review.mjs +132 -6
  310. package/vendor/heyspex-mcp/summary-contract.mjs +105 -0
  311. package/vendor/heyspex-mcp/summary-write-tools.mjs +224 -0
  312. package/vendor/heyspex-mcp/symptom-query.mjs +146 -0
  313. package/vendor/heyspex-mcp/test-helpers.mjs +108 -1
  314. package/vendor/heyspex-mcp/tool-catalog.mjs +2813 -0
  315. package/vendor/heyspex-mcp/tool-dispatch.mjs +639 -0
  316. package/vendor/heyspex-mcp/tool-helpers.mjs +325 -0
  317. package/vendor/heyspex-mcp/validate-linkage.mjs +414 -27
  318. package/vendor/heyspex-mcp/vault-io.mjs +724 -0
  319. package/vendor/heyspex-mcp/vault-less-endpoints.mjs +52 -0
  320. package/vendor/heyspex-mcp/vault-resolve.mjs +366 -68
  321. package/vendor/heyspex-mcp/verify-reproduction.mjs +270 -30
  322. package/vendor/heyspex-mcp/whoami.mjs +91 -0
@@ -0,0 +1,449 @@
1
+ #!/usr/bin/env node
2
+ // Claude Code SessionEnd hook (HS-EPIC-NWUE / HS-EPIC-2BKX; engine wiring per
3
+ // task HS-U618, epic HS-Y646 "session bootstrap and stop for every seat").
4
+ //
5
+ // Fires when a Claude Code session ends and does TWO independent things:
6
+ //
7
+ // 1. Console claim relay (original behavior, unchanged): posts the session
8
+ // id + termination `source` to `/api/agent/session-end`, which
9
+ // (server-side, tier-correctly) finds the matching active claim and
10
+ // releases it so the What's Next hero flips the Launch CTA back to
11
+ // "Re-spawn session". This is the FAST path for "the agent closed" — the
12
+ // 10-min stale timeout is the backstop for hard window kills / SIGKILL,
13
+ // which do NOT fire SessionEnd. Source gating lives SERVER-side (the
14
+ // route decides which `source` values count as "closed") so it's
15
+ // unit-testable and so this half stays a dumb relay. Notably the route
16
+ // must NOT release on source `resume` (session suspended for
17
+ // `--resume`/`--continue`) or `clear` (`/clear`) — those mean the
18
+ // session lives on.
19
+ //
20
+ // 2. Engine session-end (new, HS-U618): records the WIP hand-off, releases
21
+ // the session's active claims, and exports its events — via
22
+ // `runSessionEnd()` below — by calling straight into
23
+ // spexharness/src/session-record.mjs's durable session store
24
+ // (`createSessionStore`'s `updateWip`/`releaseClaim`/`finishSession`)
25
+ // rather than re-deriving that logic here (epic scope: "the plugin's
26
+ // SessionEnd and Stop hooks as engine calls"). This only does anything
27
+ // for a session the harness itself bootstrapped (spexharness's
28
+ // `createSession`) — a plain Claude Code plugin session has no durable
29
+ // record, so `runSessionEnd` reads `null` and no-ops. Idempotent: a
30
+ // session already sealed (`endedAt` set) is read back as closed and
31
+ // skipped rather than re-recorded, so this hook firing twice for one
32
+ // session (a duplicate SessionEnd, a retried close) never double-writes.
33
+ //
34
+ // Both halves are independently fail-open — MUST exit 0 on every failure
35
+ // path, a hook that breaks Claude Code is worse than a hook that silently
36
+ // does nothing (mirrors emit-claim-status.mjs) — and neither half's failure
37
+ // affects the other.
38
+ //
39
+ // Console URL resolution delegates to the ONE shared resolver
40
+ // (scripts/lib/console-url-resolver.mjs, [[HS-GD45-MCPC-TASK]]) — MODE-AWARE
41
+ // precedence ([[HS-VOJ7-YL1C-DIAG-console-url-resolver-ignores-dev-mode]]):
42
+ // HEYSPEX_CONSOLE_URL env → ~/.heyspex/credentials.yaml `console_url` →
43
+ // .heyspex/console-url discovery file (local reliance ONLY) → the console URL
44
+ // derived from the resolved dev mode (cloud → https://app.heyspex.com).
45
+ //
46
+ // P9 (HS-U95M, plugin parity, epic [[HS-HEBV-8CMW-EPIC-spexcode-profile-v0-learn-the-developer]]):
47
+ // a THIRD, independent half — after the console relay and the engine session-end above,
48
+ // this hook also surfaces any P6 profile proposals still `pending` (never accepted or
49
+ // declined) via spexharness/src/profile/proposals.mjs's own `listPending()` (thin shell,
50
+ // never a second read of the ledger). The plugin's Stop/SessionEnd hook cannot run an
51
+ // arrow-key select (`presentPendingProposal`, T15) the way an interactive SpexCode seat
52
+ // can — this is a plain summary naming each pending item and how to act on it (`/profile
53
+ // add|remove`, or the next SpexCode launch's own prompt). Absence of the ledger, or an
54
+ // empty pending queue, produces no section and never throws — own failure boundary,
55
+ // independent of the console-relay and engine halves, per this file's own header
56
+ // contract ("Both halves are independently fail-open").
57
+
58
+ import path from 'node:path';
59
+ import http from 'node:http';
60
+ import https from 'node:https';
61
+ import { fileURLToPath } from 'node:url';
62
+
63
+ import {
64
+ resolveConsoleUrl as resolveConsoleUrlShared,
65
+ DEFAULT_CONSOLE_URL,
66
+ } from '../lib/console-url-resolver.mjs';
67
+ // Bearer for /api/agent/session-end — same reader every other consumer uses, never
68
+ // re-implemented and never printed.
69
+ import { readCredentialsMcpToken } from '../lib/dev-mode.mjs';
70
+ import {
71
+ appendSessionRecord,
72
+ createSessionStore,
73
+ } from '../../spexharness/src/session-record.mjs';
74
+ import {
75
+ defaultProposalsLedgerPath,
76
+ listPending,
77
+ } from '../../spexharness/src/profile/proposals.mjs';
78
+
79
+ const REQUEST_TIMEOUT_MS = 800;
80
+ const ENGINE_TIMEOUT_MS = 1500;
81
+ const PROPOSALS_TIMEOUT_MS = 500;
82
+
83
+ function repoRoot() {
84
+ // session-end.mjs lives in scripts/claude-hooks/, so up two levels.
85
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
86
+ }
87
+
88
+ // Stays fail-open (must exit 0 on every failure path, per the file-header
89
+ // contract) but no longer fails SILENTLY: resolve in `strict` mode first so
90
+ // an unresolved console URL logs loudly instead of quietly posting the release
91
+ // to a guessed localhost:3000
92
+ // ([[HS-OQ34-G59F-DIAG-console-url-silently-defaults-to]]). Since the resolver
93
+ // became mode-aware this can only throw in LOCAL reliance (no
94
+ // env/credentials/discovery-file match); cloud reliance always has a resolved
95
+ // answer — the derived cloud console.
96
+ function resolveConsoleUrl() {
97
+ try {
98
+ return resolveConsoleUrlShared({ repoRoot: repoRoot(), strict: true });
99
+ } catch {
100
+ console.error(
101
+ `[session-end] console URL unresolved in local reliance (no env/credentials/discovery-file ` +
102
+ `match) — guessing ${DEFAULT_CONSOLE_URL}. The release-claim POST may silently miss the real console.`,
103
+ );
104
+ return DEFAULT_CONSOLE_URL;
105
+ }
106
+ }
107
+
108
+ async function readStdin() {
109
+ if (process.stdin.isTTY) return null;
110
+ return new Promise((resolve) => {
111
+ let buf = '';
112
+ let resolved = false;
113
+ const finish = (value) => {
114
+ if (resolved) return;
115
+ resolved = true;
116
+ resolve(value);
117
+ };
118
+ const timer = setTimeout(() => finish(buf || null), 200);
119
+ process.stdin.setEncoding('utf8');
120
+ process.stdin.on('data', (chunk) => {
121
+ buf += chunk;
122
+ });
123
+ process.stdin.on('end', () => {
124
+ clearTimeout(timer);
125
+ finish(buf || null);
126
+ });
127
+ process.stdin.on('error', () => {
128
+ clearTimeout(timer);
129
+ finish(buf || null);
130
+ });
131
+ });
132
+ }
133
+
134
+ function postSessionEnd(consoleUrl, body, token) {
135
+ return new Promise((resolve) => {
136
+ let url;
137
+ try {
138
+ url = new URL('/api/agent/session-end', consoleUrl);
139
+ } catch {
140
+ resolve();
141
+ return;
142
+ }
143
+ const transport = url.protocol === 'https:' ? https : http;
144
+ const payload = JSON.stringify(body);
145
+ const req = transport.request(
146
+ {
147
+ method: 'POST',
148
+ hostname: url.hostname,
149
+ port: url.port || (url.protocol === 'https:' ? 443 : 80),
150
+ path: `${url.pathname}${url.search}`,
151
+ headers: {
152
+ 'content-type': 'application/json',
153
+ 'content-length': Buffer.byteLength(payload),
154
+ ...(token ? { authorization: `Bearer ${token}` } : {}),
155
+ },
156
+ timeout: REQUEST_TIMEOUT_MS,
157
+ },
158
+ (res) => {
159
+ res.on('data', () => {});
160
+ res.on('end', resolve);
161
+ res.on('error', resolve);
162
+ },
163
+ );
164
+ req.on('error', resolve);
165
+ req.on('timeout', () => {
166
+ req.destroy();
167
+ resolve();
168
+ });
169
+ req.write(payload);
170
+ req.end();
171
+ });
172
+ }
173
+
174
+ /* ============================================================================================
175
+ * Engine session-end (HS-U618) — record WIP, release claims, export events, idempotently.
176
+ * ============================================================================================ */
177
+
178
+ /** Where the local (network-free) events export lands — one JSONL line per session close. */
179
+ export function sessionEventsExportPath(root = repoRoot()) {
180
+ return path.join(root, '.heyspex', 'session-end-events.jsonl');
181
+ }
182
+
183
+ /**
184
+ * Default `exporter`: appends the session's events + tool-call log to a local JSONL file via
185
+ * session-record.mjs's own `appendSessionRecord` (same line-atomic append the harness uses for
186
+ * its own per-machine records) — no network, no Neon, so this half never depends on the console
187
+ * being reachable.
188
+ *
189
+ * @param {{ root?: string }} [opts]
190
+ * @returns {{ export: (sessionId: string, data: { events?: Array, toolCalls?: Array }) => Promise<object> }}
191
+ */
192
+ export function createLocalEventsExporter({ root = repoRoot() } = {}) {
193
+ const filePath = sessionEventsExportPath(root);
194
+ return {
195
+ async export(sessionId, { events = [], toolCalls = [] } = {}) {
196
+ const record = { sessionId, exportedAt: new Date().toISOString(), events, toolCalls };
197
+ appendSessionRecord(filePath, record);
198
+ return { filePath, eventCount: events.length, toolCallCount: toolCalls.length };
199
+ },
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Default `ledger`: wraps a spexharness durable session store's `getSession` /
205
+ * `updateWip` / `finishSession` — reused, not re-derived. `getSession` fails soft to `null`
206
+ * (no durable record for this session — most Claude Code plugin sessions today, since nothing
207
+ * yet calls the store's `createSession` for them — or storage genuinely unreachable) so the
208
+ * caller can no-op rather than throw.
209
+ *
210
+ * @param {ReturnType<typeof createSessionStore>} store
211
+ */
212
+ export function createStoreLedger(store) {
213
+ return {
214
+ async getSession(sessionId) {
215
+ try {
216
+ return await store.getSession(sessionId);
217
+ } catch {
218
+ return null;
219
+ }
220
+ },
221
+ async recordWip(sessionId, wip) {
222
+ await store.updateWip(sessionId, wip);
223
+ return store.finishSession(sessionId, null);
224
+ },
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Default `claims`: releases every claim the store has on record for this session, by reading
230
+ * the session's own `claims` array and calling the store's `releaseClaim` for each — the store's
231
+ * existing claim bookkeeping, not a second copy of it.
232
+ *
233
+ * @param {ReturnType<typeof createSessionStore>} store
234
+ */
235
+ export function createStoreClaims(store) {
236
+ return {
237
+ async release(sessionId) {
238
+ let existing;
239
+ try {
240
+ existing = await store.getSession(sessionId);
241
+ } catch {
242
+ return [];
243
+ }
244
+ const specIds = (existing?.claims ?? []).map((c) => c?.specId).filter(Boolean);
245
+ for (const specId of specIds) {
246
+ await store.releaseClaim(sessionId, specId);
247
+ }
248
+ return specIds;
249
+ },
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Build the production `{ ledger, claims, exporter }` deps {@link runSessionEnd} takes. One
255
+ * store backs both `ledger` and `claims` so a real close does one connection's worth of work,
256
+ * not two. `backend`/`root` are injection points for tests (an in-memory session backend, a
257
+ * scratch events-export dir) — omitted, `ledger`/`claims` default to the real Neon-backed store
258
+ * (lazy: no connection opens until a method is actually called) and `exporter` writes under the
259
+ * real repo's `.heyspex/`.
260
+ *
261
+ * @param {{ backend?: object, root?: string }} [opts]
262
+ */
263
+ export function buildDefaultDeps({ backend = undefined, root = undefined } = {}) {
264
+ const store = createSessionStore(backend ? { backend } : {});
265
+ return {
266
+ ledger: createStoreLedger(store),
267
+ claims: createStoreClaims(store),
268
+ exporter: createLocalEventsExporter(root ? { root } : {}),
269
+ };
270
+ }
271
+
272
+ /**
273
+ * The engine session-end core (HS-U618 AC1-4): records the WIP hand-off, releases the session's
274
+ * active claims, and exports its events — exactly once per session id.
275
+ *
276
+ * Idempotent by design: a session the ledger already reports `endedAt` on is read back as
277
+ * already-closed and none of the three actions run again, so a duplicate SessionEnd (or a
278
+ * retried close) never double-records. A session id the ledger has no record for at all (the
279
+ * common case for a plain Claude Code plugin session, which nothing yet bootstraps through the
280
+ * harness's durable store) is likewise a no-op — there is nothing this routine can close.
281
+ *
282
+ * @param {{ sessionId: string, source?: string|null, wip?: unknown }} input
283
+ * @param {{
284
+ * ledger: { getSession: (id: string) => Promise<object|null>, recordWip: (id: string, wip: unknown) => Promise<object> },
285
+ * claims: { release: (id: string) => Promise<string[]> },
286
+ * exporter: { export: (id: string, data: { events?: Array, toolCalls?: Array }) => Promise<object> },
287
+ * }} deps injectable — tests pass fakes (or the real pieces above over an in-memory backend), so
288
+ * this never needs a reachable console or a live Neon connection to prove its contract.
289
+ * @returns {Promise<object>} a receipt describing what happened.
290
+ */
291
+ export async function runSessionEnd({ sessionId, source = null, wip = null } = {}, deps) {
292
+ if (!sessionId) return { closed: false, reason: 'missing_session_id' };
293
+ const { ledger, claims, exporter } = deps;
294
+
295
+ const existing = await ledger.getSession(sessionId);
296
+ if (!existing) return { closed: false, reason: 'no_session_record', sessionId };
297
+ if (existing.endedAt) return { closed: false, reason: 'already_closed', sessionId };
298
+
299
+ const wipReceipt = await ledger.recordWip(sessionId, wip ?? existing.wip ?? null);
300
+ const releasedClaims = await claims.release(sessionId, { source });
301
+ const exportReceipt = await exporter.export(sessionId, {
302
+ events: existing.events ?? [],
303
+ toolCalls: existing.toolCalls ?? [],
304
+ });
305
+
306
+ return {
307
+ closed: true,
308
+ sessionId,
309
+ wip: { endedAt: wipReceipt?.endedAt ?? null, value: wipReceipt?.wip ?? null },
310
+ releasedClaims,
311
+ exported: exportReceipt,
312
+ };
313
+ }
314
+
315
+ /* ============================================================================================
316
+ * Pending profile proposals (HS-U95M, P9) — a third, independent half.
317
+ * ============================================================================================ */
318
+
319
+ /**
320
+ * Render the P6 pending-proposals queue into the plain-text summary this hook prints for
321
+ * the developer. Thin shell over spexharness/src/profile/proposals.mjs's own `listPending`
322
+ * — never a second read or fold of the ledger. Returns `null` (no proposal section, AC2)
323
+ * when there is nothing pending, or when the ledger cannot be read at all (missing file,
324
+ * corrupt content) — a broken read must never surface as an error, only as silence.
325
+ *
326
+ * @param {{ ledgerPath: string, listPendingFn?: typeof listPending }} opts
327
+ * @returns {string|null}
328
+ */
329
+ export function renderPendingProposalsSummary({ ledgerPath, listPendingFn = listPending }) {
330
+ let pending;
331
+ try {
332
+ pending = listPendingFn({ ledgerPath });
333
+ } catch {
334
+ return null;
335
+ }
336
+ if (!Array.isArray(pending) || pending.length === 0) return null;
337
+
338
+ const count = pending.length;
339
+ const lines = [
340
+ `SpexCode Profile — ${count} pending observation${count === 1 ? '' : 's'} waiting for a decision:`,
341
+ ];
342
+ pending.forEach((proposal, i) => {
343
+ const obs = proposal?.observation ?? {};
344
+ const text = typeof obs.text === 'string' && obs.text.trim() ? obs.text.trim() : '(no text)';
345
+ lines.push(
346
+ ` ${i + 1}. ${text} _(source: ${obs.source ?? 'unknown'}, project: ${obs.project ?? 'unknown'}, session: ${obs.session ?? 'unknown'})_`,
347
+ );
348
+ });
349
+ lines.push(
350
+ 'Act on these with `/profile add` / `/profile remove`, or you will be asked again the next time SpexCode launches.',
351
+ );
352
+ return lines.join('\n');
353
+ }
354
+
355
+ /**
356
+ * The proposals half's own dependency bundle — `root` defaults to this repo's real root
357
+ * (production), but a test passes a scratch `root` so the ledger never touches
358
+ * `~/.heyspex` or this repo's own `.heyspex/` store.
359
+ *
360
+ * @param {{ root?: string }} [opts]
361
+ */
362
+ export function defaultProposalsDeps({ root = repoRoot() } = {}) {
363
+ return { ledgerPath: defaultProposalsLedgerPath(root) };
364
+ }
365
+
366
+ /**
367
+ * Print the pending-proposals summary to `stdout`, if there is one. Own failure boundary —
368
+ * this must never throw, and never affects the console relay or the engine half above.
369
+ *
370
+ * @param {{ ledgerPath: string }} deps
371
+ * @param {{ write: (chunk: string) => unknown }} [stdout]
372
+ * @returns {Promise<string|null>} the summary printed, or null when there was none.
373
+ */
374
+ export async function reportPendingProposals(deps, stdout = process.stdout) {
375
+ try {
376
+ const summary = renderPendingProposalsSummary(deps);
377
+ if (summary) stdout.write(`${summary}\n`);
378
+ return summary;
379
+ } catch {
380
+ return null;
381
+ }
382
+ }
383
+
384
+ /** Race a promise against a timeout so the engine half can never stall the hook's own exit. */
385
+ function withTimeout(promise, ms, fallback) {
386
+ return Promise.race([
387
+ promise,
388
+ new Promise((resolve) => setTimeout(() => resolve(fallback), ms)),
389
+ ]);
390
+ }
391
+
392
+ async function main() {
393
+ const stdinRaw = await readStdin();
394
+ let payload = null;
395
+ if (stdinRaw) {
396
+ try {
397
+ payload = JSON.parse(stdinRaw);
398
+ } catch {
399
+ // Non-JSON stdin — nothing actionable.
400
+ }
401
+ }
402
+
403
+ const sessionId =
404
+ payload?.session_id ??
405
+ payload?.conversation_id ??
406
+ process.env.CLAUDE_CODE_SESSION_ID ??
407
+ process.env.CURSOR_TRACE_ID ??
408
+ process.env.CURSOR_SESSION_ID ??
409
+ process.env.COMPOSER_SESSION_ID;
410
+ if (!sessionId) return;
411
+
412
+ // `source` may be absent on some payloads; forward what we have and let the
413
+ // server gate. (When absent, the server treats it as a non-closing event.)
414
+ const source = typeof payload?.source === 'string' ? payload.source : undefined;
415
+
416
+ await postSessionEnd(
417
+ resolveConsoleUrl(),
418
+ { session_id: sessionId, source },
419
+ readCredentialsMcpToken(),
420
+ );
421
+
422
+ // Engine half (HS-U618) — independent of the relay above, own failure boundary, own
423
+ // timeout, never allowed to affect this hook's exit code.
424
+ await withTimeout(
425
+ runSessionEnd({ sessionId, source }, buildDefaultDeps()).catch(() => null),
426
+ ENGINE_TIMEOUT_MS,
427
+ null,
428
+ );
429
+
430
+ // Pending profile proposals half (HS-U95M, P9) — independent of both halves above, own
431
+ // failure boundary, own timeout. No-op (prints nothing) when nothing is pending.
432
+ await withTimeout(
433
+ reportPendingProposals(defaultProposalsDeps()).catch(() => null),
434
+ PROPOSALS_TIMEOUT_MS,
435
+ null,
436
+ );
437
+ }
438
+
439
+ // Entry-guard: run main() only when executed as the hook, NOT when imported — the test file
440
+ // imports runSessionEnd/buildDefaultDeps/createLocalEventsExporter/createStoreLedger/
441
+ // createStoreClaims to unit-test the engine half with injected fakes, and importing this module
442
+ // must never itself read stdin or make a network/DB call. Mirrors preflight-reminder.mjs.
443
+ const INVOKED_DIRECTLY =
444
+ !!process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
445
+ if (INVOKED_DIRECTLY) {
446
+ main().catch(() => {
447
+ // Swallow — never break Claude Code on a hook failure.
448
+ });
449
+ }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ // GENERATED by scripts/generate-hook-shells.mjs (HS-IQBF) — do not hand-edit.
3
+ // This file only wires Claude Code's hook stdin/stdout contract to the
4
+ // spexharness engine port of this gate. To change what this gate DECIDES,
5
+ // edit spexharness/src/gates/settings-write-gate.mjs. To change the SHAPE of this shell,
6
+ // edit the generator, then rerun: node scripts/generate-hook-shells.mjs
7
+
8
+ import { runGateAsHook } from '../../spexharness/src/adapters/claude-code.mjs';
9
+ import gate from '../../spexharness/src/gates/settings-write-gate.mjs';
10
+
11
+ await runGateAsHook(gate);
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ // settlement-check-gate.mjs — thin CLI shell around the pure
3
+ // evaluateSessionSettlement() decision ([[HS-N33D-CWPX-TASK-settlement-check-before-stop-companion]]).
4
+ //
5
+ // NOT WIRED into .claude/settings.json's live `Stop` hook array by this task
6
+ // (deliberately — that array governs THIS session's own Stop event, and
7
+ // wiring an unvalidated new gate into it live is a self-referential risk).
8
+ // This script is the thin shell a follow-up task can point a Stop hook at
9
+ // once validated outside an active session: it resolves the real signals
10
+ // (a settlement/drain status file, whether SpexCompanion's heartbeat is
11
+ // recent, whether git shows uncommitted changes) and prints the pure
12
+ // evaluator's verdict as JSON. Untested by design — the pure-core/thin-shell
13
+ // split ([[unit-test-authoring-protocol]] §1) puts all the tested logic in
14
+ // session-settlement-check.mjs; this file only wires real inputs to it.
15
+ //
16
+ // Usage (once wired as a Stop hook):
17
+ // node scripts/claude-hooks/settlement-check-gate.mjs
18
+ //
19
+ // Signals are read from environment variables so a hook runner (or a human,
20
+ // for now) can supply them without this script reaching into git/Companion
21
+ // state itself — that wiring is the follow-up task's job, not this one's.
22
+
23
+ import { evaluateSessionSettlement } from "../lib/session-settlement-check.mjs";
24
+
25
+ function boolEnv(name) {
26
+ return process.env[name] === "1" || process.env[name] === "true";
27
+ }
28
+
29
+ const result = evaluateSessionSettlement({
30
+ hasUnsettledWrites: boolEnv("HEYSPEX_HAS_UNSETTLED_WRITES"),
31
+ drainTriggeredImmediately: boolEnv("HEYSPEX_DRAIN_TRIGGERED_IMMEDIATELY"),
32
+ diskEditsThisSession: boolEnv("HEYSPEX_DISK_EDITS_THIS_SESSION"),
33
+ companionRunning: boolEnv("HEYSPEX_COMPANION_RUNNING"),
34
+ });
35
+
36
+ process.stdout.write(`${JSON.stringify(result)}\n`);
37
+ // Exit 0 always — this script is report-only until a follow-up task decides
38
+ // how a fired tripwire should affect the Stop hook's own exit code.
39
+ process.exit(0);
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ // Back-compat re-exports — prefer terminal-close-shared.mjs for new code.
3
+
4
+ export {
5
+ STATUS_REVIEW_RE,
6
+ armPendingTerminalClose,
7
+ clearPendingTerminalClose,
8
+ detectShipFromEditLike as detectFlip,
9
+ isPendingTerminalCloseArmed,
10
+ isRecordWipSuccess,
11
+ isSessionEndEvent,
12
+ isShipEvent as isPostToolUseShipEvent,
13
+ pendingTerminalClosePath,
14
+ readStdin,
15
+ repoRoot,
16
+ resolveCloseTerminalOnTaskCompleteFromSettings,
17
+ } from './terminal-close-shared.mjs';