heyspex 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/README.md +21 -0
  3. package/bin/hs.mjs +1089 -104
  4. package/bin/spexcode.mjs +262 -0
  5. package/dev-factory/engine/worktree.mjs +740 -0
  6. package/dev-factory/lib/code-repo-root.mjs +67 -0
  7. package/dev-factory/lib/context-budget.mjs +84 -0
  8. package/dev-factory/lib/epic-detect.mjs +190 -0
  9. package/dev-factory/lib/fleet-queue-file.mjs +846 -0
  10. package/dev-factory/lib/fleet-queue.mjs +426 -0
  11. package/dev-factory/lib/fleet-state.mjs +14 -18
  12. package/dev-factory/lib/model-catalog.mjs +338 -0
  13. package/dev-factory/lib/seat-context.mjs +199 -0
  14. package/dev-factory/lib/spec-io.mjs +93 -0
  15. package/dev-factory/lib/sufficiency-score.mjs +8 -2
  16. package/dev-factory/lib/worktree-allocator.mjs +744 -0
  17. package/dev-factory/model-catalog.json +222 -0
  18. package/dist/cli/hs-check.mjs +1127 -103
  19. package/dist/cli/hs-claim.mjs +981 -63
  20. package/dist/cli/hs-connect.mjs +10059 -1729
  21. package/dist/cli/hs-defer.mjs +5507 -3072
  22. package/dist/cli/hs-dev.mjs +576 -0
  23. package/dist/cli/hs-env.mjs +416 -302
  24. package/dist/cli/hs-index-rebuild.mjs +7334 -0
  25. package/dist/cli/hs-index-reconcile.mjs +8138 -0
  26. package/dist/cli/hs-init.mjs +2840 -149
  27. package/dist/cli/hs-install.mjs +7214 -594
  28. package/dist/cli/hs-join.mjs +10640 -2421
  29. package/dist/cli/hs-vault-pull.mjs +8157 -6370
  30. package/dist/cli/hs-vault-push.mjs +6433 -0
  31. package/dist/cli/hs-vault.mjs +4029 -1555
  32. package/dist/cli/hs-workspace.mjs +2702 -538
  33. package/dist/cli-templates/customer-vault/specs/entity-model.md +594 -601
  34. package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +110 -177
  35. package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -0
  36. package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +13 -15
  37. package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +91 -0
  38. package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +150 -168
  39. package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -0
  40. package/dist/cli-templates/harness-commands/claude/hs-auto.md +36 -0
  41. package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -0
  42. package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -0
  43. package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -0
  44. package/dist/cli-templates/harness-commands/claude/hs-dev.md +20 -0
  45. package/dist/cli-templates/harness-commands/claude/hs-doctor.md +32 -0
  46. package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -0
  47. package/dist/cli-templates/harness-commands/claude/hs-handoff.md +33 -0
  48. package/dist/cli-templates/harness-commands/claude/hs-log.md +27 -0
  49. package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -0
  50. package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -0
  51. package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -0
  52. package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -0
  53. package/dist/cli-templates/harness-commands/claude/hs-res.md +63 -0
  54. package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -0
  55. package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -0
  56. package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -0
  57. package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -0
  58. package/dist/cli-templates/harness-commands/claude/prototype.md +91 -0
  59. package/dist/cli-templates/harness-commands/cursor/hs-auto.md +40 -0
  60. package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +31 -0
  61. package/dist/cli-templates/harness-commands/cursor/hs-capture.md +65 -0
  62. package/dist/cli-templates/harness-commands/cursor/hs-defer.md +32 -0
  63. package/dist/cli-templates/harness-commands/cursor/hs-dev.md +24 -0
  64. package/dist/cli-templates/harness-commands/cursor/hs-doctor.md +36 -0
  65. package/dist/cli-templates/harness-commands/cursor/hs-ground.md +33 -0
  66. package/dist/cli-templates/harness-commands/cursor/hs-handoff.md +37 -0
  67. package/dist/cli-templates/harness-commands/cursor/hs-log.md +31 -0
  68. package/dist/cli-templates/harness-commands/cursor/hs-next.md +32 -0
  69. package/dist/cli-templates/harness-commands/cursor/hs-promote.md +73 -0
  70. package/dist/cli-templates/harness-commands/cursor/hs-push.md +38 -0
  71. package/dist/cli-templates/harness-commands/cursor/hs-qc.md +30 -0
  72. package/dist/cli-templates/harness-commands/cursor/hs-res.md +67 -0
  73. package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +26 -0
  74. package/dist/cli-templates/harness-commands/cursor/hs-review.md +26 -0
  75. package/dist/cli-templates/harness-commands/cursor/hs-view.md +32 -0
  76. package/dist/cli-templates/harness-commands/cursor/kill-ports.md +35 -0
  77. package/dist/cli-templates/harness-commands/cursor/prototype.md +95 -0
  78. package/docs/copilot-plugin-install.md +162 -0
  79. package/docs/driving-heyspex-from-copilot.md +165 -0
  80. package/docs/mcp-client-config.md +259 -6
  81. package/package.json +249 -124
  82. package/plugins/heyspex/.claude-plugin/plugin.json +12 -0
  83. package/plugins/heyspex/.cursor-plugin/plugin.json +15 -0
  84. package/plugins/heyspex/.mcp.json +12 -0
  85. package/plugins/heyspex/README.md +309 -0
  86. package/plugins/heyspex/bin/launch-mcp.mjs +95 -0
  87. package/plugins/heyspex/bin/run-hook.mjs +80 -0
  88. package/plugins/heyspex/hooks/cursor-hooks.json +308 -0
  89. package/plugins/heyspex/hooks/hooks.json +418 -0
  90. package/plugins/heyspex/mcp.json +13 -0
  91. package/scripts/check-mcp-freshness.mjs +17 -9
  92. package/scripts/claude-hooks/_env-vault-events.mjs +68 -0
  93. package/scripts/claude-hooks/_guardrail-events.mjs +96 -0
  94. package/scripts/claude-hooks/_guardrail-loader.mjs +494 -0
  95. package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -0
  96. package/scripts/claude-hooks/_intent-classifier-semantic.mjs +356 -0
  97. package/scripts/claude-hooks/_run.mjs +413 -0
  98. package/scripts/claude-hooks/_transcript-text.mjs +60 -0
  99. package/scripts/claude-hooks/_vault-retrieval.mjs +322 -0
  100. package/scripts/claude-hooks/agent-spawn-delegation-gate.mjs +133 -0
  101. package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
  102. package/scripts/claude-hooks/capture-workflow-gate.mjs +11 -0
  103. package/scripts/claude-hooks/check-auto-memory-budget.mjs +396 -0
  104. package/scripts/claude-hooks/check-claude-md-budget.mjs +327 -0
  105. package/scripts/claude-hooks/check-mcp-freshness-hook.mjs +14 -0
  106. package/scripts/claude-hooks/claim-before-code-gate.mjs +11 -0
  107. package/scripts/claude-hooks/claim-liveness-shared.mjs +577 -0
  108. package/scripts/claude-hooks/close-terminal-on-ship.mjs +64 -0
  109. package/scripts/claude-hooks/coo-denylist-gate.mjs +11 -0
  110. package/scripts/claude-hooks/coo-guard-check.mjs +11 -0
  111. package/scripts/claude-hooks/deep-research-cost-gate.mjs +11 -0
  112. package/scripts/claude-hooks/dev-server-task-reminder.mjs +15 -0
  113. package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
  114. package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
  115. package/scripts/claude-hooks/emit-claim-status.mjs +41 -0
  116. package/scripts/claude-hooks/enforce-session-sequence.mjs +11 -0
  117. package/scripts/claude-hooks/enforce-vault-naming.mjs +11 -0
  118. package/scripts/claude-hooks/env-vault-guard.mjs +11 -0
  119. package/scripts/claude-hooks/epic-milestone-phrasing-gate.mjs +11 -0
  120. package/scripts/claude-hooks/git-identity-guard.mjs +11 -0
  121. package/scripts/claude-hooks/git-reflog-audit.mjs +15 -0
  122. package/scripts/claude-hooks/git-stash-gate.mjs +11 -0
  123. package/scripts/claude-hooks/guard-auto-memory-writes.mjs +137 -0
  124. package/scripts/claude-hooks/id-display-format-gate.mjs +11 -0
  125. package/scripts/claude-hooks/inject-communication-style.mjs +86 -0
  126. package/scripts/claude-hooks/inject-coo-seat.mjs +78 -0
  127. package/scripts/claude-hooks/inject-dev-server-awareness.mjs +11 -0
  128. package/scripts/claude-hooks/inject-doctrine.mjs +13 -0
  129. package/scripts/claude-hooks/inject-vault-context.mjs +14 -0
  130. package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -0
  131. package/scripts/claude-hooks/log-session-activity.mjs +341 -0
  132. package/scripts/claude-hooks/naughty-list-gate.mjs +11 -0
  133. package/scripts/claude-hooks/notify-status-flip.mjs +72 -0
  134. package/scripts/claude-hooks/plan-exit-ingest-gate.mjs +170 -0
  135. package/scripts/claude-hooks/plan-ingest-close-reminder.mjs +66 -0
  136. package/scripts/claude-hooks/plan-review-gate.mjs +11 -0
  137. package/scripts/claude-hooks/preflight-reminder.mjs +262 -0
  138. package/scripts/claude-hooks/protected-action-intent.mjs +221 -0
  139. package/scripts/claude-hooks/research-method-guard.mjs +398 -0
  140. package/scripts/claude-hooks/research-protocol-gate.mjs +11 -0
  141. package/scripts/claude-hooks/session-end.mjs +451 -0
  142. package/scripts/claude-hooks/settings-write-gate.mjs +11 -0
  143. package/scripts/claude-hooks/settlement-check-gate.mjs +39 -0
  144. package/scripts/claude-hooks/status-flip-detect.mjs +17 -0
  145. package/scripts/claude-hooks/terminal-close-shared.mjs +426 -0
  146. package/scripts/claude-hooks/terminal-close-watcher.mjs +122 -0
  147. package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
  148. package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
  149. package/scripts/claude-hooks/wip-checkpoint.mjs +302 -0
  150. package/scripts/cursor-hooks/manifest.mjs +281 -0
  151. package/scripts/dev-server-status.mjs +34 -13
  152. package/scripts/dev-server-stop.mjs +313 -0
  153. package/scripts/heyspex-mode.mjs +288 -0
  154. package/scripts/hs-auto-resolve.mjs +240 -0
  155. package/scripts/hs-auto-source.mjs +398 -0
  156. package/scripts/hs-auto.mjs +134 -0
  157. package/scripts/hs-capture-cli.mjs +326 -0
  158. package/scripts/hs-classifier.mjs +136 -0
  159. package/scripts/hs-companion.mjs +88 -0
  160. package/scripts/hs-gateway.mjs +115 -0
  161. package/scripts/hs-mode.mjs +125 -0
  162. package/scripts/hs-next.mjs +441 -0
  163. package/scripts/hs-settings.mjs +338 -0
  164. package/scripts/hs-view.mjs +496 -0
  165. package/scripts/hs-worktree.mjs +201 -0
  166. package/scripts/install-claude-code-hooks.mjs +272 -0
  167. package/scripts/install-cursor-hooks.mjs +471 -0
  168. package/scripts/lib/agent-env-markers.mjs +38 -0
  169. package/scripts/lib/console-url-resolver.mjs +199 -61
  170. package/scripts/lib/customer-kit.mjs +1203 -0
  171. package/scripts/lib/dev-mode.mjs +249 -32
  172. package/scripts/lib/directive-obligations.mjs +349 -0
  173. package/scripts/lib/heyspex-home.mjs +72 -0
  174. package/scripts/lib/hook-dispositions.mjs +210 -0
  175. package/scripts/lib/hook-vault-header.mjs +30 -0
  176. package/scripts/lib/hs-command.mjs +277 -0
  177. package/scripts/lib/hs-companion-settings.mjs +472 -0
  178. package/scripts/lib/hs-source-branch.mjs +196 -0
  179. package/scripts/lib/hs-source-commit.mjs +59 -5
  180. package/scripts/lib/hs-source-identity.mjs +526 -0
  181. package/scripts/lib/hs-source-log.mjs +13 -22
  182. package/scripts/lib/hs-source-preflight.mjs +18 -4
  183. package/scripts/lib/hs-source-push.mjs +386 -307
  184. package/scripts/lib/hs-worktree-args.mjs +115 -0
  185. package/scripts/lib/shared-path-helpers.mjs +134 -0
  186. package/scripts/lib/standalone-doctor.mjs +540 -3
  187. package/scripts/lib/subagent-git-lock.mjs +109 -0
  188. package/scripts/lib/template-pin.mjs +34 -0
  189. package/scripts/lib/window-placement.mjs +109 -0
  190. package/scripts/provenance-lib.mjs +91 -0
  191. package/scripts/stamp-provenance-on-edit.mjs +222 -0
  192. package/scripts/wip-ledger-health.mjs +653 -0
  193. package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -0
  194. package/spexharness/src/adapters/audit.mjs +197 -0
  195. package/spexharness/src/adapters/claude-code.mjs +449 -0
  196. package/spexharness/src/adapters/copilot.mjs +236 -0
  197. package/spexharness/src/adapters/detect-harness.mjs +34 -0
  198. package/spexharness/src/chat/tui/ansi.mjs +448 -0
  199. package/spexharness/src/chat/tui/palette.mjs +22 -0
  200. package/spexharness/src/chat/tui/select.mjs +208 -0
  201. package/spexharness/src/config-root.mjs +54 -0
  202. package/spexharness/src/context-budget.mjs +141 -0
  203. package/spexharness/src/dev-server-control.mjs +389 -0
  204. package/spexharness/src/doctrine/behaviour.mjs +371 -0
  205. package/spexharness/src/doctrine/corpus.mjs +733 -0
  206. package/spexharness/src/doctrine/inject.mjs +338 -0
  207. package/spexharness/src/doctrine/merge.mjs +184 -0
  208. package/spexharness/src/doctrine/sources.mjs +252 -0
  209. package/spexharness/src/doctrine/synthesize.mjs +401 -0
  210. package/spexharness/src/doctrine/vault-source.mjs +574 -0
  211. package/spexharness/src/doctrine/writer.mjs +176 -0
  212. package/spexharness/src/events.mjs +84 -0
  213. package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
  214. package/spexharness/src/gates/capture-workflow-gate.mjs +202 -0
  215. package/spexharness/src/gates/claim-before-code-gate.decide.mjs +33 -0
  216. package/spexharness/src/gates/claim-before-code-gate.mjs +129 -0
  217. package/spexharness/src/gates/deep-research-cost-gate.mjs +275 -0
  218. package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
  219. package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
  220. package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
  221. package/spexharness/src/gates/enforce-session-sequence.mjs +895 -0
  222. package/spexharness/src/gates/enforce-vault-naming.mjs +215 -0
  223. package/spexharness/src/gates/epic-milestone-phrasing-gate.mjs +233 -0
  224. package/spexharness/src/gates/git-identity-guard.decide.mjs +436 -0
  225. package/spexharness/src/gates/git-identity-guard.mjs +61 -0
  226. package/spexharness/src/gates/git-reflog-audit.mjs +151 -0
  227. package/spexharness/src/gates/git-stash-gate.decide.mjs +361 -0
  228. package/spexharness/src/gates/git-stash-gate.mjs +159 -0
  229. package/spexharness/src/gates/id-display-format-gate.mjs +190 -0
  230. package/spexharness/src/gates/inject-doctrine-gate.mjs +89 -0
  231. package/spexharness/src/gates/inject-vault-context.mjs +239 -0
  232. package/spexharness/src/gates/naughty-list-gate.mjs +213 -0
  233. package/spexharness/src/gates/plan-review-gate.mjs +233 -0
  234. package/spexharness/src/gates/preflight-reminder-gate.mjs +265 -0
  235. package/spexharness/src/gates/research-protocol-gate.mjs +175 -0
  236. package/spexharness/src/gates/settings-write-gate.decide.mjs +96 -0
  237. package/spexharness/src/gates/settings-write-gate.mjs +104 -0
  238. package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
  239. package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
  240. package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
  241. package/spexharness/src/git-reflog-audit.mjs +294 -0
  242. package/spexharness/src/intent-classifier.mjs +196 -0
  243. package/spexharness/src/intent-directive.mjs +626 -0
  244. package/spexharness/src/profile/learn.mjs +551 -0
  245. package/spexharness/src/profile/proposals.mjs +357 -0
  246. package/spexharness/src/profile/vault-inclusion.mjs +386 -0
  247. package/spexharness/src/redact.mjs +151 -0
  248. package/spexharness/src/rule-table.mjs +849 -0
  249. package/spexharness/src/seat-context.mjs +122 -0
  250. package/spexharness/src/session-bootstrap.mjs +512 -0
  251. package/spexharness/src/session-record.mjs +815 -0
  252. package/spexharness/src/token-inventory.mjs +207 -0
  253. package/vendor/heyspex-mcp/account-switch.mjs +96 -0
  254. package/vendor/heyspex-mcp/alignment-gate.mjs +315 -1
  255. package/vendor/heyspex-mcp/blocker-resolver.mjs +37 -2
  256. package/vendor/heyspex-mcp/breakdown-ladder.mjs +119 -4
  257. package/vendor/heyspex-mcp/breakdown-source.mjs +271 -0
  258. package/vendor/heyspex-mcp/breakdown-writer.mjs +23 -3
  259. package/vendor/heyspex-mcp/capture-buffer-patches.mjs +77 -8
  260. package/vendor/heyspex-mcp/capture-channel.mjs +55 -5
  261. package/vendor/heyspex-mcp/capture-console-client.mjs +117 -3
  262. package/vendor/heyspex-mcp/capture-focus.mjs +1061 -31
  263. package/vendor/heyspex-mcp/capture-outbox.mjs +15 -5
  264. package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
  265. package/vendor/heyspex-mcp/capture-tools.mjs +1264 -0
  266. package/vendor/heyspex-mcp/claim-tools.mjs +1147 -0
  267. package/vendor/heyspex-mcp/cli-verbs.mjs +2074 -0
  268. package/vendor/heyspex-mcp/cli.mjs +207 -15524
  269. package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
  270. package/vendor/heyspex-mcp/cloud-tool-surface.mjs +128 -0
  271. package/vendor/heyspex-mcp/cloud-vault-writes.mjs +602 -0
  272. package/vendor/heyspex-mcp/config-seed.yaml +15 -0
  273. package/vendor/heyspex-mcp/config-settings.mjs +290 -7
  274. package/vendor/heyspex-mcp/console-fetch.mjs +822 -17
  275. package/vendor/heyspex-mcp/console-transport.mjs +172 -0
  276. package/vendor/heyspex-mcp/credentials.mjs +585 -0
  277. package/vendor/heyspex-mcp/defer-spec.mjs +44 -4
  278. package/vendor/heyspex-mcp/deferred-reclaim.mjs +29 -3
  279. package/vendor/heyspex-mcp/design-handlers.mjs +31 -0
  280. package/vendor/heyspex-mcp/design-tools.mjs +4 -1
  281. package/vendor/heyspex-mcp/dev-factory-bridge.mjs +4 -4
  282. package/vendor/heyspex-mcp/dev-mode-core.mjs +45 -0
  283. package/vendor/heyspex-mcp/dev-tools.mjs +131 -0
  284. package/vendor/heyspex-mcp/diagnose-handlers.mjs +54 -0
  285. package/vendor/heyspex-mcp/events-tools.mjs +207 -0
  286. package/vendor/heyspex-mcp/filing-tools.mjs +723 -0
  287. package/vendor/heyspex-mcp/fleet-handlers.mjs +37 -0
  288. package/vendor/heyspex-mcp/fleet-queue-tools.mjs +71 -15
  289. package/vendor/heyspex-mcp/focus-tools.mjs +190 -0
  290. package/vendor/heyspex-mcp/folder-trust-store.mjs +60 -0
  291. package/vendor/heyspex-mcp/http-transport.mjs +302 -0
  292. package/vendor/heyspex-mcp/list-tools.mjs +257 -0
  293. package/vendor/heyspex-mcp/login-handoff.mjs +15 -1
  294. package/vendor/heyspex-mcp/mcp-config.mjs +107 -0
  295. package/vendor/heyspex-mcp/memory-tools.mjs +523 -0
  296. package/vendor/heyspex-mcp/mvp-mode.mjs +53 -6
  297. package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
  298. package/vendor/heyspex-mcp/occupied-keys.mjs +142 -0
  299. package/vendor/heyspex-mcp/on-deck-brief.mjs +3377 -0
  300. package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -0
  301. package/vendor/heyspex-mcp/package-root.mjs +32 -0
  302. package/vendor/heyspex-mcp/peer-presence.mjs +8 -1
  303. package/vendor/heyspex-mcp/plan-ground.mjs +621 -652
  304. package/vendor/heyspex-mcp/plan-push.mjs +908 -22
  305. package/vendor/heyspex-mcp/plan-review.mjs +124 -30
  306. package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
  307. package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -0
  308. package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -970
  309. package/vendor/heyspex-mcp/promote-capture.mjs +388 -69
  310. package/vendor/heyspex-mcp/promote-diagnose.mjs +74 -0
  311. package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +9 -2
  312. package/vendor/heyspex-mcp/promote-research.mjs +666 -53
  313. package/vendor/heyspex-mcp/promotion-helpers.mjs +251 -0
  314. package/vendor/heyspex-mcp/prompts.mjs +1460 -0
  315. package/vendor/heyspex-mcp/read-path.mjs +191 -15
  316. package/vendor/heyspex-mcp/receipt-shape.mjs +146 -0
  317. package/vendor/heyspex-mcp/recipe-handlers.mjs +117 -0
  318. package/vendor/heyspex-mcp/regen-tasks.mjs +210 -3
  319. package/vendor/heyspex-mcp/register-spec.mjs +266 -54
  320. package/vendor/heyspex-mcp/request-context.mjs +83 -0
  321. package/vendor/heyspex-mcp/research-focus.mjs +207 -4
  322. package/vendor/heyspex-mcp/research-handlers.mjs +625 -0
  323. package/vendor/heyspex-mcp/research-tools.mjs +4 -4
  324. package/vendor/heyspex-mcp/response-budget.mjs +348 -0
  325. package/vendor/heyspex-mcp/review-focus.mjs +25 -1
  326. package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
  327. package/vendor/heyspex-mcp/search-specs.mjs +280 -0
  328. package/vendor/heyspex-mcp/seat-block-ledger.mjs +113 -0
  329. package/vendor/heyspex-mcp/seat-identity.mjs +2 -6
  330. package/vendor/heyspex-mcp/session-owner.mjs +109 -0
  331. package/vendor/heyspex-mcp/session-tools.mjs +162 -0
  332. package/vendor/heyspex-mcp/settings-registry.mjs +232 -0
  333. package/vendor/heyspex-mcp/settings-tools.mjs +85 -12
  334. package/vendor/heyspex-mcp/spec-edit-tools.mjs +453 -0
  335. package/vendor/heyspex-mcp/spec-id-lib.mjs +93 -17
  336. package/vendor/heyspex-mcp/split-research.mjs +304 -56
  337. package/vendor/heyspex-mcp/stdio-rpc.mjs +22 -0
  338. package/vendor/heyspex-mcp/submit-for-review.mjs +150 -7
  339. package/vendor/heyspex-mcp/summary-contract.mjs +105 -0
  340. package/vendor/heyspex-mcp/summary-write-tools.mjs +228 -0
  341. package/vendor/heyspex-mcp/symptom-query.mjs +146 -0
  342. package/vendor/heyspex-mcp/test-helpers.mjs +123 -1
  343. package/vendor/heyspex-mcp/tool-catalog.mjs +2819 -0
  344. package/vendor/heyspex-mcp/tool-dispatch.mjs +657 -0
  345. package/vendor/heyspex-mcp/tool-helpers.mjs +325 -0
  346. package/vendor/heyspex-mcp/validate-linkage.mjs +414 -27
  347. package/vendor/heyspex-mcp/vault-io.mjs +720 -0
  348. package/vendor/heyspex-mcp/vault-less-endpoints.mjs +52 -0
  349. package/vendor/heyspex-mcp/vault-resolve.mjs +554 -68
  350. package/vendor/heyspex-mcp/verify-reproduction.mjs +270 -30
  351. package/vendor/heyspex-mcp/whoami.mjs +92 -0
  352. package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
