heyspex 0.1.0 → 0.2.1

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 (352) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/README.md +21 -0
  3. package/bin/hs.mjs +1089 -104
  4. package/bin/spexcode.mjs +262 -0
  5. package/dev-factory/engine/worktree.mjs +740 -0
  6. package/dev-factory/lib/code-repo-root.mjs +67 -0
  7. package/dev-factory/lib/context-budget.mjs +84 -0
  8. package/dev-factory/lib/epic-detect.mjs +190 -0
  9. package/dev-factory/lib/fleet-queue-file.mjs +846 -0
  10. package/dev-factory/lib/fleet-queue.mjs +426 -0
  11. package/dev-factory/lib/fleet-state.mjs +14 -18
  12. package/dev-factory/lib/model-catalog.mjs +338 -0
  13. package/dev-factory/lib/seat-context.mjs +199 -0
  14. package/dev-factory/lib/spec-io.mjs +93 -0
  15. package/dev-factory/lib/sufficiency-score.mjs +8 -2
  16. package/dev-factory/lib/worktree-allocator.mjs +744 -0
  17. package/dev-factory/model-catalog.json +222 -0
  18. package/dist/cli/hs-check.mjs +1127 -103
  19. package/dist/cli/hs-claim.mjs +981 -63
  20. package/dist/cli/hs-connect.mjs +10059 -1729
  21. package/dist/cli/hs-defer.mjs +5507 -3072
  22. package/dist/cli/hs-dev.mjs +576 -0
  23. package/dist/cli/hs-env.mjs +416 -302
  24. package/dist/cli/hs-index-rebuild.mjs +7334 -0
  25. package/dist/cli/hs-index-reconcile.mjs +8138 -0
  26. package/dist/cli/hs-init.mjs +2840 -149
  27. package/dist/cli/hs-install.mjs +7214 -594
  28. package/dist/cli/hs-join.mjs +10640 -2421
  29. package/dist/cli/hs-vault-pull.mjs +8157 -6370
  30. package/dist/cli/hs-vault-push.mjs +6433 -0
  31. package/dist/cli/hs-vault.mjs +4029 -1555
  32. package/dist/cli/hs-workspace.mjs +2702 -538
  33. package/dist/cli-templates/customer-vault/specs/entity-model.md +594 -601
  34. package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +110 -177
  35. package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -0
  36. package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +13 -15
  37. package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +91 -0
  38. package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +150 -168
  39. package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -0
  40. package/dist/cli-templates/harness-commands/claude/hs-auto.md +36 -0
  41. package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -0
  42. package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -0
  43. package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -0
  44. package/dist/cli-templates/harness-commands/claude/hs-dev.md +20 -0
  45. package/dist/cli-templates/harness-commands/claude/hs-doctor.md +32 -0
  46. package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -0
  47. package/dist/cli-templates/harness-commands/claude/hs-handoff.md +33 -0
  48. package/dist/cli-templates/harness-commands/claude/hs-log.md +27 -0
  49. package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -0
  50. package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -0
  51. package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -0
  52. package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -0
  53. package/dist/cli-templates/harness-commands/claude/hs-res.md +63 -0
  54. package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -0
  55. package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -0
  56. package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -0
  57. package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -0
  58. package/dist/cli-templates/harness-commands/claude/prototype.md +91 -0
  59. package/dist/cli-templates/harness-commands/cursor/hs-auto.md +40 -0
  60. package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +31 -0
  61. package/dist/cli-templates/harness-commands/cursor/hs-capture.md +65 -0
  62. package/dist/cli-templates/harness-commands/cursor/hs-defer.md +32 -0
  63. package/dist/cli-templates/harness-commands/cursor/hs-dev.md +24 -0
  64. package/dist/cli-templates/harness-commands/cursor/hs-doctor.md +36 -0
  65. package/dist/cli-templates/harness-commands/cursor/hs-ground.md +33 -0
  66. package/dist/cli-templates/harness-commands/cursor/hs-handoff.md +37 -0
  67. package/dist/cli-templates/harness-commands/cursor/hs-log.md +31 -0
  68. package/dist/cli-templates/harness-commands/cursor/hs-next.md +32 -0
  69. package/dist/cli-templates/harness-commands/cursor/hs-promote.md +73 -0
  70. package/dist/cli-templates/harness-commands/cursor/hs-push.md +38 -0
  71. package/dist/cli-templates/harness-commands/cursor/hs-qc.md +30 -0
  72. package/dist/cli-templates/harness-commands/cursor/hs-res.md +67 -0
  73. package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +26 -0
  74. package/dist/cli-templates/harness-commands/cursor/hs-review.md +26 -0
  75. package/dist/cli-templates/harness-commands/cursor/hs-view.md +32 -0
  76. package/dist/cli-templates/harness-commands/cursor/kill-ports.md +35 -0
  77. package/dist/cli-templates/harness-commands/cursor/prototype.md +95 -0
  78. package/docs/copilot-plugin-install.md +162 -0
  79. package/docs/driving-heyspex-from-copilot.md +165 -0
  80. package/docs/mcp-client-config.md +259 -6
  81. package/package.json +249 -124
  82. package/plugins/heyspex/.claude-plugin/plugin.json +12 -0
  83. package/plugins/heyspex/.cursor-plugin/plugin.json +15 -0
  84. package/plugins/heyspex/.mcp.json +12 -0
  85. package/plugins/heyspex/README.md +309 -0
  86. package/plugins/heyspex/bin/launch-mcp.mjs +95 -0
  87. package/plugins/heyspex/bin/run-hook.mjs +80 -0
  88. package/plugins/heyspex/hooks/cursor-hooks.json +308 -0
  89. package/plugins/heyspex/hooks/hooks.json +418 -0
  90. package/plugins/heyspex/mcp.json +13 -0
  91. package/scripts/check-mcp-freshness.mjs +17 -9
  92. package/scripts/claude-hooks/_env-vault-events.mjs +68 -0
  93. package/scripts/claude-hooks/_guardrail-events.mjs +96 -0
  94. package/scripts/claude-hooks/_guardrail-loader.mjs +494 -0
  95. package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -0
  96. package/scripts/claude-hooks/_intent-classifier-semantic.mjs +356 -0
  97. package/scripts/claude-hooks/_run.mjs +413 -0
  98. package/scripts/claude-hooks/_transcript-text.mjs +60 -0
  99. package/scripts/claude-hooks/_vault-retrieval.mjs +322 -0
  100. package/scripts/claude-hooks/agent-spawn-delegation-gate.mjs +133 -0
  101. package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
  102. package/scripts/claude-hooks/capture-workflow-gate.mjs +11 -0
  103. package/scripts/claude-hooks/check-auto-memory-budget.mjs +396 -0
  104. package/scripts/claude-hooks/check-claude-md-budget.mjs +327 -0
  105. package/scripts/claude-hooks/check-mcp-freshness-hook.mjs +14 -0
  106. package/scripts/claude-hooks/claim-before-code-gate.mjs +11 -0
  107. package/scripts/claude-hooks/claim-liveness-shared.mjs +577 -0
  108. package/scripts/claude-hooks/close-terminal-on-ship.mjs +64 -0
  109. package/scripts/claude-hooks/coo-denylist-gate.mjs +11 -0
  110. package/scripts/claude-hooks/coo-guard-check.mjs +11 -0
  111. package/scripts/claude-hooks/deep-research-cost-gate.mjs +11 -0
  112. package/scripts/claude-hooks/dev-server-task-reminder.mjs +15 -0
  113. package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
  114. package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
  115. package/scripts/claude-hooks/emit-claim-status.mjs +41 -0
  116. package/scripts/claude-hooks/enforce-session-sequence.mjs +11 -0
  117. package/scripts/claude-hooks/enforce-vault-naming.mjs +11 -0
  118. package/scripts/claude-hooks/env-vault-guard.mjs +11 -0
  119. package/scripts/claude-hooks/epic-milestone-phrasing-gate.mjs +11 -0
  120. package/scripts/claude-hooks/git-identity-guard.mjs +11 -0
  121. package/scripts/claude-hooks/git-reflog-audit.mjs +15 -0
  122. package/scripts/claude-hooks/git-stash-gate.mjs +11 -0
  123. package/scripts/claude-hooks/guard-auto-memory-writes.mjs +137 -0
  124. package/scripts/claude-hooks/id-display-format-gate.mjs +11 -0
  125. package/scripts/claude-hooks/inject-communication-style.mjs +86 -0
  126. package/scripts/claude-hooks/inject-coo-seat.mjs +78 -0
  127. package/scripts/claude-hooks/inject-dev-server-awareness.mjs +11 -0
  128. package/scripts/claude-hooks/inject-doctrine.mjs +13 -0
  129. package/scripts/claude-hooks/inject-vault-context.mjs +14 -0
  130. package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -0
  131. package/scripts/claude-hooks/log-session-activity.mjs +341 -0
  132. package/scripts/claude-hooks/naughty-list-gate.mjs +11 -0
  133. package/scripts/claude-hooks/notify-status-flip.mjs +72 -0
  134. package/scripts/claude-hooks/plan-exit-ingest-gate.mjs +170 -0
  135. package/scripts/claude-hooks/plan-ingest-close-reminder.mjs +66 -0
  136. package/scripts/claude-hooks/plan-review-gate.mjs +11 -0
  137. package/scripts/claude-hooks/preflight-reminder.mjs +262 -0
  138. package/scripts/claude-hooks/protected-action-intent.mjs +221 -0
  139. package/scripts/claude-hooks/research-method-guard.mjs +398 -0
  140. package/scripts/claude-hooks/research-protocol-gate.mjs +11 -0
  141. package/scripts/claude-hooks/session-end.mjs +451 -0
  142. package/scripts/claude-hooks/settings-write-gate.mjs +11 -0
  143. package/scripts/claude-hooks/settlement-check-gate.mjs +39 -0
  144. package/scripts/claude-hooks/status-flip-detect.mjs +17 -0
  145. package/scripts/claude-hooks/terminal-close-shared.mjs +426 -0
  146. package/scripts/claude-hooks/terminal-close-watcher.mjs +122 -0
  147. package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
  148. package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
  149. package/scripts/claude-hooks/wip-checkpoint.mjs +302 -0
  150. package/scripts/cursor-hooks/manifest.mjs +281 -0
  151. package/scripts/dev-server-status.mjs +34 -13
  152. package/scripts/dev-server-stop.mjs +313 -0
  153. package/scripts/heyspex-mode.mjs +288 -0
  154. package/scripts/hs-auto-resolve.mjs +240 -0
  155. package/scripts/hs-auto-source.mjs +398 -0
  156. package/scripts/hs-auto.mjs +134 -0
  157. package/scripts/hs-capture-cli.mjs +326 -0
  158. package/scripts/hs-classifier.mjs +136 -0
  159. package/scripts/hs-companion.mjs +88 -0
  160. package/scripts/hs-gateway.mjs +115 -0
  161. package/scripts/hs-mode.mjs +125 -0
  162. package/scripts/hs-next.mjs +441 -0
  163. package/scripts/hs-settings.mjs +338 -0
  164. package/scripts/hs-view.mjs +496 -0
  165. package/scripts/hs-worktree.mjs +201 -0
  166. package/scripts/install-claude-code-hooks.mjs +272 -0
  167. package/scripts/install-cursor-hooks.mjs +471 -0
  168. package/scripts/lib/agent-env-markers.mjs +38 -0
  169. package/scripts/lib/console-url-resolver.mjs +199 -61
  170. package/scripts/lib/customer-kit.mjs +1203 -0
  171. package/scripts/lib/dev-mode.mjs +249 -32
  172. package/scripts/lib/directive-obligations.mjs +349 -0
  173. package/scripts/lib/heyspex-home.mjs +72 -0
  174. package/scripts/lib/hook-dispositions.mjs +210 -0
  175. package/scripts/lib/hook-vault-header.mjs +30 -0
  176. package/scripts/lib/hs-command.mjs +277 -0
  177. package/scripts/lib/hs-companion-settings.mjs +472 -0
  178. package/scripts/lib/hs-source-branch.mjs +196 -0
  179. package/scripts/lib/hs-source-commit.mjs +59 -5
  180. package/scripts/lib/hs-source-identity.mjs +526 -0
  181. package/scripts/lib/hs-source-log.mjs +13 -22
  182. package/scripts/lib/hs-source-preflight.mjs +18 -4
  183. package/scripts/lib/hs-source-push.mjs +386 -307
  184. package/scripts/lib/hs-worktree-args.mjs +115 -0
  185. package/scripts/lib/shared-path-helpers.mjs +134 -0
  186. package/scripts/lib/standalone-doctor.mjs +540 -3
  187. package/scripts/lib/subagent-git-lock.mjs +109 -0
  188. package/scripts/lib/template-pin.mjs +34 -0
  189. package/scripts/lib/window-placement.mjs +109 -0
  190. package/scripts/provenance-lib.mjs +91 -0
  191. package/scripts/stamp-provenance-on-edit.mjs +222 -0
  192. package/scripts/wip-ledger-health.mjs +653 -0
  193. package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -0
  194. package/spexharness/src/adapters/audit.mjs +197 -0
  195. package/spexharness/src/adapters/claude-code.mjs +449 -0
  196. package/spexharness/src/adapters/copilot.mjs +236 -0
  197. package/spexharness/src/adapters/detect-harness.mjs +34 -0
  198. package/spexharness/src/chat/tui/ansi.mjs +448 -0
  199. package/spexharness/src/chat/tui/palette.mjs +22 -0
  200. package/spexharness/src/chat/tui/select.mjs +208 -0
  201. package/spexharness/src/config-root.mjs +54 -0
  202. package/spexharness/src/context-budget.mjs +141 -0
  203. package/spexharness/src/dev-server-control.mjs +389 -0
  204. package/spexharness/src/doctrine/behaviour.mjs +371 -0
  205. package/spexharness/src/doctrine/corpus.mjs +733 -0
  206. package/spexharness/src/doctrine/inject.mjs +338 -0
  207. package/spexharness/src/doctrine/merge.mjs +184 -0
  208. package/spexharness/src/doctrine/sources.mjs +252 -0
  209. package/spexharness/src/doctrine/synthesize.mjs +401 -0
  210. package/spexharness/src/doctrine/vault-source.mjs +574 -0
  211. package/spexharness/src/doctrine/writer.mjs +176 -0
  212. package/spexharness/src/events.mjs +84 -0
  213. package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
  214. package/spexharness/src/gates/capture-workflow-gate.mjs +202 -0
  215. package/spexharness/src/gates/claim-before-code-gate.decide.mjs +33 -0
  216. package/spexharness/src/gates/claim-before-code-gate.mjs +129 -0
  217. package/spexharness/src/gates/deep-research-cost-gate.mjs +275 -0
  218. package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
  219. package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
  220. package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
  221. package/spexharness/src/gates/enforce-session-sequence.mjs +895 -0
  222. package/spexharness/src/gates/enforce-vault-naming.mjs +215 -0
  223. package/spexharness/src/gates/epic-milestone-phrasing-gate.mjs +233 -0
  224. package/spexharness/src/gates/git-identity-guard.decide.mjs +436 -0
  225. package/spexharness/src/gates/git-identity-guard.mjs +61 -0
  226. package/spexharness/src/gates/git-reflog-audit.mjs +151 -0
  227. package/spexharness/src/gates/git-stash-gate.decide.mjs +361 -0
  228. package/spexharness/src/gates/git-stash-gate.mjs +159 -0
  229. package/spexharness/src/gates/id-display-format-gate.mjs +190 -0
  230. package/spexharness/src/gates/inject-doctrine-gate.mjs +89 -0
  231. package/spexharness/src/gates/inject-vault-context.mjs +239 -0
  232. package/spexharness/src/gates/naughty-list-gate.mjs +213 -0
  233. package/spexharness/src/gates/plan-review-gate.mjs +233 -0
  234. package/spexharness/src/gates/preflight-reminder-gate.mjs +265 -0
  235. package/spexharness/src/gates/research-protocol-gate.mjs +175 -0
  236. package/spexharness/src/gates/settings-write-gate.decide.mjs +96 -0
  237. package/spexharness/src/gates/settings-write-gate.mjs +104 -0
  238. package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
  239. package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
  240. package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
  241. package/spexharness/src/git-reflog-audit.mjs +294 -0
  242. package/spexharness/src/intent-classifier.mjs +196 -0
  243. package/spexharness/src/intent-directive.mjs +626 -0
  244. package/spexharness/src/profile/learn.mjs +551 -0
  245. package/spexharness/src/profile/proposals.mjs +357 -0
  246. package/spexharness/src/profile/vault-inclusion.mjs +386 -0
  247. package/spexharness/src/redact.mjs +151 -0
  248. package/spexharness/src/rule-table.mjs +849 -0
  249. package/spexharness/src/seat-context.mjs +122 -0
  250. package/spexharness/src/session-bootstrap.mjs +512 -0
  251. package/spexharness/src/session-record.mjs +815 -0
  252. package/spexharness/src/token-inventory.mjs +207 -0
  253. package/vendor/heyspex-mcp/account-switch.mjs +96 -0
  254. package/vendor/heyspex-mcp/alignment-gate.mjs +315 -1
  255. package/vendor/heyspex-mcp/blocker-resolver.mjs +37 -2
  256. package/vendor/heyspex-mcp/breakdown-ladder.mjs +119 -4
  257. package/vendor/heyspex-mcp/breakdown-source.mjs +271 -0
  258. package/vendor/heyspex-mcp/breakdown-writer.mjs +23 -3
  259. package/vendor/heyspex-mcp/capture-buffer-patches.mjs +77 -8
  260. package/vendor/heyspex-mcp/capture-channel.mjs +55 -5
  261. package/vendor/heyspex-mcp/capture-console-client.mjs +117 -3
  262. package/vendor/heyspex-mcp/capture-focus.mjs +1061 -31
  263. package/vendor/heyspex-mcp/capture-outbox.mjs +15 -5
  264. package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
  265. package/vendor/heyspex-mcp/capture-tools.mjs +1264 -0
  266. package/vendor/heyspex-mcp/claim-tools.mjs +1147 -0
  267. package/vendor/heyspex-mcp/cli-verbs.mjs +2074 -0
  268. package/vendor/heyspex-mcp/cli.mjs +207 -15524
  269. package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
  270. package/vendor/heyspex-mcp/cloud-tool-surface.mjs +128 -0
  271. package/vendor/heyspex-mcp/cloud-vault-writes.mjs +602 -0
  272. package/vendor/heyspex-mcp/config-seed.yaml +15 -0
  273. package/vendor/heyspex-mcp/config-settings.mjs +290 -7
  274. package/vendor/heyspex-mcp/console-fetch.mjs +822 -17
  275. package/vendor/heyspex-mcp/console-transport.mjs +172 -0
  276. package/vendor/heyspex-mcp/credentials.mjs +585 -0
  277. package/vendor/heyspex-mcp/defer-spec.mjs +44 -4
  278. package/vendor/heyspex-mcp/deferred-reclaim.mjs +29 -3
  279. package/vendor/heyspex-mcp/design-handlers.mjs +31 -0
  280. package/vendor/heyspex-mcp/design-tools.mjs +4 -1
  281. package/vendor/heyspex-mcp/dev-factory-bridge.mjs +4 -4
  282. package/vendor/heyspex-mcp/dev-mode-core.mjs +45 -0
  283. package/vendor/heyspex-mcp/dev-tools.mjs +131 -0
  284. package/vendor/heyspex-mcp/diagnose-handlers.mjs +54 -0
  285. package/vendor/heyspex-mcp/events-tools.mjs +207 -0
  286. package/vendor/heyspex-mcp/filing-tools.mjs +723 -0
  287. package/vendor/heyspex-mcp/fleet-handlers.mjs +37 -0
  288. package/vendor/heyspex-mcp/fleet-queue-tools.mjs +71 -15
  289. package/vendor/heyspex-mcp/focus-tools.mjs +190 -0
  290. package/vendor/heyspex-mcp/folder-trust-store.mjs +60 -0
  291. package/vendor/heyspex-mcp/http-transport.mjs +302 -0
  292. package/vendor/heyspex-mcp/list-tools.mjs +257 -0
  293. package/vendor/heyspex-mcp/login-handoff.mjs +15 -1
  294. package/vendor/heyspex-mcp/mcp-config.mjs +107 -0
  295. package/vendor/heyspex-mcp/memory-tools.mjs +523 -0
  296. package/vendor/heyspex-mcp/mvp-mode.mjs +53 -6
  297. package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
  298. package/vendor/heyspex-mcp/occupied-keys.mjs +142 -0
  299. package/vendor/heyspex-mcp/on-deck-brief.mjs +3377 -0
  300. package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -0
  301. package/vendor/heyspex-mcp/package-root.mjs +32 -0
  302. package/vendor/heyspex-mcp/peer-presence.mjs +8 -1
  303. package/vendor/heyspex-mcp/plan-ground.mjs +621 -652
  304. package/vendor/heyspex-mcp/plan-push.mjs +908 -22
  305. package/vendor/heyspex-mcp/plan-review.mjs +124 -30
  306. package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
  307. package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -0
  308. package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -970
  309. package/vendor/heyspex-mcp/promote-capture.mjs +388 -69
  310. package/vendor/heyspex-mcp/promote-diagnose.mjs +74 -0
  311. package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +9 -2
  312. package/vendor/heyspex-mcp/promote-research.mjs +666 -53
  313. package/vendor/heyspex-mcp/promotion-helpers.mjs +251 -0
  314. package/vendor/heyspex-mcp/prompts.mjs +1460 -0
  315. package/vendor/heyspex-mcp/read-path.mjs +191 -15
  316. package/vendor/heyspex-mcp/receipt-shape.mjs +146 -0
  317. package/vendor/heyspex-mcp/recipe-handlers.mjs +117 -0
  318. package/vendor/heyspex-mcp/regen-tasks.mjs +210 -3
  319. package/vendor/heyspex-mcp/register-spec.mjs +266 -54
  320. package/vendor/heyspex-mcp/request-context.mjs +83 -0
  321. package/vendor/heyspex-mcp/research-focus.mjs +207 -4
  322. package/vendor/heyspex-mcp/research-handlers.mjs +625 -0
  323. package/vendor/heyspex-mcp/research-tools.mjs +4 -4
  324. package/vendor/heyspex-mcp/response-budget.mjs +348 -0
  325. package/vendor/heyspex-mcp/review-focus.mjs +25 -1
  326. package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
  327. package/vendor/heyspex-mcp/search-specs.mjs +280 -0
  328. package/vendor/heyspex-mcp/seat-block-ledger.mjs +113 -0
  329. package/vendor/heyspex-mcp/seat-identity.mjs +2 -6
  330. package/vendor/heyspex-mcp/session-owner.mjs +109 -0
  331. package/vendor/heyspex-mcp/session-tools.mjs +162 -0
  332. package/vendor/heyspex-mcp/settings-registry.mjs +232 -0
  333. package/vendor/heyspex-mcp/settings-tools.mjs +85 -12
  334. package/vendor/heyspex-mcp/spec-edit-tools.mjs +453 -0
  335. package/vendor/heyspex-mcp/spec-id-lib.mjs +93 -17
  336. package/vendor/heyspex-mcp/split-research.mjs +304 -56
  337. package/vendor/heyspex-mcp/stdio-rpc.mjs +22 -0
  338. package/vendor/heyspex-mcp/submit-for-review.mjs +150 -7
  339. package/vendor/heyspex-mcp/summary-contract.mjs +105 -0
  340. package/vendor/heyspex-mcp/summary-write-tools.mjs +228 -0
  341. package/vendor/heyspex-mcp/symptom-query.mjs +146 -0
  342. package/vendor/heyspex-mcp/test-helpers.mjs +123 -1
  343. package/vendor/heyspex-mcp/tool-catalog.mjs +2819 -0
  344. package/vendor/heyspex-mcp/tool-dispatch.mjs +657 -0
  345. package/vendor/heyspex-mcp/tool-helpers.mjs +325 -0
  346. package/vendor/heyspex-mcp/validate-linkage.mjs +414 -27
  347. package/vendor/heyspex-mcp/vault-io.mjs +720 -0
  348. package/vendor/heyspex-mcp/vault-less-endpoints.mjs +52 -0
  349. package/vendor/heyspex-mcp/vault-resolve.mjs +554 -68
  350. package/vendor/heyspex-mcp/verify-reproduction.mjs +270 -30
  351. package/vendor/heyspex-mcp/whoami.mjs +92 -0
  352. package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
