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
@@ -24,6 +24,12 @@
24
24
  * 2. **Append, not overwrite.** The console PATCH route stores top-level keys
25
25
  * verbatim (last-write-wins), so multi-beat `user_quote` calls would clobber
26
26
  * each other. We read the prior buffer and append, sending the full array.
27
+ * The same rule applies to the five single-object timestamped sections
28
+ * (`details`/`example`/`userStories`/`doneWhen`/`jtbd`): a second call on
29
+ * the same field combines onto the prior call's text (blank-line joined)
30
+ * instead of replacing it — [[HS-OSCT-77P9-DIAG-heyspex-update-capture-overwrites-agent]].
31
+ * `coreIdea`/`summary` are deliberately NOT combined — a capture's core
32
+ * idea/summary is its current best statement, not an accumulating log.
27
33
  *
28
34
  * `confirmed: true` is set on every agent-authored section because the MCP write
29
35
  * is an explicit commit — the agent only calls this after the conversational
@@ -48,6 +54,34 @@ function priorChatTurns(currentBuffer) {
48
54
  );
49
55
  }
50
56
 
57
+ /**
58
+ * Read the prior confirmed text for a single-object timestamped section
59
+ * (`details`/`example`/`userStories`/`doneWhen`/`jtbd`) so a second
60
+ * `heyspex_update_capture` call can accumulate onto it instead of
61
+ * overwriting it ([[HS-OSCT-77P9-DIAG-heyspex-update-capture-overwrites-agent]]).
62
+ * The console PATCH route stores whatever this module sends verbatim
63
+ * (last-write-wins on the whole key), so accumulation has to happen here,
64
+ * on the read-before-write side — the same reason userQuotes/chatTurns
65
+ * already read the prior buffer before building their new array value.
66
+ */
67
+ function priorTimestampedText(currentBuffer, key) {
68
+ const raw = currentBuffer?.[key];
69
+ if (raw && typeof raw === 'object' && !Array.isArray(raw) && typeof raw.text === 'string') {
70
+ return raw.text.trim();
71
+ }
72
+ return '';
73
+ }
74
+
75
+ /**
76
+ * Combine a prior section's text with a new call's text so neither call's
77
+ * content is lost — joined with a blank line so the materialized section
78
+ * reads as separate paragraphs, not run together.
79
+ */
80
+ function combineTimestampedText(currentBuffer, key, nextText) {
81
+ const prior = priorTimestampedText(currentBuffer, key);
82
+ return prior ? `${prior}\n\n${nextText}` : nextText;
83
+ }
84
+
51
85
  function collapse(value) {
52
86
  return String(value).trim().replace(/\n+/g, ' ');
53
87
  }
@@ -85,13 +119,17 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
85
119
  }
86
120
 
87
121
  // agent_expansion is the `## Expanded Concept` deep-dive section → buffer.details.
122
+ // Accumulates onto the prior call's text (HS-OSCT) instead of replacing it.
88
123
  if (args.agent_expansion !== undefined && String(args.agent_expansion).trim()) {
89
- patches.push({ path: 'details', value: { at: iso, text: String(args.agent_expansion).trim(), confirmed: true } });
124
+ const text = combineTimestampedText(currentBuffer, 'details', String(args.agent_expansion).trim());
125
+ patches.push({ path: 'details', value: { at: iso, text, confirmed: true } });
90
126
  }
91
127
 
92
128
  // example is the `## Example` section (real or AI-illustrative) → buffer.example.
129
+ // Accumulates onto the prior call's text (HS-OSCT) instead of replacing it.
93
130
  if (args.example !== undefined && String(args.example).trim()) {
94
- patches.push({ path: 'example', value: { at: iso, text: String(args.example).trim(), confirmed: true } });
131
+ const text = combineTimestampedText(currentBuffer, 'example', String(args.example).trim());
132
+ patches.push({ path: 'example', value: { at: iso, text, confirmed: true } });
95
133
  }
96
134
 
97
135
  // user_stories is the `## User Stories` section → buffer.userStories. Root cause
@@ -100,9 +138,10 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
100
138
  // console content_buffer (and therefore the materialized file) never saw it —
101
139
  // silent data loss, unlike example which has always had this push.
102
140
  if (args.user_stories !== undefined && String(args.user_stories).trim()) {
141
+ const text = combineTimestampedText(currentBuffer, 'userStories', String(args.user_stories).trim());
103
142
  patches.push({
104
143
  path: 'userStories',
105
- value: { at: iso, text: String(args.user_stories).trim(), confirmed: true },
144
+ value: { at: iso, text, confirmed: true },
106
145
  });
107
146
  }
108
147
 
@@ -122,9 +161,10 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
122
161
  // {at,text,confirmed} shape as the other agent-authored sections (example,