@@ -0,0 +1,37 @@
1
+ // heyspex_fleet_queue_* handlers (thin wrappers over fleet-queue-tools.mjs). Extracted from cli.mjs under
2
+ // [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
3
+
4
+ import { executeFleetQueueClaim, executeFleetQueueComplete, executeFleetQueueList, executeFleetQueueSubmit, resolveFleetToken } from './fleet-queue-tools.mjs';
5
+ import { consoleFetch } from './console-transport.mjs';
6
+
7
+ export async function handleFleetQueueSubmit(args = {}) {
8
+ const result = await executeFleetQueueSubmit({ consoleFetch, args, fleetToken: resolveFleetToken() });
9
+ return {
10
+ isError: !!result.error,
11
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
12
+ };
13
+ }
14
+
15
+ export async function handleFleetQueueClaim(args = {}) {
16
+ const result = await executeFleetQueueClaim({ consoleFetch, args, fleetToken: resolveFleetToken() });
17
+ return {
18
+ isError: !!result.error,
19
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
20
+ };
21
+ }
22
+
23
+ export async function handleFleetQueueComplete(args = {}) {
24
+ const result = await executeFleetQueueComplete({ consoleFetch, args, fleetToken: resolveFleetToken() });
25
+ return {
26
+ isError: !!result.error,
27
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
28
+ };
29
+ }
30
+
31
+ export async function handleFleetQueueList(args = {}) {
32
+ const result = await executeFleetQueueList({ consoleFetch, args, fleetToken: resolveFleetToken() });
33
+ return {
34
+ isError: !!result.error,
35
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
36
+ };
37
+ }
@@ -24,6 +24,7 @@
24
24
  * call these tools with no extra setup.
