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,564 @@
1
+ import { createRequire as __hsCreateRequire } from 'node:module';
2
+ const require = __hsCreateRequire(import.meta.url);
3
+
4
+ // spex-console/bin/hs-dev.ts
5
+ import process3 from "node:process";
6
+
7
+ // spex-console/src/lib/dev-mode.ts
8
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
9
+ import { homedir as homedir2 } from "node:os";
10
+ import { join as join2 } from "node:path";
11
+
12
+ // vendor/heyspex-mcp/credentials.mjs
13
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
14
+ import os, { homedir } from "node:os";
15
+ import { dirname, join, resolve } from "node:path";
16
+ function underTestRunner(env = process.env) {
17
+ return Boolean(env.VITEST) || Boolean(env.NODE_TEST_CONTEXT);
18
+ }
19
+ function samePathLoose(a, b) {
20
+ const norm = (p) => {
21
+ const r = resolve(p).replace(/[\\/]+$/, "");
22
+ return process.platform === "win32" ? r.toLowerCase() : r;
23
+ };
24
+ return norm(a) === norm(b);
25
+ }
26
+ function realUserHome() {
27
+ try {
28
+ const h = os.userInfo().homedir;
29
+ if (typeof h === "string" && h) return h;
30
+ } catch {
31
+ }
32
+ return null;
33
+ }
34
+ function assertNotRealHomeUnderTest(filePath) {
35
+ if (!underTestRunner()) return;
36
+ const real = realUserHome();
37
+ if (real && samePathLoose(dirname(filePath), join(real, ".heyspex"))) {
38
+ throw new Error(
39
+ `credentials: refusing to write ${filePath} under a test runner \u2014 that is the real ~/.heyspex directory. Point the test at a temp home.`
40
+ );
41
+ }
42
+ }
43
+
44
+ // spex-console/src/lib/dev-mode.ts
45
+ var CLOUD_CONSOLE_URL = "https://app.heyspex.com";
46
+ var LOCAL_CONSOLE_URL = "http://localhost:3000";
47
+ function normalizeMode(raw) {
48
+ const v = String(raw ?? "").trim().toLowerCase();
49
+ if (v === "prod" || v === "production") return { mode: "prod", reliance: "cloud" };
50
+ if (v === "dev") return { mode: "dev", reliance: null };
51
+ if (v === "local") return { mode: "dev", reliance: "local" };
52
+ if (v === "cloud") return { mode: "dev", reliance: "cloud" };
53
+ return null;
54
+ }
55
+ function normalizeReliance(raw) {
56
+ const v = String(raw ?? "").trim().toLowerCase();
57
+ return v === "local" || v === "cloud" ? v : null;
58
+ }
59
+ function normalizeDevModeInput(mode, reliance) {
60
+ const parsed = normalizeMode(mode);
61
+ if (!parsed) return null;
62
+ return {
63
+ mode: parsed.mode,
64
+ reliance: parsed.mode === "prod" ? "cloud" : parsed.reliance ?? normalizeReliance(reliance) ?? "local"
65
+ };
66
+ }
67
+ function deriveSemantics(mode, reliance) {
68
+ const effectiveReliance = mode === "prod" ? "cloud" : reliance ?? "local";
69
+ const local = mode === "dev" && effectiveReliance === "local";
70
+ return {
71
+ reliance: effectiveReliance,
72
+ consoleUrl: local ? LOCAL_CONSOLE_URL : CLOUD_CONSOLE_URL,
73
+ readTier: local ? "disk" : "neon",
74
+ vaultWriteMode: local ? "local" : "cloud"
75
+ };
76
+ }
77
+ function devModeFilePath(homeDir = homedir2()) {
78
+ return join2(homeDir, ".heyspex", "dev-mode.json");
79
+ }
80
+ var defaultReadFile = (p) => readFileSync2(p, "utf8");
81
+ function readLocalDevModeRecord({
82
+ homeDir = homedir2(),
83
+ readFile = defaultReadFile
84
+ } = {}) {
85
+ let raw;
86
+ try {
87
+ raw = readFile(devModeFilePath(homeDir));
88
+ } catch {
89
+ return { record: null, parseError: null };
90
+ }
91
+ try {
92
+ const parsed = JSON.parse(raw);
93
+ const mode = parsed?.mode === "prod" || parsed?.mode === "dev" ? parsed.mode : null;
94
+ const reliance = normalizeReliance(parsed?.reliance);
95
+ if (!mode || mode === "dev" && !reliance) {
96
+ return { record: null, parseError: `invalid dev-mode record: ${JSON.stringify(parsed)}` };
97
+ }
98
+ return {
99
+ record: {
100
+ mode,
101
+ reliance: mode === "prod" ? "cloud" : reliance,
102
+ console_url: typeof parsed.console_url === "string" ? parsed.console_url : null,
103
+ updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : null,
104
+ source: typeof parsed.source === "string" ? parsed.source : null
105
+ },
106
+ parseError: null
107
+ };
108
+ } catch (err) {
109
+ return {
110
+ record: null,
111
+ parseError: `unparseable dev-mode.json: ${err instanceof Error ? err.message : String(err)}`
112
+ };
113
+ }
114
+ }
115
+ function resolveDevMode({
116
+ env = process.env,
117
+ homeDir = homedir2(),
118
+ readFile = defaultReadFile,
119
+ cloudSetting = void 0
120
+ } = {}) {
121
+ const local = readLocalDevModeRecord({ homeDir, readFile });
122
+ const cloudKnown = cloudSetting !== void 0;
123
+ const cloudValue = cloudKnown && cloudSetting ? {
124
+ mode: cloudSetting.mode === "dev" ? "dev" : "prod",
125
+ reliance: normalizeReliance(cloudSetting.reliance) ?? "cloud"
126
+ } : null;
127
+ let mode;
128
+ let reliance;
129
+ let source;
130
+ let relianceSource = null;
131
+ const envParsed = normalizeMode(env.HEYSPEX_DEV_MODE);
132
+ if (envParsed) {
133
+ mode = envParsed.mode;
134
+ const envReliance = envParsed.reliance ?? normalizeReliance(env.HEYSPEX_DEV_RELIANCE);
135
+ reliance = envReliance ?? "local";
136
+ source = "env";
137
+ relianceSource = envReliance ? "env" : "assumed-local";
138
+ } else if (local.record) {
139
+ mode = local.record.mode;
140
+ reliance = local.record.reliance;
141
+ source = "local-override";
142
+ } else if (cloudValue) {
143
+ mode = cloudValue.mode;
144
+ reliance = cloudValue.reliance;
145
+ source = "cloud-setting";
146
+ } else {
147
+ mode = "prod";
148
+ reliance = "cloud";
149
+ source = "default";
150
+ }
151
+ const semantics = deriveSemantics(mode, reliance);
152
+ const consoleUrl = source === "local-override" && local.record?.console_url ? local.record.console_url.replace(/\/+$/, "") : semantics.consoleUrl;
153
+ const staleAgainstCloud = source === "local-override" && cloudValue !== null && (cloudValue.mode !== mode || mode === "dev" && cloudValue.reliance !== semantics.reliance);
154
+ return {
155
+ mode,
156
+ reliance: semantics.reliance,
157
+ consoleUrl,
158
+ readTier: semantics.readTier,
159
+ vaultWriteMode: semantics.vaultWriteMode,
160
+ sources: {
161
+ mode: source,
162
+ reliance: relianceSource ?? source,
163
+ consoleUrl: source === "local-override" && local.record?.console_url ? "local-override" : "derived",
164
+ readTier: "derived",
165
+ vaultWriteMode: "derived"
166
+ },
167
+ localOverride: {
168
+ present: local.record !== null,
169
+ record: local.record,
170
+ parseError: local.parseError,
171
+ staleAgainstCloud
172
+ },
173
+ cloudSetting: { known: cloudKnown, value: cloudValue }
174
+ };
175
+ }
176
+ function modeOverrideOf(resolved) {
177
+ if (!resolved) return null;
178
+ const cloud = resolved.cloudSetting.value;
179
+ if (resolved.sources.mode === "env") {
180
+ const disagrees = cloud !== null && (cloud.mode !== resolved.mode || resolved.mode === "dev" && cloud.reliance !== resolved.reliance);
181
+ return {
182
+ source: "env",
183
+ written_at: null,
184
+ written_by: "HEYSPEX_DEV_MODE",
185
+ disagrees_with_cloud: disagrees
186
+ };
187
+ }
188
+ if (resolved.localOverride.present) {
189
+ const record = resolved.localOverride.record;
190
+ return {
191
+ source: "dev-mode.json",
192
+ written_at: record?.updated_at ?? null,
193
+ written_by: record?.source ?? null,
194
+ disagrees_with_cloud: resolved.localOverride.staleAgainstCloud === true
195
+ };
196
+ }
197
+ return null;
198
+ }
199
+ function buildDevStatusJson(resolved, cloudError) {
200
+ return { resolved, cloudError, mode_override: modeOverrideOf(resolved) };
201
+ }
202
+ function upsertCredentialsYamlKeys(homeDir, updates, deps = {}) {
203
+ const readFile = deps.readFile ?? defaultReadFile;
204
+ const writeFile = deps.writeFile ?? ((p, c) => writeFileSync2(p, c, { mode: 384 }));
205
+ const mkdir = deps.mkdir ?? ((p) => mkdirSync2(p, { recursive: true, mode: 448 }));
206
+ const dir = join2(homeDir, ".heyspex");
207
+ const filePath = join2(dir, "credentials.yaml");
208
+ if (!deps.writeFile) assertNotRealHomeUnderTest(filePath);
209
+ mkdir(dir);
210
+ let content = "";
211
+ try {
212
+ content = readFile(filePath);
213
+ } catch {
214
+ content = "";
215
+ }
216
+ const lines = content.length > 0 ? content.split(/\r?\n/) : [];
217
+ for (const [key, value] of Object.entries(updates)) {
218
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
219
+ const pattern = new RegExp(`^${escapedKey}:.*$`);
220
+ const serialized = `${key}: ${value}`;
221
+ const idx = lines.findIndex((line) => pattern.test(line));
222
+ if (idx >= 0) lines[idx] = serialized;
223
+ else lines.push(serialized);
224
+ }
225
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
226
+ writeFile(filePath, `${lines.join("\n")}
227
+ `);
228
+ return filePath;
229
+ }
230
+ function readCredentialsMcpToken({
231
+ homeDir = homedir2(),
232
+ readFile = defaultReadFile
233
+ } = {}) {
234
+ try {
235
+ const raw = readFile(join2(homeDir, ".heyspex", "credentials.yaml"));
236
+ const match = raw.match(/^mcp_token:\s*["']?([^"'\r\n#]+)/m);
237
+ return match?.[1]?.trim() || null;
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+ async function withRequestTimeout(timeoutMs, fn) {
243
+ const controller = new AbortController();
244
+ const state = { timedOut: false };
245
+ const timer = setTimeout(() => {
246
+ state.timedOut = true;
247
+ controller.abort();
248
+ }, timeoutMs);
249
+ timer.unref?.();
250
+ try {
251
+ return await fn(controller.signal);
252
+ } catch (err) {
253
+ if (state.timedOut) throw new Error(`timed out after ${timeoutMs}ms`);
254
+ throw err;
255
+ } finally {
256
+ clearTimeout(timer);
257
+ }
258
+ }
259
+ async function discardResponseBody(res) {
260
+ try {
261
+ await res?.body?.cancel();
262
+ } catch {
263
+ }
264
+ }
265
+ async function fetchCloudDevMode({
266
+ consoleUrl,
267
+ fetchImpl = fetch,
268
+ homeDir = homedir2(),
269
+ readFile = defaultReadFile,
270
+ timeoutMs = 4e3
271
+ }) {
272
+ try {
273
+ const token = readCredentialsMcpToken({ homeDir, readFile });
274
+ const res = await withRequestTimeout(
275
+ timeoutMs,
276
+ (signal) => fetchImpl(`${consoleUrl.replace(/\/+$/, "")}/api/admin/dev-mode`, {
277
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
278
+ signal
279
+ })
280
+ );
281
+ if (!res.ok) {
282
+ await discardResponseBody(res);
283
+ return { known: false, value: null, error: `HTTP ${res.status}` };
284
+ }
285
+ const body = await res.json();
286
+ if (body.cloudKnown === false)
287
+ return { known: false, value: null, error: "storage unavailable" };
288
+ const cloud = body.cloud;
289
+ return {
290
+ known: true,
291
+ value: cloud ? normalizeDevModeInput(cloud.mode, cloud.reliance) : null,
292
+ error: null
293
+ };
294
+ } catch (err) {
295
+ return { known: false, value: null, error: err instanceof Error ? err.message : String(err) };
296
+ }
297
+ }
298
+ var DevModeNotAdminError = class extends Error {
299
+ constructor() {
300
+ super(
301
+ "This account runs on HeySpex Cloud. Switching to local-disk mode is reserved for the HeySpex team's own build machines."
302
+ );
303
+ this.name = "DevModeNotAdminError";
304
+ }
305
+ };
306
+ async function applyDevMode(targetMode, targetReliance, deps = {}) {
307
+ const {
308
+ homeDir = homedir2(),
309
+ fetchImpl = fetch,
310
+ source = "cli",
311
+ now = () => (/* @__PURE__ */ new Date()).toISOString(),
312
+ syncCloud = true
313
+ } = deps;
314
+ const writeFile = deps.writeFile ?? ((p, c) => writeFileSync2(p, c, "utf8"));
315
+ const mkdir = deps.mkdir ?? ((p) => mkdirSync2(p, { recursive: true, mode: 448 }));
316
+ const parsed = normalizeMode(targetMode);
317
+ if (!parsed) {
318
+ throw new Error(
319
+ `applyDevMode: invalid mode "${targetMode}" (expected prod | dev | local | cloud)`
320
+ );
321
+ }
322
+ const mode = parsed.mode;
323
+ const reliance = parsed.reliance ?? normalizeReliance(targetReliance) ?? "local";
324
+ const semantics = deriveSemantics(mode, reliance);
325
+ if (!deps.writeFile) assertNotRealHomeUnderTest(devModeFilePath(homeDir));
326
+ async function postCloud(token) {
327
+ return withRequestTimeout(
328
+ 5e3,
329
+ (signal) => fetchImpl(`${semantics.consoleUrl}/api/admin/dev-mode`, {
330
+ method: "POST",
331
+ headers: {
332
+ "content-type": "application/json",
333
+ ...token ? { Authorization: `Bearer ${token}` } : {}
334
+ },
335
+ body: JSON.stringify({ mode, reliance: semantics.reliance, source }),
336
+ signal
337
+ })
338
+ );
339
+ }
340
+ let cloud = { attempted: false, updated: false, error: null };
341
+ if (syncCloud && semantics.reliance === "local") {
342
+ const token = readCredentialsMcpToken({ homeDir, readFile: deps.readFile });
343
+ if (token) {
344
+ try {
345
+ const res = await postCloud(token);
346
+ await discardResponseBody(res);
347
+ if (res.status === 403) throw new DevModeNotAdminError();
348
+ cloud = { attempted: true, updated: res.ok, error: res.ok ? null : `HTTP ${res.status}` };
349
+ } catch (err) {
350
+ if (err instanceof DevModeNotAdminError) throw err;
351
+ cloud = {
352
+ attempted: true,
353
+ updated: false,
354
+ error: err instanceof Error ? err.message : String(err)
355
+ };
356
+ }
357
+ }
358
+ }
359
+ const record = {
360
+ mode,
361
+ reliance: semantics.reliance,
362
+ console_url: semantics.consoleUrl,
363
+ updated_at: now(),
364
+ source
365
+ };
366
+ mkdir(join2(homeDir, ".heyspex"));
367
+ const recordPath = devModeFilePath(homeDir);
368
+ writeFile(recordPath, `${JSON.stringify(record, null, 2)}
369
+ `);
370
+ const credentialsPath = upsertCredentialsYamlKeys(
371
+ homeDir,
372
+ { console_url: semantics.consoleUrl },
373
+ { readFile: deps.readFile, writeFile: deps.writeFile, mkdir: deps.mkdir }
374
+ );
375
+ if (syncCloud && !cloud.attempted) {
376
+ cloud = { attempted: true, updated: false, error: null };
377
+ try {
378
+ const token = readCredentialsMcpToken({ homeDir, readFile: deps.readFile });
379
+ const res = await postCloud(token);
380
+ if (res.ok) cloud.updated = true;
381
+ else cloud.error = `HTTP ${res.status}`;
382
+ await discardResponseBody(res);
383
+ } catch (err) {
384
+ cloud.error = err instanceof Error ? err.message : String(err);
385
+ }
386
+ }
387
+ return {
388
+ applied: {
389
+ mode,
390
+ reliance: semantics.reliance,
391
+ consoleUrl: semantics.consoleUrl,
392
+ readTier: semantics.readTier,
393
+ vaultWriteMode: semantics.vaultWriteMode
394
+ },
395
+ record,
396
+ recordPath,
397
+ credentialsPath,
398
+ cloud
399
+ };
400
+ }
401
+
402
+ // spex-console/bin/run-entry.ts
403
+ import process2 from "node:process";
404
+ var BinExit = class extends Error {
405
+ code;
406
+ constructor(code) {
407
+ super(`exit ${code}`);
408
+ this.name = "BinExit";
409
+ this.code = code;
410
+ }
411
+ };
412
+ function runEntry(name, main2, options = {}) {
413
+ const { fallbackCode = 1, unhandledLabel = "unhandled" } = options;
414
+ void (async () => {
415
+ try {
416
+ await main2();
417
+ } catch (err) {
418
+ if (err instanceof BinExit) {
419
+ process2.exitCode = err.code;
420
+ return;
421
+ }
422
+ const message = err instanceof Error ? err.message : String(err);
423
+ process2.stderr.write(`${name}: ${unhandledLabel}: ${message}
424
+ `);
425
+ process2.exitCode = fallbackCode;
426
+ }
427
+ })();
428
+ }
429
+
430
+ // spex-console/bin/hs-dev.ts
431
+ var HELP = `hs dev \u2014 dev/prod mode + local/cloud reliance
432
+
433
+ Usage:
434
+ hs dev [--json] Show the resolved mode and the source of each value
435
+ hs dev mode:local Switch to dev mode, local reliance (localhost console, disk reads, local writes)
436
+ hs dev mode:cloud Switch to dev mode, cloud reliance (cloud console, Neon reads, cloud writes)
437
+ hs dev mode:prod Switch to prod (cloud console, Neon reads, cloud writes)
438
+ hs dev local|cloud|prod Shorthand for the mode:* forms
439
+
440
+ Resolution order: env (HEYSPEX_DEV_MODE) \u2192 ~/.heyspex/dev-mode.json \u2192
441
+ cloud admin setting (account_settings.dev_mode) \u2192 default prod.
442
+
443
+ Switching writes ~/.heyspex/dev-mode.json and console_url in
444
+ ~/.heyspex/credentials.yaml (which repoints MCP/hooks/dev-factory via the
445
+ shared console-url resolver), and best-effort updates the cloud admin setting.
446
+ Never touches repo-tracked files.`;
447
+ var SOURCE_LABEL = {
448
+ env: "env override",
449
+ "local-override": "local override (~/.heyspex/dev-mode.json)",
450
+ "cloud-setting": "cloud admin setting",
451
+ default: "default",
452
+ derived: "derived",
453
+ // HS-EVVI AC1: HEYSPEX_DEV_MODE=dev with no HEYSPEX_DEV_RELIANCE is never
454
+ // silently local any more — the row says the reliance was assumed.
455
+ "assumed-local": "ASSUMED local \u2014 HEYSPEX_DEV_MODE=dev names no reliance; set HEYSPEX_DEV_RELIANCE"
456
+ };
457
+ function describeCloudFailure(cloudError) {
458
+ if (cloudError === "HTTP 401") {
459
+ return "not signed in on this machine (HTTP 401) \u2014 run `hs login`";
460
+ }
461
+ return `unavailable${cloudError ? ` (${cloudError})` : ""}`;
462
+ }
463
+ function printStatus(resolved, cloudError) {
464
+ const rows = [
465
+ ["mode", resolved.mode, resolved.sources.mode],
466
+ ["reliance", resolved.reliance, resolved.sources.reliance],
467
+ ["console URL", resolved.consoleUrl, resolved.sources.consoleUrl],
468
+ ["read tier", resolved.readTier, resolved.sources.readTier],
469
+ ["vault writes", resolved.vaultWriteMode, resolved.sources.vaultWriteMode]
470
+ ];
471
+ const out = ["hs dev \u2014 resolved mode", ""];
472
+ for (const [label, value, source] of rows) {
473
+ out.push(` ${label.padEnd(13)}${value.padEnd(28)}${SOURCE_LABEL[source] ?? source}`);
474
+ }
475
+ out.push("");
476
+ if (resolved.sources.mode === "env") {
477
+ out.push(
478
+ " env override: active (HEYSPEX_DEV_MODE) \u2014 hides the local record and the cloud setting"
479
+ );
480
+ }
481
+ if (resolved.localOverride.present) {
482
+ const rec = resolved.localOverride.record;
483
+ out.push(
484
+ ` local override: active (written ${rec?.updated_at ?? "?"} by ${rec?.source ?? "?"})` + (resolved.localOverride.staleAgainstCloud ? " \u2014 DISAGREES with the cloud setting (stale override?)" : "")
485
+ );
486
+ } else if (resolved.localOverride.parseError) {
487
+ out.push(` local override: UNREADABLE, ignored \u2014 ${resolved.localOverride.parseError}`);
488
+ } else {
489
+ out.push(" local override: none");
490
+ }
491
+ if (resolved.cloudSetting.known) {
492
+ const v = resolved.cloudSetting.value;
493
+ out.push(
494
+ v ? ` cloud setting: ${v.mode}${v.mode === "dev" ? ` (${v.reliance})` : ""}` : " cloud setting: unset"
495
+ );
496
+ } else {
497
+ out.push(` cloud setting: ${describeCloudFailure(cloudError)}`);
498
+ }
499
+ process3.stdout.write(`${out.join("\n")}
500
+ `);
501
+ }
502
+ async function main() {
503
+ const argv = process3.argv.slice(3);
504
+ if (argv.includes("--help") || argv.includes("-h")) {
505
+ process3.stdout.write(`${HELP}
506
+ `);
507
+ process3.exitCode = 0;
508
+ return;
509
+ }
510
+ const json = argv.includes("--json");
511
+ const positional = argv.filter((a) => !a.startsWith("--"));
512
+ const token = positional[0]?.toLowerCase();
513
+ if (token === void 0 || token === "status") {
514
+ const pre = resolveDevMode();
515
+ const cloud = await fetchCloudDevMode({ consoleUrl: pre.consoleUrl });
516
+ const resolved = resolveDevMode({ cloudSetting: cloud.known ? cloud.value : void 0 });
517
+ if (json) {
518
+ process3.stdout.write(
519
+ `${JSON.stringify(buildDevStatusJson(resolved, cloud.error), null, 2)}
520
+ `
521
+ );
522
+ } else {
523
+ printStatus(resolved, cloud.error);
524
+ }
525
+ process3.exitCode = 0;
526
+ return;
527
+ }
528
+ const modeToken = token.startsWith("mode:") ? token.slice("mode:".length) : token;
529
+ if (!["local", "cloud", "prod", "dev"].includes(modeToken)) {
530
+ process3.stderr.write(`hs dev: unknown subcommand \`${token}\`
531
+
532
+ ${HELP}
533
+ `);
534
+ process3.exitCode = 2;
535
+ return;
536
+ }
537
+ try {
538
+ const result = await applyDevMode(modeToken, positional[1] ?? null, { source: "cli" });
539
+ if (json) {
540
+ process3.stdout.write(`${JSON.stringify(result, null, 2)}
541
+ `);
542
+ } else {
543
+ const a = result.applied;
544
+ const lines = [
545
+ `Switched to ${a.mode}${a.mode === "dev" ? ` (${a.reliance})` : ""}:`,
546
+ ` console URL ${a.consoleUrl}`,
547
+ ` read tier ${a.readTier}`,
548
+ ` vault writes ${a.vaultWriteMode}`,
549
+ ` wrote ${result.recordPath}`,
550
+ ` wrote console_url in ${result.credentialsPath}`,
551
+ result.cloud.attempted ? result.cloud.updated ? " cloud admin setting updated" : ` cloud admin setting NOT updated (${result.cloud.error}) \u2014 local files still switched` : " cloud sync skipped"
552
+ ];
553
+ process3.stdout.write(`${lines.join("\n")}
554
+ `);
555
+ }
556
+ process3.exitCode = 0;
557
+ } catch (err) {
558
+ const message = err instanceof Error ? err.message : String(err);
559
+ process3.stderr.write(`hs dev: ${message}
560
+ `);
561
+ process3.exitCode = 1;
562
+ }
563
+ }
564
+ runEntry("hs dev", main, { fallbackCode: 1 });