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
@@ -24,12 +24,20 @@
24
24
  * 2. **Append, not overwrite.** The console PATCH route stores top-level keys
25
25
  * verbatim (last-write-wins), so multi-beat `user_quote` calls would clobber
26
26
  * each other. We read the prior buffer and append, sending the full array.
27
+ * The same rule applies to the five single-object timestamped sections
28
+ * (`details`/`example`/`userStories`/`doneWhen`/`jtbd`): a second call on
29
+ * the same field combines onto the prior call's text (blank-line joined)
30
+ * instead of replacing it — [[HS-OSCT-77P9-DIAG-heyspex-update-capture-overwrites-agent]].
31
+ * `coreIdea`/`summary` are deliberately NOT combined — a capture's core
32
+ * idea/summary is its current best statement, not an accumulating log.
27
33
  *
28
34
  * `confirmed: true` is set on every agent-authored section because the MCP write
29
35
  * is an explicit commit — the agent only calls this after the conversational
30
36
  * confirm in the /hs-capture four-beat protocol.
31
37
  */
32
38
 
39
+ import { buildSourcesPatches, normalizeCaptureSources } from './capture-sources.mjs';
40
+
33
41
  /** Keep only well-formed `{ at, text }` quote entries from a prior buffer. */
34
42
  function priorUserQuotes(currentBuffer) {
35
43
  const raw = currentBuffer?.userQuotes ?? currentBuffer?.user_quotes;
@@ -48,10 +56,49 @@ function priorChatTurns(currentBuffer) {
48
56
  );
49
57
  }
50
58
 
59
+ /**
60
+ * Read the prior confirmed text for a single-object timestamped section
61
+ * (`details`/`example`/`userStories`/`doneWhen`/`jtbd`) so a second
62
+ * `heyspex_update_capture` call can accumulate onto it instead of
63
+ * overwriting it ([[HS-OSCT-77P9-DIAG-heyspex-update-capture-overwrites-agent]]).
64
+ * The console PATCH route stores whatever this module sends verbatim
65
+ * (last-write-wins on the whole key), so accumulation has to happen here,
66
+ * on the read-before-write side — the same reason userQuotes/chatTurns
67
+ * already read the prior buffer before building their new array value.
68
+ */
69
+ function priorTimestampedText(currentBuffer, key) {
70
+ const raw = currentBuffer?.[key];
71
+ if (raw && typeof raw === 'object' && !Array.isArray(raw) && typeof raw.text === 'string') {
72
+ return raw.text.trim();
73
+ }
74
+ return '';
75
+ }
76
+
77
+ /**
78
+ * Combine a prior section's text with a new call's text so neither call's
79
+ * content is lost — joined with a blank line so the materialized section
80
+ * reads as separate paragraphs, not run together.
81
+ */
82
+ function combineTimestampedText(currentBuffer, key, nextText) {
83
+ const prior = priorTimestampedText(currentBuffer, key);
84
+ return prior ? `${prior}\n\n${nextText}` : nextText;
85
+ }
86
+
51
87
  function collapse(value) {
52
88
  return String(value).trim().replace(/\n+/g, ' ');
53
89
  }
54
90
 
