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,327 @@
1
+ #!/usr/bin/env node
2
+ // Claude Code SessionStart hook → CLAUDE.md (agent contract) budget check.
3
+ //
4
+ // CLAUDE.md is the always-loaded agent contract: every byte is injected into
5
+ // context at the start of every session. Unlike the auto-memory index there is no
6
+ // hard harness cap, but compliance degrades as it grows — Anthropic's own guidance
7
+ // is "bloated CLAUDE.md files cause Claude to ignore your actual instructions," and
8
+ // practitioner testing reports compliance dropping sharply past ~200 lines (rules
9
+ // get buried). So this is a SOFT quality budget, not a data-loss cap.
10
+ //
11
+ // The warn thresholds are user-configurable from the HeySpex Console at
12
+ // Settings → Config → Memory (persisted in spex-console/settings.json under
13
+ // `agentMemory`): an on/off toggle plus the line + byte target. Defaults below
14
+ // (120 lines / 15KB) are the lean target this file was trimmed to (see
15
+ // [[minimizing-claude-md-thin-agent-contract]],
16
+ // [[HS-TASK-AIU4-claude-md-trim-procedures-to-mcp-pointers]]); past ~200 lines
17
+ // compliance drops sharply (rules get buried). It stays SILENT while CLAUDE.md is
18
+ // lean and, once it crosses the target, names the largest sections to trim and
19
+ // reminds the agent of the keep/cut test: "Would removing this line cause Claude to
20
+ // make mistakes? If not, cut it." Detail belongs in a procedure (fetched on demand
21
+ // via heyspex_get_procedure), not here.
22
+ //
23
+ // Mirrors check-auto-memory-budget.mjs (same env-gate + exit-0 discipline).
24
+ //
25
+ // Env gate:
26
+ // CLAUDE_AGENT=true opt in (unset → silent, so interactive
27
+ // humans aren't spammed).
28
+ // HEYSPEX_CLAUDE_MD_BUDGET_OFF=1 hard kill switch — silent even when opted in.
29
+ // HEYSPEX_CLAUDE_MD_FILE=<path> override the resolved CLAUDE.md path
30
+ // (used by the smoke test; also a manual escape hatch).
31
+ // HEYSPEX_CLAUDE_MD_SETTINGS_FILE=<path> override the spex-console/settings.json
32
+ // path read for the budget knobs (smoke test).
33
+ //
34
+ // MUST exit 0 on every path — a hook that breaks Claude Code is worse than one
35
+ // that silently does nothing.
36
+ //
37
+ // Output shape: { hookSpecificOutput: { hookEventName, additionalContext } }.
38
+
39
+ import fs from 'node:fs';
40
+ import path from 'node:path';
41
+ import { fileURLToPath } from 'node:url';
42
+ import { getActiveConfig } from '../../vendor/heyspex-mcp/vault-resolve.mjs';
43
+
44
+ const COMPLIANCE_CLIFF_LINES = 200; // documented point where rules get buried
45
+ const DEFAULT_MAX_LINES = 120; // lean target; overridable in Console settings
46
+ const DEFAULT_MAX_BYTES = 15000; // ~14.6 KiB; overridable in Console settings
47
+ const MAX_SECTIONS = 6;
48
+
49
+ // Duplication detection ([[HS-UFQS-5PQ2-DIAG]]): "Size is not the violation —
50
+ // duplication is. A budget check that only counts lines will keep passing
51
+ // while the file rots." A line/byte-lean CLAUDE.md can still carry a stale
52
+ // inlined copy of a procedure — this check fires independent of the size
53
+ // budget above. Conservative on purpose (exact-line match, length floor,
54
+ // headers/tables excluded) to avoid flagging shared boilerplate as a false
55
+ // positive; it is meant to catch verbatim paragraph-level copies, not any
56
+ // shared phrase.
57
+ const DUPLICATE_MIN_LINE_LENGTH = 50;
58
+ const MAX_DUPLICATE_HITS_SHOWN = 5;
59
+
60
+ function normalizeLineForDupeCheck(line) {
61
+ return line.trim().replace(/\s+/g, ' ');
62
+ }
63
+
64
+ function isCheckableLine(line) {
65
+ if (line.length < DUPLICATE_MIN_LINE_LENGTH) return false;
66
+ if (line.startsWith('#')) return false; // headings repeat by design, not real dup
67
+ if (line.startsWith('|')) return false; // table rows (change-log rows etc.)
68
+ if (line.startsWith('```')) return false;
69
+ return true;
70
+ }
71
+
72
+ export function resolveProtocolsDir(projectDir) {
73
+ const override = process.env.HEYSPEX_CLAUDE_MD_PROTOCOLS_DIR;
74
+ if (override && override.trim()) return override;
75
+ // Since the repo split (2026-08-31) protocols live in the vault clone, not the
76
+ // project dir — prefer the registry-resolved vault when the local dir is gone.
77
+ const local = path.join(projectDir, 'specs', 'protocols');
78
+ if (fs.existsSync(local)) return local;
79
+ try {
80
+ const vault = getActiveConfig()?.repoRootAbs;
81
+ if (vault) return path.join(vault, 'specs', 'protocols');
82
+ } catch {
83
+ // registry unreadable — fall through to the local default
84
+ }
85
+ return local;
86
+ }
87
+
88
+ /** normalized line → Set of protocol basenames it appears in verbatim. PURE given fs snapshot. */
89
+ export function collectProtocolLines(protocolsDir) {
90
+ const map = new Map();
91
+ let entries = [];
92
+ try {
93
+ entries = fs.readdirSync(protocolsDir).filter((f) => f.toLowerCase().endsWith('.md'));
94
+ } catch {
95
+ return map; // no protocols dir (fixture / misconfigured repo) → nothing to compare against
96
+ }
97
+ for (const file of entries) {
98
+ let text;
99
+ try {
100
+ text = fs.readFileSync(path.join(protocolsDir, file), 'utf8');
101
+ } catch {
102
+ continue;
103
+ }
104
+ for (const raw of text.split(/\r?\n/)) {
105
+ const line = normalizeLineForDupeCheck(raw);
106
+ if (!isCheckableLine(line)) continue;
107
+ if (!map.has(line)) map.set(line, new Set());
108
+ map.get(line).add(file);
109
+ }
110
+ }
111
+ return map;
112
+ }
113
+
114
+ /**
115
+ * Lines in `content` (CLAUDE.md) that appear verbatim in a `specs/protocols/*.md`
116
+ * file. Returns [] when nothing matches (including when the protocols dir is
117
+ * absent/empty — never throws). Pure given the protocolLines snapshot.
118
+ */
119
+ export function detectDuplicateLines(content, protocolLines) {
120
+ if (protocolLines.size === 0) return [];
121
+ const hits = [];
122
+ const seen = new Set(); // de-dupe repeated identical lines within CLAUDE.md itself
123
+ for (const raw of content.split(/\r?\n/)) {
124
+ const line = normalizeLineForDupeCheck(raw);
125
+ if (!isCheckableLine(line) || seen.has(line)) continue;
126
+ const files = protocolLines.get(line);
127
+ if (files && files.size > 0) {
128
+ hits.push({ line, files: [...files] });
129
+ seen.add(line);
130
+ }
131
+ }
132
+ return hits;
133
+ }
134
+
135
+ function buildDuplicationReport(hits) {
136
+ if (!hits || hits.length === 0) return null;
137
+ const out = [
138
+ `CLAUDE.md duplication check — ${hits.length} line(s) match specs/protocols/*.md verbatim. ` +
139
+ `CLAUDE.md is always-loaded; an inlined copy silently overrides the live procedure once the ` +
140
+ `procedure is updated and the copy isn't. Replace with a one-line pointer (e.g. ` +
141
+ `"heyspex_get_procedure({ name: '<protocol>' })") instead of the inlined text.`,
142
+ ];
143
+ for (const h of hits.slice(0, MAX_DUPLICATE_HITS_SHOWN)) {
144
+ const preview = h.line.length > 90 ? `${h.line.slice(0, 90)}…` : h.line;
145
+ out.push(` • "${preview}" — also verbatim in ${h.files.join(', ')}`);
146
+ }
147
+ if (hits.length > MAX_DUPLICATE_HITS_SHOWN) {
148
+ out.push(` …and ${hits.length - MAX_DUPLICATE_HITS_SHOWN} more.`);
149
+ }
150
+ return out.join('\n');
151
+ }
152
+
153
+ function repoRoot() {
154
+ // scripts/claude-hooks/ → up two levels.
155
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
156
+ }
157
+
158
+ async function readStdinJson() {
159
+ if (process.stdin.isTTY) return null;
160
+ return new Promise((resolve) => {
161
+ let buf = '';
162
+ let settled = false;
163
+ const finish = (value) => {
164
+ if (settled) return;
165
+ settled = true;
166
+ resolve(value);
167
+ };
168
+ const timer = setTimeout(() => finish(buf || ''), 200);
169
+ process.stdin.setEncoding('utf8');
170
+ process.stdin.on('data', (chunk) => {
171
+ buf += chunk;
172
+ });
173
+ process.stdin.on('end', () => {
174
+ clearTimeout(timer);
175
+ finish(buf);
176
+ });
177
+ process.stdin.on('error', () => {
178
+ clearTimeout(timer);
179
+ finish(buf);
180
+ });
181
+ });
182
+ }
183
+
184
+ function emit(eventName, additionalContext) {
185
+ if (!additionalContext) return;
186
+ process.stdout.write(
187
+ JSON.stringify({
188
+ hookSpecificOutput: { hookEventName: eventName, additionalContext },
189
+ }),
190
+ );
191
+ }
192
+
193
+ function resolveClaudeMdFile(projectDir) {
194
+ const override = process.env.HEYSPEX_CLAUDE_MD_FILE;
195
+ if (override && override.trim()) return override;
196
+ return path.join(projectDir, 'CLAUDE.md');
197
+ }
198
+
199
+ // Read the user's budget knobs from the Console settings file (Settings → Config →
200
+ // Memory writes `agentMemory` here). Missing/malformed → defaults. Standalone JSON
201
+ // read so this .mjs hook needs no TS import.
202
+ function resolveBudget(projectDir) {
203
+ const file =
204
+ process.env.HEYSPEX_CLAUDE_MD_SETTINGS_FILE?.trim() ||
205
+ path.join(projectDir, 'spex-console', 'settings.json');
206
+ let mem = {};
207
+ try {
208
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
209
+ if (parsed && typeof parsed.agentMemory === 'object' && parsed.agentMemory) {
210
+ mem = parsed.agentMemory;
211
+ }
212
+ } catch {
213
+ // absent / malformed → defaults
214
+ }
215
+ const enabled = typeof mem.budgetEnabled === 'boolean' ? mem.budgetEnabled : true;
216
+ const maxLines =
217
+ typeof mem.budgetMaxLines === 'number' && mem.budgetMaxLines > 0
218
+ ? Math.floor(mem.budgetMaxLines)
219
+ : DEFAULT_MAX_LINES;
220
+ const maxBytes =
221
+ typeof mem.budgetMaxBytes === 'number' && mem.budgetMaxBytes > 0
222
+ ? Math.floor(mem.budgetMaxBytes)
223
+ : DEFAULT_MAX_BYTES;
224
+ return { enabled, maxLines, maxBytes };
225
+ }
226
+
227
+ // Group lines under their nearest `## ` heading and measure each section's size,
228
+ // so the report can name the heaviest sections to trim first.
229
+ function analyze(content) {
230
+ const bytes = Buffer.byteLength(content, 'utf8');
231
+ const lines = content.split(/\r?\n/);
232
+ const sections = [];
233
+ let current = { title: '(preamble)', lineCount: 0 };
234
+ for (const line of lines) {
235
+ const m = line.match(/^##\s+(.+?)\s*$/); // top sections are `## `; deeper ones roll up
236
+ if (m) {
237
+ sections.push(current);
238
+ current = { title: m[1], lineCount: 0 };
239
+ }
240
+ current.lineCount += 1;
241
+ }
242
+ sections.push(current);
243
+ sections.sort((a, b) => b.lineCount - a.lineCount);
244
+ return { bytes, lineCount: lines.length, sections };
245
+ }
246
+
247
+ function buildReport(analysis, filePath, budget) {
248
+ const { bytes, lineCount, sections } = analysis;
249
+ if (lineCount < budget.maxLines && bytes < budget.maxBytes) return null; // lean → silent
250
+ const kb = (bytes / 1024).toFixed(1);
251
+ const maxKb = (budget.maxBytes / 1024).toFixed(1);
252
+ const out = [
253
+ `CLAUDE.md budget check — ${filePath}`,
254
+ `CLAUDE.md is ${lineCount} lines / ${kb}KB, past your ${budget.maxLines}-line / ${maxKb}KB ` +
255
+ `target (Settings → Config → Memory; past ~${COMPLIANCE_CLIFF_LINES} lines rule compliance ` +
256
+ `drops sharply — the compliance cliff). It is always-loaded, so trim it: move detail into a ` +
257
+ `procedure (fetched on demand via heyspex_get_procedure) and keep only the non-negotiable ` +
258
+ `contract. For each line ask "Would removing this cause Claude to make mistakes? If not, cut ` +
259
+ `it." See [[minimizing-claude-md-thin-agent-contract]].`,
260
+ ];
261
+ const heaviest = sections.filter((s) => s.title !== '(preamble)').slice(0, MAX_SECTIONS);
262
+ if (heaviest.length) {
263
+ out.push('Largest sections (trim or push to a procedure first):');
264
+ for (const s of heaviest) out.push(` • ${s.lineCount} lines — ${s.title}`);
265
+ }
266
+ return out.join('\n');
267
+ }
268
+
269
+ async function main() {
270
+ if (process.env.HEYSPEX_CLAUDE_MD_BUDGET_OFF === '1') return;
271
+ // No CLAUDE_AGENT gate ([[HS-DT9L-NLR7-DIAG-nine-hooks-are-switched-off-in]]).
272
+ // This used to exit early in interactive sessions, so the one check on the
273
+ // always-loaded agent contract never ran where that file is actually edited —
274
+ // and nothing else guards it either ([[HS-UFQS-5PQ2-DIAG-nothing-gates-an-edit-to-claude-md]]).
275
+ // Safe to run everywhere: it emits NOTHING while the file is inside budget
276
+ // (measured 0 bytes on 2026-08-15), so it speaks only when there is something
277
+ // to say. Break-glass stays HEYSPEX_CLAUDE_MD_BUDGET_OFF=1.
278
+
279
+ const raw = await readStdinJson();
280
+ if (!raw) return;
281
+ let payload;
282
+ try {
283
+ payload = JSON.parse(raw);
284
+ } catch {
285
+ return;
286
+ }
287
+ if (payload?.hook_event_name !== 'SessionStart') return;
288
+
289
+ const projectDir =
290
+ (typeof payload.cwd === 'string' && payload.cwd) || process.env.CLAUDE_PROJECT_DIR || repoRoot();
291
+
292
+ const budget = resolveBudget(projectDir);
293
+ if (!budget.enabled) return; // user turned the gate off in Console settings
294
+
295
+ const file = resolveClaudeMdFile(projectDir);
296
+ let content;
297
+ try {
298
+ content = fs.readFileSync(file, 'utf8');
299
+ } catch {
300
+ return; // absent / unreadable → silent
301
+ }
302
+
303
+ // Duplication runs independent of the size budget above ([[HS-UFQS-5PQ2-DIAG]]):
304
+ // a lean, under-budget CLAUDE.md can still carry a stale inlined procedure copy.
305
+ const sizeReport = buildReport(analyze(content), file, budget);
306
+ const protocolLines = collectProtocolLines(resolveProtocolsDir(projectDir));
307
+ const dupReport = buildDuplicationReport(detectDuplicateLines(content, protocolLines));
308
+ const combined = [sizeReport, dupReport].filter(Boolean).join('\n\n');
309
+
310
+ emit('SessionStart', combined || null);
311
+ }
312
+
313
+ // Only run the hook when this file is executed directly (the Claude Code
314
+ // hooks runner spawns it as a subprocess) — NOT on import. Without this guard,
315
+ // a test/gate script that imports the pure helpers above (resolveProtocolsDir,
316
+ // collectProtocolLines, detectDuplicateLines — reused by
317
+ // scripts/check-claude-md-duplication.mjs per PORTING.md's "import, don't
318
+ // re-derive") would also trigger main()'s stdin read as a side effect, which
319
+ // registers stdin listeners that outlive the read and hang the importing
320
+ // process ([[HS-STRB-NYPR-TASK-replace-every-inline-copy-of-a]]).
321
+ const isDirectExecution =
322
+ process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
323
+ if (isDirectExecution) {
324
+ main().catch(() => {
325
+ // Never break Claude Code on a hook failure.
326
+ });
327
+ }
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ // Thin shell over the spexharness engine port of this gate (wave 6 of
3
+ // [[HS-AZ61-NYPR]]). Same shape as the twelve shells
4
+ // scripts/generate-hook-shells.mjs writes; this one is authored rather than
5
+ // generated only because it is not part of that generator's frozen twelve.
6
+ // To change what this gate DECIDES, edit
7
+ // spexharness/src/gates/check-mcp-freshness-hook.mjs (the rule's mapping lives
8
+ // in spexharness/src/mcp-freshness.mjs, the freshness FACT in
9
+ // scripts/check-mcp-freshness.mjs).
10
+
11
+ import { runGateAsHook } from '../../spexharness/src/adapters/claude-code.mjs';
12
+ import gate from '../../spexharness/src/gates/check-mcp-freshness-hook.mjs';
13
+
14
+ await runGateAsHook(gate);
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ // GENERATED by scripts/generate-hook-shells.mjs (HS-IQBF) — do not hand-edit.
3
+ // This file only wires Claude Code's hook stdin/stdout contract to the
4
+ // spexharness engine port of this gate. To change what this gate DECIDES,
5
+ // edit spexharness/src/gates/claim-before-code-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/claim-before-code-gate.mjs';
10
+
11
+ await runGateAsHook(gate);