123
162
  // user_stories, details) — sibling half of the HS-NR43 fix above.
124
163
  if (args.done_when !== undefined && String(args.done_when).trim()) {
164
+ const text = combineTimestampedText(currentBuffer, 'doneWhen', String(args.done_when).trim());
125
165
  patches.push({
126
166
  path: 'doneWhen',
127
- value: { at: iso, text: String(args.done_when).trim(), confirmed: true },
167
+ value: { at: iso, text, confirmed: true },
128
168
  });
129
169
  }
130
170
 
@@ -137,9 +177,10 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
137
177
  // is refused by name at the handler's first hop, never silently dropped here
138
178
  // — [[additionalproperties-false-in-an-mcp]].
139
179
  if (args.jtbd !== undefined && String(args.jtbd).trim()) {
180
+ const text = combineTimestampedText(currentBuffer, 'jtbd', String(args.jtbd).trim());
140
181
  patches.push({
141
182
  path: 'jtbd',
142
- value: { at: iso, text: String(args.jtbd).trim(), confirmed: true },
183
+ value: { at: iso, text, confirmed: true },
143
184
  });
144
185
  }
145
186
 
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { existsSync, watch as fsWatch } from 'node:fs';
14
14
  import { basename as pathBasename, dirname, join } from 'node:path';
15
- import { loadCaptureFocus, captureFocusPath } from './capture-focus.mjs';
15
+ import { loadCaptureFocusOffline, captureFocusPath } from './capture-focus.mjs';
16
16
  import { parseSpecId } from './spec-id-lib.mjs';
17
17
 
18
18
  // Exponential reconnect backoff (ms). Capped at 30s; jittered ±50% on use.
