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,657 @@
1
+ // JSON-RPC method handlers for initialize, tools/list, tools/call (the
2
+ // dispatchHeySpexTool switch), session/end and hooks/*, plus the seat banner and
3
+ // peer-presence injection applied to every tool result. Extracted from cli.mjs under
4
+ // [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
5
+
6
+ import { executeHook, finalizeToolResultWithHooks, listHookConfig, loadHooksConfig, matchHooks, runHooks } from './hooks-engine.mjs';
7
+ import { detectAgentIdentity, getConfig } from './mcp-config.mjs';
8
+ import { setWriteOutboxRoot } from './write-outbox.mjs';
9
+ import { existsSync, readFileSync } from 'node:fs';
10
+ import { evaluatePreflightGate } from './preflight-gate.mjs';
11
+ import { evaluateObligationBackstop } from './obligation-backstop.mjs';
12
+ import { TOOLS, handleLoadTools } from './tool-catalog.mjs';
13
+ import { handleNextOnDeck, handleNextResolution, handleNextReview, handleResolvePrerequisite, handleSuggestPillar } from './on-deck-brief.mjs';
14
+ import { handleFileSpec, handleGroundPlan, handlePlanQc, handlePlanReview, handlePushPlan, handleRegisterSpec, handleValidateLinkage, handleValidateUserSkills } from './filing-tools.mjs';
15
+ import { handleActiveResearch, handleCompleteResearch, handlePromoteResearch, handlePromoteResearchToDiagnose, handleSaveResearch, handleSplitResearch } from './research-handlers.mjs';
16
+ import { handleRecallMemoriesViaTier } from './memory-tools.mjs';
17
+ import { handleCanvasInbox, handleEditFile, handleListKnownTags, handleProposeMetadata, handleRegenTask, handleRegenTasksForEpic, handleShipEpic } from './spec-edit-tools.mjs';
18
+ import { handleAuthorSummary } from './summary-write-tools.mjs';
19
+ import { handleActiveCapture, handlePinCapture, handlePromoteCapture, handlePromoteCaptureToDiagnose, handleUpdateCapture } from './capture-tools.mjs';
20
+ import { handleClaimAcquire, handleClaimFile, handleClaimList, handleClaimRelease, handleClaimSpec, handleDeferSpec, handleRecordAlignmentViaTier, handleRecordWip, handleReleaseFileClaim, handleReleaseSpec, handleReportProgress, handleSubmitForReview } from './claim-tools.mjs';
21
+ import { handlePromoteDiagnose, handleVerifyReproduction } from './diagnose-handlers.mjs';
22
+ import { handleHandoffLoad, handleHandoffRecord, handleImportAgent } from './session-tools.mjs';
23
+ import { handleEventsShow, handleEvidenceShow, handleExportEvents, handleMilestoneShow } from './events-tools.mjs';
24
+ import { handleClearFocusAreas, handleGetFocusAreas, handlePinGet, handlePinSet, handleResolveFocusIntent, handleSetFocusAreas } from './focus-tools.mjs';
25
+ import { handleSearchSpecs } from './search-specs.mjs';
26
+ import { handleDesignGetDoc, handleDesignGetPattern, handleDesignGetToken, handleDesignListPatterns, handleDesignProposePattern } from './design-handlers.mjs';
27
+ import { handleGetProcedureViaTier, handleListProceduresViaTier, handleRunPreflightViaTier } from './procedure-handlers.mjs';
28
+ import { handleListRecipes, handleStartRecipe } from './recipe-handlers.mjs';
29
+ import { handleFleetQueueClaim, handleFleetQueueComplete, handleFleetQueueList, handleFleetQueueSubmit } from './fleet-handlers.mjs';
30
+ import { handleDevMode, handleDevServerStatus } from './dev-tools.mjs';
31
+ import { wrapDesignResult } from './tool-helpers.mjs';
32
+ import { executeSettingsGet, executeSettingsList, executeSettingsSet } from './settings-tools.mjs';
33
+ import { executeFirecrawlResearch, executeResolveResearchPlan } from './research-tools.mjs';
34
+ import { formatSeatBanner, readSeatsRegistry, resolveSeat } from './seat-identity.mjs';
35
+ import { seatBlockForSessionSurface } from './seat-block-ledger.mjs';
36
+ import { resolveCooModeEnabled } from '../../dev-factory/lib/fleet-readiness.mjs';
37
+ import { applyToolBudget } from './response-budget.mjs';
38
+ import { resolveDevMode } from '../../scripts/lib/dev-mode.mjs';
39
+ import { dirname, join } from 'node:path';
40
+ import { MODULE_DIR } from './package-root.mjs';
41
+
42
+ export const SERVER_NAME = 'heyspex';
43
+
44
+ /**
45
+ * Read the server version from the nearest `package.json`, walking up from
46
+ * THIS file's own directory (`vendor/heyspex-mcp/package.json` if one ever
47
+ * exists, else the repo root's) — [[HS-7OKX-0JNK-TASK]]. Previously a
48
+ * hard-coded `'0.9.0'` that had drifted from the packed `heyspex@0.1.0`, so
49
+ * the `initialize` handshake's `serverInfo.version` disagreed with what was
50
+ * actually installed. Never throws: an unreadable/missing/malformed
51
+ * package.json anywhere up the tree falls through to `'0.0.0-unknown'`.
52
+ */
53
+ export function readServerVersion() {
54
+ let dir = MODULE_DIR;
55
+ for (let i = 0; i < 16; i += 1) {
56
+ const candidate = join(dir, 'package.json');
57
+ if (existsSync(candidate)) {
58
+ try {
59
+ const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
60
+ if (pkg && typeof pkg.version === 'string' && pkg.version.trim()) {
61
+ return pkg.version;
62
+ }
63
+ } catch {
64
+ // malformed package.json at this level — keep walking up rather than throw
65
+ }
66
+ }
67
+ const parent = dirname(dir);
68
+ if (parent === dir) break; // reached filesystem root
69
+ dir = parent;
70
+ }
71
+ return '0.0.0-unknown';
72
+ }
73
+
74
+ export const SERVER_VERSION = readServerVersion();
75
+
76
+ // MCP Channels capability is advertised in the initialize handshake.
77
+ // Per https://code.claude.com/docs/en/channels-reference#server-options the
78
+ // channel capability is nested under `experimental` and pushed events use
79
+ // the `notifications/claude/channel` method. The instructions string is
80
+ // injected into Claude's system prompt so it knows how to react to
81
+ // <channel source="heyspex" …> events emitted by capture-channel.mjs.
82
+ // Owning epic: [[HS-EPIC-QXJ3-modal-agent-handshake-mcp-channels-push-]].
83
+ export const CHANNEL_INSTRUCTIONS =
84
+ 'Events from the heyspex capture flow arrive as <channel source="heyspex" capture_id="HS-CAP-XXXX" event_type="user_quote">…</channel>. ' +
85
+ 'When you receive one, call heyspex_active_capture WITH spec_id set to that exact capture_id ' +
86
+ '(e.g. heyspex_active_capture({ spec_id: "HS-CAP-XXXX" })) to fetch the latest capture state, and pass that same spec_id ' +
87
+ 'to every heyspex_update_capture call for this capture. Do NOT call these tools without spec_id when you have the channel capture_id — ' +
88
+ 'the no-spec_id path attaches to whichever capture was most recently active, which may be a different session (another console tab, ' +
89
+ 'the companion app, or another agent) and would write to the wrong capture id. Then respond per the /hs-capture capture protocol ' +
90
+ '(Capture → Ground=Expand [grounding auto-writes agent_expansion, no confirm] → Promote to research). Do not infer user_quote ' +
91
+ 'contents from the channel hint alone — always fetch fresh state via the tool.';
92
+
93
+ // ── Dev-factory seat identity delivery (epic HS-CXO0) ────────────────────────
94
+ // Resolve the seat THIS caller holds — gated by the shared fleet-readiness
95
+ // predicate (identical to the SessionStart hook → no drift). Returns null (inject
96
+ // nothing) on any error, cooMode off / auto-not-ready, or an unknown caller. A
97
+ // Builder worker resolves to the Builder seat, a COO session to COO — each is told
98
+ // its OWN seat, never another's.
99
+ export function resolveInjectableSeat() {
100
+ try {
101
+ const { vaultRoot } = getConfig();
102
+ const registry = readSeatsRegistry(vaultRoot);
103
+ if (!registry) return null;
104
+ if (!resolveCooModeEnabled(registry.settings?.cooMode).enabled) return null;
105
+ const identity = detectAgentIdentity();
106
+ return resolveSeat(identity, { registry, env: process.env });
107
+ } catch {
108
+ return null; // seat logic must never break the server
109
+ }
110
+ }
111
+
112
+ export async function handleInitialize(params) {
113
+ let instructions = CHANNEL_INSTRUCTIONS;
114
+ try {
115
+ const banner = formatSeatBanner(resolveInjectableSeat());
116
+ if (banner) {
117
+ // Gate on the shared per-session ledger (HS-8U8X) rather than emitting
118
+ // unconditionally — the first tool result of the session (see cli.mjs
119
+ // maybePrependSeatBanner) shares this same ledger, so whichever surface
120
+ // asks first wins and the other gets null, never a second copy.
121
+ const { sessionId } = detectAgentIdentity();
122
+ const toSend = await seatBlockForSessionSurface(sessionId, 'mcp_instructions', banner);
123
+ if (toSend) instructions += `\n\n${toSend}`;
124
+ }
125
+ } catch {
126
+ // never break the initialize handshake on seat logic
127
+ }
128
+ return {
129
+ protocolVersion: '2024-11-05',
130
+ capabilities: {
131
+ // listChanged: heyspex_load_tools expands the visible surface at runtime
132
+ // ([[HS-LCRS-TASK]] progressive disclosure) and notifies the client.
133
+ tools: { listChanged: true },
134
+ prompts: {},
135
+ // [[HS-EPIC-QXJ3]] — channels capability nested under `experimental`
136
+ // per https://code.claude.com/docs/en/channels-reference#server-options.
137
+ // Presence registers the notification listener; emission happens in
138
+ // capture-channel.mjs via sendNotification('notifications/claude/channel', …).
139
+ experimental: {
140
+ 'claude/channel': {},
141
+ },
142
+ },
143
+ instructions,
144
+ serverInfo: {
145
+ name: SERVER_NAME,
146
+ version: SERVER_VERSION,
147
+ },
148
+ };
149
+ }
150
+
151
+ export function handleToolsList() {
152
+ // `budget` is a server-side contract (enforced below), not part of the MCP
153
+ // Tool wire shape — strip it so the client sees only name/description/schema.
154
+ return { tools: TOOLS.map(({ budget: _budget, ...tool }) => tool) };
155
+ }
156
+
157
+ /** The catalog's declared byte budget for a tool, or `null` when it declares none. */
158
+ export function toolBudgetFor(name) {
159
+ const tool = TOOLS_BY_NAME.get(name);
160
+ return tool?.budget && Number(tool.budget.max_bytes) > 0 ? tool.budget : null;
161
+ }
162
+
163
+ const TOOLS_BY_NAME = new Map(TOOLS.map((t) => [t.name, t]));
164
+
165
+ /**
166
+ * Every tool call goes through here. A tool that declares `budget` in the
167
+ * catalog ([[HS-4H8T-8KK5-TASK-every-mcp-read-tool-declares-a-byte]]) has
168
+ * its reply bounded to `budget.max_bytes` on the way out — trailing list items
169
+ * dropped behind a `next_cursor`, `truncated: true` — and a budget cursor on
170
+ * the way in resumes from the first dropped item (see response-budget.mjs).
171
+ * A tool without a budget is dispatched untouched.
172
+ */
173
+ export function dispatchHeySpexTool(name, args, vaultRoot, config) {
174
+ // Lets deep writers (createSpecViaConsole & co.) and the first-success drain
175
+ // find the write outbox without every caller threading a vaultRoot.
176
+ setWriteOutboxRoot(vaultRoot);
177
+ return applyToolBudget(
178
+ name,
179
+ args,
180
+ (effectiveArgs) => dispatchHeySpexToolUnbudgeted(name, effectiveArgs, vaultRoot, config),
181
+ toolBudgetFor(name),
182
+ );
183
+ }
184
+
185
+ function dispatchHeySpexToolUnbudgeted(name, args, vaultRoot, config) {
186
+ switch (name) {
187
+ case 'heyspex_load_tools':
188
+ // Normally intercepted parent-side at the tools/call dispatch; this case is
189
+ // defense-in-depth for direct handleToolsCall callers (subprocess/CLI paths).
190
+ return handleLoadTools(args);
191
+ case 'heyspex_next_on_deck':
192
+ return handleNextOnDeck(vaultRoot, config, args);
193
+ case 'heyspex_next_review':
194
+ return handleNextReview(vaultRoot, config, args);
195
+ case 'heyspex_next_resolution':
196
+ return handleNextResolution(args);
197
+ case 'heyspex_validate_linkage':
198
+ return handleValidateLinkage(vaultRoot, args);
199
+ case 'heyspex_suggest_pillar':
200
+ return handleSuggestPillar(args);
201
+ case 'heyspex_validate_user_skills':
202
+ return handleValidateUserSkills(vaultRoot, args);
203
+ case 'heyspex_register_spec':
204
+ return handleRegisterSpec(vaultRoot, args);
205
+ case 'heyspex_save_research':
206
+ return handleSaveResearch(vaultRoot, args);
207
+ case 'heyspex_recall_memories':
208
+ return handleRecallMemoriesViaTier(vaultRoot, args);
209
+ case 'heyspex_push_plan':
210
+ return handlePushPlan(vaultRoot, args);
211
+ case 'heyspex_backfill_epic':
212
+ return handleShipEpic(vaultRoot, args);
213
+ case 'heyspex_ground_plan':
214
+ return handleGroundPlan(vaultRoot, args);
215
+ case 'heyspex_plan_review':
216
+ return handlePlanReview(vaultRoot, args);
217
+ case 'heyspex_plan_qc':
218
+ return handlePlanQc(vaultRoot, args);
219
+ case 'heyspex_active_capture':
220
+ return handleActiveCapture(vaultRoot, args);
221
+ case 'heyspex_update_capture':
222
+ return handleUpdateCapture(vaultRoot, args);
223
+ case 'heyspex_pin_capture':
224
+ return handlePinCapture(vaultRoot, args);
225
+ case 'heyspex_active_research':
226
+ return handleActiveResearch(vaultRoot, args);
227
+ case 'heyspex_complete_research':
228
+ return handleCompleteResearch(vaultRoot, args);
229
+ case 'heyspex_claim_spec':
230
+ return handleClaimSpec(args);
231
+ case 'heyspex_report_progress':
232
+ return handleReportProgress(args);
233
+ case 'heyspex_submit_for_review':
234
+ return handleSubmitForReview(vaultRoot, args);
235
+ case 'heyspex_verify_reproduction':
236
+ return handleVerifyReproduction(vaultRoot, args);
237
+ case 'heyspex_promote_diagnose':
238
+ return handlePromoteDiagnose(vaultRoot, args);
239
+ case 'heyspex_release_spec':
240
+ return handleReleaseSpec(args);
241
+ case 'heyspex_record_wip':
242
+ return handleRecordWip(args);
243
+ case 'heyspex_author_summary':
244
+ return handleAuthorSummary(vaultRoot, args);
245
+ case 'heyspex_defer_spec':
246
+ return handleDeferSpec(args);
247
+ case 'heyspex_handoff_record':
248
+ return handleHandoffRecord(args);
249
+ case 'heyspex_handoff_load':
250
+ return handleHandoffLoad(args);
251
+ case 'heyspex_milestone_show':
252
+ return handleMilestoneShow(args);
253
+ case 'heyspex_pin_get':
254
+ return handlePinGet(args);
255
+ case 'heyspex_pin_set':
256
+ return handlePinSet(args);
257
+ case 'heyspex_resolve_prerequisite':
258
+ return handleResolvePrerequisite(args);
259
+ case 'heyspex_claim_acquire':
260
+ return handleClaimAcquire(args);
261
+ case 'heyspex_claim_release':
262
+ return handleClaimRelease(args);
263
+ case 'heyspex_claim_list':
264
+ return handleClaimList(args);
265
+ case 'heyspex_claim_file':
266
+ return handleClaimFile(args);
267
+ case 'heyspex_release_file_claim':
268
+ return handleReleaseFileClaim(args);
269
+ case 'heyspex_edit_file':
270
+ return handleEditFile(args);
271
+ case 'heyspex_list_known_tags':
272
+ return handleListKnownTags(args);
273
+ case 'heyspex_set_focus_areas':
274
+ return handleSetFocusAreas(args);
275
+ case 'heyspex_get_focus_areas':
276
+ return handleGetFocusAreas(args);
277
+ case 'heyspex_clear_focus_areas':
278
+ return handleClearFocusAreas(args);
279
+ case 'heyspex_resolve_focus_intent':
280
+ return handleResolveFocusIntent(args);
281
+ case 'heyspex_search_specs':
282
+ return handleSearchSpecs(args);
283
+ case 'heyspex_events_show':
284
+ return handleEventsShow(args);
285
+ case 'heyspex_evidence_show':
286
+ return handleEvidenceShow(args);
287
+ case 'heyspex_export_events':
288
+ return handleExportEvents(args);
289
+ case 'heyspex_propose_metadata':
290
+ return handleProposeMetadata(args);
291
+ case 'heyspex_promote_research':
292
+ return handlePromoteResearch(vaultRoot, args);
293
+ case 'heyspex_promote_capture':
294
+ return handlePromoteCapture(vaultRoot, args);
295
+ case 'heyspex_promote_capture_to_diagnose':
296
+ return handlePromoteCaptureToDiagnose(vaultRoot, args);
297
+ case 'heyspex_promote_research_to_diagnose':
298
+ return handlePromoteResearchToDiagnose(vaultRoot, args);
299
+ case 'heyspex_split_research':
300
+ return handleSplitResearch(vaultRoot, args);
301
+ case 'heyspex_record_alignment':
302
+ return handleRecordAlignmentViaTier(vaultRoot, config, args);
303
+ case 'heyspex_design_list_patterns':
304
+ return handleDesignListPatterns(vaultRoot, args);
305
+ case 'heyspex_design_get_pattern':
306
+ return handleDesignGetPattern(vaultRoot, args);
307
+ case 'heyspex_design_get_token':
308
+ return handleDesignGetToken(vaultRoot, args);
309
+ case 'heyspex_design_get_doc':
310
+ return handleDesignGetDoc(vaultRoot, args);
311
+ case 'heyspex_design_propose_pattern':
312
+ return handleDesignProposePattern(vaultRoot, args);
313
+ case 'heyspex_list_procedures':
314
+ return handleListProceduresViaTier(vaultRoot, args);
315
+ case 'heyspex_get_procedure':
316
+ return handleGetProcedureViaTier(vaultRoot, args);
317
+ case 'heyspex_file_spec':
318
+ return handleFileSpec(args, vaultRoot);
319
+ case 'heyspex_import_agent':
320
+ return handleImportAgent(args);
321
+ case 'heyspex_preflight':
322
+ return handleRunPreflightViaTier(vaultRoot, args);
323
+ case 'heyspex_list_recipes':
324
+ return handleListRecipes(vaultRoot, args);
325
+ case 'heyspex_start_recipe':
326
+ return handleStartRecipe(vaultRoot, args);
327
+ case 'heyspex_canvas_inbox':
328
+ return handleCanvasInbox(vaultRoot, args);
329
+ case 'heyspex_fleet_queue_submit':
330
+ return handleFleetQueueSubmit(args);
331
+ case 'heyspex_fleet_queue_claim':
332
+ return handleFleetQueueClaim(args);
333
+ case 'heyspex_fleet_queue_complete':
334
+ return handleFleetQueueComplete(args);
335
+ case 'heyspex_fleet_queue_list':
336
+ return handleFleetQueueList(args);
337
+
338
+ case 'heyspex_dev_server_status':
339
+ return handleDevServerStatus(vaultRoot);
340
+ case 'heyspex_dev_mode':
341
+ return handleDevMode(vaultRoot, args);
342
+ case 'heyspex_settings_list':
343
+ return wrapDesignResult(executeSettingsList(vaultRoot, args));
344
+ case 'heyspex_settings_get':
345
+ return wrapDesignResult(executeSettingsGet(vaultRoot, args));
346
+ case 'heyspex_settings_set':
347
+ return wrapDesignResult(executeSettingsSet(vaultRoot, args));
348
+ case 'heyspex_resolve_research_plan':
349
+ // async since HS-ZJSP (the Firecrawl endpoint is resolved through the lazy
350
+ // dev-factory bridge) — same Promise-returning shape as the case below.
351
+ return executeResolveResearchPlan(vaultRoot).then(wrapDesignResult);
352
+ case 'heyspex_firecrawl_research':
353
+ // dispatchHeySpexTool is NOT async (mirrors the heyspex_dev_server_status precedent
354
+ // above) — return the chained Promise directly; finalizeToolResultWithHooks/
355
+ // Promise.resolve() at the call site unwrap it.
356
+ return executeFirecrawlResearch(args).then(wrapDesignResult);
357
+ case 'heyspex_regen_task':
358
+ return handleRegenTask(vaultRoot, args);
359
+ case 'heyspex_regen_tasks_for_epic':
360
+ return handleRegenTasksForEpic(vaultRoot, args);
361
+ default:
362
+ return {
363
+ isError: true,
364
+ content: [{ type: 'text', text: `Unknown tool: ${name}` }],
365
+ };
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Mode-aware vault gate, shared by `tools/call` (here) and `prompts/get`
371
+ * (cli.mjs). Returns `null` to proceed, or a `{ error, message }` payload.
372
+ *
373
+ * The old gate was `if (!existsSync(vaultRoot)) refuse` — mode-blind. In
374
+ * prod / cloud reliance (`resolveDevMode().readTier === 'neon'`) the server
375
+ * runs locally under Claude Code but every read is Neon-via-console and every
376
+ * write goes to the console, so a machine may legitimately have NO vault
377
+ * clone on disk; refusing there took every tool offline for exactly the
378
+ * customers the cloud path exists for. So: an absent root only refuses on the
379
+ * disk tier. On the neon tier the call proceeds with `vaultRoot` possibly
380
+ * absent — the `*ViaTier` handlers never touch it for reads, and the few
381
+ * machine-local writes (preflight markers) fall back to the HeySpex home
382
+ * (preflight-gate.mjs `preflightMarkerDir`).
383
+ *
384
+ * `resolveDevMode` is synchronous (env -> ~/.heyspex/dev-mode.json -> default
385
+ * prod) so `handleToolsCall` keeps its sync contract; a failure to resolve the
386
+ * mode degrades to the disk-tier refusal, the safe direction.
387
+ *
388
+ * Coverage: vault-gate-mode-aware.test.mjs drives `handleToolsCall` itself with
389
+ * a nonexistent root on both tiers. Note that cloud-reliant-session.test.mjs
390
+ * BYPASSES this gate on purpose — it calls the `*ViaTier` helpers directly —
391
+ * so it proves the handlers, not the gate; keep both.
392
+ */
393
+ export function vaultRootGateVerdict(vaultRoot, { env = process.env } = {}) {
394
+ if (typeof vaultRoot === 'string' && vaultRoot && existsSync(vaultRoot)) return null;
395
+ let readTier = 'disk';
396
+ try {
397
+ readTier = resolveDevMode({ env }).readTier;
398
+ } catch {
399
+ readTier = 'disk';
400
+ }
401
+ if (readTier === 'neon') return null;
402
+ return { error: 'VAULT_NOT_FOUND', message: `Vault root not found: ${vaultRoot}` };
403
+ }
404
+
405
+ export function handleToolsCall(params) {
406
+ let { name, arguments: args = {} } = params;
407
+ const config = getConfig();
408
+ const vaultRoot = config.vaultRoot;
409
+
410
+ const gate = vaultRootGateVerdict(vaultRoot);
411
+ if (gate) {
412
+ return {
413
+ isError: true,
414
+ content: [{ type: 'text', text: JSON.stringify(gate) }],
415
+ };
416
+ }
417
+
418
+ const hooksCfg = loadHooksConfig(vaultRoot);
419
+
420
+ // Meta hook plumbing — avoid Pre/PostToolUse recursion on hook tools themselves.
421
+ if (name === 'heyspex_session_end') return handleSessionEnd(vaultRoot, args, hooksCfg);
422
+ if (name === 'heyspex_hooks_list') return applyToolBudget(name, args, () => handleHooksList(vaultRoot), toolBudgetFor(name));
423
+ if (name === 'heyspex_hooks_invoke') return handleHooksInvoke(vaultRoot, args, hooksCfg);
424
+
425
+ // ── PreToolUse hooks ──
426
+ const preResult = runHooks('PreToolUse', name, args, null, hooksCfg);
427
+ if (preResult.blocked) {
428
+ return {
429
+ isError: true,
430
+ content: [{
431
+ type: 'text',
432
+ text: JSON.stringify({
433
+ error: 'HOOK_BLOCKED',
434
+ message: preResult.reason ?? 'Blocked by PreToolUse hook',
435
+ }),
436
+ }],
437
+ };
438
+ }
439
+ if (preResult.modifiedInput) {
440
+ args = preResult.modifiedInput;
441
+ }
442
+
443
+ // Preflight proof-of-use ([[preflight-gate]]). Deliberately here and not in a
444
+ // PreToolUse hook: hooks fail open and `.heyspex/mode.json` can disarm them,
445
+ // whereas this runs on every call in every session. `refuse` returns instead
446
+ // of dispatching; `warn` lets the call through and rides along on the result.
447
+ const preflightVerdict = evaluatePreflightGate(
448
+ vaultRoot,
449
+ name,
450
+ args,
451
+ detectAgentIdentity(args.agent, args.session_id).sessionId,
452
+ );
453
+ if (preflightVerdict && preflightVerdict.level === 'refuse') {
454
+ return {
455
+ isError: true,
456
+ content: [{
457
+ type: 'text',
458
+ text: JSON.stringify({
459
+ error: preflightVerdict.error,
460
+ message: preflightVerdict.message,
461
+ required_intents: preflightVerdict.intents,
462
+ tool: preflightVerdict.tool,
463
+ }, null, 2),
464
+ }],
465
+ };
466
+ }
467
+
468
+ // Capture-obligation backstop ([[HS-20SW-DRSM-DIAG-heyspex-capture-on-idea-and-workflow]]): holds on surfaces where
469
+ // no hook loads. Fail open; break-glass HEYSPEX_OBLIGATION_BACKSTOP_OFF=1.
470
+ const obligationVerdict = evaluateObligationBackstop(name, args, {
471
+ sessionId: detectAgentIdentity(args.agent, args.session_id).sessionId,
472
+ vaultRoot,
473
+ });
474
+ if (obligationVerdict) {
475
+ return {
476
+ isError: true,
477
+ content: [{ type: 'text', text: JSON.stringify(obligationVerdict, null, 2) }],
478
+ };
479
+ }
480
+
481
+ const result = dispatchHeySpexTool(name, args, vaultRoot, config);
482
+ const finalized = finalizeToolResultWithHooks(name, args, result, hooksCfg, preResult);
483
+ return preflightVerdict && preflightVerdict.level === 'warn'
484
+ ? attachPreflightWarning(finalized, preflightVerdict)
485
+ : finalized;
486
+ }
487
+
488
+ /**
489
+ * Ride a warn-level preflight verdict along on an otherwise successful result.
490
+ * Best-effort by construction — a result whose text is not JSON is left exactly
491
+ * as it was rather than mangled to carry a warning.
492
+ */
493
+ export function attachPreflightWarning(result, verdict) {
494
+ try {
495
+ const block = result?.content?.[0];
496
+ if (!block || block.type !== 'text' || typeof block.text !== 'string') return result;
497
+ const payload = JSON.parse(block.text);
498
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return result;
499
+ payload.preflight_warning = {
500
+ code: verdict.error,
501
+ message: verdict.message,
502
+ required_intents: verdict.intents,
503
+ };
504
+ return {
505
+ ...result,
506
+ content: [{ ...block, text: JSON.stringify(payload, null, 2) }, ...result.content.slice(1)],
507
+ };
508
+ } catch {
509
+ return result;
510
+ }
511
+ }
512
+
513
+ /**
514
+ * Handle heyspex_session_end — fire SessionEnd hooks and return results.
515
+ *
516
+ * The agent calls this explicitly at session close to trigger cleanup hooks
517
+ * (release claim, record WIP, notify console). In Claude Code these fire
518
+ * automatically via SessionEnd/Stop hooks; in Command Code the agent must
519
+ * call this tool.
520
+ */
521
+ export function handleSessionEnd(vaultRoot, args, hooksCfg) {
522
+ const cfg = hooksCfg ?? loadHooksConfig(vaultRoot);
523
+ const groups = matchHooks('SessionEnd', 'heyspex_session_end', cfg);
524
+ if (groups.length === 0) {
525
+ return {
526
+ content: [{
527
+ type: 'text',
528
+ text: JSON.stringify({
529
+ session_ended: true,
530
+ hooks_run: 0,
531
+ note: 'No SessionEnd hooks configured. Call heyspex_release_spec if a spec is claimed.',
532
+ }, null, 2),
533
+ }],
534
+ };
535
+ }
536
+
537
+ const results = [];
538
+ const payload = {
539
+ hook_event_name: 'SessionEnd',
540
+ tool_name: 'heyspex_session_end',
541
+ tool_input: { reason: args?.reason ?? 'completed' },
542
+ cwd: vaultRoot,
543
+ };
544
+
545
+ for (const group of groups) {
546
+ const handlers = group.hooks ?? [];
547
+ for (const hookDef of handlers) {
548
+ try {
549
+ const r = executeHook(hookDef, payload, { vaultRoot });
550
+ results.push({
551
+ command: hookDef.command,
552
+ args: hookDef.args,
553
+ success: !r.blocked,
554
+ ...(r.reason ? { reason: r.reason } : {}),
555
+ });
556
+ } catch (err) {
557
+ results.push({
558
+ command: hookDef.command,
559
+ success: false,
560
+ error: err?.message ?? String(err),
561
+ });
562
+ }
563
+ }
564
+ }
565
+
566
+ return {
567
+ content: [{
568
+ type: 'text',
569
+ text: JSON.stringify({
570
+ session_ended: true,
571
+ hooks_run: results.length,
572
+ results,
573
+ }, null, 2),
574
+ }],
575
+ };
576
+ }
577
+
578
+ /**
579
+ * Handle heyspex_hooks_invoke — run lifecycle hooks on demand (Command Code parity).
580
+ */
581
+ export function handleHooksInvoke(vaultRoot, args, hooksCfg) {
582
+ const event = args?.event;
583
+ const validEvents = ['UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'SessionEnd'];
584
+ if (!validEvents.includes(event)) {
585
+ return {
586
+ isError: true,
587
+ content: [{
588
+ type: 'text',
589
+ text: JSON.stringify({
590
+ error: 'INVALID_EVENT',
591
+ message: `event must be one of: ${validEvents.join(', ')}`,
592
+ }),
593
+ }],
594
+ };
595
+ }
596
+
597
+ if (event === 'SessionEnd') {
598
+ return handleSessionEnd(vaultRoot, { reason: args?.reason ?? 'hooks_invoke' }, hooksCfg);
599
+ }
600
+
601
+ if (event === 'UserPromptSubmit') {
602
+ const prompt = args?.prompt;
603
+ if (typeof prompt !== 'string' || !prompt.trim()) {
604
+ return {
605
+ isError: true,
606
+ content: [{
607
+ type: 'text',
608
+ text: JSON.stringify({
609
+ error: 'MISSING_PROMPT',
610
+ message: 'prompt is required for UserPromptSubmit',
611
+ }),
612
+ }],
613
+ };
614
+ }
615
+ const hookResult = runHooks('UserPromptSubmit', '*', { prompt }, null, hooksCfg);
616
+ return formatHookInvokeResult(hookResult);
617
+ }
618
+
619
+ const toolName = args?.tool_name ?? '*';
620
+ const toolInput = args?.tool_input ?? {};
621
+
622
+ if (event === 'PreToolUse') {
623
+ const hookResult = runHooks('PreToolUse', toolName, toolInput, null, hooksCfg);
624
+ return formatHookInvokeResult(hookResult);
625
+ }
626
+
627
+ const toolResponse = args?.tool_response ?? {};
628
+ const hookResult = runHooks('PostToolUse', toolName, toolInput, toolResponse, hooksCfg);
629
+ return formatHookInvokeResult(hookResult);
630
+ }
631
+
632
+ export function formatHookInvokeResult(hookResult) {
633
+ return {
634
+ content: [{
635
+ type: 'text',
636
+ text: JSON.stringify({
637
+ blocked: hookResult.blocked ?? false,
638
+ reason: hookResult.reason ?? null,
639
+ additional_contexts: hookResult.additionalContexts ?? [],
640
+ modified_input: hookResult.modifiedInput ?? null,
641
+ }, null, 2),
642
+ }],
643
+ };
644
+ }
645
+
646
+ /**
647
+ * Handle heyspex_hooks_list — return the active hook configuration.
648
+ */
649
+ export function handleHooksList(vaultRoot) {
650
+ const cfg = listHookConfig(vaultRoot);
651
+ return {
652
+ content: [{
653
+ type: 'text',
654
+ text: JSON.stringify(cfg, null, 2),
655
+ }],
656
+ };
657
+ }