25
25
  */
26
26
 
27
+ import { heyspexHome } from '../../scripts/lib/heyspex-home.mjs';
27
28
  import { execFileSync } from 'node:child_process';
28
29
  import { readFileSync } from 'node:fs';
29
30
  import { homedir, platform } from 'node:os';
@@ -40,7 +41,7 @@ export function resolveFleetToken() {
40
41
  const env = process.env.HEYSPEX_FLEET_TOKEN;
41
42
  if (env && env.trim()) return env.trim();
42
43
  try {
43
- const yaml = readFileSync(join(homedir(), '.heyspex', 'credentials.yaml'), 'utf8');
44
+ const yaml = readFileSync(join(heyspexHome(), '.heyspex', 'credentials.yaml'), 'utf8');
44
45
  const clerk = yaml.match(/^clerk:\s*$([\s\S]*?)(?=^\S|\s*$(?![\s\S]))/m)?.[1] ?? yaml;
45
46
  const m = clerk.match(/^\s*companion_token:\s*["']?([^"'\r\n#]+)/m);
46
47
  if (m?.[1]?.trim()) return m[1].trim();
@@ -66,32 +67,88 @@ function bearerHeaders(fleetToken) {
66
67
  }
67
68
 
68
69
  /**
69
- * HS-FS7X (interim only — full fix is the unstarted [[HS-XOCK-60Z1-EPIC-fleet-work-queue-in-heyspex]]
70
- * child tasks). This route writes to the Neon-backed `/api/fleet/queue`, but
70
+ * HS-FS7X — SUBMIT REFUSES WHILE NO RUNNER DRAINS THE NEON QUEUE.
71
+ *
72
+ * This tool writes to the Neon-backed `/api/fleet/queue`, but
71
73
  * `dev-factory/lib/fleet-queue-runner.mjs` — the only thing that actually
72
74
  * claims and executes queued work — still reads exclusively from the legacy
73
- * file-backed queue (`dev-factory/lib/fleet-queue.mjs`). A submit through this
74
- * tool therefore succeeds and is durably recorded, but nothing will ever pick
75
- * it up. Every success return carries this warning so a caller cannot mistake
76
- * "accepted" for "will run" until the runner is repointed at Neon.
75
+ * file-backed queue (`dev-factory/lib/fleet-queue.mjs`). Work submitted here
76
+ * would be durably recorded and never executed.
77
+ *
78
+ * It previously returned `{ id, position, warning }`. That was not enough: a
79
+ * warning riding along on a SUCCESS shape is still a success —
80
+ * `handleFleetQueueSubmit` sets `isError: !!result.error`, so the MCP client
81
+ * saw a green tool call and an agent that submits and moves on never learned
82
+ * the work was dropped. So the submit now REFUSES before the round trip and
83
+ * writes nothing at all — same shape as the argument refusal in
84
+ * [[HS-IRX0-5KFT-DIAG-record-wip-silently-drops-the-summary]]: refuse at the
85
+ * boundary, because a queue with no consumer cannot refuse on your behalf.
86
+ *
87
+ * Repointing the runner at Neon is a separate BUILD, already owned by
88
+ * [[HS-XOCK-60Z1-EPIC-fleet-work-queue-in-heyspex]] (HS-ZXKF: runner -> queue
89
+ * API; HS-HQP1: make fleet-queue.mjs actually select a backend). When that
90
+ * lands, delete this gate. Meanwhile a machine whose runner genuinely does
91
+ * drain Neon opts back in with HEYSPEX_FLEET_QUEUE_RUNNER_READY=1 — an
92
+ * explicit, per-machine assertion, never something a caller can set per-call.
93
+ */
94
+ export const NEON_QUEUE_RUNNER_READY_ENV = 'HEYSPEX_FLEET_QUEUE_RUNNER_READY';
95
+
96
+ export const NEON_QUEUE_NO_RUNNER_MESSAGE =
97
+ 'Refusing to submit — nothing was submitted. The Neon fleet queue has NO runner reading it: ' +
98
+ 'dev-factory/lib/fleet-queue-runner.mjs still reads only the legacy JSONL file queue ' +
99
+ '(dev-factory/lib/fleet-queue.mjs), so anything accepted here would be durably recorded and ' +
100
+ 'NEVER executed. Track: [[HS-XOCK-60Z1-EPIC-fleet-work-queue-in-heyspex]] (HS-ZXKF repoints the ' +
101
+ 'runner; HS-HQP1 makes the fleet-queue facade select a backend). To run work now, enqueue against ' +
102
+ 'dev-factory/lib/fleet-queue.mjs directly — that is the queue the runner reads. If this ' +
103
+ 'machine’s ' +
104
+ `runner really does drain the Neon queue, set ${NEON_QUEUE_RUNNER_READY_ENV}=1 to re-enable submits.`;
105
+
106
+ /**
107
+ * True only when this machine asserts its runner drains the Neon fleet queue.
108
+ * Defaults to false: the safe answer to "will this run?" is "no".
109
+ *
110
+ * @param {Record<string, string|undefined>} [env]
77
111
  */
78
- export const NEON_QUEUE_NO_RUNNER_WARNING =
79
- 'Neon fleet queue has NO runner reading it yet — dev-factory/lib/fleet-queue-runner.mjs ' +
80
- 'still only reads the legacy JSONL file queue. This item is durably recorded in Neon but ' +
81
- 'will NOT execute until HS-XOCK-60Z1 (fleet-queue Neon port) lands. Track: ' +
82
- '[[HS-XOCK-60Z1-EPIC-fleet-work-queue-in-heyspex]].';
112
+ export function neonQueueRunnerReady(env = process.env) {
113
+ const raw = env?.[NEON_QUEUE_RUNNER_READY_ENV];
114
+ if (typeof raw !== 'string') return false;
115
+ const v = raw.trim().toLowerCase();
116
+ return v === '1' || v === 'true' || v === 'yes' || v === 'on';
117
+ }
83
118
 
84
119
  /**
85
120
  * `heyspex_fleet_queue_submit` — POST /api/fleet/queue. `kind` defaults to
86
121
  * "task" when omitted (the API requires a kind string).
87
122
  *
88
- * @param {{ consoleFetch: Function, args?: Record<string, unknown>, fleetToken?: string|null }} ctx
123
+ * HS-FS7X: refuses outright (writing nothing) unless this machine asserts a
124
+ * runner actually drains the Neon queue — see NEON_QUEUE_NO_RUNNER_MESSAGE.
125
+ *
126
+ * @param {{ consoleFetch: Function, args?: Record<string, unknown>, fleetToken?: string|null, env?: Record<string, string|undefined> }} ctx
89
127
  */
90
- export async function executeFleetQueueSubmit({ consoleFetch, args = {}, fleetToken = null }) {
128
+ export async function executeFleetQueueSubmit({
129
+ consoleFetch,
130
+ args = {},
131
+ fleetToken = null,
132
+ env = process.env,
133
+ }) {
91
134
  const lane = typeof args.lane === 'string' ? args.lane.trim() : '';
92
135
  if (!lane) {
93
136
  return { error: 'INVALID_ARGS', message: 'lane is required' };
94
137
  }
138
+
139
+ // HS-FS7X: refuse BEFORE the round trip. Accepting work into a queue with no
140
+ // consumer and returning `{ id, position }` told the caller it would run.
141
+ if (!neonQueueRunnerReady(env)) {
142
+ return {
143
+ error: 'NO_RUNNER',
144
+ message: NEON_QUEUE_NO_RUNNER_MESSAGE,
145
+ lane,
146
+ submitted: false,
147
+ track: 'HS-XOCK-60Z1',
148
+ override_env: NEON_QUEUE_RUNNER_READY_ENV,
149
+ };
150
+ }
151
+
95
152
  const kind = typeof args.kind === 'string' && args.kind.trim() ? args.kind.trim() : 'task';
96
153
 
97
154
  const body = {
@@ -120,7 +177,6 @@ export async function executeFleetQueueSubmit({ consoleFetch, args = {}, fleetTo
120
177
  id: result.body?.id ?? null,
121
178
  position: result.body?.position ?? null,
122
179
  lane,
123
- warning: NEON_QUEUE_NO_RUNNER_WARNING,
124
180
  };
125
181
  }
126
182
 
@@ -0,0 +1,190 @@
1
+ // Focus pin and focus-area tools (heyspex_pin_get/pin_set, set/get/clear_focus_areas,
2
+ // resolve_focus_intent) plus the Neon focus-pin injector the on-deck brief uses. Extracted from cli.mjs under
3
+ // [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
4
+ //
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ // Focus-areas CRUD — [[HS-TASK-1GQ5]] / [[HS-EPIC-7WJN]] / [[HS-MIL-AFOC]].
7
+ // Agent-controllable roadmap focus: distinct from the on-deck pin tools above
8
+ // (heyspex_pin_get/set, the `focus_pin` table). These write the `focus_areas`
9
+ // control plane (tag-band) via the console route, which tier-dispatches Neon vs
10
+ // the free-tier `.heyspex/focus-areas.yaml`.
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+
13
+ import { consoleFetch, controlError, controlOk } from './console-transport.mjs';
14
+ import { detectAgentIdentity, getConfig } from './mcp-config.mjs';
15
+ import { buildDegradedFocusResult, resolveFocusIntentLexical } from './plan-ground.mjs';
16
+
17
+ /**
18
+ * Fetch the Neon layer-1 focus pin (the console-set fuse pin) and inject its
19
+ * spec_id into args as `neon_pin_spec_id`, so the synchronous on-deck resolver
20
+ * can let it win precedence #1 over the file's layer-2 `spec_path`.
21
+ *
22
+ * Why this exists: per [[heyspex-is-saas-cloud-primary-both-tiers]] the focus
23
+ * pin lives in Neon `focus_pin` on a Neon-up machine; the file `pin:` block is
24
+ * dormant. So vendor /hs-next — which reads the file synchronously — was blind to
25
+ * a console-set pin (the documented FCPN paid-tier soak gap). We do the one async
26
+ * read here, at the handler boundary, and thread the result down as a sync option.
27
+ *
28
+ * Best-effort: any unreachable / non-OK (incl. free-tier 402) / empty response
29
+ * leaves args untouched, so the resolver falls back to the file focus. Never
30
+ * throws, never blocks /hs-next. Skipped when `ignore_focus` or an explicit
31
+ * `spec_id` (direct lookup, no focus) is set. The GET response is
32
+ * `{ pin: FocusPinRow | null }` with camelCase keys (`pin.specId`).
33
+ */
34
+ export async function injectNeonFocusPin(args = {}) {
35
+ if (args.ignore_focus === true || args.ignore_focus === 'true') return args;
36
+ if (args.spec_id) return args;
37
+ let next = args;
38
+ try {
39
+ // Both focus layers live in Neon on a Neon-up machine (the file is dormant):
40
+ // layer-1 pin in `focus_pin`, layer-2 selection in `focus_selection`
41
+ // ([[HS-TASK-FCPN]] + [[HS-TASK-FSEL]]). Fetch both, thread the ids into the
42
+ // sync resolver. Responses are `{ pin }` / `{ selection }` with camelCase keys.
43
+ const [pinRes, selRes] = await Promise.all([
44
+ consoleFetch('/api/control/pin'),
45
+ consoleFetch('/api/control/focus-selection'),
46
+ ]);
47
+ const pinId = pinRes && pinRes.ok ? pinRes.body?.pin?.specId : null;
48
+ if (typeof pinId === 'string' && pinId) next = { ...next, neon_pin_spec_id: pinId };
49
+ const selPath = selRes && selRes.ok ? selRes.body?.selection?.specPath : null;
50
+ if (typeof selPath === 'string' && selPath) {
51
+ next = { ...next, neon_selection_spec_path: selPath };
52
+ }
53
+ } catch {
54
+ // Swallow — fall back to the file focus. Focus is a steering nicety, not a
55
+ // correctness gate; an unreachable console must not break /hs-next.
56
+ }
57
+ return next;
58
+ }
59
+
60
+ export async function handlePinGet(_args = {}) {
61
+ const result = await consoleFetch('/api/control/pin');
62
+ if (!result.ok) return controlError(result, 'PIN_GET_FAILED');
63
+ return controlOk(result.body);
64
+ }
65
+
66
+ /**
67
+ * Set or clear the on-deck pin — [[HS-EPIC-NCPS]] focus_pin / [[HS-EPIC-CLMW]]
68
+ * `hs pin set` / `hs pin clear`. PUT /api/control/pin. Omit/null
69
+ * spec_id clears (mirrors the endpoint's null-clears contract).
70
+ */
71
+ export async function handlePinSet(args = {}) {
72
+ const identity = detectAgentIdentity(args.agent, args.session_id);
73
+ const pinnedBy =
74
+ typeof args.pinned_by === 'string' && args.pinned_by.trim()
75
+ ? args.pinned_by.trim()
76
+ : identity.agent;
77
+ const clearing = args.spec_id === undefined || args.spec_id === null;
78
+ const body = clearing
79
+ ? { spec_id: null, pinned_by: pinnedBy }
80
+ : {
81
+ spec_id: args.spec_id,
82
+ pinned_by: pinnedBy,
83
+ ...(args.ttl_ms !== undefined ? { ttl_ms: args.ttl_ms } : {}),
84
+ };
85
+ const result = await consoleFetch('/api/control/pin', { method: 'PUT', body });
86
+ if (!result.ok) return controlError(result, 'PIN_SET_FAILED', { spec_id: args.spec_id ?? null });
87
+ return controlOk(result.body);
88
+ }
89
+
90
+ /** `heyspex_set_focus_areas` → PUT /api/scheduler/focus-areas. */
91
+ export async function handleSetFocusAreas(args = {}) {
92
+ const tags = Array.isArray(args.tags)
93
+ ? args.tags.map((t) => String(t).trim()).filter(Boolean)
94
+ : [];
95
+ if (tags.length === 0) {
96
+ return controlError(
97
+ { ok: false, status: 0, url: '', body: null },
98
+ 'FOCUS_AREAS_INVALID',
99
+ { reason: 'tags must be a non-empty array of known-tag strings' },
100
+ );
101
+ }
102
+ const identity = detectAgentIdentity(args.agent, args.session_id);
103
+ const body = {
104
+ rationale: typeof args.rationale === 'string' ? args.rationale : '',
105
+ tags,
106
+ updated_by: `${identity.agent} · ${identity.sessionId}`,
107
+ ...(typeof args.expires_at === 'string' ? { expires_at: args.expires_at } : {}),
108
+ ...(typeof args.replace === 'boolean' ? { replace: args.replace } : {}),
109
+ };
110
+ const result = await consoleFetch('/api/scheduler/focus-areas', { method: 'PUT', body });
111
+ if (!result.ok) return controlError(result, 'FOCUS_AREAS_SET_FAILED');
112
+ return controlOk(result.body);
113
+ }
114
+
115
+ /** `heyspex_get_focus_areas` → GET /api/scheduler/focus-areas. */
116
+ export async function handleGetFocusAreas(_args = {}) {
117
+ const result = await consoleFetch('/api/scheduler/focus-areas');
118
+ if (!result.ok) return controlError(result, 'FOCUS_AREAS_GET_FAILED');
119
+ return controlOk(result.body);
120
+ }
121
+
122
+ /** `heyspex_clear_focus_areas` → DELETE /api/scheduler/focus-areas. */
123
+ export async function handleClearFocusAreas(_args = {}) {
124
+ const result = await consoleFetch('/api/scheduler/focus-areas', { method: 'DELETE' });
125
+ if (!result.ok) return controlError(result, 'FOCUS_AREAS_CLEAR_FAILED');
126
+ return controlOk(result.body);
127
+ }
128
+
129
+ /**
130
+ * `heyspex_resolve_focus_intent` — Phase-B lexical resolver ([[HS-TASK-JZEJ]] /
131
+ * [[HS-EPIC-OOPW]]). Runs lexical wide-recall through the console search
132
+ * endpoint and returns a wide candidate window ({ spec, tags, why }[]) for the
133
+ * calling agent's LLM to rerank into focus tags — HeySpex hosts no reranker. No
134
+ * embeddings (Gate B = NO,
135
+ * [[retrieval-substrate-lexical-not-embeddings-gate-b-no]]). Retrieval is
136
+ * bounded `GET /api/search` / `GET /api/specs?tag=` pages, never a vault walk
137
+ * ([[HS-YIR1-8KK5-TASK-planning-grounding-retrieves-through]]). An
138
+ * empty/no-match window is returned (not an error) so the caller can fall back
139
+ * to heyspex_list_known_tags + exact-tag NLU.
140
+ *
141
+ * Graceful degradation ([[HS-TASK-09I3]] / focus-intent-routing §6): if the
142
+ * lexical recall primitive is unreachable or throws (console down, 401, 5xx),
143
+ * the resolver core returns a `degraded` marker rather
144
+ * than raising. The try/catch here is belt-and-suspenders — a throw outside the
145
+ * core's own guard must still resolve to a graceful degraded result so the MCP
146
+ * tool never 500s a focus pivot.
147
+ */
148
+ export async function handleResolveFocusIntent(args = {}) {
149
+ const { vaultRoot } = getConfig();
150
+ let result;
151
+ try {
152
+ result = await resolveFocusIntentLexical(vaultRoot, {
153
+ utterance: args.utterance,
154
+ top_k: args.top_k,
155
+ });
156
+ } catch (err) {
157
+ result = buildDegradedFocusResult(args.utterance, err);
158
+ }
159
+ return controlOk(result);
160
+ }
161
+
162
+ /**
163
+ * Persist the /hs-push-next layer-2 focus selection to Neon `focus_selection`
164
+ * via the console endpoint (so the console On-Deck AND vendor /hs-next, both of
165
+ * which read Neon, see it). There is NO `.heyspex/focus.yaml` fallback
166
+ * ([[no-fallbacks-by-default]]; the focus.yaml-fallback incident) — Neon is the
167
+ * only focus store. If the console is unreachable the push itself still
168
+ * succeeds (the spec files were written); the focus pin just isn't set, and we
169
+ * say so on stderr rather than writing a stale file. Returns true iff focus was
170
+ * persisted.
171
+ */
172
+ export async function writeFocusSelectionVendor(vaultRoot, specPath, reason) {
173
+ try {
174
+ const res = await consoleFetch('/api/control/focus-selection', {
175
+ method: 'PUT',
176
+ body: { spec_path: specPath, kind: 'epic', picked_by: 'human', reason: reason ?? null },
177
+ });
178
+ if (res && res.ok) return true;
179
+ console.error(
180
+ `[hs-push] focus not pinned — console returned ${res?.status ?? 'no response'} for PUT /api/control/focus-selection. ` +
181
+ 'Neon is required (no focus.yaml fallback); re-pin once the console is reachable.',
182
+ );
183
+ } catch (err) {
184
+ console.error(
185
+ `[hs-push] focus not pinned — console unreachable (${err?.message ?? err}). ` +
186
+ 'Neon is required (no focus.yaml fallback); re-pin once the console is reachable.',
187
+ );
188
+ }
189
+ return false;
190
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Machine-local folder-trust store I/O — plain ESM.
3
+ *
4
+ * `~/.heyspex/trusted-folders.json` ([[HS-ZBEB-8CMW-TASK-folder-trust-for-spexcode-first-run]])
5
+ * is read by two fronts that cannot share a TypeScript import:
6
+ * - `spex-console/src/lib/cli/folder-trust.ts` (the CLI/`hs chat`/`hs trust` home — TypeScript,
7
+ * run through tsx) — its own decision functions (`resolveFolderTrust`, `recordFolderTrust`,
8
+ * `revokeFolderTrust`, …) stay there; it imports and RE-EXPORTS the I/O functions below
9
+ * rather than re-implementing them.
10
+ * - `vendor/heyspex-mcp/config-settings.mjs` (the settings capability's core — plain ESM, no
11
+ * TypeScript toolchain available) — reads this store directly to answer
12
+ * `heyspex_settings_get("agent_behaviors.spexcode.trusted_folders")`
13
+ * ([[HS-25AJ-8CMW-TASK-folder-trust-revoke-through-the]]).
14
+ *
15
+ * This file is the ONE place this I/O is implemented — never duplicated. Storage contract
16
+ * (realpath-keyed, `{ trusted, decidedAt }` entries) is documented in full in folder-trust.ts's
17
+ * own header; this file only carries the read/write/list primitives, not the trust decision
18
+ * logic itself.
19
+ */
20
+ import { heyspexHome } from '../../scripts/lib/heyspex-home.mjs';
21
+ import fs from 'node:fs';
22
+ import os from 'node:os';
23
+ import path from 'node:path';
24
+
25
+ /** `~/.heyspex/trusted-folders.json`, relative to a home directory. */
26
+ export const TRUST_STORE_REL = path.join('.heyspex', 'trusted-folders.json');
27
+
28
+ /** Resolve the real trust-store path for a given home directory (defaults to `os.homedir()`). */
29
+ export function trustStorePath(home = heyspexHome()) {
30
+ return path.join(home, TRUST_STORE_REL);
31
+ }
32
+
33
+ /** Read the trust store from disk. A missing/corrupt/unreadable file reads as an empty store
34
+ * (fail open to "nothing answered yet", never a crash). */
35
+ export function readTrustStore(filePath = trustStorePath()) {
36
+ try {
37
+ const raw = fs.readFileSync(filePath, 'utf8');
38
+ const parsed = JSON.parse(raw);
39
+ return parsed && typeof parsed === 'object' ? parsed : {};
40
+ } catch {
41
+ return {};
42
+ }
43
+ }
44
+
45
+ /** Write the trust store to disk, creating `~/.heyspex/` if needed. */
46
+ export function writeTrustStore(store, filePath = trustStorePath()) {
47
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
48
+ fs.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}\n`, 'utf8');
49
+ }
50
+
51
+ /** List every stored trust decision as a flat array — the read-only listing surface both
52
+ * `hs trust list` and the `agent_behaviors.spexcode.trusted_folders` settings-capability
53
+ * computed key format their output from. */
54
+ export function listTrustedFolders(store) {
55
+ return Object.entries(store).map(([key, entry]) => ({
56
+ path: key,
57
+ trusted: entry.trusted,
58
+ decidedAt: entry.decidedAt,
59
+ }));
60
+ }