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,201 @@
1
+ #!/usr/bin/env node
2
+ // scripts/hs-worktree.mjs — `hs worktree list|alloc|release|reap|rebind`.
3
+ // Task: HS-V9XH (epic HS-03DO "Claim-bound worktree isolation for parallel agent
4
+ // sessions"). Thin shell: parse argv (scripts/lib/hs-worktree-args.mjs), then call
5
+ // dev-factory/lib/worktree-allocator.mjs's exported functions IN-PROCESS — never
6
+ // shells out to the allocator's own CLI mode, so there is exactly one process per
7
+ // invocation and JSON output never has to round-trip through a child's stdout.
8
+ //
9
+ // Exit codes:
10
+ // 0 ok
11
+ // 1 usage (unknown verb/flag — from the parser)
12
+ // 2 check-failed — ONLY `reap --expect-removed` that removed nothing
13
+ // 11 bad-arg (missing required flag / value-flag with no value — from the parser)
14
+ // 13 write-failed (the allocator threw on a git/fs write — alloc or rebind)
15
+
16
+ import { fileURLToPath } from "node:url";
17
+ import path from "node:path";
18
+
19
+ import { parseWorktreeArgs } from "./lib/hs-worktree-args.mjs";
20
+ import {
21
+ makeCtx,
22
+ DEFAULT_DEPS,
23
+ keyInfoFor,
24
+ worktreePathFor,
25
+ allocateForClaim,
26
+ markReleased,
27
+ rebind,
28
+ listWorktrees,
29
+ reapWorktrees,
30
+ } from "../dev-factory/lib/worktree-allocator.mjs";
31
+
32
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
33
+ const REPO_ROOT = path.resolve(__dirname, "..");
34
+
35
+ const EXIT_OK = 0;
36
+ const EXIT_CHECK_FAILED = 2;
37
+ const EXIT_WRITE_FAILED = 13;
38
+
39
+ // ── human-output formatting ─────────────────────────────────────────────────
40
+
41
+ function formatAge(ms) {
42
+ if (ms == null || !Number.isFinite(ms) || ms < 0) return "-";
43
+ const s = Math.floor(ms / 1000);
44
+ if (s < 60) return `${s}s`;
45
+ const m = Math.floor(s / 60);
46
+ if (m < 60) return `${m}m`;
47
+ const h = Math.floor(m / 60);
48
+ if (h < 24) return `${h}h`;
49
+ const d = Math.floor(h / 24);
50
+ return `${d}d`;
51
+ }
52
+
53
+ function formatPid(pid, alive) {
54
+ if (pid == null) return "-";
55
+ return `${pid}(${alive ? "alive" : "dead"})`;
56
+ }
57
+
58
+ function renderTable(rows) {
59
+ const headers = ["PATH", "BRANCH", "KIND", "SPEC", "SESSION", "PID", "AGE", "DIRTY", "RELEASED"];
60
+ const data = rows.map((w) => [
61
+ w.path,
62
+ w.branch || "(detached)",
63
+ w.kind,
64
+ w.specId || "-",
65
+ w.sessionId || "-",
66
+ formatPid(w.pid, w.pidAlive),
67
+ formatAge(w.ageMs),
68
+ w.dirtyCount == null ? "?" : String(w.dirtyCount),
69
+ w.releasedAt ? "yes" : "-",
70
+ ]);
71
+ const widths = headers.map((h, i) => Math.max(h.length, ...data.map((r) => String(r[i]).length)));
72
+ const line = (cols) => cols.map((c, i) => String(c).padEnd(widths[i])).join(" ").trimEnd();
73
+ return [line(headers), ...data.map(line)];
74
+ }
75
+
76
+ // ── agent identity ──────────────────────────────────────────────────────────
77
+
78
+ /**
79
+ * Session id default per task brief: env only, never the MCP's fuller
80
+ * detectAgentIdentity precedence (Cursor/CI vars, etc) — a --session flag
81
+ * always wins.
82
+ */
83
+ function resolveSessionId(flags) {
84
+ return (
85
+ flags.session
86
+ || process.env.CLAUDE_CODE_SESSION_ID
87
+ || process.env.HEYSPEX_SESSION_ID
88
+ || "cli"
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Agent default per task brief: the same detection the MCP uses
94
+ * (vendor/heyspex-mcp/mcp-config.mjs detectAgentIdentity) when it is
95
+ * trivially importable, else HEYSPEX_AGENT env or "cli". A --agent flag
96
+ * always wins over both.
97
+ */
98
+ async function resolveAgent(flags, sessionId) {
99
+ if (flags.agent) return flags.agent;
100
+ try {
101
+ const mod = await import("../vendor/heyspex-mcp/mcp-config.mjs");
102
+ if (typeof mod.detectAgentIdentity === "function") {
103
+ const identity = mod.detectAgentIdentity(undefined, sessionId);
104
+ if (identity?.agent) return identity.agent;
105
+ }
106
+ } catch {
107
+ // Not importable in this install shape (e.g. a published package without
108
+ // vendor/heyspex-mcp) — fall back below.
109
+ }
110
+ return process.env.HEYSPEX_AGENT || "cli";
111
+ }
112
+
113
+ // ── main ─────────────────────────────────────────────────────────────────────
114
+
115
+ async function main() {
116
+ const parsed = parseWorktreeArgs(process.argv.slice(2));
117
+ if (!parsed.ok) {
118
+ process.stderr.write(`hs worktree: ${parsed.message}\n`);
119
+ process.exitCode = parsed.code;
120
+ return;
121
+ }
122
+ const { verb, flags } = parsed;
123
+ const ctx = makeCtx({ repo: REPO_ROOT, worktreeRoot: flags.root || undefined });
124
+
125
+ const emit = (obj, lines) => {
126
+ if (flags.json) process.stdout.write(`${JSON.stringify(obj, null, 2)}\n`);
127
+ else for (const l of lines) process.stdout.write(`${l}\n`);
128
+ };
129
+
130
+ if (verb === "list") {
131
+ const worktrees = listWorktrees(ctx);
132
+ emit({ worktrees }, renderTable(worktrees));
133
+ process.exitCode = EXIT_OK;
134
+ return;
135
+ }
136
+
137
+ if (verb === "alloc") {
138
+ const info = keyInfoFor(flags.spec);
139
+ if (flags.dryRun) {
140
+ const wtPath = worktreePathFor(ctx, flags.spec);
141
+ const baseSha = (DEFAULT_DEPS.git(ctx, ctx.repo, "rev-parse", "HEAD").stdout || "").trim() || null;
142
+ emit(
143
+ { dryRun: true, path: wtPath, branch: info.branch, baseSha },
144
+ [`would allocate ${wtPath} on ${info.branch} from ${baseSha ?? "(unknown)"}`],
145
+ );
146
+ process.exitCode = EXIT_OK;
147
+ return;
148
+ }
149
+ const sessionId = resolveSessionId(flags);
150
+ const agent = await resolveAgent(flags, sessionId);
151
+ try {
152
+ const r = allocateForClaim(ctx, { specId: flags.spec, sessionId, agent, pid: process.pid });
153
+ emit(r, [`${r.created ? "allocated" : "re-attached"} ${r.path} on ${r.branch}`]);
154
+ process.exitCode = EXIT_OK;
155
+ } catch (e) {
156
+ process.stderr.write(`hs worktree alloc: ${e.message}\n`);
157
+ process.exitCode = EXIT_WRITE_FAILED;
158
+ }
159
+ return;
160
+ }
161
+
162
+ if (verb === "release") {
163
+ const r = markReleased(ctx, { specId: flags.spec });
164
+ emit(r, [
165
+ r.found
166
+ ? `${r.alreadyReleased ? "already released" : "released"} ${worktreePathFor(ctx, flags.spec)}`
167
+ : `no worktree for ${flags.spec}`,
168
+ ]);
169
+ process.exitCode = EXIT_OK;
170
+ return;
171
+ }
172
+
173
+ if (verb === "rebind") {
174
+ try {
175
+ const r = rebind(ctx, { fromSpecId: flags.spec, toSpecId: flags.to });
176
+ emit(r, [r.found ? `rebound ${flags.spec} -> ${flags.to} on ${r.branch}` : `no worktree for ${flags.spec}`]);
177
+ process.exitCode = EXIT_OK;
178
+ } catch (e) {
179
+ process.stderr.write(`hs worktree rebind: ${e.message}\n`);
180
+ process.exitCode = EXIT_WRITE_FAILED;
181
+ }
182
+ return;
183
+ }
184
+
185
+ // reap
186
+ const r = await reapWorktrees(ctx, {
187
+ dryRun: flags.dryRun,
188
+ includeForeign: flags.includeForeign,
189
+ yes: flags.yes,
190
+ });
191
+ emit(r, [
192
+ `${r.dryRun ? "would remove" : "removed"} ${r.removed.length}: ${r.removed.join(", ") || "(none)"}`,
193
+ ...r.skipped.map((s) => ` skipped ${s.path} - ${s.reason}`),
194
+ ]);
195
+ process.exitCode = flags.expectRemoved && r.removed.length === 0 ? EXIT_CHECK_FAILED : EXIT_OK;
196
+ }
197
+
198
+ main().catch((err) => {
199
+ process.stderr.write(`hs worktree: ${err?.stack ?? err}\n`);
200
+ process.exitCode = 1;
201
+ });
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+ // Install Claude Code lifecycle hooks that emit agent status to the console
3
+ // (HS-TASK-P89W). Merges entries into `.claude/settings.json` without
4
+ // clobbering existing hooks. Idempotent, and self-heals command-string drift.
5
+ //
6
+ // Usage:
7
+ // node scripts/install-claude-code-hooks.mjs # install/repair
8
+ // node scripts/install-claude-code-hooks.mjs --check # exit 1 on drift
9
+ // node scripts/install-claude-code-hooks.mjs --uninstall
10
+ //
11
+ // What it writes:
12
+ // - PreToolUse → emit-claim-status.mjs --event=PreToolUse
13
+ // - Stop → emit-claim-status.mjs --event=Stop
14
+ // - PostToolUse → emit-claim-status.mjs --event=PostToolUse
15
+ // - SessionEnd → session-end.mjs (release claim on clean exit)
16
+ // - PreToolUse + PostToolUse → log-session-activity.mjs (agent.tool_called
17
+ // session log — HS-6QEW; reads hook_event_name from the stdin payload)
18
+ //
19
+ // Each hook command is identified by its script MARKER. On reinstall, any entry
20
+ // matching the marker but whose command string diverges from the canonical
21
+ // form is replaced — guards against the regression where the path got
22
+ // rewritten to a CWD-relative form (see [[hook-paths]]).
23
+
24
+ import fs from 'node:fs';
25
+ import path from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
27
+
28
+ const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
29
+ const SETTINGS_PATH = path.join(REPO_ROOT, '.claude', 'settings.json');
30
+ const HOOK_TIMEOUT_SECONDS = 5;
31
+
32
+ const CLAIM_STATUS_MARKER = 'scripts/claude-hooks/emit-claim-status.mjs';
33
+ const SESSION_END_MARKER = 'scripts/claude-hooks/session-end.mjs';
34
+ const SESSION_ACTIVITY_MARKER = 'scripts/claude-hooks/log-session-activity.mjs';
35
+
36
+ // Anchor every script path via $CLAUDE_PROJECT_DIR so hooks resolve regardless
37
+ // of the agent's cwd (it often drifts into spex-console/ during a session).
38
+ // See [[HS-LES-hook-paths]] and the prior NIHZ regression.
39
+ //
40
+ // Hooks are not invoked directly — every one routes through the `_run.mjs`
41
+ // shim, which is where the HeySpex master on/off switch lives (`hs mode off`
42
+ // makes every hook exit 0 without the settings file changing). See
43
+ // scripts/claude-hooks/_run.mjs and docs/claude-code-setup.md.
44
+ const SHIM = 'scripts/claude-hooks/_run.mjs';
45
+
46
+ /** Canonical command string for a hook: `node "$CLAUDE_PROJECT_DIR/_run.mjs" <name> [args]`. */
47
+ function shimCommand(scriptMarker, args = '') {
48
+ const name = scriptMarker.replace('scripts/claude-hooks/', '').replace(/\.mjs$/, '');
49
+ return `node "$CLAUDE_PROJECT_DIR/${SHIM}" ${name}${args}`;
50
+ }
51
+
52
+ /**
53
+ * Markers that identify one of our entries inside an event's groups. The shim
54
+ * form is first (what we self-heal toward); the pre-shim direct form is kept so
55
+ * a checkout installed before the shim landed gets REPAIRED in place rather
56
+ * than growing a duplicate entry alongside the old one.
57
+ */
58
+ function markersFor(scriptMarker, args = '') {
59
+ const name = scriptMarker.replace('scripts/claude-hooks/', '').replace(/\.mjs$/, '');
60
+ return [`${SHIM}" ${name}${args}`, `${scriptMarker}"${args}`];
61
+ }
62
+
63
+ // One descriptor per (event, script). `markers` identify our entry within an
64
+ // event's groups; `command` is the canonical string we self-heal toward.
65
+ const HOOKS = [
66
+ {
67
+ event: 'PreToolUse',
68
+ markers: markersFor(CLAIM_STATUS_MARKER, ' --event=PreToolUse'),
69
+ withMatcher: true,
70
+ command: shimCommand(CLAIM_STATUS_MARKER, ' --event=PreToolUse'),
71
+ },
72
+ {
73
+ event: 'Stop',
74
+ markers: markersFor(CLAIM_STATUS_MARKER, ' --event=Stop'),
75
+ withMatcher: false,
76
+ command: shimCommand(CLAIM_STATUS_MARKER, ' --event=Stop'),
77
+ },
78
+ {
79
+ event: 'PostToolUse',
80
+ markers: markersFor(CLAIM_STATUS_MARKER, ' --event=PostToolUse'),
81
+ withMatcher: true,
82
+ command: shimCommand(CLAIM_STATUS_MARKER, ' --event=PostToolUse'),
83
+ },
84
+ {
85
+ event: 'SessionEnd',
86
+ markers: markersFor(SESSION_END_MARKER),
87
+ withMatcher: false,
88
+ command: shimCommand(SESSION_END_MARKER),
89
+ },
90
+ {
91
+ event: 'PreToolUse',
92
+ markers: markersFor(SESSION_ACTIVITY_MARKER),
93
+ withMatcher: true,
94
+ command: shimCommand(SESSION_ACTIVITY_MARKER),
95
+ },
96
+ {
97
+ event: 'PostToolUse',
98
+ markers: markersFor(SESSION_ACTIVITY_MARKER),
99
+ withMatcher: true,
100
+ command: shimCommand(SESSION_ACTIVITY_MARKER),
101
+ },
102
+ ];
103
+
104
+ const ALL_MARKERS = [...new Set(HOOKS.flatMap((h) => h.markers))];
105
+
106
+ function readSettings() {
107
+ try {
108
+ const raw = fs.readFileSync(SETTINGS_PATH, 'utf8').trim();
109
+ if (!raw) return {};
110
+ return JSON.parse(raw);
111
+ } catch (err) {
112
+ if (err && err.code === 'ENOENT') return {};
113
+ throw err;
114
+ }
115
+ }
116
+
117
+ function writeSettings(settings) {
118
+ fs.mkdirSync(path.dirname(SETTINGS_PATH), { recursive: true });
119
+ fs.writeFileSync(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}\n`, 'utf8');
120
+ }
121
+
122
+ function hookMatchesMarker(h, markers) {
123
+ if (!h || h.type !== 'command' || typeof h.command !== 'string') return false;
124
+ return [].concat(markers).some((m) => h.command.includes(m));
125
+ }
126
+
127
+ function isOurHook(h) {
128
+ return hookMatchesMarker(h, ALL_MARKERS);
129
+ }
130
+
131
+ function stripOurHooks(groups) {
132
+ if (!Array.isArray(groups)) return [];
133
+ return groups
134
+ .map((group) => {
135
+ if (!group || !Array.isArray(group.hooks)) return group;
136
+ const hooks = group.hooks.filter((h) => !isOurHook(h));
137
+ return { ...group, hooks };
138
+ })
139
+ .filter((group) => Array.isArray(group?.hooks) && group.hooks.length > 0);
140
+ }
141
+
142
+ // Returns 'added' (no entry existed), 'repaired' (entry existed with wrong
143
+ // command string and was rewritten), or 'noop' (entry already canonical).
144
+ function ensureHook(settings, { event, markers, command, withMatcher }) {
145
+ settings.hooks ??= {};
146
+ settings.hooks[event] ??= [];
147
+ const groups = settings.hooks[event];
148
+
149
+ let foundOurs = false;
150
+ let repaired = false;
151
+ for (const group of groups) {
152
+ if (!group || !Array.isArray(group.hooks)) continue;
153
+ for (const h of group.hooks) {
154
+ if (!hookMatchesMarker(h, markers)) continue;
155
+ foundOurs = true;
156
+ if (h.command !== command) {
157
+ h.command = command;
158
+ repaired = true;
159
+ }
160
+ if (h.timeout !== HOOK_TIMEOUT_SECONDS) {
161
+ h.timeout = HOOK_TIMEOUT_SECONDS;
162
+ repaired = true;
163
+ }
164
+ }
165
+ }
166
+ if (foundOurs) return repaired ? 'repaired' : 'noop';
167
+
168
+ const newGroup = {
169
+ ...(withMatcher ? { matcher: '.*' } : {}),
170
+ hooks: [{ type: 'command', command, timeout: HOOK_TIMEOUT_SECONDS }],
171
+ };
172
+ groups.push(newGroup);
173
+ return 'added';
174
+ }
175
+
176
+ function uninstall() {
177
+ const settings = readSettings();
178
+ if (!settings.hooks) {
179
+ console.log('install-claude-code-hooks: nothing to uninstall (no hooks block).');
180
+ return;
181
+ }
182
+ let removed = 0;
183
+ const events = [...new Set(HOOKS.map((h) => h.event))];
184
+ for (const name of events) {
185
+ const before = settings.hooks[name];
186
+ if (!Array.isArray(before)) continue;
187
+ const after = stripOurHooks(before);
188
+ const droppedHookCount =
189
+ before.reduce((n, g) => n + (Array.isArray(g?.hooks) ? g.hooks.length : 0), 0) -
190
+ after.reduce((n, g) => n + (Array.isArray(g?.hooks) ? g.hooks.length : 0), 0);
191
+ removed += droppedHookCount;
192
+ if (after.length === 0) {
193
+ delete settings.hooks[name];
194
+ } else {
195
+ settings.hooks[name] = after;
196
+ }
197
+ }
198
+ if (Object.keys(settings.hooks).length === 0) {
199
+ delete settings.hooks;
200
+ }
201
+ writeSettings(settings);
202
+ console.log(`install-claude-code-hooks: uninstalled ${removed} hook entr${removed === 1 ? 'y' : 'ies'}.`);
203
+ }
204
+
205
+ function install() {
206
+ const settings = readSettings();
207
+ const added = [];
208
+ const repaired = [];
209
+ for (const descriptor of HOOKS) {
210
+ const outcome = ensureHook(settings, descriptor);
211
+ if (outcome === 'added') added.push(descriptor.event);
212
+ else if (outcome === 'repaired') repaired.push(descriptor.event);
213
+ }
214
+ if (added.length === 0 && repaired.length === 0) {
215
+ console.log('install-claude-code-hooks: already installed (no changes).');
216
+ return;
217
+ }
218
+ writeSettings(settings);
219
+ const parts = [];
220
+ if (added.length > 0) parts.push(`installed ${added.join(', ')}`);
221
+ if (repaired.length > 0) parts.push(`repaired ${repaired.join(', ')}`);
222
+ console.log(`install-claude-code-hooks: ${parts.join('; ')} → ${path.relative(REPO_ROOT, SETTINGS_PATH)}`);
223
+ console.log(' Wrappers: scripts/claude-hooks/emit-claim-status.mjs, scripts/claude-hooks/session-end.mjs');
224
+ console.log(' Console URL: .heyspex/console-url (auto-written by `npm run dev`) → HEYSPEX_CONSOLE_URL env var → http://localhost:3000');
225
+ }
226
+
227
+ // Read-only audit: walks installed hooks and prints any drift without
228
+ // writing. Exit code 1 if anything is missing or has a non-canonical
229
+ // command string; 0 if everything is healthy. Intended for CI / preflight.
230
+ function check() {
231
+ const settings = readSettings();
232
+ const issues = [];
233
+ const groupsFor = (event) =>
234
+ Array.isArray(settings?.hooks?.[event]) ? settings.hooks[event] : [];
235
+ for (const { event, markers, command } of HOOKS) {
236
+ const groups = groupsFor(event);
237
+ const ours = groups
238
+ .flatMap((g) => (Array.isArray(g?.hooks) ? g.hooks : []))
239
+ .filter((h) => hookMatchesMarker(h, markers));
240
+ if (ours.length === 0) {
241
+ issues.push(`${event}: missing hook for ${markers[0]}`);
242
+ continue;
243
+ }
244
+ for (const h of ours) {
245
+ if (h.command !== command) {
246
+ issues.push(`${event}: command drift — expected \`${command}\`, found \`${h.command}\``);
247
+ }
248
+ if (h.timeout !== HOOK_TIMEOUT_SECONDS) {
249
+ issues.push(`${event}: timeout drift — expected ${HOOK_TIMEOUT_SECONDS}, found ${h.timeout}`);
250
+ }
251
+ }
252
+ }
253
+ if (issues.length === 0) {
254
+ console.log('install-claude-code-hooks --check: OK');
255
+ process.exit(0);
256
+ }
257
+ console.error('install-claude-code-hooks --check: drift detected');
258
+ for (const issue of issues) console.error(` - ${issue}`);
259
+ console.error(' Run `node scripts/install-claude-code-hooks.mjs` to repair.');
260
+ process.exit(1);
261
+ }
262
+
263
+ const mode = process.argv.includes('--uninstall')
264
+ ? 'uninstall'
265
+ : process.argv.includes('--check')
266
+ ? 'check'
267
+ : 'install';
268
+ if (mode === 'uninstall') uninstall();
269
+ else if (mode === 'check') check();
270
+ else install();