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
@@ -31,9 +31,11 @@ import {
31
31
  renameSync,
32
32
  rmSync,
33
33
  } from 'node:fs';
34
- import { basename, join } from 'node:path';
34
+ import { basename, join, isAbsolute, resolve, sep } from 'node:path';
35
35
 
36
36
  import { buildCaptureBufferPatches } from './capture-buffer-patches.mjs';
37
+ import { assertPathUnderRoot } from '../../scripts/lib/shared-path-helpers.mjs';
38
+
37
39
 
38
40
  /**
39
41
  * Capture args that carry content worth preserving, in chronological write order.
@@ -87,12 +89,15 @@ export function stashCaptureArgs(vaultRoot, args = {}) {
87
89
  for (const kind of OUTBOX_KINDS) {
88
90
  const raw = args[kind];
89
91
  if (raw !== undefined && String(raw).trim()) {
90
- entries.push({ kind, text: String(raw).trim() });
92
+ // user_quote is stashed verbatim (no trim) so an offline replay keeps the
93
+ // exact words (HS-TGHU).
94
+ entries.push({ kind, text: kind === 'user_quote' ? String(raw) : String(raw).trim() });
91
95
  }
92
96
  }
93
97
  if (entries.length === 0) return [];
94
98
 
95
99
  const dir = captureOutboxDir(vaultRoot);
100
+ assertPathUnderRoot(vaultRoot, dir, 'capture-outbox write');
96
101
  mkdirSync(dir, { recursive: true });
97
102
  const stampBase = new Date().toISOString().replace(/[:.]/g, '-');
98
103
  const written = [];
@@ -119,7 +124,10 @@ export function listOutbox(vaultRoot) {
119
124
  const kind = m ? m[1] : 'user_quote';
120
125
  let text = '';
121
126
  try {
122
- text = readFileSync(join(dir, f), 'utf8').replace(/\n+$/, '');
127
+ // stash writes text + one newline: strip exactly that one so a verbatim
128
+ // quote's own trailing whitespace survives the round trip.
129
+ text = readFileSync(join(dir, f), 'utf8').replace(/\n$/, '');
130
+ if (kind !== 'user_quote') text = text.replace(/\n+$/, '');
123
131
  } catch {
124
132
  /* skip unreadable */
125
133
  }
