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,371 @@
1
+ // spexharness/src/doctrine/behaviour.mjs
2
+ //
3
+ // DR1 addendum (owner request, 2026-09-11, mid-build on
4
+ // [[HS-TWTC-8CMW-TASK-dr1-doctrine-corpus-reader-stream-every]]) — folds HeySpex's OWN
5
+ // historical session logs into the Doctrine corpus, as structured BEHAVIOUR evidence
6
+ // rather than transcript text: how sessions actually ran (tools called, gate
7
+ // refusals/blocks, claims made/released, specs advanced), not what the owner said.
8
+ //
9
+ // Two sources, both aggregated (never one observation per raw event — that would be
10
+ // tens of thousands of near-noise rows):
11
+ // - `heyspex-events` — the vault's own event stream, <vault>/specs/events/*.jsonl
12
+ // (`{timestamp, event_kind, actor, spec_id, metadata}`; session id lives at
13
+ // `metadata.sessionId` or `metadata.session_id` depending on the writer).
14
+ // - `heyspex-sesslog` — this repo's local hook-capture sidecars under `.heyspex/`:
15
+ // sesslog-history.jsonl (agent.tool_called, the SAME envelope shape), plus
16
+ // harness-audit.jsonl, guardrail-history.jsonl, claim-history.jsonl, and
17
+ // intent-classifier-history.jsonl — each its own shape; see
18
+ // {@link normalizeBehaviourRecord}.
19
+ // A third, OPTIONAL source — the console's Neon-indexed `/api/agent-sessions` — is
20
+ // folded in as `heyspex-agent-sessions` when the console answers; a down/erroring
21
+ // console is skipped with a note, never a hard failure (see {@link fetchAgentSessionsSummaries}).
22
+ //
23
+ // Grouping: every normalized record is bucketed by (sessionId ?? 'unknown', UTC day)
24
+ // and rendered into ONE `kind: 'behaviour'` observation per bucket — a compact,
25
+ // redacted, <=600-char summary (tool counts, guardrail rule names, claim/spec
26
+ // activity) — never the raw per-event stream.
27
+ //
28
+ // Unlike corpus.mjs's per-line incremental sources, these are FULL-RECOMPUTE every
29
+ // `hs doctrine extract` run: the source files are relatively small (event-stream +
30
+ // sidecars, not multi-hundred-MB session transcripts) and aggregation naturally spans
31
+ // many lines into one row, which does not compose with a line-offset resume. Their
32
+ // output lands in its own file ({@link defaultBehaviourObservationsPath}), REWRITTEN
33
+ // wholesale each run (never appended to), so a re-run never accumulates duplicate
34
+ // summaries the way an append-only log would.
35
+ //
36
+ // Streaming throughout: every file is read via `node:readline` over a
37
+ // `createReadStream`, never a whole-file `readFileSync` of the larger event-stream
38
+ // months (2026-07/08 run tens of MB).
39
+
40
+ import { createReadStream, mkdirSync, writeFileSync } from 'node:fs';
41
+ // The ONE shared console-URL resolver ([[HS-GD45-MCPC-TASK-mcp-cloud-auth-shared-console-resolver]])
42
+ // — replaces this module's own hard-wired `http://localhost:3000` default with
43
+ // env → ~/.heyspex/credentials.yaml console_url → the mode-derived default (no
44
+ // `repoRoot` here, so the admin-local discovery-file rung is never consulted).
45
+ import { resolveConsoleUrl as resolveConsoleUrlShared } from '../../../scripts/lib/console-url-resolver.mjs';
46
+ import { createInterface } from 'node:readline';
47
+ import { dirname, join } from 'node:path';
48
+
49
+ import { redactText } from '../redact.mjs';
50
+
51
+ export const HEYSPEX_EVENTS_SOURCE = 'heyspex-events';
52
+ export const HEYSPEX_SESSLOG_SOURCE = 'heyspex-sesslog';
53
+ export const HEYSPEX_AGENT_SESSIONS_SOURCE = 'heyspex-agent-sessions';
54
+
55
+ const MAX_SUMMARY_CHARS = 600;
56
+
57
+ export function defaultVaultEventsDir(vaultRoot) {
58
+ return join(vaultRoot, 'specs', 'events');
59
+ }
60
+
61
+ /** The local hook-capture sidecars under `.heyspex/` this module reads (read-only — never written). */
62
+ export function defaultSesslogFiles(repoRoot) {
63
+ return [
64
+ join(repoRoot, '.heyspex', 'sesslog-history.jsonl'),
65
+ join(repoRoot, '.heyspex', 'harness-audit.jsonl'),
66
+ join(repoRoot, '.heyspex', 'guardrail-history.jsonl'),
67
+ join(repoRoot, '.heyspex', 'claim-history.jsonl'),
68
+ join(repoRoot, '.heyspex', 'intent-classifier-history.jsonl'),
69
+ ];
70
+ }
71
+
72
+ export function defaultBehaviourObservationsPath(repoRoot) {
73
+ return join(repoRoot, '.heyspex', 'doctrine', 'behaviour-observations.jsonl');
74
+ }
75
+
76
+ /**
77
+ * Overwrite the behaviour-observations file wholesale (never appended — see module
78
+ * header: these sources are full-recompute every run, so the file always reflects
79
+ * exactly this run's aggregation, never a union with a stale prior run's rows).
80
+ * @param {string} path
81
+ * @param {import('./corpus.mjs').DoctrineObservation[]} observations
82
+ */
83
+ export function writeBehaviourObservations(path, observations) {
84
+ mkdirSync(dirname(path), { recursive: true });
85
+ const text = observations.map((o) => JSON.stringify(o)).join('\n');
86
+ writeFileSync(path, observations.length ? `${text}\n` : '');
87
+ }
88
+
89
+ /** Yield one parsed JSON record per non-blank line of `filePath`. Missing file → nothing (no throw). */
90
+ async function* streamJsonlRecords(filePath) {
91
+ let stream;
92
+ try {
93
+ stream = createReadStream(filePath, { encoding: 'utf8' });
94
+ } catch {
95
+ return;
96
+ }
97
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
98
+ try {
99
+ for await (const line of rl) {
100
+ const trimmed = line.trim();
101
+ if (!trimmed) continue;
102
+ try {
103
+ yield JSON.parse(trimmed);
104
+ } catch {
105
+ // corrupt/partial line — skip, don't throw
106
+ }
107
+ }
108
+ } catch (err) {
109
+ if (err && (err.code === 'ENOENT' || err.code === 'EISDIR')) return; // missing/odd path — silently empty
110
+ throw err;
111
+ } finally {
112
+ rl.close();
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Normalize one raw record — from the vault's `{timestamp, event_kind, actor,
118
+ * spec_id, metadata}` envelope (shared by `specs/events/*.jsonl` AND
119
+ * sesslog-history.jsonl/guardrail-history.jsonl, which use the SAME shape) or one of
120
+ * the other three sidecars' own shapes — into `{ ts, sessionId, kind, detail }`.
121
+ * Returns `null` for a record with no usable timestamp (nothing to bucket by day).
122
+ *
123
+ * @param {unknown} rec
124
+ * @returns {{ ts: string, sessionId: string|null, kind: string, detail: string }|null}
125
+ */
126
+ export function normalizeBehaviourRecord(rec) {
127
+ if (!rec || typeof rec !== 'object') return null;
128
+
129
+ // claim-history.jsonl: { specId, agent, sessionId, sessionShort, claimedAt, releasedAt, status }
130
+ if (typeof rec.specId === 'string' && typeof rec.claimedAt === 'string' && rec.event_kind === undefined) {
131
+ return {
132
+ ts: rec.releasedAt ?? rec.claimedAt,
133
+ sessionId: typeof rec.sessionId === 'string' ? rec.sessionId : null,
134
+ kind: rec.releasedAt ? 'claim.released' : 'claim.acquired',
135
+ detail: rec.specId,
136
+ };
137
+ }
138
+
139
+ // harness-audit.jsonl: { timestamp, actor, action, target, workspace, mode }
140
+ if (typeof rec.action === 'string' && typeof rec.actor === 'string' && rec.event_kind === undefined) {
141
+ if (typeof rec.timestamp !== 'string') return null;
142
+ return { ts: rec.timestamp, sessionId: null, kind: `audit.${rec.action}`, detail: typeof rec.target === 'string' ? rec.target : '' };
143
+ }
144
+
145
+ // intent-classifier-history.jsonl: { timestamp, event_kind: 'intent_classifier.decision', decision, ... }
146
+ if (rec.event_kind === 'intent_classifier.decision') {
147
+ if (typeof rec.timestamp !== 'string') return null;
148
+ return { ts: rec.timestamp, sessionId: null, kind: 'intent-classifier.decision', detail: typeof rec.decision === 'string' ? rec.decision : '' };
149
+ }
150
+
151
+ // The shared envelope: vault specs/events/*.jsonl, sesslog-history.jsonl, guardrail-history.jsonl.
152
+ if (typeof rec.event_kind === 'string') {
153
+ if (typeof rec.timestamp !== 'string') return null;
154
+ const meta = rec && typeof rec.metadata === 'object' && rec.metadata ? rec.metadata : {};
155
+ const sessionId = typeof meta.sessionId === 'string' ? meta.sessionId : typeof meta.session_id === 'string' ? meta.session_id : null;
156
+ let detail = '';
157
+ if (rec.event_kind === 'agent.tool_called') detail = typeof meta.tool_name === 'string' ? meta.tool_name : '';
158
+ else if (rec.event_kind === 'guardrail.fired') detail = typeof meta.rule === 'string' ? meta.rule : '';
159
+ else if (typeof rec.spec_id === 'string') detail = rec.spec_id;
160
+ return { ts: rec.timestamp, sessionId, kind: rec.event_kind, detail };
161
+ }
162
+
163
+ return null;
164
+ }
165
+
166
+ function dayOf(ts) {
167
+ return typeof ts === 'string' && ts.length >= 10 ? ts.slice(0, 10) : 'unknown-day';
168
+ }
169
+
170
+ /**
171
+ * Bucket normalized records by (sessionId ?? 'unknown', UTC day).
172
+ * @param {Array<{ ts: string, sessionId: string|null, kind: string, detail: string }>} records
173
+ * @returns {Array<{ sessionId: string, day: string, kindCounts: Map<string,number>, toolCounts: Map<string,number>, guardrailCounts: Map<string,number>, specs: Set<string>, firstTs: string, lastTs: string }>}
174
+ */
175
+ export function groupBehaviourRecords(records) {
176
+ const groups = new Map();
177
+ for (const r of records) {
178
+ if (!r || !r.ts) continue;
179
+ const sessionId = r.sessionId ?? 'unknown';
180
+ const day = dayOf(r.ts);
181
+ const key = `${sessionId}::${day}`;
182
+ let g = groups.get(key);
183
+ if (!g) {
184
+ g = { sessionId, day, kindCounts: new Map(), toolCounts: new Map(), guardrailCounts: new Map(), specs: new Set(), firstTs: r.ts, lastTs: r.ts };
185
+ groups.set(key, g);
186
+ }
187
+ g.kindCounts.set(r.kind, (g.kindCounts.get(r.kind) ?? 0) + 1);
188
+ if (r.kind === 'agent.tool_called' && r.detail) g.toolCounts.set(r.detail, (g.toolCounts.get(r.detail) ?? 0) + 1);
189
+ if (r.kind === 'guardrail.fired' && r.detail) g.guardrailCounts.set(r.detail, (g.guardrailCounts.get(r.detail) ?? 0) + 1);
190
+ if (r.detail && (r.kind.startsWith('spec.') || r.kind.startsWith('claim.') || r.kind === 'focus.set')) g.specs.add(r.detail);
191
+ if (r.ts < g.firstTs) g.firstTs = r.ts;
192
+ if (r.ts > g.lastTs) g.lastTs = r.ts;
193
+ }
194
+ return [...groups.values()];
195
+ }
196
+
197
+ function topEntries(map, limit) {
198
+ return [...map.entries()]
199
+ .sort((a, b) => b[1] - a[1])
200
+ .slice(0, limit)
201
+ .map(([k, v]) => `${k}×${v}`)
202
+ .join(', ');
203
+ }
204
+
205
+ /**
206
+ * Render one bucket into a compact (<=600 char) summary: tools called with counts,
207
+ * guardrail refusals/blocks with rule names, claims made/released and specs advanced,
208
+ * and the session's spec ids.
209
+ * @param {ReturnType<typeof groupBehaviourRecords>[number]} g
210
+ * @returns {string}
211
+ */
212
+ export function summarizeBehaviourGroup(g) {
213
+ const parts = [];
214
+ if (g.toolCounts.size) parts.push(`tools: ${topEntries(g.toolCounts, 8)}`);
215
+ if (g.guardrailCounts.size) parts.push(`guardrail blocks: ${topEntries(g.guardrailCounts, 8)}`);
216
+ const acquired = g.kindCounts.get('claim.acquired') ?? 0;
217
+ const released = g.kindCounts.get('claim.released') ?? 0;
218
+ if (acquired || released) parts.push(`claims: ${acquired} acquired, ${released} released`);
219
+ const statusChanges = g.kindCounts.get('spec.status_change') ?? 0;
220
+ if (statusChanges) parts.push(`${statusChanges} status change(s)`);
221
+ const permissionEvents = [...g.kindCounts.keys()].filter((k) => k.startsWith('audit.'));
222
+ if (permissionEvents.length) parts.push(`audit: ${permissionEvents.map((k) => `${k}×${g.kindCounts.get(k)}`).join(', ')}`);
223
+ if (g.specs.size) parts.push(`specs: ${[...g.specs].slice(0, 12).join(', ')}`);
224
+
225
+ let text = `Session ${g.sessionId} on ${g.day} — ${parts.length ? parts.join('; ') : 'no notable activity'}.`;
226
+ if (text.length > MAX_SUMMARY_CHARS) text = `${text.slice(0, MAX_SUMMARY_CHARS - 1)}…`;
227
+ return text;
228
+ }
229
+
230
+ /**
231
+ * Build `kind: 'behaviour'` observations from a flat list of normalized records —
232
+ * one per (session, day) bucket, sorted deterministically (day, then session).
233
+ * @param {Array<{ ts: string, sessionId: string|null, kind: string, detail: string }>} records
234
+ * @param {{ source: string, project: string, redact?: (text: string) => string }} opts
235
+ * @returns {import('./corpus.mjs').DoctrineObservation[]}
236
+ */
237
+ export function buildBehaviourObservations(records, { source, project, redact = redactText }) {
238
+ const groups = groupBehaviourRecords(records).sort((a, b) => a.day.localeCompare(b.day) || a.sessionId.localeCompare(b.sessionId));
239
+ return groups.map((g, i) => ({
240
+ id: `${source}:${project}:${g.sessionId}:${g.day}`,
241
+ source,
242
+ project,
243
+ session: g.sessionId,
244
+ ts: g.lastTs,
245
+ seq: i + 1,
246
+ kind: 'behaviour',
247
+ text: redact(summarizeBehaviourGroup(g)),
248
+ }));
249
+ }
250
+
251
+ /** Stream every `*.jsonl` file directly under `dir` (non-recursive — the vault's events dir is flat). */
252
+ async function collectFromDir(dir) {
253
+ const { readdirSync } = await import('node:fs');
254
+ let entries = [];
255
+ try {
256
+ entries = readdirSync(dir, { withFileTypes: true });
257
+ } catch {
258
+ return { records: [], filesRead: 0, linesRead: 0 };
259
+ }
260
+ const records = [];
261
+ let filesRead = 0;
262
+ let linesRead = 0;
263
+ for (const ent of entries) {
264
+ if (!ent.isFile() || !ent.name.endsWith('.jsonl')) continue;
265
+ filesRead++;
266
+ for await (const rec of streamJsonlRecords(join(dir, ent.name))) {
267
+ linesRead++;
268
+ const normalized = normalizeBehaviourRecord(rec);
269
+ if (normalized) records.push(normalized);
270
+ }
271
+ }
272
+ return { records, filesRead, linesRead };
273
+ }
274
+
275
+ async function collectFromFiles(paths) {
276
+ const records = [];
277
+ let filesRead = 0;
278
+ let linesRead = 0;
279
+ for (const p of paths) {
280
+ filesRead++;
281
+ for await (const rec of streamJsonlRecords(p)) {
282
+ linesRead++;
283
+ const normalized = normalizeBehaviourRecord(rec);
284
+ if (normalized) records.push(normalized);
285
+ }
286
+ }
287
+ return { records, filesRead, linesRead };
288
+ }
289
+
290
+ /**
291
+ * Collect + aggregate BOTH behaviour sources (`heyspex-events`, `heyspex-sesslog`).
292
+ * Full-recompute every call — see module header. Never throws on a missing file/dir
293
+ * (silently empty).
294
+ *
295
+ * @param {{ vaultEventsDir: string, sesslogFiles: string[], project: string, redact?: (text: string) => string }} opts
296
+ * @returns {Promise<{ observations: import('./corpus.mjs').DoctrineObservation[], countsBySource: Record<string, number>, stats: Record<string, { filesRead: number, linesRead: number }> }>}
297
+ */
298
+ export async function collectBehaviourObservations({ vaultEventsDir, sesslogFiles, project, redact = redactText }) {
299
+ const events = await collectFromDir(vaultEventsDir);
300
+ const sesslog = await collectFromFiles(sesslogFiles);
301
+
302
+ const eventsObs = buildBehaviourObservations(events.records, { source: HEYSPEX_EVENTS_SOURCE, project, redact });
303
+ const sesslogObs = buildBehaviourObservations(sesslog.records, { source: HEYSPEX_SESSLOG_SOURCE, project, redact });
304
+
305
+ return {
306
+ observations: [...eventsObs, ...sesslogObs],
307
+ countsBySource: { [HEYSPEX_EVENTS_SOURCE]: eventsObs.length, [HEYSPEX_SESSLOG_SOURCE]: sesslogObs.length },
308
+ stats: {
309
+ [HEYSPEX_EVENTS_SOURCE]: { filesRead: events.filesRead, linesRead: events.linesRead },
310
+ [HEYSPEX_SESSLOG_SOURCE]: { filesRead: sesslog.filesRead, linesRead: sesslog.linesRead },
311
+ },
312
+ };
313
+ }
314
+
315
+ /**
316
+ * OPTIONAL third source: the console's Neon-indexed `/api/agent-sessions`. A down
317
+ * console, a non-2xx response, or any network failure is reported as `{ skipped: true,
318
+ * reason }` — never thrown — per the owner's "if it 500s, skip with a note" ask.
319
+ *
320
+ * @param {{ baseUrl?: string, project?: string, redact?: (text: string) => string, fetchFn?: typeof fetch, timeoutMs?: number }} [opts]
321
+ * @returns {Promise<{ observations: import('./corpus.mjs').DoctrineObservation[], skipped: boolean, reason?: string }>}
322
+ */
323
+ export async function fetchAgentSessionsSummaries({
324
+ baseUrl = resolveConsoleUrlShared(),
325
+ project = 'heyspex-app',
326
+ redact = redactText,
327
+ fetchFn = typeof fetch === 'function' ? fetch : null,
328
+ timeoutMs = 3000,
329
+ } = {}) {
330
+ if (!fetchFn) return { observations: [], skipped: true, reason: 'no fetch implementation available' };
331
+ const ac = new AbortController();
332
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
333
+ let res;
334
+ try {
335
+ res = await fetchFn(`${baseUrl.replace(/\/$/, '')}/api/agent-sessions`, { signal: ac.signal });
336
+ } catch (err) {
337
+ return { observations: [], skipped: true, reason: `unreachable: ${err?.message ?? String(err)}` };
338
+ } finally {
339
+ clearTimeout(timer);
340
+ }
341
+ if (!res || !res.ok) {
342
+ return { observations: [], skipped: true, reason: `console responded ${res?.status ?? 'with no status'}` };
343
+ }
344
+ let body;
345
+ try {
346
+ body = await res.json();
347
+ } catch (err) {
348
+ return { observations: [], skipped: true, reason: `unparseable response: ${err?.message ?? String(err)}` };
349
+ }
350
+ const sessions = Array.isArray(body) ? body : Array.isArray(body?.sessions) ? body.sessions : [];
351
+ if (!sessions.length) return { observations: [], skipped: false };
352
+
353
+ const observations = sessions.map((s, i) => {
354
+ const sessionId = s.sessionId ?? s.id ?? `session-${i}`;
355
+ const ts = s.updatedAt ?? s.startedAt ?? s.createdAt ?? new Date().toISOString();
356
+ const specs = Array.isArray(s.specIds) ? s.specIds : Array.isArray(s.specs) ? s.specs : [];
357
+ let text = `Console-indexed session ${sessionId} — status ${s.status ?? 'unknown'}${specs.length ? `, specs: ${specs.slice(0, 12).join(', ')}` : ''}.`;
358
+ if (text.length > MAX_SUMMARY_CHARS) text = `${text.slice(0, MAX_SUMMARY_CHARS - 1)}…`;
359
+ return {
360
+ id: `${HEYSPEX_AGENT_SESSIONS_SOURCE}:${project}:${sessionId}:${dayOf(ts)}`,
361
+ source: HEYSPEX_AGENT_SESSIONS_SOURCE,
362
+ project,
363
+ session: sessionId,
364
+ ts,
365
+ seq: i + 1,
366
+ kind: 'behaviour',
367
+ text: redact(text),
368
+ };
369
+ });
370
+ return { observations, skipped: false };
371
+ }