heyspex 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/README.md +21 -0
  2. package/bin/hs.mjs +931 -98
  3. package/bin/spexcode.mjs +262 -0
  4. package/dev-factory/engine/worktree.mjs +740 -0
  5. package/dev-factory/lib/code-repo-root.mjs +67 -0
  6. package/dev-factory/lib/context-budget.mjs +84 -0
  7. package/dev-factory/lib/epic-detect.mjs +190 -0
  8. package/dev-factory/lib/fleet-queue-file.mjs +846 -0
  9. package/dev-factory/lib/fleet-queue.mjs +426 -0
  10. package/dev-factory/lib/fleet-state.mjs +14 -18
  11. package/dev-factory/lib/model-catalog.mjs +338 -0
  12. package/dev-factory/lib/seat-context.mjs +199 -0
  13. package/dev-factory/lib/spec-io.mjs +93 -0
  14. package/dev-factory/lib/sufficiency-score.mjs +8 -2
  15. package/dev-factory/lib/worktree-allocator.mjs +744 -0
  16. package/dev-factory/model-catalog.json +222 -0
  17. package/dist/cli/hs-check.mjs +1085 -103
  18. package/dist/cli/hs-claim.mjs +936 -63
  19. package/dist/cli/hs-connect.mjs +9786 -1910
  20. package/dist/cli/hs-defer.mjs +5267 -3053
  21. package/dist/cli/hs-dev.mjs +564 -0
  22. package/dist/cli/hs-env.mjs +385 -299
  23. package/dist/cli/hs-index-rebuild.mjs +7129 -0
  24. package/dist/cli/hs-index-reconcile.mjs +7852 -0
  25. package/dist/cli/hs-init.mjs +777 -138
  26. package/dist/cli/hs-install.mjs +6583 -485
  27. package/dist/cli/hs-join.mjs +10137 -2368
  28. package/dist/cli/hs-vault-pull.mjs +4659 -5882
  29. package/dist/cli/hs-vault.mjs +3793 -1537
  30. package/dist/cli/hs-workspace.mjs +2550 -524
  31. package/dist/cli-templates/customer-vault/specs/entity-model.md +594 -601
  32. package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +110 -177
  33. package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -0
  34. package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +13 -15
  35. package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +91 -0
  36. package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +150 -168
  37. package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -0
  38. package/dist/cli-templates/harness-commands/claude/hs-auto.md +36 -0
  39. package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -0
  40. package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -0
  41. package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -0
  42. package/dist/cli-templates/harness-commands/claude/hs-dev.md +20 -0
  43. package/dist/cli-templates/harness-commands/claude/hs-doctor.md +32 -0
  44. package/dist/cli-templates/harness-commands/claude/hs-ground.md +28 -0
  45. package/dist/cli-templates/harness-commands/claude/hs-handoff.md +33 -0
  46. package/dist/cli-templates/harness-commands/claude/hs-log.md +27 -0
  47. package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -0
  48. package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -0
  49. package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -0
  50. package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -0
  51. package/dist/cli-templates/harness-commands/claude/hs-res.md +63 -0
  52. package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -0
  53. package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -0
  54. package/dist/cli-templates/harness-commands/claude/hs-view.md +26 -0
  55. package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -0
  56. package/dist/cli-templates/harness-commands/claude/prototype.md +91 -0
  57. package/dist/cli-templates/harness-commands/cursor/hs-auto.md +40 -0
  58. package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +31 -0
  59. package/dist/cli-templates/harness-commands/cursor/hs-capture.md +65 -0
  60. package/dist/cli-templates/harness-commands/cursor/hs-defer.md +32 -0
  61. package/dist/cli-templates/harness-commands/cursor/hs-dev.md +24 -0
  62. package/dist/cli-templates/harness-commands/cursor/hs-doctor.md +36 -0
  63. package/dist/cli-templates/harness-commands/cursor/hs-ground.md +31 -0
  64. package/dist/cli-templates/harness-commands/cursor/hs-handoff.md +37 -0
  65. package/dist/cli-templates/harness-commands/cursor/hs-log.md +31 -0
  66. package/dist/cli-templates/harness-commands/cursor/hs-next.md +32 -0
  67. package/dist/cli-templates/harness-commands/cursor/hs-promote.md +73 -0
  68. package/dist/cli-templates/harness-commands/cursor/hs-push.md +38 -0
  69. package/dist/cli-templates/harness-commands/cursor/hs-qc.md +30 -0
  70. package/dist/cli-templates/harness-commands/cursor/hs-res.md +67 -0
  71. package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +26 -0
  72. package/dist/cli-templates/harness-commands/cursor/hs-review.md +26 -0
  73. package/dist/cli-templates/harness-commands/cursor/hs-view.md +30 -0
  74. package/dist/cli-templates/harness-commands/cursor/kill-ports.md +35 -0
  75. package/dist/cli-templates/harness-commands/cursor/prototype.md +95 -0
  76. package/docs/mcp-client-config.md +259 -6
  77. package/package.json +231 -124
  78. package/plugins/heyspex/.claude-plugin/plugin.json +12 -0
  79. package/plugins/heyspex/.cursor-plugin/plugin.json +15 -0
  80. package/plugins/heyspex/.mcp.json +12 -0
  81. package/plugins/heyspex/bin/launch-mcp.mjs +95 -0
  82. package/plugins/heyspex/bin/run-hook.mjs +62 -0
  83. package/plugins/heyspex/hooks/cursor-hooks.json +227 -0
  84. package/plugins/heyspex/hooks/hooks.json +361 -0
  85. package/plugins/heyspex/mcp.json +13 -0
  86. package/scripts/check-mcp-freshness.mjs +17 -9
  87. package/scripts/claude-hooks/_env-vault-events.mjs +68 -0
  88. package/scripts/claude-hooks/_guardrail-events.mjs +96 -0
  89. package/scripts/claude-hooks/_guardrail-loader.mjs +494 -0
  90. package/scripts/claude-hooks/_intent-classifier-router.mjs +410 -0
  91. package/scripts/claude-hooks/_intent-classifier-semantic.mjs +351 -0
  92. package/scripts/claude-hooks/_run.mjs +120 -0
  93. package/scripts/claude-hooks/_transcript-text.mjs +60 -0
  94. package/scripts/claude-hooks/_vault-retrieval.mjs +322 -0
  95. package/scripts/claude-hooks/agent-spawn-delegation-gate.mjs +133 -0
  96. package/scripts/claude-hooks/capture-workflow-gate.mjs +11 -0
  97. package/scripts/claude-hooks/check-auto-memory-budget.mjs +396 -0
  98. package/scripts/claude-hooks/check-claude-md-budget.mjs +327 -0
  99. package/scripts/claude-hooks/check-mcp-freshness-hook.mjs +14 -0
  100. package/scripts/claude-hooks/claim-before-code-gate.mjs +11 -0
  101. package/scripts/claude-hooks/claim-liveness-shared.mjs +575 -0
  102. package/scripts/claude-hooks/close-terminal-on-ship.mjs +64 -0
  103. package/scripts/claude-hooks/coo-denylist-gate.mjs +11 -0
  104. package/scripts/claude-hooks/coo-guard-check.mjs +11 -0
  105. package/scripts/claude-hooks/deep-research-cost-gate.mjs +11 -0
  106. package/scripts/claude-hooks/dev-server-task-reminder.mjs +15 -0
  107. package/scripts/claude-hooks/emit-claim-status.mjs +41 -0
  108. package/scripts/claude-hooks/enforce-session-sequence.mjs +11 -0
  109. package/scripts/claude-hooks/enforce-vault-naming.mjs +11 -0
  110. package/scripts/claude-hooks/env-vault-guard.mjs +11 -0
  111. package/scripts/claude-hooks/epic-milestone-phrasing-gate.mjs +11 -0
  112. package/scripts/claude-hooks/git-identity-guard.mjs +11 -0
  113. package/scripts/claude-hooks/git-reflog-audit.mjs +15 -0
  114. package/scripts/claude-hooks/git-stash-gate.mjs +11 -0
  115. package/scripts/claude-hooks/guard-auto-memory-writes.mjs +137 -0
  116. package/scripts/claude-hooks/id-display-format-gate.mjs +11 -0
  117. package/scripts/claude-hooks/inject-communication-style.mjs +66 -0
  118. package/scripts/claude-hooks/inject-coo-seat.mjs +78 -0
  119. package/scripts/claude-hooks/inject-dev-server-awareness.mjs +11 -0
  120. package/scripts/claude-hooks/inject-doctrine.mjs +13 -0
  121. package/scripts/claude-hooks/inject-vault-context.mjs +14 -0
  122. package/scripts/claude-hooks/intent-classifier-trigger.mjs +247 -0
  123. package/scripts/claude-hooks/log-session-activity.mjs +341 -0
  124. package/scripts/claude-hooks/naughty-list-gate.mjs +11 -0
  125. package/scripts/claude-hooks/notify-status-flip.mjs +72 -0
  126. package/scripts/claude-hooks/plan-exit-ingest-gate.mjs +170 -0
  127. package/scripts/claude-hooks/plan-ingest-close-reminder.mjs +66 -0
  128. package/scripts/claude-hooks/plan-review-gate.mjs +11 -0
  129. package/scripts/claude-hooks/preflight-reminder.mjs +262 -0
  130. package/scripts/claude-hooks/protected-action-intent.mjs +221 -0
  131. package/scripts/claude-hooks/research-method-guard.mjs +398 -0
  132. package/scripts/claude-hooks/research-protocol-gate.mjs +11 -0
  133. package/scripts/claude-hooks/session-end.mjs +449 -0
  134. package/scripts/claude-hooks/settings-write-gate.mjs +11 -0
  135. package/scripts/claude-hooks/settlement-check-gate.mjs +39 -0
  136. package/scripts/claude-hooks/status-flip-detect.mjs +17 -0
  137. package/scripts/claude-hooks/terminal-close-shared.mjs +426 -0
  138. package/scripts/claude-hooks/terminal-close-watcher.mjs +122 -0
  139. package/scripts/claude-hooks/wip-checkpoint.mjs +298 -0
  140. package/scripts/cursor-hooks/manifest.mjs +235 -0
  141. package/scripts/dev-server-status.mjs +34 -13
  142. package/scripts/dev-server-stop.mjs +313 -0
  143. package/scripts/heyspex-mode.mjs +268 -0
  144. package/scripts/hs-auto-resolve.mjs +240 -0
  145. package/scripts/hs-auto-source.mjs +398 -0
  146. package/scripts/hs-auto.mjs +134 -0
  147. package/scripts/hs-capture-cli.mjs +326 -0
  148. package/scripts/hs-classifier.mjs +136 -0
  149. package/scripts/hs-companion.mjs +88 -0
  150. package/scripts/hs-gateway.mjs +115 -0
  151. package/scripts/hs-mode.mjs +125 -0
  152. package/scripts/hs-next.mjs +441 -0
  153. package/scripts/hs-settings.mjs +338 -0
  154. package/scripts/hs-view.mjs +333 -0
  155. package/scripts/hs-worktree.mjs +201 -0
  156. package/scripts/install-claude-code-hooks.mjs +270 -0
  157. package/scripts/install-cursor-hooks.mjs +471 -0
  158. package/scripts/lib/agent-env-markers.mjs +38 -0
  159. package/scripts/lib/console-url-resolver.mjs +197 -60
  160. package/scripts/lib/customer-kit.mjs +886 -0
  161. package/scripts/lib/dev-mode.mjs +244 -28
  162. package/scripts/lib/heyspex-home.mjs +72 -0
  163. package/scripts/lib/hs-command.mjs +277 -0
  164. package/scripts/lib/hs-companion-settings.mjs +472 -0
  165. package/scripts/lib/hs-source-commit.mjs +59 -5
  166. package/scripts/lib/hs-source-identity.mjs +526 -0
  167. package/scripts/lib/hs-source-log.mjs +11 -21
  168. package/scripts/lib/hs-source-push.mjs +107 -58
  169. package/scripts/lib/hs-worktree-args.mjs +115 -0
  170. package/scripts/lib/shared-path-helpers.mjs +134 -0
  171. package/scripts/lib/standalone-doctor.mjs +540 -3
  172. package/scripts/lib/subagent-git-lock.mjs +109 -0
  173. package/scripts/lib/template-pin.mjs +34 -0
  174. package/scripts/provenance-lib.mjs +91 -0
  175. package/scripts/stamp-provenance-on-edit.mjs +222 -0
  176. package/scripts/wip-ledger-health.mjs +653 -0
  177. package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -0
  178. package/spexharness/src/adapters/audit.mjs +197 -0
  179. package/spexharness/src/adapters/claude-code.mjs +441 -0
  180. package/spexharness/src/chat/tui/ansi.mjs +448 -0
  181. package/spexharness/src/chat/tui/palette.mjs +22 -0
  182. package/spexharness/src/chat/tui/select.mjs +208 -0
  183. package/spexharness/src/config-root.mjs +54 -0
  184. package/spexharness/src/context-budget.mjs +141 -0
  185. package/spexharness/src/dev-server-control.mjs +389 -0
  186. package/spexharness/src/doctrine/behaviour.mjs +371 -0
  187. package/spexharness/src/doctrine/corpus.mjs +733 -0
  188. package/spexharness/src/doctrine/inject.mjs +338 -0
  189. package/spexharness/src/doctrine/merge.mjs +184 -0
  190. package/spexharness/src/doctrine/sources.mjs +252 -0
  191. package/spexharness/src/doctrine/synthesize.mjs +401 -0
  192. package/spexharness/src/doctrine/vault-source.mjs +574 -0
  193. package/spexharness/src/doctrine/writer.mjs +176 -0
  194. package/spexharness/src/events.mjs +84 -0
  195. package/spexharness/src/gates/capture-workflow-gate.mjs +202 -0
  196. package/spexharness/src/gates/claim-before-code-gate.decide.mjs +33 -0
  197. package/spexharness/src/gates/claim-before-code-gate.mjs +129 -0
  198. package/spexharness/src/gates/deep-research-cost-gate.mjs +274 -0
  199. package/spexharness/src/gates/enforce-session-sequence.mjs +895 -0
  200. package/spexharness/src/gates/enforce-vault-naming.mjs +215 -0
  201. package/spexharness/src/gates/epic-milestone-phrasing-gate.mjs +233 -0
  202. package/spexharness/src/gates/git-identity-guard.decide.mjs +436 -0
  203. package/spexharness/src/gates/git-identity-guard.mjs +61 -0
  204. package/spexharness/src/gates/git-reflog-audit.mjs +151 -0
  205. package/spexharness/src/gates/git-stash-gate.decide.mjs +361 -0
  206. package/spexharness/src/gates/git-stash-gate.mjs +159 -0
  207. package/spexharness/src/gates/id-display-format-gate.mjs +190 -0
  208. package/spexharness/src/gates/inject-doctrine-gate.mjs +89 -0
  209. package/spexharness/src/gates/inject-vault-context.mjs +239 -0
  210. package/spexharness/src/gates/naughty-list-gate.mjs +213 -0
  211. package/spexharness/src/gates/plan-review-gate.mjs +233 -0
  212. package/spexharness/src/gates/preflight-reminder-gate.mjs +265 -0
  213. package/spexharness/src/gates/research-protocol-gate.mjs +175 -0
  214. package/spexharness/src/gates/settings-write-gate.decide.mjs +96 -0
  215. package/spexharness/src/gates/settings-write-gate.mjs +104 -0
  216. package/spexharness/src/git-reflog-audit.mjs +294 -0
  217. package/spexharness/src/intent-classifier.mjs +194 -0
  218. package/spexharness/src/intent-directive.mjs +541 -0
  219. package/spexharness/src/profile/learn.mjs +551 -0
  220. package/spexharness/src/profile/proposals.mjs +357 -0
  221. package/spexharness/src/profile/vault-inclusion.mjs +386 -0
  222. package/spexharness/src/redact.mjs +151 -0
  223. package/spexharness/src/rule-table.mjs +819 -0
  224. package/spexharness/src/seat-context.mjs +122 -0
  225. package/spexharness/src/session-bootstrap.mjs +512 -0
  226. package/spexharness/src/session-record.mjs +815 -0
  227. package/spexharness/src/token-inventory.mjs +207 -0
  228. package/vendor/heyspex-mcp/account-switch.mjs +95 -0
  229. package/vendor/heyspex-mcp/alignment-gate.mjs +315 -1
  230. package/vendor/heyspex-mcp/blocker-resolver.mjs +37 -2
  231. package/vendor/heyspex-mcp/breakdown-ladder.mjs +119 -4
  232. package/vendor/heyspex-mcp/breakdown-source.mjs +271 -0
  233. package/vendor/heyspex-mcp/breakdown-writer.mjs +23 -3
  234. package/vendor/heyspex-mcp/capture-buffer-patches.mjs +46 -5
  235. package/vendor/heyspex-mcp/capture-channel.mjs +55 -5
  236. package/vendor/heyspex-mcp/capture-console-client.mjs +80 -1
  237. package/vendor/heyspex-mcp/capture-focus.mjs +1035 -29
  238. package/vendor/heyspex-mcp/capture-outbox.mjs +5 -1
  239. package/vendor/heyspex-mcp/capture-tools.mjs +1219 -0
  240. package/vendor/heyspex-mcp/claim-tools.mjs +1124 -0
  241. package/vendor/heyspex-mcp/cli-verbs.mjs +2076 -0
  242. package/vendor/heyspex-mcp/cli.mjs +201 -15524
  243. package/vendor/heyspex-mcp/cloud-tool-surface.mjs +128 -0
  244. package/vendor/heyspex-mcp/cloud-vault-writes.mjs +412 -0
  245. package/vendor/heyspex-mcp/config-seed.yaml +15 -0
  246. package/vendor/heyspex-mcp/config-settings.mjs +290 -7
  247. package/vendor/heyspex-mcp/console-fetch.mjs +657 -16
  248. package/vendor/heyspex-mcp/console-transport.mjs +166 -0
  249. package/vendor/heyspex-mcp/credentials.mjs +584 -0
  250. package/vendor/heyspex-mcp/defer-spec.mjs +44 -4
  251. package/vendor/heyspex-mcp/deferred-reclaim.mjs +29 -3
  252. package/vendor/heyspex-mcp/design-handlers.mjs +31 -0
  253. package/vendor/heyspex-mcp/design-tools.mjs +4 -1
  254. package/vendor/heyspex-mcp/dev-factory-bridge.mjs +4 -4
  255. package/vendor/heyspex-mcp/dev-mode-core.mjs +45 -0
  256. package/vendor/heyspex-mcp/dev-tools.mjs +124 -0
  257. package/vendor/heyspex-mcp/diagnose-handlers.mjs +54 -0
  258. package/vendor/heyspex-mcp/events-tools.mjs +207 -0
  259. package/vendor/heyspex-mcp/filing-tools.mjs +688 -0
  260. package/vendor/heyspex-mcp/fleet-handlers.mjs +37 -0
  261. package/vendor/heyspex-mcp/fleet-queue-tools.mjs +69 -14
  262. package/vendor/heyspex-mcp/focus-tools.mjs +190 -0
  263. package/vendor/heyspex-mcp/folder-trust-store.mjs +59 -0
  264. package/vendor/heyspex-mcp/http-transport.mjs +300 -0
  265. package/vendor/heyspex-mcp/list-tools.mjs +257 -0
  266. package/vendor/heyspex-mcp/login-handoff.mjs +15 -1
  267. package/vendor/heyspex-mcp/mcp-config.mjs +94 -0
  268. package/vendor/heyspex-mcp/memory-tools.mjs +523 -0
  269. package/vendor/heyspex-mcp/mvp-mode.mjs +53 -6
  270. package/vendor/heyspex-mcp/occupied-keys.mjs +141 -0
  271. package/vendor/heyspex-mcp/on-deck-brief.mjs +3360 -0
  272. package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -0
  273. package/vendor/heyspex-mcp/package-root.mjs +32 -0
  274. package/vendor/heyspex-mcp/peer-presence.mjs +8 -1
  275. package/vendor/heyspex-mcp/plan-ground.mjs +621 -652
  276. package/vendor/heyspex-mcp/plan-push.mjs +906 -21
  277. package/vendor/heyspex-mcp/plan-review.mjs +124 -30
  278. package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
  279. package/vendor/heyspex-mcp/procedure-handlers.mjs +325 -0
  280. package/vendor/heyspex-mcp/procedure-tools.mjs +540 -351
  281. package/vendor/heyspex-mcp/promote-capture.mjs +386 -69
  282. package/vendor/heyspex-mcp/promote-diagnose.mjs +74 -0
  283. package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +9 -2
  284. package/vendor/heyspex-mcp/promote-research.mjs +660 -53
  285. package/vendor/heyspex-mcp/promotion-helpers.mjs +251 -0
  286. package/vendor/heyspex-mcp/prompts.mjs +1460 -0
  287. package/vendor/heyspex-mcp/read-path.mjs +29 -9
  288. package/vendor/heyspex-mcp/receipt-shape.mjs +146 -0
  289. package/vendor/heyspex-mcp/recipe-handlers.mjs +117 -0
  290. package/vendor/heyspex-mcp/regen-tasks.mjs +210 -3
  291. package/vendor/heyspex-mcp/register-spec.mjs +266 -54
  292. package/vendor/heyspex-mcp/request-context.mjs +59 -0
  293. package/vendor/heyspex-mcp/research-focus.mjs +207 -4
  294. package/vendor/heyspex-mcp/research-handlers.mjs +623 -0
  295. package/vendor/heyspex-mcp/research-tools.mjs +4 -4
  296. package/vendor/heyspex-mcp/response-budget.mjs +348 -0
  297. package/vendor/heyspex-mcp/review-focus.mjs +25 -1
  298. package/vendor/heyspex-mcp/search-specs.mjs +280 -0
  299. package/vendor/heyspex-mcp/seat-block-ledger.mjs +113 -0
  300. package/vendor/heyspex-mcp/seat-identity.mjs +2 -6
  301. package/vendor/heyspex-mcp/session-owner.mjs +108 -0
  302. package/vendor/heyspex-mcp/session-tools.mjs +152 -0
  303. package/vendor/heyspex-mcp/settings-registry.mjs +232 -0
  304. package/vendor/heyspex-mcp/settings-tools.mjs +85 -12
  305. package/vendor/heyspex-mcp/spec-edit-tools.mjs +446 -0
  306. package/vendor/heyspex-mcp/spec-id-lib.mjs +93 -17
  307. package/vendor/heyspex-mcp/split-research.mjs +301 -56
  308. package/vendor/heyspex-mcp/stdio-rpc.mjs +22 -0
  309. package/vendor/heyspex-mcp/submit-for-review.mjs +132 -6
  310. package/vendor/heyspex-mcp/summary-contract.mjs +105 -0
  311. package/vendor/heyspex-mcp/summary-write-tools.mjs +224 -0
  312. package/vendor/heyspex-mcp/symptom-query.mjs +146 -0
  313. package/vendor/heyspex-mcp/test-helpers.mjs +108 -1
  314. package/vendor/heyspex-mcp/tool-catalog.mjs +2813 -0
  315. package/vendor/heyspex-mcp/tool-dispatch.mjs +639 -0
  316. package/vendor/heyspex-mcp/tool-helpers.mjs +325 -0
  317. package/vendor/heyspex-mcp/validate-linkage.mjs +414 -27
  318. package/vendor/heyspex-mcp/vault-io.mjs +724 -0
  319. package/vendor/heyspex-mcp/vault-less-endpoints.mjs +52 -0
  320. package/vendor/heyspex-mcp/vault-resolve.mjs +366 -68
  321. package/vendor/heyspex-mcp/verify-reproduction.mjs +270 -30
  322. package/vendor/heyspex-mcp/whoami.mjs +91 -0
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+ // Claude Code PostToolUse hook → ExitPlanMode reminder + session marker.
3
+ //
4
+ // [[HS-38J5-2BXD-DIAG-plan-execute-boundary-has-no-ingest-clai]] — the
5
+ // ExitPlanMode PreToolUse gate (plan-review-gate.mjs) hard-gates plan QUALITY
6
+ // (V1 review + QC), but nothing gated plan → execute: an approved plan let the
7
+ // agent go straight to coding with no epic filed and no claim acquired. This is
8
+ // item 1 of the diagnose's REMAINING fix list — the "soft (unavoidable)" half:
9
+ // rides the ONE event guaranteed to fire once a plan is approved (PostToolUse
10
+ // on ExitPlanMode), and fail-silent (never blocks — plan-review-gate already
11
+ // owns the hard quality gate on this same tool).
12
+ //
13
+ // It does two things on every ExitPlanMode PostToolUse:
14
+ // 1. Emits an unavoidable `additionalContext` reminder: ingest the approved
15
+ // plan as an epic (plan-ingest-agent-workflow) and claim before coding.
16
+ // 2. Arms a per-session marker (.heyspex/post-plan-pending/<sessionId>.json)
17
+ // that claim-before-code-gate.mjs (the PreToolUse Edit|Write HARD gate,
18
+ // item 2 of the same diagnose) reads to know "this session just exited
19
+ // plan mode" before deciding whether to deny the first code edit.
20
+ //
21
+ // The marker is cleared by claim-before-code-gate.mjs once a claim is observed
22
+ // (or left to age out — it is advisory only, never itself a security boundary).
23
+ //
24
+ // No CLAUDE_AGENT gate ([[HS-DT9L-NLR7-DIAG-nine-hooks-are-switched-off-in]]).
25
+ // This hook never denies anything — it only emits a reminder and arms a marker file
26
+ // — so there is no approval-fatigue risk in firing it everywhere. Removing the gate
27
+ // also unblocks claim-before-code-gate.mjs, which reads the marker this hook arms:
28
+ // with this hook silent in interactive sessions, the marker never existed there, so
29
+ // that gate was doubly inert regardless of its own env gate.
30
+ //
31
+ // Env gate:
32
+ // HEYSPEX_PLAN_EXIT_GATE_OFF=1 Hard kill switch.
33
+ //
34
+ // MUST exit 0 on every failure path — a reminder that breaks Claude Code is
35
+ // worse than no reminder.
36
+
37
+ import fs from 'node:fs';
38
+ import path from 'node:path';
39
+ import { fileURLToPath } from 'node:url';
40
+
41
+ import { resolveSessionId } from './claim-liveness-shared.mjs';
42
+
43
+ export function repoRoot() {
44
+ const fromEnv = process.env.CLAUDE_PROJECT_DIR?.trim();
45
+ if (fromEnv) return fromEnv.replace(/[\\/]+$/, '');
46
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
47
+ }
48
+
49
+ const MARKER_DIR = 'post-plan-pending';
50
+ // Advisory TTL — long enough to cover a slow ingest+claim, short enough that a
51
+ // marker from an old plan/session can't linger forever. Not a security boundary
52
+ // (claim-before-code-gate's actual deny decision comes from the claim-liveness
53
+ // negative cache, which has its own TTL) — this only decides whether the "did
54
+ // this session just exit plan mode" nudge/gate applies at all.
55
+ export const MARKER_TTL_MS = 2 * 60 * 60 * 1000; // 2h
56
+
57
+ function markerFile(sessionId, root) {
58
+ return path.join(root, '.heyspex', MARKER_DIR, `${sessionId}.json`);
59
+ }
60
+
61
+ /** Arm the per-session "just exited plan mode" marker. Best-effort; never throws. */
62
+ export function armPlanExitMarker(sessionId, root = repoRoot()) {
63
+ const trimmed = typeof sessionId === 'string' ? sessionId.trim() : '';
64
+ if (!trimmed) return;
65
+ try {
66
+ const file = markerFile(trimmed, root);
67
+ fs.mkdirSync(path.dirname(file), { recursive: true });
68
+ fs.writeFileSync(file, JSON.stringify({ armedAt: Date.now(), sessionId: trimmed }));
69
+ } catch {
70
+ // never block the hook
71
+ }
72
+ }
73
+
74
+ /** True when `sessionId` has a live (unexpired) plan-exit marker. Never throws. */
75
+ export function hasFreshPlanExitMarker(sessionId, root = repoRoot()) {
76
+ const trimmed = typeof sessionId === 'string' ? sessionId.trim() : '';
77
+ if (!trimmed) return false;
78
+ try {
79
+ const raw = JSON.parse(fs.readFileSync(markerFile(trimmed, root), 'utf8'));
80
+ const armedAt = typeof raw?.armedAt === 'number' ? raw.armedAt : 0;
81
+ return armedAt > 0 && Date.now() - armedAt < MARKER_TTL_MS;
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+
87
+ /** Clear the marker (called once a claim is acquired — no more nudging needed). */
88
+ export function clearPlanExitMarker(sessionId, root = repoRoot()) {
89
+ const trimmed = typeof sessionId === 'string' ? sessionId.trim() : '';
90
+ if (!trimmed) return;
91
+ try {
92
+ fs.unlinkSync(markerFile(trimmed, root));
93
+ } catch {
94
+ // absent is fine
95
+ }
96
+ }
97
+
98
+ export const PLAN_EXIT_REMINDER =
99
+ 'HEYSPEX — plan approved (ExitPlanMode). Before writing any code: (1) ingest the ' +
100
+ 'approved plan into the vault — heyspex_preflight({ intent: "file_epic" }) then ' +
101
+ 'file the epic (heyspex_file_spec / heyspex_push_plan) per plan-ingest-agent-workflow ' +
102
+ '(heyspex_get_procedure({ name: "plan-ingest-agent-workflow" })) — unless the plan is a ' +
103
+ 'trivial fix with no new spec warranted; (2) claim the spec you are about to work ' +
104
+ '(heyspex_claim_spec) before the first non-plan Edit/Write. The plan→execute boundary ' +
105
+ 'has no other enforcement besides this reminder and the PreToolUse claim gate on ' +
106
+ 'code edits — do not skip filing+claiming because the plan already passed review.';
107
+
108
+ async function readStdinJson() {
109
+ if (process.stdin.isTTY) return null;
110
+ return new Promise((resolve) => {
111
+ let buf = '';
112
+ let settled = false;
113
+ const finish = (value) => {
114
+ if (settled) return;
115
+ settled = true;
116
+ resolve(value);
117
+ };
118
+ const timer = setTimeout(() => finish(buf || ''), 200);
119
+ process.stdin.setEncoding('utf8');
120
+ process.stdin.on('data', (chunk) => {
121
+ buf += chunk;
122
+ });
123
+ process.stdin.on('end', () => {
124
+ clearTimeout(timer);
125
+ finish(buf);
126
+ });
127
+ process.stdin.on('error', () => {
128
+ clearTimeout(timer);
129
+ finish(buf);
130
+ });
131
+ });
132
+ }
133
+
134
+ function emit(additionalContext) {
135
+ process.stdout.write(
136
+ JSON.stringify({
137
+ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext },
138
+ }),
139
+ );
140
+ }
141
+
142
+ async function main() {
143
+ if (process.env.HEYSPEX_PLAN_EXIT_GATE_OFF === '1') return;
144
+
145
+ const raw = await readStdinJson();
146
+ if (!raw) return;
147
+ let payload;
148
+ try {
149
+ payload = JSON.parse(raw);
150
+ } catch {
151
+ return;
152
+ }
153
+ if (payload?.hook_event_name !== 'PostToolUse') return;
154
+ if (payload?.tool_name !== 'ExitPlanMode') return;
155
+ // A denied/errored ExitPlanMode (e.g. plan-review-gate blocked it) never actually
156
+ // left plan mode — nothing to arm or remind about.
157
+ if (payload?.tool_response?.is_error === true) return;
158
+
159
+ const sessionId = resolveSessionId(payload);
160
+ armPlanExitMarker(sessionId);
161
+ emit(PLAN_EXIT_REMINDER);
162
+ }
163
+
164
+ const INVOKED_DIRECTLY =
165
+ !!process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
166
+ if (INVOKED_DIRECTLY) {
167
+ main().catch(() => {
168
+ // Never break Claude Code on a hook failure.
169
+ });
170
+ }
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ // UserPromptSubmit hook → after plan mode, file specs and close — do not implement.
3
+ //
4
+ // Command Code / harnesses default to "exit plan → implement". HeySpex plan-ingest
5
+ // sessions file milestone+epic specs via MCP, validate linkage, then end session.
6
+ // Fires on distinctive plan-exit phrasing in the user prompt or agent narration.
7
+ //
8
+ // Invoke via native UserPromptSubmit (Claude Code) or heyspex_hooks_invoke
9
+ // ({ event: "UserPromptSubmit", prompt: "..." }) in Command Code.
10
+ //
11
+ // FAIL-SAFE: exit 0 on every path.
12
+
13
+ const PLAN_EXIT_PATTERNS = [
14
+ /\bexited?\s+plan\s+mode\b/i,
15
+ /\bPLAN\s*\(\s*exit\s*\)/i,
16
+ /\bexit\s+plan\s+mode\b/i,
17
+ /\bnow\s+implementing\b/i,
18
+ /\bleaving\s+plan\s+mode\b/i,
19
+ /\bplan\s+mode\s+ended\b/i,
20
+ ];
21
+
22
+ const INJECT =
23
+ 'HeySpex plan-ingest close: you just left plan mode. Do NOT start coding. ' +
24
+ 'File milestones and epics via heyspex_push_plan / heyspex_file_spec, run ' +
25
+ 'heyspex_validate_linkage, confirm summaries, then close with ' +
26
+ 'heyspex_preflight({ intent: "close_session", thread_type: "plan_ingest" }) ' +
27
+ 'and heyspex_session_end({ reason: "plan_ingest_complete" }). ' +
28
+ 'Task filing and code execution are a separate session via /hs-next.';
29
+
30
+ async function readStdinJson() {
31
+ if (process.stdin.isTTY) return null;
32
+ let buf = '';
33
+ process.stdin.setEncoding('utf8');
34
+ for await (const chunk of process.stdin) buf += chunk;
35
+ if (!buf.trim()) return null;
36
+ try {
37
+ return JSON.parse(buf);
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ function emit(additionalContext) {
44
+ process.stdout.write(JSON.stringify({
45
+ hookSpecificOutput: {
46
+ hookEventName: 'UserPromptSubmit',
47
+ additionalContext,
48
+ },
49
+ }));
50
+ }
51
+
52
+ async function main() {
53
+ if (process.env.HEYSPEX_PLAN_INGEST_REMINDER_OFF === '1') return;
54
+
55
+ const payload = await readStdinJson();
56
+ if (!payload || payload.hook_event_name !== 'UserPromptSubmit') return;
57
+
58
+ const prompt = payload.prompt ?? payload.tool_input?.prompt ?? '';
59
+ if (typeof prompt !== 'string' || !prompt.trim()) return;
60
+
61
+ if (!PLAN_EXIT_PATTERNS.some((re) => re.test(prompt))) return;
62
+
63
+ emit(INJECT);
64
+ }
65
+
66
+ main().catch(() => {});
@@ -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/plan-review-gate.mjs. To change the SHAPE of this shell,
6
+ // edit the generator, then rerun: node scripts/generate-hook-shells.mjs
7
+
8
+ import { runGateAsHook } from '../../spexharness/src/adapters/claude-code.mjs';
9
+ import gate from '../../spexharness/src/gates/plan-review-gate.mjs';
10
+
11
+ await runGateAsHook(gate);
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env node
2
+ // Claude Code hook → inject preflight reminders for high-stakes intents.
3
+ //
4
+ // Phase 3 of HS-EPIC-3QS8 (procedure discovery & enforcement). Fires on
5
+ // UserPromptSubmit and PreToolUse to nudge the agent toward heyspex_preflight /
6
+ // heyspex_get_procedure / destructive-action-checklist at the moment of need —
7
+ // even when the agent forgets to ask.
8
+ //
9
+ // Three intent categories:
10
+ // 1. UserPromptSubmit → user says "file a milestone" / "create an epic" /
11
+ // "file a new task" → the registry-driven
12
+ // spec-creation-via-minter prime ([[HS-EPIC-ZGWR]] AC#6):
13
+ // patterns + inject live in the guardrail registry, not here.
14
+ // 2. PreToolUse Edit → file_path under specs/protocols/ → protocol_edit
15
+ // reminder (spec-hierarchy §Agent autonomy).
16
+ // 3. PreToolUse Bash/PowerShell → command contains rm -rf, git push --force,
17
+ // git reset --hard, Remove-Item -Recurse -Force →
18
+ // destructive-action-checklist reminder.
19
+ //
20
+ // Env gate (per HS-EPIC-3QS8 resolved Open question on "Hook scope"):
21
+ // CLAUDE_AGENT=true Set to opt in. When unset, the hook exits 0 silently so
22
+ // humans typing the same triggers in an interactive
23
+ // Claude Code session don't get spammed.
24
+ // HEYSPEX_PREFLIGHT_REMINDER_OFF=1 Hard kill switch — exits silently even
25
+ // when CLAUDE_AGENT=true. Useful for parallel agents that
26
+ // want to mute reminders without touching the gate.
27
+ //
28
+ // MUST exit 0 on every failure path (matches the [[claude-code-hooks-integration]]
29
+ // fail-silent contract). A reminder that breaks Claude Code is worse than no
30
+ // reminder.
31
+ //
32
+ // Output shape:
33
+ // { hookSpecificOutput: { hookEventName, additionalContext } }
34
+ // Plain JSON to stdout; Claude Code injects additionalContext into the model's
35
+ // next turn alongside the prompt or tool result.
36
+
37
+ import path from 'node:path';
38
+ import { fileURLToPath } from 'node:url';
39
+
40
+ // HS-N01T: the UserPromptSubmit half (the registry-driven filing-intent prime and the
41
+ // grounding nudge) is PORTED to the engine — spexharness/src/gates/preflight-reminder-gate.mjs
42
+ // — and this hook is now the thin Claude Code shell over it: same decision function, same
43
+ // reminder text, same per-rule config/injected-content gating. What stays here is the
44
+ // plumbing (stdin, the CLAUDE_AGENT opt-in gate, the single emit) and the PreToolUse half.
45
+ import {
46
+ detectRegistryPrimeReminder as detectPrimeReminderPorted,
47
+ detectDesignIntent,
48
+ PRIME_RULES_OWNED_ELSEWHERE,
49
+ } from '../../spexharness/src/gates/preflight-reminder-gate.mjs';
50
+ import { defaultConfigRoot } from '../../spexharness/src/config-root.mjs';
51
+
52
+ const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
53
+
54
+ // Filing-intent priming is REGISTRY-DRIVEN ([[HS-EPIC-ZGWR]] AC#6): the prime rules on
55
+ // the userpromptsubmit surface (e.g. spec-creation-via-minter) carry their patterns +
56
+ // inject text in specs/protocols/*.md → .heyspex/guardrails.json, so editing the nudge
57
+ // is a prose edit with zero JS change. research-before-decision is a userpromptsubmit
58
+ // prime rule too, but it has its own dedicated dispatch (via the unified
59
+ // intent-classifier-trigger.mjs router; the old standalone research-trigger.mjs shim
60
+ // was retired after burn-in, HS-IYU1) that does
61
+ // extra workflow orchestration — exclude it here so it never double-injects. That
62
+ // exclusion set (`PRIME_RULES_OWNED_ELSEWHERE`) now lives in the engine gate and is passed
63
+ // back in explicitly below, so this hook's sweep is unchanged.
64
+
65
+ // Destructive shell patterns. Conservative — only patterns that map to real
66
+ // data loss / shared-state mutation, not arbitrary "dangerous-looking" calls.
67
+ const DESTRUCTIVE_PATTERNS = [
68
+ // POSIX rm with recursive force in any flag order
69
+ /\brm\s+(?:-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*r[a-zA-Z]*|--recursive\s+--force|--force\s+--recursive)\b/,
70
+ // git force-push (--force, -f, --force-with-lease)
71
+ /\bgit\s+push\s+(?:.*\s+)?(?:--force\b|-f\b|--force-with-lease\b)/,
72
+ // git reset --hard
73
+ /\bgit\s+reset\s+--hard\b/,
74
+ // git clean -fd / -fdx (untracked + ignored)
75
+ /\bgit\s+clean\s+(?:-[a-zA-Z]*f[a-zA-Z]*d[a-zA-Z]*|-[a-zA-Z]*d[a-zA-Z]*f[a-zA-Z]*)/,
76
+ // git branch -D / branch --delete --force
77
+ /\bgit\s+branch\s+(?:.*\s+)?(?:-D\b|--delete\s+--force\b|-d\s+--force\b)/,
78
+ // PowerShell Remove-Item -Recurse -Force
79
+ /\bRemove-Item\s+(?:[^|;&\n]*\s)?-Recurse\b(?:[^|;&\n]*\s)?-Force\b/i,
80
+ /\bRemove-Item\s+(?:[^|;&\n]*\s)?-Force\b(?:[^|;&\n]*\s)?-Recurse\b/i,
81
+ ];
82
+
83
+ const PROTOCOL_EDIT_REGEX = /(?:^|[\\/])specs[\\/]protocols[\\/][^\\/]+\.md$/i;
84
+
85
+ // CLAUDE.md edit gate ([[HS-UFQS-5PQ2-DIAG]]): CLAUDE.md is the always-loaded
86
+ // agent contract — a decision recorded correctly in a protocol/memory doc that
87
+ // also gets INLINED into CLAUDE.md silently wins once the protocol drifts,
88
+ // because CLAUDE.md is what every session actually reads. Fires on the FILE
89
+ // being edited (any CLAUDE.md, root or a nested customer-vault scaffold), not
90
+ // on a folder pattern — mirrors detectProtocolEdit above.
91
+ const CLAUDE_MD_EDIT_REGEX = /(?:^|[\\/])CLAUDE\.md$/;
92
+
93
+ const DESTRUCTIVE_REMINDER =
94
+ 'Detected destructive shell command (rm -rf, force-push, reset --hard, or ' +
95
+ 'equivalent). Walk [[destructive-action-checklist]] before proceeding — ' +
96
+ '`heyspex_get_procedure({ name: "destructive-action-checklist" })` returns the ' +
97
+ 'full checklist. If this is a non-vault scratch path or sandbox, you can ' +
98
+ 'proceed; otherwise confirm with the user.';
99
+
100
+ const PROTOCOL_EDIT_REMINDER =
101
+ 'Detected an Edit on a file under `specs/protocols/`. Protocol edits require ' +
102
+ 'user approval per spec-hierarchy §Agent autonomy. Fetch the full rule with ' +
103
+ '`heyspex_get_procedure({ name: "spec-hierarchy" })` and confirm scope with ' +
104
+ 'the user before saving.';
105
+
106
+ const CLAUDE_MD_EDIT_REMINDER =
107
+ 'Detected an Edit/Write on CLAUDE.md — the always-loaded agent contract. Before ' +
108
+ 'saving, check `heyspex_preflight({ intent: "protocol_edit" })` and ' +
109
+ '`heyspex_get_procedure({ name: "rule-placement-protocol" })`: procedure content ' +
110
+ 'ranks ABOVE CLAUDE.md (CLAUDE.md is rank 4 — last resort, load-bearing handshake ' +
111
+ 'rules only). Detail belongs in a `specs/protocols/*.md` procedure, fetched on ' +
112
+ 'demand — never inlined here. An inlined copy silently overrides the live ' +
113
+ 'procedure once it drifts, because CLAUDE.md is what every session actually reads ' +
114
+ '([[HS-UFQS-5PQ2-DIAG]]). If you are adding detail rather than a one-line pointer, ' +
115
+ 'stop and move it to a procedure instead.';
116
+
117
+ // Grounding-before-proposing nudge ([[HS-RES-2QX6]] / [[HS-EPIC-PLRV]]): the patterns and
118
+ // GROUNDING_REMINDER text live in the engine gate (DESIGN_INTENT_PATTERNS /
119
+ // GROUNDING_REMINDER there); `detectDesignIntent` is imported above.
120
+
121
+ async function readStdinJson() {
122
+ if (process.stdin.isTTY) return null;
123
+ return new Promise((resolve) => {
124
+ let buf = '';
125
+ let settled = false;
126
+ const finish = (value) => {
127
+ if (settled) return;
128
+ settled = true;
129
+ resolve(value);
130
+ };
131
+ const timer = setTimeout(() => finish(buf || ''), 200);
132
+ process.stdin.setEncoding('utf8');
133
+ process.stdin.on('data', (chunk) => {
134
+ buf += chunk;
135
+ });
136
+ process.stdin.on('end', () => {
137
+ clearTimeout(timer);
138
+ finish(buf);
139
+ });
140
+ process.stdin.on('error', () => {
141
+ clearTimeout(timer);
142
+ finish(buf);
143
+ });
144
+ });
145
+ }
146
+
147
+ function emit(eventName, additionalContext) {
148
+ if (!additionalContext) return;
149
+ const out = {
150
+ hookSpecificOutput: {
151
+ hookEventName: eventName,
152
+ additionalContext,
153
+ },
154
+ };
155
+ process.stdout.write(JSON.stringify(out));
156
+ }
157
+
158
+ // Registry-driven filing-intent prime ([[HS-EPIC-ZGWR]] AC#6). Reads the
159
+ // userpromptsubmit `prime` rules from the compiled guardrail registry, matches the
160
+ // prompt against each rule's authored patterns, and returns the rule's inject text.
161
+ // Honors each rule's config_key gate + ignore_injected_content, and skips rules owned
162
+ // by a dedicated hook. Fail-safe: any error → null (no reminder, never throws).
163
+ // Exported so the smoke can unit-test the config/injected-content behavior in-process.
164
+ // HS-N01T: the body is the engine gate's; this hook passes its OWN exclusion set (the
165
+ // engine's default is wider — see the gate's header) so the hook's sweep is byte-identical.
166
+ // HS-4C76: the registry (guardrails.json) stays on the code root, but the per-rule
167
+ // config_key toggles live in `.heyspex/config.yaml` at the VAULT root. A caller passing
168
+ // an explicit fixture root (the smoke) gets both from that root; the live path resolves
169
+ // the config root through the vault resolver.
170
+ export function detectRegistryPrimeReminder(
171
+ prompt,
172
+ repoRoot = REPO_ROOT,
173
+ configRoot = repoRoot === REPO_ROOT ? defaultConfigRoot() : repoRoot,
174
+ ) {
175
+ return detectPrimeReminderPorted(prompt, repoRoot, {
176
+ ownedElsewhere: PRIME_RULES_OWNED_ELSEWHERE,
177
+ configRoot,
178
+ });
179
+ }
180
+
181
+ function detectProtocolEdit(toolName, toolInput) {
182
+ if (toolName !== 'Edit' && toolName !== 'MultiEdit' && toolName !== 'Write') return false;
183
+ const path = toolInput?.file_path ?? toolInput?.path;
184
+ if (typeof path !== 'string') return false;
185
+ return PROTOCOL_EDIT_REGEX.test(path);
186
+ }
187
+
188
+ function detectClaudeMdEdit(toolName, toolInput) {
189
+ if (toolName !== 'Edit' && toolName !== 'MultiEdit' && toolName !== 'Write') return false;
190
+ const path = toolInput?.file_path ?? toolInput?.path;
191
+ if (typeof path !== 'string') return false;
192
+ return CLAUDE_MD_EDIT_REGEX.test(path);
193
+ }
194
+
195
+ function detectDestructiveCommand(toolName, toolInput) {
196
+ if (toolName !== 'Bash' && toolName !== 'PowerShell') return false;
197
+ const command = toolInput?.command;
198
+ if (typeof command !== 'string' || !command.trim()) return false;
199
+ return DESTRUCTIVE_PATTERNS.some((re) => re.test(command));
200
+ }
201
+
202
+ async function main() {
203
+ // Env gate: must be explicitly opted in. Hard kill switch wins.
204
+ if (process.env.HEYSPEX_PREFLIGHT_REMINDER_OFF === '1') return;
205
+ if (process.env.CLAUDE_AGENT !== 'true') return;
206
+
207
+ const raw = await readStdinJson();
208
+ if (!raw) return;
209
+ let payload;
210
+ try {
211
+ payload = JSON.parse(raw);
212
+ } catch {
213
+ return;
214
+ }
215
+
216
+ const event = payload?.hook_event_name;
217
+ if (!event) return;
218
+
219
+ if (event === 'UserPromptSubmit') {
220
+ // Combine reminders into ONE emit — emitting twice would write two JSON
221
+ // objects to stdout and break the hook contract. The filing-intent reminder
222
+ // and the grounding nudge are independent and can co-fire.
223
+ const parts = [];
224
+ const intentReminder = detectRegistryPrimeReminder(payload?.prompt, REPO_ROOT);
225
+ if (intentReminder) parts.push(intentReminder);
226
+ if (process.env.HEYSPEX_GROUNDING_NUDGE_OFF !== '1') {
227
+ const grounding = detectDesignIntent(payload?.prompt);
228
+ if (grounding) parts.push(grounding);
229
+ }
230
+ if (parts.length) emit(event, parts.join('\n\n'));
231
+ return;
232
+ }
233
+
234
+ if (event === 'PreToolUse') {
235
+ const toolName = payload?.tool_name;
236
+ const toolInput = payload?.tool_input ?? {};
237
+
238
+ if (detectProtocolEdit(toolName, toolInput)) {
239
+ emit(event, PROTOCOL_EDIT_REMINDER);
240
+ return;
241
+ }
242
+ if (detectClaudeMdEdit(toolName, toolInput)) {
243
+ emit(event, CLAUDE_MD_EDIT_REMINDER);
244
+ return;
245
+ }
246
+ if (detectDestructiveCommand(toolName, toolInput)) {
247
+ emit(event, DESTRUCTIVE_REMINDER);
248
+ return;
249
+ }
250
+ return;
251
+ }
252
+ }
253
+
254
+ // Entry-guard: run main() only when executed as the hook, NOT when imported (the smoke
255
+ // imports detectRegistryPrimeReminder to unit-test the config/injected-content paths).
256
+ const INVOKED_DIRECTLY =
257
+ !!process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
258
+ if (INVOKED_DIRECTLY) {
259
+ main().catch(() => {
260
+ // Never break Claude Code on a hook failure. Match emit-claim-status.mjs.
261
+ });
262
+ }