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,67 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lib/code-repo-root.mjs — resolves the CODE repo root (workflow-protocol), not the vault.
4
+ *
5
+ * Repo-split context (2026-08-31, [[repo-split-step16-specs-path-inventory-2026-08-31]]):
6
+ * the spec vault (specs/ + heyspex.yaml) moved out of this repo to a sibling checkout
7
+ * (C:\dev-projects\heyspex-specs). A family of dev-factory/lib modules used to find "the repo
8
+ * root" by walking UP from their own directory looking for heyspex.yaml — that marker is gone
9
+ * from this repo now, so that walk silently fell back to process.cwd(), fragmenting
10
+ * .heyspex/ state files (fleet-state.json, queues, progress, join-codes, etc.) into whatever
11
+ * directory the process happened to start in.
12
+ *
13
+ * KEY POINT: those modules manage CODE-REPO state, not vault state — the fix is NOT to chase
14
+ * the vault registry, it's to anchor on the code repo itself. The code repo is reliably
15
+ * identifiable (it contains dev-factory/ and package.json with name "heyspex"), and every
16
+ * caller of this helper lives inside it — so there is nothing to walk or discover: the repo
17
+ * root is simply two directories up from THIS file (dev-factory/lib/code-repo-root.mjs ->
18
+ * dev-factory -> repo root), resolved via import.meta.url rather than process.cwd().
19
+ *
20
+ * node dev-factory/lib/code-repo-root.mjs # prints the resolved root
21
+ */
22
+
23
+ import { existsSync, readFileSync } from "node:fs";
24
+ import { dirname, join, resolve } from "node:path";
25
+ import { fileURLToPath } from "node:url";
26
+
27
+ const __dirname = dirname(fileURLToPath(import.meta.url));
28
+
29
+ /**
30
+ * Returns the absolute path to the code repo root (the workflow-protocol checkout this file
31
+ * lives in). No walking, no markers — this file's own location fixes the answer. Throws loudly
32
+ * if the sanity check fails (would only happen if this file were copied/moved somewhere else).
33
+ */
34
+ export function codeRepoRoot() {
35
+ const root = resolve(__dirname, "..", "..");
36
+
37
+ if (!existsSync(join(root, "dev-factory"))) {
38
+ throw new Error(
39
+ `code-repo-root: sanity check failed — no dev-factory/ found under "${root}". ` +
40
+ `codeRepoRoot() assumes this file stays at dev-factory/lib/code-repo-root.mjs.`,
41
+ );
42
+ }
43
+
44
+ const pkgPath = join(root, "package.json");
45
+ if (existsSync(pkgPath)) {
46
+ try {
47
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
48
+ if (pkg?.name && pkg.name !== "heyspex") {
49
+ throw new Error(
50
+ `code-repo-root: sanity check failed — "${root}"/package.json name is "${pkg.name}", expected "heyspex".`,
51
+ );
52
+ }
53
+ } catch (e) {
54
+ if (e instanceof SyntaxError) {
55
+ throw new Error(`code-repo-root: sanity check failed — could not parse "${pkgPath}": ${e.message}`);
56
+ }
57
+ throw e;
58
+ }
59
+ }
60
+
61
+ return root;
62
+ }
63
+
64
+ // ── CLI: print the resolved root ────────────────────────────────────────────────────────────
65
+ if (import.meta.url.endsWith("/" + (process.argv[1]?.split(/[\\/]/).pop() ?? ""))) {
66
+ console.log(codeRepoRoot());
67
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * lib/context-budget.mjs — THE prompt-token budget formula, in one place.
3
+ *
4
+ * HS-6NFD defect 3: `builder.mjs`'s prompt assembler and `lib/pick-num-ctx.mjs`'s bucket
5
+ * picker each computed "does this fit?" with DIFFERENT arithmetic, and the picker's was the
6
+ * looser one:
7
+ *
8
+ * picker: estimatedTokens + maxOutputTokens <= numCtx → "fits"
9
+ * assembler: estimatedTokens <= floor(numCtx * 0.8) - maxOutputTokens → "fits"
10
+ *
11
+ * So the picker under-reserved by `0.2 * numCtx` on EVERY call. On HS-CHWJ (est 12000,
12
+ * recommended bucket 16384, output 4096) the picker said 16096 <= 16384 → fits, the
13
+ * assembler said 9011 < 12000 → drop every reference file, and the builder hallucinated a
14
+ * duplicate type because it never saw the enum it was extending.
15
+ *
16
+ * Both sides now import from here. The headroom exists because the estimate is a
17
+ * chars/4 approximation over the spec JSON + targets + reference files only — it does not
18
+ * count the system prompt, the instruction scaffolding, the chat template, or tokenizer
19
+ * variance, so the window must never be planned to the brim.
20
+ */
21
+
22
+ /** Fraction of the context window the PROMPT may plan to occupy (the rest absorbs the
23
+ * system prompt, scaffolding, chat template, and chars/4 estimation error). */
24
+ export const PROMPT_WINDOW_FRACTION = 0.8;
25
+
26
+ /** Chars-per-token used by every prompt-size estimate in the factory. */
27
+ export const CHARS_PER_TOKEN = 4;
28
+
29
+ /** Token budget available to PROMPT content at a given window + output reservation.
30
+ * This is the authoritative definition — `builder.mjs` drops reference files until the
31
+ * estimate fits under it. May go <= 0 for a tiny window with a large output reservation;
32
+ * callers treat that as "nothing fits, size up". */
33
+ export function promptBudgetTokens(numCtx, maxOutputTokens) {
34
+ return Math.floor(Number(numCtx) * PROMPT_WINDOW_FRACTION) - Number(maxOutputTokens);
35
+ }
36
+
37
+ /** Smallest context window whose {@link promptBudgetTokens} holds `promptTokens`.
38
+ * The exact inverse of the above: `promptBudgetTokens(numCtxNeededFor(p, o), o) >= p`
39
+ * holds for all non-negative integers p, o (pinned in the self-test). */
40
+ export function numCtxNeededFor(promptTokens, maxOutputTokens) {
41
+ return Math.ceil((Number(promptTokens) + Number(maxOutputTokens)) / PROMPT_WINDOW_FRACTION);
42
+ }
43
+
44
+ /** Does `promptTokens` fit the prompt budget at this window? */
45
+ export function promptFitsNumCtx(promptTokens, numCtx, maxOutputTokens) {
46
+ return Number(promptTokens) <= promptBudgetTokens(numCtx, maxOutputTokens);
47
+ }
48
+
49
+ /** Estimate tokens for a raw string (the chars/4 convention used repo-wide). */
50
+ export function estimateTokens(text) {
51
+ return Math.ceil(String(text ?? "").length / CHARS_PER_TOKEN);
52
+ }
53
+
54
+ // ── self-test ────────────────────────────────────────────────────────────────
55
+ if (process.argv.includes("--self-test")
56
+ && import.meta.url.endsWith("/" + (process.argv[1]?.split(/[\\/]/).pop() ?? ""))) {
57
+ let ok = true;
58
+ const check = (name, cond) => { console.log(` ${cond ? "✓" : "✗"} ${name}`); if (!cond) ok = false; };
59
+ console.log("== lib/context-budget.mjs self-test ==\n");
60
+
61
+ check("promptBudgetTokens matches builder.mjs's historical formula (16384/4096 → 9011)",
62
+ promptBudgetTokens(16384, 4096) === 9011);
63
+
64
+ // THE HS-CHWJ regression: the picker used to accept 16384 for a 12000-token prompt.
65
+ check("HS-6NFD regression: 12000 prompt tokens do NOT fit 16384 with a 4096 output reserve",
66
+ promptFitsNumCtx(12000, 16384, 4096) === false);
67
+ check("HS-6NFD regression: the window that DOES hold 12000 + 4096 is 20120",
68
+ numCtxNeededFor(12000, 4096) === 20120);
69
+
70
+ // The inverse must hold everywhere, not just on the one motivating case — this is what
71
+ // stops the picker from ever again handing the assembler a window it will overflow.
72
+ let inverseHolds = true;
73
+ for (const p of [0, 1, 999, 9011, 12000, 65536, 250000]) {
74
+ for (const o of [0, 4096, 16384]) {
75
+ if (!promptFitsNumCtx(p, numCtxNeededFor(p, o), o)) { inverseHolds = false; console.log(` ✗ p=${p} o=${o}`); }
76
+ }
77
+ }
78
+ check("numCtxNeededFor is a true inverse of promptBudgetTokens across the range", inverseHolds);
79
+
80
+ check("estimateTokens uses chars/4, rounding up", estimateTokens("abcde") === 2 && estimateTokens("") === 0);
81
+
82
+ console.log(`\n${ok ? "✓ self-test PASS" : "✗ self-test FAIL"}`);
83
+ process.exit(ok ? 0 : 1);
84
+ }
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * epic-detect.mjs — the ONE source of truth for "is this spec an epic / a
4
+ * parent-shaped spec / dispatchable as a single target?" (2026-07-11 consolidation).
5
+ *
6
+ * Before this module, the same predicate lived in THREE places with subtly different
7
+ * regexes — and one of them (the old `/-EPIC-/` in hs-auto-resolve.mjs) missed ids
8
+ * ENDING in `-EPIC`, which is exactly how the idle-fleet incident's epic slipped
9
+ * toward a single-target dispatch. Consolidating removes the drift risk.
10
+ *
11
+ * Callers pass a spec's raw text (+ optional path/id) to `specFacts`, then compose
12
+ * the decision they need from the pure predicates:
13
+ * - isEpicSpec — type: epic, id containing OR ending in `-EPIC`, or a file
14
+ * under specs/epics/.
15
+ * - isMilestoneSpec — type: milestone or a `-MIL` id.
16
+ * - isParentShaped — declares `children:` but the list is EMPTY and it has no
17
+ * `target:` of its own (nothing single-target about it).
18
+ * - isDispatchableSingleTarget — none of the above (the controller's build loop
19
+ * and the dispatch gate use this).
20
+ *
21
+ * `frontmatterChildIds` is the shared child-id extractor (block-form `children:` +
22
+ * `[[wikilink]]` items) so the childless-epic routing in engine/breakdown.mjs reads
23
+ * children the same way everywhere.
24
+ *
25
+ * Pure string logic, no network, no fs. `--self-test` covers every predicate + the
26
+ * exact -EPIC-suffix regression that motivated the module.
27
+ */
28
+
29
+ import { pathToFileURL } from "node:url";
30
+
31
+ const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
32
+
33
+ /** Extract the frontmatter block from raw spec text (empty string when absent). */
34
+ export function extractFrontmatter(raw) {
35
+ return String(raw ?? "").match(FRONTMATTER)?.[1] ?? "";
36
+ }
37
+
38
+ function fmScalar(fm, key) {
39
+ const m = fm.match(new RegExp(`^${key}:[ \\t]*(.+)$`, "m"));
40
+ return m ? m[1].trim().replace(/^['"]|['"]$/g, "") : null;
41
+ }
42
+
43
+ /**
44
+ * Count entries of a frontmatter list key (`key: []`, `key:` + `- item` block, or
45
+ * inline `key: [a, b]`). Returns null when the key is absent entirely (so a caller
46
+ * can distinguish "no children: key" from "children: []").
47
+ */
48
+ export function fmListCount(fm, key) {
49
+ const scalar = fm.match(new RegExp(`^${key}:[ \\t]*(.*)$`, "m"));
50
+ if (!scalar) return null; // key absent entirely
51
+ const inline = scalar[1].trim();
52
+ if (inline === "[]" || inline === "") {
53
+ const block = fm.match(new RegExp(`^${key}:[ \\t]*\\r?\\n((?:[ \\t]+-[ \\t]+.+\\r?\\n?)*)`, "m"));
54
+ if (!block || !block[1]) return 0;
55
+ return block[1].split(/\r?\n/).filter((l) => /^\s*-\s+\S/.test(l)).length;
56
+ }
57
+ if (inline.startsWith("[") && inline.endsWith("]")) {
58
+ const body = inline.slice(1, -1).trim();
59
+ return body ? body.split(",").filter((s) => s.trim()).length : 0;
60
+ }
61
+ return 1; // scalar value — treat as one entry
62
+ }
63
+
64
+ /**
65
+ * Extract the `[[wikilink]]` child basenames from a block-form `children:` key.
66
+ * Accepts either raw spec text (with `---` fences) or a bare frontmatter string.
67
+ * Matches engine/breakdown.mjs's original children reader exactly, so
68
+ * childless-epic routing has one implementation.
69
+ * @param {string} fmOrRaw
70
+ * @returns {string[]} child basenames (the inner text of each `[[…]]`)
71
+ */
72
+ export function frontmatterChildIds(fmOrRaw) {
73
+ const fm = /^---\r?\n/.test(String(fmOrRaw ?? "")) ? extractFrontmatter(fmOrRaw) : String(fmOrRaw ?? "");
74
+ const re = /^children:[ \t]*\r?\n([\s\S]*?)(?=^[A-Za-z_]|(?![\s\S]))/m;
75
+ const m = fm.match(re);
76
+ if (!m) return [];
77
+ return [...m[1].matchAll(/\[\[([^\]]+)\]\]/g)].map((x) => x[1]);
78
+ }
79
+
80
+ /**
81
+ * Normalize a spec into the facts the predicates read. Pass raw text; optionally a
82
+ * resolved path (enables the specs/epics/ check) and a fallback id.
83
+ * @param {{ raw?: string, specPath?: string, specId?: string|null }} input
84
+ * @returns {{ id: string, type: string, path: string, hasChildrenKey: boolean, childCount: number, target: string|null }}
85
+ */
86
+ export function specFacts({ raw = "", specPath = "", specId = null } = {}) {
87
+ const fm = extractFrontmatter(raw);
88
+ const id = fmScalar(fm, "id") ?? specId ?? "";
89
+ const type = (fmScalar(fm, "type") ?? "").toLowerCase();
90
+ const path = String(specPath).replace(/\\/g, "/");
91
+ const rawCount = fmListCount(fm, "children");
92
+ const target = fmScalar(fm, "target");
93
+ return {
94
+ id,
95
+ type,
96
+ path,
97
+ hasChildrenKey: rawCount !== null,
98
+ childCount: rawCount ?? 0,
99
+ target,
100
+ };
101
+ }
102
+
103
+ /** type: epic · id containing OR ending in `-EPIC` · a file under specs/epics/. */
104
+ export function isEpicSpec(facts) {
105
+ const f = facts ?? {};
106
+ return (
107
+ f.type === "epic" ||
108
+ /-EPIC(?:-|$)/i.test(f.id ?? "") ||
109
+ /(^|\/)specs\/epics\//i.test(f.path ?? "")
110
+ );
111
+ }
112
+
113
+ /** type: milestone · a `-MIL` id. */
114
+ export function isMilestoneSpec(facts) {
115
+ const f = facts ?? {};
116
+ return f.type === "milestone" || /-MIL(?:-|$)/i.test(f.id ?? "");
117
+ }
118
+
119
+ /** Declares `children:` but the list is EMPTY and it has no `target:` of its own. */
120
+ export function isParentShaped(facts) {
121
+ const f = facts ?? {};
122
+ return Boolean(f.hasChildrenKey) && (f.childCount ?? 0) === 0 && !f.target;
123
+ }
124
+
125
+ /** None of epic / milestone / parent-shaped — safe for the single-target build loop. */
126
+ export function isDispatchableSingleTarget(facts) {
127
+ return !isEpicSpec(facts) && !isMilestoneSpec(facts) && !isParentShaped(facts);
128
+ }
129
+
130
+ // ── Self-test (pure) ─────────────────────────────────────────────────────────────
131
+ function runSelfTest() {
132
+ let ok = true;
133
+ const check = (name, cond) => { console.log(` ${cond ? "✓" : "✗"} ${name}`); if (!cond) ok = false; };
134
+ console.log("== lib/epic-detect.mjs self-test ==\n");
135
+
136
+ const f = (raw, specPath = "", specId = null) => specFacts({ raw, specPath, specId });
137
+
138
+ const epicByType = f(`---\nid: HS-AAAA-EPIC\ntype: epic\nchildren: []\n---\n`);
139
+ const epicSuffix = f(`---\nid: HS-REM-EPIC\nstatus: open\n---\n`); // the regression: ends in -EPIC
140
+ const epicMid = f(`---\nid: HS-EPIC-legacy-1234\ntype: epic\n---\n`);
141
+ const epicByPath = f(`# bare\n`, "specs/epics/HS-Z.md");
142
+ const epicWithTarget = f(`---\nid: HS-BBBB-EPIC\ntype: epic\ntarget: a.ts\nchildren: []\n---\n`);
143
+ const milestone = f(`---\nid: HS-GGGG-MIL\ntype: milestone\n---\n`);
144
+ const parentShaped = f(`---\nid: HS-CCCC-TASK\ntype: task\nchildren: []\n---\n`);
145
+ const goodTask = f(`---\nid: HS-DDDD-TASK\ntype: task\ntarget: a.ts\n---\n`);
146
+ const taskWithChildrenAndTarget = f(`---\nid: HS-EEEE-TASK\ntype: task\ntarget: a.ts\nchildren:\n - "[[HS-FFFF-TASK-x]]"\n---\n`);
147
+ const bare = f(`# nothing\n`);
148
+
149
+ check("isEpicSpec: type: epic", isEpicSpec(epicByType));
150
+ check("isEpicSpec: id ENDING in -EPIC (the /-EPIC-/ regression)", isEpicSpec(epicSuffix));
151
+ check("isEpicSpec: id CONTAINING -EPIC-", isEpicSpec(epicMid));
152
+ check("isEpicSpec: file under specs/epics/", isEpicSpec(epicByPath));
153
+ check("isEpicSpec: epic even when it carries a target:", isEpicSpec(epicWithTarget));
154
+ check("isEpicSpec: a plain task is NOT an epic", !isEpicSpec(goodTask));
155
+
156
+ check("isMilestoneSpec: -MIL id", isMilestoneSpec(milestone));
157
+ check("isMilestoneSpec: a task is not a milestone", !isMilestoneSpec(goodTask));
158
+
159
+ check("isParentShaped: children: [] and no target:", isParentShaped(parentShaped));
160
+ check("isParentShaped: NOT triggered by a task with a target: and no children key", !isParentShaped(goodTask));
161
+ check("isParentShaped: NOT triggered by non-empty children (real children present)", !isParentShaped(taskWithChildrenAndTarget));
162
+ check("isParentShaped: NOT triggered when children: [] BUT a target: exists", !isParentShaped(epicWithTarget) || isEpicSpec(epicWithTarget));
163
+
164
+ check("isDispatchableSingleTarget: plain task with target passes", isDispatchableSingleTarget(goodTask));
165
+ check("isDispatchableSingleTarget: task with children + target passes", isDispatchableSingleTarget(taskWithChildrenAndTarget));
166
+ check("isDispatchableSingleTarget: epic refused", !isDispatchableSingleTarget(epicByType));
167
+ check("isDispatchableSingleTarget: milestone refused", !isDispatchableSingleTarget(milestone));
168
+ check("isDispatchableSingleTarget: parent-shaped refused", !isDispatchableSingleTarget(parentShaped));
169
+ check("isDispatchableSingleTarget: frontmatter-less fixture passes", isDispatchableSingleTarget(bare));
170
+
171
+ check("hasChildrenKey: absent children key → false", bare.hasChildrenKey === false);
172
+ check("hasChildrenKey: children: [] → true, count 0", parentShaped.hasChildrenKey === true && parentShaped.childCount === 0);
173
+
174
+ check("frontmatterChildIds: block wikilinks extracted", (() => {
175
+ const ids = frontmatterChildIds(`---\nid: HS-X-EPIC\nchildren:\n - "[[HS-A-TASK-a]]"\n - "[[HS-B-TASK-b]]"\n---\n`);
176
+ return ids.length === 2 && ids[0] === "HS-A-TASK-a" && ids[1] === "HS-B-TASK-b";
177
+ })());
178
+ check("frontmatterChildIds: children: [] → empty array", frontmatterChildIds(`---\nid: HS-X-EPIC\nchildren: []\n---\n`).length === 0);
179
+ check("frontmatterChildIds: accepts a bare frontmatter string too", (() => {
180
+ const ids = frontmatterChildIds(`id: HS-X-EPIC\nchildren:\n - [[HS-A-TASK-a]]\n`);
181
+ return ids.length === 1 && ids[0] === "HS-A-TASK-a";
182
+ })());
183
+
184
+ console.log(`\n${ok ? "✓ self-test PASS" : "✗ self-test FAIL"}`);
185
+ return ok ? 0 : 1;
186
+ }
187
+
188
+ if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
189
+ if (process.argv.includes("--self-test")) process.exit(runSelfTest());
190
+ }