91
+ /**
92
+ * A user's quote is stored STRICTLY VERBATIM ([[HS-TGHU-ST07-TASK-store-and-render-capture-quotes]],
93
+ * owner 2026-10-10: "always verbatim words in notes"): no trim, no newline
94
+ * flattening, no whitespace collapsing. Only CRLF/CR are normalized to LF.
95
+ * A whitespace-only quote is dropped (nothing was said).
96
+ */
97
+ function verbatimQuote(value) {
98
+ const raw = String(value).replace(/\r\n?/g, '\n');
99
+ return raw.trim() ? raw : '';
100
+ }
101
+
55
102
  /**
56
103
  * @param {object} args - { user_quote, agent_note, core_idea, summary (alias: concept), agent_expansion, example, user_stories, kind, done_when, jtbd }
57
104
  * @param {object} currentBuffer - the session's existing content_buffer (may be {})
@@ -61,9 +108,17 @@ function collapse(value) {
61
108
  export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
62
109
  const patches = [];
63
110
 
64
- if (args.user_quote !== undefined && collapse(args.user_quote)) {
65
- const next = [...priorUserQuotes(currentBuffer), { at: iso, text: collapse(args.user_quote) }];
66
- patches.push({ path: 'userQuotes', value: next });
111
+ if (args.user_quote !== undefined && verbatimQuote(args.user_quote)) {
112
+ const entry = { at: iso, text: verbatimQuote(args.user_quote) };
113
+ // op 'append' + items: a current console appends under its row lock (no
114
+ // stale read-modify-write); an older console ignores both and stores the
115
+ // full prior-plus-new value.
116
+ patches.push({
117
+ path: 'userQuotes',
118
+ value: [...priorUserQuotes(currentBuffer), entry],
119
+ op: 'append',
120
+ items: [entry],
121
+ });
67
122
  }
68
123
 
69
124
  if (args.agent_note !== undefined && collapse(args.agent_note)) {
@@ -85,13 +140,17 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
85
140
  }
86
141
 
87
142
  // agent_expansion is the `## Expanded Concept` deep-dive section → buffer.details.
143
+ // Accumulates onto the prior call's text (HS-OSCT) instead of replacing it.
88
144
  if (args.agent_expansion !== undefined && String(args.agent_expansion).trim()) {
89
- patches.push({ path: 'details', value: { at: iso, text: String(args.agent_expansion).trim(), confirmed: true } });
145
+ const text = combineTimestampedText(currentBuffer, 'details', String(args.agent_expansion).trim());
146
+ patches.push({ path: 'details', value: { at: iso, text, confirmed: true } });
90
147
  }
91
148
 
92
149
  // example is the `## Example` section (real or AI-illustrative) → buffer.example.
150
+ // Accumulates onto the prior call's text (HS-OSCT) instead of replacing it.
93
151
  if (args.example !== undefined && String(args.example).trim()) {
94
- patches.push({ path: 'example', value: { at: iso, text: String(args.example).trim(), confirmed: true } });
152
+ const text = combineTimestampedText(currentBuffer, 'example', String(args.example).trim());
153
+ patches.push({ path: 'example', value: { at: iso, text, confirmed: true } });
95
154
  }
96
155
 
97
156
  // user_stories is the `## User Stories` section → buffer.userStories. Root cause
@@ -100,9 +159,10 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
100
159
  // console content_buffer (and therefore the materialized file) never saw it —
101
160
  // silent data loss, unlike example which has always had this push.
102
161
  if (args.user_stories !== undefined && String(args.user_stories).trim()) {
162
+ const text = combineTimestampedText(currentBuffer, 'userStories', String(args.user_stories).trim());
103
163
  patches.push({
104
164
  path: 'userStories',
105
- value: { at: iso, text: String(args.user_stories).trim(), confirmed: true },
165
+ value: { at: iso, text, confirmed: true },
106
166
  });
107
167
  }
108
168
 
@@ -122,9 +182,10 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
122
182
  // {at,text,confirmed} shape as the other agent-authored sections (example,
123
183
  // user_stories, details) — sibling half of the HS-NR43 fix above.
124
184
  if (args.done_when !== undefined && String(args.done_when).trim()) {
185
+ const text = combineTimestampedText(currentBuffer, 'doneWhen', String(args.done_when).trim());
125
186
  patches.push({
126
187
  path: 'doneWhen',
127
- value: { at: iso, text: String(args.done_when).trim(), confirmed: true },
188
+ value: { at: iso, text, confirmed: true },
128
189
  });
129
190
  }
130
191
 
@@ -137,11 +198,19 @@ export function buildCaptureBufferPatches(args = {}, currentBuffer = {}, iso) {
137
198
  // is refused by name at the handler's first hop, never silently dropped here
138
199
  // — [[additionalproperties-false-in-an-mcp]].
139
200
  if (args.jtbd !== undefined && String(args.jtbd).trim()) {
201
+ const text = combineTimestampedText(currentBuffer, 'jtbd', String(args.jtbd).trim());
140
202
  patches.push({
141
203
  path: 'jtbd',
142
- value: { at: iso, text: String(args.jtbd).trim(), confirmed: true },
204
+ value: { at: iso, text, confirmed: true },
143
205
  });
144
206
  }
145
207
 
208
+ // sources: links + original files saved with the capture (HS-9K12). The
209
+ // handler pre-validates via normalizeCaptureSources; an invalid bag emits nothing.
210
+ if (args.sources !== undefined) {
211
+ const normalized = normalizeCaptureSources(args.sources, iso);
212
+ if (normalized.ok) patches.push(...buildSourcesPatches(normalized, currentBuffer));
213
+ }
214
+
146
215
  return patches;
147
216
  }
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { existsSync, watch as fsWatch } from 'node:fs';
14
14
  import { basename as pathBasename, dirname, join } from 'node:path';
15
- import { loadCaptureFocus, captureFocusPath } from './capture-focus.mjs';
15
+ import { loadCaptureFocusOffline, captureFocusPath } from './capture-focus.mjs';
16
16
  import { parseSpecId } from './spec-id-lib.mjs';
17
17
 
18
18
  // Exponential reconnect backoff (ms). Capped at 30s; jittered ±50% on use.
@@ -205,6 +205,54 @@ export function createCaptureChannelWatcher({
205
205
  return body?.session ?? null;
206
206
  }
207
207
 
208
+ /**
209
+ * Resolve the LIVE capture-on-deck pin, the same way `readCaptureFocusVendor`
210
+ * does elsewhere in the vendor MCP (`capture-tools.mjs`): GET the Neon
211
+ * control-plane pin + selection, falling back to the offline file
212
+ * (`loadCaptureFocusOffline`) only when the console can't be reached, and only
213
+ * when that file pin is not itself stale. Kept local
214
+ * (own `fetchImpl`/`getConsoleUrl`, mirroring `fetchSessionSnapshot` above)
215
+ * rather than importing capture-tools.mjs, so this watcher's boot-time pin
216
+ * read stays on the SAME injectable transport its tests already stub.
217
+ *
218
+ * Fixes [[HS-O4U6-2ZRO-DIAG-the-offline-capture-pin-file-is-months]]: on a
219
+ * console-backed vault the pin lives in Neon, not the offline file — every
220
+ * MCP server start used to `loadCaptureFocus(vaultRoot)` (the file) here,
221
+ * so on this repo it printed "pin → HS-CAP-VDPB is already materialized"
222
+ * for a capture promoted two months earlier, on every single boot.
223
+ */
224
+ async function fetchLiveCaptureFocus() {
225
+ try {
226
+ const consoleUrl = getConsoleUrl().replace(/\/$/, '');
227
+ const [pinRes, selRes] = await Promise.all([
228
+ fetchImpl(`${consoleUrl}/api/control/capture-focus-pin`, { headers: { accept: 'application/json' } }),
229
+ fetchImpl(`${consoleUrl}/api/control/capture-focus-selection`, { headers: { accept: 'application/json' } }),
230
+ ]);
231
+ if (pinRes.ok) {
232
+ const pinBody = await pinRes.json();
233
+ const pin = pinBody?.pin ?? null;
234
+ if (!pin) return null; // Neon authoritative — no pin means nothing on deck.
235
+ const selBody = selRes.ok ? await selRes.json() : null;
236
+ const sel = selBody?.selection ?? null;
237
+ const base = (p) => String(p || '').replace(/^.*[/\\]/, '');
238
+ const selMatches = sel?.specPath ? base(sel.specPath).startsWith(pin.specId) : false;
239
+ const focus = {
240
+ spec_path: selMatches ? sel.specPath : `specs/captures/${pin.specId}.md`,
241
+ pinned_at: pin.pinnedAt,
242
+ };
243
+ if (pin.expiresAt) focus.expires = pin.expiresAt;
244
+ focus.kind = 'idea';
245
+ return focus;
246
+ }
247
+ } catch {
248
+ // Fall through to the offline file seam.
249
+ }
250
+ // Offline seam — refuse a rotted file pin instead of starting a watcher on it.
251
+ // This watcher spent two months announcing a June capture read off this file
252
+ // ([[HS-O4U6-2ZRO-DIAG-the-offline-capture-pin-file-is-months]]).
253
+ return loadCaptureFocusOffline(vaultRoot).focus;
254
+ }
255
+
208
256
  async function openAndConsumeStream(sessionId, controller) {
209
257
  const consoleUrl = getConsoleUrl();
210
258
  const url = `${consoleUrl.replace(/\/$/, '')}/api/capture/sessions/${encodeURIComponent(sessionId)}/events`;
@@ -447,13 +495,14 @@ export function createCaptureChannelWatcher({
447
495
  if (pinDebounceTimer) clearTimeout(pinDebounceTimer);
448
496
  pinDebounceTimer = setTimeout(() => {
449
497
  pinDebounceTimer = null;
450
- reevaluatePin();
498
+ reevaluatePin().catch((err) => log(`reevaluatePin failed: ${err?.message ?? err}`));
451
499
  }, PIN_WATCH_DEBOUNCE_MS);
452
500
  }
453
501
 
454
- function reevaluatePin() {
502
+ async function reevaluatePin() {
455
503
  if (stopped) return;
456
- const focus = loadCaptureFocus(vaultRoot);
504
+ const focus = await fetchLiveCaptureFocus();
505
+ if (stopped) return; // may have been stopped while the pin fetch was in flight
457
506
  const nextSessionId = focus ? deriveSessionIdFromFocus(focus) : null;
458
507
 
459
508
  if (nextSessionId === activeSessionId) {
@@ -533,7 +582,8 @@ export function createCaptureChannelWatcher({
533
582
  return;
534
583
  }
535
584
  startPinWatcher();
536
- reevaluatePin(); // initial open if pin already exists
585
+ // Initial open if pin already exists — fire-and-forget (async network read).
586
+ reevaluatePin().catch((err) => log(`reevaluatePin failed: ${err?.message ?? err}`));
537
587
  }
538
588
 
539
589
  function stop() {
@@ -19,6 +19,17 @@ import {
19
19
  removeOutboxEntry,
20
20
  stashCaptureArgs,
21
21
  } from "./capture-outbox.mjs";
22
+ import {
23
+ CONSOLE_NOT_SIGNED_IN,
24
+ consoleAuthErrorMessage,
25
+ consoleUnauthorizedMessage,
26
+ } from "./console-fetch.mjs";
27
+ // The runnable form of a HeySpex command on THIS machine — `hs login` when the
28
+ // bin is on PATH, else an absolute-path `node "<install>/bin/hs.mjs" login`.
29
+ // Never an `npx` form: npx resolves from the READER's working directory, and
30
+ // outside a checkout `npx hs` installs the unrelated public package hs@2.0.1
31
+ // ([[HS-I6HT-EKYU-DIAG-every-message-tells-the-user-to-run-hs]]).
32
+ import { hsCommand } from "../../scripts/lib/hs-command.mjs";
22
33
 
23
34
  /**
24
35
  * Error taxonomy for capture-session console calls ([[HS-K27H-DIAG-0G86]]).
@@ -39,14 +50,28 @@ import {
39
50
  * message verbatim. (Entries ALREADY in the outbox from a genuine
40
51
  * transport failure still pause the drain on 5xx and retry later — the
41
52
  * ban is on adding new entries for an answered request.)
53
+ * - `CONSOLE_LOCAL_URL_IN_CLOUD_MODE` / `CONSOLE_NOT_SIGNED_IN`
54
+ * ([[HS-9R6S-N8VP-DIAG-capture-writes-report-the-console-as]]): the call
55
+ * never had a chance — cloud mode pointed at a local console, no bearer
56
+ * token on this machine, or an answered HTTP 401. NEVER stash: nothing
57
+ * becomes reachable until `hs login` runs, so a "your words are buffered
58
+ * and will sync" promise would be false AND would hide the real cause.
59
+ * console-fetch stamps `authProblem` on the transport result; the 401 form
60
+ * stays inside the 4xx "the server ANSWERED, never stash" family and only
61
+ * swaps the generic CAPTURE_CONFLICT code for an actionable one.
42
62
  */
43
63
  export const CAPTURE_ERROR_UNREACHABLE = "CONSOLE_UNREACHABLE";
44
64
  export const CAPTURE_ERROR_CONFLICT = "CAPTURE_CONFLICT";
45
65
  export const CAPTURE_ERROR_FAILED = "PATCH_FAILED";
66
+ export const CAPTURE_ERROR_NOT_SIGNED_IN = CONSOLE_NOT_SIGNED_IN;
46
67
 
47
68
  /** Classify a `consoleFetch` result by HTTP status range. */
48
69
  export function classifyConsoleFetchError(result) {
70
+ // Sign-in problems are stamped by console-fetch on the status-0 result and
71
+ // outrank the generic transport class — they never buffer.
72
+ if (result.authProblem) return result.authProblem;
49
73
  if (result.status === 0) return CAPTURE_ERROR_UNREACHABLE;
74
+ if (result.status === 401) return CONSOLE_NOT_SIGNED_IN;
50
75
  if (result.status >= 400 && result.status < 500)
51
76
  return CAPTURE_ERROR_CONFLICT;
52
77
  return CAPTURE_ERROR_FAILED;
@@ -66,7 +91,7 @@ export function extractConsoleMessage(result) {
66
91
  * - list/create helper failures: `{ ok:false, error, result }` (status on `result`)
67
92
  *
68
93
  * @returns {{
69
- * kind: 'transport'|'client'|'server',
94
+ * kind: 'transport'|'auth'|'client'|'server',
70
95
  * shouldStash: boolean,
71
96
  * error: string,
72
97
  * http_status: number,
@@ -86,6 +111,38 @@ export function describeCaptureWriteFailure(failure) {
86
111
  const serverCode = typeof result?.body?.error === "string" ? result.body.error : null;
87
112
  const serverMessage = typeof result?.body?.message === "string" ? result.body.message : null;
88
113
 
114
+ // Sign-in problems first ([[HS-9R6S-N8VP-DIAG]]). A refused connection to the
115
+ // WRONG console is not an outage, and a 401 is not a capture conflict —
116
+ // both are "run hs login", and neither may touch the offline buffer.
117
+ const authProblem =
118
+ typeof result?.authProblem === "string" ? result.authProblem : null;
119
+ if (status === 0 && authProblem) {
120
+ return {
121
+ kind: "auth",
122
+ shouldStash: false,
123
+ error: authProblem,
124
+ http_status: 0,
125
+ server_error_code: null,
126
+ server_message: null,
127
+ message:
128
+ result?.authMessage
129
+ || consoleAuthErrorMessage(authProblem, result?.url)
130
+ || null,
131
+ };
132
+ }
133
+ if (status === 401) {
134
+ const verbatim401 = serverMessage ? ` The server said: ${serverMessage}` : "";
135
+ return {
136
+ kind: "auth",
137
+ shouldStash: false,
138
+ error: CONSOLE_NOT_SIGNED_IN,
139
+ http_status: 401,
140
+ server_error_code: serverCode,
141
+ server_message: serverMessage,
142
+ message: `${consoleUnauthorizedMessage(result?.url)}${verbatim401}`,
143
+ };
144
+ }
145
+
89
146
  // status 0 ⇔ genuine transport failure (ECONNREFUSED / DNS / timeout /
90
147
  // abort — console-fetch stamps status 0 on every non-response). Anything
91
148
  // else is an HTTP response the server actually returned.
@@ -116,9 +173,14 @@ export function describeCaptureWriteFailure(failure) {
116
173
  `The console answered — it is not down. This write was NOT saved and will NOT retry automatically; fix the request and send it again.`,
117
174
  };
118
175
  }
176
+ // [[HS-K27H]] decided a 5xx was NOT stashed because the OLD code reported
177
+ // server errors as "can't reach the server". The sin was that false
178
+ // connectivity claim, not the buffering: degrade-don't-die buffers the user's
179
+ // words on a 5xx too, but the message says truthfully that the console
180
+ // ANSWERED with an error (see respondToCaptureWriteFailure).
119
181
  return {
120
182
  kind: "server",
121
- shouldStash: false,
183
+ shouldStash: true,
122
184
  error: CAPTURE_ERROR_FAILED,
123
185
  http_status: status,
124
186
  server_error_code: serverCode,
@@ -141,9 +203,25 @@ export function describeCaptureWriteFailure(failure) {
141
203
  * (AC4) — the drain path replays every OUTBOX_KINDS field in canonical
142
204
  * structured shape.
143
205
  * - 4xx → `status: "refused"` + the server's error/message verbatim (AC2/AC5).
144
- * - 5xx → `status: "failed"` + the server's message verbatim (AC3/AC5).
206
+ * - 5xx → stash too, `status: "failed_buffered"`, `buffered: true`, truthful "console answered <status>" message (not "unreachable"); `status: "failed"` only when there was nothing to stash.
207
+ * - sign-in (`authProblem` on a status-0 result, or an answered 401) →
208
+ * `status: "blocked"`, `buffered: false`, `action` = the sign-in command in
209
+ * the form this machine can run (`hs login`, or `node "<abs>/bin/hs.mjs" login`),
210
+ * and a message
211
+ * that says plainly the words were NOT saved ([[HS-9R6S-N8VP-DIAG]]).
145
212
  */
146
213
  export function respondToCaptureWriteFailure(vaultRoot, args, failure) {
214
+ const payload = respondToCaptureWriteFailureInner(vaultRoot, args, failure);
215
+ // sources (original files / links) are NOT stashed offline: say so honestly
216
+ // instead of letting the "your words are saved" message imply they were.
217
+ if (Array.isArray(args?.sources) && args.sources.length > 0) {
218
+ payload.sources_not_saved = true;
219
+ payload.message = `${payload.message} NOTE: the sources you attached were NOT saved offline. Re-send them with heyspex_update_capture once the console is reachable.`;
220
+ }
221
+ return payload;
222
+ }
223
+
224
+ function respondToCaptureWriteFailureInner(vaultRoot, args, failure) {
147
225
  const d = describeCaptureWriteFailure(failure);
148
226
  if (d.kind === "transport") {
149
227
  const stashed = stashCaptureArgs(vaultRoot, args || {});
@@ -161,6 +239,42 @@ export function respondToCaptureWriteFailure(vaultRoot, args, failure) {
161
239
  pending_sync: pending,
162
240
  };
163
241
  }
242
+ if (d.kind === "auth") {
243
+ // NOT buffered, and the payload must not imply otherwise ([[HS-9R6S-N8VP-DIAG]]).
244
+ return {
245
+ ok: false,
246
+ status: "blocked",
247
+ error: d.error,
248
+ http_status: d.http_status,
249
+ server_error: d.server_error_code,
250
+ console_error: d.server_message ?? d.server_error_code,
251
+ buffered: false,
252
+ stashed: [],
253
+ // The form that actually runs on this machine, never the bare `hs`
254
+ // spelling ([[HS-I6HT-EKYU-DIAG-every-message-tells-the-user-to-run-hs]]).
255
+ action: hsCommand("login"),
256
+ message: `This capture was not filed, and your words were not buffered. ${d.message}`,
257
+ };
258
+ }
259
+ if (d.kind === "server") {
260
+ const stashed = stashCaptureArgs(vaultRoot, args || {});
261
+ if (stashed.length > 0) {
262
+ return {
263
+ ok: false,
264
+ status: "failed_buffered",
265
+ error: d.error,
266
+ http_status: d.http_status,
267
+ server_error: d.server_error_code,
268
+ console_error: d.server_message ?? d.server_error_code,
269
+ buffered: true,
270
+ source: "offline-buffer",
271
+ message:
272
+ `The console answered ${d.http_status} applying the write; your words are buffered locally in .heyspex/capture-outbox and will be replayed on the next successful capture call.`,
273
+ stashed: stashed.map((s) => ({ kind: s.kind })),
274
+ pending_sync: outboxCount(vaultRoot),
275
+ };
276
+ }
277
+ }
164
278
  return {
165
279
  ok: false,
166
280
  status: d.kind === "client" ? "refused" : "failed",