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,325 @@
1
+ // Small cross-cutting helpers for tool handlers: spec-id guards, the design
2
+ // result wrapper, and the disk-landing measurement. Extracted from cli.mjs under
3
+ // [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
4
+
5
+ import { statSync } from 'node:fs';
6
+ import { relative } from 'node:path';
7
+ import {
8
+ collectOccupiedFamilyKeysFromVault,
9
+ fileKeyFromBasename,
10
+ requireResolvableSpecId,
11
+ } from './spec-id-lib.mjs';
12
+ import { resolveSpecFileByFileKey } from './vault-io.mjs';
13
+ import { getConfig } from './mcp-config.mjs';
14
+
15
+ // Ambiguity guard for mutating ops ([[HS-EPIC-6SNQ-dual-read-resolver-grace-period-for-pipe]]
16
+ // AC#3). A bare family token (`HS-P2TR`) names a whole shared-suffix family, so a
17
+ // mutating op given one would act on an arbitrary family member.
18
+ //
19
+ // Era-3 escape ([[HS-0QVQ-RHIB-TASK]]): under era 3 a spec's `id:` IS a bare
20
+ // `HS-<fileKey>` token, which the shape check alone cannot tell apart from a
21
+ // family token. Before rejecting, try resolving the token as a FILE KEY — a
22
+ // unique hit that is NOT also an occupied family key names exactly one spec
23
+ // and is safe to act on. A token that matches BOTH namespaces (the
24
+ // [[HS-SJOT-AEP1-DIAG]] cross-namespace collision) fails closed, naming both
25
+ // interpretations — never silently pick one.
26
+ //
27
+ // Cloud reliance ([[HS-05ZW-XHR4-DIAG-the-capture-dropped-it-warning-blamed]]):
28
+ // that file-key escape is a LOCAL `readdirSync` scan, and a cloud-filed spec has
29
+ // no `.md` on this machine at all (`landed.onDisk: false`). So the scan could not
30
+ // tell "genuinely ambiguous" apart from "this machine has no copy", and every
31
+ // cloud-filed spec's own freshly minted id was refused by the very next tool
32
+ // called on it. `classifyBareSpecId` now separates those two verdicts, and the
33
+ // async `resolveSpecIdForMutation` asks the console before rejecting a token
34
+ // that is merely unresolvable FROM HERE.
35
+
36
+ /** How long the console disambiguation lookup may take before it is abandoned. */
37
+ export const SPEC_ID_CONSOLE_LOOKUP_TIMEOUT_MS = 8000;
38
+
39
+ /**
40
+ * Local-only verdict on a bare `HS-XXXX` spec id. One implementation shared by
41
+ * the synchronous guard and the async, console-aware resolver — never forked.
42
+ *
43
+ * @returns {{ kind: 'unambiguous' }
44
+ * | { kind: 'local_file_key', path: string }
45
+ * | { kind: 'collision', error: { error: string, message: string } }
46
+ * | { kind: 'local_family', error: { error: string, message: string } }
47
+ * | { kind: 'unresolved_locally', error: { error: string, message: string } }}
48
+ */
49
+ export function classifyBareSpecId(vaultRoot, specId) {
50
+ const shapeError = requireResolvableSpecId(specId);
51
+ if (!shapeError) return { kind: 'unambiguous' };
52
+
53
+ const token = String(specId).trim().replace(/\.md$/i, '').replace(/^HS-/i, '').toUpperCase();
54
+ let byKey = null;
55
+ try {
56
+ byKey = resolveSpecFileByFileKey(vaultRoot, specId);
57
+ } catch {
58
+ byKey = null; // unreadable vault — the local scan could not answer
59
+ }
60
+ let isFamily = false;
61
+ try {
62
+ isFamily = collectOccupiedFamilyKeysFromVault(vaultRoot).has(token);
63
+ } catch {
64
+ isFamily = false;
65
+ }
66
+
67
+ if (byKey && isFamily) {
68
+ return {
69
+ kind: 'collision',
70
+ error: {
71
+ error: 'AMBIGUOUS_SPEC_ID',
72
+ message:
73
+ `"${specId}" matches BOTH the unique file key of ${byKey} AND the family key of other ` +
74
+ `specs (cross-namespace collision) — refusing to guess. Pass the full basename of the ` +
75
+ `spec you mean.`,
76
+ },
77
+ };
78
+ }
79
+ if (byKey) return { kind: 'local_file_key', path: byKey }; // clean era-3 file-key hit
80
+ if (isFamily) {
81
+ // PROVEN a family key against this vault, so the strong wording is earned.
82
+ return {
83
+ kind: 'local_family',
84
+ error: requireResolvableSpecId(specId, { confirmedFamily: true }),
85
+ };
86
+ }
87
+ // Neither namespace matched anything on disk. That is NOT proof of ambiguity —
88
+ // in cloud reliance it is the expected shape of a spec that lives only in Neon.
89
+ return { kind: 'unresolved_locally', error: shapeError };
90
+ }
91
+
92
+ /**
93
+ * Synchronous, local-only guard. Returns an error object to short-circuit on,
94
+ * or null when the id is safe to act on. Kept for callers (and tests) holding a
95
+ * vault root and no console; a tool handler should prefer
96
+ * {@link resolveSpecIdForMutation}, which additionally clears cloud-only ids.
97
+ */
98
+ export function bareTokenSpecIdGuard(vaultRoot, specId) {
99
+ const verdict = classifyBareSpecId(vaultRoot, specId);
100
+ return verdict.error ?? null;
101
+ }
102
+
103
+ function errorResponse(payload) {
104
+ return { isError: true, content: [{ type: 'text', text: JSON.stringify(payload) }] };
105
+ }
106
+
107
+ /** Local mirror of spec-id-lib's (unexported) id normalizer. */
108
+ function normalizeId(input) {
109
+ return String(input ?? '')
110
+ .trim()
111
+ .replace(/\.md$/i, '')
112
+ .toUpperCase();
113
+ }
114
+
115
+ /** `{ error, message }` for a token nothing could verify, naming WHY not. */
116
+ function unverifiedResponse(specId, detail) {
117
+ return requireResolvableSpecId(specId, { detail });
118
+ }
119
+
120
+ /** Lazily-bound console transport; injectable for tests, never imported eagerly. */
121
+ async function defaultConsoleFetch() {
122
+ const mod = await import('./console-transport.mjs');
123
+ return mod.consoleFetch;
124
+ }
125
+
126
+ /**
127
+ * Ask the console to resolve a bare token this machine cannot.
128
+ * `GET /api/specs/:id` resolves an era-3 file key server-side and answers with
129
+ * the full spec (including `basename`), or `404 { error: "ambiguous_id",
130
+ * candidates }` when the token really does name more than one spec.
131
+ *
132
+ * Never throws and never hangs: the transport carries an explicit timeout and
133
+ * every failure degrades to a rejection saying the id could not be VERIFIED
134
+ * from here. Silence is never upgraded into a claim about family keys.
135
+ */
136
+ async function resolveBareSpecIdViaConsole(specId, deps = {}) {
137
+ let fetcher = deps.consoleFetch;
138
+ if (!fetcher) {
139
+ try {
140
+ fetcher = await defaultConsoleFetch();
141
+ } catch {
142
+ return {
143
+ error: unverifiedResponse(
144
+ specId,
145
+ 'The console transport is unavailable in this process, so nothing was checked against the vault of record.',
146
+ ),
147
+ };
148
+ }
149
+ }
150
+
151
+ let res;
152
+ try {
153
+ res = await fetcher(`/api/specs/${encodeURIComponent(specId)}`, {
154
+ timeout: deps.timeoutMs ?? SPEC_ID_CONSOLE_LOOKUP_TIMEOUT_MS,
155
+ });
156
+ } catch (err) {
157
+ const why = err?.message ? String(err.message) : String(err);
158
+ return {
159
+ error: unverifiedResponse(
160
+ specId,
161
+ `The console lookup that would have resolved it failed (${why}).`,
162
+ ),
163
+ };
164
+ }
165
+
166
+ const body = res?.body ?? null;
167
+
168
+ if (res?.ok) {
169
+ const spec = body?.spec ?? null;
170
+ const basename =
171
+ typeof spec?.basename === 'string' && spec.basename.trim() ? spec.basename.trim() : null;
172
+ if (!basename) {
173
+ return {
174
+ error: unverifiedResponse(
175
+ specId,
176
+ 'The console answered without a basename, so the id could not be pinned to one spec.',
177
+ ),
178
+ };
179
+ }
180
+ // Do NOT trust a resolution that does not carry the token asked about. A
181
+ // live `heyspex_register_spec` dry run once resolved one spec's basename to
182
+ // an UNRELATED spec ([[HS-J6IE]]); this gate is why a server-side fuzzy
183
+ // match can never do that here.
184
+ const resolvedKey = fileKeyFromBasename(basename);
185
+ const token = String(specId).trim().replace(/^HS-/i, '').toUpperCase();
186
+ const idMatches = normalizeId(spec?.id ?? '') === normalizeId(specId);
187
+ if (resolvedKey?.toUpperCase() !== token && !idMatches) {
188
+ return {
189
+ error: {
190
+ error: 'AMBIGUOUS_SPEC_ID',
191
+ message:
192
+ `"${specId}" was answered by the console with "${basename}", whose file key does not ` +
193
+ `match the token asked about — refusing to act on a resolution that changed the id. ` +
194
+ `Pass the full basename of the spec you mean.`,
195
+ },
196
+ };
197
+ }
198
+ return { basename };
199
+ }
200
+
201
+ if (body?.error === 'ambiguous_id') {
202
+ const candidates = Array.isArray(body.candidates) ? body.candidates : [];
203
+ return {
204
+ error: {
205
+ error: 'AMBIGUOUS_SPEC_ID',
206
+ message:
207
+ `"${specId}" names ${candidates.length || 'more than one'} specs in the vault of record` +
208
+ (candidates.length ? ` (${candidates.join(', ')})` : '') +
209
+ ` — refusing to guess. Pass the full basename of the spec you mean.`,
210
+ },
211
+ };
212
+ }
213
+
214
+ if (res?.status === 404) {
215
+ return {
216
+ error: unverifiedResponse(
217
+ specId,
218
+ 'The vault of record has no spec under that id either (console answered 404).',
219
+ ),
220
+ };
221
+ }
222
+
223
+ const status =
224
+ res?.authProblem
225
+ || (res?.status === 0 || res?.status == null ? 'unreachable' : `HTTP ${res.status}`);
226
+ return {
227
+ error: unverifiedResponse(
228
+ specId,
229
+ `The console could not be asked (${status}), so nothing about this id was actually verified.`,
230
+ ),
231
+ };
232
+ }
233
+
234
+ /**
235
+ * THE shared entry point for every mutating tool handler that takes a
236
+ * `spec_id`. Resolves the id to something unambiguous, or returns the MCP error
237
+ * response to short-circuit on.
238
+ *
239
+ * - full basename / already-unambiguous id → passes straight through
240
+ * - bare token the LOCAL vault resolves to one file → passes through
241
+ * - bare token only the CONSOLE can resolve → cleared, and rewritten to the
242
+ * full basename so the caller acts on an unambiguous id
243
+ * - genuinely ambiguous (either namespace, locally or server-side) → refused
244
+ * - console unreachable / not signed in / 404 → refused, saying plainly that
245
+ * the id could not be verified from this machine and why
246
+ *
247
+ * The console is consulted ONLY when the local scan came back empty, so the
248
+ * happy path costs exactly what it did before.
249
+ *
250
+ * `deps` is the test seam and nothing else: `{ vaultRoot }` overrides the
251
+ * resolved config, `{ consoleFetch }` replaces the transport (so no suite ever
252
+ * touches the live network), `{ timeoutMs }` shortens the lookup budget.
253
+ *
254
+ * @returns {Promise<{ specId: string, resolvedVia?: 'console' } | { response: object }>}
255
+ */
256
+ export async function resolveSpecIdForMutation(specId, deps = {}) {
257
+ let verdict;
258
+ try {
259
+ verdict = classifyBareSpecId(deps.vaultRoot ?? getConfig().vaultRoot, specId);
260
+ } catch {
261
+ // Config unavailable — no local scan is possible, which is exactly the
262
+ // "could not answer from here" case, not proof of ambiguity.
263
+ const shapeError = requireResolvableSpecId(specId);
264
+ verdict = shapeError
265
+ ? { kind: 'unresolved_locally', error: shapeError }
266
+ : { kind: 'unambiguous' };
267
+ }
268
+
269
+ if (verdict.kind === 'unambiguous' || verdict.kind === 'local_file_key') {
270
+ return { specId: typeof specId === 'string' ? specId.trim() : specId };
271
+ }
272
+ if (verdict.kind !== 'unresolved_locally') return { response: errorResponse(verdict.error) };
273
+
274
+ const viaConsole = await resolveBareSpecIdViaConsole(specId, deps);
275
+ if (viaConsole.basename) return { specId: viaConsole.basename, resolvedVia: 'console' };
276
+ return { response: errorResponse(viaConsole.error ?? verdict.error) };
277
+ }
278
+
279
+ /**
280
+ * Back-compat face of {@link resolveSpecIdForMutation} for handlers that need
281
+ * only the veto and not the resolved id. Same single implementation.
282
+ */
283
+ export async function ambiguousSpecIdResponse(specId, deps = {}) {
284
+ const resolved = await resolveSpecIdForMutation(specId, deps);
285
+ return resolved.response ?? null;
286
+ }
287
+
288
+ /**
289
+ * Measure a direct-to-disk vault write ([[HS-IOJW-5YDH-DIAG]] sweep). Tools that
290
+ * call writeFileSync themselves cannot land in the cloud, but they still
291
+ * reported on the request rather than the result — this stats the file so the
292
+ * `landed` block means the bytes are really there. Never throws.
293
+ */
294
+ export function measureDiskLanding(vaultRoot, absPath) {
295
+ let onDisk = false;
296
+ let bytes = null;
297
+ try {
298
+ const st = statSync(absPath);
299
+ onDisk = st.isFile();
300
+ bytes = onDisk ? st.size : null;
301
+ } catch {
302
+ onDisk = false;
303
+ bytes = null;
304
+ }
305
+ return {
306
+ tier: 'disk',
307
+ reported_by: 'measured',
308
+ abs_path: absPath,
309
+ rel_path: vaultRoot ? relative(vaultRoot, absPath) : null,
310
+ on_disk: onDisk,
311
+ bytes,
312
+ };
313
+ }
314
+
315
+ export function wrapDesignResult(result) {
316
+ if (result && result.error) {
317
+ return {
318
+ isError: true,
319
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
320
+ };
321
+ }
322
+ return {
323
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
324
+ };
325
+ }