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,2813 @@
1
+ // The MCP tool catalog: every heyspex_* tool definition (TOOLS), plus the
2
+ // progressive-disclosure state and handler (heyspex_load_tools). Extracted from
3
+ // cli.mjs under [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]]
4
+ // (no behavior change).
5
+ //
6
+ // ─────────────────────────────────────────────────────────────
7
+ // Tool-surface reduction ([[HS-MTSA-EPIC]] / [[HS-LCRS-TASK]], dispositions in
8
+ // [[HS-OO5K-RES]]): the default tools/list exposes only the observed daily-loop
9
+ // core (15 keeps + the loader = 16 ≤ the epic's ≤20 target). Everything else is
10
+ // scope-deferred — DEFINITIONS STAY IN `TOOLS` (hidden, not deleted; tools/call
11
+ // dispatches by name regardless of visibility) pending the spec-scoped filter
12
+ // ([[HS-A4BY-EPIC]]). Escape hatch: HEYSPEX_TOOL_SURFACE=full lists everything
13
+ // (automation / dev-factory). Surface state lives in the PARENT server process —
14
+ // in MCP_SUBPROCESS_TOOLS mode the fresh subprocess returns the full list and
15
+ // the parent filters it, and heyspex_load_tools is intercepted parent-side.
16
+ // ─────────────────────────────────────────────────────────────
17
+
18
+ import { runFrontCliHint } from './run-front.mjs';
19
+ import { CLOSE_SESSION_THREAD_TYPES, PREFLIGHT_INTENTS } from './procedure-tools.mjs';
20
+ import { sendNotification } from './stdio-rpc.mjs';
21
+ import { DEFAULT_TOOL_BUDGET_BYTES } from './response-budget.mjs';
22
+
23
+ // Byte budget every READ tool declares ([[HS-4H8T-8KK5-TASK-every-mcp-read-tool-declares-a-byte]]):
24
+ // tool-dispatch.mjs enforces `budget.max_bytes` on the way out (see response-budget.mjs);
25
+ // `list_key` names the field a trim shortens when the tool's reply is a list.
26
+ // `tool-budget.test.mjs` walks the catalog and fails on a read-shaped tool without one.
27
+ const READ_TOOL_BUDGET = Object.freeze({ max_bytes: DEFAULT_TOOL_BUDGET_BYTES });
28
+ const listBudget = (list_key) => Object.freeze({ max_bytes: DEFAULT_TOOL_BUDGET_BYTES, list_key });
29
+
30
+ export const TOOLS = [
31
+ {
32
+ name: 'heyspex_validate_user_skills',
33
+ description:
34
+ 'Validate user-authored gateway skills under .heyspex/skills/user/<slug>/SKILL.md ([[HS-WEC3-UH11-EPIC-user-customizable-mcp-servers-and]]; design [[HS-P861-G9JR-RES-user-customizable-mcp-servers-and]]). Checks frontmatter integrity (name/description required; malformed tool_scope/requires_servers FAIL CLOSED — the skill is inert until it parses) and scope coverage (skills whose declared tool_scope no registered server provides are "orphaned" and stay inert). Scope matching is a deterministic exact-tag set operation, never semantic. Report-only: never edits, deletes, or auto-disables user content. Returns { skills, registry, orphans }; registry.available is false until HS-DCCP\'s server-registration table exists. Same core as `hs gateway validate` and `hs doctor --check-gateway`.',
35
+ inputSchema: {
36
+ type: 'object',
37
+ properties: {
38
+ skill: {
39
+ type: 'string',
40
+ description: 'Optional: validate only the skill with this directory slug. If omitted, validates every user skill.',
41
+ },
42
+ },
43
+ additionalProperties: false,
44
+ },
45
+ },
46
+ {
47
+ name: 'heyspex_validate_linkage',
48
+ description:
49
+ 'Validate spec linkage across the vault: detect orphan specs (milestones/epics/tasks not reachable from the roadmap), broken `[[wikilinks]]`, roadmap manifest entries pointing to missing files, and parent/child links that do not point back. Returns a structured report { valid, orphan_specs, broken_wikilinks, missing_manifest_refs, parent_child_mismatches, frozen_parent_child_mismatches, warnings, summary }. Reachability follows the manifest-v3 hierarchy: roadmap → milestones → epics (`milestone:`) → tasks (`parent:`). `frozen_parent_child_mismatches` is INFORMATIONAL, not vault debt: a mismatch where either side is `status: closed`/`superseded` cannot be repaired because that spec\'s `children:`/`epics:` roster is frozen historical record accurate to what was true at close (only the close-out change-log row, `status:`, `close_reason:` and `superseded_by:` may still be touched). Do NOT "fix" these — they are excluded from `valid` and from `summary.mismatches`, and counted in `summary.frozen_mismatches`.',
50
+ inputSchema: {
51
+ type: 'object',
52
+ properties: {
53
+ spec_id: {
54
+ type: 'string',
55
+ description: 'Optional: scope the report to a single spec (by id or basename). If omitted, validates the whole vault.',
56
+ },
57
+ include_warnings: {
58
+ type: 'boolean',
59
+ default: true,
60
+ description: 'Include non-critical warnings (e.g. a subject spec with no related[] links).',
61
+ },
62
+ },
63
+ additionalProperties: false,
64
+ },
65
+ },
66
+ {
67
+ name: 'heyspex_suggest_pillar',
68
+ description:
69
+ 'Rank the pillar taxonomy against a draft milestone/epic BEFORE filing it, so the `pillar:` is chosen from each pillar\'s written contract instead of guessed from its name ([[pillar-assignment-protocol]] §"The fit test"). Pass the draft\'s `name` (plus `summary`/`tags` when you have them). Returns { verdict, new_pillar_recommended, rationale, candidates[], other_pillars[] } where each candidate carries `belongs` / `excludes` bullets, a `score`, the charter lines that matched (`evidence`), and `excluded_by` when that pillar explicitly disclaims this work. READ the shortlisted contracts before choosing — picking by pillar name is the documented failure mode that mis-filed the command palette, the console rail and the entire scheduler into HS-PILR-architecture. `verdict: "none"` / `new_pillar_recommended: true` means no existing theme fits: draft a new-pillar proposal and present it to the OWNER with evidence and the closest existing alternative. This tool never writes, and an agent never creates a pillar without explicit owner approval.',
70
+ inputSchema: {
71
+ type: 'object',
72
+ properties: {
73
+ name: {
74
+ type: 'string',
75
+ description: 'The draft spec\'s title — the primary scoring signal.',
76
+ },
77
+ summary: {
78
+ type: 'string',
79
+ description: 'The draft spec\'s plain-language summary. Strongly improves ranking; pass it whenever you have authored one.',
80
+ },
81
+ tags: {
82
+ type: 'array',
83
+ items: { type: 'string' },
84
+ description: 'Optional tags to fold into the scored subject text.',
85
+ },
86
+ subject: {
87
+ type: 'string',
88
+ description: 'Optional freeform text to score instead of / in addition to name+summary (e.g. a research doc excerpt during plan ingest).',
89
+ },
90
+ shortlist: {
91
+ type: 'number',
92
+ description: 'How many ranked candidates to return with full charters (default 5, max 10). The rest come back as id+name under other_pillars.',
93
+ },
94
+ },
95
+ additionalProperties: false,
96
+ },
97
+ },
98
+ {
99
+ name: 'heyspex_edit_file',
100
+ description:
101
+ 'Safe file edit for parallel agents ([[HS-Y00G-EPIC]]). Acquires a file claim, writes the content, and releases the claim — all in one call. If the file is already claimed by another agent, returns CONFLICT with the claiming agent id and TTL. This is the stopgap edit primitive that prevents silent last-writer-wins data loss until the CRDT layer (Epic 2) ships. Use this INSTEAD of raw writeFileSync / edit_file when editing shared spec files.',
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ file_path: {
106
+ type: 'string',
107
+ description: 'Absolute path of the file to write.',
108
+ },
109
+ content: {
110
+ type: 'string',
111
+ description: 'The new file content to write.',
112
+ },
113
+ agent: {
114
+ type: 'string',
115
+ description: 'Agent display name (default: detected from env)',
116
+ },
117
+ session_id: {
118
+ type: 'string',
119
+ description: 'Session identifier (default: detected from env)',
120
+ },
121
+ working_spec_id: {
122
+ type: 'string',
123
+ description: 'The spec id the agent is working on (informational).',
124
+ },
125
+ },
126
+ required: ['file_path', 'content'],
127
+ additionalProperties: false,
128
+ },
129
+ },
130
+ {
131
+ name: 'heyspex_register_spec',
132
+ description:
133
+ 'Link a spec into the roadmap so it is reachable (the fix counterpart to heyspex_validate_linkage). What it edits depends on the spec kind, following the manifest-v3 hierarchy: a milestone is added to a roadmap\'s roadmap_manifest.milestones; an epic gets its `milestone:` field set AND is added to that milestone\'s epics[]; a task gets its `parent:` field set AND is added to that epic\'s children[]. Both sides are written, so it also repairs one-sided parent/child mismatches. Idempotent — re-running on an already-linked spec is a no-op success. Returns { success, spec_id, type, parent, already_registered, changes, validation }.',
134
+ inputSchema: {
135
+ type: 'object',
136
+ properties: {
137
+ spec_id: {
138
+ type: 'string',
139
+ description: 'The spec to register (id or basename), e.g. "HS-TASK-5K13".',
140
+ },
141
+ parent_id: {
142
+ type: 'string',
143
+ description:
144
+ 'Target parent: the milestone for an epic, the epic for a task, or the roadmap for a milestone. If omitted for an epic/task, the spec\'s own `milestone:`/`parent:` field is used (repairs a missing back-reference); for a milestone it defaults to roadmap_id.',
145
+ },
146
+ roadmap_id: {
147
+ type: 'string',
148
+ default: 'HS-ROADMAP-PRODUCT',
149
+ description: 'Roadmap to register a milestone into (milestones only).',
150
+ },
151
+ position: {
152
+ type: 'string',
153
+ description: "Where to insert in the parent's list: 'first', 'last' (default), 'after:<ref>', or 'before:<ref>'.",
154
+ },
155
+ dry_run: {
156
+ type: 'boolean',
157
+ default: false,
158
+ description: 'Plan the edits and return the change list without writing files or re-validating.',
159
+ },
160
+ },
161
+ required: ['spec_id'],
162
+ additionalProperties: false,
163
+ },
164
+ },
165
+ {
166
+ name: 'heyspex_next_on_deck',
167
+ budget: READ_TOOL_BUDGET,
168
+ description:
169
+ 'Returns the next task "on deck" from the HeySpex roadmap — the highest priority OPEN task in manifest order. This is what the agent should work on next, not what is already in progress. Response carries `user_prompt_markdown` (print verbatim) and `agent_dispatch` (decode as structured routing per specs/schemas/agent-dispatch.schema.json — never echo). The bundled `prompt_markdown` field is @deprecated; it now mirrors `user_prompt_markdown` (no agent prose). When the claim target has no `summary:`, the `author_summary_then_claim` phase names the write for THIS session: `heyspex_author_summary` (console PUT) in cloud reliance or for a Neon-only spec, the vault file via Edit only in local reliance ([[HS-T41O-NZ8S-DIAG]]).',
170
+ inputSchema: {
171
+ type: 'object',
172
+ properties: {
173
+ spec_id: {
174
+ type: 'string',
175
+ description: 'Specific MILESTONE/epic/task ID to fetch NEXT STEP from (e.g., "HS-IUQO", "HS-EPIC-6RKN"). The short id is all that is needed. A milestone id resolves to that milestone\'s next on-deck epic — this is the parallel-agents entry point (owner ruling 2026-08-10): each CLI session runs `/hs-next <its milestone>` to drive its own milestone, while bare `/hs-next` remains the scheduler-fed GLOBAL queue and may surface unrelated items (triage, other milestones) by design. A session hand-off that continues a specific milestone must therefore name it. Bypasses focus.yaml and manifest lookup.',
176
+ },
177
+ roadmap_id: {
178
+ type: 'string',
179
+ description: "Roadmap to query (default: env HEYSPEX_ROADMAP_ID or 'HS-ROADMAP-PRODUCT')",
180
+ },
181
+ include_horizon: {
182
+ type: 'boolean',
183
+ default: false,
184
+ description: 'Include horizon backlog items if no scheduled tasks remain',
185
+ },
186
+ ignore_focus: {
187
+ type: 'boolean',
188
+ default: false,
189
+ description: 'Skip .heyspex/focus.yaml override and use manifest traversal',
190
+ },
191
+ persist_plan_ingest: {
192
+ type: 'boolean',
193
+ default: false,
194
+ description:
195
+ 'When external .plan.md files resolve but the spec lacks ## Canonical implementation plan (HeySpex), write ingested content back into the spec file',
196
+ },
197
+ summary: {
198
+ type: 'boolean',
199
+ default: false,
200
+ description:
201
+ 'Lightweight mode — return only the claim-target banner + alignment state (skip prompt_markdown body, WIP block, plan hydration, conflict scan, and design context). Used by the /hs-next slash command for a fast first-response before the user authorizes the claim. Re-call without summary after claim to get the full execution payload.',
202
+ },
203
+ auto_claim: {
204
+ type: 'boolean',
205
+ default: false,
206
+ description:
207
+ 'Skip the yes/proceed/confirm handshake and claim immediately. Set automatically when front is autonomous (/hs-next auto, /hs-next-auto) or when the console injects --auto-claim on launch.',
208
+ },
209
+ front: {
210
+ type: 'string',
211
+ description:
212
+ `What's Next run front — handheld (default: drive one on-deck task), assist/assisted (dispatcher, human on the loop), auto/autonomous (unattended dispatcher). Aliases: ${runFrontCliHint()}.`,
213
+ },
214
+ },
215
+ additionalProperties: false,
216
+ },
217
+ },
218
+ {
219
+ name: 'heyspex_author_summary',
220
+ description:
221
+ 'Write a spec\'s plain-language `summary:` frontmatter field where this session\'s source of record lives ([[HS-T41O-NZ8S-DIAG]]) — the completion step of the /hs-next `author_summary_then_claim` phase. Cloud vault-write mode (prod, or dev-mode cloud reliance): GET /api/specs/:id, set `summary`, PUT /api/specs/:id — the Neon row is updated and nothing touches this machine\'s disk. Local mode: upserts the `summary: |` block scalar into the vault file\'s frontmatter. Only `summary` changes either way; every other field and the body are preserved. Pass the PROSE only (no `summary: |` YAML wrapper) authored per spec-summary-authoring (heyspex_get_procedure({ name: "spec-summary-authoring" })); the ≤160-char opening sentence / ≤400-char total targets come back as `warnings`, never a refusal. Then re-run heyspex_next_on_deck { summary: true } to reach the claim handshake.',
222
+ inputSchema: {
223
+ type: 'object',
224
+ properties: {
225
+ spec_id: {
226
+ type: 'string',
227
+ description: 'Spec whose `summary:` to write — use `claim_target.spec_id` from the brief (short id or full basename).',
228
+ },
229
+ summary: {
230
+ type: 'string',
231
+ description:
232
+ 'The authored summary as plain prose: ONE standalone opening sentence (target ≤160 chars) that says what this spec delivers and the parent goal it serves, optionally followed by a sentence or two of context (target ≤400 chars total). No YAML, no `summary:` prefix.',
233
+ },
234
+ },
235
+ required: ['spec_id', 'summary'],
236
+ additionalProperties: false,
237
+ },
238
+ },
239
+ {
240
+ name: 'heyspex_next_review',
241
+ budget: READ_TOOL_BUDGET,
242
+ description:
243
+ 'Returns the next review-ready spec from the HeySpex review queue — parent body + HS-REV companion + reviewer prompt markdown. Mirrors heyspex_next_on_deck but filters to status: in_review specs that have an HS-REV-<suffix>-*.md companion in specs/reviews/. Honors `.heyspex/review-focus.yaml` pin when present.',
244
+ inputSchema: {
245
+ type: 'object',
246
+ properties: {
247
+ spec_id: {
248
+ type: 'string',
249
+ description:
250
+ 'Specific spec id to fetch review context for (e.g., "HS-TASK-RVDC"). Bypasses .heyspex/review-focus.yaml pin and manifest lookup. Errors if the spec is not in_review or has no HS-REV companion.',
251
+ },
252
+ roadmap_id: {
253
+ type: 'string',
254
+ description: "Roadmap to query (default: env HEYSPEX_ROADMAP_ID or 'HS-ROADMAP-PRODUCT')",
255
+ },
256
+ ignore_focus: {
257
+ type: 'boolean',
258
+ default: false,
259
+ description: 'Skip .heyspex/review-focus.yaml override and use manifest/folder-scan traversal',
260
+ },
261
+ },
262
+ additionalProperties: false,
263
+ },
264
+ },
265
+ {
266
+ name: 'heyspex_next_resolution',
267
+ budget: READ_TOOL_BUDGET,
268
+ description:
269
+ 'Returns the top open resolution item from the HeySpex Resolution lane + a fix-brief prompt_markdown — conflicts, dangling refs, cycles, prerequisites, validation/stale-index issues, ordered with gating/errors first. The agent face of the Resolution surface; ordered consistently with GET /api/resolution/next and honors the Neon focus pin. This is the surviving /hs-resolve (it never type-checks the target, so it no longer errors "Not an Epic" on a milestone). Reads console over HTTP — start the dev server if it is unreachable.',
270
+ inputSchema: {
271
+ type: 'object',
272
+ properties: {
273
+ item_key: {
274
+ type: 'string',
275
+ description:
276
+ 'Specific resolution item key (e.g. "dangling_ref:hs-epic-x:hs-task-y") to fetch the fix brief for. Bypasses the focus pin.',
277
+ },
278
+ ignore_focus: {
279
+ type: 'boolean',
280
+ default: false,
281
+ description: 'Skip the Neon resolution focus pin and lead with the top-ranked open item.',
282
+ },
283
+ scope: {
284
+ type: 'string',
285
+ description:
286
+ 'Deck-aware: restrict detection to one spec id (e.g. the on-deck claim-target epic) so its gating prerequisite surfaces. Mirrors GET /api/resolution?scope=.',
287
+ },
288
+ },
289
+ additionalProperties: false,
290
+ },
291
+ },
292
+ {
293
+ name: 'heyspex_save_research',
294
+ description: 'Saves research findings to specs/research/ as HS-RES-XXXX-<slug>.md (random alphanumeric ID per [[vault-id-assignment-protocol]]). Use after web search or exploration. GATED ([[HS-K0ZP]]): refuses with PREFLIGHT_NOT_RUN unless research-capture-protocol was consulted this session -- heyspex_preflight({ intent: "research" }) (or file_research / promote_research, or heyspex_get_procedure on the protocol) first. The same rule gates WebSearch / WebFetch at PreToolUse. Owner-only break-glass: HEYSPEX_RESEARCH_PROTOCOL_GATE_OFF=1.',
295
+ inputSchema: {
296
+ type: 'object',
297
+ properties: {
298
+ title: {
299
+ type: 'string',
300
+ description: 'Descriptive title for the research (e.g., "Agent-Human Testing Workflow Research")',
301
+ },
302
+ summary: {
303
+ type: 'string',
304
+ description: 'Executive summary of research findings (2-5 sentences)',
305
+ },
306
+ content: {
307
+ type: 'string',
308
+ description: 'Full markdown content of the research (sections, tables, findings)',
309
+ },
310
+ tags: {
311
+ type: 'array',
312
+ items: { type: 'string' },
313
+ description: 'Tags for categorization (e.g., ["testing", "ux", "workflow"])',
314
+ },
315
+ related: {
316
+ type: 'array',
317
+ items: { type: 'string' },
318
+ description: 'Related spec IDs as wikilinks (e.g., ["HS-EPIC-0033", "HS-TASK-FLX9"])',
319
+ },
320
+ sources: {
321
+ type: 'array',
322
+ items: {
323
+ type: 'object',
324
+ properties: {
325
+ title: { type: 'string' },
326
+ url: { type: 'string' },
327
+ description: { type: 'string' },
328
+ },
329
+ },
330
+ description: 'Source URLs with titles and descriptions',
331
+ },
332
+ research_type: {
333
+ type: 'string',
334
+ enum: ['new-feature', 'enhancement', 'diagnose', 'code-audit', 'reference'],
335
+ description:
336
+ 'Declared purpose of this research doc ([[entity-model]] §research). Defaults to "reference" when omitted, with a non-blocking `warning: "RESEARCH_TYPE_MISSING"` returned on the response — the save still succeeds ([[HS-MVC1-TASK-deterministic-research-type-assist-sugge]]). This tool has no source capture to derive a kind-based suggestion from (unlike heyspex_promote_capture, see research-type-suggest.mjs) — pass the value explicitly to avoid the warning.',
337
+ },
338
+ scope: {
339
+ type: 'string',
340
+ enum: ['internal', 'codebase', 'external', 'all'],
341
+ description:
342
+ 'How broad a search context this research was run at ([[entity-model]] §research). Defaults to "internal" when omitted. Not required — a consistency fix ([[HS-NGD1-TASK-consolidate-and-fix-the-research-scope-field]]) so every research-creation path writes the same enum + default.',
343
+ },
344
+ },
345
+ required: ['title', 'summary', 'content'],
346
+ additionalProperties: false,
347
+ },
348
+ },
349
+ {
350
+ name: 'heyspex_push_plan',
351
+ description:
352
+ 'Write an epic plan into the HeySpex vault (specs/epics only — `children: []`). Ingests external Cursor/proprietary .plan.md files into ## Canonical implementation plan (HeySpex) on the spec. Requires `summary` at epic filing time per spec-summary-authoring. Tasks are NOT filed here — break down just-in-time via `/hs-next` + `heyspex_file_spec`. Use hs-push / hs-push-next; on_deck: true for focus + optional NEXT STEP override.',
353
+ inputSchema: {
354
+ type: 'object',
355
+ properties: {
356
+ epic_name: {
357
+ type: 'string',
358
+ description: 'Human title for the epic (required)',
359
+ },
360
+ summary: {
361
+ type: 'string',
362
+ description:
363
+ 'Required plain-language frontmatter summary for the epic at plan-ingest time (spec-summary-authoring).',
364
+ },
365
+ plan_markdown: {
366
+ type: 'string',
367
+ description: 'Epic body markdown (goal, phases, acceptance criteria)',
368
+ },
369
+ implementation_prompt: {
370
+ type: 'string',
371
+ description:
372
+ 'Ready-to-paste agent prompt stored in epic NEXT STEP block (required when on_deck is true)',
373
+ },
374
+ source_plan_paths: {
375
+ type: 'array',
376
+ items: { type: 'string' },
377
+ description:
378
+ 'Absolute or repo-relative paths to .plan.md files to ingest (e.g. Cursor ~/.cursor/plans/foo.plan.md). Also auto-detects references inside plan_markdown.',
379
+ },
380
+ children: {
381
+ type: 'array',
382
+ description:
383
+ 'Rejected — heyspex_push_plan files the epic with `children: []`. File tasks via `/hs-next` breakdown + heyspex_file_spec.',
384
+ items: {
385
+ type: 'object',
386
+ properties: {
387
+ name: { type: 'string' },
388
+ body: { type: 'string' },
389
+ summary: { type: 'string' },
390
+ },
391
+ required: ['name'],
392
+ },
393
+ },
394
+ on_deck: {
395
+ type: 'boolean',
396
+ default: false,
397
+ description:
398
+ 'hs-push-next: set epic in_progress, write .heyspex/focus.yaml, add NEXT STEP block',
399
+ },
400
+ focus_reason: {
401
+ type: 'string',
402
+ description: 'Optional reason string stored in focus.yaml',
403
+ },
404
+ kind: {
405
+ type: 'string',
406
+ enum: ['foundation', 'feature', 'polish', 'infra'],
407
+ description: 'Scheduler kind classification for the epic (default: feature). See [[HS-DEC-deterministic-priority-scheduler]].',
408
+ },
409
+ effort: {
410
+ type: 'number',
411
+ enum: [1, 2, 3, 5],
412
+ description: 'Scheduler effort estimate in t-shirt sizes (default: 3 = M).',
413
+ },
414
+ tags: {
415
+ type: 'array',
416
+ items: { type: 'string' },
417
+ description: 'Epic tags (default includes epic)',
418
+ },
419
+ related: {
420
+ type: 'array',
421
+ items: { type: 'string' },
422
+ description: 'Wikilinks to related protocols/specs for conflict detection (e.g., ["companion-orb-ui-spec", "companion-ui-design-spec"])',
423
+ },
424
+ skip_conflict_check: {
425
+ type: 'boolean',
426
+ default: false,
427
+ description: 'Force the push even when behavioral conflicts are detected against linked specs. When on_deck: true, conflicts block the push by default (no files written, no focus pinned) — pass true to bypass (a conflict_override row should be recorded via the console Dismiss-with-reason action after landing).',
428
+ },
429
+ milestone: {
430
+ type: 'string',
431
+ description:
432
+ 'Wikilink to the parent HS-MIL-* milestone this epic belongs to (e.g. "[[HS-MIL-HMNJ-status-and-roadmap-surface-refactor]]" or "HS-MIL-HMNJ-..."). REQUIRED per [[HS-TASK-T5PX]]. The milestone file must exist under specs/milestones/; this tool mirror-updates its `epics:` array.',
433
+ },
434
+ },
435
+ required: ['epic_name', 'milestone', 'summary'],
436
+ additionalProperties: false,
437
+ },
438
+ },
439
+ {
440
+ name: 'heyspex_recall_memories',
441
+ budget: listBudget('memories'),
442
+ description: 'Retrieves project-level memories (lessons, decisions, references, ui-patterns) that the AI should know about. Call this at the start of a session or when working on a specific task to get relevant context. Memories help avoid repeating mistakes and understand past decisions.',
443
+ inputSchema: {
444
+ type: 'object',
445
+ properties: {
446
+ category: {
447
+ type: 'string',
448
+ enum: ['all', 'lessons', 'decisions', 'references', 'ui-patterns'],
449
+ default: 'all',
450
+ description: 'Filter by memory category (default: all)',
451
+ },
452
+ related_to: {
453
+ type: 'string',
454
+ description: 'Filter to memories related to a specific spec ID (e.g., "HS-EPIC-0033")',
455
+ },
456
+ tags: {
457
+ type: 'array',
458
+ items: { type: 'string' },
459
+ description: 'Filter by tags (e.g., ["testing", "mcp"])',
460
+ },
461
+ limit: {
462
+ type: 'number',
463
+ default: 10,
464
+ description: 'Page size (default: 10, max: 30). The response is ONE page under `memories` plus `next_cursor`, fitted to an 8 KB byte budget — never the whole memory set, never a total count ([[HS-E66W-8KK5-TASK-mcp-list-tools-page-with-a-cursor-and-a]]).',
465
+ },
466
+ after: {
467
+ type: 'string',
468
+ description: 'Opaque cursor: the `next_cursor` value of the prior page. Omit for page 1. A page whose `next_cursor` is null was the last one.',
469
+ },
470
+ compact: {
471
+ type: 'boolean',
472
+ default: true,
473
+ description: 'Return compact summaries only (default: true). Set false for full body preview.',
474
+ },
475
+ },
476
+ additionalProperties: false,
477
+ },
478
+ },
479
+ {
480
+ name: 'heyspex_backfill_epic',
481
+ description:
482
+ 'Retroactively create an epic + child tasks for completed work ("backfill this completed work into the vault"). All specs are created with status: in_review and a review doc is auto-generated. Use /hs-backfill for the full workflow. (Renamed from heyspex_ship_epic / hs-ship 2026-06-19 so the word "ship" belongs to the Shipped surface — [[HS-TASK-1ZQF]].)',
483
+ inputSchema: {
484
+ type: 'object',
485
+ properties: {
486
+ epic_name: {
487
+ type: 'string',
488
+ description: 'Human title for the epic (required)',
489
+ },
490
+ summary: {
491
+ type: 'string',
492
+ description:
493
+ 'Required plain-language frontmatter summary for the epic (spec-summary-authoring). Also used in the review doc.',
494
+ },
495
+ plan_markdown: {
496
+ type: 'string',
497
+ description: 'Epic body markdown (what was done, acceptance criteria met)',
498
+ },
499
+ children: {
500
+ type: 'array',
501
+ description: 'Child tasks that were completed — each requires summary',
502
+ items: {
503
+ type: 'object',
504
+ properties: {
505
+ name: { type: 'string' },
506
+ summary: { type: 'string' },
507
+ },
508
+ required: ['name', 'summary'],
509
+ },
510
+ },
511
+ files_changed: {
512
+ type: 'array',
513
+ items: { type: 'string' },
514
+ description: 'List of files that were modified (for review doc)',
515
+ },
516
+ kind: {
517
+ type: 'string',
518
+ enum: ['foundation', 'feature', 'polish', 'infra'],
519
+ description: 'Scheduler kind classification for the epic (default: feature). See [[HS-DEC-deterministic-priority-scheduler]].',
520
+ },
521
+ effort: {
522
+ type: 'number',
523
+ enum: [1, 2, 3, 5],
524
+ description: 'Scheduler effort estimate in t-shirt sizes (default: 3 = M).',
525
+ },
526
+ tags: {
527
+ type: 'array',
528
+ items: { type: 'string' },
529
+ description: 'Tags for the epic',
530
+ },
531
+ related: {
532
+ type: 'array',
533
+ items: { type: 'string' },
534
+ description: 'Wikilinks to related specs',
535
+ },
536
+ pillar: {
537
+ type: 'string',
538
+ description: 'Optional `[[HS-PILR-<slug>]]` for the backfilled epic. DEFAULTS to the parent milestone\'s authored pillar, which is right for most backfills; pass it explicitly only when the epic\'s own subject belongs to a different perpetual theme than its milestone does. Choose against each pillar\'s written charter (heyspex_suggest_pillar), never by pillar name ([[pillar-assignment-protocol]]). If neither this nor the milestone resolves, the call fails PILLAR_REQUIRED instead of writing an epic that fails the vault gate.',
539
+ },
540
+ roadmap_id: {
541
+ type: 'string',
542
+ description: 'Roadmap ID to register in (default: HS-ROADMAP-PRODUCT)',
543
+ default: 'HS-ROADMAP-PRODUCT',
544
+ },
545
+ roadmap_phase: {
546
+ type: 'string',
547
+ description: 'Phase ID to add epic to (default: P0). NOTE: HS-ROADMAP-PRODUCT is on manifest v3, which replaced `phases:` (P0–P7) with a flat `milestones:` array — so phase registration no-ops there and returns `roadmap_registration.success: false` with "Phase P0 not found". That is expected and harmless: a backfilled epic is reachable through its parent milestone. Pass skip_roadmap_registration: true to silence it.',
548
+ default: 'P0',
549
+ },
550
+ skip_roadmap_registration: {
551
+ type: 'boolean',
552
+ description: 'Skip adding epic to roadmap manifest (default: false)',
553
+ default: false,
554
+ },
555
+ milestone: {
556
+ type: 'string',
557
+ description:
558
+ 'Wikilink to the parent HS-MIL-* milestone this epic belongs to (e.g. "[[HS-MIL-HMNJ-status-and-roadmap-surface-refactor]]" or "HS-MIL-HMNJ-..."). REQUIRED per [[HS-TASK-T5PX]]. The milestone file must exist under specs/milestones/; this tool mirror-updates its `epics:` array.',
559
+ },
560
+ },
561
+ required: ['epic_name', 'milestone', 'summary'],
562
+ additionalProperties: false,
563
+ },
564
+ },
565
+ {
566
+ name: 'heyspex_active_capture',
567
+ description:
568
+ 'Returns the capture on deck. Auto-creates and pins a fresh HS-CAP-* when none exists (agent-first capture). Also works after console + New → Capture or heyspex_pin_capture. Use with /hs-capture.\n\nMulti-agent warning ([[HS-1C4L-9VZV-DIAG-multi-agent-capture-focus-pin-collision]]): with no spec_id, this attaches to the most-recently-active PENDING capture for the owner — which may belong to a DIFFERENT concurrent session (another agent window, the console, or the companion app), not the one you are about to start. If you are opening a brand-new topic (not resuming a capture your OWN session already opened this conversation), pass force_new: true to guarantee a fresh capture instead of risking attachment to someone else\'s in-flight one.',
569
+ inputSchema: {
570
+ type: 'object',
571
+ properties: {
572
+ spec_id: {
573
+ type: 'string',
574
+ description: 'Optional capture ID; default is .heyspex/capture-focus.yaml pin',
575
+ },
576
+ force_new: {
577
+ type: 'boolean',
578
+ description:
579
+ 'Bypass pending-session attach/reuse entirely and unconditionally mint a brand-new capture session, even if another pending session exists for this owner. Use this when starting a genuinely new topic in a concurrent session, to avoid colliding with another live agent/console/companion session\'s in-flight capture ([[HS-1C4L-9VZV-DIAG-multi-agent-capture-focus-pin-collision]]). Ignored when spec_id is also passed.',
580
+ },
581
+ },
582
+ additionalProperties: false,
583
+ },
584
+ },
585
+ {
586
+ name: 'heyspex_update_capture',
587
+ description:
588
+ 'Capture to the on-deck capture: append verbatim user_quote, optional agent_note, author core_idea + summary (the zero-confirm baseline pass). agent_expansion (the `## Expanded Concept` deep-dive) is auto-written the moment the agent grounds the idea — grounding IS expansion, so write it immediately with no user confirmation. No epics/tasks at capture stage.\n\nMulti-beat protocol: separate calls are supported on the same spec_id across the capture flow (user_quote → core_idea → summary → agent_expansion) — the materialized spec file is re-stamped from the merged buffer on each save. `concept` is a deprecated alias for `summary`.\n\nError classes (per HS-EPIC-CPMR, corrected by HS-K27H-DIAG):\n - CONSOLE_UNREACHABLE (status 0 — genuine transport failure: ECONNREFUSED/DNS/timeout/abort): call stashes verbatim input to the offline buffer; next reachable call replays. The ONLY class that buffers.\n - CAPTURE_CONFLICT (4xx, e.g. abandoned session, invalid body): the server ANSWERED and rejected the write — payload is `status: "refused"` with `http_status` + `server_error` + `console_error` (verbatim server message); NOT stashed, NOT retried. Fix the request or start a new capture.\n - PATCH_FAILED (5xx): the server ANSWERED and errored applying the write — payload is `status: "failed"` with the verbatim server message; NOT stashed, NOT retried (the console is NOT down; do not tell the user their words are buffered — they are not).\n - CONSOLE_LOCAL_URL_IN_CLOUD_MODE (status 0, sign-in — HS-9R6S-N8VP-DIAG): HeySpex is in cloud mode but this machine is pointed at a local console, so the call never had a chance to go out. Payload is `status: "blocked"` with `buffered: false` and an `action` naming the sign-in command in the form this machine can run. NOT stashed, NOT retried. Do not say the words are buffered, and do not start a local dev server — cloud is the normal mode.\n - CONSOLE_NOT_SIGNED_IN (no bearer token on this machine, or an answered HTTP 401 — HS-9R6S-N8VP-DIAG): the machine is not signed in, so the write was refused. Same `status: "blocked"` / `buffered: false` payload with an `action` naming the sign-in command. NOT stashed, NOT retried. The server is NOT down — run the command in `action`, then call again.',
589
+ inputSchema: {
590
+ type: 'object',
591
+ properties: {
592
+ spec_id: { type: 'string', description: 'Capture ID (default: pinned capture)' },
593
+ core_idea: {
594
+ type: 'string',
595
+ description: 'Revised core statement — prefer user phrasing, ≤500 chars',
596
+ },
597
+ summary: {
598
+ type: 'string',
599
+ description:
600
+ 'AI brief summary — authors the `## Summary` layer (one paragraph, ≤800 chars soft). Part of the zero-confirm baseline pass alongside core_idea (HS-EPIC-OED2).',
601
+ },
602
+ concept: {
603
+ type: 'string',
604
+ description: '@deprecated alias for `summary` (renamed concept→summary, HS-EPIC-OED2).',
605
+ },
606
+ user_quote: {
607
+ type: 'string',
608
+ description: 'Verbatim excerpt from the human in this turn (appended to Notes)',
609
+ },
610
+ agent_note: {
611
+ type: 'string',
612
+ description: 'Brief agent context using user words (appended to Notes)',
613
+ },
614
+ agent_expansion: {
615
+ type: 'string',
616
+ description:
617
+ 'Expanded capture prose — auto-written the moment the agent grounds the idea (grounding IS expansion; no user confirmation needed); written as the `## Expanded Concept` deep-dive section below Notes (writing it flips capture_status raw → expanded). Not Core/Summary/Notes.',
618
+ },
619
+ example: {
620
+ type: 'string',
621
+ description:
622
+ 'Concrete example illustrating the idea — written as the `## Example` section between Notes and Expanded Concept. Either a real verbatim example (label it real, e.g. "Real example:") or an AI-authored illustrative one (label it as such, e.g. "Illustrative example:"). Persisted across updates (HS-PR20-EPIC).',
623
+ },
624
+ user_stories: {
625
+ type: 'string',
626
+ description:
627
+ 'User stories — written as the `## User Stories` section between Example and Expanded Concept. Optional but highly encouraged: 2-4 "As a <role>, I want <capability>, so that <outcome>." statements (format encouraged, not enforced). Roles must be real actors from the capture (owner, agent, user, reviewer — never invented personas); ground stories ONLY in what the capture says, never invent features. Preserve a user-provided story verbatim (format-wrap, never substitute); otherwise author at expand time alongside Core Idea/Summary, prompting the user for a missing role/capability/outcome when needed. Persisted across updates.',
628
+ },
629
+ kind: {
630
+ type: 'string',
631
+ enum: ['idea', 'bug-fix', 'clip', 'reference', 'question'],
632
+ description:
633
+ 'Re-classify the capture kind — ONLY after the user confirms ([[HS-RZQO-TASK]] elicitation: suggest, never auto-apply). Suggest when the content signals one: error/stacktrace/bug/broken/regression language → bug-fix (unlocks the promote-to-diagnose exit); a bare URL / link-only body → clip; a pointer to a doc/resource to remember → reference; an open question to revisit → question; otherwise leave as idea. Most captures wrongly sit at the default idea — offer the suggestion in your reply ("this reads like a bug-fix capture — reclassify?") and pass this arg only on the user\'s yes. spec-import is importer-stamped and never agent-set. Also the fix for the promote sufficiency gate\'s "kind" missing-element ([[HS-NR43-DIAG-CAIO]]): pass this instead of force_promote when the gate reports kind as missing.',
634
+ },
635
+ done_when: {
636
+ type: 'string',
637
+ description:
638
+ 'Author the `## Done When` checklist — markdown checklist lines (e.g. "- [ ] criterion") stating how the idea will be verified done. Written between User Stories and Expanded Concept. This is the fix for the promote sufficiency gate\'s "done-criteria" missing-element ([[HS-NR43-DIAG-CAIO]]): pass this instead of force_promote when the gate reports done-criteria as missing — elicit the criteria from the user rather than inventing them.',
639
+ },
640
+ jtbd: {
641
+ type: 'string',
642
+ description:
643
+ 'The JTBD (job-to-be-done) — the underlying job the user is trying to get done, not the mechanism they happened to describe. Written as the `## JTBD (job-to-be-done)` section between User Stories and Done When. Authored by the `/hs-capture` enhancement pass per the 2026-08-23 owner ruling; persisted across updates (same contract as `example`/`user_stories`/`done_when`). Replaces the interim `JTBD (job-to-be-done):` leading block inside `## Expanded Concept`.',
644
+ },
645
+ },
646
+ additionalProperties: false,
647
+ },
648
+ },
649
+ {
650
+ name: 'heyspex_pin_capture',
651
+ description:
652
+ 'Pin an HS-CAP-* spec as capture-on-deck (.heyspex/capture-focus.yaml) for /hs-capture.',
653
+ inputSchema: {
654
+ type: 'object',
655
+ properties: {
656
+ spec_id: { type: 'string', description: 'Capture spec id e.g. HS-CAP-1JJF' },
657
+ reason: { type: 'string', description: 'Optional pin reason' },
658
+ },
659
+ required: ['spec_id'],
660
+ additionalProperties: false,
661
+ },
662
+ },
663
+ {
664
+ name: 'heyspex_active_research',
665
+ description:
666
+ 'Returns the research-on-deck pin (.heyspex/research-focus.yaml) written by the console doc-toolbar Promote button. Mirrors heyspex_active_capture. Use with /hs-res.',
667
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
668
+ },
669
+ {
670
+ name: 'heyspex_complete_research',
671
+ description:
672
+ 'Finalize a /hs-res session: atomically write specs/research/HS-RES-<id>-<slug>.md, set source idea promoted_to: "[[HS-RES-…]]", and clear .heyspex/research-focus.yaml. source_idea_id + scope come from the pin unless explicitly overridden.',
673
+ inputSchema: {
674
+ type: 'object',
675
+ properties: {
676
+ title: { type: 'string', description: 'Descriptive research title (required)' },
677
+ executive_summary: {
678
+ type: 'string',
679
+ description: 'Executive summary, 3–5 sentences (required if `summary` not provided)',
680
+ },
681
+ summary: {
682
+ type: 'string',
683
+ description: 'Alias for executive_summary (back-compat with heyspex_save_research callers)',
684
+ },
685
+ content: {
686
+ type: 'string',
687
+ description: 'Findings body markdown (required if `findings` not provided)',
688
+ },
689
+ findings: {
690
+ type: 'string',
691
+ description: 'Alias for content',
692
+ },
693
+ sources: {
694
+ type: 'array',
695
+ items: {
696
+ type: 'object',
697
+ properties: {
698
+ title: { type: 'string' },
699
+ url: { type: 'string' },
700
+ description: { type: 'string' },
701
+ },
702
+ },
703
+ description: 'Source URLs with titles + descriptions',
704
+ },
705
+ tags: {
706
+ type: 'array',
707
+ items: { type: 'string' },
708
+ description: 'Tags (default: ["research"])',
709
+ },
710
+ related: {
711
+ type: 'array',
712
+ items: { type: 'string' },
713
+ description: 'Related spec wikilinks (source idea is added automatically)',
714
+ },
715
+ source_idea_id: {
716
+ type: 'string',
717
+ description: 'Override source idea id (default: read from .heyspex/research-focus.yaml)',
718
+ },
719
+ scope: {
720
+ type: 'string',
721
+ enum: ['internal', 'codebase', 'external', 'all'],
722
+ description: 'Override scope (default: read from pin)',
723
+ },
724
+ owner: { type: 'string', description: 'Owner (default: pwher)' },
725
+ },
726
+ required: ['title'],
727
+ additionalProperties: false,
728
+ },
729
+ },
730
+ {
731
+ name: 'heyspex_ground_plan',
732
+ budget: READ_TOOL_BUDGET,
733
+ description:
734
+ 'Pre-plan grounding (lexical wide-recall + conflict/drift detection, read-only). Use /hs-ground. PREFER report:true — returns a single bounded ≤max_tokens markdown report (report_markdown) with six sections (Scope · Relevant specs · Relevant code · Graph hints · Conflicts · Gaps) that you paste verbatim and act on WITHOUT reading more files. The legacy compact/full modes return discovery arrays + the six-phase scaffold for the Cursor skill.',
735
+ inputSchema: {
736
+ type: 'object',
737
+ properties: {
738
+ topic: {
739
+ type: 'string',
740
+ description: 'Short title of the feature or request (required)',
741
+ },
742
+ summary: {
743
+ type: 'string',
744
+ description: 'Longer description from chat or draft plan',
745
+ },
746
+ tags: {
747
+ type: 'array',
748
+ items: { type: 'string' },
749
+ description: 'Explicit tags to boost discovery (e.g. ["console", "mcp"])',
750
+ },
751
+ areas: {
752
+ type: 'array',
753
+ items: { type: 'string' },
754
+ description: 'Areas: console, companion, mcp, vault, indexer',
755
+ },
756
+ include_codebase_hints: {
757
+ type: 'boolean',
758
+ default: true,
759
+ description: 'Include grep_queries, path_globs',
760
+ },
761
+ limit: {
762
+ type: 'number',
763
+ default: 8,
764
+ description: 'Max discovered specs per bucket (default 8, max 20)',
765
+ },
766
+ check_conflicts: {
767
+ type: 'boolean',
768
+ default: true,
769
+ description:
770
+ 'Run conflict/drift analysis (focused epic, draft vs protocols, cross-spec drift). Same detector as console /api/specs/:id/conflicts',
771
+ },
772
+ compact: {
773
+ type: 'boolean',
774
+ default: true,
775
+ description:
776
+ 'Compact mode (~6k tokens): discovered arrays → counts+basenames only. All workflow-critical fields preserved (anchors, stress_test_prompts, enhancement_suggestions, etc). Set false for full discovered objects (~17k tokens). Ignored when report:true.',
777
+ },
778
+ report: {
779
+ type: 'boolean',
780
+ default: false,
781
+ description:
782
+ 'Bounded report mode (RECOMMENDED for /hs-ground): returns a single capped report_markdown (six sections) + token_estimate, suppressing discovery arrays / codebase_hints / stress prompts / read-everything next_steps. Paste report_markdown verbatim.',
783
+ },
784
+ max_tokens: {
785
+ type: 'number',
786
+ default: 2000,
787
+ description: 'Hard cap on report_markdown size when report:true (clamped 500–4000; default 2000).',
788
+ },
789
+ },
790
+ required: ['topic'],
791
+ additionalProperties: false,
792
+ },
793
+ },
794
+ {
795
+ name: 'heyspex_claim_spec',
796
+ description:
797
+ 'Claim a spec for the current agent session (POSTs /api/specs/:id/claim). Flips spec status to in_progress if currently draft/open/deferred and records the claim in .heyspex/active-claims.json so the Roadmap In Progress card shows a green agent badge. Call this after the user confirms claim+begin in the /hs-next gate; release with heyspex_release_spec on hand-off.',
798
+ inputSchema: {
799
+ type: 'object',
800
+ properties: {
801
+ spec_id: {
802
+ type: 'string',
803
+ description: 'Spec id to claim (e.g. "HS-TASK-XA40", "HS-EPIC-Y9OJ")',
804
+ },
805
+ agent: {
806
+ type: 'string',
807
+ description:
808
+ 'Agent display name (default: detected from env — "Claude Code" / "Cursor" / HEYSPEX_AGENT)',
809
+ },
810
+ session_id: {
811
+ type: 'string',
812
+ description:
813
+ 'Session identifier (default: detected from CLAUDE_CODE_SESSION_ID / CURSOR_TRACE_ID / HEYSPEX_SESSION_ID)',
814
+ },
815
+ agent_profile: {
816
+ type: 'string',
817
+ description:
818
+ 'Optional agent-identity slug to operate as for this claim (e.g. "code-reviewer", "plan-grounder"). Binds the claim to a HeySpex agent identity so the Agents surface shows this session under that identity (HS-EPIC-96YK). Omit to leave unbound.',
819
+ },
820
+ },
821
+ required: ['spec_id'],
822
+ additionalProperties: false,
823
+ },
824
+ },
825
+ {
826
+ name: 'heyspex_release_spec',
827
+ description:
828
+ 'Release a previously claimed spec (DELETEs /api/specs/:id/claim). Status is left as-is. Call when handing off mid-session or when the spec moves to in_review at session close-out.',
829
+ inputSchema: {
830
+ type: 'object',
831
+ properties: {
832
+ spec_id: {
833
+ type: 'string',
834
+ description: 'Spec id whose claim should be cleared',
835
+ },
836
+ },
837
+ required: ['spec_id'],
838
+ additionalProperties: false,
839
+ },
840
+ },
841
+ {
842
+ name: 'heyspex_report_progress',
843
+ description:
844
+ "Report the current work-stage of a claimed spec as you work (PATCHes /api/specs/:id/claim/status with status=working). The verb shows live on the What's Next surface (hero line + workflow spine), and ac_progress drives the task acceptance-criteria checklist — so the user sees what the agent is doing right now, not just that it is claimed. Call it as you move between stages (and bump ac_progress as you complete acceptance criteria during a task). The closed verb vocabulary is defined in the execute procedure (heyspex_get_procedure({ name: 'pre-execution-alignment-gate' })). Requires an existing claim on the spec (heyspex_claim_spec first) — returns 404 otherwise.",
845
+ inputSchema: {
846
+ type: 'object',
847
+ properties: {
848
+ spec_id: {
849
+ type: 'string',
850
+ description: 'Spec id whose claim to update (e.g. "HS-TASK-XA40")',
851
+ },
852
+ current_stage: {
853
+ type: 'string',
854
+ enum: ['Grounding', 'Breaking down', 'Drafting', 'Validating', 'Testing', 'Reviewing'],
855
+ description:
856
+ 'Present-progressive work-stage verb (closed vocabulary). Maps on the spine: Grounding/Breaking down → Break down, Drafting/Validating/Testing → Build, Reviewing → Review.',
857
+ },
858
+ ac_progress: {
859
+ type: 'number',
860
+ description:
861
+ 'Optional 0-based index of the acceptance criterion you are currently on (task-grain). Criteria before it render as done in the checklist. Omit when not on a per-criterion task.',
862
+ },
863
+ },
864
+ required: ['spec_id', 'current_stage'],
865
+ additionalProperties: false,
866
+ },
867
+ },
868
+ {
869
+ name: 'heyspex_promote_diagnose',
870
+ description:
871
+ "Break a bug write-up (HS-DIAG-*) down into planned work — the SAME breakdown stage `heyspex_promote_research` uses, entered from its second input kind ([[HS-ALBV-BFIX-EPIC-breakdown-stage-accepts-a-diagnose]]: \"one stage, two inputs, not a second parallel tool\"). Use it when an investigation concludes the bug needs SEVERAL separately-verifiable fixes; a one-fix bug is claimed and fixed directly, exactly as today. The stage grounds first (reports the open milestone/epic already covering the area and recommends attaching over minting), then sizes by COUNTING the fixes the write-up's own `## Fix` section lists — never by length and never by AI judgment. Placement goes DOWN, never up: a matching epic gets tasks, a matching milestone with no fitting epic gets a new epic, and only when neither exists is a new milestone proposed, which asks the owner first. Everything spawned carries a resolvable milestone, no exemption. The write-up LINKS to what it spawned and never parents it — an epic parented to a bug report does not render on the roadmap — and its own `parent:` keeps its existing job of grouping small bugs under one epic. Once it has spawned work the write-up leaves the on-deck queue and its Resolution row stops offering a claim; it closes on `heyspex_verify_reproduction`, not when its child work closes.",
872
+ inputSchema: {
873
+ type: 'object',
874
+ properties: {
875
+ diagnose_id: {
876
+ type: 'string',
877
+ description:
878
+ 'The bug write-up to break down. Accepts the frontmatter `id:` (e.g. HS-WZ62), the full file basename, the basename with .md, or a [[wikilink]]. Resolution reads specs/diagnoses/ and the file\'s own `type:` — no id-shape pattern match, so a bare era-3 file key works.',
879
+ },
880
+ accepted: {
881
+ type: 'boolean',
882
+ description:
883
+ 'Two-phase, like the research path. Omit on the FIRST call to get the grounding report (what already covers this area), the ladder result (how many fixes the write-up lists and which rung that lands on), and the proposed placement — no writes. Show that to the human, then call again with `accepted: true` to execute.',
884
+ },
885
+ confirm_new_milestone: {
886
+ type: 'boolean',
887
+ description:
888
+ 'Required only when the proposed placement is `new_milestone` — the last rung, reached only when grounding found neither a fitting epic nor a fitting milestone. Without it the execute refuses with OWNER_CONFIRMATION_REQUIRED and writes nothing. Pass true ONLY after the owner has agreed to the proposed milestone.',
889
+ },
890
+ pillar: {
891
+ type: 'string',
892
+ description:
893
+ 'HS-PILR-* id/basename or [[wikilink]] for an epic this call would spawn. Optional when attaching under an existing milestone that declares its own `pillar:` (it is inherited); REQUIRED for the new-milestone rung. An unresolvable pillar fails the whole execute before any write.',
894
+ },
895
+ measurement_parent_epic: {
896
+ type: 'string',
897
+ description:
898
+ 'Only for the measure-first hold — when the write-up concludes the cause is not yet known, the only thing filed is the measurement, and a task needs an epic to live under. Name one here when grounding found no covering epic. No epic and no milestone is ever minted on this path.',
899
+ },
900
+ },
901
+ required: ['diagnose_id'],
902
+ additionalProperties: false,
903
+ },
904
+ },
905
+ {
906
+ name: 'heyspex_verify_reproduction',
907
+ description:
908
+ "Record a reproduction re-run on a diagnose (HS-DIAG-*, specs/diagnoses/) and close it when the run comes back clean — criterion 7 of [[HS-ALBV-BFIX-EPIC-breakdown-stage-accepts-a-diagnose]]. A bug write-up that has been broken down into work closes when its REPRODUCTION stops reproducing, NOT when its last child task closes: every one of the seven prior console read-path attempts closed clean while the bug stayed live. This is also the only route a diagnose has to `closed` at all — a diagnose is standalone, so the console's review_pending cascade (which walks milestone → epic → task) never reaches it, and 211 diagnoses sit parked at in_review/review_pending as of 2026-08-22 with nothing that would ever move them. Re-run the `## Reproduction` steps in the write-up, then call this with the verdict. `no_longer_reproduces` on a build-complete (in_review/review_pending) diagnose closes it through PATCH /api/specs/:id/status — never a hand-edited `status:`. `still_reproduces` records the verdict and closes nothing. `evidence` (≥30 chars, saying what you re-ran and what happened) is required either way; a verdict with no account of the run is a green light wired to nothing. A second re-run OVERWRITES the recorded verdict — the history lives in the diagnose's ## Change log. Cloud contract ([[HS-7UJZ-O86I-DIAG]]): in cloud reliance the diagnose is resolved and the verdict recorded THROUGH the console (GET + PUT /api/specs/:id), so a diagnose filed in cloud mode that exists only as a Neon row closes too; in local reliance the disk scan runs first and the console is consulted only when specs/diagnoses/ has no match.",
909
+ inputSchema: {
910
+ type: 'object',
911
+ properties: {
912
+ spec_id: {
913
+ type: 'string',
914
+ description:
915
+ 'The diagnose to record against. Accepts the frontmatter `id:` (e.g. HS-WZ62), the full file basename, a [[wikilink]], or an unambiguous id prefix. Resolution reads specs/diagnoses/ and the file\'s own `type:` — no id-shape pattern match.',
916
+ },
917
+ result: {
918
+ type: 'string',
919
+ enum: ['no_longer_reproduces', 'still_reproduces'],
920
+ description:
921
+ 'The verdict of the re-run, authored by whoever ran it — never inferred from whether the child work closed.',
922
+ },
923
+ evidence: {
924
+ type: 'string',
925
+ description:
926
+ 'What you re-ran and what happened (≥30 characters). Written into frontmatter and the change-log row.',
927
+ },
928
+ by: {
929
+ type: 'string',
930
+ description: 'Who ran it. Defaults to the detected agent identity.',
931
+ },
932
+ },
933
+ required: ['spec_id', 'result', 'evidence'],
934
+ additionalProperties: false,
935
+ },
936
+ },
937
+ {
938
+ name: 'heyspex_submit_for_review',
939
+ description:
940
+ "Submit a completed spec for review via PATCH /api/specs/:id/status (never hand-edit frontmatter status). Applies active review-grain coercion (in_review → review_pending for units below the grain), parent rollup, and spec events. Validates acceptance-criteria progress from the active claim unless skip_ac_check: true. Releases the claim by default. Returns close_out guidance for HS-REV vs change-log seed steps per session-close-protocol. Call once all acceptance criteria are verified — after incremental heyspex_report_progress ticks.",
941
+ inputSchema: {
942
+ type: 'object',
943
+ properties: {
944
+ spec_id: {
945
+ type: 'string',
946
+ description: 'Spec id to submit (e.g. "HS-TASK-XA40", "HS-EPIC-Y9OJ")',
947
+ },
948
+ skip_ac_check: {
949
+ type: 'boolean',
950
+ description:
951
+ 'Bypass the ac_progress >= criteria_count gate. Use only when criteria are genuinely N/A or progress was reported under a stale claim.',
952
+ },
953
+ release_claim: {
954
+ type: 'boolean',
955
+ description: 'Release the active claim after a successful status flip (default true).',
956
+ },
957
+ actual_tokens: {
958
+ type: 'number',
959
+ description:
960
+ 'Optional: the real context-window tokens this task consumed, if the agent knows it. Stored beside the pre-build estimate so the sprint-grouping heuristics can be calibrated (HS-HBD2).',
961
+ },
962
+ },
963
+ required: ['spec_id'],
964
+ additionalProperties: false,
965
+ },
966
+ },
967
+ {
968
+ name: 'heyspex_record_wip',
969
+ description:
970
+ "Record the machine-readable WIP ledger for an epic at session close (POSTs /api/specs/:id/wip → .heyspex/wip-state.json). This is the SOLE structured-handoff close action — the prose next-session-handoff.md journal is retired (HS-EPIC-HNDR). The touched + untracked file lists are captured from `git status` server-side — do NOT pass them; supply only the semantic fields you alone know. The next agent's /hs-next brief surfaces this entry so it reads state instead of reconstructing it from specs + git diff ([[HS-EPIC-FCSM]] forensic-reconstruction incident). The ledger is coordination only — route durable knowledge (decisions, rationale) to vault memory specs, not here. Omitted semantic fields preserve their prior values. THE HAND-OFF PROSE GOES IN `notes` — there is no `summary` field, and passing one (or any other unlisted argument) now FAILS the call by name rather than dropping it silently ([[HS-IRX0-5KFT-DIAG]]: four hand-offs written into `summary` were lost while every call reported success). A `notes` string over 4000 characters is refused, never clipped. Every success returns a measured `stored` receipt — read back from the ledger file after the write — naming the characters/items actually kept per field, so a write that lands empty is impossible to miss.",
971
+ inputSchema: {
972
+ type: 'object',
973
+ properties: {
974
+ spec_id: {
975
+ type: 'string',
976
+ description: 'Epic id whose WIP to record (e.g. "HS-EPIC-FCSM")',
977
+ },
978
+ specs_filed: {
979
+ type: 'array',
980
+ items: { type: 'string' },
981
+ description: 'Spec ids filed this session (e.g. ["HS-TASK-9CVR", "HS-TASK-PK4G"])',
982
+ },
983
+ remaining: {
984
+ type: 'array',
985
+ items: { type: 'string' },
986
+ description: 'Remaining work items, one short line each (e.g. ["9CVR conveyor", "PK4G peek agenda"])',
987
+ },
988
+ verified: {
989
+ type: 'array',
990
+ items: { type: 'string' },
991
+ description: 'What was built AND verified, one line each (e.g. ["shell registered in surfaces.ts, chrome-hide works"])',
992
+ },
993
+ notes: {
994
+ type: 'string',
995
+ description:
996
+ 'THE hand-off prose for the next agent — this is the only free-text field the ledger stores. Max 4000 characters; a longer string is refused by name, never clipped. Surfaces verbatim as **Notes:** in the next /hs-next brief.',
997
+ },
998
+ agent: {
999
+ type: 'string',
1000
+ description: 'Agent display name (default: detected from env)',
1001
+ },
1002
+ session_id: {
1003
+ type: 'string',
1004
+ description: 'Session identifier (default: detected from env)',
1005
+ },
1006
+ },
1007
+ required: ['spec_id'],
1008
+ additionalProperties: false,
1009
+ },
1010
+ },
1011
+ {
1012
+ name: 'heyspex_defer_spec',
1013
+ description:
1014
+ 'Defer remaining work on a claimed task to the next agent: files a split child with the handoff envelope (reason / summary_done / remaining / success_criteria), updates the parent (change-log + ## Split), pins the child on-deck, releases the parent claim, and records WIP on the parent epic. Invoked mid-session on the currently-claimed task per [[HS-TASK-1U0Z-hs-defer-mcp-tool-and-slash-command]] / [[HS-EPIC-YQ5A-delegation-defer-work-from-one-agent-to]]. Optional `handoff_id` dedups re-posts ([[HS-TASK-W146-defer-idempotency-handoff-id-and-cross-a]]).',
1015
+ inputSchema: {
1016
+ type: 'object',
1017
+ properties: {
1018
+ spec_id: {
1019
+ type: 'string',
1020
+ description: 'Parent task id to defer (HS-TASK-*). Defaults to the task you hold a claim on when omitted.',
1021
+ },
1022
+ reason: {
1023
+ type: 'string',
1024
+ description: 'Why the defer/split happened (context budget, blocked, lane hand-off, etc.).',
1025
+ },
1026
+ summary_done: {
1027
+ type: 'array',
1028
+ items: { type: 'string' },
1029
+ description: 'Work completed before the handoff — mirrors WIP verified semantics.',
1030
+ },
1031
+ remaining: {
1032
+ type: 'array',
1033
+ items: { type: 'string' },
1034
+ description: 'Actionable items for the picking-up agent.',
1035
+ },
1036
+ success_criteria: {
1037
+ type: 'array',
1038
+ items: { type: 'string' },
1039
+ description: "How the child spec's reviewer knows the deferred work is done.",
1040
+ },
1041
+ parent_status: {
1042
+ type: 'string',
1043
+ enum: ['in_review', 'in_progress'],
1044
+ description:
1045
+ 'Parent status after defer (default in_review when remainder is independently reviewable).',
1046
+ },
1047
+ effort: {
1048
+ type: 'number',
1049
+ description: 'Re-estimated effort for the child remainder (inherits parent when omitted).',
1050
+ },
1051
+ child_name: {
1052
+ type: 'string',
1053
+ description: 'Optional title for the split child (default: "<parent name> (continued)").',
1054
+ },
1055
+ handoff_id: {
1056
+ type: 'string',
1057
+ description:
1058
+ 'Optional dedup key — re-posting defer with the same handoff_id returns the existing split child instead of filing a duplicate ([[HS-TASK-W146]]).',
1059
+ },
1060
+ pin_on_deck: {
1061
+ type: 'boolean',
1062
+ description: 'Pin the child on-deck for the next /hs-next (default true).',
1063
+ },
1064
+ release_claim: {
1065
+ type: 'boolean',
1066
+ description: 'Release the parent claim after defer (default true).',
1067
+ },
1068
+ record_wip: {
1069
+ type: 'boolean',
1070
+ description: 'Record WIP on the parent epic (default true).',
1071
+ },
1072
+ agent: {
1073
+ type: 'string',
1074
+ description: 'Agent display name (default: detected from env)',
1075
+ },
1076
+ session_id: {
1077
+ type: 'string',
1078
+ description: 'Session identifier (default: detected from env)',
1079
+ },
1080
+ },
1081
+ required: ['reason', 'summary_done', 'remaining', 'success_criteria'],
1082
+ additionalProperties: false,
1083
+ },
1084
+ },
1085
+ {
1086
+ name: 'heyspex_handoff_record',
1087
+ description:
1088
+ "Upsert the CURRENT resume context for this agent/lane into the Neon control plane (PUT /api/control/handoff-context). Self-purging: overwrite-in-place keyed on lane_key (default = this agent's session), TTL/expiry set server-side — NOT an append-only ledger. Coordination ONLY (spec_id, phase, blockers, estimated_time, links); decisions/rationale/knowledge route to vault memory specs, never here — the endpoint REJECTS knowledge fields. This is the Neon successor for the handoff-coordination job; the file-based heyspex_record_wip still runs alongside until the file→Neon cutover (HS-EPIC-PFMN). Requires a reachable console + Neon.",
1089
+ inputSchema: {
1090
+ type: 'object',
1091
+ properties: {
1092
+ lane: {
1093
+ type: 'string',
1094
+ description: "Lane key — the upsert identity. Default: this agent's session id (one lane per session). Pass an explicit lane to share/overwrite a named lane.",
1095
+ },
1096
+ spec_id: {
1097
+ type: 'string',
1098
+ description: 'Current spec id being worked (e.g. "HS-EPIC-NCPS"); omit when no spec is in flight',
1099
+ },
1100
+ phase: {
1101
+ type: 'string',
1102
+ description: 'Short phase label (e.g. "slice", "verifying")',
1103
+ },
1104
+ blockers: {
1105
+ type: 'array',
1106
+ items: { type: 'string' },
1107
+ description: 'Coordination blockers, one short line each',
1108
+ },
1109
+ estimated_time: {
1110
+ type: 'string',
1111
+ description: 'Human time estimate to resume/finish (e.g. "~2h")',
1112
+ },
1113
+ links: {
1114
+ type: 'array',
1115
+ items: { type: 'string' },
1116
+ description: 'Relevant spec ids / links to resume against',
1117
+ },
1118
+ agent: {
1119
+ type: 'string',
1120
+ description: 'Agent display name (default: detected from env)',
1121
+ },
1122
+ session_id: {
1123
+ type: 'string',
1124
+ description: 'Session identifier (default: detected from env; also the default lane)',
1125
+ },
1126
+ },
1127
+ required: [],
1128
+ additionalProperties: false,
1129
+ },
1130
+ },
1131
+ {
1132
+ name: 'heyspex_handoff_load',
1133
+ budget: READ_TOOL_BUDGET,
1134
+ description:
1135
+ "Read the CURRENT resume context for a lane from the Neon control plane (GET /api/control/handoff-context). Default reads this agent's own lane (its session); pass lane to read a specific lane, or all:true to list every active lane (parallel-agent off-on-deck context). Self-purging — there is no per-session history to page through; expired rows read as absent.",
1136
+ inputSchema: {
1137
+ type: 'object',
1138
+ properties: {
1139
+ lane: {
1140
+ type: 'string',
1141
+ description: "Lane key to read. Default: this agent's session id.",
1142
+ },
1143
+ all: {
1144
+ type: 'boolean',
1145
+ description: 'List every active lane for this user instead of a single lane (ignores `lane`).',
1146
+ },
1147
+ agent: {
1148
+ type: 'string',
1149
+ description: 'Agent display name (default: detected from env)',
1150
+ },
1151
+ session_id: {
1152
+ type: 'string',
1153
+ description: 'Session identifier (default: detected from env; also the default lane)',
1154
+ },
1155
+ },
1156
+ required: [],
1157
+ additionalProperties: false,
1158
+ },
1159
+ },
1160
+ {
1161
+ name: 'heyspex_pin_get',
1162
+ budget: READ_TOOL_BUDGET,
1163
+ description:
1164
+ 'Read the current on-deck pinned spec from the Neon control plane (GET /api/control/pin). Returns the pin row (spec_id, pinned_by, expires_at) or null when no pin is set / the fuse has blown. Requires a reachable console + Neon.',
1165
+ inputSchema: {
1166
+ type: 'object',
1167
+ properties: {},
1168
+ required: [],
1169
+ additionalProperties: false,
1170
+ },
1171
+ },
1172
+ {
1173
+ name: 'heyspex_milestone_show',
1174
+ budget: READ_TOOL_BUDGET,
1175
+ description:
1176
+ 'Show a milestone (GET /api/specs/:id). Returns its id, name, status, created, and the structured needs-breakdown shape — epics_count, needs_breakdown (true when epics: []), needs_breakdown_message (the locked "Needs breakdown — file the first epic to start", else null). Consume needs_breakdown directly; do not re-derive from epics_count. [[HS-TASK-HMSE]] / [[HS-EPIC-MNBD]].',
1177
+ inputSchema: {
1178
+ type: 'object',
1179
+ properties: {
1180
+ spec_id: {
1181
+ type: 'string',
1182
+ description: 'Milestone id or basename (e.g. "HS-MIL-HMNJ").',
1183
+ },
1184
+ },
1185
+ required: ['spec_id'],
1186
+ additionalProperties: false,
1187
+ },
1188
+ },
1189
+ {
1190
+ name: 'heyspex_pin_set',
1191
+ description:
1192
+ 'Set or clear the on-deck pin via the Neon control plane (PUT /api/control/pin). Pass spec_id to pin a spec; OMIT spec_id (or pass null) to CLEAR the pin. Set/clear are logged as focus_events. ttl_ms: omit = default 7-day fuse, null = no fuse, number = custom ms. Requires a reachable console + Neon.',
1193
+ inputSchema: {
1194
+ type: 'object',
1195
+ properties: {
1196
+ spec_id: {
1197
+ type: ['string', 'null'],
1198
+ description: 'Spec id to pin (e.g. "HS-EPIC-CLMW"). Omit or null to clear the pin.',
1199
+ },
1200
+ pinned_by: {
1201
+ type: 'string',
1202
+ description: 'Actor doing the pinning (default: detected agent identity).',
1203
+ },
1204
+ ttl_ms: {
1205
+ type: ['number', 'null'],
1206
+ description: 'Fuse in ms; null = no fuse. Omit for the server default 7-day fuse.',
1207
+ },
1208
+ agent: {
1209
+ type: 'string',
1210
+ description: 'Agent display name (default: detected from env)',
1211
+ },
1212
+ session_id: {
1213
+ type: 'string',
1214
+ description: 'Session identifier (default: detected from env)',
1215
+ },
1216
+ },
1217
+ required: [],
1218
+ additionalProperties: false,
1219
+ },
1220
+ },
1221
+ {
1222
+ name: 'heyspex_resolve_prerequisite',
1223
+ description:
1224
+ 'Execute ONE deterministic cross-milestone prerequisite resolution path via the console (POST /api/focus/prereq). The blocked spec depends on a prereq that lives elsewhere; `path` chooses how to resolve it: "do_first" (work the prereq now, then return), "switch_milestone" (move focus to the prereq\'s milestone), "move_in" (relocate the prereq into the blocked spec\'s milestone — REQUIRES confirm: true), "dispatch" (hand the prereq to another agent), or "defer" (push the blocked spec behind the prereq). Optional auto_return brings focus back to the blocked spec when the prereq resolves. Returns { ok: true, result } on success; a 409 confirmation_required (e.g. an unconfirmed move_in) and other failures surface the console error body. Requires a reachable console. [[HS-EPIC-QQBD]]',
1225
+ inputSchema: {
1226
+ type: 'object',
1227
+ properties: {
1228
+ blocked_id: {
1229
+ type: 'string',
1230
+ description: 'Spec id that is blocked by the prerequisite (e.g. "HS-EPIC-CLMW").',
1231
+ },
1232
+ prereq_id: {
1233
+ type: 'string',
1234
+ description: 'Spec id of the prerequisite that must be resolved.',
1235
+ },
1236
+ path: {
1237
+ type: 'string',
1238
+ enum: ['do_first', 'switch_milestone', 'move_in', 'dispatch', 'defer'],
1239
+ description:
1240
+ 'The resolution path to execute. "move_in" requires confirm: true.',
1241
+ },
1242
+ auto_return: {
1243
+ type: 'boolean',
1244
+ description:
1245
+ 'Return focus to the blocked spec automatically once the prerequisite resolves.',
1246
+ },
1247
+ confirm: {
1248
+ type: 'boolean',
1249
+ description: 'Required (true) for the "move_in" path; the console returns 409 confirmation_required without it.',
1250
+ },
1251
+ prereq_milestone_id: {
1252
+ type: 'string',
1253
+ description: 'Milestone id of the prerequisite (optional hint; resolved server-side when omitted).',
1254
+ },
1255
+ blocked_milestone_id: {
1256
+ type: 'string',
1257
+ description: 'Milestone id of the blocked spec (optional hint; resolved server-side when omitted).',
1258
+ },
1259
+ return_to: {
1260
+ type: 'string',
1261
+ description: 'Spec id to return focus to (optional; defaults to the blocked spec).',
1262
+ },
1263
+ },
1264
+ required: ['blocked_id', 'prereq_id', 'path'],
1265
+ additionalProperties: false,
1266
+ },
1267
+ },
1268
+ {
1269
+ name: 'heyspex_claim_acquire',
1270
+ description:
1271
+ 'Atomically acquire a claim on a spec via the Neon control plane (POST /api/control/active-claims/acquire). Returns acquired:true with the claim on success, or acquired:false + already_claimed naming the existing claimant when another agent holds it (atomic INSERT … ON CONFLICT — exactly one of N concurrent acquires wins). This is the control-plane primitive that heyspex_claim_spec routes through once the file→Neon cutover lands (HS-EPIC-PFMN). Requires a reachable console + Neon.',
1272
+ inputSchema: {
1273
+ type: 'object',
1274
+ properties: {
1275
+ spec_id: {
1276
+ type: 'string',
1277
+ description: 'Spec id to claim (e.g. "HS-EPIC-CLMW").',
1278
+ },
1279
+ ttl_ms: {
1280
+ type: ['number', 'null'],
1281
+ description: 'Claim fuse in ms; null = no fuse. Omit for the server default.',
1282
+ },
1283
+ agent: {
1284
+ type: 'string',
1285
+ description: 'Agent display name (default: detected from env)',
1286
+ },
1287
+ session_id: {
1288
+ type: 'string',
1289
+ description: 'Session identifier (default: detected from env)',
1290
+ },
1291
+ wait_if_held: {
1292
+ type: 'boolean',
1293
+ description:
1294
+ 'When true and the spec is already claimed, join the waiting line behind the holder instead of just being refused — the refusal then reports the waiter\'s queue position. Omit for today\'s behaviour (a held spec just refuses).',
1295
+ },
1296
+ },
1297
+ required: ['spec_id'],
1298
+ additionalProperties: false,
1299
+ },
1300
+ },
1301
+ {
1302
+ name: 'heyspex_claim_release',
1303
+ description:
1304
+ 'Release a claim on a spec via the Neon control plane (DELETE /api/control/active-claims/:spec_id). Returns released:true, or not_found when no active claim exists for the spec. Logs a released claim_event. Requires a reachable console + Neon.',
1305
+ inputSchema: {
1306
+ type: 'object',
1307
+ properties: {
1308
+ spec_id: {
1309
+ type: 'string',
1310
+ description: 'Spec id whose claim to release.',
1311
+ },
1312
+ agent: {
1313
+ type: 'string',
1314
+ description: 'Releasing agent (recorded on the event; default: detected from env)',
1315
+ },
1316
+ session_id: {
1317
+ type: 'string',
1318
+ description: 'Session identifier (default: detected from env)',
1319
+ },
1320
+ },
1321
+ required: ['spec_id'],
1322
+ additionalProperties: false,
1323
+ },
1324
+ },
1325
+ {
1326
+ name: 'heyspex_claim_list',
1327
+ budget: READ_TOOL_BUDGET,
1328
+ description:
1329
+ 'List all active claims from the Neon control plane (GET /api/control/active-claims), newest first. Use as parallel-agent off-on-deck context to see which specs other agents currently hold. Requires a reachable console + Neon.',
1330
+ inputSchema: {
1331
+ type: 'object',
1332
+ properties: {},
1333
+ required: [],
1334
+ additionalProperties: false,
1335
+ },
1336
+ },
1337
+ {
1338
+ name: 'heyspex_claim_file',
1339
+ description:
1340
+ 'Claim a file path for exclusive editing (POSTs /api/file-claims/acquire). Default TTL 5 minutes — a crashed agent releases the file after expiry. Returns acquired:true on success, or acquired:false + the claiming agent when another agent holds the file. Call before writing to a shared spec file — this is the ephemeral stopgap ([[HS-Y00G-EPIC]]) that prevents silent data loss while the CRDT layer is built.',
1341
+ inputSchema: {
1342
+ type: 'object',
1343
+ properties: {
1344
+ file_path: {
1345
+ type: 'string',
1346
+ description: 'Vault-relative path to claim (e.g. "specs/epics/HS-EPIC-YQ5A.md")',
1347
+ },
1348
+ agent: {
1349
+ type: 'string',
1350
+ description: 'Agent display name (default: detected from env)',
1351
+ },
1352
+ session_id: {
1353
+ type: 'string',
1354
+ description: 'Session identifier (default: detected from env)',
1355
+ },
1356
+ ttl_ms: {
1357
+ type: ['number', 'null'],
1358
+ description: 'Claim TTL in ms; defaults to 5 minutes (300000).',
1359
+ },
1360
+ working_spec_id: {
1361
+ type: 'string',
1362
+ description: 'The spec id the agent is working on (informational, optional).',
1363
+ },
1364
+ },
1365
+ required: ['file_path'],
1366
+ additionalProperties: false,
1367
+ },
1368
+ },
1369
+ {
1370
+ name: 'heyspex_release_file_claim',
1371
+ description:
1372
+ 'Release a file claim (DELETEs /api/file-claims/release). Call after writing to release the lock so other agents can edit. Also auto-released by TTL expiry on crashed agents.',
1373
+ inputSchema: {
1374
+ type: 'object',
1375
+ properties: {
1376
+ file_path: {
1377
+ type: 'string',
1378
+ description: 'Vault-relative path to release',
1379
+ },
1380
+ },
1381
+ required: ['file_path'],
1382
+ additionalProperties: false,
1383
+ },
1384
+ },
1385
+ {
1386
+ name: 'heyspex_list_known_tags',
1387
+ budget: listBudget('tags'),
1388
+ description:
1389
+ 'List the vault tag vocabulary (GET /api/scheduler/known-tags): distinct frontmatter `tags:` values with per-tag spec counts, sorted by count desc then tag asc. The reply is ONE page under `tags` plus `next_cursor` (keyset over (count, tag) from an aggregate query — never the whole vocabulary, never a total count; [[HS-4H8T-8KK5-TASK-every-mcp-read-tool-declares-a-byte]] resolving [[HS-7QM6-1Q1C-DIAG-heyspex-list-known-tags-response-busts-t]]). Call this BEFORE proposing a focus-areas pivot so you only suggest tags that actually exist — agents must pick from this closed vocabulary, never invent tags ([[HS-EPIC-7WJN]] / focus-intent-routing); the first page (most-used tags) is what a focus pivot needs. Also returns `drift: [{ a, b, reason, similarity }]` — near-duplicate tag pairs within the page (e.g. `chrome-ext`/`chrome-extension`) flagged by lexical string similarity so you can propose a merge; no tag is changed automatically ([[HS-TASK-H0SI]]). Requires a reachable console.',
1390
+ inputSchema: {
1391
+ type: 'object',
1392
+ properties: {
1393
+ limit: {
1394
+ type: 'number',
1395
+ default: 100,
1396
+ description: 'Page size (default: 100, max: 500). The page is fitted to the tool byte budget — a trimmed page hands back a `next_cursor` that resumes at the first tag dropped.',
1397
+ },
1398
+ after: {
1399
+ type: 'string',
1400
+ description: 'Opaque cursor: the `next_cursor` value of the prior page. Omit for page 1. A page whose `next_cursor` is null was the last one.',
1401
+ },
1402
+ },
1403
+ required: [],
1404
+ additionalProperties: false,
1405
+ },
1406
+ },
1407
+ {
1408
+ name: 'heyspex_set_focus_areas',
1409
+ description:
1410
+ 'Declare the active roadmap focus areas (PUT /api/scheduler/focus-areas). Specs whose `tags:` intersect this set float to the top of their layer on the What\'s Next / roadmap surfaces; everything else stays in scheduler order but dims. Pick `tags` ONLY from heyspex_list_known_tags (closed vocabulary). Follow the focus-intent-routing procedure: confirm with the user first, and NEVER add a tag the user said to STOP working on (negation). Returns the new state. Requires a reachable console.',
1411
+ inputSchema: {
1412
+ type: 'object',
1413
+ properties: {
1414
+ rationale: { type: 'string', description: 'Short reason for the pivot (user-facing).' },
1415
+ tags: {
1416
+ type: 'array',
1417
+ items: { type: 'string' },
1418
+ description: 'Tag strings from the known-tags vocabulary to focus on.',
1419
+ },
1420
+ expires_at: { type: 'string', description: 'Optional ISO 8601 expiry; omit for no fuse.' },
1421
+ replace: {
1422
+ type: 'boolean',
1423
+ description: 'Replace the current focus set (default) vs merge — the route treats focus as replace-by-default.',
1424
+ },
1425
+ },
1426
+ required: ['tags'],
1427
+ additionalProperties: false,
1428
+ },
1429
+ },
1430
+ {
1431
+ name: 'heyspex_get_focus_areas',
1432
+ budget: READ_TOOL_BUDGET,
1433
+ description:
1434
+ 'Read the current roadmap focus areas (GET /api/scheduler/focus-areas) → the active state, or { active: false } when none is set. Requires a reachable console.',
1435
+ inputSchema: {
1436
+ type: 'object',
1437
+ properties: {},
1438
+ required: [],
1439
+ additionalProperties: false,
1440
+ },
1441
+ },
1442
+ {
1443
+ name: 'heyspex_clear_focus_areas',
1444
+ description:
1445
+ 'Clear the roadmap focus areas (DELETE /api/scheduler/focus-areas) — the roadmap returns to undimmed scheduler order. Returns { active: false }. Requires a reachable console.',
1446
+ inputSchema: {
1447
+ type: 'object',
1448
+ properties: {},
1449
+ required: [],
1450
+ additionalProperties: false,
1451
+ },
1452
+ },
1453
+ {
1454
+ name: 'heyspex_resolve_focus_intent',
1455
+ description:
1456
+ 'Resolve a free-text focus utterance to a WIDE candidate window of relevant specs via lexical wide-recall (BM25 over titles/bodies/tags), tuned for Recall@N — YOUR LLM reranks the window to pick the focus tags ([[llm-as-reranker-lexical-recall-companion-architecture]]). NOT embeddings (Gate B = NO, [[retrieval-substrate-lexical-not-embeddings-gate-b-no]]). Returns { utterance, candidates: [{ spec, tags, why }], total }; top_k caps the window (default 30, max 50). Utterances need not use a spec\'s exact tag word. Returns an empty window (not an error) on no match; fall back to heyspex_list_known_tags + exact-tag NLU when the window is empty.',
1457
+ inputSchema: {
1458
+ type: 'object',
1459
+ properties: {
1460
+ utterance: { type: 'string', description: 'The user\'s free-text focus phrase.' },
1461
+ top_k: { type: 'number', description: 'Optional max suggestions to return (Phase B).' },
1462
+ },
1463
+ required: ['utterance'],
1464
+ additionalProperties: false,
1465
+ },
1466
+ },
1467
+ {
1468
+ name: 'heyspex_search_specs',
1469
+ budget: listBudget('matches'),
1470
+ description:
1471
+ 'Search the CLOUD spec vault by TEXT — the duplicate check to run before filing a diagnose/task/epic ([[HS-YFLS]]). Pass the raw symptom sentence as `query`; the tool splits it into bounded angles (the phrase, quoted concept bigrams, the OR-ed salient terms) against GET /api/search and merges the windows, because a long sentence passed verbatim is AND-ed by websearch_to_tsquery and matches nothing. Reads the SYSTEM OF RECORD over HTTP — never local disk, whose mirror is incomplete in PROD mode — so a `grep` of the checkout is not a substitute. Returns { query, terms, queries_tried, searched, result, match_count, matches: [{ id, name, type, status, rank, snippet, matched_by }] }. NEGATIVES ARE EXPLICIT AND DISTINCT: `result: "no_match"` with `searched: true` means the corpus was read and holds nothing (safe to file); `result: "no_query"` means nothing searchable was extracted; every transport/auth/scope problem is an isError envelope (CONSOLE_UNREACHABLE, CONSOLE_NOT_SIGNED_IN, VAULT_SCOPE_UNRESOLVED, VAULT_SCOPE_UNAUTHORIZED, SEARCH_FAILED) — NEVER an empty list. Use this, not heyspex_resolve_focus_intent, to find a specific spec: that tool resolves a focus utterance to a wide tag-based window for reranking and will happily return 20 specs that share one broad tag.',
1472
+ inputSchema: {
1473
+ type: 'object',
1474
+ properties: {
1475
+ query: {
1476
+ type: 'string',
1477
+ description: 'The symptom sentence or concept to search for, as you would write it in a bug report. Long sentences are fine and preferred — the tool extracts the salient terms itself.',
1478
+ },
1479
+ top_k: {
1480
+ type: 'number',
1481
+ description: 'Max matches to return (default 10, max 30).',
1482
+ },
1483
+ type: {
1484
+ type: 'string',
1485
+ description: 'Optional spec-type filter, single or CSV (e.g. "diagnose" or "task,epic").',
1486
+ },
1487
+ status: {
1488
+ type: 'string',
1489
+ description: 'Optional status filter, single or CSV (e.g. "open,in_progress"). Omit when checking for duplicates — a closed spec is still a duplicate.',
1490
+ },
1491
+ },
1492
+ required: ['query'],
1493
+ additionalProperties: false,
1494
+ },
1495
+ },
1496
+ {
1497
+ name: 'heyspex_events_show',
1498
+ budget: READ_TOOL_BUDGET,
1499
+ description:
1500
+ 'Read the per-spec audit log from the Neon control plane (GET /api/control/spec-events), newest first. Optional kind / since / limit filters. event kinds: status_change, wikilink_added, wikilink_removed, review_requested, review_resolved, priority_change, parent_change, claim_acquired, claim_released. Requires a reachable console + Neon.',
1501
+ inputSchema: {
1502
+ type: 'object',
1503
+ properties: {
1504
+ spec_id: {
1505
+ type: 'string',
1506
+ description: 'Spec id whose event log to read.',
1507
+ },
1508
+ kind: {
1509
+ type: 'string',
1510
+ description: 'Filter by event kind (one of the nine spec_event kinds).',
1511
+ },
1512
+ since: {
1513
+ type: 'string',
1514
+ description: 'Only events at/after this ISO date.',
1515
+ },
1516
+ limit: {
1517
+ type: 'number',
1518
+ description: 'Cap the number of rows returned (positive integer).',
1519
+ },
1520
+ },
1521
+ required: ['spec_id'],
1522
+ additionalProperties: false,
1523
+ },
1524
+ },
1525
+ {
1526
+ name: 'heyspex_evidence_show',
1527
+ budget: READ_TOOL_BUDGET,
1528
+ description:
1529
+ 'Read a spec\'s FULL consolidated evidence trail in one call (GET /api/evidence/:specId): the `## Change log` table parsed from the spec markdown, `spec_events` from the Neon control plane, and matching dev-factory cost-ledger / seat-gate / seat-audit rows. Backs both the console UI and this tool from the same logic ([[HS-BNDJ-TASK-consolidated-evidence-read-path-evidence]]). Pass the spec BASENAME when you have it (e.g. "HS-BNDJ-TASK-consolidated-evidence-read-path-evidence") — child tasks of an epic share the frontmatter id, so the basename is the only unique handle per task; a bare shared id resolves to no match (returns found:false plus the sibling basenames to disambiguate with). Never errors on an unknown/missing spec — returns { found: false, notes: [...] } instead of failing. NOTE: cost-ledger/seat-gate/seat-audit rows are keyed by the dev-factory pipeline\'s bare id only, so when the resolved spec shares its id with siblings those three sections may include sibling rows too — the response\'s `notes` field calls this out when it applies. Requires a reachable console.',
1530
+ inputSchema: {
1531
+ type: 'object',
1532
+ properties: {
1533
+ spec_id: {
1534
+ type: 'string',
1535
+ description: 'Spec basename (preferred, unique) or bare frontmatter id whose evidence trail to read.',
1536
+ },
1537
+ },
1538
+ required: ['spec_id'],
1539
+ additionalProperties: false,
1540
+ },
1541
+ },
1542
+ {
1543
+ name: 'heyspex_export_events',
1544
+ description:
1545
+ 'Run the idempotent audit export (POST /api/control/export-events): the control-plane event logs (claim_events + focus_events + spec_events on the paid tier; .heyspex/*-history.jsonl on the free tier) land in specs/events/YYYY-MM.jsonl, one file per UTC month, byte-identical on re-run. A LOCAL console writes the files into its own vault; a CLOUD console (app.heyspex.com) has no vault on disk, so it RETURNS the month files and this tool merges them into the vault repo this session resolves (the receipt names files_written_locally / unchanged_locally) — run it from a checkout of the vault repo. Call this at session close to flush the audit trail to the vault (git-clone-is-backup). Optional since lower bound. Requires a reachable console; works on both tiers and both modes.',
1546
+ inputSchema: {
1547
+ type: 'object',
1548
+ properties: {
1549
+ since: {
1550
+ type: 'string',
1551
+ description: 'Only export events at/after this ISO date (default: all history).',
1552
+ },
1553
+ },
1554
+ additionalProperties: false,
1555
+ },
1556
+ },
1557
+ {
1558
+ name: 'heyspex_record_alignment',
1559
+ description:
1560
+ 'Record the pre-execution alignment checkpoint on an epic/milestone after breaking it down or revisiting its tasks for drift ([[pre-execution-alignment-gate]]). Writes alignment_reviewed_at (+ optional alignment_summary / alignment_override) into the spec frontmatter, which clears the hard gate that /hs-next raises on a childless or drifted node. Pass override_reason to proceed past a drift finding you judge a false positive / intentional supersession — it is stored as alignment_override for the audit trail. The gate re-fires when a corpus doc (related/depends_on/milestone) is modified after the checkpoint (temporal drift, HS-TASK-DCRP) or the 7-day fuse expires. (Signature hashing was removed in HS-TASK-DSIG — it was non-deterministic; drift is now temporal, not hash-based.)',
1561
+ inputSchema: {
1562
+ type: 'object',
1563
+ properties: {
1564
+ spec_id: {
1565
+ type: 'string',
1566
+ description: 'Epic or milestone id to checkpoint (e.g. "HS-EPIC-MILE", "HS-MIL-IART")',
1567
+ },
1568
+ summary: {
1569
+ type: 'string',
1570
+ description: 'One-line note on what was broken down / confirmed (stored as alignment_summary)',
1571
+ },
1572
+ override_reason: {
1573
+ type: 'string',
1574
+ description: 'Why a detected drift finding is being accepted/overridden (stored as alignment_override). Must be at least 40 characters AFTER TRIMMING and actually say why the finding is a false positive or an intentional supersession — a shorter one, an empty string, or whitespace only returns `error: "ALIGNMENT_OVERRIDE_REASON_TOO_THIN"` and records nothing at all (not even the checkpoint). The response reports `overridden` per what was actually STORED. Omit this argument entirely on a clean review — note that omitting it clears the checkpoint with no reason bar, because the gate reads alignment_reviewed_at and never reads alignment_override.',
1575
+ },
1576
+ skipped_despite_findings: {
1577
+ type: 'boolean',
1578
+ description:
1579
+ 'After a breakdown review (step 1b): false when BLOCKER/SIBLING findings were resolved before filing; true when you filed despite unresolved findings. Stamps the plan-review marker for skip-rate telemetry ([[HS-TASK-MEN2]]). Omit when no breakdown review ran.',
1580
+ },
1581
+ },
1582
+ required: ['spec_id'],
1583
+ additionalProperties: false,
1584
+ },
1585
+ },
1586
+ {
1587
+ name: 'heyspex_propose_metadata',
1588
+ description:
1589
+ 'AI-propose the four scheduler fields (depends_on, kind, effort, requires_user_testing) plus a reasoning string for a new task/epic via console POST /api/ai/propose-metadata. Haiku 4.5; one-shot at create time only — never on edit, status change, or update flows ([[HS-TASK-TAOD]]). Returns { ok, kind, effort, requires_user_testing, depends_on, reasoning } on success; { ok: false, reason, message } on failure (caller falls back to manual entry).',
1590
+ inputSchema: {
1591
+ type: 'object',
1592
+ properties: {
1593
+ name: { type: 'string', description: 'Spec name / title' },
1594
+ body: { type: 'string', description: 'Spec body markdown that the model reads' },
1595
+ specType: {
1596
+ type: 'string',
1597
+ enum: ['task', 'epic', 'capture'],
1598
+ description: 'Spec type being proposed for (informs the prompt framing)',
1599
+ },
1600
+ parentExcerpt: {
1601
+ type: 'string',
1602
+ description: 'Optional excerpt of the parent epic body for tier calibration',
1603
+ },
1604
+ },
1605
+ required: ['name'],
1606
+ additionalProperties: false,
1607
+ },
1608
+ },
1609
+ {
1610
+ name: 'heyspex_promote_research',
1611
+ description:
1612
+ 'Promote a research doc (HS-RES-*) to a milestone with AI-assisted epic stubs ([[HS-TASK-QQHP]]). **Default: target_milestone "NEW"** (omit target_milestone to default). Attaching to an existing milestone is the exception — target must be status: open; in_progress requires attach_to_in_progress_milestone + attach_override_reason (≥40 chars). Two-phase flow: omit accepted_stubs for AI proposals (Phase 1), then call again with accepted_stubs to execute (Phase 2). Research stays status: open (evergreen); enhances link is the promoted signal. Every spawned epic requires a resolvable `pillar:` (top-level `pillar` batch default or a per-stub override) — an unresolved pillar fails the whole execute with `error: "pillar_required"` before any write ([[pillar-assignment-protocol]]). Spawned milestone + epics also inherit `family:` from the source research doc.',
1613
+ inputSchema: {
1614
+ type: 'object',
1615
+ properties: {
1616
+ research_id: {
1617
+ type: 'string',
1618
+ description: 'Id or basename of the research to promote (must be in vault; must not already enhance the target milestone). Accepts the frontmatter `id:` (e.g. HS-X5FH), the full file basename (HS-X5FH-TN54-RES-<slug>), the basename with .md, a [[wikilink]], or a legacy HS-RES-* id.',
1619
+ },
1620
+ target_milestone: {
1621
+ type: 'string',
1622
+ description:
1623
+ 'Optional. Defaults to "NEW". HS-MIL-* id/basename for an existing open milestone (exception path), or "NEW" to create a fresh milestone (then new_milestone_name is required).',
1624
+ },
1625
+ new_milestone_name: {
1626
+ type: 'string',
1627
+ description: 'Required when target_milestone === "NEW". Human-readable milestone title.',
1628
+ },
1629
+ attach_to_in_progress_milestone: {
1630
+ type: 'boolean',
1631
+ description:
1632
+ 'Rare owner exception only. When true, allows spawning into a target milestone with status: in_progress. Requires attach_override_reason (≥40 chars). Default false — use NEW instead.',
1633
+ },
1634
+ attach_override_reason: {
1635
+ type: 'string',
1636
+ description:
1637
+ 'Audit trail when attach_to_in_progress_milestone is true. Must be ≥40 chars explaining why epics belong on an in-progress milestone.',
1638
+ },
1639
+ intent: {
1640
+ type: 'string',
1641
+ enum: ['spawn', 'augment', 'both'],
1642
+ description:
1643
+ 'spawn = create new epics from AI stubs; augment = record research-side reverse pointers on existing epics; both = do both.',
1644
+ },
1645
+ spawn_epic_count: {
1646
+ type: 'number',
1647
+ description: 'Hint for the AI proposer for how many stubs to propose (clamped to [1, 6]). Default 2.',
1648
+ },
1649
+ augment_epic_ids: {
1650
+ type: 'array',
1651
+ items: { type: 'string' },
1652
+ description:
1653
+ 'Required when intent includes "augment". HS-EPIC-* ids (or basenames) of existing epics to augment. Each must already belong to the target milestone.',
1654
+ },
1655
+ pillar: {
1656
+ type: 'string',
1657
+ description:
1658
+ 'Batch-default pillar for every spawned epic: an HS-PILR-* id/basename or [[wikilink]] naming an existing pillar doc under specs/pillars/ ([[pillar-assignment-protocol]]). A per-stub `pillar` (in accepted_stubs) overrides this default. REQUIRED whenever accepted_stubs is present (Phase 2 execute) — either this top-level default or a per-stub override must resolve for EVERY spawned epic to a pillar that exists on disk BEFORE any file is written, mirroring POST /api/specs\' hard-block on epics without one. If neither is supplied or the reference does not resolve, execute fails the WHOLE call with `error: "pillar_required"` (lists the pillars that DO exist) before any write; no partial spawn.',
1659
+ },
1660
+ accepted_stubs: {
1661
+ type: 'array',
1662
+ description:
1663
+ 'Phase-2 execution payload. Each stub is the agent-confirmed (possibly edited) form of a Phase-1 proposed_stubs entry. Required to execute the spawn writes; omit on the first call to get AI proposals back for review. Every stub here spawns an epic, and every spawned epic REQUIRES a resolvable pillar (this stub\'s `pillar` or the top-level `pillar` batch default) — an unresolved pillar fails the whole execute with `error: "pillar_required"` before any file is written.',
1664
+ items: {
1665
+ type: 'object',
1666
+ properties: {
1667
+ name: { type: 'string' },
1668
+ kind: { type: 'string', enum: ['infra', 'foundation', 'feature', 'polish'] },
1669
+ effort: { type: 'number', enum: [1, 2, 3, 5, 8, 13] },
1670
+ depends_on: { type: 'array', items: { type: 'string' } },
1671
+ rationale: { type: 'string' },
1672
+ pillar: {
1673
+ type: 'string',
1674
+ description:
1675
+ 'Per-stub pillar override (HS-PILR-* id/basename or [[wikilink]]) — wins over the top-level `pillar` batch default. REQUIRED (this or the top-level default) for this stub\'s spawned epic to resolve; same `pillar_required` failure mode — fails the whole execute before any write — when neither resolves to a pillar doc on disk.',
1676
+ },
1677
+ },
1678
+ required: ['name', 'kind', 'effort'],
1679
+ additionalProperties: false,
1680
+ },
1681
+ },
1682
+ stub_edit_note: {
1683
+ type: 'string',
1684
+ description:
1685
+ 'What the human changed about the proposed stubs, and why (≥40 chars). Only needed when NONE of the accepted_stubs names match any name from the Phase-1 proposal — a full rewrite is legitimate, but so is an agent inventing stubs after the review, and the two look identical from here. Supplying this records the human\'s intent; without it a fully-drifted execute returns `error: "STUB_REVIEW_DRIFT"`.',
1686
+ },
1687
+ },
1688
+ required: ['research_id', 'intent'],
1689
+ additionalProperties: false,
1690
+ },
1691
+ },
1692
+ {
1693
+ name: 'heyspex_promote_capture',
1694
+ description:
1695
+ 'Promote 1+ captures (HS-CAP-*) into a research doc (HS-RES-*, new or existing) with AI-assisted outline ([[HS-TASK-WQCE]] of [[HS-EPIC-M2J3]]). Two-phase flow when `target_research: "NEW"`: omit `accepted_outline` to get an AI-proposed `{ name, outline, tags, enhances_suggestion }` back for human review (Phase 1: no file writes), then call again with the same args plus `accepted_outline: {...}` to execute the transactional writes (Phase 2: creates the research file with sources_captures populated, mutates each capture\'s informs_research, sets promoted_to on first promotion only). Existing-research target skips Phase 1 — the tool goes straight to the bidirectional append and is idempotent on re-runs. Closed-research target is allowed but flagged with `warning: "TARGET_RESEARCH_CLOSED"`; the closed research keeps its existing enhances pointer untouched. The Phase-2 response also carries `research_still_stub` / `unresolved_placeholder_count` / `next_step` — when the resulting research doc still has unresolved `_TBD — fill in this section._` outline placeholders (freshly created, or an existing target that was never filled in), it is an outline of section headings only, NOT authored findings; run the `/hs-res` method against it before calling `heyspex_promote_research`, which will otherwise refuse with `RESEARCH_STILL_STUB` ([[HS-DSFO-49G3-DIAG-promote-capture-s-success-response]]). A NEW research doc INHERITS the family key of the first/primary capture_ids entry unconditionally ([[family-id-is-inherited-capture-research]], owner ruling 2026-07-12) — any number of research docs may now legally share one family key (the V3 shared-surname model, [[HS-ZU1Q-TASK-PNAS-flip-new-spec-minting-to-the-file-key]]: `id:` keys onto the per-file file key, not the family, so there is no collision to fall back from). Bidirectional consistency (capture.informs_research ⇔ research.sources_captures) is enforced by scripts/validate-vault-ids.mjs per [[HS-TASK-YJX6]].',
1696
+ inputSchema: {
1697
+ type: 'object',
1698
+ properties: {
1699
+ capture_ids: {
1700
+ type: 'array',
1701
+ minItems: 1,
1702
+ items: { type: 'string' },
1703
+ description: 'One or more HS-CAP-* ids to promote into the target research.',
1704
+ },
1705
+ target_research: {
1706
+ type: 'string',
1707
+ description:
1708
+ 'HS-RES-* id of an existing research, OR the literal string "NEW" to create a fresh research doc (then new_research_name + accepted_outline are required).',
1709
+ },
1710
+ new_research_name: {
1711
+ type: 'string',
1712
+ description:
1713
+ 'Required when target_research === "NEW". Working name used in the AI prompt as a hint; the human may edit before confirming accepted_outline.name.',
1714
+ },
1715
+ enhances: {
1716
+ type: 'string',
1717
+ description:
1718
+ 'Optional HS-MIL-* id or basename. When set on a NEW research create, the research is filed with enhances pointing at that milestone. Omit to leave the research exploratory (enhances added later by /hs-promote-research).',
1719
+ },
1720
+ accepted_outline: {
1721
+ type: 'object',
1722
+ description:
1723
+ 'Phase-2 execution payload (only required for target_research === "NEW"). The agent-confirmed (possibly edited) form of the Phase-1 proposed_outline. For existing target_research no outline is needed — sources_captures is just extended.',
1724
+ properties: {
1725
+ name: { type: 'string', description: 'Final research name (used to form the slug + title).' },
1726
+ outline: {
1727
+ type: 'array',
1728
+ items: { type: 'string' },
1729
+ description: 'Ordered list of section headings the new research doc should contain.',
1730
+ },
1731
+ tags: {
1732
+ type: 'array',
1733
+ items: { type: 'string' },
1734
+ description: 'Frontmatter tag list for the new research doc.',
1735
+ },
1736
+ enhances_suggestion: {
1737
+ type: 'string',
1738
+ description: 'Optional milestone basename suggestion echoed from the AI for the audit trail.',
1739
+ },
1740
+ research_type: {
1741
+ type: 'string',
1742
+ enum: ['new-feature', 'enhancement', 'diagnose', 'code-audit', 'reference'],
1743
+ description:
1744
+ "The declared purpose of the new research doc ([[entity-model]] §research, [[HS-120I-TASK-implement-research-type-field-end-to-end]]). Written into frontmatter; defaults to 'reference' when omitted. HS-MVC1 supplies a kind-derived suggestion here.",
1745
+ },
1746
+ scope: {
1747
+ type: 'string',
1748
+ enum: ['internal', 'codebase', 'external', 'all'],
1749
+ description:
1750
+ "How broad a search context this research was run at ([[entity-model]] §research, [[HS-NGD1-TASK-consolidate-and-fix-the-research-scope-field]]). Written into frontmatter; defaults to 'internal' when omitted.",
1751
+ },
1752
+ },
1753
+ required: ['name', 'outline'],
1754
+ additionalProperties: false,
1755
+ },
1756
+ force_promote: {
1757
+ type: 'boolean',
1758
+ description:
1759
+ 'Sufficiency-gate override ([[HS-0CFX-TASK]] / [[HS-3EFV-EPIC]]). Promotion is blocked with `error: "CAPTURE_INSUFFICIENT"` (naming the missing elements: core-idea, example, done-criteria, kind, user-stories) when a capture scores below the sufficiency bar — the intended response is to ELICIT the missing detail from the user and improve the capture, not to force. Pass true ONLY when the human explicitly says to promote anyway.',
1760
+ },
1761
+ },
1762
+ required: ['capture_ids', 'target_research'],
1763
+ additionalProperties: false,
1764
+ },
1765
+ },
1766
+ {
1767
+ name: 'heyspex_promote_capture_to_diagnose',
1768
+ description:
1769
+ 'Promote ONE bug-report capture (HS-CAP-*) into a new first-class diagnose spec (HS-DIAG-*, specs/diagnoses/) — the executable bug fix ([[HS-MIL-BFIX]]). Single-phase (no AI outline): the capture\'s Core Idea seeds the diagnose Symptom; Reproduction/Root cause/Red herrings/Fix/Sweep/Acceptance criteria are fill-in skeletons — Root cause prompts the level check (if you fix exactly this, can the same class of failure still happen?), Red herrings holds the tempting-but-wrong fixes you ruled out, Sweep is the sibling-sweep receipt, Acceptance criteria asks for a check that FAILS when the fix is reverted. Prompted, never enforced: a trivial bug may leave them empty. The diagnose is standalone (no parent) and carries `severity:` (error|warning|info; default warning) which ranks it on the Resolution surface + on-deck. Bidirectional linkage: diagnose.promoted_from ⇔ capture.promoted_to (first-promotion) + capture.informs_diagnose (idempotent append). Transactional commit with rollback. Use this instead of heyspex_promote_capture when the capture is a bug to fix, not research to aggregate.',
1770
+ inputSchema: {
1771
+ type: 'object',
1772
+ properties: {
1773
+ capture_id: {
1774
+ type: 'string',
1775
+ description: 'The HS-CAP-* id of the bug-report capture to promote into a diagnose.',
1776
+ },
1777
+ severity: {
1778
+ type: 'string',
1779
+ enum: ['error', 'warning', 'info'],
1780
+ description: 'Bug severity (default warning). error = P0/blocker, warning = normal, info = minor.',
1781
+ },
1782
+ name: {
1783
+ type: 'string',
1784
+ description: 'Optional diagnose name (used for title + slug). Defaults to the capture name.',
1785
+ },
1786
+ },
1787
+ required: ['capture_id'],
1788
+ additionalProperties: false,
1789
+ },
1790
+ },
1791
+ {
1792
+ name: 'heyspex_promote_research_to_diagnose',
1793
+ description:
1794
+ 'Promote a research investigation (HS-RES-*) that surfaced a bug into a new first-class diagnose spec (HS-DIAG-*, specs/diagnoses/) — the research-side sibling of heyspex_promote_capture_to_diagnose ([[HS-MSHY-TASK-research-diagnose-link-send-bugs-to-the]]). Reuses the existing diagnose machinery — no new infrastructure. Single-phase (no AI outline): the research\'s `## Executive Summary` (falling back to its `name:`) seeds the diagnose Symptom; Reproduction/Root cause/Red herrings/Fix/Sweep/Acceptance criteria are fill-in skeletons — Root cause prompts the level check (if you fix exactly this, can the same class of failure still happen?), Red herrings holds the tempting-but-wrong fixes you ruled out, Sweep is the sibling-sweep receipt, Acceptance criteria asks for a check that FAILS when the fix is reverted. Prompted, never enforced: a trivial bug may leave them empty. The diagnose is standalone (no parent) and carries `severity:` (error|warning|info; default warning). Bidirectional linkage: research.produces_diagnose ⇔ diagnose.derived_from (mirrors research.spawned_epics ⇔ epic.derived_from). Duplicate promotion of the SAME (research_id, diagnose name) pair is rejected with DIAGNOSE_ALREADY_PROMOTED (idempotent no-op, no new file) — a research doc is evergreen and may still spawn a genuinely different diagnose under a different name later. Transactional commit with rollback.',
1795
+ inputSchema: {
1796
+ type: 'object',
1797
+ properties: {
1798
+ research_id: {
1799
+ type: 'string',
1800
+ description: 'The HS-RES-* id of the research investigation to promote into a diagnose.',
1801
+ },
1802
+ severity: {
1803
+ type: 'string',
1804
+ enum: ['error', 'warning', 'info'],
1805
+ description: 'Bug severity (default warning). error = P0/blocker, warning = normal, info = minor.',
1806
+ },
1807
+ name: {
1808
+ type: 'string',
1809
+ description: 'Optional diagnose name (used for title + slug + the duplicate-promotion key). Defaults to the research name.',
1810
+ },
1811
+ },
1812
+ required: ['research_id'],
1813
+ additionalProperties: false,
1814
+ },
1815
+ },
1816
+ {
1817
+ name: 'heyspex_split_research',
1818
+ description:
1819
+ 'Split a research doc (HS-RES-*) into N (≥2) child research docs — each targeting exactly one milestone — before promotion ([[HS-TASK-GTH9]] of [[HS-EPIC-M2J3]]). The original stays as an audit archive carrying `split_into:` linkage; each child carries `split_from:` linkage plus a caller-provided body_slice and a subset of the original\'s sources_captures. Capture files\' `informs_research:` arrays update bidirectionally per [[HS-TASK-YJX6]]. Research is evergreen ([[research-is-evergreen]]): the archive stays `status: open` and re-split is guarded by the presence of `split_into:` (RESEARCH_ALREADY_SPLIT). Children land as `status: open` — milestone binding happens later via /hs-promote-research (split is content partitioning, not promotion). Each returned child also carries `research_still_stub` / `unresolved_placeholder_count` — set when its body_slice still has unresolved `_TBD — fill in this section._` outline placeholders (e.g. splitting a promote-capture outline stub before running /hs-res); a top-level `next_step` names the /hs-res follow-up when any child is still a stub, since `heyspex_promote_research` will otherwise refuse that child with `RESEARCH_STILL_STUB` ([[HS-DSFO-49G3-DIAG-promote-capture-s-success-response]]). Children inherit the ORIGINAL research\'s family key unconditionally ([[family-id-is-inherited-capture-research]], owner ruling 2026-07-12) — every child AND the archived original may now legally share one family key (the V3 shared-surname model, [[HS-ZU1Q-TASK-PNAS-flip-new-spec-minting-to-the-file-key]]: `id:` keys onto the per-file file key, not the family, so there is no collision to fall back from). All writes commit transactionally; any failure rolls back. Caller prepares body_slices — AI auto_partition is intentionally out of scope in v1.',
1820
+ inputSchema: {
1821
+ type: 'object',
1822
+ properties: {
1823
+ research_id: {
1824
+ type: 'string',
1825
+ description: 'Id or basename of the research to split (must be in vault and not already split — no `split_into:`). Accepts the frontmatter `id:`, the full file basename, a [[wikilink]], or a legacy HS-RES-* id.',
1826
+ },
1827
+ splits: {
1828
+ type: 'array',
1829
+ minItems: 2,
1830
+ description:
1831
+ 'Array of ≥2 split entries. Each entry produces one child research doc.',
1832
+ items: {
1833
+ type: 'object',
1834
+ properties: {
1835
+ name: { type: 'string', description: 'Human-readable child title (becomes name + slug).' },
1836
+ target_milestone: {
1837
+ type: 'string',
1838
+ description:
1839
+ 'HS-MIL-* id or basename of the target milestone, OR the literal string "NEW" to indicate a future milestone (recorded in the body callout; not allocated at split time).',
1840
+ },
1841
+ new_milestone_name: {
1842
+ type: 'string',
1843
+ description: 'Required when target_milestone === "NEW". Human-readable milestone title (recorded in body callout only).',
1844
+ },
1845
+ body_slice: {
1846
+ type: 'string',
1847
+ description: 'Markdown content for this child\'s body. Caller-prepared (no auto-partition in v1).',
1848
+ },
1849
+ sources_captures: {
1850
+ type: 'array',
1851
+ items: { type: 'string' },
1852
+ description:
1853
+ 'Subset of the original research\'s sources_captures (HS-CAP-* ids or basenames). May be empty. May overlap across children (Capture → Research is M:N per [[HS-TASK-YJX6]]).',
1854
+ },
1855
+ },
1856
+ required: ['name', 'target_milestone', 'body_slice', 'sources_captures'],
1857
+ additionalProperties: false,
1858
+ },
1859
+ },
1860
+ },
1861
+ required: ['research_id', 'splits'],
1862
+ additionalProperties: false,
1863
+ },
1864
+ },
1865
+ {
1866
+ name: 'heyspex_design_list_patterns',
1867
+ budget: READ_TOOL_BUDGET,
1868
+ description:
1869
+ 'List design system patterns from spex-console/public/design-system/. Returns compact summaries (name, title, layer, tags, summary) — call heyspex_design_get_pattern for the full registry-item.json. Filters: tag (case-insensitive hsTag), layer (primitive|semantic|component|pattern), query (substring against name/title/description).',
1870
+ inputSchema: {
1871
+ type: 'object',
1872
+ properties: {
1873
+ tag: { type: 'string', description: 'Filter to patterns whose hsTags contains this tag (case-insensitive)' },
1874
+ layer: {
1875
+ type: 'string',
1876
+ enum: ['primitive', 'semantic', 'component', 'pattern'],
1877
+ description: 'Filter by hsLayer classification',
1878
+ },
1879
+ query: { type: 'string', description: 'Substring match against name/title/description (case-insensitive)' },
1880
+ },
1881
+ additionalProperties: false,
1882
+ },
1883
+ },
1884
+ {
1885
+ name: 'heyspex_design_get_pattern',
1886
+ budget: READ_TOOL_BUDGET,
1887
+ description:
1888
+ 'Fetch one full registry-item.json from spex-console/public/design-system/r/<name>.json (the shadcn-compatible payload with HeySpex extensions). Prefer this over grepping spex-console/DESIGN.md for a single pattern.',
1889
+ inputSchema: {
1890
+ type: 'object',
1891
+ properties: {
1892
+ name: { type: 'string', description: 'Pattern slug (e.g. "on-deck-callout-sky")' },
1893
+ includeCode: {
1894
+ type: 'boolean',
1895
+ default: true,
1896
+ description: 'Include hsCodePreview HTML snippet (default true). Set false to slim the response.',
1897
+ },
1898
+ },
1899
+ required: ['name'],
1900
+ additionalProperties: false,
1901
+ },
1902
+ },
1903
+ {
1904
+ name: 'heyspex_design_get_token',
1905
+ budget: READ_TOOL_BUDGET,
1906
+ description:
1907
+ 'Look up a CSS variable (e.g. "--accent-green") across registry items plus tokens.dtcg.json. Returns dark + light values, the DTCG token kind, the token group path, and the patterns that reference the variable.',
1908
+ inputSchema: {
1909
+ type: 'object',
1910
+ properties: {
1911
+ var: {
1912
+ type: 'string',
1913
+ description: 'CSS variable name (with or without leading "--", e.g. "--accent-green" or "accent-green")',
1914
+ },
1915
+ },
1916
+ required: ['var'],
1917
+ additionalProperties: false,
1918
+ },
1919
+ },
1920
+ {
1921
+ name: 'heyspex_design_get_doc',
1922
+ budget: READ_TOOL_BUDGET,
1923
+ description:
1924
+ 'Fetch the spex-console/DESIGN.md prose excerpt for a pattern anchor. Anchors are leading-line markers of the form **`pattern-name`** — the excerpt runs from the anchor line through the next heading or anchor.',
1925
+ inputSchema: {
1926
+ type: 'object',
1927
+ properties: {
1928
+ name: { type: 'string', description: 'Pattern slug to look up (matches the registry item designDocAnchor field)' },
1929
+ },
1930
+ required: ['name'],
1931
+ additionalProperties: false,
1932
+ },
1933
+ },
1934
+ {
1935
+ name: 'heyspex_design_propose_pattern',
1936
+ description:
1937
+ 'File a new design pattern as a draft proposal at specs/design-proposals/<name>.json. Mirrors registry-item.json shape with hsStatus: "proposed" + provenance fields (hsProposedAt, hsProposedBy). Humans review and promote into spex-console/public/design-system/r/. Folder is exempt from manifest registration.',
1938
+ inputSchema: {
1939
+ type: 'object',
1940
+ properties: {
1941
+ name: {
1942
+ type: 'string',
1943
+ description: 'Kebab-case slug for the new pattern (e.g. "agent-handoff-callout"). Must be unique against shipped registry items.',
1944
+ },
1945
+ title: { type: 'string', description: 'Short human title for the pattern.' },
1946
+ description: {
1947
+ type: 'string',
1948
+ description: 'One-paragraph description (purpose, anatomy, when to use).',
1949
+ },
1950
+ hsLayer: {
1951
+ type: 'string',
1952
+ enum: ['primitive', 'semantic', 'component', 'pattern'],
1953
+ description: 'Layer classification.',
1954
+ },
1955
+ hsTags: {
1956
+ type: 'array',
1957
+ items: { type: 'string' },
1958
+ description: 'Tags (e.g. ["callout", "agent", "on-deck"]).',
1959
+ },
1960
+ hsDecisionRefs: {
1961
+ type: 'array',
1962
+ items: { type: 'string' },
1963
+ description: 'Wikilink basenames pointing to memory decisions that govern the pattern.',
1964
+ },
1965
+ cssVars: {
1966
+ type: 'object',
1967
+ description: 'Optional dark + light CSS variable blocks. Shape: { dark: { "--name": "#hex", ... }, light: { ... } }',
1968
+ },
1969
+ hsCodePreview: { type: 'string', description: 'Optional standalone HTML snippet preview.' },
1970
+ designDocAnchor: { type: 'string', description: 'DESIGN.md anchor slug (default = name).' },
1971
+ proposed_by: {
1972
+ type: 'string',
1973
+ description: 'Display name of the agent/human filing the proposal.',
1974
+ },
1975
+ },
1976
+ required: ['name', 'title', 'description', 'hsLayer'],
1977
+ additionalProperties: false,
1978
+ },
1979
+ },
1980
+ {
1981
+ name: 'heyspex_list_procedures',
1982
+ budget: listBudget('procedures'),
1983
+ description:
1984
+ 'List HeySpex procedures from specs/protocols/.catalog.json. Returns compact summaries (name, id, title, tags, triggers, summary, path) — call heyspex_get_procedure for the full markdown body. Filters: tag (case-insensitive), trigger (intent like "file_milestone", "destructive_action"), query (substring against name/title/summary).',
1985
+ inputSchema: {
1986
+ type: 'object',
1987
+ properties: {
1988
+ tag: {
1989
+ type: 'string',
1990
+ description: 'Filter to procedures whose tags includes this value (case-insensitive)',
1991
+ },
1992
+ trigger: {
1993
+ type: 'string',
1994
+ description:
1995
+ 'Filter to procedures that declare this trigger (e.g. "file_milestone", "destructive_action", "memory_creation", "close_session")',
1996
+ },
1997
+ query: {
1998
+ type: 'string',
1999
+ description: 'Substring match against name/title/summary (case-insensitive)',
2000
+ },
2001
+ limit: {
2002
+ type: 'number',
2003
+ default: 10,
2004
+ description: 'Page size (default: 10, max: 50). The response is ONE page under `procedures` plus `next_cursor`, fitted to an 8 KB byte budget — never the whole catalog, never a total count ([[HS-E66W-8KK5-TASK-mcp-list-tools-page-with-a-cursor-and-a]]).',
2005
+ },
2006
+ after: {
2007
+ type: 'string',
2008
+ description: 'Opaque cursor: the `next_cursor` value of the prior page. Omit for page 1. A page whose `next_cursor` is null was the last one.',
2009
+ },
2010
+ },
2011
+ additionalProperties: false,
2012
+ },
2013
+ },
2014
+ {
2015
+ name: 'heyspex_get_procedure',
2016
+ budget: READ_TOOL_BUDGET,
2017
+ description:
2018
+ 'Fetch one HeySpex procedure: catalog metadata plus the full markdown body from specs/protocols/<name>.md. Prefer this over Read-ing the protocol file directly when you only need one procedure.',
2019
+ inputSchema: {
2020
+ type: 'object',
2021
+ properties: {
2022
+ name: {
2023
+ type: 'string',
2024
+ description: 'Procedure slug — the filename of the protocol minus the .md extension (e.g. "spec-hierarchy", "destructive-action-checklist")',
2025
+ },
2026
+ },
2027
+ required: ['name'],
2028
+ additionalProperties: false,
2029
+ },
2030
+ },
2031
+ {
2032
+ name: 'heyspex_file_spec',
2033
+ // Trimmed 2026-09-09 ([[HS-EZIX-NYPR-TASK-measure-default-tool-schema-bytes-per]]
2034
+ // AC2 — one of the two largest default-visible tool descriptions). Meaning
2035
+ // preserved (every enum value, error code, and required-field rule below
2036
+ // survives); cut was rationale/backstory prose (why the rule exists) and
2037
+ // spec-id citations that don't change how an agent calls the tool.
2038
+ description:
2039
+ 'File a NEW spec of any non-capture kind in one call (POSTs /api/specs) — the deterministic path instead of hand-authoring frontmatter: the server allocates the id, generates frontmatter, links the parent, and validates. type ∈ { milestone, epic, task, diagnose, research, memory-lesson, memory-decision, memory-reference, memory-ui-pattern, review, pillar, adr }; captures are NOT supported here (use heyspex_active_capture — a type:capture call returns 400). ADR is always filed status:proposed; a non-proposed status at filing is refused (400 adr_status_forced) — the owner\'s later status flip is the recorded decision. Milestone/epic MUST pass `summary` at planning time (plan ingest / breakdown, never with tasks); task MUST pass `summary` only from the `/hs-next` breakdown. Missing summary → summary_required. Epics ALSO REQUIRE frontmatter.pillar (an existing HS-PILR theme); missing/unknown → 400 pillar_required (call again with type:"pillar" to create one). diagnose files a standalone HS-DIAG (specs/diagnoses/), optional epic/milestone parent, severity defaults to "warning". Returns { spec_id, path, landed } — landed is measured after the write (which console, the absolute path, whether it\'s really on disk, the active dev-mode). A cloud landing while this session has a local clone returns isError WRITE_LANDED_OFF_DISK with the real spec id (do not re-file); declare expect_tier: "disk"|"cloud" to enforce which. POST errors (invalid parent, missing pillar, capture rejection) surface verbatim.',
2040
+ inputSchema: {
2041
+ type: 'object',
2042
+ properties: {
2043
+ type: {
2044
+ type: 'string',
2045
+ description:
2046
+ 'Entity kind: milestone | epic | task | diagnose | research | memory-lesson | memory-decision | memory-reference | memory-ui-pattern | review | pillar | adr. NOT capture (use heyspex_active_capture). diagnose = a quality note / bug that surfaces on the Resolution center (standalone by default; severity defaults to "warning"). adr = files proposed, always; the owner\'s later status flip to accepted is the recorded decision.',
2047
+ },
2048
+ name: {
2049
+ type: 'string',
2050
+ description: 'Human-readable title. The slug is derived from it server-side.',
2051
+ },
2052
+ summary: {
2053
+ type: 'string',
2054
+ description:
2055
+ 'Required plain-language summary at filing time. Milestone/epic: planning phase only (plan ingest / breakdown — never with tasks). Task: What\'s Next Launch `/hs-next` breakdown only. Alias: frontmatter.summary.',
2056
+ },
2057
+ frontmatter: {
2058
+ type: 'object',
2059
+ description:
2060
+ 'Optional frontmatter overrides/extras. Set the parent linkage here: task → parent: "[[HS-EPIC-…]]" (or a parent task), epic → milestone: "[[HS-MIL-…]]", review → parent: "[[<spec under review>]]" (required for review). Epics ALSO REQUIRE pillar: "[[HS-PILR-…]]" (the perpetual theme; filing fails 400 pillar_required without a resolvable one). Do NOT set id — it is auto-assigned.',
2061
+ additionalProperties: true,
2062
+ },
2063
+ body: {
2064
+ type: 'string',
2065
+ description:
2066
+ 'Optional markdown body. When omitted, the server injects the canonical body skeleton for the kind.',
2067
+ },
2068
+ recipe_context: {
2069
+ type: 'object',
2070
+ description:
2071
+ 'Optional workflow-recipe start context (the object heyspex_start_recipe returns — buildRecipeStartContext output) to stamp recipe provenance on the filed spec. Forwarded verbatim to POST /api/specs, where the server reads recipe_id + tool_scope and stamps them into frontmatter ([[HS-98G6-TASK]] / [[HS-5G0P-EPIC]] AC5). Omit for non-recipe filings — the server no-ops when absent.',
2072
+ additionalProperties: true,
2073
+ },
2074
+ checks: {
2075
+ type: 'array',
2076
+ description:
2077
+ 'OPTIONAL — the FROZEN checks manifest ([[checks-manifest-authoring]]), authored at breakdown BEFORE any builder sees the brief. Optional means optional: omitting it FILES THE TASK ANYWAY with a `no_checks_manifest` warning, and that task then reaches build-complete on the agent\'s own unverified self-report, because the verification gate in apply-status-transition.ts only bites where a manifest exists. So authoring one is how a task becomes verifiable — it is not enforced, and nothing downstream will notice you skipped it. One row per acceptance criterion, same order. The server writes specs/checks/<new-id>.checks.json and `hs check run <id>` is the per-criterion verdict. Row: { ac: <1-based position>, text: "<the criterion, verbatim>", method: "machine" | "property" | "human", checks: [ { kind: "test"|"lint"|"types"|"command"|"guard", run: "<command>", expect: "exit-0", cwd?: "<dir>" } ] }. A method:"human" row may carry no runnable check; machine/property rows must carry at least one. Two rules DO reject a manifest at filing: (1) commands run from the REPO ROOT (a cwd-less check is retried against spex-console/ as a fallback), so a command that resolves config from its working directory MUST name that directory — npx/pnpm/yarn/bun runners, `npm run <script>` and `npm test`, node_modules/.bin binaries, and bare tsc/vitest/jest/eslint/prettier/tsx/vite/next off PATH, e.g. { kind: "types", run: "npx tsc --noEmit", cwd: "spex-console" } ([[HS-IRCO-D0ME-DIAG-checks-manifests-silently-false-green]]: ~50 frozen manifests were permanently red for a reason unrelated to the code); (2) a machine/property criterion whose check cannot fail (`echo ok`, `true`, `:`, `exit 0`) is refused — a green light wired to nothing is worse than no manifest. A filtered test command (--filter/--testNamePattern/--grep/-k) asserting only exit-0 is accepted with a warning, since it exits 0 when the filter matches nothing.',
2078
+ items: { type: 'object', additionalProperties: true },
2079
+ },
2080
+ expect_tier: {
2081
+ type: 'string',
2082
+ enum: ['disk', 'cloud'],
2083
+ description:
2084
+ 'OPTIONAL — declare WHERE you expect the write to land, and the call fails instead of quietly landing elsewhere ([[HS-IOJW-5YDH-DIAG]]). "disk" = a file in this clone; "cloud" = the Neon index only. Omit it and the default expectation applies: any clone-less cloud landing returns WRITE_LANDED_OFF_DISK rather than plain success, because that is the shape that silently lost work on 2026-08-21. Pass "cloud" when you are deliberately working against app.heyspex.com. Every response, error or not, carries a measured `landed` block naming the console, the path, and whether the file is actually there.',
2085
+ },
2086
+ },
2087
+ required: ['type', 'name'],
2088
+ additionalProperties: false,
2089
+ },
2090
+ },
2091
+ {
2092
+ name: 'heyspex_import_agent',
2093
+ description:
2094
+ 'Import a community agent role into a type:agent vault spec (POSTs /api/agents/import). Parity with `hs agent import` — resolves a vendored collection role, normalizes via the shared import-normalizer core, files with server-allocated id, and records source_attribution. Returns { spec_id, path, warnings }. Requires a reachable console.',
2095
+ inputSchema: {
2096
+ type: 'object',
2097
+ properties: {
2098
+ collection: {
2099
+ type: 'string',
2100
+ description: 'Vendored collection alias (e.g. "voltagent")',
2101
+ },
2102
+ role: {
2103
+ type: 'string',
2104
+ description: 'Role identifier within the collection (e.g. "deployment-engineer")',
2105
+ },
2106
+ dry_run: {
2107
+ type: 'boolean',
2108
+ default: false,
2109
+ description: 'Normalize only — do not write a spec',
2110
+ },
2111
+ },
2112
+ required: ['collection', 'role'],
2113
+ additionalProperties: false,
2114
+ },
2115
+ },
2116
+ {
2117
+ name: 'heyspex_preflight',
2118
+ description:
2119
+ 'Pre-flight checklist for a named agent intent. Returns the procedures the agent should consult before acting, each annotated with `severity` ("required", "recommended", or "advisory" — a procedure this vault does not have is demoted to advisory with `demoted_from` and `reason`) and a short `why`. Call before high-stakes actions (filing a milestone/epic/task, destructive operations, protocol edits, memory creation, session close, spec claim) — then fetch the procedure body via heyspex_get_procedure. THIS CALL IS NOW CHECKED: a successful preflight records proof-of-use for that intent for the rest of the session, and the tools it governs read it. `heyspex_claim_spec` REFUSES with `PREFLIGHT_NOT_RUN` without a matching `claim_spec`/`execute_spec` preflight, and `heyspex_submit_for_review` refuses without `close_session`. `heyspex_file_spec` currently WARNS (attaches `preflight_warning`) when the matching `file_*` intent was not run — raise it to a refusal with HEYSPEX_PREFLIGHT_GATE_FILE_SPEC=refuse. Break-glass for the whole gate: HEYSPEX_PREFLIGHT_GATE_OFF=1.',
2120
+ inputSchema: {
2121
+ type: 'object',
2122
+ properties: {
2123
+ intent: {
2124
+ type: 'string',
2125
+ enum: PREFLIGHT_INTENTS,
2126
+ description: `Agent intent to look up. One of: ${PREFLIGHT_INTENTS.join(', ')}.`,
2127
+ },
2128
+ thread_type: {
2129
+ type: 'string',
2130
+ enum: CLOSE_SESSION_THREAD_TYPES,
2131
+ description:
2132
+ 'ONLY for intent "close_session": declare what kind of session this was, and the response returns that thread type\'s CONDITIONAL close-out steps instead of the full static checklist. Thread-type is declared (not inferred) — some signals are not session-scoped. Omit it to get the full checklist plus the list of thread types to choose from. For a mixed session, request each applicable type and run the UNION of their steps. One of: capture_only | research_only | plan_ingest_only | one_task | multi_task_epic | resolve_diagnose | mid_rollover.',
2133
+ },
2134
+ },
2135
+ required: ['intent'],
2136
+ additionalProperties: false,
2137
+ },
2138
+ },
2139
+ {
2140
+ name: 'heyspex_list_recipes',
2141
+ budget: listBudget('recipes'),
2142
+ description:
2143
+ 'List HeySpex workflow-recipes from specs/recipes/ — ready-made templates that structure the START of a multi-step assisted session (Bug fix, Feature, Investigation, Refactor). With no args, returns every recipe. With intent, returns only recipes whose intent_signatures (or name/description) match, each annotated with match_strength ("high" for a signature-exact hit, "medium" for a substring/name hit). Start one with heyspex_start_recipe. Matching is case- and separator-insensitive ("fix bug" == "bug_fix").',
2144
+ inputSchema: {
2145
+ type: 'object',
2146
+ properties: {
2147
+ intent: {
2148
+ type: 'string',
2149
+ description:
2150
+ 'Optional plain intent to match against recipe intent_signatures (e.g. "bug_fix", "fix bug", "refactor", "new feature"). Omit to list all recipes.',
2151
+ },
2152
+ },
2153
+ additionalProperties: false,
2154
+ },
2155
+ },
2156
+ {
2157
+ name: 'heyspex_start_recipe',
2158
+ description:
2159
+ 'Start a HeySpex workflow-recipe: creates and pins a capture session (when the console is reachable) and returns the recipe start context — the capture_sequence to walk, the spec_template to pre-fill the resulting spec, and the recipe_id + tool_scope to stamp for provenance. Recipes structure the start of work; execution still happens in your coding agent. Pass recipe_id as the authored id ("HS-RECIPE-bug-fix") or the bare slug ("bug-fix"). If the console is unreachable the context is still returned so the flow can be walked later (status: deferred).',
2160
+ inputSchema: {
2161
+ type: 'object',
2162
+ properties: {
2163
+ recipe_id: {
2164
+ type: 'string',
2165
+ description: 'Recipe to start — authored id ("HS-RECIPE-bug-fix") or slug ("bug-fix").',
2166
+ },
2167
+ capture_text: {
2168
+ type: 'string',
2169
+ description: "Optional seed text for the capture (the user's initial words / the thing being worked on).",
2170
+ },
2171
+ },
2172
+ required: ['recipe_id'],
2173
+ additionalProperties: false,
2174
+ },
2175
+ },
2176
+ {
2177
+ name: 'heyspex_canvas_inbox',
2178
+ description:
2179
+ 'Pull any pending HeySpex canvas write-back messages for THIS Claude Code session ([[HS-1ROV-EPIC]] stage 5c). When a user types a reply into the session-mirror canvas in the console, it queues in the companion daemon; call this to claim those messages and act on them. Claiming marks them delivered so the Stop-hook drain does NOT re-deliver them (exactly-once across both channels — the daemon outbox is the single source of truth). Session id: pass session_id explicitly, or omit to use CLAUDE_CODE_SESSION_ID. Companion not running is a normal, non-error result: { ok:true, messages:[], note }. Returns { ok, sessionId, delivered, messages[] }.',
2180
+ inputSchema: {
2181
+ type: 'object',
2182
+ properties: {
2183
+ session_id: {
2184
+ type: 'string',
2185
+ description:
2186
+ 'The Claude Code session id to claim canvas messages for. Omit to use CLAUDE_CODE_SESSION_ID from the environment.',
2187
+ },
2188
+ },
2189
+ additionalProperties: false,
2190
+ },
2191
+ },
2192
+ {
2193
+ name: 'heyspex_dev_server_status',
2194
+ // Trimmed 2026-09-09 ([[HS-EZIX-NYPR-TASK-measure-default-tool-schema-bytes-per]]
2195
+ // AC2 — the other of the two largest default-visible tool descriptions).
2196
+ // Meaning preserved (both probes, both fields, the discrepancy enum, the
2197
+ // call-before-server-impact rule); cut was the worked example illustrating
2198
+ // why `uiHealthy` exists.
2199
+ description:
2200
+ 'Ground-truth status of the spex-console dev server: running, and on which port(s)? Derived from a live HTTP probe (`/api/version`) plus an OS listener scan — NOT from .heyspex/dev-server.lock.json, which can be stale, miss an untracked bare `next dev`, or disagree on port; the lockfile is read only as a hint, any disagreement surfaces as a discrepancy (stale_lock | untracked_server | multiple_servers | port_mismatch | page_probe_failed). `running` is API-probe-only — a green API does NOT prove the UI renders. Check `uiHealthy` (a second probe against a real page route; null = no console identified, true/false = checked) before trusting `running` alone as "the console is usable". CALL THIS before any server-impacting action — a migration, Neon branch reset, dependency install, deleting .next, or a second dev server — and stop a live server first. Returns { running, uiHealthy, servers[] (each with a `pageProbe` when identified as console), lockfile, discrepancies, candidatePorts, checkedAt }. Takes no arguments.',
2201
+ inputSchema: {
2202
+ type: 'object',
2203
+ properties: {},
2204
+ additionalProperties: false,
2205
+ },
2206
+ },
2207
+ {
2208
+ name: 'heyspex_dev_mode',
2209
+ description:
2210
+ 'Get or set the HeySpex dev/prod mode + local/cloud reliance (epic HS-Q6M2, /hs-dev). Model: mode prod|dev; when dev, reliance local|cloud. prod and dev+cloud => cloud console (app.heyspex.com), Neon reads, cloud writes; dev+local => localhost:3000 console, disk reads, local writes. action:"get" returns the fully resolved state with the SOURCE of every value (env override → ~/.heyspex/dev-mode.json machine-local override → cloud admin setting → default prod) — a stale local override is flagged, never silent. action:"set" applies the switch: writes ~/.heyspex/dev-mode.json (the cross-component contract file the Companion also reads), updates console_url in ~/.heyspex/credentials.yaml (rung 2 of the shared console-url resolver — MCP/hooks/dev-factory repoint automatically), and best-effort updates the cloud account_settings row. Never edits repo-tracked files. mode local|cloud imply dev with that reliance. CLI face: `hs dev [mode:local|mode:cloud|mode:prod]`; console face: Settings → ADMIN → Dev Tools.',
2211
+ inputSchema: {
2212
+ type: 'object',
2213
+ properties: {
2214
+ action: {
2215
+ type: 'string',
2216
+ enum: ['get', 'set'],
2217
+ description: '"get" = resolved state + sources; "set" = apply a mode change.',
2218
+ },
2219
+ mode: {
2220
+ type: 'string',
2221
+ enum: ['prod', 'dev', 'local', 'cloud'],
2222
+ description: 'For set: target mode. local/cloud imply dev with that reliance.',
2223
+ },
2224
+ reliance: {
2225
+ type: 'string',
2226
+ enum: ['local', 'cloud'],
2227
+ description: 'For set with mode:"dev": which reliance (default local).',
2228
+ },
2229
+ },
2230
+ required: ['action'],
2231
+ additionalProperties: false,
2232
+ },
2233
+ },
2234
+ {
2235
+ name: 'heyspex_settings_list',
2236
+ budget: READ_TOOL_BUDGET,
2237
+ description:
2238
+ 'List HeySpex workflow settings — every scalar key in .heyspex/config.yaml as dotted keys with parsed values (e.g. agent_behaviors.capture_on_idea.enabled). Read-only. Optional prefix narrows to one block (e.g. "agent_behaviors" or "agent_behaviors.intent_classifier"). Part of the settings capability (core: vendor/heyspex-mcp/config-settings.mjs; CLI face: `hs settings list`; console face: Settings → Agent Behaviors).',
2239
+ inputSchema: {
2240
+ type: 'object',
2241
+ properties: {
2242
+ prefix: {
2243
+ type: 'string',
2244
+ description: 'Optional dotted-key prefix filter (e.g. "agent_behaviors").',
2245
+ },
2246
+ },
2247
+ additionalProperties: false,
2248
+ },
2249
+ },
2250
+ {
2251
+ name: 'heyspex_settings_get',
2252
+ budget: READ_TOOL_BUDGET,
2253
+ description:
2254
+ 'Read one HeySpex workflow setting from .heyspex/config.yaml by dotted key (e.g. "agent_behaviors.intent_classifier.min_confidence"). Read-only. Returns { key, found, value }; found:false means the key is absent and the consumer falls back to its documented default.',
2255
+ inputSchema: {
2256
+ type: 'object',
2257
+ properties: {
2258
+ key: {
2259
+ type: 'string',
2260
+ description: 'Dotted settings key, e.g. "agent_behaviors.capture_on_idea.ask_first".',
2261
+ },
2262
+ },
2263
+ required: ['key'],
2264
+ additionalProperties: false,
2265
+ },
2266
+ },
2267
+ {
2268
+ name: 'heyspex_settings_set',
2269
+ description:
2270
+ 'Change one HeySpex workflow setting in .heyspex/config.yaml — PERMISSION-GATED two-step flow (agent-settings-protocol). Settings writes always require EXPLICIT user approval, regardless of the ask_forgiveness_not_permission switch. Call 1 (key + value, NO confirm_token): nothing is written; returns status "confirmation_required" with the current value and a confirm_token. You MUST show the user the exact change and get their explicit approval. Call 2 (confirm_token only): applies the reviewed change and audits it to .heyspex/settings-history.jsonl. Tokens expire after 10 minutes. NEVER edit .heyspex/config.yaml directly — a PreToolUse gate blocks that route.',
2271
+ inputSchema: {
2272
+ type: 'object',
2273
+ properties: {
2274
+ key: {
2275
+ type: 'string',
2276
+ description: 'Dotted settings key to change (call 1). Ignored when confirm_token is given.',
2277
+ },
2278
+ value: {
2279
+ description: 'New scalar value (string, number, boolean, or null) — call 1. Ignored when confirm_token is given.',
2280
+ type: ['string', 'number', 'boolean', 'null'],
2281
+ },
2282
+ confirm_token: {
2283
+ type: 'string',
2284
+ description: 'The token from a prior confirmation_required response — call 2, ONLY after the user explicitly approved the exact change.',
2285
+ },
2286
+ actor: {
2287
+ type: 'string',
2288
+ description: 'Optional audit actor label (defaults to "agent (MCP)").',
2289
+ },
2290
+ },
2291
+ additionalProperties: false,
2292
+ },
2293
+ },
2294
+ {
2295
+ name: 'heyspex_resolve_research_plan',
2296
+ description:
2297
+ 'Resolve the CONFIGURED research plan from .heyspex/config.yaml agent_behaviors.research — the authoritative, live-read source of truth for "which research method/backend applies right now" ([[HS-LFUH-DIAG-W1YE]]). Call this before/at research time (especially before RUNG 0 `sequential` search/scrape) rather than trusting only the research-trigger hook injection, which is a best-effort snapshot taken at prompt-submit time and can go stale mid-session (a Firecrawl lease can be granted or refused, a box can go up or down). Returns { method, default_agent, default_source, resource: {source, endpoint}, note }. Read-only, no side effects. Scope note: this reads the CONFIGURED plan only — it does NOT resolve resource profiles, primary/fallback chains, or capability gating (that generalized feature is a separate, not-yet-built system: [[HS-X5GJ-RES-EU6J]]).',
2298
+ inputSchema: {
2299
+ type: 'object',
2300
+ properties: {},
2301
+ additionalProperties: false,
2302
+ },
2303
+ },
2304
+ {
2305
+ name: 'heyspex_firecrawl_research',
2306
+ description:
2307
+ 'Run ONE Firecrawl search or scrape for THIS interactive turn (RUNG 0 `sequential` — "the main agent runs the searches itself") — makes the Firecrawl backend actually REACHABLE from an interactive agent, which previously had no way to honor a configured `agent_behaviors.research.default_source: firecrawl-self-hosted/firecrawl-hosted` ([[HS-LFUH-DIAG-W1YE]] root-cause part 2). Wraps the EXISTING, self-tested dev-factory/spexcrawl/default-backends.mjs + firecrawl-adapter.mjs implementation unchanged — no search/scrape logic is reimplemented here. Respects the existing availability gates as-is, never bypasses them: the Ollama-XOR-Firecrawl mode lease (self-hosted — Night Action) or the fail-closed attended-daytime credit gate (hosted api.firecrawl.dev; this tool NEVER sets RESEARCH_ATTENDED itself, that env var is owner-only). mode:"search" needs `query`; mode:"scrape" needs `url`. ALWAYS returns ok:true with an explicit envelope — on success `{used_source:"firecrawl-self-hosted", downgraded:false, results|text}`; when the configured backend is unavailable `{used_source:"web-native", requested_source, downgraded:true, reason}` — on downgraded:true, YOU must fall back to your own native WebSearch/WebFetch and tell the user plainly that the configured backend was unavailable; never substitute silently. Only ok:false is a caller error (missing query/url).',
2308
+ inputSchema: {
2309
+ type: 'object',
2310
+ properties: {
2311
+ mode: {
2312
+ type: 'string',
2313
+ enum: ['search', 'scrape'],
2314
+ description: 'Which Firecrawl operation to run. Default "search".',
2315
+ },
2316
+ query: {
2317
+ type: 'string',
2318
+ description: 'Search query — required when mode is "search".',
2319
+ },
2320
+ url: {
2321
+ type: 'string',
2322
+ description: 'URL to scrape — required when mode is "scrape".',
2323
+ },
2324
+ spec_id: {
2325
+ type: 'string',
2326
+ description: 'Optional spec id for cost-ledger attribution of the call.',
2327
+ },
2328
+ },
2329
+ additionalProperties: false,
2330
+ },
2331
+ },
2332
+ {
2333
+ name: 'heyspex_plan_review',
2334
+ description:
2335
+ 'Plan-mode quality + canonical check ([[HS-EPIC-PLRV]]). Runs overlap grep, code audit, canonical detection, vocabulary reconciliation, and Axis-B drift over a draft plan OR a proposed epic→task breakdown (scope: "breakdown"). Writes .heyspex/plan-reviews/<sha256>.json. Pass plan_path/plan_content for plan mode, OR scope breakdown + epic_id + proposed_tasks for /hs-next decomposition review (advisory v1). Optional quality_pass on breakdown runs Pass 7 (MECE/sizing/ordering); pass quality_findings from the session agent after LLM judgment — HeySpex never calls an in-app model.',
2336
+ inputSchema: {
2337
+ type: 'object',
2338
+ properties: {
2339
+ plan_path: {
2340
+ type: 'string',
2341
+ description: 'Path to the plan markdown file (absolute, or relative to the vault root). Preferred over plan_content.',
2342
+ },
2343
+ plan_content: {
2344
+ type: 'string',
2345
+ description: 'Inline plan markdown. Use when the plan is not yet on disk (Cursor / inline fallback). Ignored if plan_path is given.',
2346
+ },
2347
+ scope: {
2348
+ type: 'string',
2349
+ enum: ['plan', 'breakdown'],
2350
+ description: 'Review target: "plan" (default, ExitPlanMode) or "breakdown" (/hs-next task-set review).',
2351
+ },
2352
+ epic_id: {
2353
+ type: 'string',
2354
+ description: 'When scope is "breakdown": parent epic id (e.g. HS-EPIC-XXXX). Required with proposed_tasks unless plan_content/plan_path is given.',
2355
+ },
2356
+ epic_name: {
2357
+ type: 'string',
2358
+ description: 'Optional epic title for breakdown scope serialization.',
2359
+ },
2360
+ proposed_tasks: {
2361
+ type: 'array',
2362
+ description: 'When scope is "breakdown": draft tasks as { title, scope?, files?, symbols?, depends_on? }[].',
2363
+ items: {
2364
+ type: 'object',
2365
+ properties: {
2366
+ title: { type: 'string' },
2367
+ name: { type: 'string' },
2368
+ scope: { type: 'string' },
2369
+ files: { type: 'array', items: { type: 'string' } },
2370
+ symbols: { type: 'array', items: { type: 'string' } },
2371
+ depends_on: { type: 'array', items: { type: 'string' } },
2372
+ },
2373
+ },
2374
+ },
2375
+ quality_pass: {
2376
+ type: 'boolean',
2377
+ description:
2378
+ 'Breakdown only: run Pass 7 quality checks (MECE, sizing, ordering). Default false — no latency when omitted.',
2379
+ },
2380
+ quality_findings: {
2381
+ type: 'array',
2382
+ description:
2383
+ 'Breakdown + quality_pass: agent-as-judge findings from the session LLM ({ kind, title, note }). HeySpex validates and merges with heuristic checks; it never calls an in-app model.',
2384
+ items: {
2385
+ type: 'object',
2386
+ properties: {
2387
+ kind: { type: 'string', enum: ['mece_gap', 'oversize', 'missing_depends_on'] },
2388
+ title: { type: 'string' },
2389
+ note: { type: 'string' },
2390
+ },
2391
+ required: ['kind', 'title', 'note'],
2392
+ },
2393
+ },
2394
+ },
2395
+ additionalProperties: false,
2396
+ },
2397
+ },
2398
+ {
2399
+ name: 'heyspex_plan_qc',
2400
+ description:
2401
+ 'Plan-mode QC gut-check Proof-of-Use record ([[HS-EPIC-PLRV]] / [[plan-quality-canonical-check]]). After running the `/hs-qc` gut-check over a drafted plan and folding the findings in, call this to record that the gut-check happened, with an auditable findings_summary. Writes a marker file .heyspex/plan-qc/<sha256-of-plan-content>.json. The ExitPlanMode gate requires BOTH this QC marker AND the heyspex_plan_review marker for the current plan before it allows the exit. A call without a substantive findings_summary is refused (Proof-of-Use — required tool calls are otherwise gameable, [[HS-RES-2QX6]] F13). When axis_b_drift conflicts exist, the agent MUST present them to the user first — default is re-scope; pass axis_b_user_confirmed: true + axis_b_confirmation_note only after explicit user confirmation to proceed anyway. Pass plan_path (preferred) OR plan_content, plus findings_summary.',
2402
+ inputSchema: {
2403
+ type: 'object',
2404
+ properties: {
2405
+ plan_path: {
2406
+ type: 'string',
2407
+ description: 'Path to the plan markdown file (absolute, or relative to the vault root). Preferred over plan_content.',
2408
+ },
2409
+ plan_content: {
2410
+ type: 'string',
2411
+ description: 'Inline plan markdown. Use when the plan is not yet on disk. Ignored if plan_path is given.',
2412
+ },
2413
+ findings_summary: {
2414
+ type: 'string',
2415
+ description: 'Auditable summary of what the QC gut-check actually found — must include an "Axis B:" line. Where the plan could be wrong, risks, breakages, JTBD. If user confirmed an Axis B override, note "user confirmed override" here.',
2416
+ },
2417
+ axis_b_user_confirmed: {
2418
+ type: 'boolean',
2419
+ description: 'Set true ONLY after the user explicitly confirms they want to proceed despite axis_b_drift conflicts (not the default). Requires axis_b_confirmation_note.',
2420
+ },
2421
+ axis_b_confirmation_note: {
2422
+ type: 'string',
2423
+ description: 'Audit trail when axis_b_user_confirmed is true: which decision was overridden and what the user confirmed (>= 40 chars).',
2424
+ },
2425
+ },
2426
+ required: ['findings_summary'],
2427
+ additionalProperties: false,
2428
+ },
2429
+ },
2430
+ {
2431
+ name: 'heyspex_regen_task',
2432
+ description:
2433
+ 'Re-derive a single task body from its parent epic + linked plans ([[HS-EPIC-HSN1]] / [[HS-TASK-0CKW]]). Brief mode (no commit) returns the context the agent needs to author a new body. Commit mode (`commit: true` + `new_body`) soft-deletes the old body to `.trash/<epic-slug>/` and writes the new one. Reversible via .trash/.',
2434
+ inputSchema: {
2435
+ type: 'object',
2436
+ properties: {
2437
+ task_id: {
2438
+ type: 'string',
2439
+ description: 'Task spec id to regenerate (e.g. "HS-TASK-XXXX").',
2440
+ },
2441
+ commit: {
2442
+ type: 'boolean',
2443
+ description: 'When true, soft-delete the existing body and write `new_body`. When false (default), return the context brief only.',
2444
+ default: false,
2445
+ },
2446
+ new_body: {
2447
+ type: 'string',
2448
+ description: 'Replacement task body (markdown including frontmatter). Required in commit mode.',
2449
+ },
2450
+ retire_reason: {
2451
+ type: 'string',
2452
+ description: 'One-line reason recorded with the retired body (optional but recommended).',
2453
+ },
2454
+ },
2455
+ required: ['task_id'],
2456
+ additionalProperties: false,
2457
+ },
2458
+ },
2459
+ {
2460
+ name: 'heyspex_regen_tasks_for_epic',
2461
+ description:
2462
+ 'Run the Stage 1-2-3 regen pipeline (generate fresh from acceptance criteria → reconcile pre-existing tasks → retire superseded ones to .trash/) for every task under an Epic ([[HS-EPIC-HSN1]] / [[HS-TASK-0CKW]]). Brief mode returns the context the agent needs to compose the fresh task set. Commit mode (`commit: true` + `task_specs[]`) atomically retires superseded tasks and writes the new bodies. All retirements land in `.trash/<epic-slug>/` and are reversible.',
2463
+ inputSchema: {
2464
+ type: 'object',
2465
+ properties: {
2466
+ epic_id: {
2467
+ type: 'string',
2468
+ description: 'Epic spec id whose tasks should be regenerated (e.g. "HS-EPIC-XXXX").',
2469
+ },
2470
+ commit: {
2471
+ type: 'boolean',
2472
+ description: 'When true, perform Stage-3 retire + write. When false (default), return the Stage-1-2-3 brief only.',
2473
+ default: false,
2474
+ },
2475
+ task_specs: {
2476
+ type: 'array',
2477
+ description:
2478
+ 'In commit mode, the new task set. Each entry is either { id, new_body, retire_reason } to replace an existing task, or { new_id, body, rel_path } to file a new one.',
2479
+ items: { type: 'object' },
2480
+ },
2481
+ },
2482
+ required: ['epic_id'],
2483
+ additionalProperties: false,
2484
+ },
2485
+ },
2486
+ {
2487
+ name: 'heyspex_session_end',
2488
+ description:
2489
+ 'Signal session termination and fire SessionEnd hooks (release claim, cleanup, notify console). ' +
2490
+ 'Call this explicitly at session close — Command Code has no automatic Stop/SessionEnd hooks.',
2491
+ inputSchema: {
2492
+ type: 'object',
2493
+ properties: {
2494
+ reason: {
2495
+ type: 'string',
2496
+ description: 'Optional reason for session end (e.g. "completed", "handoff", "interrupted")',
2497
+ default: 'completed',
2498
+ },
2499
+ },
2500
+ additionalProperties: false,
2501
+ },
2502
+ },
2503
+ {
2504
+ name: 'heyspex_hooks_invoke',
2505
+ description:
2506
+ 'Invoke HeySpex lifecycle hooks for harnesses without native hook support (Command Code). ' +
2507
+ 'Use event UserPromptSubmit with prompt on each user turn for intent detection; ' +
2508
+ 'call after plan-mode exit to get plan-ingest close guidance. ' +
2509
+ 'PreToolUse/PostToolUse replay for testing or skill-driven guardrails.',
2510
+ inputSchema: {
2511
+ type: 'object',
2512
+ properties: {
2513
+ event: {
2514
+ type: 'string',
2515
+ enum: ['UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'SessionEnd'],
2516
+ description: 'Hook lifecycle event to fire',
2517
+ },
2518
+ prompt: {
2519
+ type: 'string',
2520
+ description: 'User message (required for UserPromptSubmit)',
2521
+ },
2522
+ tool_name: {
2523
+ type: 'string',
2524
+ description: 'Tool name for PreToolUse/PostToolUse (default: *)',
2525
+ },
2526
+ tool_input: {
2527
+ type: 'object',
2528
+ description: 'Tool input payload for PreToolUse/PostToolUse',
2529
+ },
2530
+ tool_response: {
2531
+ type: 'object',
2532
+ description: 'Prior tool result for PostToolUse replay',
2533
+ },
2534
+ reason: {
2535
+ type: 'string',
2536
+ description: 'Session end reason when event is SessionEnd',
2537
+ },
2538
+ },
2539
+ required: ['event'],
2540
+ additionalProperties: false,
2541
+ },
2542
+ },
2543
+ {
2544
+ name: 'heyspex_hooks_list',
2545
+ budget: READ_TOOL_BUDGET,
2546
+ description:
2547
+ 'List all registered hooks from .commandcode/hooks.json. Returns the parsed config with resolved paths.',
2548
+ inputSchema: {
2549
+ type: 'object',
2550
+ properties: {},
2551
+ additionalProperties: false,
2552
+ },
2553
+ },
2554
+ {
2555
+ name: 'heyspex_load_tools',
2556
+ description:
2557
+ 'Progressive tool disclosure ([[HS-MTSA-EPIC]] / [[HS-OO5K-RES]]): the default tools/list shows only the core daily-loop tools; the rest are DEFERRED — hidden, not deleted, and loadable here. Call with { list_deferred: true } to see every deferred tool (name + description) without loading. Call with { names: [...] } (or { all: true }) to make those tools visible for this session — the server emits notifications/tools/list_changed so the client refreshes. At the MCP wire level deferred tools still answer tools/call, but Claude Code only calls names present in its tools/list, so load a deferred tool here before calling it from a Claude Code session.',
2558
+ inputSchema: {
2559
+ type: 'object',
2560
+ properties: {
2561
+ names: {
2562
+ type: 'array',
2563
+ items: { type: 'string' },
2564
+ description: 'Deferred tool names to load into the visible surface (e.g. ["heyspex_next_review"])',
2565
+ },
2566
+ all: {
2567
+ type: 'boolean',
2568
+ default: false,
2569
+ description: 'Load every deferred tool',
2570
+ },
2571
+ list_deferred: {
2572
+ type: 'boolean',
2573
+ default: false,
2574
+ description: 'List deferred (currently hidden) tools with descriptions, without loading them',
2575
+ },
2576
+ },
2577
+ additionalProperties: false,
2578
+ },
2579
+ },
2580
+ {
2581
+ name: 'heyspex_fleet_queue_submit',
2582
+ description:
2583
+ 'Submit work into the fleet-queue namespace (bearer-authed POST /api/fleet/queue) — [[HS-60Z1-EPIC]]. CURRENTLY REFUSES (error NO_RUNNER, nothing written): the only executor, dev-factory/lib/fleet-queue-runner.mjs, still reads the legacy JSONL file queue, so work accepted here would be recorded and never run — [[HS-FS7X-6S8F-DIAG-fleet-queue-submit-writes-to-a-queue]]. The runner repoint is [[HS-XOCK-60Z1-EPIC-fleet-work-queue-in-heyspex]]; to run work now, enqueue against dev-factory/lib/fleet-queue.mjs. Once a runner drains Neon (HEYSPEX_FLEET_QUEUE_RUNNER_READY=1) this returns { id, position } and work that loses a race for a busy machine-gated resource waits in the lane until a machine claims it (heyspex_fleet_queue_claim) instead of failing.',
2584
+ inputSchema: {
2585
+ type: 'object',
2586
+ properties: {
2587
+ lane: {
2588
+ type: 'string',
2589
+ description: 'Queue lane the task waits in (e.g. "build"). Required.',
2590
+ },
2591
+ kind: {
2592
+ type: 'string',
2593
+ description: 'Task kind for eligibility filtering (defaults to "task" when omitted).',
2594
+ },
2595
+ payload: {
2596
+ type: 'object',
2597
+ description: 'Arbitrary JSON payload for the task (maps to the dispatcher\'s `spec`).',
2598
+ },
2599
+ requested_by: {
2600
+ type: 'string',
2601
+ description: 'Display name/id of the requester (informational).',
2602
+ },
2603
+ },
2604
+ required: ['lane'],
2605
+ additionalProperties: false,
2606
+ },
2607
+ },
2608
+ {
2609
+ name: 'heyspex_fleet_queue_claim',
2610
+ description:
2611
+ 'Claim a specific fleet-queue task by id (bearer-authed POST /api/fleet/queue/:taskId/claim) — [[HS-60Z1-EPIC]]. A 404 (unknown id) or a 409 (already claimed / terminal state — another machine won the race) is a normal outcome, not a failure: this tool surfaces those as a clean { ok:false, reason } instead of throwing, so the caller can fall back to polling the queue or picking another task.',
2612
+ inputSchema: {
2613
+ type: 'object',
2614
+ properties: {
2615
+ task_id: {
2616
+ type: 'string',
2617
+ description: 'The queue task id to claim.',
2618
+ },
2619
+ machine_key: {
2620
+ type: 'string',
2621
+ description: 'The claiming machine\'s fleet machine key.',
2622
+ },
2623
+ },
2624
+ required: ['task_id', 'machine_key'],
2625
+ additionalProperties: false,
2626
+ },
2627
+ },
2628
+ {
2629
+ name: 'heyspex_fleet_queue_complete',
2630
+ description:
2631
+ 'Mark a claimed fleet-queue task done (bearer-authed POST /api/fleet/queue/:taskId/complete) — [[HS-60Z1-EPIC]]. Records a canonical result row; the machineKey must match the machine that holds the claim. Returns { accepted: true }.',
2632
+ inputSchema: {
2633
+ type: 'object',
2634
+ properties: {
2635
+ task_id: {
2636
+ type: 'string',
2637
+ description: 'The queue task id to complete.',
2638
+ },
2639
+ machine_key: {
2640
+ type: 'string',
2641
+ description: 'The claiming machine\'s fleet machine key.',
2642
+ },
2643
+ result: {
2644
+ type: 'object',
2645
+ description: 'Optional result payload to record with the completion.',
2646
+ },
2647
+ },
2648
+ required: ['task_id', 'machine_key'],
2649
+ additionalProperties: false,
2650
+ },
2651
+ },
2652
+ {
2653
+ name: 'heyspex_fleet_queue_list',
2654
+ budget: READ_TOOL_BUDGET,
2655
+ description:
2656
+ 'List fleet-queue tasks (bearer-authed GET /api/fleet/queue) — [[HS-60Z1-EPIC]]. The visibility read for the queue: queue depth, in-flight (claimed) items, and history, oldest-first. Filter by lane and/or status; limit caps the returned count.',
2657
+ inputSchema: {
2658
+ type: 'object',
2659
+ properties: {
2660
+ lane: {
2661
+ type: 'string',
2662
+ description: 'Filter to one lane.',
2663
+ },
2664
+ status: {
2665
+ type: 'string',
2666
+ description: 'Filter to one status (e.g. "pending", "claimed", "done", "failed").',
2667
+ },
2668
+ limit: {
2669
+ type: 'number',
2670
+ description: 'Max number of items to return.',
2671
+ },
2672
+ },
2673
+ additionalProperties: false,
2674
+ },
2675
+ },
2676
+ ];
2677
+
2678
+ export const DEFAULT_VISIBLE_TOOLS = new Set([
2679
+ // the 15 keeps from [[HS-OO5K-RES]]
2680
+ 'heyspex_validate_linkage',
2681
+ 'heyspex_next_on_deck',
2682
+ 'heyspex_save_research',
2683
+ 'heyspex_recall_memories',
2684
+ 'heyspex_claim_spec',
2685
+ 'heyspex_release_spec',
2686
+ 'heyspex_report_progress',
2687
+ 'heyspex_submit_for_review',
2688
+ 'heyspex_record_wip',
2689
+ 'heyspex_defer_spec',
2690
+ 'heyspex_list_procedures',
2691
+ 'heyspex_get_procedure',
2692
+ 'heyspex_file_spec',
2693
+ 'heyspex_preflight',
2694
+ 'heyspex_dev_server_status',
2695
+ // /hs-dev depends on this being directly callable (epic HS-Q6M2); still
2696
+ // within the [[HS-MTSA-EPIC]] ≤20 default-surface budget.
2697
+ 'heyspex_dev_mode',
2698
+ // the progressive-disclosure loader itself
2699
+ 'heyspex_load_tools',
2700
+ // [[HS-1C4L-9VZV-DIAG-multi-agent-capture-focus-pin-collision]] AC3: keep
2701
+ // heyspex_pin_capture directly discoverable — heyspex_active_capture's own
2702
+ // description points agents to it as the pin escape hatch, and it must not
2703
+ // be effectively invisible behind the progressive-disclosure loader.
2704
+ 'heyspex_pin_capture',
2705
+ // Session hand-off pair: the SessionStart / PostCompact / PreCompact hooks in
2706
+ // scripts/claude-hooks/wip-checkpoint.mjs instruct the agent to call these
2707
+ // by name. Claude Code refuses tools/call for any name absent from its
2708
+ // tools/list, so a deferred hand-off tool fails every session start with
2709
+ // "No such tool available". Fills the last two slots of the 20-tool cap.
2710
+ 'heyspex_handoff_load',
2711
+ 'heyspex_handoff_record',
2712
+ ]);
2713
+
2714
+ /** Session-scoped set of deferred tools made visible via heyspex_load_tools. */
2715
+ export const loadedToolNames = new Set();
2716
+
2717
+ export function toolSurfaceIsFull() {
2718
+ return String(process.env.HEYSPEX_TOOL_SURFACE ?? '').toLowerCase() === 'full';
2719
+ }
2720
+
2721
+ /** Filter a tools/list result to the default-visible ∪ session-loaded surface. */
2722
+ export function filterToolSurface(result) {
2723
+ if (toolSurfaceIsFull()) return result;
2724
+ if (!result || !Array.isArray(result.tools)) return result;
2725
+ return {
2726
+ ...result,
2727
+ tools: result.tools.filter(
2728
+ (t) => DEFAULT_VISIBLE_TOOLS.has(t.name) || loadedToolNames.has(t.name),
2729
+ ),
2730
+ };
2731
+ }
2732
+
2733
+ /** Parent-side handler for heyspex_load_tools (must NOT run in a fresh subprocess —
2734
+ * the loaded-set mutation and list_changed notification belong to the long-lived
2735
+ * parent that owns the client connection). */
2736
+ export function handleLoadTools(args = {}) {
2737
+ const deferred = TOOLS.filter(
2738
+ (t) => !DEFAULT_VISIBLE_TOOLS.has(t.name) && !loadedToolNames.has(t.name),
2739
+ );
2740
+ const ALLOWED_LOAD_TOOLS_KEYS = new Set(['names', 'all', 'list_deferred']);
2741
+ const unknownKeys = Object.keys(args).filter((k) => !ALLOWED_LOAD_TOOLS_KEYS.has(k));
2742
+ if (unknownKeys.length > 0) {
2743
+ return {
2744
+ isError: true,
2745
+ content: [{
2746
+ type: 'text',
2747
+ text: `heyspex_load_tools: unrecognized argument(s) ${unknownKeys.join(', ')}. Valid keys: names (string[]), all (boolean), list_deferred (boolean).`,
2748
+ }],
2749
+ };
2750
+ }
2751
+ if (args.names !== undefined && !Array.isArray(args.names)) {
2752
+ return {
2753
+ isError: true,
2754
+ content: [{
2755
+ type: 'text',
2756
+ text: `heyspex_load_tools: "names" must be an array of tool name strings, got ${typeof args.names}.`,
2757
+ }],
2758
+ };
2759
+ }
2760
+ if (args.list_deferred) {
2761
+ return {
2762
+ content: [{
2763
+ type: 'text',
2764
+ text: JSON.stringify({
2765
+ deferred_count: deferred.length,
2766
+ deferred: deferred.map((t) => ({
2767
+ name: t.name,
2768
+ description: String(t.description ?? '').slice(0, 160),
2769
+ })),
2770
+ }),
2771
+ }],
2772
+ };
2773
+ }
2774
+ if (!args.all && !(Array.isArray(args.names) && args.names.length > 0)) {
2775
+ return {
2776
+ isError: true,
2777
+ content: [{
2778
+ type: 'text',
2779
+ text: 'heyspex_load_tools: called with neither { names: [...] } nor { all: true } — nothing to load. Use { list_deferred: true } to browse deferred tools first.',
2780
+ }],
2781
+ };
2782
+ }
2783
+ const known = new Set(TOOLS.map((t) => t.name));
2784
+ const requested = args.all ? deferred.map((t) => t.name) : args.names;
2785
+ const unknown = requested.filter((n) => !known.has(n));
2786
+ const loaded = [];
2787
+ for (const n of requested) {
2788
+ if (!known.has(n) || DEFAULT_VISIBLE_TOOLS.has(n)) continue;
2789
+ if (!loadedToolNames.has(n)) {
2790
+ loadedToolNames.add(n);
2791
+ loaded.push(n);
2792
+ }
2793
+ }
2794
+ if (loaded.length > 0) {
2795
+ try {
2796
+ sendNotification('notifications/tools/list_changed', {});
2797
+ } catch {
2798
+ // stdout not writable (CLI mode) — visibility still applies to later tools/list
2799
+ }
2800
+ }
2801
+ return {
2802
+ isError: unknown.length > 0 && loaded.length === 0,
2803
+ content: [{
2804
+ type: 'text',
2805
+ text: JSON.stringify({
2806
+ loaded,
2807
+ already_visible: requested.filter((n) => DEFAULT_VISIBLE_TOOLS.has(n) || (loadedToolNames.has(n) && !loaded.includes(n))),
2808
+ unknown,
2809
+ visible_count: DEFAULT_VISIBLE_TOOLS.size + loadedToolNames.size,
2810
+ }),
2811
+ }],
2812
+ };
2813
+ }