@@ -0,0 +1,426 @@
1
+ #!/usr/bin/env node
2
+ // Shared auto-close terminal helpers — ship detection, pending arm, process kill,
3
+ // debug trace. Used by close-terminal-on-ship.mjs (hooks) and terminal-close-watcher.mjs.
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { spawn } from 'node:child_process';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ import { getActiveConfig } from '../../vendor/heyspex-mcp/vault-resolve.mjs';
11
+
12
+ export const STATUS_REVIEW_RE = /^\s*status:\s*in_review\s*$/m;
13
+ export const STATUS_CLOSED_RE = /^\s*status:\s*closed\s*$/m;
14
+ export const RECORD_WIP_TOOL_RE = /heyspex_record_wip/i;
15
+ export const REVIEW_DOC_RE = /[\\/]specs[\\/]reviews[\\/]HS-REV-/i;
16
+
17
+ const EDIT_TOOL_NAMES = new Set([
18
+ 'Edit',
19
+ 'Write',
20
+ 'MultiEdit',
21
+ 'StrReplace',
22
+ 'search_replace',
23
+ 'apply_patch',
24
+ 'WriteFile',
25
+ 'Replace',
26
+ ]);
27
+
28
+ export function repoRoot() {
29
+ const fromEnv = process.env.CLAUDE_PROJECT_DIR?.trim();
30
+ if (fromEnv) return fromEnv.replace(/[\\/]+$/, '');
31
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
32
+ }
33
+
34
+ const DEBUG_LOG_NAME = 'terminal-close-debug.jsonl';
35
+ /** Opt-in only — default off so hooks never grow a multi-MB trace during normal agent work. */
36
+ const DEBUG_LOG_MAX_BYTES = 256 * 1024;
37
+
38
+ export function terminalCloseDebugEnabled() {
39
+ return process.env.HEYSPEX_TERMINAL_CLOSE_DEBUG === '1';
40
+ }
41
+
42
+ function trimDebugLogIfOversized(logPath) {
43
+ try {
44
+ const stat = fs.statSync(logPath);
45
+ if (stat.size <= DEBUG_LOG_MAX_BYTES) return;
46
+ const raw = fs.readFileSync(logPath, 'utf8');
47
+ const lines = raw.split('\n').filter(Boolean);
48
+ const keep = lines.slice(-200);
49
+ fs.writeFileSync(logPath, keep.join('\n') + (keep.length ? '\n' : ''));
50
+ } catch {
51
+ // absent or unreadable — append below will create it
52
+ }
53
+ }
54
+
55
+ export function debugLog(event, detail = {}) {
56
+ if (!terminalCloseDebugEnabled()) return;
57
+ try {
58
+ const logPath = path.join(repoRoot(), '.heyspex', DEBUG_LOG_NAME);
59
+ trimDebugLogIfOversized(logPath);
60
+ const line =
61
+ JSON.stringify({
62
+ at: new Date().toISOString(),
63
+ event,
64
+ pid: process.pid,
65
+ ppid: process.ppid,
66
+ ...detail,
67
+ }) + '\n';
68
+ fs.appendFileSync(logPath, line);
69
+ } catch {
70
+ // never block
71
+ }
72
+ }
73
+
74
+ export function resolveCloseTerminalOnTaskCompleteFromSettings() {
75
+ if (process.env.HEYSPEX_AUTO_CLOSE_TERMINAL === '0') return false;
76
+ try {
77
+ const raw = fs.readFileSync(path.join(repoRoot(), 'spex-console', 'settings.json'), 'utf8');
78
+ const parsed = JSON.parse(raw);
79
+ const launch = parsed?.launchModels;
80
+ if (typeof launch?.closeTerminalOnTaskComplete === 'boolean') {
81
+ return launch.closeTerminalOnTaskComplete;
82
+ }
83
+ if (typeof launch?.closeTerminalOnTaskCreation === 'boolean') {
84
+ return launch.closeTerminalOnTaskCreation;
85
+ }
86
+ return true;
87
+ } catch {
88
+ return true;
89
+ }
90
+ }
91
+
92
+ const PENDING_CLOSE_FILE = '.pending-terminal-close';
93
+
94
+ export function pendingTerminalClosePath() {
95
+ return path.join(repoRoot(), '.heyspex', PENDING_CLOSE_FILE);
96
+ }
97
+
98
+ export function readPendingTerminalClose() {
99
+ try {
100
+ return JSON.parse(fs.readFileSync(pendingTerminalClosePath(), 'utf8'));
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+
106
+ export function armPendingTerminalClose(sessionId, reason = 'ship') {
107
+ try {
108
+ fs.mkdirSync(path.join(repoRoot(), '.heyspex'), { recursive: true });
109
+ const body = {
110
+ armedAt: new Date().toISOString(),
111
+ sessionId: typeof sessionId === 'string' ? sessionId : null,
112
+ reason,
113
+ };
114
+ fs.writeFileSync(pendingTerminalClosePath(), JSON.stringify(body));
115
+ debugLog('armed', body);
116
+ } catch {
117
+ // never block the hook
118
+ }
119
+ }
120
+
121
+ export function clearPendingTerminalClose() {
122
+ try {
123
+ fs.unlinkSync(pendingTerminalClosePath());
124
+ } catch {
125
+ // absent is fine
126
+ }
127
+ }
128
+
129
+ export function isPendingTerminalCloseArmed() {
130
+ return readPendingTerminalClose() !== null;
131
+ }
132
+
133
+ /**
134
+ * Bases that can contain spec files. Since the repo split (2026-08-31) the
135
+ * vault lives outside the code repo, so spec edits arrive as paths under the
136
+ * registry-resolved vault root, not under CLAUDE_PROJECT_DIR — test both.
137
+ */
138
+ export function specBaseRoots() {
139
+ const roots = [repoRoot()];
140
+ try {
141
+ const resolved = getActiveConfig();
142
+ if (resolved?.repoRootAbs && !roots.includes(resolved.repoRootAbs)) {
143
+ roots.push(resolved.repoRootAbs);
144
+ }
145
+ } catch {
146
+ // registry unreadable — the code-repo base still applies
147
+ }
148
+ return roots;
149
+ }
150
+
151
+ export function isUnderSpecsTaskOrEpic(filePath) {
152
+ if (typeof filePath !== 'string' || filePath.length === 0) return false;
153
+ const abs = path.resolve(filePath);
154
+ for (const root of specBaseRoots()) {
155
+ for (const sub of ['specs/tasks', 'specs/epics']) {
156
+ const base = path.join(root, sub);
157
+ const rel = path.relative(base, abs);
158
+ if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) return true;
159
+ }
160
+ }
161
+ return false;
162
+ }
163
+
164
+ export function isReviewDocPath(filePath) {
165
+ if (typeof filePath !== 'string' || filePath.length === 0) return false;
166
+ return REVIEW_DOC_RE.test(path.resolve(filePath));
167
+ }
168
+
169
+ function textFlipToShipped(prev, next) {
170
+ const wasShipped = STATUS_REVIEW_RE.test(prev) || STATUS_CLOSED_RE.test(prev);
171
+ const nowShipped = STATUS_REVIEW_RE.test(next) || STATUS_CLOSED_RE.test(next);
172
+ return nowShipped && !wasShipped;
173
+ }
174
+
175
+ export function detectShipFromEditLike(toolName, toolInput) {
176
+ if (!toolInput || typeof toolInput !== 'object') return false;
177
+ if (!isUnderSpecsTaskOrEpic(toolInput.file_path)) return false;
178
+
179
+ if (toolName === 'Edit' || toolName === 'StrReplace' || toolName === 'search_replace') {
180
+ const next = typeof toolInput.new_string === 'string' ? toolInput.new_string : '';
181
+ const prev = typeof toolInput.old_string === 'string' ? toolInput.old_string : '';
182
+ return textFlipToShipped(prev, next);
183
+ }
184
+
185
+ if (toolName === 'MultiEdit') {
186
+ const edits = Array.isArray(toolInput.edits) ? toolInput.edits : [];
187
+ return edits.some((edit) => {
188
+ const next = typeof edit?.new_string === 'string' ? edit.new_string : '';
189
+ const prev = typeof edit?.old_string === 'string' ? edit.old_string : '';
190
+ return textFlipToShipped(prev, next);
191
+ });
192
+ }
193
+
194
+ if (toolName === 'Write' || toolName === 'WriteFile') {
195
+ const content = typeof toolInput.content === 'string' ? toolInput.content : '';
196
+ return STATUS_REVIEW_RE.test(content) || STATUS_CLOSED_RE.test(content);
197
+ }
198
+
199
+ return false;
200
+ }
201
+
202
+ export function detectShipFromAfterFileEdit(payload) {
203
+ const filePath = payload?.file_path;
204
+ if (!isUnderSpecsTaskOrEpic(filePath)) return false;
205
+ const edits = Array.isArray(payload?.edits) ? payload.edits : [];
206
+ return edits.some((edit) => {
207
+ const next = typeof edit?.new_string === 'string' ? edit.new_string : '';
208
+ const prev = typeof edit?.old_string === 'string' ? edit.old_string : '';
209
+ return textFlipToShipped(prev, next);
210
+ });
211
+ }
212
+
213
+ export function normalizeHookEvent(payload) {
214
+ const raw = payload?.hook_event_name;
215
+ if (typeof raw === 'string' && raw.trim()) {
216
+ const key = raw.trim();
217
+ if (key === 'postToolUse' || key === 'PostToolUse') return 'postToolUse';
218
+ if (key === 'sessionEnd' || key === 'SessionEnd') return 'sessionEnd';
219
+ if (key === 'afterFileEdit') return 'afterFileEdit';
220
+ if (key === 'afterMCPExecution') return 'afterMCPExecution';
221
+ if (key === 'stop' || key === 'Stop') return 'stop';
222
+ return key;
223
+ }
224
+ if (Array.isArray(payload?.edits) && payload?.file_path) return 'afterFileEdit';
225
+ if (typeof payload?.tool_name === 'string') return 'postToolUse';
226
+ return null;
227
+ }
228
+
229
+ export function toolCallFailed(payload) {
230
+ const response = payload?.tool_response;
231
+ if (response && typeof response === 'object' && response.is_error === true) return true;
232
+ if (payload?.failure_type) return true;
233
+ const output = payload?.tool_output;
234
+ if (typeof output === 'string') {
235
+ try {
236
+ const parsed = JSON.parse(output);
237
+ if (parsed?.isError === true || parsed?.error) return true;
238
+ } catch {
239
+ // not json
240
+ }
241
+ }
242
+ return false;
243
+ }
244
+
245
+ export function isShipEvent(payload) {
246
+ const event = normalizeHookEvent(payload);
247
+ if (!event) return false;
248
+ if (toolCallFailed(payload)) return false;
249
+
250
+ if (event === 'afterFileEdit') {
251
+ return detectShipFromAfterFileEdit(payload);
252
+ }
253
+
254
+ if (event === 'postToolUse') {
255
+ const toolName = payload?.tool_name;
256
+ if (typeof toolName === 'string' && EDIT_TOOL_NAMES.has(toolName)) {
257
+ return detectShipFromEditLike(toolName, payload?.tool_input ?? {});
258
+ }
259
+ }
260
+
261
+ return false;
262
+ }
263
+
264
+ export function isRecordWipSuccess(payload) {
265
+ const event = normalizeHookEvent(payload);
266
+ if (event !== 'postToolUse' && event !== 'afterMCPExecution') return false;
267
+ if (toolCallFailed(payload)) return false;
268
+
269
+ const toolName = typeof payload?.tool_name === 'string' ? payload.tool_name : '';
270
+ if (!RECORD_WIP_TOOL_RE.test(toolName)) return false;
271
+ return true;
272
+ }
273
+
274
+ export function isReviewDocWriteSuccess(payload) {
275
+ const event = normalizeHookEvent(payload);
276
+ if (event !== 'postToolUse' && event !== 'afterFileEdit') return false;
277
+ if (toolCallFailed(payload)) return false;
278
+
279
+ if (event === 'afterFileEdit') {
280
+ return isReviewDocPath(payload?.file_path);
281
+ }
282
+
283
+ const toolName = payload?.tool_name;
284
+ if (toolName !== 'Write' && toolName !== 'WriteFile' && toolName !== 'Edit') return false;
285
+ return isReviewDocPath(payload?.tool_input?.file_path);
286
+ }
287
+
288
+ export function isSessionEndEvent(payload) {
289
+ return normalizeHookEvent(payload) === 'sessionEnd';
290
+ }
291
+
292
+ export function wipStateUpdatedAfter(armedAtIso) {
293
+ if (!armedAtIso) return false;
294
+ const armedMs = Date.parse(armedAtIso);
295
+ if (!Number.isFinite(armedMs)) return false;
296
+ // Since the repo split the ledger heyspex_record_wip writes lives in the VAULT.
297
+ // Watching only repoRoot() here meant a recorded WIP never satisfied the arm, so
298
+ // the ship-close never fired. Check every base that can hold a ledger.
299
+ for (const root of specBaseRoots()) {
300
+ try {
301
+ const raw = fs.readFileSync(path.join(root, '.heyspex', 'wip-state.json'), 'utf8');
302
+ const data = JSON.parse(raw);
303
+ if (!data || typeof data !== 'object') continue;
304
+ for (const entry of Object.values(data)) {
305
+ const updatedAt = entry?.updatedAt;
306
+ if (typeof updatedAt === 'string' && Date.parse(updatedAt) > armedMs) return true;
307
+ }
308
+ } catch {
309
+ // missing or unreadable at this root — try the next one
310
+ }
311
+ }
312
+ return false;
313
+ }
314
+
315
+ export function shouldCloseNow() {
316
+ const pending = readPendingTerminalClose();
317
+ if (!pending?.armedAt) return false;
318
+ return wipStateUpdatedAfter(pending.armedAt);
319
+ }
320
+
321
+ export function scheduleAgentHostExit(startPid = process.ppid) {
322
+ if (!Number.isFinite(startPid) || startPid <= 0) {
323
+ debugLog('kill_skipped', { reason: 'invalid_start_pid', startPid });
324
+ return;
325
+ }
326
+
327
+ debugLog('kill_scheduled', { startPid });
328
+
329
+ if (process.platform === 'win32') {
330
+ const ps = [
331
+ '-NoProfile',
332
+ '-WindowStyle',
333
+ 'Hidden',
334
+ '-Command',
335
+ `
336
+ $ErrorActionPreference = 'SilentlyContinue'
337
+ $current = ${startPid}
338
+ for ($i = 0; $i -lt 40; $i++) {
339
+ $p = Get-CimInstance Win32_Process -Filter "ProcessId=$current"
340
+ if (-not $p) { break }
341
+ $name = [string]$p.Name
342
+ $cmd = [string]$p.CommandLine
343
+ if ($name -ieq 'claude.exe') { Stop-Process -Id $current -Force; exit 0 }
344
+ if ($cmd -match 'cursor-agent|@anthropic-ai/claude-code|\\\\cursor-agent\\\\') { Stop-Process -Id $current -Force; exit 0 }
345
+ $parent = [int]$p.ParentProcessId
346
+ if ($parent -le 0 -or $parent -eq $current) { break }
347
+ $current = $parent
348
+ }
349
+ Stop-Process -Id ${startPid} -Force -ErrorAction SilentlyContinue
350
+ `.trim(),
351
+ ];
352
+ try {
353
+ const child = spawn('powershell.exe', ps, {
354
+ detached: true,
355
+ stdio: 'ignore',
356
+ windowsHide: true,
357
+ });
358
+ child.unref();
359
+ } catch (err) {
360
+ debugLog('kill_failed', { message: err instanceof Error ? err.message : String(err) });
361
+ }
362
+ return;
363
+ }
364
+
365
+ const sh = `pid=${startPid}; for i in $(seq 1 30); do
366
+ line=$(ps -p "$pid" -o comm= 2>/dev/null) || break
367
+ case "$line" in *claude*|*Claude*) kill -TERM "$pid" 2>/dev/null; exit 0 ;; esac
368
+ cmd=$(ps -p "$pid" -o args= 2>/dev/null || true)
369
+ case "$cmd" in *cursor-agent*) kill -TERM "$pid" 2>/dev/null; exit 0 ;; esac
370
+ ppid=$(ps -p "$pid" -o ppid= 2>/dev/null | tr -d ' ')
371
+ [ -z "$ppid" ] || [ "$ppid" = "0" ] || [ "$ppid" = "$pid" ] && break
372
+ pid=$ppid
373
+ done
374
+ kill -TERM ${startPid} 2>/dev/null || true`;
375
+ try {
376
+ const child = spawn('sh', ['-c', sh], { detached: true, stdio: 'ignore' });
377
+ child.unref();
378
+ } catch (err) {
379
+ debugLog('kill_failed', { message: err instanceof Error ? err.message : String(err) });
380
+ }
381
+ }
382
+
383
+ export function tryCloseIfReady(reason) {
384
+ if (!resolveCloseTerminalOnTaskCompleteFromSettings()) {
385
+ debugLog('close_skipped', { reason: 'setting_disabled', trigger: reason });
386
+ return false;
387
+ }
388
+ if (!isPendingTerminalCloseArmed()) {
389
+ debugLog('close_skipped', { reason: 'not_armed', trigger: reason });
390
+ return false;
391
+ }
392
+ if (!shouldCloseNow() && reason !== 'session_end' && reason !== 'record_wip') {
393
+ debugLog('close_skipped', { reason: 'wip_not_updated', trigger: reason });
394
+ return false;
395
+ }
396
+ clearPendingTerminalClose();
397
+ debugLog('closing', { trigger: reason });
398
+ scheduleAgentHostExit();
399
+ return true;
400
+ }
401
+
402
+ export async function readStdin() {
403
+ if (process.stdin.isTTY) return null;
404
+ return new Promise((resolve) => {
405
+ let buf = '';
406
+ let resolved = false;
407
+ const finish = (value) => {
408
+ if (resolved) return;
409
+ resolved = true;
410
+ resolve(value);
411
+ };
412
+ const timer = setTimeout(() => finish(buf || null), 500);
413
+ process.stdin.setEncoding('utf8');
414
+ process.stdin.on('data', (chunk) => {
415
+ buf += chunk;
416
+ });
417
+ process.stdin.on('end', () => {
418
+ clearTimeout(timer);
419
+ finish(buf || null);
420
+ });
421
+ process.stdin.on('error', () => {
422
+ clearTimeout(timer);
423
+ finish(buf || null);
424
+ });
425
+ });
426
+ }
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ // Spawn-side fallback when hooks miss ship/close events (external cursor-agent terminal).
3
+ // Polls claimed spec status + wip-state.json; kills the agent host when close-out lands.
4
+ //
5
+ // Started detached from spawnNewAgent when auto-close is enabled.
6
+ // Mute: HEYSPEX_AUTO_CLOSE_TERMINAL=0 or HEYSPEX_CLOSE_TERMINAL_ON_SHIP_OFF=1
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ import {
13
+ armPendingTerminalClose,
14
+ debugLog,
15
+ clearPendingTerminalClose,
16
+ isPendingTerminalCloseArmed,
17
+ readPendingTerminalClose,
18
+ repoRoot,
19
+ resolveCloseTerminalOnTaskCompleteFromSettings,
20
+ specBaseRoots,
21
+ scheduleAgentHostExit,
22
+ shouldCloseNow,
23
+ STATUS_CLOSED_RE,
24
+ STATUS_REVIEW_RE,
25
+ } from './terminal-close-shared.mjs';
26
+
27
+ const POLL_MS = 3000;
28
+ const MAX_MS = 8 * 60 * 60 * 1000;
29
+
30
+ function readActiveClaimSpecId() {
31
+ try {
32
+ const raw = fs.readFileSync(path.join(repoRoot(), '.heyspex', 'active-claims.json'), 'utf8');
33
+ const claims = JSON.parse(raw);
34
+ const session =
35
+ process.env.CURSOR_TRACE_ID ??
36
+ process.env.CLAUDE_CODE_SESSION_ID ??
37
+ process.env.HEYSPEX_SESSION_ID ??
38
+ '';
39
+ if (!Array.isArray(claims)) return null;
40
+ const mine = claims.find(
41
+ (c) =>
42
+ c &&
43
+ (session
44
+ ? c.sessionId === session || c.session_id === session
45
+ : c.agent === process.env.HEYSPEX_AGENT),
46
+ );
47
+ return mine?.specId ?? mine?.spec_id ?? null;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ function findSpecFile(specId) {
54
+ for (const root of specBaseRoots()) {
55
+ for (const dir of ['specs/tasks', 'specs/epics']) {
56
+ const folder = path.join(root, dir);
57
+ let names;
58
+ try {
59
+ names = fs.readdirSync(folder);
60
+ } catch {
61
+ continue;
62
+ }
63
+ const hit = names.find((n) => n.startsWith(`${specId}-`) || n.startsWith(specId));
64
+ if (hit) return path.join(folder, hit);
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+
70
+ function readSpecStatus(specPath) {
71
+ try {
72
+ const raw = fs.readFileSync(specPath, 'utf8');
73
+ const m = raw.match(/^\s*status:\s*(\S+)\s*$/m);
74
+ return m?.[1] ?? null;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ function isShippedStatus(status) {
81
+ return status === 'in_review' || status === 'closed' || status === 'review_pending';
82
+ }
83
+
84
+ function pollOnce() {
85
+ if (!resolveCloseTerminalOnTaskCompleteFromSettings()) return false;
86
+
87
+ const specId = readActiveClaimSpecId();
88
+ if (!specId) return false;
89
+
90
+ const specPath = findSpecFile(specId);
91
+ if (!specPath) return false;
92
+
93
+ const status = readSpecStatus(specPath);
94
+ if (!isShippedStatus(status)) return false;
95
+
96
+ if (!isPendingTerminalCloseArmed()) {
97
+ armPendingTerminalClose(null, `watcher:${status}`);
98
+ }
99
+
100
+ if (shouldCloseNow()) {
101
+ debugLog('watcher_close', { specId, status });
102
+ clearPendingTerminalClose();
103
+ scheduleAgentHostExit();
104
+ return true;
105
+ }
106
+
107
+ const pending = readPendingTerminalClose();
108
+ if (pending?.armedAt) {
109
+ debugLog('watcher_waiting_wip', { specId, status, armedAt: pending.armedAt });
110
+ }
111
+ return false;
112
+ }
113
+
114
+ const started = Date.now();
115
+ debugLog('watcher_start', { pid: process.pid });
116
+
117
+ while (Date.now() - started < MAX_MS) {
118
+ if (pollOnce()) break;
119
+ await new Promise((r) => setTimeout(r, POLL_MS));
120
+ }
121
+
122
+ debugLog('watcher_exit', {});
@@ -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/vault-mirror-catchup.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/vault-mirror-catchup.mjs';
10
+
11
+ await runGateAsHook(gate);
@@ -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/vault-write-tier-guard.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/vault-write-tier-guard.mjs';
10
+
11
+ await runGateAsHook(gate);