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,396 @@
1
+ #!/usr/bin/env node
2
+ // Claude Code SessionStart hook → auto-memory index (MEMORY.md) self-healing + budget check.
3
+ //
4
+ // The Claude Code auto-memory index at ~/.claude/projects/<repo>/memory/MEMORY.md
5
+ // is injected into context at the start of every session, but only the first 200
6
+ // lines OR 25KB (whichever comes first) are loaded — content past that threshold
7
+ // is silently dropped from session-start context, degrading recall. Detail kept
8
+ // in the sibling topic files is NOT loaded at startup, so it is free.
9
+ //
10
+ // Per the research-backed retention policy ([[auto-memory-index-hygiene]] /
11
+ // [[HS-VN9W-RES]]), the auto-memory index is a thin curated-working-set + ROUTER
12
+ // over the HeySpex vault — it must NOT mirror vault memory. The vault
13
+ // (specs/memory/) is the retrieval store, served dynamically by
14
+ // heyspex_recall_memories. So any index entry whose link already targets
15
+ // specs/memory/... is, by construction, a duplicate of content that lives in the
16
+ // vault — provably safe to remove from the always-loaded index.
17
+ //
18
+ // This hook therefore does two things at session start:
19
+ // 1. SELF-HEAL (write) — evicts those provably-mirrored entries in place (content
20
+ // stays in the vault; only the redundant index line leaves). A sanctioned
21
+ // PINNED allowlist (lines tagged [pinned]) is never touched. This is the
22
+ // eviction mechanism that keeps the index from re-bloating (MemGPT/Letta
23
+ // eviction pattern applied to the index tier).
24
+ // 2. ADVISE (emit) — for whatever remains, stays SILENT while healthy and, once
25
+ // the index crosses ~70% of the cap, names the specific over-length entries
26
+ // to relocate. The advisory half never blocks.
27
+ //
28
+ // Eviction is conservative: it only removes entries whose link path contains
29
+ // `specs/memory/` (those have a guaranteed vault home). Domain entries backed by
30
+ // local topic files are left for the one-time migration + the PreToolUse
31
+ // write-guard; this hook never drops a line it cannot prove is mirrored.
32
+ //
33
+ // Env gate (mirrors wip-checkpoint.mjs):
34
+ // CLAUDE_AGENT=true opt in (unset → silent + no self-heal, so
35
+ // interactive humans aren't spammed/mutated).
36
+ // HEYSPEX_AUTO_MEMORY_BUDGET_OFF=1 hard kill switch — silent + no self-heal.
37
+ // HEYSPEX_AUTO_MEMORY_SELF_HEAL_OFF=1 disable only the WRITE (stay advisory-only).
38
+ // HEYSPEX_AUTO_MEMORY_FILE=<path> override the resolved MEMORY.md path
39
+ // (used by the smoke test; also a manual escape hatch).
40
+ //
41
+ // MUST exit 0 on every path — a hook that breaks Claude Code is worse than one
42
+ // that silently does nothing. Writes are best-effort: a failed write is swallowed
43
+ // and the advisory still emits.
44
+ //
45
+ // Output shape: { hookSpecificOutput: { hookEventName, additionalContext } }.
46
+
47
+ import fs from 'node:fs';
48
+ import os from 'node:os';
49
+ import path from 'node:path';
50
+ import { fileURLToPath } from 'node:url';
51
+
52
+ import { defaultConfigRoot } from '../../spexharness/src/config-root.mjs';
53
+
54
+ export const CAP_BYTES = 25000; // harness session-start load cap (~24.4 KiB), per the official memory docs
55
+ export const CAP_LINES = 200;
56
+ export const TRIGGER = 0.7; // compact proactively at 70% of the cap, not at overflow
57
+ export const HOOK_MAX_CHARS = 200; // per-entry index-hook limit ([[auto-memory-index-hygiene]])
58
+ const MAX_OFFENDERS = 8;
59
+
60
+ // A top-level index entry whose markdown link target contains `specs/memory/` is a
61
+ // duplicate of content that already lives in the vault retrieval store — provably
62
+ // safe to evict from the always-loaded index. Captures the link target so the
63
+ // eviction can confirm the vault home actually exists before dropping the line
64
+ // (a dangling link has no other home, so evicting it would be lossy).
65
+ const VAULT_MIRROR_RE = /^- \[[^\]]*\]\(([^)]*specs\/memory\/[^)]*)\)/;
66
+
67
+ // Lines carrying an explicit pinned marker are the sanctioned curated working set
68
+ // (identity / mode / standing rules) — never evicted, even if they somehow match.
69
+ const PINNED_RE = /\[pinned\]|\(pinned\)/i;
70
+
71
+ function repoRoot() {
72
+ // scripts/claude-hooks/ → up two levels.
73
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
74
+ }
75
+
76
+ async function readStdinJson() {
77
+ if (process.stdin.isTTY) return null;
78
+ return new Promise((resolve) => {
79
+ let buf = '';
80
+ let settled = false;
81
+ const finish = (value) => {
82
+ if (settled) return;
83
+ settled = true;
84
+ resolve(value);
85
+ };
86
+ const timer = setTimeout(() => finish(buf || ''), 200);
87
+ process.stdin.setEncoding('utf8');
88
+ process.stdin.on('data', (chunk) => {
89
+ buf += chunk;
90
+ });
91
+ process.stdin.on('end', () => {
92
+ clearTimeout(timer);
93
+ finish(buf);
94
+ });
95
+ process.stdin.on('error', () => {
96
+ clearTimeout(timer);
97
+ finish(buf);
98
+ });
99
+ });
100
+ }
101
+
102
+ function emit(eventName, additionalContext) {
103
+ if (!additionalContext) return;
104
+ process.stdout.write(
105
+ JSON.stringify({
106
+ hookSpecificOutput: { hookEventName: eventName, additionalContext },
107
+ }),
108
+ );
109
+ }
110
+
111
+ function readJsonSafe(file) {
112
+ try {
113
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+
119
+ // Claude Code derives the auto-memory dir name by replacing path separators and
120
+ // the drive colon with '-' (e.g. C:\Users\pwher\dev-projects\workflow-protocol →
121
+ // C--Users-pwher-dev-projects-workflow-protocol).
122
+ function mangleProjectDir(dir) {
123
+ return dir.replace(/[:\\/]+/g, '-');
124
+ }
125
+
126
+ function resolveAutoMemoryDir(projectDir) {
127
+ // Honor an explicit autoMemoryDirectory setting (local overrides project).
128
+ for (const name of ['settings.local.json', 'settings.json']) {
129
+ const settings = readJsonSafe(path.join(repoRoot(), '.claude', name));
130
+ const dir = settings?.autoMemoryDirectory;
131
+ if (typeof dir === 'string' && dir.trim()) {
132
+ return dir.startsWith('~/') ? path.join(os.homedir(), dir.slice(2)) : dir;
133
+ }
134
+ }
135
+ return path.join(os.homedir(), '.claude', 'projects', mangleProjectDir(projectDir), 'memory');
136
+ }
137
+
138
+ function resolveMemoryFile(projectDir) {
139
+ const override = process.env.HEYSPEX_AUTO_MEMORY_FILE;
140
+ if (override && override.trim()) return override;
141
+ return path.join(resolveAutoMemoryDir(projectDir), 'MEMORY.md');
142
+ }
143
+
144
+ function analyze(content) {
145
+ const bytes = Buffer.byteLength(content, 'utf8');
146
+ const allLines = content.split(/\r?\n/);
147
+ const offenders = [];
148
+ for (const line of allLines) {
149
+ if (!line.startsWith('- ')) continue; // only top-level index entries
150
+ if (line.length <= HOOK_MAX_CHARS) continue;
151
+ const m = line.match(/^- \[([^\]]+)\]/);
152
+ offenders.push({ label: m ? m[1] : line.slice(2, 60), length: line.length });
153
+ }
154
+ offenders.sort((a, b) => b.length - a.length);
155
+ return { bytes, lineCount: allLines.length, offenders };
156
+ }
157
+
158
+ // Remove provably-mirrored entries (link → specs/memory/) from the index. Pinned
159
+ // lines and any non-entry lines (router line, headers, blank lines) are preserved
160
+ // verbatim. Returns the rewritten content, the evicted entry labels, and whether
161
+ // anything changed. Pure — does not touch disk.
162
+ export function evictVaultMirrors(content, vaultHomeExists) {
163
+ const eol = content.includes('\r\n') ? '\r\n' : '\n';
164
+ const lines = content.split(/\r?\n/);
165
+ const kept = [];
166
+ const evicted = [];
167
+ for (const line of lines) {
168
+ const m = VAULT_MIRROR_RE.exec(line);
169
+ // Evict only a non-pinned line whose specs/memory/ target ACTUALLY resolves —
170
+ // a dangling link has no vault home, so dropping it would lose the only pointer.
171
+ if (m && !PINNED_RE.test(line) && vaultHomeExists(m[1])) {
172
+ const label = line.match(/^- \[([^\]]+)\]/);
173
+ evicted.push(label ? label[1] : line.slice(2, 60));
174
+ continue; // drop the redundant index line
175
+ }
176
+ kept.push(line);
177
+ }
178
+ return { newContent: kept.join(eol), evicted, changed: evicted.length > 0 };
179
+ }
180
+
181
+ function buildEvictionNote(evicted) {
182
+ if (!evicted.length) return null;
183
+ const shown = evicted.slice(0, MAX_OFFENDERS);
184
+ const out = [
185
+ `Auto-memory self-heal — evicted ${evicted.length} vault-mirrored ` +
186
+ `${evicted.length === 1 ? 'entry' : 'entries'} from the always-loaded index ` +
187
+ `(their content lives in the vault; recall via heyspex_recall_memories). ` +
188
+ `Per [[auto-memory-index-hygiene]], the index must not mirror specs/memory/.`,
189
+ ];
190
+ for (const label of shown) out.push(` • ${label}`);
191
+ if (evicted.length > MAX_OFFENDERS) out.push(` • …and ${evicted.length - MAX_OFFENDERS} more`);
192
+ return out.join('\n');
193
+ }
194
+
195
+ // ── Compaction engine ────────────────────────────────────────────────────────
196
+ // [[HS-AE28-NYPR-TASK-add-a-compaction-rule-that-runs-when]]: eviction above
197
+ // only removes entries with a PROVABLE vault home (specs/memory/ mirrors) —
198
+ // safe, but not sufficient on its own once the index is bloated with long,
199
+ // non-mirrored hook entries too. This adds the second lever: once the index
200
+ // (post-eviction) has crossed `trigger` (70%) of its byte/line cap, truncate
201
+ // any remaining over-`hookMaxChars` entry to a one-line hook in place. Pure —
202
+ // does not touch disk; `compactMemoryIndex` is a fixture-testable engine
203
+ // module callers can run against ANY injected content/path (never the
204
+ // caller's real MEMORY.md by default — the caller decides what to read/write).
205
+
206
+ /** Truncate every non-pinned, over-length `- ` entry to `hookMaxChars`. Pure. */
207
+ export function truncateOverLengthEntries(content, { hookMaxChars = HOOK_MAX_CHARS } = {}) {
208
+ const eol = content.includes('\r\n') ? '\r\n' : '\n';
209
+ const lines = content.split(/\r?\n/);
210
+ const truncated = [];
211
+ const out = lines.map((line) => {
212
+ if (!line.startsWith('- ')) return line; // only top-level index entries
213
+ if (PINNED_RE.test(line)) return line; // sanctioned curated working set — never touched
214
+ if (line.length <= hookMaxChars) return line;
215
+ const m = line.match(/^- \[([^\]]+)\]/);
216
+ truncated.push({ label: m ? m[1] : line.slice(2, 60), originalLength: line.length });
217
+ const cut = line.slice(0, Math.max(0, hookMaxChars - 1)).trimEnd();
218
+ return `${cut}…`;
219
+ });
220
+ return { newContent: out.join(eol), truncated, changed: truncated.length > 0 };
221
+ }
222
+
223
+ /**
224
+ * The compaction rule: runs ONLY once `content` has crossed `trigger` (70% by
225
+ * default) of its byte/line cap, and truncates over-length entries to bring it
226
+ * back down. Returns `ranCompaction: false` (no-op, `newContent === content`)
227
+ * below the trigger — the rule stays silent while the index is healthy, same
228
+ * discipline as the advisory report.
229
+ *
230
+ * @param {string} content
231
+ * @param {{ capBytes?: number, capLines?: number, trigger?: number, hookMaxChars?: number }} [opts]
232
+ */
233
+ export function compactMemoryIndex(
234
+ content,
235
+ { capBytes = CAP_BYTES, capLines = CAP_LINES, trigger = TRIGGER, hookMaxChars = HOOK_MAX_CHARS } = {},
236
+ ) {
237
+ const bytes = Buffer.byteLength(content, 'utf8');
238
+ const lineCount = content.split(/\r?\n/).length;
239
+ const before = { bytes, lineCount };
240
+ if (bytes < capBytes * trigger && lineCount < capLines * trigger) {
241
+ return { newContent: content, ranCompaction: false, changed: false, truncated: [], before, after: before };
242
+ }
243
+ const { newContent, truncated, changed } = truncateOverLengthEntries(content, { hookMaxChars });
244
+ return {
245
+ newContent,
246
+ ranCompaction: true,
247
+ changed,
248
+ truncated,
249
+ before,
250
+ after: { bytes: Buffer.byteLength(newContent, 'utf8'), lineCount: newContent.split(/\r?\n/).length },
251
+ };
252
+ }
253
+
254
+ function buildCompactionNote(result, { capBytes = CAP_BYTES, capLines = CAP_LINES, hookMaxChars = HOOK_MAX_CHARS } = {}) {
255
+ if (!result.ranCompaction || result.truncated.length === 0) return null;
256
+ const capKb = (capBytes / 1024).toFixed(1);
257
+ const out = [
258
+ `Auto-memory compaction — MEMORY.md crossed ${Math.round(TRIGGER * 100)}% of its ${capKb}KB / ${capLines}-line ` +
259
+ `cap, so ${result.truncated.length} over-length ${result.truncated.length === 1 ? 'entry was' : 'entries were'} ` +
260
+ `truncated to ${hookMaxChars} chars in place (detail belongs in the linked topic file / vault, not the ` +
261
+ `always-loaded index — [[auto-memory-index-hygiene]]).`,
262
+ ];
263
+ for (const t of result.truncated.slice(0, MAX_OFFENDERS)) {
264
+ out.push(` • ${t.originalLength} → ${hookMaxChars} chars — ${t.label}`);
265
+ }
266
+ if (result.truncated.length > MAX_OFFENDERS) out.push(` • …and ${result.truncated.length - MAX_OFFENDERS} more`);
267
+ return out.join('\n');
268
+ }
269
+
270
+ function buildReport(analysis, filePath) {
271
+ const { bytes, lineCount, offenders } = analysis;
272
+ if (bytes < CAP_BYTES * TRIGGER && lineCount < CAP_LINES * TRIGGER) return null; // healthy → silent
273
+ const kb = (bytes / 1024).toFixed(1);
274
+ const capKb = (CAP_BYTES / 1024).toFixed(1);
275
+ const pct = Math.round((bytes / CAP_BYTES) * 100);
276
+ const out = [
277
+ `Auto-memory index budget check — ${filePath}`,
278
+ `MEMORY.md is ${kb}KB / ${lineCount} lines (${pct}% of the ${capKb}KB / ${CAP_LINES}-line ` +
279
+ `session-start load cap). Compact toward one-line hooks per ` +
280
+ `[[auto-memory-index-hygiene]] before it overflows — detail belongs in the linked ` +
281
+ `topic files, which are not loaded at startup.`,
282
+ ];
283
+ if (offenders.length) {
284
+ out.push(`Entries over ${HOOK_MAX_CHARS} chars (compact these first):`);
285
+ for (const o of offenders.slice(0, MAX_OFFENDERS)) out.push(` • ${o.length} chars — ${o.label}`);
286
+ if (offenders.length > MAX_OFFENDERS) out.push(` • …and ${offenders.length - MAX_OFFENDERS} more`);
287
+ }
288
+ return out.join('\n');
289
+ }
290
+
291
+ async function main() {
292
+ if (process.env.HEYSPEX_AUTO_MEMORY_BUDGET_OFF === '1') return;
293
+ // No CLAUDE_AGENT gate ([[HS-DT9L-NLR7-DIAG-nine-hooks-are-switched-off-in]]).
294
+ // The memory index is loaded into EVERY session, interactive included, so the
295
+ // context it costs is paid in both — gating the check on unattended sessions
296
+ // only meant the one place a human reads the warning never got it.
297
+ // Safe to run everywhere: it emits NOTHING while the index is inside budget
298
+ // (measured 0 bytes on 2026-08-15). Break-glass stays HEYSPEX_AUTO_MEMORY_BUDGET_OFF=1.
299
+
300
+ const raw = await readStdinJson();
301
+ if (!raw) return;
302
+ let payload;
303
+ try {
304
+ payload = JSON.parse(raw);
305
+ } catch {
306
+ return;
307
+ }
308
+ if (payload?.hook_event_name !== 'SessionStart') return;
309
+
310
+ const projectDir =
311
+ (typeof payload.cwd === 'string' && payload.cwd) || process.env.CLAUDE_PROJECT_DIR || repoRoot();
312
+ const file = resolveMemoryFile(projectDir);
313
+
314
+ let content;
315
+ try {
316
+ content = fs.readFileSync(file, 'utf8');
317
+ } catch {
318
+ return; // absent / unreadable → silent
319
+ }
320
+
321
+ // 1. Self-heal: evict provably-mirrored entries and persist (best-effort).
322
+ // A `specs/memory/<…>` link is resolved against the repo before eviction —
323
+ // a dangling link is kept (its content has no other home). `specs/` is a
324
+ // VAULT artifact (per ADR-0009's two-root split, spexharness/src/config-
325
+ // root.mjs), not a code-checkout one — since the 2026-08-31 repo split put
326
+ // the vault in its own checkout, resolving against `repoRoot()` (this code
327
+ // repo) would find `specs/` nowhere and treat every real mirror as
328
+ // dangling. `defaultConfigRoot()` resolves the ACTIVE vault the same way
329
+ // every other hook's config-gated rule does (env HEYSPEX_VAULT_DIR /
330
+ // HEYSPEX_VAULT_ROOT wins outright; falls back to a cwd-contained vault,
331
+ // then the registry pointer, then this code root).
332
+ const base = defaultConfigRoot();
333
+ const vaultHomeExists = (rel) => {
334
+ try {
335
+ return fs.existsSync(path.resolve(base, rel.split('#')[0].trim()));
336
+ } catch {
337
+ return false;
338
+ }
339
+ };
340
+ let evictionNote = null;
341
+ const { newContent, evicted, changed } = evictVaultMirrors(content, vaultHomeExists);
342
+ if (changed) {
343
+ if (process.env.HEYSPEX_AUTO_MEMORY_SELF_HEAL_OFF === '1') {
344
+ // Write disabled — fall back to flagging the mirrors without mutating.
345
+ evictionNote = buildEvictionNote(evicted).replace('evicted', 'would evict');
346
+ } else {
347
+ try {
348
+ fs.writeFileSync(file, newContent);
349
+ content = newContent; // advise against the healed file
350
+ evictionNote = buildEvictionNote(evicted);
351
+ } catch {
352
+ // Write failed (permissions, etc.) — keep going; advise against the original.
353
+ evictionNote = buildEvictionNote(evicted).replace('evicted', 'could not evict');
354
+ }
355
+ }
356
+ }
357
+
358
+ // 1.5. Compact: eviction alone only removes provably-mirrored entries: once
359
+ // what remains has crossed 70% of the cap, truncate remaining over-length
360
+ // entries in place too. [[HS-AE28-NYPR-TASK-add-a-compaction-rule-that-runs-when]]
361
+ let compactionNote = null;
362
+ const compaction = compactMemoryIndex(content);
363
+ if (compaction.changed) {
364
+ if (process.env.HEYSPEX_AUTO_MEMORY_SELF_HEAL_OFF === '1') {
365
+ compactionNote = buildCompactionNote(compaction)?.replace('were truncated', 'would be truncated') ?? null;
366
+ } else {
367
+ try {
368
+ fs.writeFileSync(file, compaction.newContent);
369
+ content = compaction.newContent; // advise against the compacted file
370
+ compactionNote = buildCompactionNote(compaction);
371
+ } catch {
372
+ compactionNote = buildCompactionNote(compaction)?.replace('were truncated', 'could not be truncated') ?? null;
373
+ }
374
+ }
375
+ }
376
+
377
+ // 2. Advise: budget report for whatever remains.
378
+ const report = buildReport(analyze(content), file);
379
+ const combined = [evictionNote, compactionNote, report].filter(Boolean).join('\n\n') || null;
380
+ emit('SessionStart', combined);
381
+ }
382
+
383
+ // Only run the hook when this file is executed directly (the Claude Code hooks
384
+ // runner spawns it as a subprocess) — NOT on import. Without this guard, a
385
+ // test/gate script importing the pure engine functions above (evictVaultMirrors,
386
+ // compactMemoryIndex, truncateOverLengthEntries) would also trigger main()'s
387
+ // stdin read as a side effect, hanging the importing process
388
+ // ([[HS-STRB-NYPR-TASK-replace-every-inline-copy-of-a]] found the same defect
389
+ // in check-claude-md-budget.mjs).
390
+ const isDirectExecution =
391
+ process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
392
+ if (isDirectExecution) {
393
+ main().catch(() => {
394
+ // Never break Claude Code on a hook failure.
395
+ });
396
+ }