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
@@ -16,16 +16,26 @@
16
16
  *
17
17
  * Force-push and protected-ref refusal DEFERS to the existing destructive-git
18
18
  * guardrail (`classifyGitDestructive` in
19
- * scripts/claude-hooks/git-stash-gate.mjs) and the protected-ref list already
20
- * used by scripts/hs-night-branch.mjs — this module does not ship a second,
21
- * competing allowlist.
19
+ * spexharness/src/gates/git-stash-gate.decide.mjs — relocated out of
20
+ * scripts/claude-hooks/git-stash-gate.mjs by HS-IQBF; that hook script is now
21
+ * a generated shell over the spexharness/src/gates/git-stash-gate.mjs port)
22
+ * and the protected-ref list already used by scripts/hs-night-branch.mjs —
23
+ * this module does not ship a second, competing allowlist.
24
+ *
25
+ * Both of those modules (plus git-stash-gate.decide.mjs's own hook-chain
26
+ * dependencies) are dev-only — never in the package's `files`
27
+ * (RUNTIME_FILES / `package.json`) — so a static import of either one crashed
28
+ * EVERY `hs source push` invocation with a raw `ERR_MODULE_NOT_FOUND` on a
29
+ * published npm install. They are loaded via a guarded dynamic import instead
30
+ * (see `loadDestructiveGitGuard` below); `pushBranch` needs them on every
31
+ * call (the force-push/protected-ref check is not behind an opt-in flag), so
32
+ * a resolution failure refuses the ENTIRE push — zero `git push` subprocess
33
+ * calls — with a clear message, never a stack trace.
22
34
  */
23
35
  import { spawn, execFileSync } from 'node:child_process';
24
36
  import path from 'node:path';
25
37
  import { fileURLToPath } from 'node:url';
26
38
 
27
- import { PROTECTED_REFS } from '../hs-night-branch.mjs';
28
- import { classifyGitDestructive } from '../claude-hooks/git-stash-gate.mjs';
29
39
  import { scanPaths } from './hs-source-secret-scan.mjs';
30
40
 
31
41
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -33,6 +43,41 @@ export const REPO_ROOT = path.resolve(__dirname, '..', '..');
33
43
 
34
44
  export const DEFAULT_TIMEOUT_MS = 60_000;
35
45
 
46
+ export const DESTRUCTIVE_GIT_GUARD_UNAVAILABLE_MESSAGE =
47
+ 'hs source push: refusing — the destructive-git safety check ' +
48
+ '(scripts/hs-night-branch.mjs, scripts/claude-hooks/git-stash-gate.mjs) is not available on ' +
49
+ 'this install. Those modules ship only inside a full source checkout of the HeySpex repo, not ' +
50
+ 'a published npm install, and `hs source push` requires them to classify force-push / ' +
51
+ 'protected-ref requests before it ever touches git — so it refuses rather than pushing ' +
52
+ 'without that check.';
53
+
54
+ let destructiveGitGuardPromise;
55
+
56
+ /**
57
+ * Guarded, on-demand, cached load of the two dev-only modules `pushBranch`
58
+ * depends on for every call. Never throws — a resolution failure (module
59
+ * absent, as on a published install) comes back as `{ ok: false }` so the
60
+ * caller can refuse gracefully instead of letting `ERR_MODULE_NOT_FOUND`
61
+ * propagate as an unhandled rejection / stack trace.
62
+ * @returns {Promise<{ ok: true, PROTECTED_REFS: string[], classifyGitDestructive: Function } | { ok: false, error: unknown }>}
63
+ */
64
+ export function loadDestructiveGitGuard() {
65
+ if (!destructiveGitGuardPromise) {
66
+ destructiveGitGuardPromise = Promise.all([
67
+ import('../hs-night-branch.mjs'),
68
+ import('../../spexharness/src/gates/git-stash-gate.decide.mjs'),
69
+ ]).then(
70
+ ([nightBranch, stashGate]) => ({
71
+ ok: true,
72
+ PROTECTED_REFS: nightBranch.PROTECTED_REFS,
73
+ classifyGitDestructive: stashGate.classifyGitDestructive,
74
+ }),
75
+ (error) => ({ ok: false, error }),
76
+ );
77
+ }
78
+ return destructiveGitGuardPromise;
79
+ }
80
+
36
81
  /**
37
82
  * Pushes `branch` to `remote` through the credential-hang-safe invocation,
38
83
  * refusing outright (zero subprocess calls) on a force-push flag or a
@@ -50,7 +95,7 @@ export const DEFAULT_TIMEOUT_MS = 60_000;
50
95
  * }} opts
51
96
  * @returns {Promise<{ ok: boolean, code: string, message?: string }>}
52
97
  */
