heyspex 0.1.0 → 0.2.1

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 (352) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/README.md +21 -0
  3. package/bin/hs.mjs +1089 -104
  4. package/bin/spexcode.mjs +262 -0
  5. package/dev-factory/engine/worktree.mjs +740 -0
  6. package/dev-factory/lib/code-repo-root.mjs +67 -0
  7. package/dev-factory/lib/context-budget.mjs +84 -0
  8. package/dev-factory/lib/epic-detect.mjs +190 -0
  9. package/dev-factory/lib/fleet-queue-file.mjs +846 -0
  10. package/dev-factory/lib/fleet-queue.mjs +426 -0
  11. package/dev-factory/lib/fleet-state.mjs +14 -18
  12. package/dev-factory/lib/model-catalog.mjs +338 -0
  13. package/dev-factory/lib/seat-context.mjs +199 -0
  14. package/dev-factory/lib/spec-io.mjs +93 -0
  15. package/dev-factory/lib/sufficiency-score.mjs +8 -2
  16. package/dev-factory/lib/worktree-allocator.mjs +744 -0
  17. package/dev-factory/model-catalog.json +222 -0
  18. package/dist/cli/hs-check.mjs +1127 -103
  19. package/dist/cli/hs-claim.mjs +981 -63
  20. package/dist/cli/hs-connect.mjs +10059 -1729
  21. package/dist/cli/hs-defer.mjs +5507 -3072
  22. package/dist/cli/hs-dev.mjs +576 -0
  23. package/dist/cli/hs-env.mjs +416 -302
  24. package/dist/cli/hs-index-rebuild.mjs +7334 -0
  25. package/dist/cli/hs-index-reconcile.mjs +8138 -0
  26. package/dist/cli/hs-init.mjs +2840 -149
  27. package/dist/cli/hs-install.mjs +7214 -594
  28. package/dist/cli/hs-join.mjs +10640 -2421
  29. package/dist/cli/hs-vault-pull.mjs +8157 -6370
  30. package/dist/cli/hs-vault-push.mjs +6433 -0
  31. package/dist/cli/hs-vault.mjs +4029 -1555
  32. package/dist/cli/hs-workspace.mjs +2702 -538
  33. package/dist/cli-templates/customer-vault/specs/entity-model.md +594 -601
  34. package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +110 -177
  35. package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -0
  36. package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +13 -15
  37. package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +91 -0
  38. package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +150 -168
  39. package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -0
  40. package/dist/cli-templates/harness-commands/claude/hs-auto.md +36 -0
  41. package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -0
  42. package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -0
  43. package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -0
  44. package/dist/cli-templates/harness-commands/claude/hs-dev.md +20 -0
  45. package/dist/cli-templates/harness-commands/claude/hs-doctor.md +32 -0
  46. package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -0
  47. package/dist/cli-templates/harness-commands/claude/hs-handoff.md +33 -0
  48. package/dist/cli-templates/harness-commands/claude/hs-log.md +27 -0
  49. package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -0
  50. package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -0
  51. package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -0
  52. package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -0
  53. package/dist/cli-templates/harness-commands/claude/hs-res.md +63 -0
  54. package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -0
  55. package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -0
  56. package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -0
  57. package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -0
  58. package/dist/cli-templates/harness-commands/claude/prototype.md +91 -0
  59. package/dist/cli-templates/harness-commands/cursor/hs-auto.md +40 -0
  60. package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +31 -0
  61. package/dist/cli-templates/harness-commands/cursor/hs-capture.md +65 -0
  62. package/dist/cli-templates/harness-commands/cursor/hs-defer.md +32 -0
  63. package/dist/cli-templates/harness-commands/cursor/hs-dev.md +24 -0
  64. package/dist/cli-templates/harness-commands/cursor/hs-doctor.md +36 -0
  65. package/dist/cli-templates/harness-commands/cursor/hs-ground.md +33 -0
  66. package/dist/cli-templates/harness-commands/cursor/hs-handoff.md +37 -0
  67. package/dist/cli-templates/harness-commands/cursor/hs-log.md +31 -0
  68. package/dist/cli-templates/harness-commands/cursor/hs-next.md +32 -0
  69. package/dist/cli-templates/harness-commands/cursor/hs-promote.md +73 -0
  70. package/dist/cli-templates/harness-commands/cursor/hs-push.md +38 -0
  71. package/dist/cli-templates/harness-commands/cursor/hs-qc.md +30 -0
  72. package/dist/cli-templates/harness-commands/cursor/hs-res.md +67 -0
  73. package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +26 -0
  74. package/dist/cli-templates/harness-commands/cursor/hs-review.md +26 -0
  75. package/dist/cli-templates/harness-commands/cursor/hs-view.md +32 -0
  76. package/dist/cli-templates/harness-commands/cursor/kill-ports.md +35 -0
  77. package/dist/cli-templates/harness-commands/cursor/prototype.md +95 -0
  78. package/docs/copilot-plugin-install.md +162 -0
  79. package/docs/driving-heyspex-from-copilot.md +165 -0
  80. package/docs/mcp-client-config.md +259 -6
  81. package/package.json +249 -124
  82. package/plugins/heyspex/.claude-plugin/plugin.json +12 -0
  83. package/plugins/heyspex/.cursor-plugin/plugin.json +15 -0
  84. package/plugins/heyspex/.mcp.json +12 -0
  85. package/plugins/heyspex/README.md +309 -0
  86. package/plugins/heyspex/bin/launch-mcp.mjs +95 -0
  87. package/plugins/heyspex/bin/run-hook.mjs +80 -0
  88. package/plugins/heyspex/hooks/cursor-hooks.json +308 -0
  89. package/plugins/heyspex/hooks/hooks.json +418 -0
  90. package/plugins/heyspex/mcp.json +13 -0
  91. package/scripts/check-mcp-freshness.mjs +17 -9
  92. package/scripts/claude-hooks/_env-vault-events.mjs +68 -0
  93. package/scripts/claude-hooks/_guardrail-events.mjs +96 -0
  94. package/scripts/claude-hooks/_guardrail-loader.mjs +494 -0
  95. package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -0
  96. package/scripts/claude-hooks/_intent-classifier-semantic.mjs +356 -0
  97. package/scripts/claude-hooks/_run.mjs +413 -0
  98. package/scripts/claude-hooks/_transcript-text.mjs +60 -0
  99. package/scripts/claude-hooks/_vault-retrieval.mjs +322 -0
  100. package/scripts/claude-hooks/agent-spawn-delegation-gate.mjs +133 -0
  101. package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
  102. package/scripts/claude-hooks/capture-workflow-gate.mjs +11 -0
  103. package/scripts/claude-hooks/check-auto-memory-budget.mjs +396 -0
  104. package/scripts/claude-hooks/check-claude-md-budget.mjs +327 -0
  105. package/scripts/claude-hooks/check-mcp-freshness-hook.mjs +14 -0
  106. package/scripts/claude-hooks/claim-before-code-gate.mjs +11 -0
  107. package/scripts/claude-hooks/claim-liveness-shared.mjs +577 -0
  108. package/scripts/claude-hooks/close-terminal-on-ship.mjs +64 -0
  109. package/scripts/claude-hooks/coo-denylist-gate.mjs +11 -0
  110. package/scripts/claude-hooks/coo-guard-check.mjs +11 -0
  111. package/scripts/claude-hooks/deep-research-cost-gate.mjs +11 -0
  112. package/scripts/claude-hooks/dev-server-task-reminder.mjs +15 -0
  113. package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
  114. package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
  115. package/scripts/claude-hooks/emit-claim-status.mjs +41 -0
  116. package/scripts/claude-hooks/enforce-session-sequence.mjs +11 -0
  117. package/scripts/claude-hooks/enforce-vault-naming.mjs +11 -0
  118. package/scripts/claude-hooks/env-vault-guard.mjs +11 -0
  119. package/scripts/claude-hooks/epic-milestone-phrasing-gate.mjs +11 -0
  120. package/scripts/claude-hooks/git-identity-guard.mjs +11 -0
  121. package/scripts/claude-hooks/git-reflog-audit.mjs +15 -0
  122. package/scripts/claude-hooks/git-stash-gate.mjs +11 -0
  123. package/scripts/claude-hooks/guard-auto-memory-writes.mjs +137 -0
  124. package/scripts/claude-hooks/id-display-format-gate.mjs +11 -0
  125. package/scripts/claude-hooks/inject-communication-style.mjs +86 -0
  126. package/scripts/claude-hooks/inject-coo-seat.mjs +78 -0
  127. package/scripts/claude-hooks/inject-dev-server-awareness.mjs +11 -0
  128. package/scripts/claude-hooks/inject-doctrine.mjs +13 -0
  129. package/scripts/claude-hooks/inject-vault-context.mjs +14 -0
  130. package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -0
  131. package/scripts/claude-hooks/log-session-activity.mjs +341 -0
  132. package/scripts/claude-hooks/naughty-list-gate.mjs +11 -0
  133. package/scripts/claude-hooks/notify-status-flip.mjs +72 -0
  134. package/scripts/claude-hooks/plan-exit-ingest-gate.mjs +170 -0
  135. package/scripts/claude-hooks/plan-ingest-close-reminder.mjs +66 -0
  136. package/scripts/claude-hooks/plan-review-gate.mjs +11 -0
  137. package/scripts/claude-hooks/preflight-reminder.mjs +262 -0
  138. package/scripts/claude-hooks/protected-action-intent.mjs +221 -0
  139. package/scripts/claude-hooks/research-method-guard.mjs +398 -0
  140. package/scripts/claude-hooks/research-protocol-gate.mjs +11 -0
  141. package/scripts/claude-hooks/session-end.mjs +451 -0
  142. package/scripts/claude-hooks/settings-write-gate.mjs +11 -0
  143. package/scripts/claude-hooks/settlement-check-gate.mjs +39 -0
  144. package/scripts/claude-hooks/status-flip-detect.mjs +17 -0
  145. package/scripts/claude-hooks/terminal-close-shared.mjs +426 -0
  146. package/scripts/claude-hooks/terminal-close-watcher.mjs +122 -0
  147. package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
  148. package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
  149. package/scripts/claude-hooks/wip-checkpoint.mjs +302 -0
  150. package/scripts/cursor-hooks/manifest.mjs +281 -0
  151. package/scripts/dev-server-status.mjs +34 -13
  152. package/scripts/dev-server-stop.mjs +313 -0
  153. package/scripts/heyspex-mode.mjs +288 -0
  154. package/scripts/hs-auto-resolve.mjs +240 -0
  155. package/scripts/hs-auto-source.mjs +398 -0
  156. package/scripts/hs-auto.mjs +134 -0
  157. package/scripts/hs-capture-cli.mjs +326 -0
  158. package/scripts/hs-classifier.mjs +136 -0
  159. package/scripts/hs-companion.mjs +88 -0
  160. package/scripts/hs-gateway.mjs +115 -0
  161. package/scripts/hs-mode.mjs +125 -0
  162. package/scripts/hs-next.mjs +441 -0
  163. package/scripts/hs-settings.mjs +338 -0
  164. package/scripts/hs-view.mjs +496 -0
  165. package/scripts/hs-worktree.mjs +201 -0
  166. package/scripts/install-claude-code-hooks.mjs +272 -0
  167. package/scripts/install-cursor-hooks.mjs +471 -0
  168. package/scripts/lib/agent-env-markers.mjs +38 -0
  169. package/scripts/lib/console-url-resolver.mjs +199 -61
  170. package/scripts/lib/customer-kit.mjs +1203 -0
  171. package/scripts/lib/dev-mode.mjs +249 -32
  172. package/scripts/lib/directive-obligations.mjs +349 -0
  173. package/scripts/lib/heyspex-home.mjs +72 -0
  174. package/scripts/lib/hook-dispositions.mjs +210 -0
  175. package/scripts/lib/hook-vault-header.mjs +30 -0
  176. package/scripts/lib/hs-command.mjs +277 -0
  177. package/scripts/lib/hs-companion-settings.mjs +472 -0
  178. package/scripts/lib/hs-source-branch.mjs +196 -0
  179. package/scripts/lib/hs-source-commit.mjs +59 -5
  180. package/scripts/lib/hs-source-identity.mjs +526 -0
  181. package/scripts/lib/hs-source-log.mjs +13 -22
  182. package/scripts/lib/hs-source-preflight.mjs +18 -4
  183. package/scripts/lib/hs-source-push.mjs +386 -307
  184. package/scripts/lib/hs-worktree-args.mjs +115 -0
  185. package/scripts/lib/shared-path-helpers.mjs +134 -0
  186. package/scripts/lib/standalone-doctor.mjs +540 -3
  187. package/scripts/lib/subagent-git-lock.mjs +109 -0
  188. package/scripts/lib/template-pin.mjs +34 -0
  189. package/scripts/lib/window-placement.mjs +109 -0
  190. package/scripts/provenance-lib.mjs +91 -0
  191. package/scripts/stamp-provenance-on-edit.mjs +222 -0
  192. package/scripts/wip-ledger-health.mjs +653 -0
  193. package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -0
  194. package/spexharness/src/adapters/audit.mjs +197 -0
  195. package/spexharness/src/adapters/claude-code.mjs +449 -0
  196. package/spexharness/src/adapters/copilot.mjs +236 -0
  197. package/spexharness/src/adapters/detect-harness.mjs +34 -0
  198. package/spexharness/src/chat/tui/ansi.mjs +448 -0
  199. package/spexharness/src/chat/tui/palette.mjs +22 -0
  200. package/spexharness/src/chat/tui/select.mjs +208 -0
  201. package/spexharness/src/config-root.mjs +54 -0
  202. package/spexharness/src/context-budget.mjs +141 -0
  203. package/spexharness/src/dev-server-control.mjs +389 -0
  204. package/spexharness/src/doctrine/behaviour.mjs +371 -0
  205. package/spexharness/src/doctrine/corpus.mjs +733 -0
  206. package/spexharness/src/doctrine/inject.mjs +338 -0
  207. package/spexharness/src/doctrine/merge.mjs +184 -0
  208. package/spexharness/src/doctrine/sources.mjs +252 -0
  209. package/spexharness/src/doctrine/synthesize.mjs +401 -0
  210. package/spexharness/src/doctrine/vault-source.mjs +574 -0
  211. package/spexharness/src/doctrine/writer.mjs +176 -0
  212. package/spexharness/src/events.mjs +84 -0
  213. package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
  214. package/spexharness/src/gates/capture-workflow-gate.mjs +202 -0
  215. package/spexharness/src/gates/claim-before-code-gate.decide.mjs +33 -0
  216. package/spexharness/src/gates/claim-before-code-gate.mjs +129 -0
  217. package/spexharness/src/gates/deep-research-cost-gate.mjs +275 -0
  218. package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
  219. package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
  220. package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
  221. package/spexharness/src/gates/enforce-session-sequence.mjs +895 -0
  222. package/spexharness/src/gates/enforce-vault-naming.mjs +215 -0
  223. package/spexharness/src/gates/epic-milestone-phrasing-gate.mjs +233 -0
  224. package/spexharness/src/gates/git-identity-guard.decide.mjs +436 -0
  225. package/spexharness/src/gates/git-identity-guard.mjs +61 -0
  226. package/spexharness/src/gates/git-reflog-audit.mjs +151 -0
  227. package/spexharness/src/gates/git-stash-gate.decide.mjs +361 -0
  228. package/spexharness/src/gates/git-stash-gate.mjs +159 -0
  229. package/spexharness/src/gates/id-display-format-gate.mjs +190 -0
  230. package/spexharness/src/gates/inject-doctrine-gate.mjs +89 -0
  231. package/spexharness/src/gates/inject-vault-context.mjs +239 -0
  232. package/spexharness/src/gates/naughty-list-gate.mjs +213 -0
  233. package/spexharness/src/gates/plan-review-gate.mjs +233 -0
  234. package/spexharness/src/gates/preflight-reminder-gate.mjs +265 -0
  235. package/spexharness/src/gates/research-protocol-gate.mjs +175 -0
  236. package/spexharness/src/gates/settings-write-gate.decide.mjs +96 -0
  237. package/spexharness/src/gates/settings-write-gate.mjs +104 -0
  238. package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
  239. package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
  240. package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
  241. package/spexharness/src/git-reflog-audit.mjs +294 -0
  242. package/spexharness/src/intent-classifier.mjs +196 -0
  243. package/spexharness/src/intent-directive.mjs +626 -0
  244. package/spexharness/src/profile/learn.mjs +551 -0
  245. package/spexharness/src/profile/proposals.mjs +357 -0
  246. package/spexharness/src/profile/vault-inclusion.mjs +386 -0
  247. package/spexharness/src/redact.mjs +151 -0
  248. package/spexharness/src/rule-table.mjs +849 -0
  249. package/spexharness/src/seat-context.mjs +122 -0
  250. package/spexharness/src/session-bootstrap.mjs +512 -0
  251. package/spexharness/src/session-record.mjs +815 -0
  252. package/spexharness/src/token-inventory.mjs +207 -0
  253. package/vendor/heyspex-mcp/account-switch.mjs +96 -0
  254. package/vendor/heyspex-mcp/alignment-gate.mjs +315 -1
  255. package/vendor/heyspex-mcp/blocker-resolver.mjs +37 -2
  256. package/vendor/heyspex-mcp/breakdown-ladder.mjs +119 -4
  257. package/vendor/heyspex-mcp/breakdown-source.mjs +271 -0
  258. package/vendor/heyspex-mcp/breakdown-writer.mjs +23 -3
  259. package/vendor/heyspex-mcp/capture-buffer-patches.mjs +77 -8
  260. package/vendor/heyspex-mcp/capture-channel.mjs +55 -5
  261. package/vendor/heyspex-mcp/capture-console-client.mjs +117 -3
  262. package/vendor/heyspex-mcp/capture-focus.mjs +1061 -31
  263. package/vendor/heyspex-mcp/capture-outbox.mjs +15 -5
  264. package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
  265. package/vendor/heyspex-mcp/capture-tools.mjs +1264 -0
  266. package/vendor/heyspex-mcp/claim-tools.mjs +1147 -0
  267. package/vendor/heyspex-mcp/cli-verbs.mjs +2074 -0
  268. package/vendor/heyspex-mcp/cli.mjs +207 -15524
  269. package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
  270. package/vendor/heyspex-mcp/cloud-tool-surface.mjs +128 -0
  271. package/vendor/heyspex-mcp/cloud-vault-writes.mjs +602 -0
  272. package/vendor/heyspex-mcp/config-seed.yaml +15 -0
  273. package/vendor/heyspex-mcp/config-settings.mjs +290 -7
  274. package/vendor/heyspex-mcp/console-fetch.mjs +822 -17
  275. package/vendor/heyspex-mcp/console-transport.mjs +172 -0
  276. package/vendor/heyspex-mcp/credentials.mjs +585 -0
  277. package/vendor/heyspex-mcp/defer-spec.mjs +44 -4
  278. package/vendor/heyspex-mcp/deferred-reclaim.mjs +29 -3
  279. package/vendor/heyspex-mcp/design-handlers.mjs +31 -0
  280. package/vendor/heyspex-mcp/design-tools.mjs +4 -1
  281. package/vendor/heyspex-mcp/dev-factory-bridge.mjs +4 -4
  282. package/vendor/heyspex-mcp/dev-mode-core.mjs +45 -0
  283. package/vendor/heyspex-mcp/dev-tools.mjs +131 -0
  284. package/vendor/heyspex-mcp/diagnose-handlers.mjs +54 -0
  285. package/vendor/heyspex-mcp/events-tools.mjs +207 -0
  286. package/vendor/heyspex-mcp/filing-tools.mjs +723 -0
  287. package/vendor/heyspex-mcp/fleet-handlers.mjs +37 -0
  288. package/vendor/heyspex-mcp/fleet-queue-tools.mjs +71 -15
  289. package/vendor/heyspex-mcp/focus-tools.mjs +190 -0
  290. package/vendor/heyspex-mcp/folder-trust-store.mjs +60 -0
  291. package/vendor/heyspex-mcp/http-transport.mjs +302 -0
  292. package/vendor/heyspex-mcp/list-tools.mjs +257 -0
  293. package/vendor/heyspex-mcp/login-handoff.mjs +15 -1
  294. package/vendor/heyspex-mcp/mcp-config.mjs +107 -0
  295. package/vendor/heyspex-mcp/memory-tools.mjs +523 -0
  296. package/vendor/heyspex-mcp/mvp-mode.mjs +53 -6
  297. package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
  298. package/vendor/heyspex-mcp/occupied-keys.mjs +142 -0
  299. package/vendor/heyspex-mcp/on-deck-brief.mjs +3377 -0
  300. package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -0
  301. package/vendor/heyspex-mcp/package-root.mjs +32 -0
  302. package/vendor/heyspex-mcp/peer-presence.mjs +8 -1
  303. package/vendor/heyspex-mcp/plan-ground.mjs +621 -652
  304. package/vendor/heyspex-mcp/plan-push.mjs +908 -22
  305. package/vendor/heyspex-mcp/plan-review.mjs +124 -30
  306. package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
  307. package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -0
  308. package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -970
  309. package/vendor/heyspex-mcp/promote-capture.mjs +388 -69
  310. package/vendor/heyspex-mcp/promote-diagnose.mjs +74 -0
  311. package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +9 -2
  312. package/vendor/heyspex-mcp/promote-research.mjs +666 -53
  313. package/vendor/heyspex-mcp/promotion-helpers.mjs +251 -0
  314. package/vendor/heyspex-mcp/prompts.mjs +1460 -0
  315. package/vendor/heyspex-mcp/read-path.mjs +191 -15
  316. package/vendor/heyspex-mcp/receipt-shape.mjs +146 -0
  317. package/vendor/heyspex-mcp/recipe-handlers.mjs +117 -0
  318. package/vendor/heyspex-mcp/regen-tasks.mjs +210 -3
  319. package/vendor/heyspex-mcp/register-spec.mjs +266 -54
  320. package/vendor/heyspex-mcp/request-context.mjs +83 -0
  321. package/vendor/heyspex-mcp/research-focus.mjs +207 -4
  322. package/vendor/heyspex-mcp/research-handlers.mjs +625 -0
  323. package/vendor/heyspex-mcp/research-tools.mjs +4 -4
  324. package/vendor/heyspex-mcp/response-budget.mjs +348 -0
  325. package/vendor/heyspex-mcp/review-focus.mjs +25 -1
  326. package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
  327. package/vendor/heyspex-mcp/search-specs.mjs +280 -0
  328. package/vendor/heyspex-mcp/seat-block-ledger.mjs +113 -0
  329. package/vendor/heyspex-mcp/seat-identity.mjs +2 -6
  330. package/vendor/heyspex-mcp/session-owner.mjs +109 -0
  331. package/vendor/heyspex-mcp/session-tools.mjs +162 -0
  332. package/vendor/heyspex-mcp/settings-registry.mjs +232 -0
  333. package/vendor/heyspex-mcp/settings-tools.mjs +85 -12
  334. package/vendor/heyspex-mcp/spec-edit-tools.mjs +453 -0
  335. package/vendor/heyspex-mcp/spec-id-lib.mjs +93 -17
  336. package/vendor/heyspex-mcp/split-research.mjs +304 -56
  337. package/vendor/heyspex-mcp/stdio-rpc.mjs +22 -0
  338. package/vendor/heyspex-mcp/submit-for-review.mjs +150 -7
  339. package/vendor/heyspex-mcp/summary-contract.mjs +105 -0
  340. package/vendor/heyspex-mcp/summary-write-tools.mjs +228 -0
  341. package/vendor/heyspex-mcp/symptom-query.mjs +146 -0
  342. package/vendor/heyspex-mcp/test-helpers.mjs +123 -1
  343. package/vendor/heyspex-mcp/tool-catalog.mjs +2819 -0
  344. package/vendor/heyspex-mcp/tool-dispatch.mjs +657 -0
  345. package/vendor/heyspex-mcp/tool-helpers.mjs +325 -0
  346. package/vendor/heyspex-mcp/validate-linkage.mjs +414 -27
  347. package/vendor/heyspex-mcp/vault-io.mjs +720 -0
  348. package/vendor/heyspex-mcp/vault-less-endpoints.mjs +52 -0
  349. package/vendor/heyspex-mcp/vault-resolve.mjs +554 -68
  350. package/vendor/heyspex-mcp/verify-reproduction.mjs +270 -30
  351. package/vendor/heyspex-mcp/whoami.mjs +92 -0
  352. package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ // hs-gateway.mjs — `hs gateway <command>` face of the user-customizable