@@ -156,6 +164,7 @@ export function removeOutboxEntry(path) {
156
164
  export function quarantineOutboxEntry(vaultRoot, entryPath, metadata = {}) {
157
165
  try {
158
166
  const qdir = captureOutboxQuarantineDir(vaultRoot);
167
+ assertPathUnderRoot(vaultRoot, qdir, 'capture-outbox write');
159
168
  mkdirSync(qdir, { recursive: true });
160
169
  const name = basename(entryPath);
161
170
  const dest = join(qdir, name);
@@ -231,8 +240,9 @@ export function quarantineCount(vaultRoot) {
231
240
  * @returns {{path: string, value: unknown} | null}
232
241
  */
233
242
  export function outboxEntryToPatch(entry, iso, currentBuffer = {}) {
234
- const text = String(entry.text ?? '').trim();
235
- if (!text) return null;
243
+ const text =
244
+ entry.kind === 'user_quote' ? String(entry.text ?? '') : String(entry.text ?? '').trim();
245
+ if (!text.trim()) return null;
236
246
  if (!OUTBOX_KINDS.includes(entry.kind)) return null;
237
247
  const patches = buildCaptureBufferPatches({ [entry.kind]: text }, currentBuffer, iso);
238
248
  return patches[0] ?? null;
@@ -0,0 +1,195 @@
1
+ /**
2
+ * `sources` argument of `heyspex_update_capture`: links, original text
3
+ * documents and binary files saved WITH a capture
4
+ * ([[HS-9K12-ST07-TASK-heyspex-update-capture-sources-argument]], epic
5
+ * [[HS-WF23-ST07-EPIC-verbatim-words-and-source-files]]; owner 2026-10-10:
6
+ * "files/original docs etc saved as source").
7
+ *
8
+ * Entry forms (exactly one content key per entry):
9
+ * { url, title? } -> link source (`## Source Material`, grain link)
10
+ * { name, text, mime?, origin? } -> original TEXT document, stored byte-for-byte
11
+ * { name, content_base64, mime?, origin? } -> binary file
12
+ * { path, name?, mime? } -> the MCP reads the file at `path` itself (so the
13
+ * agent never has to pass the content through its context)
14
+ *
15
+ * Caps mirror the console (`spex-console/src/lib/capture-source-files.ts`),
16
+ * which re-validates server-side: 256 KiB per file, 1 MiB total, 20 files.
17
+ * Oversize / malformed entries are REFUSED by name; nothing is truncated.
18
+ */
19
+ import { createHash } from 'node:crypto';
20
+ import { readFileSync, statSync } from 'node:fs';
21
+ import { basename } from 'node:path';
22
+
23
+ export const SOURCE_FILE_MAX_BYTES = 256 * 1024;
24
+ export const SOURCE_FILES_MAX_TOTAL_BYTES = 1024 * 1024;
25
+ export const SOURCE_FILES_MAX_COUNT = 20;
26
+ const BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
27
+
28
+ /** Reduce a name to one safe path segment (mirrors the console). */
29
+ export function sanitizeSourceFileName(raw) {
30
+ const segs = String(raw).split(/[\\/]/);
31
+ // eslint-disable-next-line no-control-regex
32
+ return (segs[segs.length - 1] ?? '').replace(/[\u0000-\u001f\u007f]/g, '').trim().slice(0, 200);
33
+ }
34
+
35
+ function defaultMime(name, encoding) {
36
+ if (encoding === 'base64') return 'application/octet-stream';
37
+ return /\.(md|markdown)$/i.test(name) ? 'text/markdown' : 'text/plain';
38
+ }
39
+
40
+ function fileFromBuffer(buf, { name, mime, origin, iso, encoding, content }) {
41
+ if (buf.length === 0) return { error: `source "${name}" is empty` };
42
+ if (buf.length > SOURCE_FILE_MAX_BYTES) {
43
+ return {
44
+ error: `source "${name}" is ${buf.length} bytes; the inline source-file cap is ${SOURCE_FILE_MAX_BYTES} bytes. Attach a smaller file or link it by url.`,
45
+ };
46
+ }
47
+ return {
48
+ file: {
49
+ name,
50
+ mime: mime || defaultMime(name, encoding),
51
+ bytes: buf.length,
52
+ sha256: createHash('sha256').update(buf).digest('hex'),
53
+ at: iso,
54
+ encoding,
55
+ content,
56
+ ...(origin ? { origin } : {}),
57
+ },
58
+ };
59
+ }
60
+
61
+ /** Decode bytes as UTF-8 text only when that is lossless; otherwise treat as binary. */
62
+ function bufferToStored(buf) {
63
+ if (!buf.includes(0)) {
64
+ const text = buf.toString('utf8');
65
+ if (Buffer.from(text, 'utf8').equals(buf)) return { encoding: 'utf8', content: text };
66
+ }
67
+ return { encoding: 'base64', content: buf.toString('base64') };
68
+ }
69
+
70
+ /**
71
+ * Validate + normalize the `sources` argument.
72
+ * @returns {{ ok: true, links: object[], files: object[] } | { ok: false, error: object }}
73
+ */
74
+ export function normalizeCaptureSources(sources, iso, deps = {}) {
75
+ const read = deps.readFile ?? ((p) => readFileSync(p));
76
+ const size = deps.fileSize ?? ((p) => statSync(p).size);
77
+ const refuse = (message, index) => ({
78
+ ok: false,
79
+ error: {
80
+ error: 'INVALID_SOURCES',
81
+ message: `${message} NOTHING WAS WRITTEN.`,
82
+ field: `sources[${index}]`,
83
+ },
84
+ });
85
+ if (!Array.isArray(sources)) return refuse('sources must be an array.', 0);
86
+ if (sources.length > SOURCE_FILES_MAX_COUNT) {
87
+ return refuse(`sources cannot exceed ${SOURCE_FILES_MAX_COUNT} entries.`, 0);
88
+ }
89
+ const links = [];
90
+ const files = [];
91
+ let total = 0;
92
+ for (let i = 0; i < sources.length; i += 1) {
93
+ const s = sources[i];
94
+ if (!s || typeof s !== 'object') return refuse(`sources[${i}] must be an object.`, i);
95
+ if (typeof s.url === 'string' && s.url.trim()) {
96
+ if (!/^https?:\/\//i.test(s.url.trim())) {
97
+ return refuse(`sources[${i}].url must be an http(s) URL.`, i);
98
+ }
99
+ links.push({
100
+ url: s.url.trim(),
101
+ ...(typeof s.title === 'string' && s.title.trim() ? { title: s.title.trim() } : {}),
102
+ added_at: iso,
103
+ grain: 'link',
104
+ });
105
+ continue;
106
+ }
107
+ let name = typeof s.name === 'string' ? sanitizeSourceFileName(s.name) : '';
108
+ const mime = typeof s.mime === 'string' && s.mime.trim() ? s.mime.trim().slice(0, 100) : '';
109
+ const origin =
110
+ typeof s.origin === 'string' && s.origin.trim() ? s.origin.trim().slice(0, 500) : '';
111
+ let r;
112
+ if (typeof s.path === 'string' && s.path.trim()) {
113
+ const p = s.path.trim();
114
+ let sz;
115
+ try {
116
+ sz = size(p);
117
+ } catch (e) {
118
+ return refuse(`sources[${i}].path could not be read (${e.code || e.message}).`, i);
119
+ }
120
+ if (sz > SOURCE_FILE_MAX_BYTES) {
121
+ return refuse(
122
+ `sources[${i}] (${basename(p)}) is ${sz} bytes; the inline source-file cap is ${SOURCE_FILE_MAX_BYTES} bytes.`,
123
+ i,
124
+ );
125
+ }
126
+ let buf;
127
+ try {
128
+ buf = read(p);
129
+ } catch (e) {
130
+ return refuse(`sources[${i}].path could not be read (${e.code || e.message}).`, i);
131
+ }
132
+ name = name || sanitizeSourceFileName(basename(p));
133
+ if (!name) return refuse(`sources[${i}] needs a name.`, i);
134
+ r = fileFromBuffer(buf, { name, mime, origin: origin || p, iso, ...bufferToStored(buf) });
135
+ } else if (typeof s.text === 'string') {
136
+ if (!name) return refuse(`sources[${i}].name is required for a text document.`, i);
137
+ r = fileFromBuffer(Buffer.from(s.text, 'utf8'), {
138
+ name,
139
+ mime,
140
+ origin,
141
+ iso,
142
+ encoding: 'utf8',
143
+ content: s.text,
144
+ });
145
+ } else if (typeof s.content_base64 === 'string') {
146
+ if (!name) return refuse(`sources[${i}].name is required for a binary file.`, i);
147
+ const b64 = s.content_base64.replace(/\s+/g, '');
148
+ if (b64.length % 4 !== 0 || !BASE64_RE.test(b64)) {
149
+ return refuse(`sources[${i}].content_base64 is not valid base64.`, i);
150
+ }
151
+ r = fileFromBuffer(Buffer.from(b64, 'base64'), {
152
+ name,
153
+ mime,
154
+ origin,
155
+ iso,
156
+ encoding: 'base64',
157
+ content: b64,
158
+ });
159
+ } else {
160
+ return refuse(`sources[${i}] needs one of url, path, text (+name) or content_base64 (+name).`, i);
161
+ }
162
+ if (r.error) return refuse(r.error, i);
163
+ total += r.file.bytes;
164
+ files.push(r.file);
165
+ }
166
+ if (total > SOURCE_FILES_MAX_TOTAL_BYTES) {
167
+ return refuse(
168
+ `sources total ${total} bytes exceeds the ${SOURCE_FILES_MAX_TOTAL_BYTES} byte per-capture cap.`,
169
+ 0,
170
+ );
171
+ }
172
+ return { ok: true, links, files };
173
+ }
174
+
175
+ /** Patches for validated sources: prior-plus-new full array (old consoles) + append hint (new consoles). */
176
+ export function buildSourcesPatches(normalized, currentBuffer = {}) {
177
+ const patches = [];
178
+ if (normalized.files.length > 0) {
179
+ const prior = Array.isArray(currentBuffer?.sourceFiles) ? currentBuffer.sourceFiles : [];
180
+ const fresh = normalized.files.filter(
181
+ (f) => !prior.some((p) => p && p.name === f.name && p.sha256 === f.sha256),
182
+ );
183
+ if (fresh.length > 0) {
184
+ patches.push({ path: 'sourceFiles', value: [...prior, ...fresh], op: 'append', items: fresh });
185
+ }
186
+ }
187
+ if (normalized.links.length > 0) {
188
+ const prior = Array.isArray(currentBuffer?.sources) ? currentBuffer.sources : [];
189
+ const fresh = normalized.links.filter((l) => !prior.some((p) => p && p.url === l.url));
190
+ if (fresh.length > 0) {
191
+ patches.push({ path: 'sources', value: [...prior, ...fresh], op: 'append', items: fresh });
192
+ }
193
+ }
194
+ return patches;
195
+ }