53
- export function pushBranch({
98
+ export async function pushBranch({
54
99
  remote = 'origin',
55
100
  branch,
56
101
  extraArgs = [],
@@ -59,64 +104,68 @@ export function pushBranch({
59
104
  spawnFn = spawn,
60
105
  preScan = null,
61
106
  } = {}) {
62
- return new Promise((resolve) => {
63
- if (!branch || typeof branch !== 'string' || branch.trim() === '') {
64
- resolve({
65
- ok: false,
66
- code: 'NO_BRANCH',
67
- message: 'hs source push: a branch name is required (e.g. `hs source push origin my-branch`).',
68
- });
69
- return;
70
- }
107
+ if (!branch || typeof branch !== 'string' || branch.trim() === '') {
108
+ return {
109
+ ok: false,
110
+ code: 'NO_BRANCH',
111
+ message: 'hs source push: a branch name is required (e.g. `hs source push origin my-branch`).',
112
+ };
113
+ }
71
114
 
72
- // ---- protected-ref refusal — reuses hs-night-branch.mjs's own list ----
73
- if (PROTECTED_REFS.includes(branch)) {
74
- resolve({
75
- ok: false,
76
- code: 'PROTECTED_REF',
77
- message: `hs source push: refusing to push to protected ref \`${branch}\` (${PROTECTED_REFS.join(', ')}). This is never allowed through hs source push.`,
78
- });
79
- return;
80
- }
115
+ // ---- destructive-git guard load — every push needs it, not just an
116
+ // opt-in flag, so a resolution failure (module absent, as on a published
117
+ // install) refuses the entire push before any git subprocess is spawned ----
118
+ const guard = await loadDestructiveGitGuard();
119
+ if (!guard.ok) {
120
+ return { ok: false, code: 'PACKAGE_INCOMPLETE', message: DESTRUCTIVE_GIT_GUARD_UNAVAILABLE_MESSAGE };
121
+ }
122
+ const { PROTECTED_REFS, classifyGitDestructive } = guard;
81
123
 
82
- // ---- force-push refusal — defers to the destructive-git guardrail's
83
- // own classifier rather than a second, competing allowlist ----
84
- const syntheticCommand = ['git', 'push', ...extraArgs, remote, branch].join(' ');
85
- if (classifyGitDestructive('Bash', { command: syntheticCommand }) === 'gated') {
86
- resolve({
124
+ // ---- protected-ref refusal — reuses hs-night-branch.mjs's own list ----
125
+ if (PROTECTED_REFS.includes(branch)) {
126
+ return {
127
+ ok: false,
128
+ code: 'PROTECTED_REF',
129
+ message: `hs source push: refusing to push to protected ref \`${branch}\` (${PROTECTED_REFS.join(', ')}). This is never allowed through hs source push.`,
130
+ };
131
+ }
132
+
133
+ // ---- force-push refusal — defers to the destructive-git guardrail's
134
+ // own classifier rather than a second, competing allowlist ----
135
+ const syntheticCommand = ['git', 'push', ...extraArgs, remote, branch].join(' ');
136
+ if (classifyGitDestructive('Bash', { command: syntheticCommand }) === 'gated') {
137
+ return {
138
+ ok: false,
139
+ code: 'FORCE_REFUSED',
140
+ message:
141
+ 'hs source push: refusing — this invocation is classified as a gated destructive git action (force-push or similar) by the existing destructive-git guardrail. Force-push is never allowed through hs source push; use the guardrail\'s own owner-authorized path if this is truly intended.',
142
+ };
143
+ }
144
+
145
+ // ---- secret-scan extension point (implemented by sibling task
146
+ // HS-5NXX-CWPX; inert here — a caller that passes no preScan gets no
147
+ // scanning, exactly today's behavior) ----
148
+ if (typeof preScan === 'function') {
149
+ let scanResult;
150
+ try {
151
+ scanResult = preScan({ remote, branch, cwd });
152
+ } catch (err) {
153
+ return {
87
154
  ok: false,
88
- code: 'FORCE_REFUSED',
89
- message:
90
- 'hs source push: refusing — this invocation is classified as a gated destructive git action (force-push or similar) by the existing destructive-git guardrail. Force-push is never allowed through hs source push; use the guardrail\'s own owner-authorized path if this is truly intended.',
91
- });
92
- return;
155
+ code: 'PRESCAN_ERROR',
156
+ message: `hs source push: the preScan hook threw — ${err instanceof Error ? err.message : String(err)}`,
157
+ };
93
158
  }
94
-
95
- // ---- secret-scan extension point (implemented by sibling task
96
- // HS-5NXX-CWPX; inert here — a caller that passes no preScan gets no
97
- // scanning, exactly today's behavior) ----
98
- if (typeof preScan === 'function') {
99
- let scanResult;
100
- try {
101
- scanResult = preScan({ remote, branch, cwd });
102
- } catch (err) {
103
- resolve({
104
- ok: false,
105
- code: 'PRESCAN_ERROR',
106
- message: `hs source push: the preScan hook threw — ${err instanceof Error ? err.message : String(err)}`,
107
- });
108
- return;
109
- }
110
- if (scanResult && scanResult.ok === false) {
111
- resolve({
112
- ok: false,
113
- code: 'PRESCAN_BLOCKED',
114
- message: scanResult.message || 'hs source push: blocked by preScan (likely secret detected).',
115
- });
116
- return;
117
- }
159
+ if (scanResult && scanResult.ok === false) {
160
+ return {
161
+ ok: false,
162
+ code: 'PRESCAN_BLOCKED',
163
+ message: scanResult.message || 'hs source push: blocked by preScan (likely secret detected).',
164
+ };
118
165
  }
166
+ }
119
167
 
168
+ return new Promise((resolve) => {
120
169
  // ---- the actual push: credential-helper override + GIT_TERMINAL_PROMPT=0,
121
170
  // both together, unconditionally, per the recorded lesson ----
122
171
  const argv = [
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ // scripts/lib/hs-worktree-args.mjs — pure argv parser for `hs worktree <verb>`.
3
+ // Task: HS-V9XH (epic HS-03DO "Claim-bound worktree isolation for parallel agent
4
+ // sessions") — the CLI face over dev-factory/lib/worktree-allocator.mjs.
5
+ //
6
+ // Deliberately NOT a wrapper around the allocator's own exported `parseCliArgs`:
7
+ // that parser (a) never validates a verb's REQUIRED flags (alloc/release/rebind
8
+ // with a missing --spec/--to parses clean there — the caller in its own CLI mode
9
+ // checks that per-branch, after parsing), (b) has no `--expect-removed` flag (a
10
+ // hs-worktree-only concept — the allocator's `reap` has no notion of "did the
11
+ // caller expect something to be removed"), and (c) accepts every value flag
12
+ // (--pid, --repo, --session, --agent, ...) for every verb rather than scoping
13
+ // them to the verbs that use them. This module needs both, so it is its own
14
+ // small parser rather than a wrap-then-patch of the core's.
15
+ //
16
+ // → parseWorktreeArgs(argv) => { ok: true, verb, flags }
17
+ // | { ok: false, code, message }
18
+ //
19
+ // Exit-code contract (mirrors dev-factory/lib/worktree-allocator.mjs's CLI):
20
+ // 1 (EXIT_USAGE) unknown verb, unknown flag for that verb
21
+ // 11 (EXIT_BAD_ARG) a required flag is missing, or a value-flag has no value
22
+
23
+ export const EXIT_USAGE = 1;
24
+ export const EXIT_BAD_ARG = 11;
25
+
26
+ const VERBS = new Set(["list", "alloc", "release", "reap", "rebind"]);
27
+
28
+ // Value-taking flags common to every verb, plus per-verb value flags.
29
+ const COMMON_VALUE_FLAGS = new Set(["--root"]);
30
+ const VERB_VALUE_FLAGS = {
31
+ list: new Set(),
32
+ alloc: new Set(["--spec", "--session", "--agent"]),
33
+ release: new Set(["--spec"]),
34
+ reap: new Set(),
35
+ rebind: new Set(["--spec", "--to"]),
36
+ };
37
+
38
+ // Boolean flags common to every verb, plus per-verb boolean flags.
39
+ const COMMON_BOOL_FLAGS = new Set(["--json", "--dry-run"]);
40
+ const VERB_BOOL_FLAGS = {
41
+ list: new Set(),
42
+ alloc: new Set(),
43
+ release: new Set(),
44
+ reap: new Set(["--include-foreign", "--yes", "--expect-removed"]),
45
+ rebind: new Set(),
46
+ };
47
+
48
+ const REQUIRED_FLAGS = {
49
+ alloc: ["spec"],
50
+ release: ["spec"],
51
+ rebind: ["spec", "to"],
52
+ };
53
+
54
+ const usageFail = (message) => ({ ok: false, code: EXIT_USAGE, message });
55
+ const badArgFail = (message) => ({ ok: false, code: EXIT_BAD_ARG, message });
56
+
57
+ /**
58
+ * Parse `hs worktree <verb> [flags]`.
59
+ *
60
+ * `list | alloc --spec <id> [--session <id>] [--agent <name>] | release --spec <id>
61
+ * | reap [--include-foreign] [--yes] [--expect-removed] | rebind --spec <id> --to <id>`
62
+ * plus the common `--json`, `--dry-run`, `--root <dir>`.
63
+ */
64
+ export function parseWorktreeArgs(argv) {
65
+ const args = Array.isArray(argv) ? argv.map(String) : [];
66
+ if (args.length === 0) return usageFail("missing verb (expected list|alloc|release|reap|rebind)");
67
+
68
+ const verb = args[0];
69
+ if (!VERBS.has(verb)) return usageFail(`unknown verb "${verb}" (expected list|alloc|release|reap|rebind)`);
70
+
71
+ const boolFlags = VERB_BOOL_FLAGS[verb];
72
+ const valueFlags = VERB_VALUE_FLAGS[verb];
73
+
74
+ const flags = {
75
+ json: false,
76
+ dryRun: false,
77
+ root: null,
78
+ };
79
+ if (verb === "reap") { flags.includeForeign = false; flags.yes = false; flags.expectRemoved = false; }
80
+
81
+ for (let i = 1; i < args.length; i++) {
82
+ const a = args[i];
83
+ if (!a.startsWith("--")) return usageFail(`unexpected argument "${a}" for worktree ${verb}`);
84
+
85
+ if (COMMON_BOOL_FLAGS.has(a)) {
86
+ if (a === "--json") flags.json = true;
87
+ else flags.dryRun = true;
88
+ continue;
89
+ }
90
+ if (boolFlags.has(a)) {
91
+ if (a === "--include-foreign") flags.includeForeign = true;
92
+ else if (a === "--yes") flags.yes = true;
93
+ else if (a === "--expect-removed") flags.expectRemoved = true;
94
+ continue;
95
+ }
96
+ if (COMMON_VALUE_FLAGS.has(a) || valueFlags.has(a)) {
97
+ const v = args[i + 1];
98
+ if (v === undefined) return badArgFail(`${a} requires a value`);
99
+ i += 1;
100
+ if (a === "--root") flags.root = v;
101
+ else if (a === "--spec") flags.spec = v;
102
+ else if (a === "--to") flags.to = v;
103
+ else if (a === "--session") flags.session = v;
104
+ else if (a === "--agent") flags.agent = v;
105
+ continue;
106
+ }
107
+ return usageFail(`unknown flag "${a}" for worktree ${verb}`);
108
+ }
109
+
110
+ for (const key of REQUIRED_FLAGS[verb] ?? []) {
111
+ if (!flags[key]) return badArgFail(`worktree ${verb} requires --${key === "to" ? "to" : key}`);
112
+ }
113
+
114
+ return { ok: true, verb, flags };
115
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * The two path helpers this repo had written by hand twenty-five times
3
+ * ([[HS-ZA6P-PFMN-TASK-block-a-vault-owned-state-file-from]]).
4
+ *
5
+ * Thirteen places answered "where is the code repo" in seven different ways,
6
+ * and twelve places checked "does this path stay inside its root" with twelve
7
+ * different names and one identical body. Sixteen of the twenty-five were
8
+ * written on a single night — by the sessions fixing the bug that duplication
9
+ * causes ([[HS-AI1T-UGC9-DIAG-nothing-in-the-mcp-server-checks-a]],
10
+ * [[HS-MMR6-DS0Q-DIAG-the-plan-review-creates-folders-outside]]). Per-site
11
+ * acceptance criteria cannot see duplication; `scripts/check-shared-helpers.mjs`
12
+ * is the gate that can.
13
+ *
14
+ * Behavior is deliberately unchanged from the copies this replaces — the
15
+ * thrown messages are byte-identical apart from the caller's own label, so a
16
+ * test asserting a refusal message keeps passing and a caller catching the
17
+ * throw keeps catching it.
18
+ *
19
+ * Published surface: listed in `package.json`'s `files` (verified by
20
+ * `scripts/verify-mcp-package.mjs`) because it is in the static import closure
21
+ * of `vendor/heyspex-mcp/cli.mjs`.
22
+ */
23
+ import { isAbsolute, resolve, sep } from "node:path";
24
+
25
+ /**
26
+ * Refuse to build a path under `root` when the root itself is not absolute, or
27
+ * when the resolved path would land outside it.
28
+ *
29
+ * This is the [[HS-MMR6]] failure: a root that lost its backslashes
30
+ * (`C:\dev-projects\heyspex-specs` → `C:dev-projectsheyspex-specs`) is
31
+ * *drive-relative* on Windows, not absolute, and `mkdirSync(..., { recursive:
32
+ * true })` resolves it against `process.cwd()` and builds a real directory
33
+ * tree in the wrong repo, returning success. Call this before any
34
+ * `mkdirSync`/`writeFileSync` so a refusal creates nothing on disk.
35
+ *
36
+ * It always THROWS. That is not a policy choice made here — every one of the
37
+ * twelve copies threw, and each call site routes the throw into a failure path
38
+ * that already existed in that function (throw onward where the write is the
39
+ * primary action, `return false` where it is a tidy-up step after the real
40
+ * work already succeeded, wrap into the module's own error shape where a
41
+ * caller reads the return). Those per-site decisions live at the call sites
42
+ * and are untouched by sharing the body.
43
+ *
44
+ * @param {string} root The root everything must stay under (absolute).
45
+ * @param {string} candidate The path about to be written.
46
+ * @param {string} label Caller prefix, e.g. "capture-focus write" — it is
47
+ * interpolated as `<label> refused: …`.
48
+ * @throws {Error} naming the root, the resolved path, and the reason refused.
49
+ */
50
+ export function assertPathUnderRoot(root, candidate, label) {
51
+ if (typeof root !== "string" || !root.trim() || !isAbsolute(root)) {
52
+ throw new Error(
53
+ `${label} refused: vaultRoot "${root}" is not an absolute path — ` +
54
+ "refusing rather than silently creating directories wherever a relative or " +
55
+ "drive-relative path resolves against the current working directory.",
56
+ );
57
+ }
58
+ const normalizedRoot = resolve(root);
59
+ const normalizedPath = resolve(candidate);
60
+ if (normalizedPath === normalizedRoot || normalizedPath.startsWith(normalizedRoot + sep)) {
61
+ return;
62
+ }
63
+ throw new Error(
64
+ `${label} refused: resolved to "${normalizedPath}" but vaultRoot requires ` +
65
+ `everything under "${normalizedRoot}" — refusing rather than silently writing to the wrong location.`,
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Where is the code repo? One precedence order, everywhere:
71
+ *
72
+ * 1. `explicit` — a caller that already knows (tests, a dispatcher).
73
+ * 2. `HS_PKG_ROOT` — the operator override.
74
+ * 3. `packageRoot` — derived from the calling module's own location.
75
+ * 4. `vaultRoot` — ONLY when no `packageRoot` was supplied.
76
+ *
77
+ * Step 4 is the single-checkout case and it is not a mistake: before the
78
+ * repo split, the vault and the code lived in one folder, and two callers
79
+ * (`spex-console/bin/hs-check.ts`, `spex-console/bin/hs-revalidate.ts`) still
80
+ * rely on that. It does mean the same call reads "the code" in one setup and
81
+ * "the vault" in another — which nothing at those call sites used to say out
82
+ * loud. {@link resolveCodeAndVaultRoots} is how you say it: it returns the
83
+ * vault root ALONGSIDE the code root rather than instead of it, so a caller
84
+ * that wants both checkouts searched gets both, labeled.
85
+ *
86
+ * @param {object} [opts]
87
+ * @param {string} [opts.explicit] Caller-supplied override, highest precedence.
88
+ * @param {string} [opts.packageRoot] Root derived from the calling module's location.
89
+ * @param {string} [opts.vaultRoot] Single-checkout fallback; used only without packageRoot.
90
+ * @param {NodeJS.ProcessEnv} [opts.env] Injectable for tests.
91
+ * @returns {string} The resolved code root, or "" when nothing resolved.
92
+ */
93
+ export function resolveCodeRoot({
94
+ explicit,
95
+ packageRoot,
96
+ vaultRoot,
97
+ env = process.env,
98
+ } = {}) {
99
+ const fromExplicit = typeof explicit === "string" ? explicit.trim() : "";
100
+ if (fromExplicit) return fromExplicit;
101
+ const fromEnv = typeof env?.HS_PKG_ROOT === "string" ? env.HS_PKG_ROOT.trim() : "";
102
+ if (fromEnv) return fromEnv;
103
+ const fromPackage = typeof packageRoot === "string" ? packageRoot.trim() : "";
104
+ if (fromPackage) return fromPackage;
105
+ const fromVault = typeof vaultRoot === "string" ? vaultRoot.trim() : "";
106
+ return fromVault;
107
+ }
108
+
109
+ /**
110
+ * The code root and the vault root as a labeled, deduped list — the shape
111
+ * `resolveCodeAuditRoots` in `plan-review.mjs` settled on, lifted here so every
112
+ * caller that searches "the checkout" can search both without re-deciding what
113
+ * that means.
114
+ *
115
+ * The vault entry is omitted when the two resolve to the same directory (the
116
+ * single-checkout case), so a caller never searches the same folder twice and
117
+ * never reports one finding as two.
118
+ *
119
+ * @param {object} opts
120
+ * @param {string} opts.vaultRoot
121
+ * @param {string} [opts.explicit]
122
+ * @param {string} [opts.packageRoot]
123
+ * @param {NodeJS.ProcessEnv} [opts.env]
124
+ * @returns {{ label: string, root: string }[]}
125
+ */
126
+ export function resolveCodeAndVaultRoots({ vaultRoot, explicit, packageRoot, env } = {}) {
127
+ const codeRoot = resolveCodeRoot({ explicit, packageRoot, vaultRoot, env });
128
+ const roots = [{ label: "code root", root: codeRoot }];
129
+ const vault = typeof vaultRoot === "string" ? vaultRoot : "";
130
+ if (vault && (!codeRoot || resolve(vault) !== resolve(codeRoot))) {
131
+ roots.push({ label: "vault root", root: vault });
132
+ }
133
+ return roots;
134
+ }