@@ -205,6 +205,54 @@ export function createCaptureChannelWatcher({
205
205
  return body?.session ?? null;
206
206
  }
207
207
 
208
+ /**
209
+ * Resolve the LIVE capture-on-deck pin, the same way `readCaptureFocusVendor`
210
+ * does elsewhere in the vendor MCP (`capture-tools.mjs`): GET the Neon
211
+ * control-plane pin + selection, falling back to the offline file
212
+ * (`loadCaptureFocusOffline`) only when the console can't be reached, and only
213
+ * when that file pin is not itself stale. Kept local
214
+ * (own `fetchImpl`/`getConsoleUrl`, mirroring `fetchSessionSnapshot` above)
215
+ * rather than importing capture-tools.mjs, so this watcher's boot-time pin
216
+ * read stays on the SAME injectable transport its tests already stub.
217
+ *
218
+ * Fixes [[HS-O4U6-2ZRO-DIAG-the-offline-capture-pin-file-is-months]]: on a
219
+ * console-backed vault the pin lives in Neon, not the offline file — every
220
+ * MCP server start used to `loadCaptureFocus(vaultRoot)` (the file) here,
221
+ * so on this repo it printed "pin → HS-CAP-VDPB is already materialized"
222
+ * for a capture promoted two months earlier, on every single boot.
223
+ */
224
+ async function fetchLiveCaptureFocus() {
225
+ try {
226
+ const consoleUrl = getConsoleUrl().replace(/\/$/, '');
227
+ const [pinRes, selRes] = await Promise.all([
228
+ fetchImpl(`${consoleUrl}/api/control/capture-focus-pin`, { headers: { accept: 'application/json' } }),
229
+ fetchImpl(`${consoleUrl}/api/control/capture-focus-selection`, { headers: { accept: 'application/json' } }),
230
+ ]);
231
+ if (pinRes.ok) {
232
+ const pinBody = await pinRes.json();
233
+ const pin = pinBody?.pin ?? null;
234
+ if (!pin) return null; // Neon authoritative — no pin means nothing on deck.
235
+ const selBody = selRes.ok ? await selRes.json() : null;
236
+ const sel = selBody?.selection ?? null;
237
+ const base = (p) => String(p || '').replace(/^.*[/\\]/, '');
238
+ const selMatches = sel?.specPath ? base(sel.specPath).startsWith(pin.specId) : false;
239
+ const focus = {
240
+ spec_path: selMatches ? sel.specPath : `specs/captures/${pin.specId}.md`,
241
+ pinned_at: pin.pinnedAt,
242
+ };
243
+ if (pin.expiresAt) focus.expires = pin.expiresAt;
244
+ focus.kind = 'idea';
245
+ return focus;
246
+ }
247
+ } catch {
248
+ // Fall through to the offline file seam.
249
+ }
250
+ // Offline seam — refuse a rotted file pin instead of starting a watcher on it.
251
+ // This watcher spent two months announcing a June capture read off this file
252
+ // ([[HS-O4U6-2ZRO-DIAG-the-offline-capture-pin-file-is-months]]).
253
+ return loadCaptureFocusOffline(vaultRoot).focus;
254
+ }
255
+
208
256
  async function openAndConsumeStream(sessionId, controller) {
209
257
  const consoleUrl = getConsoleUrl();
210
258
  const url = `${consoleUrl.replace(/\/$/, '')}/api/capture/sessions/${encodeURIComponent(sessionId)}/events`;
@@ -447,13 +495,14 @@ export function createCaptureChannelWatcher({
447
495
  if (pinDebounceTimer) clearTimeout(pinDebounceTimer);
448
496
  pinDebounceTimer = setTimeout(() => {
449
497
  pinDebounceTimer = null;
450
- reevaluatePin();
498
+ reevaluatePin().catch((err) => log(`reevaluatePin failed: ${err?.message ?? err}`));
451
499
  }, PIN_WATCH_DEBOUNCE_MS);
452
500
  }
453
501
 
454
- function reevaluatePin() {
502
+ async function reevaluatePin() {
455
503
  if (stopped) return;
456
- const focus = loadCaptureFocus(vaultRoot);
504
+ const focus = await fetchLiveCaptureFocus();
505
+ if (stopped) return; // may have been stopped while the pin fetch was in flight
457
506
  const nextSessionId = focus ? deriveSessionIdFromFocus(focus) : null;
458
507
 
459
508
  if (nextSessionId === activeSessionId) {
@@ -533,7 +582,8 @@ export function createCaptureChannelWatcher({
533
582
  return;
534
583
  }
535
584
  startPinWatcher();
536
- reevaluatePin(); // initial open if pin already exists
585
+ // Initial open if pin already exists — fire-and-forget (async network read).
586
+ reevaluatePin().catch((err) => log(`reevaluatePin failed: ${err?.message ?? err}`));
537
587
  }
538
588
 
539
589
  function stop() {
@@ -19,6 +19,17 @@ import {
19
19
  removeOutboxEntry,
20
20
  stashCaptureArgs,
21
21
  } from "./capture-outbox.mjs";
22
+ import {
23
+ CONSOLE_NOT_SIGNED_IN,
24
+ consoleAuthErrorMessage,
25
+ consoleUnauthorizedMessage,
26
+ } from "./console-fetch.mjs";
27
+ // The runnable form of a HeySpex command on THIS machine — `hs login` when the
28
+ // bin is on PATH, else an absolute-path `node "<install>/bin/hs.mjs" login`.
29
+ // Never an `npx` form: npx resolves from the READER's working directory, and
30
+ // outside a checkout `npx hs` installs the unrelated public package hs@2.0.1
31
+ // ([[HS-I6HT-EKYU-DIAG-every-message-tells-the-user-to-run-hs]]).
32
+ import { hsCommand } from "../../scripts/lib/hs-command.mjs";
22
33
 
23
34
  /**
24
35
  * Error taxonomy for capture-session console calls ([[HS-K27H-DIAG-0G86]]).
@@ -39,14 +50,28 @@ import {
39
50
  * message verbatim. (Entries ALREADY in the outbox from a genuine
40
51
  * transport failure still pause the drain on 5xx and retry later — the
41
52
  * ban is on adding new entries for an answered request.)
53
+ * - `CONSOLE_LOCAL_URL_IN_CLOUD_MODE` / `CONSOLE_NOT_SIGNED_IN`
54
+ * ([[HS-9R6S-N8VP-DIAG-capture-writes-report-the-console-as]]): the call
55
+ * never had a chance — cloud mode pointed at a local console, no bearer
56
+ * token on this machine, or an answered HTTP 401. NEVER stash: nothing
57
+ * becomes reachable until `hs login` runs, so a "your words are buffered
58
+ * and will sync" promise would be false AND would hide the real cause.
59
+ * console-fetch stamps `authProblem` on the transport result; the 401 form
60
+ * stays inside the 4xx "the server ANSWERED, never stash" family and only
61
+ * swaps the generic CAPTURE_CONFLICT code for an actionable one.
42
62
  */
43
63
  export const CAPTURE_ERROR_UNREACHABLE = "CONSOLE_UNREACHABLE";
44
64
  export const CAPTURE_ERROR_CONFLICT = "CAPTURE_CONFLICT";
45
65
  export const CAPTURE_ERROR_FAILED = "PATCH_FAILED";
66
+ export const CAPTURE_ERROR_NOT_SIGNED_IN = CONSOLE_NOT_SIGNED_IN;
46
67
 
47
68
  /** Classify a `consoleFetch` result by HTTP status range. */
48
69
  export function classifyConsoleFetchError(result) {
70
+ // Sign-in problems are stamped by console-fetch on the status-0 result and
71
+ // outrank the generic transport class — they never buffer.
72
+ if (result.authProblem) return result.authProblem;
49
73
  if (result.status === 0) return CAPTURE_ERROR_UNREACHABLE;
74
+ if (result.status === 401) return CONSOLE_NOT_SIGNED_IN;
50
75
  if (result.status >= 400 && result.status < 500)
51
76
  return CAPTURE_ERROR_CONFLICT;
52
77
  return CAPTURE_ERROR_FAILED;
@@ -66,7 +91,7 @@ export function extractConsoleMessage(result) {
66
91
  * - list/create helper failures: `{ ok:false, error, result }` (status on `result`)
67
92
  *
68
93
  * @returns {{
69
- * kind: 'transport'|'client'|'server',
94
+ * kind: 'transport'|'auth'|'client'|'server',
70
95
  * shouldStash: boolean,
71
96
  * error: string,
72
97
  * http_status: number,
@@ -86,6 +111,38 @@ export function describeCaptureWriteFailure(failure) {
86
111
  const serverCode = typeof result?.body?.error === "string" ? result.body.error : null;
87
112
  const serverMessage = typeof result?.body?.message === "string" ? result.body.message : null;
88
113
 
114
+ // Sign-in problems first ([[HS-9R6S-N8VP-DIAG]]). A refused connection to the
115
+ // WRONG console is not an outage, and a 401 is not a capture conflict —
116
+ // both are "run hs login", and neither may touch the offline buffer.
117
+ const authProblem =
118
+ typeof result?.authProblem === "string" ? result.authProblem : null;
119
+ if (status === 0 && authProblem) {
120
+ return {
121
+ kind: "auth",
122
+ shouldStash: false,
123
+ error: authProblem,
124
+ http_status: 0,
125
+ server_error_code: null,
126
+ server_message: null,
127
+ message:
128
+ result?.authMessage
129
+ || consoleAuthErrorMessage(authProblem, result?.url)
130
+ || null,
131
+ };
132
+ }
133
+ if (status === 401) {
134
+ const verbatim401 = serverMessage ? ` The server said: ${serverMessage}` : "";
135
+ return {
136
+ kind: "auth",
137
+ shouldStash: false,
138
+ error: CONSOLE_NOT_SIGNED_IN,
139
+ http_status: 401,
140
+ server_error_code: serverCode,
141
+ server_message: serverMessage,
142
+ message: `${consoleUnauthorizedMessage(result?.url)}${verbatim401}`,
143
+ };
144
+ }
145
+
89
146
  // status 0 ⇔ genuine transport failure (ECONNREFUSED / DNS / timeout /
90
147
  // abort — console-fetch stamps status 0 on every non-response). Anything
91
148
  // else is an HTTP response the server actually returned.
@@ -142,6 +199,11 @@ export function describeCaptureWriteFailure(failure) {
142
199
  * structured shape.
143
200
  * - 4xx → `status: "refused"` + the server's error/message verbatim (AC2/AC5).
144
201
  * - 5xx → `status: "failed"` + the server's message verbatim (AC3/AC5).
202
+ * - sign-in (`authProblem` on a status-0 result, or an answered 401) →
203
+ * `status: "blocked"`, `buffered: false`, `action` = the sign-in command in
204
+ * the form this machine can run (`hs login`, or `node "<abs>/bin/hs.mjs" login`),
205
+ * and a message
206
+ * that says plainly the words were NOT saved ([[HS-9R6S-N8VP-DIAG]]).
145
207
  */
146
208
  export function respondToCaptureWriteFailure(vaultRoot, args, failure) {
147
209
  const d = describeCaptureWriteFailure(failure);
@@ -161,6 +223,23 @@ export function respondToCaptureWriteFailure(vaultRoot, args, failure) {
161
223
  pending_sync: pending,
162
224
  };
163
225
  }
226
+ if (d.kind === "auth") {
227
+ // NOT buffered, and the payload must not imply otherwise ([[HS-9R6S-N8VP-DIAG]]).
228
+ return {
229
+ ok: false,
230
+ status: "blocked",
231
+ error: d.error,
232
+ http_status: d.http_status,
233
+ server_error: d.server_error_code,
234
+ console_error: d.server_message ?? d.server_error_code,
235
+ buffered: false,
236
+ stashed: [],
237
+ // The form that actually runs on this machine, never the bare `hs`
238
+ // spelling ([[HS-I6HT-EKYU-DIAG-every-message-tells-the-user-to-run-hs]]).
239
+ action: hsCommand("login"),
240
+ message: `This capture was not filed, and your words were not buffered. ${d.message}`,
241
+ };
242
+ }
164
243
  return {
165
244
  ok: false,
166
245
  status: d.kind === "client" ? "refused" : "failed",