3
+ // gateway (HS-WEC3; design: specs/research/HS-P861-G9JR-RES-*).
4
+ //
5
+ // hs gateway validate [slug] [--json] validate user skills + scope coverage
6
+ // hs gateway register … blocked on HS-DCCP (prints why, exit 2)
7
+ //
8
+ // Validation is read-only and report-only: it never edits, deletes, or
9
+ // auto-disables user content, and never rewrites a scope declaration.
10
+
11
+ import path from 'node:path';
12
+ import process from 'node:process';
13
+
14
+ import { buildGatewayReport, USER_SKILLS_REL } from './gateway-skills-lib.mjs';
15
+
16
+ const BLOCKED_COMMANDS = new Set(['register', 'rotate-credentials', 'disable', 'unregister', 'list']);
17
+
18
+ function usage() {
19
+ process.stderr.write(
20
+ [
21
+ 'Usage: hs gateway <command>',
22
+ '',
23
+ ' validate [slug] [--json] Validate user-authored skill files under',
24
+ ` ${USER_SKILLS_REL}${path.sep}<slug>${path.sep}SKILL.md and report`,
25
+ ' skills whose declared scope no registered server provides.',
26
+ ' register | rotate-credentials | disable | unregister | list',
27
+ ' Not available yet — server registration lands with',
28
+ ' HS-DCCP-EPIC-thin-project-scoped-mcp-connector.',
29
+ '',
30
+ ].join('\n'),
31
+ );
32
+ }
33
+
34
+ function renderHuman(report, { slugFilter }) {
35
+ const lines = [];
36
+ const { skills, registry, orphans } = report;
37
+ if (!skills.exists) {
38
+ lines.push(`No user skills — ${USER_SKILLS_REL} does not exist yet.`);
39
+ lines.push(`Author one at ${path.join(USER_SKILLS_REL, '<slug>', 'SKILL.md')} (see the README there or the HS-P861 research doc for the frontmatter format).`);
40
+ return { text: lines.join('\n'), exitCode: 0 };
41
+ }
42
+
43
+ const shown = slugFilter ? skills.skills.filter((s) => s.slug === slugFilter) : skills.skills;
44
+ if (slugFilter && shown.length === 0) {
45
+ return { text: `No user skill named "${slugFilter}" under ${skills.root}.`, exitCode: 1 };
46
+ }
47
+
48
+ for (const s of shown) {
49
+ const flag = s.ok ? (s.warnings.length ? 'warn' : ' ok ') : 'FAIL';
50
+ lines.push(`[${flag}] ${s.slug} (${s.relPath})`);
51
+ for (const e of s.errors) lines.push(` error: ${e.message}`);
52
+ for (const w of s.warnings) lines.push(` warning: ${w.message}`);
53
+ }
54
+ for (const m of skills.misplaced) lines.push(`[warn] ${m.name} — ${m.reason}`);
55
+
56
+ lines.push('');
57
+ if (!registry.available) lines.push(`Server registry: not yet available — ${registry.reason ?? report.orphans.registry_reason ?? ''}`.trimEnd());
58
+ const orphanList = slugFilter ? orphans.orphans.filter((o) => o.slug === slugFilter) : orphans.orphans;
59
+ if (orphanList.length) {
60
+ lines.push(`Scope coverage: ${orphanList.length} skill(s) declare a scope no registered server provides (they stay inert until fixed):`);
61
+ for (const o of orphanList) lines.push(` - ${o.slug}: ${o.missing.join(', ')}`);
62
+ } else {
63
+ lines.push('Scope coverage: no orphaned scope declarations.');
64
+ }
65
+
66
+ const invalidShown = shown.filter((s) => !s.ok).length;
67
+ lines.push('');
68
+ lines.push(`${shown.length} skill(s) checked · ${invalidShown} invalid · ${orphanList.length} orphaned`);
69
+ return { text: lines.join('\n'), exitCode: invalidShown > 0 ? 1 : 0 };
70
+ }
71
+
72
+ async function main(argv) {
73
+ const command = argv[0];
74
+ if (!command || command === '--help' || command === '-h') {
75
+ usage();
76
+ return command ? 0 : 2;
77
+ }
78
+
79
+ if (BLOCKED_COMMANDS.has(command)) {
80
+ process.stderr.write(
81
+ `hs gateway ${command}: not available yet — user MCP server registration (registration table, encrypted credentials, live probe) lands with HS-DCCP-EPIC-thin-project-scoped-mcp-connector, which is still open. Skill validation works today: hs gateway validate\n`,
82
+ );
83
+ return 2;
84
+ }
85
+
86
+ if (command !== 'validate') {
87
+ process.stderr.write(`hs gateway: unknown command "${command}"\n\n`);
88
+ usage();
89
+ return 2;
90
+ }
91
+
92
+ const rest = argv.slice(1);
93
+ const json = rest.includes('--json');
94
+ const slugFilter = rest.find((a) => !a.startsWith('--')) ?? null;
95
+ const vaultRoot = process.env.HS_VAULT_ROOT ? path.resolve(process.env.HS_VAULT_ROOT) : process.cwd();
96
+
97
+ const report = buildGatewayReport(vaultRoot);
98
+ if (json) {
99
+ const filtered = slugFilter
100
+ ? {
101
+ ...report,
102
+ skills: { ...report.skills, skills: report.skills.skills.filter((s) => s.slug === slugFilter) },
103
+ orphans: { ...report.orphans, orphans: report.orphans.orphans.filter((o) => o.slug === slugFilter) },
104
+ }
105
+ : report;
106
+ process.stdout.write(`${JSON.stringify(filtered, null, 2)}\n`);
107
+ return filtered.skills.skills.some((s) => !s.ok) ? 1 : 0;
108
+ }
109
+
110
+ const { text, exitCode } = renderHuman(report, { slugFilter });
111
+ process.stdout.write(`${text}\n`);
112
+ return exitCode;
113
+ }
114
+
115
+ process.exit(await main(process.argv.slice(2)));
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ // hs-mode.mjs — CLI for the HeySpex master on/off switch.
3
+ //
4
+ // hs mode → status
5
+ // hs mode status → what is on, what is off, and why
6
+ // hs mode on|off → flip the whole layer
7
+ // hs mode toggle → flip to the opposite of the current mode
8
+ //
9
+ // Flags: --json (machine-readable), --quiet (exit code only).
10
+ // Exit codes: 0 ok · 1 usage · 2 flip failed.
11
+
12
+ import { MODE_ROOT as REPO_ROOT, resolveMode, setMode, mcpEnabled, claudeMdStubbed } from './heyspex-mode.mjs';
13
+
14
+ const argv = process.argv.slice(2).filter((a) => a !== 'mode');
15
+ const flags = new Set(argv.filter((a) => a.startsWith('--')));
16
+ const verb = argv.find((a) => !a.startsWith('--')) ?? 'status';
17
+ const JSON_OUT = flags.has('--json');
18
+ const QUIET = flags.has('--quiet');
19
+
20
+ const say = (s = '') => { if (!QUIET && !JSON_OUT) console.log(s); };
21
+
22
+ function statusReport() {
23
+ const { mode, source, overriddenByEnv, persisted, state } = resolveMode(REPO_ROOT);
24
+ return {
25
+ mode,
26
+ source,
27
+ overriddenByEnv,
28
+ persisted,
29
+ changedAt: state?.changedAt ?? null,
30
+ surfaces: {
31
+ hooks: mode === 'on' ? 'live' : 'silenced',
32
+ mcp: mcpEnabled(REPO_ROOT) ? 'enabled' : 'disabled',
33
+ statusline: mode === 'on' ? 'live' : 'blank',
34
+ claudeMd: claudeMdStubbed(REPO_ROOT) ? 'stubbed' : 'full agent contract',
35
+ },
36
+ };
37
+ }
38
+
39
+ function printStatus(r) {
40
+ const badge = r.mode === 'on' ? 'ON' : 'OFF';
41
+ say(`HeySpex is ${badge} (from ${r.source})`);
42
+ say('');
43
+ say(` hooks ${r.surfaces.hooks}`);
44
+ say(` MCP server ${r.surfaces.mcp}`);
45
+ say(` statusline ${r.surfaces.statusline}`);
46
+ say(` CLAUDE.md ${r.surfaces.claudeMd}`);
47
+ if (r.changedAt) say(`\n last changed ${r.changedAt}`);
48
+ if (r.overriddenByEnv) {
49
+ say('');
50
+ say(` ! HEYSPEX_MODE is set in this shell and overrides the saved mode`);
51
+ say(` (saved: ${r.persisted}). Unset it to go back to the saved value.`);
52
+ }
53
+ // A mismatch here means Claude Code was never restarted after the flip.
54
+ const mcpShouldBe = r.mode === 'on' ? 'enabled' : 'disabled';
55
+ if (r.surfaces.mcp !== mcpShouldBe) {
56
+ say(`\n ! MCP server is ${r.surfaces.mcp} but mode is ${r.mode} — rerun \`hs mode ${r.mode}\`.`);
57
+ }
58
+ }
59
+
60
+ if (flags.has('--help') || flags.has('-h')) {
61
+ console.log(`hs mode — turn the HeySpex agent layer on or off
62
+
63
+ Usage:
64
+ hs mode [status] Show what is on, what is off, and why
65
+ hs mode on Turn HeySpex on (hooks, MCP, statusline, agent contract)
66
+ hs mode off Turn HeySpex off (this repo behaves like a plain codebase)
67
+ hs mode toggle Flip to the opposite mode
68
+
69
+ Flags:
70
+ --force With \`on\`: restore CLAUDE.md even if you edited it
71
+ while HeySpex was off (your version is overwritten;
72
+ the real contract is at .heyspex/mode-backup/).
73
+ --json Machine-readable output
74
+ --quiet Exit code only
75
+ --help, -h This help
76
+
77
+ Both \`on\` and \`off\` require restarting Claude Code: the MCP server and
78
+ CLAUDE.md are read once at startup. Hooks and the statusline take effect
79
+ immediately.
80
+
81
+ Per-shell override without touching saved state:
82
+ HEYSPEX_MODE=off claude # off for this session only
83
+ `);
84
+ process.exit(0);
85
+ }
86
+
87
+ if (verb === 'status') {
88
+ const r = statusReport();
89
+ if (JSON_OUT) console.log(JSON.stringify(r, null, 2));
90
+ else printStatus(r);
91
+ process.exit(0);
92
+ }
93
+
94
+ let target = verb;
95
+ if (verb === 'toggle') target = resolveMode(REPO_ROOT).mode === 'on' ? 'off' : 'on';
96
+
97
+ if (target !== 'on' && target !== 'off') {
98
+ process.stderr.write(`hs mode: expected \`on\`, \`off\`, \`toggle\`, or \`status\` (got \`${verb}\`)\n`);
99
+ process.exit(1);
100
+ }
101
+
102
+ const before = resolveMode(REPO_ROOT).mode;
103
+ const result = setMode(target, { repoRoot: REPO_ROOT, force: flags.has('--force') });
104
+
105
+ if (!result.ok) {
106
+ if (JSON_OUT) console.log(JSON.stringify(result, null, 2));
107
+ else process.stderr.write(`hs mode: could not turn HeySpex ${target} — ${result.error}\n`);
108
+ process.exit(2);
109
+ }
110
+
111
+ if (JSON_OUT) {
112
+ console.log(JSON.stringify({ ...result, previous: before, ...statusReport() }, null, 2));
113
+ process.exit(0);
114
+ }
115
+
116
+ say(before === target ? `HeySpex was already ${target} — reapplied.` : `HeySpex is now ${target.toUpperCase()}.`);
117
+ say('');
118
+ for (const step of result.steps) say(` ${step.surface.padEnd(12)} ${step.action}${step.reason ? ` (${step.reason})` : ''}`);
119
+ say('');
120
+ say(' Restart Claude Code for the MCP server and CLAUDE.md to take effect.');
121
+ if (target === 'off') {
122
+ say(' Turn it back on any time with: npm run heyspex:on');
123
+ } else {
124
+ say(' Verify the whole setup with: npm run doctor');
125
+ }
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * hs next — on-deck resolver CLI fallback ([[HS-TASK-ISK7]]).
4
+ * Dispatched from bin/hs.mjs. One call, no MCP required.
5
+ */
6
+ import process from 'node:process';
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { spawn } from 'node:child_process';
10
+ import readline from 'node:readline/promises';
11
+ import { fileURLToPath, pathToFileURL } from 'node:url';
12
+ import {
13
+ isRunFrontToken,
14
+ normalizeRunFrontToken,
15
+ runFrontCliHint,
16
+ } from '../vendor/heyspex-mcp/run-front.mjs';
17
+ import { normalizeBareSpecIdToken } from '../vendor/heyspex-mcp/spec-id-lib.mjs';
18
+ import { resolveCodeRoot } from './lib/shared-path-helpers.mjs';
19
+
20
+ process.env.HEYSPEX_DISABLE_STDIO_BOOTSTRAP = '1';
21
+
22
+ // This script's own install location — vendor/heyspex-mcp/cli.mjs is CODE,
23
+ // not vault content, so it must resolve against the code checkout, not
24
+ // HS_VAULT_ROOT. Since the repo split, HS_VAULT_ROOT can point at a vault-only
25
+ // checkout (e.g. heyspex-specs) with no vendor/ at all
26
+ // ([[HS-1LCC-4XKS-DIAG-frozen-checks-run-against-the-vault]]).
27
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
28
+
29
+ const HELP = `hs next — resolve the on-deck /hs-next brief
30
+
31
+ Usage:
32
+ hs next [front] [spec_id] [--summary] [--json] [--text] [--ignore-focus] [--include-horizon]
33
+ hs next --help
34
+
35
+ Spec id (optional):
36
+ A bare key works everywhere a full id does: hs next CJV2 == hs next HS-CJV2.
37
+
38
+ Front (optional — the default is whatever 'hs next front' last set; handheld until then):
39
+ handheld Handheld: drive one on-deck task, you in the loop
40
+ assist | assisted Assisted: arm a dispatcher run on the milestone, you on the loop
41
+ auto | autonomous Autonomous: arm an unattended dispatcher run on the milestone
42
+
43
+ Flags:
44
+ --summary Lightweight claim banner only (default: on)
45
+ --no-summary Full execution brief (post-claim shape)
46
+ --json Machine-readable JSON on stdout
47
+ --text Print user_prompt_markdown only (default when not --json)
48
+ --ignore-focus Skip focus pin / manifest focus override
49
+ --include-horizon Include horizon backlog when queue is empty
50
+ --roadmap <id> Roadmap id (default: HS-ROADMAP-PRODUCT)
51
+ --front <name> Explicit front (${runFrontCliHint()})
52
+
53
+ At a terminal, a claim-ready brief ends with a real y/N question: answer y
54
+ (or yes / proceed / confirm) to claim the spec and launch an agent on it
55
+ (same as: hs next <id> --launch --claim). Piped or --json output never
56
+ prompts; it prints that command instead. HS_NEXT_NO_PROMPT=1 disables the
57
+ question.
58
+
59
+ Under an assist/auto front the terminal question is "Arm the run?" instead:
60
+ yes arms it (same as: hs next assist|auto <HS-MIL…> --launch), which writes
61
+ the queue + front files the running dispatcher reads — no terminal opens. An
62
+ epic or task id arms its parent milestone, because the dispatcher runs at
63
+ milestone grain.
64
+
65
+ Examples:
66
+ hs next HS-EPIC-YQ5A --summary --json
67
+ hs next CJV2 (same as: hs next HS-CJV2)
68
+ hs next auto HS-EPIC-YQ5A --summary --json
69
+ hs next assist HS-MIL-DECK --text
70
+
71
+ Launch + per-kind defaults (needs the console): this help covers the brief
72
+ resolver only. hs next <id> --launch, hs next model|permission-mode [kind]
73
+ [value] and hs next claim [on|off] are handled by the console half - run
74
+ hs next --launch --help
75
+ for that help.
76
+ `;
77
+
78
+ export const EXIT = {
79
+ OK: 0,
80
+ USAGE: 1,
81
+ RESOLVE_FAILED: 2,
82
+ };
83
+
84
+ /** @param {string[]} argv tokens after "next" */
85
+ /** Replies that mean "yes, claim it" at the terminal prompt (case-insensitive). */
86
+ export const CLAIM_CONFIRM_TOKENS = new Set(['y', 'yes', 'proceed', 'confirm']);
87
+
88
+ const AGENT_CLAIM_LINE =
89
+ /^Ready to claim \*\*.+?\*\* `[^`]+` to \*\*.+?\*\*(?:, as \*\*[^*]*\*\*)?\. Type `proceed`.*$/m;
90
+
91
+ /**
92
+ * The on-deck brief's last line is written for an agent sitting in a chat
93
+ * ("Type `proceed` ... to claim it and begin"). At a shell nothing listens
94
+ * for that reply — typing `yes` into PowerShell runs a command named `yes`.
95
+ * Rewrite the line for a terminal: keep "Ready to claim ... to <intent>",
96
+ * drop the agent-identity clause (no agent is attached yet), and say what
97
+ * actually claims it from here. Returns the markdown unchanged when the
98
+ * brief is not a claim question.
99
+ */
100
+ export function renderTerminalClaimPrompt(md, result, { interactive = false } = {}) {
101
+ const claimId = result?.claim_target?.spec_id;
102
+ if (result?.agent_dispatch?.phase !== 'claim_authorization' || !claimId) return md;
103
+ const m = AGENT_CLAIM_LINE.exec(md);
104
+ if (!m) return md;
105
+ const head = m[0]
106
+ .replace(/, as \*\*[^*]*\*\*\./, '.')
107
+ .replace(/\. Type `proceed`.*$/, '.');
108
+ const launchCmd = `hs next ${claimId} --launch --claim`;
109
+ const line = interactive
110
+ ? head
111
+ : `${head}\nTo claim it and launch an agent on it: \`${launchCmd}\``;
112
+ return md.replace(m[0], line);
113
+ }
114
+
115
+ /** Ask at the terminal whether to claim + launch. Resolves to true on a yes-token. */
116
+ async function askClaimAtTerminal(claimId) {
117
+ return askYesNoAtTerminal(`Claim ${claimId} and launch an agent on it now? [y/N] `);
118
+ }
119
+
120
+ /** One y/N question at the terminal. Resolves to true on a yes-token. */
121
+ async function askYesNoAtTerminal(question) {
122
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
123
+ try {
124
+ const answer = await rl.question(question);
125
+ return CLAIM_CONFIRM_TOKENS.has(answer.trim().toLowerCase());
126
+ } finally {
127
+ rl.close();
128
+ }
129
+ }
130
+
131
+ /** Hand off to the console half of `hs next` (POST /api/agent/start); exits with its code. */
132
+ function launchClaimedSpec(claimId) {
133
+ spawnHsNext([claimId, '--launch', '--claim']);
134
+ }
135
+
136
+ /** Run `hs next <args…>` through the console half and exit with its code. */
137
+ function spawnHsNext(nextArgs) {
138
+ const hsBin = path.join(PACKAGE_ROOT, 'bin', 'hs.mjs');
139
+ const child = spawn(process.execPath, [hsBin, 'next', ...nextArgs], {
140
+ cwd: PACKAGE_ROOT,
141
+ stdio: 'inherit',
142
+ env: process.env,
143
+ });
144
+ child.on('exit', (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
145
+ child.on('error', (err) => {
146
+ process.stderr.write(`hs next: failed to launch: ${err.message}\n`);
147
+ process.exit(EXIT.RESOLVE_FAILED);
148
+ });
149
+ }
150
+
151
+ /**
152
+ * The stored default front — `launchModels.defaultRunMode` in
153
+ * `spex-console/settings.json`, the file `hs next front <name>` and the
154
+ * console's Pin & Launch modal both write. Read straight off disk so a bare
155
+ * `hs next <id>` needs no console. Null when unset or unreadable (caller
156
+ * keeps handheld).
157
+ * @param {string} codeRoot
158
+ * @returns {import('../vendor/heyspex-mcp/run-front.mjs').RunFront | null}
159
+ */
160
+ export function readDefaultRunFront(...roots) {
161
+ // The console keeps settings.json under its ACTIVE VAULT root (repoRoot() is
162
+ // the vault checkout), so the vault root is tried first; the code checkout
163
+ // is the pre-split location and stays as the fallback.
164
+ for (const root of roots) {
165
+ if (!root) continue;
166
+ try {
167
+ const raw = fs.readFileSync(path.join(root, 'spex-console', 'settings.json'), 'utf8');
168
+ const parsed = JSON.parse(raw);
169
+ const front = normalizeRunFrontToken(parsed?.launchModels?.defaultRunMode);
170
+ if (front) return front;
171
+ } catch {
172
+ // try the next root
173
+ }
174
+ }
175
+ return null;
176
+ }
177
+
178
+ /** `assist` / `auto` — the word the CLI takes for a run front. */
179
+ export function frontWord(front) {
180
+ return front === 'autonomous' ? 'auto' : 'assist';
181
+ }
182
+
183
+ /**
184
+ * Under a run front (assisted / autonomous) the brief is not a one-task claim
185
+ * question — the unit the dispatcher takes is a MILESTONE. Resolve which one:
186
+ * the id itself when it is a milestone, else the top of the claim target's
187
+ * breadcrumb (an epic or task arms its parent milestone). Null under handheld
188
+ * or when no milestone can be named.
189
+ * @param {Record<string, any> | null | undefined} result
190
+ * @param {string} front
191
+ * @returns {{ milestoneId: string, from: 'milestone' | 'epic' | 'task', specId: string } | null}
192
+ */
193
+ export function resolveArmTarget(result, front) {
194
+ if (!result || (front !== 'assisted' && front !== 'autonomous')) return null;
195
+ const specPath = String(result.spec_path || '').replace(/\\/g, '/');
196
+ const specId = result.spec_id ? String(result.spec_id) : '';
197
+ if (specId && /(^|\/)milestones\//.test(specPath)) {
198
+ return { milestoneId: specId, from: 'milestone', specId };
199
+ }
200
+ const crumb = result.claim_target?.hierarchy_breadcrumb;
201
+ const top = Array.isArray(crumb) && crumb.length > 1 ? String(crumb[0]) : '';
202
+ if (!top) return null;
203
+ const from = /(^|\/)epics\//.test(specPath) ? 'epic' : 'task';
204
+ return { milestoneId: top, from, specId: specId || String(result.claim_target?.spec_id || '') };
205
+ }
206
+
207
+ /** The console-half command that arms the run for `target`. */
208
+ export function armCommand(target, front) {
209
+ return `hs next ${frontWord(front)} ${target.milestoneId} --launch`;
210
+ }
211
+
212
+ /** Terminal-facing lines that replace the one-task claim question under a run front. */
213
+ export function renderArmLines(target, front) {
214
+ const lines = ['', '---', ''];
215
+ if (target.from !== 'milestone') {
216
+ lines.push(
217
+ `${front} runs arm at milestone grain — ${target.specId} sits under milestone ${target.milestoneId}, so that whole milestone is what gets queued.`,
218
+ );
219
+ }
220
+ lines.push(`To arm the ${front} run: \`${armCommand(target, front)}\``);
221
+ return lines.join('\n');
222
+ }
223
+
224
+ export function parseNextArgs(argv) {
225
+ let specId;
226
+ let roadmapId;
227
+ /** @type {import('../vendor/heyspex-mcp/run-front.mjs').RunFront} */
228
+ let front = 'handheld';
229
+ let frontExplicit = false;
230
+ let summary = true;
231
+ let json = false;
232
+ let text = false;
233
+ let ignoreFocus = false;
234
+ let includeHorizon = false;
235
+
236
+ for (let i = 0; i < argv.length; i += 1) {
237
+ const a = argv[i];
238
+ if (a === '--help' || a === '-h') return { help: true };
239
+ if (a === '--json') {
240
+ json = true;
241
+ continue;
242
+ }
243
+ if (a === '--text') {
244
+ text = true;
245
+ continue;
246
+ }
247
+ if (a === '--summary') {
248
+ summary = true;
249
+ continue;
250
+ }
251
+ if (a === '--no-summary') {
252
+ summary = false;
253
+ continue;
254
+ }
255
+ if (a === '--ignore-focus') {
256
+ ignoreFocus = true;
257
+ continue;
258
+ }
259
+ if (a === '--include-horizon') {
260
+ includeHorizon = true;
261
+ continue;
262
+ }
263
+ if (a === '--roadmap') {
264
+ const v = argv[++i];
265
+ if (!v) return { error: { code: EXIT.USAGE, message: '--roadmap requires a value' } };
266
+ roadmapId = v;
267
+ continue;
268
+ }
269
+ if (a === '--front') {
270
+ const v = argv[++i];
271
+ if (!v) return { error: { code: EXIT.USAGE, message: '--front requires a value' } };
272
+ const normalized = normalizeRunFrontToken(v);
273
+ if (!normalized) {
274
+ return {
275
+ error: {
276
+ code: EXIT.USAGE,
277
+ message: `unknown front: ${v} (expected ${runFrontCliHint()})`,
278
+ },
279
+ };
280
+ }
281
+ front = normalized;
282
+ frontExplicit = true;
283
+ continue;
284
+ }
285
+ if (a.startsWith('-')) {
286
+ return { error: { code: EXIT.USAGE, message: `unknown flag: ${a}` } };
287
+ }
288
+ if (isRunFrontToken(a)) {
289
+ front = normalizeRunFrontToken(a);
290
+ frontExplicit = true;
291
+ continue;
292
+ }
293
+ // A bare key resolves like a full id: `hs next CJV2` is `hs next HS-CJV2`
294
+ // (owner ruling 2026-09-01). Only tokens that already failed as a front or
295
+ // a flag reach here, so this widens what used to be a hard error rather
296
+ // than changing any input that already worked.
297
+ if (!specId) {
298
+ const normalized = normalizeBareSpecIdToken(a);
299
+ if (normalized) {
300
+ specId = normalized;
301
+ continue;
302
+ }
303
+ }
304
+ return { error: { code: EXIT.USAGE, message: `unexpected argument: ${a}` } };
305
+ }
306
+
307
+ return {
308
+ args: {
309
+ spec_id: specId,
310
+ roadmap_id: roadmapId,
311
+ front,
312
+ front_explicit: frontExplicit,
313
+ summary,
314
+ json,
315
+ text: text || !json,
316
+ ignore_focus: ignoreFocus,
317
+ include_horizon: includeHorizon,
318
+ },
319
+ };
320
+ }
321
+
322
+ async function main() {
323
+ const parsed = parseNextArgs(process.argv.slice(2));
324
+ if (parsed.help) {
325
+ process.stdout.write(`${HELP}\n`);
326
+ process.exit(EXIT.OK);
327
+ }
328
+ if (parsed.error) {
329
+ process.stderr.write(`hs next: ${parsed.error.message}\n`);
330
+ process.exit(parsed.error.code);
331
+ }
332
+
333
+ const vaultRoot = process.env.HS_VAULT_ROOT;
334
+ if (!vaultRoot) {
335
+ process.stderr.write('hs next: HS_VAULT_ROOT is not set (the dispatcher must pass it).\n');
336
+ process.exit(EXIT.USAGE);
337
+ }
338
+
339
+ const codeRoot = resolveCodeRoot({ packageRoot: PACKAGE_ROOT });
340
+ // No front word typed: the stored default (`hs next front <name>`) decides.
341
+ if (!parsed.args.front_explicit) {
342
+ const stored = readDefaultRunFront(vaultRoot, codeRoot);
343
+ if (stored) parsed.args.front = stored;
344
+ }
345
+ // resolveNextOnDeckPayload / applyOnDeckHttpAugments / injectCloudWipState
346
+ // live in on-deck-brief.mjs, not cli.mjs (cli.mjs never re-exports them —
347
+ // every other caller, e.g. prompts.mjs and claim-tools.mjs, imports them
348
+ // straight from on-deck-brief.mjs).
349
+ const onDeckBriefPath = path.join(codeRoot, 'vendor', 'heyspex-mcp', 'on-deck-brief.mjs');
350
+ const { resolveNextOnDeckAroundActiveClaims, applyOnDeckHttpAugments, injectCloudWipState } =
351
+ await import(pathToFileURL(onDeckBriefPath).href);
352
+ const config = {
353
+ vaultRoot,
354
+ defaultRoadmapId: process.env.HEYSPEX_ROADMAP_ID || 'HS-ROADMAP-PRODUCT',
355
+ };
356
+ const toolArgs = {
357
+ summary: parsed.args.summary,
358
+ ignore_focus: parsed.args.ignore_focus,
359
+ include_horizon: parsed.args.include_horizon,
360
+ front: parsed.args.front,
361
+ };
362
+ if (parsed.args.spec_id) toolArgs.spec_id = parsed.args.spec_id;
363
+ if (parsed.args.roadmap_id) toolArgs.roadmap_id = parsed.args.roadmap_id;
364
+
365
+ // Cloud reliance ([[HS-VOCF]]): the hand-off ledger lives in Neon there, so
366
+ // pre-fetch it (GET /api/wip via the MCP transport) and let the WIP enricher
367
+ // surface the row instead of the stale local file. Best-effort — falls back
368
+ // to the file ledger on any failure.
369
+ const argsWithCloudWip = await injectCloudWipState(vaultRoot, toolArgs);
370
+ // Steps past tasks another agent actively holds and lists who holds what,
371
+ // so a second terminal gets the next launchable task ([[HS-3C9M-A4TX-CAP]]).
372
+ const { error, result } = await resolveNextOnDeckAroundActiveClaims(
373
+ vaultRoot,
374
+ config,
375
+ argsWithCloudWip,
376
+ );
377
+ if (error) {
378
+ if (parsed.args.json) {
379
+ process.stderr.write(`${JSON.stringify({ error })}\n`);
380
+ } else {
381
+ process.stderr.write(`hs next: ${error.error ?? 'RESOLVE_FAILED'} — ${error.message ?? ''}\n`);
382
+ }
383
+ process.exit(EXIT.RESOLVE_FAILED);
384
+ }
385
+
386
+ await applyOnDeckHttpAugments(result, toolArgs);
387
+
388
+ if (parsed.args.json) {
389
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
390
+ process.exit(EXIT.OK);
391
+ }
392
+
393
+ const rawMd = result?.user_prompt_markdown ?? result?.prompt_markdown ?? '';
394
+ const interactive =
395
+ Boolean(process.stdin.isTTY && process.stdout.isTTY) && !process.env.HS_NEXT_NO_PROMPT;
396
+ // A run front never asks to claim one task — drop the agent claim line and
397
+ // ask about arming the milestone instead.
398
+ const arm = resolveArmTarget(result, parsed.args.front);
399
+ const md = arm
400
+ ? rawMd.replace(AGENT_CLAIM_LINE, '').trimEnd()
401
+ : renderTerminalClaimPrompt(rawMd, result, { interactive });
402
+ process.stdout.write(`${md}\n`);
403
+
404
+ if (arm) {
405
+ // The brief's own orchestration section (phase arm_run) already names the
406
+ // command; only print the CLI's lines when an older brief shape lacks it.
407
+ if (result?.agent_dispatch?.phase !== 'arm_run') {
408
+ process.stdout.write(`${renderArmLines(arm, parsed.args.front)}\n`);
409
+ }
410
+ if (!interactive) process.exit(EXIT.OK);
411
+ if (await askYesNoAtTerminal(`Arm the ${parsed.args.front} run on ${arm.milestoneId} now? [y/N] `)) {
412
+ spawnHsNext([frontWord(parsed.args.front), arm.milestoneId, '--launch']);
413
+ return;
414
+ }
415
+ process.stdout.write(`Not armed. Later: ${armCommand(arm, parsed.args.front)}\n`);
416
+ process.exit(EXIT.OK);
417
+ }
418
+
419
+ const claimId = result?.claim_target?.spec_id;
420
+ const isClaimQuestion = result?.agent_dispatch?.phase === 'claim_authorization' && claimId;
421
+ if (!interactive || !isClaimQuestion) process.exit(EXIT.OK);
422
+
423
+ // Attached to a real terminal: actually wait for the answer the brief asks
424
+ // for, then hand off to the launch half of `hs next` on yes.
425
+ if (await askClaimAtTerminal(claimId)) {
426
+ launchClaimedSpec(claimId);
427
+ return;
428
+ }
429
+ process.stdout.write(`Not claimed. Later: hs next ${claimId} --launch --claim\n`);
430
+ process.exit(EXIT.OK);
431
+ }
432
+
433
+ const invoked = process.argv[1]
434
+ && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
435
+
436
+ if (invoked) {
437
+ main().catch((err) => {
438
+ process.stderr.write(`hs next: ${err instanceof Error ? err.message : String(err)}\n`);
439
+ process.exit(EXIT.RESOLVE_FAILED);
440
+ });
441
+ }