spexcode 0.6.2 → 0.6.6

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 (495) hide show
  1. package/README.md +3 -1
  2. package/bin/spex.mjs +4 -0
  3. package/node_modules/@spexcode/session-core/dist/delivery-queue.d.ts +23 -0
  4. package/node_modules/@spexcode/session-core/dist/delivery-queue.js +195 -0
  5. package/node_modules/@spexcode/session-core/dist/index.d.ts +5 -0
  6. package/node_modules/@spexcode/session-core/dist/index.js +5 -0
  7. package/node_modules/@spexcode/session-core/dist/internal.d.ts +3 -0
  8. package/node_modules/@spexcode/session-core/dist/internal.js +3 -0
  9. package/node_modules/@spexcode/session-core/dist/message.d.ts +22 -0
  10. package/node_modules/@spexcode/session-core/dist/message.js +52 -0
  11. package/node_modules/@spexcode/session-core/dist/record-lock.d.ts +7 -0
  12. package/node_modules/@spexcode/session-core/dist/record-lock.js +152 -0
  13. package/node_modules/@spexcode/session-core/dist/runtime-session.d.ts +50 -0
  14. package/node_modules/@spexcode/session-core/dist/runtime-session.js +286 -0
  15. package/node_modules/@spexcode/session-core/dist/session-cursors.d.ts +14 -0
  16. package/node_modules/@spexcode/session-core/dist/session-cursors.js +82 -0
  17. package/node_modules/@spexcode/session-core/dist/session-timeline.d.ts +46 -0
  18. package/node_modules/@spexcode/session-core/dist/session-timeline.js +216 -0
  19. package/node_modules/@spexcode/session-core/package.json +33 -0
  20. package/node_modules/@spexcode/spec-cli/bin/spex.mjs +77 -0
  21. package/node_modules/@spexcode/spec-cli/dist/attach.d.ts +2 -0
  22. package/node_modules/@spexcode/spec-cli/dist/attach.js +48 -0
  23. package/node_modules/@spexcode/spec-cli/dist/claude-headless.d.ts +36 -0
  24. package/node_modules/@spexcode/spec-cli/dist/claude-headless.js +316 -0
  25. package/node_modules/@spexcode/spec-cli/dist/cli.d.ts +1 -0
  26. package/node_modules/@spexcode/spec-cli/dist/cli.js +1908 -0
  27. package/node_modules/@spexcode/spec-cli/dist/client.d.ts +112 -0
  28. package/node_modules/@spexcode/spec-cli/dist/client.js +481 -0
  29. package/node_modules/@spexcode/spec-cli/dist/cockpit.d.ts +7 -0
  30. package/node_modules/@spexcode/spec-cli/dist/cockpit.js +26 -0
  31. package/node_modules/@spexcode/spec-cli/dist/codex-headless.d.ts +1 -0
  32. package/{spec-cli/src/codex-headless.ts → node_modules/@spexcode/spec-cli/dist/codex-headless.js} +3 -9
  33. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +58 -0
  34. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +798 -0
  35. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.d.ts +1 -0
  36. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.js +90 -0
  37. package/node_modules/@spexcode/spec-cli/dist/contract-filter.d.ts +14 -0
  38. package/node_modules/@spexcode/spec-cli/dist/contract-filter.js +219 -0
  39. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.d.ts +6 -0
  40. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.js +50 -0
  41. package/node_modules/@spexcode/spec-cli/dist/doctor.d.ts +9 -0
  42. package/node_modules/@spexcode/spec-cli/dist/doctor.js +553 -0
  43. package/node_modules/@spexcode/spec-cli/dist/eval-host.d.ts +1 -0
  44. package/node_modules/@spexcode/spec-cli/dist/eval-host.js +19 -0
  45. package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +26 -0
  46. package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +475 -0
  47. package/node_modules/@spexcode/spec-cli/dist/file-write.d.ts +2 -0
  48. package/node_modules/@spexcode/spec-cli/dist/file-write.js +26 -0
  49. package/node_modules/@spexcode/spec-cli/dist/flat.d.ts +89 -0
  50. package/node_modules/@spexcode/spec-cli/dist/flat.js +1076 -0
  51. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.d.ts +56 -0
  52. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.js +168 -0
  53. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.d.ts +28 -0
  54. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +363 -0
  55. package/node_modules/@spexcode/spec-cli/dist/gateway.d.ts +38 -0
  56. package/node_modules/@spexcode/spec-cli/dist/gateway.js +527 -0
  57. package/node_modules/@spexcode/spec-cli/dist/graphCache.d.ts +27 -0
  58. package/node_modules/@spexcode/spec-cli/dist/graphCache.js +717 -0
  59. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.d.ts +14 -0
  60. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +36 -0
  61. package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +85 -0
  62. package/node_modules/@spexcode/spec-cli/dist/graphStream.js +1195 -0
  63. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.d.ts +64 -0
  64. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.js +166 -0
  65. package/node_modules/@spexcode/spec-cli/dist/guide.d.ts +8 -0
  66. package/{spec-cli/src/guide.ts → node_modules/@spexcode/spec-cli/dist/guide.js} +36 -32
  67. package/node_modules/@spexcode/spec-cli/dist/harness-select.d.ts +18 -0
  68. package/node_modules/@spexcode/spec-cli/dist/harness-select.js +62 -0
  69. package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +338 -0
  70. package/node_modules/@spexcode/spec-cli/dist/harness.js +3146 -0
  71. package/node_modules/@spexcode/spec-cli/dist/headless-controller.d.ts +9 -0
  72. package/node_modules/@spexcode/spec-cli/dist/headless-controller.js +47 -0
  73. package/node_modules/@spexcode/spec-cli/dist/help.d.ts +15 -0
  74. package/node_modules/@spexcode/spec-cli/dist/help.js +594 -0
  75. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.d.ts +18 -0
  76. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +122 -0
  77. package/node_modules/@spexcode/spec-cli/dist/hooks.d.ts +1 -0
  78. package/{spec-cli/src/hooks.ts → node_modules/@spexcode/spec-cli/dist/hooks.js} +15 -15
  79. package/node_modules/@spexcode/spec-cli/dist/host-resources.d.ts +97 -0
  80. package/node_modules/@spexcode/spec-cli/dist/host-resources.js +795 -0
  81. package/node_modules/@spexcode/spec-cli/dist/host.d.ts +88 -0
  82. package/node_modules/@spexcode/spec-cli/dist/host.js +663 -0
  83. package/node_modules/@spexcode/spec-cli/dist/index.d.ts +1 -0
  84. package/node_modules/@spexcode/spec-cli/dist/index.js +899 -0
  85. package/node_modules/@spexcode/spec-cli/dist/init.d.ts +1 -0
  86. package/node_modules/@spexcode/spec-cli/dist/init.js +284 -0
  87. package/node_modules/@spexcode/spec-cli/dist/issues-cli.d.ts +6 -0
  88. package/node_modules/@spexcode/spec-cli/dist/issues-cli.js +334 -0
  89. package/node_modules/@spexcode/spec-cli/dist/issues.d.ts +69 -0
  90. package/node_modules/@spexcode/spec-cli/dist/issues.js +189 -0
  91. package/node_modules/@spexcode/spec-cli/dist/lint.d.ts +34 -0
  92. package/node_modules/@spexcode/spec-cli/dist/lint.js +476 -0
  93. package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +8 -0
  94. package/node_modules/@spexcode/spec-cli/dist/listen.js +24 -0
  95. package/node_modules/@spexcode/spec-cli/dist/localIssues.d.ts +65 -0
  96. package/node_modules/@spexcode/spec-cli/dist/localIssues.js +582 -0
  97. package/node_modules/@spexcode/spec-cli/dist/login-page.d.ts +5 -0
  98. package/{spec-cli/src/login-page.ts → node_modules/@spexcode/spec-cli/dist/login-page.js} +7 -7
  99. package/node_modules/@spexcode/spec-cli/dist/loop-in.d.ts +23 -0
  100. package/{spec-cli/src/loop-in.ts → node_modules/@spexcode/spec-cli/dist/loop-in.js} +35 -45
  101. package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +69 -0
  102. package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +603 -0
  103. package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +18 -0
  104. package/node_modules/@spexcode/spec-cli/dist/materialize.js +468 -0
  105. package/node_modules/@spexcode/spec-cli/dist/mentions.d.ts +50 -0
  106. package/node_modules/@spexcode/spec-cli/dist/mentions.js +117 -0
  107. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.d.ts +5 -0
  108. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.js +215 -0
  109. package/node_modules/@spexcode/spec-cli/dist/opencode.d.ts +3 -0
  110. package/{spec-cli/src/opencode.ts → node_modules/@spexcode/spec-cli/dist/opencode.js} +9 -13
  111. package/node_modules/@spexcode/spec-cli/dist/pi-harness.d.ts +3 -0
  112. package/{spec-cli/src/pi-harness.ts → node_modules/@spexcode/spec-cli/dist/pi-harness.js} +45 -40
  113. package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +25 -0
  114. package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +196 -0
  115. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.d.ts +21 -0
  116. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.js +146 -0
  117. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.d.ts +10 -0
  118. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +404 -0
  119. package/node_modules/@spexcode/spec-cli/dist/pty-helper.d.mts +1 -0
  120. package/node_modules/@spexcode/spec-cli/dist/pty-helper.mjs +101 -0
  121. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.d.mts +2 -0
  122. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.mjs +23 -0
  123. package/node_modules/@spexcode/spec-cli/dist/public-graph.d.ts +46 -0
  124. package/node_modules/@spexcode/spec-cli/dist/public-graph.js +56 -0
  125. package/node_modules/@spexcode/spec-cli/dist/ranker.d.ts +15 -0
  126. package/node_modules/@spexcode/spec-cli/dist/ranker.js +188 -0
  127. package/node_modules/@spexcode/spec-cli/dist/reaper.d.ts +6 -0
  128. package/node_modules/@spexcode/spec-cli/dist/reaper.js +81 -0
  129. package/node_modules/@spexcode/spec-cli/dist/reviews.d.ts +189 -0
  130. package/node_modules/@spexcode/spec-cli/dist/reviews.js +350 -0
  131. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.d.ts +6 -0
  132. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.js +37 -0
  133. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +21 -0
  134. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +84 -0
  135. package/node_modules/@spexcode/spec-cli/dist/runtime-rotate.d.ts +1 -0
  136. package/node_modules/@spexcode/spec-cli/dist/runtime-rotate.js +58 -0
  137. package/node_modules/@spexcode/spec-cli/dist/search.bench.d.mts +1 -0
  138. package/node_modules/@spexcode/spec-cli/dist/search.bench.mjs +75 -0
  139. package/node_modules/@spexcode/spec-cli/dist/search.d.ts +20 -0
  140. package/node_modules/@spexcode/spec-cli/dist/search.js +61 -0
  141. package/node_modules/@spexcode/spec-cli/dist/session-declarations.d.ts +13 -0
  142. package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +126 -0
  143. package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +10 -0
  144. package/node_modules/@spexcode/spec-cli/dist/session-execution.js +70 -0
  145. package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +28 -0
  146. package/node_modules/@spexcode/spec-cli/dist/session-files.js +130 -0
  147. package/node_modules/@spexcode/spec-cli/dist/session-follow.d.ts +31 -0
  148. package/node_modules/@spexcode/spec-cli/dist/session-follow.js +179 -0
  149. package/node_modules/@spexcode/spec-cli/dist/session-reparent.d.ts +10 -0
  150. package/node_modules/@spexcode/spec-cli/dist/session-reparent.js +33 -0
  151. package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +11 -0
  152. package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +20 -0
  153. package/node_modules/@spexcode/spec-cli/dist/session-web.d.ts +24 -0
  154. package/node_modules/@spexcode/spec-cli/dist/session-web.js +141 -0
  155. package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +433 -0
  156. package/node_modules/@spexcode/spec-cli/dist/sessions.js +4667 -0
  157. package/node_modules/@spexcode/spec-cli/dist/sh.d.ts +1 -0
  158. package/node_modules/@spexcode/spec-cli/dist/sh.js +3 -0
  159. package/node_modules/@spexcode/spec-cli/dist/shim-runtime.d.ts +1 -0
  160. package/{spec-cli/src/shim-runtime.ts → node_modules/@spexcode/spec-cli/dist/shim-runtime.js} +3 -4
  161. package/node_modules/@spexcode/spec-cli/dist/slash-commands.d.ts +9 -0
  162. package/node_modules/@spexcode/spec-cli/dist/slash-commands.js +307 -0
  163. package/node_modules/@spexcode/spec-cli/dist/source-files.d.ts +9 -0
  164. package/node_modules/@spexcode/spec-cli/dist/source-files.js +113 -0
  165. package/node_modules/@spexcode/spec-cli/dist/supervise.d.ts +1 -0
  166. package/node_modules/@spexcode/spec-cli/dist/supervise.js +308 -0
  167. package/node_modules/@spexcode/spec-cli/dist/tree.d.ts +25 -0
  168. package/node_modules/@spexcode/spec-cli/dist/tree.js +98 -0
  169. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.d.ts +3 -0
  170. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.js +41 -0
  171. package/node_modules/@spexcode/spec-cli/dist/uninstall.d.ts +3 -0
  172. package/node_modules/@spexcode/spec-cli/dist/uninstall.js +177 -0
  173. package/node_modules/@spexcode/spec-cli/dist/uploads.d.ts +36 -0
  174. package/node_modules/@spexcode/spec-cli/dist/uploads.js +243 -0
  175. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.d.ts +1 -0
  176. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.js +47 -0
  177. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/dispatch.sh +5 -5
  178. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/harness.sh +1 -1
  179. package/node_modules/@spexcode/spec-cli/package.json +49 -0
  180. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
  181. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec-first.sh +1 -1
  182. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec-of-file.sh +1 -0
  183. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/spec.md +2 -2
  184. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +31 -29
  185. package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +94 -0
  186. package/node_modules/@spexcode/spec-core/dist/anchors.js +730 -0
  187. package/node_modules/@spexcode/spec-core/dist/git.d.ts +170 -0
  188. package/node_modules/@spexcode/spec-core/dist/git.js +2768 -0
  189. package/node_modules/@spexcode/spec-core/dist/graph-delta.d.ts +44 -0
  190. package/node_modules/@spexcode/spec-core/dist/graph-delta.js +72 -0
  191. package/node_modules/@spexcode/spec-core/dist/graph.d.ts +34 -0
  192. package/node_modules/@spexcode/spec-core/dist/graph.js +237 -0
  193. package/node_modules/@spexcode/spec-core/dist/graphDelta.d.ts +3 -0
  194. package/node_modules/@spexcode/spec-core/dist/graphDelta.js +13 -0
  195. package/node_modules/@spexcode/spec-core/dist/harness-identity.d.ts +31 -0
  196. package/node_modules/@spexcode/spec-core/dist/harness-identity.js +20 -0
  197. package/node_modules/@spexcode/spec-core/dist/identity-presets.d.ts +152 -0
  198. package/node_modules/@spexcode/spec-core/dist/identity-presets.js +132 -0
  199. package/node_modules/@spexcode/spec-core/dist/index.d.ts +45 -0
  200. package/node_modules/@spexcode/spec-core/dist/index.js +19 -0
  201. package/node_modules/@spexcode/spec-core/dist/layout.d.ts +187 -0
  202. package/node_modules/@spexcode/spec-core/dist/layout.js +548 -0
  203. package/node_modules/@spexcode/spec-core/dist/process-identity.d.ts +37 -0
  204. package/node_modules/@spexcode/spec-core/dist/process-identity.js +214 -0
  205. package/node_modules/@spexcode/spec-core/dist/project-identity.d.ts +12 -0
  206. package/node_modules/@spexcode/spec-core/dist/project-identity.js +71 -0
  207. package/node_modules/@spexcode/spec-core/dist/project-store.d.ts +3 -0
  208. package/node_modules/@spexcode/spec-core/dist/project-store.js +14 -0
  209. package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +2 -0
  210. package/node_modules/@spexcode/spec-core/dist/resilience.js +40 -0
  211. package/node_modules/@spexcode/spec-core/dist/review/index.d.ts +3 -0
  212. package/node_modules/@spexcode/spec-core/dist/review/index.js +5 -0
  213. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +77 -0
  214. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +308 -0
  215. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +66 -0
  216. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +180 -0
  217. package/node_modules/@spexcode/spec-core/dist/review/session.d.ts +4 -0
  218. package/node_modules/@spexcode/spec-core/dist/review/session.js +8 -0
  219. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.d.ts +15 -0
  220. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.js +12 -0
  221. package/node_modules/@spexcode/spec-core/dist/root-lru.d.ts +4 -0
  222. package/{spec-cli/src/root-lru.ts → node_modules/@spexcode/spec-core/dist/root-lru.js} +26 -30
  223. package/node_modules/@spexcode/spec-core/dist/specs.d.ts +117 -0
  224. package/node_modules/@spexcode/spec-core/dist/specs.js +489 -0
  225. package/node_modules/@spexcode/spec-core/package.json +28 -0
  226. package/node_modules/@spexcode/spec-eval/dist/cache.d.ts +10 -0
  227. package/node_modules/@spexcode/spec-eval/dist/cache.js +50 -0
  228. package/node_modules/@spexcode/spec-eval/dist/cli.d.ts +22 -0
  229. package/node_modules/@spexcode/spec-eval/dist/cli.js +981 -0
  230. package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +98 -0
  231. package/node_modules/@spexcode/spec-eval/dist/evaltab.js +176 -0
  232. package/node_modules/@spexcode/spec-eval/dist/filing.d.ts +16 -0
  233. package/node_modules/@spexcode/spec-eval/dist/filing.js +36 -0
  234. package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +48 -0
  235. package/node_modules/@spexcode/spec-eval/dist/freshness.js +799 -0
  236. package/node_modules/@spexcode/spec-eval/dist/host.d.ts +46 -0
  237. package/node_modules/@spexcode/spec-eval/dist/host.js +53 -0
  238. package/node_modules/@spexcode/spec-eval/dist/humanok.d.ts +11 -0
  239. package/node_modules/@spexcode/spec-eval/dist/humanok.js +27 -0
  240. package/node_modules/@spexcode/spec-eval/dist/index.d.ts +4 -0
  241. package/node_modules/@spexcode/spec-eval/dist/index.js +4 -0
  242. package/node_modules/@spexcode/spec-eval/dist/remarks.d.ts +31 -0
  243. package/node_modules/@spexcode/spec-eval/dist/remarks.js +1 -0
  244. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.d.ts +13 -0
  245. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.js +303 -0
  246. package/node_modules/@spexcode/spec-eval/dist/scenarios.d.ts +99 -0
  247. package/node_modules/@spexcode/spec-eval/dist/scenarios.js +650 -0
  248. package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +307 -0
  249. package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +1883 -0
  250. package/node_modules/@spexcode/spec-eval/dist/sidecar.d.ts +55 -0
  251. package/node_modules/@spexcode/spec-eval/dist/sidecar.js +82 -0
  252. package/node_modules/@spexcode/spec-eval/dist/timeline.d.ts +25 -0
  253. package/node_modules/@spexcode/spec-eval/dist/timeline.js +65 -0
  254. package/node_modules/@spexcode/spec-eval/dist/ui-path.d.ts +1 -0
  255. package/node_modules/@spexcode/spec-eval/dist/ui-path.js +2 -0
  256. package/node_modules/@spexcode/spec-eval/package.json +36 -0
  257. package/node_modules/@spexcode/spec-forge/dist/cache.d.ts +29 -0
  258. package/node_modules/@spexcode/spec-forge/dist/cache.js +59 -0
  259. package/node_modules/@spexcode/spec-forge/dist/cli.d.ts +1 -0
  260. package/node_modules/@spexcode/spec-forge/dist/cli.js +100 -0
  261. package/node_modules/@spexcode/spec-forge/dist/drivers/github.d.ts +2 -0
  262. package/node_modules/@spexcode/spec-forge/dist/drivers/github.js +116 -0
  263. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.d.ts +7 -0
  264. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.js +144 -0
  265. package/node_modules/@spexcode/spec-forge/dist/drivers.d.ts +10 -0
  266. package/node_modules/@spexcode/spec-forge/dist/drivers.js +83 -0
  267. package/node_modules/@spexcode/spec-forge/dist/index.d.ts +4 -0
  268. package/node_modules/@spexcode/spec-forge/dist/index.js +4 -0
  269. package/node_modules/@spexcode/spec-forge/dist/links.d.ts +15 -0
  270. package/node_modules/@spexcode/spec-forge/dist/links.js +65 -0
  271. package/node_modules/@spexcode/spec-forge/dist/needs-eval.d.ts +10 -0
  272. package/node_modules/@spexcode/spec-forge/dist/needs-eval.js +20 -0
  273. package/node_modules/@spexcode/spec-forge/dist/port.d.ts +53 -0
  274. package/node_modules/@spexcode/spec-forge/dist/port.js +1 -0
  275. package/node_modules/@spexcode/spec-forge/dist/resident.d.ts +7 -0
  276. package/node_modules/@spexcode/spec-forge/dist/resident.js +43 -0
  277. package/node_modules/@spexcode/spec-forge/package.json +32 -0
  278. package/package.json +33 -19
  279. package/spec-cli/bin/spex.mjs +0 -69
  280. package/spec-cli/src/anchors.ts +0 -728
  281. package/spec-cli/src/attach.ts +0 -43
  282. package/spec-cli/src/claude-headless.ts +0 -321
  283. package/spec-cli/src/cli.ts +0 -1258
  284. package/spec-cli/src/client.ts +0 -414
  285. package/spec-cli/src/cockpit.ts +0 -43
  286. package/spec-cli/src/codex-runtime-generations.ts +0 -646
  287. package/spec-cli/src/commit-surgery.ts +0 -68
  288. package/spec-cli/src/contract-filter.ts +0 -202
  289. package/spec-cli/src/delivery-queue.ts +0 -165
  290. package/spec-cli/src/doctor.ts +0 -516
  291. package/spec-cli/src/execution-trace.ts +0 -444
  292. package/spec-cli/src/file-write.ts +0 -22
  293. package/spec-cli/src/gateway-auth.ts +0 -181
  294. package/spec-cli/src/gateway-hub.ts +0 -358
  295. package/spec-cli/src/gateway.ts +0 -461
  296. package/spec-cli/src/git.ts +0 -2556
  297. package/spec-cli/src/graph.ts +0 -271
  298. package/spec-cli/src/graphCache.ts +0 -710
  299. package/spec-cli/src/graphDelta.ts +0 -77
  300. package/spec-cli/src/graphStream.ts +0 -1019
  301. package/spec-cli/src/harness-select.ts +0 -77
  302. package/spec-cli/src/harness.ts +0 -3025
  303. package/spec-cli/src/headless-controller.ts +0 -63
  304. package/spec-cli/src/help.ts +0 -492
  305. package/spec-cli/src/host-resources.ts +0 -762
  306. package/spec-cli/src/host.ts +0 -611
  307. package/spec-cli/src/identity-presets.d.ts +0 -13
  308. package/spec-cli/src/identity-presets.js +0 -138
  309. package/spec-cli/src/index.ts +0 -818
  310. package/spec-cli/src/init.ts +0 -258
  311. package/spec-cli/src/issues-cli.ts +0 -276
  312. package/spec-cli/src/issues.ts +0 -237
  313. package/spec-cli/src/layout.ts +0 -640
  314. package/spec-cli/src/lint.ts +0 -488
  315. package/spec-cli/src/listen.ts +0 -28
  316. package/spec-cli/src/localIssues.ts +0 -538
  317. package/spec-cli/src/materialize.ts +0 -417
  318. package/spec-cli/src/mentions.ts +0 -81
  319. package/spec-cli/src/opencode-headless.ts +0 -205
  320. package/spec-cli/src/pi-headless.ts +0 -153
  321. package/spec-cli/src/plugin-harness.ts +0 -157
  322. package/spec-cli/src/process-identity.ts +0 -207
  323. package/spec-cli/src/project-identity.ts +0 -73
  324. package/spec-cli/src/project-store.ts +0 -17
  325. package/spec-cli/src/pty-bridge.ts +0 -396
  326. package/spec-cli/src/pty-helper.mjs +0 -96
  327. package/spec-cli/src/pty-native-helper.mjs +0 -22
  328. package/spec-cli/src/ranker.ts +0 -191
  329. package/spec-cli/src/reaper.ts +0 -125
  330. package/spec-cli/src/resilience.ts +0 -41
  331. package/spec-cli/src/reviewSnapshot.ts +0 -28
  332. package/spec-cli/src/reviews.ts +0 -434
  333. package/spec-cli/src/runtime-guard.ts +0 -35
  334. package/spec-cli/src/runtime-ownership.ts +0 -88
  335. package/spec-cli/src/search.bench.mjs +0 -80
  336. package/spec-cli/src/search.ts +0 -63
  337. package/spec-cli/src/session-cursors.ts +0 -91
  338. package/spec-cli/src/session-execution.ts +0 -68
  339. package/spec-cli/src/session-files.ts +0 -121
  340. package/spec-cli/src/session-follow.ts +0 -182
  341. package/spec-cli/src/session-reparent.ts +0 -33
  342. package/spec-cli/src/session-timeline.ts +0 -247
  343. package/spec-cli/src/session-web.ts +0 -135
  344. package/spec-cli/src/sessions.ts +0 -3883
  345. package/spec-cli/src/sh.ts +0 -3
  346. package/spec-cli/src/slash-commands.ts +0 -318
  347. package/spec-cli/src/source-files.ts +0 -104
  348. package/spec-cli/src/specs.ts +0 -498
  349. package/spec-cli/src/supervise.ts +0 -245
  350. package/spec-cli/src/tree.ts +0 -118
  351. package/spec-cli/src/tsx-bin.ts +0 -18
  352. package/spec-cli/src/uninstall.ts +0 -171
  353. package/spec-cli/src/uploads.ts +0 -253
  354. package/spec-cli/src/worktree-sources.ts +0 -42
  355. package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +0 -8
  356. package/spec-dashboard/dist/assets/App-B6IbFbGu.js +0 -2
  357. package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
  358. package/spec-dashboard/dist/assets/Dashboard-_sDk7Rcg.js +0 -27
  359. package/spec-dashboard/dist/assets/EvalsPage-BT357Lyt.js +0 -2
  360. package/spec-dashboard/dist/assets/IssuesPage-BG9rNOgV.js +0 -1
  361. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  362. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  363. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  364. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  365. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  366. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  367. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  368. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  369. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  370. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  371. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  372. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  373. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  374. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  375. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  376. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  377. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  378. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  379. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  380. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  381. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  382. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  383. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  384. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  385. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  386. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  387. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  388. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  389. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  390. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  391. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  392. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  393. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  394. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  395. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  396. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  397. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  398. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  399. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  400. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  401. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  402. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  403. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  404. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  405. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  406. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  407. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  408. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  409. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  410. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  411. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  412. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  413. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  414. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  415. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  416. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  417. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  418. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  419. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  420. package/spec-dashboard/dist/assets/MobileApp-D9yPMR3A.js +0 -2
  421. package/spec-dashboard/dist/assets/Modal-B3EcgeA3.js +0 -1
  422. package/spec-dashboard/dist/assets/PageScroll-CNZOugWV.js +0 -1
  423. package/spec-dashboard/dist/assets/ProjectsPage-C2CRHmvs.js +0 -1
  424. package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
  425. package/spec-dashboard/dist/assets/SessionInterface-DOEjuT3l.js +0 -40
  426. package/spec-dashboard/dist/assets/SessionWindow-1nYgZ4CL.js +0 -1
  427. package/spec-dashboard/dist/assets/Settings-7WeTJn0V.js +0 -1
  428. package/spec-dashboard/dist/assets/Thread-CEbzAFQn.js +0 -13
  429. package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
  430. package/spec-dashboard/dist/assets/data-SlvxNA2r.js +0 -1
  431. package/spec-dashboard/dist/assets/index-DLVeOHL-.js +0 -41
  432. package/spec-dashboard/dist/assets/index-DYLGfPjx.css +0 -1
  433. package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
  434. package/spec-dashboard/dist/assets/launch-yxCYr64x.js +0 -276
  435. package/spec-dashboard/dist/assets/sessionSurface--7l69h4F.js +0 -1
  436. package/spec-dashboard/dist/index.html +0 -29
  437. package/spec-dashboard/src/reviewFilters.js +0 -322
  438. package/spec-dashboard/src/reviewQuery.js +0 -174
  439. package/spec-dashboard/src/session.js +0 -199
  440. package/spec-eval/src/cache.ts +0 -57
  441. package/spec-eval/src/cli.ts +0 -894
  442. package/spec-eval/src/evaltab.ts +0 -264
  443. package/spec-eval/src/filing.ts +0 -39
  444. package/spec-eval/src/freshness.ts +0 -836
  445. package/spec-eval/src/humanok.ts +0 -28
  446. package/spec-eval/src/scenariofresh.ts +0 -244
  447. package/spec-eval/src/scenarios.ts +0 -672
  448. package/spec-eval/src/sessioneval.ts +0 -2220
  449. package/spec-eval/src/sidecar.ts +0 -93
  450. package/spec-eval/src/timeline.ts +0 -54
  451. package/spec-forge/src/__fixtures__/github-forge.json +0 -63
  452. package/spec-forge/src/cache.ts +0 -66
  453. package/spec-forge/src/cli.ts +0 -99
  454. package/spec-forge/src/drivers/github.ts +0 -132
  455. package/spec-forge/src/drivers/gitlab.ts +0 -153
  456. package/spec-forge/src/drivers.ts +0 -73
  457. package/spec-forge/src/links.ts +0 -72
  458. package/spec-forge/src/needs-eval.ts +0 -29
  459. package/spec-forge/src/port.ts +0 -36
  460. package/spec-forge/src/resident.ts +0 -46
  461. /package/{spec-cli → node_modules/@spexcode/spec-cli}/README.md +0 -0
  462. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -0
  463. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-sed-v0.fixture +0 -0
  464. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/commit-msg +0 -0
  465. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-checkout +0 -0
  466. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-merge +0 -0
  467. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/pre-commit +0 -0
  468. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/prepare-commit-msg +0 -0
  469. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/reference-transaction +0 -0
  470. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/extract/spec.md +0 -0
  471. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/regroup/spec.md +0 -0
  472. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/rename/spec.md +0 -0
  473. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/spec.md +0 -0
  474. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/tidy/spec.md +0 -0
  475. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -0
  476. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/idle.sh +0 -0
  477. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/spec.md +0 -0
  478. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/mark-active.sh +0 -0
  479. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/spec.md +0 -0
  480. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/fail.sh +0 -0
  481. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/spec.md +0 -0
  482. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec.md +0 -0
  483. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec.md +0 -0
  484. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec.md +0 -0
  485. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -0
  486. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -0
  487. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -0
  488. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -0
  489. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/spec.md +0 -0
  490. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/digest.mjs +0 -0
  491. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/spec.md +0 -0
  492. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/spec.md +0 -0
  493. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/spec.md +0 -0
  494. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/spec.md +0 -0
  495. /package/{spec-cli → node_modules/@spexcode/spec-core}/templates/spexcode.json +0 -0
@@ -0,0 +1,61 @@
1
+ import { loadSpecsLite } from '@spexcode/spec-core';
2
+ import { rankDocs } from './ranker.js';
3
+ // zero-result fallback: nearest node titles by per-word edit distance over title+id — the lightweight
4
+ // string distance that routes a typo'd query to a next step. Each query word takes its best normalised
5
+ // Levenshtein similarity against any title/id word (≥0.5, so unrelated words don't accumulate), then the
6
+ // words sum. Same loadSpecsLite read as the ranker; deliberately NOT part of rankDocs scoring (this
7
+ // tolerates typos, the ranker must not).
8
+ export function nearestTitles(query, n = 3) {
9
+ const words = (s) => s.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 1);
10
+ const lev = (a, b) => {
11
+ const dp = Array.from({ length: b.length + 1 }, (_, i) => i);
12
+ for (let i = 1; i <= a.length; i++) {
13
+ let prev = dp[0];
14
+ dp[0] = i;
15
+ for (let j = 1; j <= b.length; j++) {
16
+ const cur = dp[j];
17
+ dp[j] = Math.min(dp[j] + 1, dp[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
18
+ prev = cur;
19
+ }
20
+ }
21
+ return dp[b.length];
22
+ };
23
+ const qws = words(query);
24
+ if (!qws.length)
25
+ return []; // nothing to be "near" (e.g. a pure-CJK query) — the caller still points at spex graph
26
+ return loadSpecsLite()
27
+ .map((s) => {
28
+ const tws = [...new Set(words(`${s.title} ${s.id}`))];
29
+ let score = 0;
30
+ for (const q of qws) {
31
+ let best = 0;
32
+ for (const t of tws)
33
+ best = Math.max(best, 1 - lev(q, t) / Math.max(q.length, t.length));
34
+ if (best >= 0.5)
35
+ score += best;
36
+ }
37
+ return { id: s.id, title: s.title, score };
38
+ })
39
+ .filter((s) => s.score > 0)
40
+ .sort((a, b) => b.score - a.score)
41
+ .slice(0, n)
42
+ .map(({ id, title }) => ({ id, title }));
43
+ }
44
+ export async function searchSpecs(query, opts = {}) {
45
+ const t0 = performance.now();
46
+ // pre-sort by (shorter id, then id): rankDocs sorts stably, so for equal-scored nodes this IS the tiebreak.
47
+ const nodes = loadSpecsLite()
48
+ .slice()
49
+ .sort((a, b) => a.id.length - b.id.length || a.id.localeCompare(b.id));
50
+ const inputs = nodes.map((s) => ({
51
+ ref: s, name: `${s.title} ${s.id}`, desc: s.desc, body: s.body,
52
+ }));
53
+ const out = rankDocs(query, inputs, { limit: opts.limit }).map((r) => ({
54
+ id: r.ref.id, title: r.ref.title, path: r.ref.path, score: r.score, snippet: r.snippet,
55
+ }));
56
+ if (opts.onStats) {
57
+ const tokens = inputs.reduce((a, i) => a + `${i.name} ${i.desc} ${i.body}`.split(/[^a-z0-9]+/i).filter(Boolean).length, 0);
58
+ opts.onStats({ nodes: inputs.length, tokens, ms: performance.now() - t0 });
59
+ }
60
+ return out;
61
+ }
@@ -0,0 +1,13 @@
1
+ type DeclarationVerb = 'done' | 'park' | 'ask';
2
+ export declare function sessionStateKit(sessionId?: string): Promise<{
3
+ s: typeof import("./sessions.js");
4
+ sess: string | undefined;
5
+ noRecord: () => string;
6
+ mark: (fn: () => boolean) => {
7
+ ok: boolean;
8
+ reason?: string;
9
+ };
10
+ noteEcho: (note?: string) => string;
11
+ }>;
12
+ export declare function runSessionDeclaration(verb: DeclarationVerb, argv?: readonly string[]): Promise<void>;
13
+ export {};
@@ -0,0 +1,126 @@
1
+ // @@@ worker declarations ([[cli-surface]] / [[state]]) - these are the three porcelain writes an
2
+ // agent performs from its own worktree. Keep their session resolution, no-record diagnosis, durable note
3
+ // receipt, and state write together so the CLI hub only selects the verb and never becomes a second owner.
4
+ const DECLARED = ' — recorded; the human sees it in the dashboard. This declaration remains in the session timeline; your next tool call flips only the current graph state back to active (the mark-active hook, by design).';
5
+ // appended ONLY to a propose-close declaration: a worktree about to be discarded may still own ephemeral things the agent started to test this change; nudge (not gate) it to reclaim them before the worktree goes, keyed on whether the thing should outlive the task — never on who started it (a deliberately long-running service / a production build is started-by-you yet must be left alone). Project-agnostic on purpose.
6
+ // @@@ the sweep never includes THIS session - the nudge names sessions as sweepable, and it is read at the one moment a session is thinking about closing. Said loosely it reads as permission to close yourself, which deletes the worktree the reading agent is running in. So the target is scoped to sessions the agent spawned, and the exclusion is stated rather than implied.
7
+ const CLOSE_CLEANUP = '\n\nBefore this worktree closes, check whether you left anything running that you started to test this change — a background process, a dev or preview server, a bound port, a throwaway session you spawned. If nothing depends on it anymore, shut it down, or it keeps running as an orphan. Leave anything meant to keep running: a service you deliberately stood up, a production build, anything other work relies on. What matters is whether it still needs to exist after this task, not whether you started it. If unsure, leave it. This sweep never includes THIS session: you have PROPOSED that the human close it, and closing your own session would delete the worktree you are running in. This is a reminder to check, not a required step.';
8
+ const flag = (argv, name) => {
9
+ const i = argv.indexOf(`--${name}`);
10
+ return i >= 0 ? argv[i + 1] : undefined;
11
+ };
12
+ function nothingProposalTrap() {
13
+ console.error('`spex session done --propose nothing` is an intended trap: no state was recorded.');
14
+ console.error('Choose the true destination:');
15
+ console.error(' `merge` - committed spec and code have not landed in `main`.');
16
+ console.error(' `close` - work is complete: it has landed (or had nothing to land), is verified, and no worktree, human decision/follow-up, or posted artifact needs human inspection.');
17
+ console.error(' `ask` - a human reply, direction, or decision is needed, including a reported finding/recommendation, handoff, or posted artifact inspection.');
18
+ console.error(' `park` - a managed delivery or background job will resume a named next action; watching terminal children is not a wake-up.');
19
+ process.exit(2);
20
+ }
21
+ // Shared with `spex internal session-*`: machine lifecycle producers use the same record-resolution and
22
+ // diagnostic rules, but they do not own any porcelain declaration verb.
23
+ export async function sessionStateKit(sessionId) {
24
+ const s = await import('./sessions.js');
25
+ const l = await import('@spexcode/spec-core');
26
+ const { existsSync, writeFileSync } = await import('node:fs');
27
+ // the agent-authored state writers resolve WHICH session by id: a `--session <id>` flag (the lifecycle
28
+ // hooks pass it, parsed from the payload, since they no longer have a cwd `.session`) wins, else the
29
+ // harness env var (ownSessionId — the agent's own `spex session …` carries the harness session id).
30
+ const sess = sessionId;
31
+ // @@@ no-record diagnosis ([[state]]) - the session store resolves from the CURRENT directory (runtimeRoot
32
+ // ← the cwd's git common dir), so the classic declaration failure is a cd OUTSIDE the session's project —
33
+ // and a bare "no session record" told the author none of that (field-reported). Name the actual cause and
34
+ // route the fix; each branch is a distinct situation, distinguished by probing the same store the writer used.
35
+ const noRecord = () => {
36
+ // cwd probe FIRST: outside a git repo nothing below can resolve — not the store, and not even the env
37
+ // id (ownSessionId's alias walk reads the store too) — so diagnose the cwd before touching either.
38
+ let ids;
39
+ try {
40
+ ids = l.listSessionIds();
41
+ }
42
+ catch {
43
+ ids = null;
44
+ }
45
+ if (ids === null)
46
+ return `no session record — declarations resolve the session store from the CURRENT directory, and ${process.cwd()} is not inside a git repository. cd back into the session's worktree and re-declare.`;
47
+ const wid = sess || s.ownSessionId(); // safe now: the store just resolved, so the alias walk cannot throw
48
+ if (!wid)
49
+ return 'no session record — no session id to write: this shell carries no harness session env (SPEXCODE_SESSION_ID / CLAUDE_CODE_SESSION_ID) and no --session was given. Pass --session <id> (spex session ls lists ids).';
50
+ if (ids.length === 0)
51
+ return `no session record for ${wid.slice(0, 8)} — declarations resolve the session store from the CURRENT directory, and the project at ${process.cwd()} has no sessions at all: you are in a different project's checkout. cd back into the session's worktree and re-declare.`;
52
+ return `no session record for ${wid.slice(0, 8)} — this project's store (resolved from ${process.cwd()}) holds ${ids.length} session(s) but not this one: a wrong --session id, or you are declaring from a different project's checkout. cd back into the session's worktree and re-declare, or pass a valid --session <id> (spex session ls).`;
53
+ };
54
+ // a state writer from a non-repo cwd throws git's not-a-repo before it can return false — map exactly
55
+ // that throw to the no-record path (noRecord re-probes and names the cwd); anything else stays loud.
56
+ // A record that exists but CANNOT carry state (unreadable bytes, or a worktree that is gone) is its own
57
+ // answer: the writer refused on purpose and already knows why, so that reason is what the caller prints —
58
+ // never the no-record diagnosis, which would send the author chasing a wrong cwd ([[sessions-core]]).
59
+ const mark = (fn) => {
60
+ try {
61
+ return { ok: fn() };
62
+ }
63
+ catch (e) {
64
+ if (e instanceof s.SessionRecordUnusable)
65
+ return { ok: false, reason: e.message };
66
+ if (/not a git repository/i.test(String(e?.stderr ?? e)))
67
+ return { ok: false };
68
+ throw e;
69
+ }
70
+ };
71
+ // Taught once per record: the CLI's explicit NOTE column is the only note display that cuts prose.
72
+ const noteEcho = (note) => {
73
+ if (!note)
74
+ return '';
75
+ const tableCut = s.displayWidth(note) > s.NOTE_BOARD_LIMIT;
76
+ if (!tableCut)
77
+ return '';
78
+ const wid = sess || s.ownSessionId();
79
+ const rid = wid ? (l.readAliasedRawRecord(wid)?.session_id ?? wid) : null; // sentinel lives in the RECORD's dir, so an aliased codex id lands on the same file
80
+ if (rid) {
81
+ const sentinel = l.sessionArtifactPath(rid, 'note-echo-taught');
82
+ try {
83
+ if (existsSync(sentinel))
84
+ return '';
85
+ writeFileSync(sentinel, `${new Date().toISOString()}\n`); // only reached on a successful declaration (the echo rides the success branch)
86
+ }
87
+ catch { /* unreadable/unwritable store dir → fall through and teach again; never block the echo */ }
88
+ }
89
+ return `\nyour note is ${note.length} chars — the session table's NOTE column shows only the first ${s.NOTE_BOARD_LIMIT} display columns. the full text IS recorded, and readable via spex session review ${(wid || '<your-session>').slice(0, 8)} / spex session ls --json. (said once — later cut notes won't repeat this.)`;
90
+ };
91
+ return { s, sess, noRecord, mark, noteEcho };
92
+ }
93
+ export async function runSessionDeclaration(verb, argv = process.argv) {
94
+ const sessionId = flag(argv, 'session');
95
+ const note = flag(argv, 'note');
96
+ if (verb === 'done') {
97
+ // `merge`/`close` are awaiting declarations; `nothing` is an intentional no-write correction trap.
98
+ const proposal = flag(argv, 'propose') || 'nothing';
99
+ if (proposal === 'nothing')
100
+ nothingProposalTrap();
101
+ const { s, sess, mark, noRecord, noteEcho } = await sessionStateKit(sessionId);
102
+ let closeNote = proposal === 'close' ? CLOSE_CLEANUP : '';
103
+ if (proposal === 'close') {
104
+ // The closeout nudge is advisory: a declaration must land even if the local issue store fails.
105
+ try {
106
+ closeNote += (await import('./localIssues.js')).closeoutNudge(sess ?? s.ownSessionId());
107
+ }
108
+ catch (e) {
109
+ console.error(`issue closeout check failed (declaration unaffected): ${e instanceof Error ? e.message : e}`);
110
+ }
111
+ }
112
+ const done = mark(() => s.markDone(proposal, sess, note));
113
+ console.log(done.ok ? `done (${proposal})${DECLARED}${noteEcho(note)}${closeNote}` : done.reason ?? noRecord());
114
+ return;
115
+ }
116
+ const { s, sess, mark, noRecord, noteEcho } = await sessionStateKit(sessionId);
117
+ if (verb === 'park') {
118
+ // sugar: the agent is waiting on a background task; it will self-resume (NOT idle/awaiting)
119
+ const parked = mark(() => s.markState('parked', { note, sessionId: sess }));
120
+ console.log(parked.ok ? `parked${DECLARED}${noteEcho(note)}` : parked.reason ?? noRecord());
121
+ return;
122
+ }
123
+ // The agent deliberately pauses for a human response. This is authored state, not an active-only inference.
124
+ const asked = mark(() => s.markState('asking', { note, sessionId: sess }));
125
+ console.log(asked.ok ? `asking${DECLARED}${noteEcho(note)}` : asked.reason ?? noRecord());
126
+ }
@@ -0,0 +1,10 @@
1
+ import type { Context } from 'hono';
2
+ import type { ExecutionStep } from './execution-trace.js';
3
+ export type SessionExecution = Readonly<{
4
+ revision: string;
5
+ turnId: string | null;
6
+ workingNote: string | null;
7
+ steps: readonly ExecutionStep[];
8
+ }>;
9
+ export declare function readSessionExecution(id: string): SessionExecution | null;
10
+ export declare function sessionExecutionStream(c: Context): Promise<Response>;
@@ -0,0 +1,70 @@
1
+ import { streamSSE } from 'hono/streaming';
2
+ import { harnessById } from './harness.js';
3
+ import { readAliasedRawRecord } from '@spexcode/spec-core';
4
+ import { currentHumanTurn } from './session-timeline.js';
5
+ const absentExecution = (turn = null) => ({ revision: `turn:${turn?.token ?? '0'}`, turnId: turn?.token ?? null, workingNote: null, steps: [] });
6
+ // This is deliberately a read projection. Native transcript bytes stay within the adapter, while the API owns
7
+ // only enough information for the frontend's fixed renderer to paint the current execution slice.
8
+ export function readSessionExecution(id) {
9
+ let record;
10
+ try {
11
+ record = readAliasedRawRecord(id);
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ if (!record?.governed)
17
+ return null;
18
+ const turn = currentHumanTurn(id);
19
+ const threadId = typeof record.harness_session_id === 'string' ? record.harness_session_id : '';
20
+ if (!threadId)
21
+ return absentExecution(turn);
22
+ try {
23
+ const trace = harnessById(typeof record.harness === 'string' && record.harness ? record.harness : 'claude')
24
+ .executionTrace(threadId, turn);
25
+ return trace || absentExecution(turn);
26
+ }
27
+ catch {
28
+ // A missing/unreadable native source is an absent transient trace, not a synthetic conversation failure.
29
+ return absentExecution(turn);
30
+ }
31
+ }
32
+ const TRACE_TICK_MS = 500;
33
+ const HEARTBEAT_TICKS = 20;
34
+ export async function sessionExecutionStream(c) {
35
+ const id = c.req.param('id') || '';
36
+ if (!readSessionExecution(id))
37
+ return c.json({ error: 'no such session' }, 404);
38
+ return streamSSE(c, async (stream) => {
39
+ let aborted = false;
40
+ let lastRevision = '';
41
+ let ticks = 0;
42
+ const publish = async () => {
43
+ const execution = readSessionExecution(id);
44
+ if (!execution)
45
+ return false;
46
+ if (execution.revision === lastRevision)
47
+ return true;
48
+ lastRevision = execution.revision;
49
+ await stream.writeSSE({ event: 'execution', data: JSON.stringify(execution) });
50
+ return true;
51
+ };
52
+ stream.onAbort(() => { aborted = true; });
53
+ try {
54
+ if (!await publish())
55
+ return;
56
+ while (!aborted) {
57
+ await stream.sleep(TRACE_TICK_MS);
58
+ if (aborted)
59
+ break;
60
+ if (!await publish())
61
+ break;
62
+ if (++ticks % HEARTBEAT_TICKS === 0)
63
+ await stream.writeSSE({ event: 'ping', data: 'x' });
64
+ }
65
+ }
66
+ catch {
67
+ // EventSource reconnects a dropped stream. A native trace read must never take down the session server.
68
+ }
69
+ });
70
+ }
@@ -0,0 +1,28 @@
1
+ export declare class SessionFileError extends Error {
2
+ readonly status: 400 | 403 | 404 | 500;
3
+ constructor(status: 400 | 403 | 404 | 500, message: string);
4
+ }
5
+ type SessionFileLock = <T>(id: string, body: () => T) => T;
6
+ export declare const SESSION_FILE_PREVIEW_MAX_BYTES: number;
7
+ export type SessionFilePreviewKind = 'text' | 'image' | 'html';
8
+ export declare function listSessionFiles(id: string): string[];
9
+ export declare function readSessionFiles(id: string): string[];
10
+ export declare function addSessionFile(id: string, input: string, lock: SessionFileLock, cwd?: string): {
11
+ path: string;
12
+ added: boolean;
13
+ };
14
+ export declare function retractSessionFile(id: string, input: string, lock: SessionFileLock, cwd?: string): {
15
+ path: string;
16
+ removed: boolean;
17
+ };
18
+ export declare function openSessionFile(id: string, path: string): {
19
+ path: string;
20
+ name: string;
21
+ size: number;
22
+ };
23
+ export declare function sessionFilePreviewKind(path: string): {
24
+ kind: SessionFilePreviewKind;
25
+ contentType: string;
26
+ } | null;
27
+ export declare const sessionFilesPath: (id: string) => string;
28
+ export {};
@@ -0,0 +1,130 @@
1
+ import { accessSync, constants, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
2
+ import { basename, isAbsolute, resolve } from 'node:path';
3
+ import { readRecordEntry, sessionArtifactPath } from '@spexcode/spec-core';
4
+ export class SessionFileError extends Error {
5
+ status;
6
+ constructor(status, message) {
7
+ super(message);
8
+ this.status = status;
9
+ this.name = 'SessionFileError';
10
+ }
11
+ }
12
+ export const SESSION_FILE_PREVIEW_MAX_BYTES = 2 * 1024 * 1024;
13
+ const HTML_EXTENSIONS = new Set(['.html', '.htm']);
14
+ const TEXT_EXTENSIONS = new Set(['.txt', '.md', '.markdown', '.json', '.yaml', '.yml', '.toml', '.ini', '.log', '.csv', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.css', '.xml', '.py', '.go', '.rs', '.java', '.sh', '.sql']);
15
+ const IMAGE_TYPES = {
16
+ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp',
17
+ };
18
+ const filesPath = (id) => sessionArtifactPath(id, 'files.json');
19
+ function requireSession(id) {
20
+ const record = readRecordEntry(id);
21
+ if (record.kind === 'absent')
22
+ throw new SessionFileError(404, `session ${id} does not exist`);
23
+ if (record.kind === 'corrupt')
24
+ throw new SessionFileError(500, `session ${id} has an unreadable record: ${record.error}`);
25
+ }
26
+ function readFiles(id) {
27
+ const path = filesPath(id);
28
+ let parsed;
29
+ try {
30
+ parsed = JSON.parse(readFileSync(path, 'utf8'));
31
+ }
32
+ catch (error) {
33
+ if (error.code === 'ENOENT')
34
+ return [];
35
+ throw new SessionFileError(500, `session file list is unreadable: ${path} — ${error instanceof Error ? error.message : String(error)}`);
36
+ }
37
+ if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== 'string' || !isAbsolute(value)))
38
+ throw new SessionFileError(500, `session file list is invalid: ${path}`);
39
+ return [...new Set(parsed)];
40
+ }
41
+ function writeFiles(id, files) {
42
+ const path = filesPath(id);
43
+ const tmp = `${path}.${process.pid}.tmp`;
44
+ try {
45
+ writeFileSync(tmp, `${JSON.stringify(files, null, 2)}\n`);
46
+ renameSync(tmp, path);
47
+ }
48
+ finally {
49
+ try {
50
+ unlinkSync(tmp);
51
+ }
52
+ catch { /* rename already consumed the temporary file */ }
53
+ }
54
+ }
55
+ function currentFile(path) {
56
+ try {
57
+ if (!statSync(path).isFile())
58
+ throw new SessionFileError(400, `not a regular file: ${path}`);
59
+ accessSync(path, constants.R_OK);
60
+ }
61
+ catch (error) {
62
+ if (error instanceof SessionFileError)
63
+ throw error;
64
+ const code = error.code;
65
+ if (code === 'ENOENT' || code === 'ENOTDIR')
66
+ throw new SessionFileError(404, `file does not exist: ${path}`);
67
+ throw new SessionFileError(403, `file is not readable: ${path}`);
68
+ }
69
+ }
70
+ export function listSessionFiles(id) {
71
+ requireSession(id);
72
+ return readSessionFiles(id);
73
+ }
74
+ // Session projections already hold a parsed record; re-checking session.json here would make an in-memory
75
+ // projection depend on the public route's existence guard. The route-facing list keeps that guard above.
76
+ export function readSessionFiles(id) {
77
+ return readFiles(id);
78
+ }
79
+ export function addSessionFile(id, input, lock, cwd = process.cwd()) {
80
+ const path = resolve(cwd, input);
81
+ currentFile(path);
82
+ return lock(id, () => {
83
+ requireSession(id);
84
+ const files = readFiles(id);
85
+ if (files.includes(path))
86
+ return { path, added: false };
87
+ writeFiles(id, [...files, path]);
88
+ return { path, added: true };
89
+ });
90
+ }
91
+ export function retractSessionFile(id, input, lock, cwd = process.cwd()) {
92
+ const path = resolve(cwd, input);
93
+ return lock(id, () => {
94
+ requireSession(id);
95
+ const files = readFiles(id);
96
+ if (!files.includes(path))
97
+ return { path, removed: false };
98
+ writeFiles(id, files.filter((file) => file !== path));
99
+ return { path, removed: true };
100
+ });
101
+ }
102
+ export function openSessionFile(id, path) {
103
+ requireSession(id);
104
+ if (!readFiles(id).includes(path))
105
+ throw new SessionFileError(403, 'that path was not posted by this session');
106
+ try {
107
+ const stat = statSync(path);
108
+ if (!stat.isFile())
109
+ throw new SessionFileError(404, `file no longer exists: ${path}`);
110
+ accessSync(path, constants.R_OK);
111
+ return { path, name: basename(path) || 'download', size: stat.size };
112
+ }
113
+ catch (error) {
114
+ if (error instanceof SessionFileError)
115
+ throw error;
116
+ throw new SessionFileError(404, `file no longer exists: ${path}`);
117
+ }
118
+ }
119
+ export function sessionFilePreviewKind(path) {
120
+ const extension = basename(path).toLowerCase().match(/\.[^.]+$/)?.[0];
121
+ if (!extension)
122
+ return null;
123
+ if (HTML_EXTENSIONS.has(extension))
124
+ return { kind: 'html', contentType: 'text/html; charset=utf-8' };
125
+ if (TEXT_EXTENSIONS.has(extension))
126
+ return { kind: 'text', contentType: 'text/plain; charset=utf-8' };
127
+ const contentType = IMAGE_TYPES[extension];
128
+ return contentType ? { kind: 'image', contentType } : null;
129
+ }
130
+ export const sessionFilesPath = (id) => filesPath(id);
@@ -0,0 +1,31 @@
1
+ import { type DisplayStatus, type Session } from './sessions.js';
2
+ export declare function sessionEvent(s: Session): string;
3
+ export declare function launchEvent(s: Session): string;
4
+ export type FollowOpts = {
5
+ targets: () => string[];
6
+ self?: string | null;
7
+ statuses?: string[];
8
+ includeIdle?: boolean;
9
+ intervalMs?: number;
10
+ as?: string;
11
+ take?: boolean;
12
+ timeoutMs?: number;
13
+ onObserved?: (id: string, status: DisplayStatus, previous: DisplayStatus | null) => void;
14
+ row?: (id: string, status: DisplayStatus, note: string | null) => Session | null;
15
+ };
16
+ export type FollowOutcome = {
17
+ reached: DisplayStatus;
18
+ id: string;
19
+ path: DisplayStatus[];
20
+ } | {
21
+ mail: {
22
+ from: string | null;
23
+ text: string;
24
+ };
25
+ } | {
26
+ timedOut: true;
27
+ path: DisplayStatus[];
28
+ } | {
29
+ gone: string;
30
+ };
31
+ export declare function followSessions(emit: (line: string) => void, opts: FollowOpts): Promise<FollowOutcome>;
@@ -0,0 +1,179 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { sessionStoreDir } from '@spexcode/spec-core';
3
+ import { advanceFollow, followCursor, unreadSince } from '@spexcode/session-core';
4
+ import { timelineDisplay, timelineEvents, timelineStamp } from './session-timeline.js';
5
+ import { sessionTitle } from './sessions.js';
6
+ // @@@ session-follow - supervision is FOLLOWING a log past a cursor, never polling a derived board. One tick
7
+ // costs ONE stat per target: if timeline.ndjson has not grown, nothing is opened and nothing is parsed. No
8
+ // call here reaches the backend, a rendezvous socket, or tmux, which is the whole point — M followers over N
9
+ // sessions cost the control plane zero, where the old poll cost one board build (a connect + a tmux spawn per
10
+ // live session) per follower per interval.
11
+ // Actionable = a state whose arrival means "a human/supervisor must now act". `offline` is deliberately ABSENT
12
+ // where the old poll had it: liveness is a present-tense probe derivation, never authored, so it can never
13
+ // appear on a log ([[state]]). A follower learns what a session DECLARED and never that it died.
14
+ const ACTIONABLE = new Set(['review', 'done', 'close-pending', 'error', 'asking']);
15
+ const NEXT = {
16
+ review: 'merge | close',
17
+ done: 'merge | close',
18
+ 'close-pending': 'close',
19
+ error: 'resume (relaunch & retry) | show --capture | close',
20
+ asking: 'send "<msg>" | show --capture',
21
+ idle: 'send "<msg>" | show --capture',
22
+ queued: 'waiting for a free slot — starts automatically | close',
23
+ };
24
+ const trunc = (s, n) => (s.length <= n ? s : `${s.slice(0, n - 1)}…`);
25
+ export function sessionEvent(s) {
26
+ const note = s.note ? ` — note: ${s.note}` : '';
27
+ const asked = s.promptPreview ? ` · asked: ${s.promptPreview}` : '';
28
+ return `[spex] ${s.status} · ${sessionTitle(s)} — act: ${NEXT[s.status] || '—'}${note}${asked} [id ${s.id}]`;
29
+ }
30
+ // @@@ launchEvent - a session's FIRST sighting, emitted once per id whatever its status, so the stream is a
31
+ // complete lifecycle feed: launched → [transitions] → closed. A launch's own first line is `active` (not
32
+ // actionable), so without this the feed would be blind to new sessions starting.
33
+ export function launchEvent(s) {
34
+ const note = s.note ? ` — note: ${s.note}` : '';
35
+ const asked = s.promptPreview ? ` · asked: ${s.promptPreview}` : '';
36
+ return `[spex] launched · ${sessionTitle(s)} — act: capture | send "<msg>"${note}${asked} [id ${s.id}]`;
37
+ }
38
+ export async function followSessions(emit, opts) {
39
+ const { targets, self = null, statuses, includeIdle = false, intervalMs = 1000, as, take = false, timeoutMs = 1_200_000, onObserved, row } = opts;
40
+ const tag = as ? `[${as}] ` : '';
41
+ const state = new Map();
42
+ const memo = new Map(); // cursors for a follower that has no record of its own to store them in
43
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
44
+ // the no-hang wall: a fixed deadline computed ONCE, checked before EVERY sleep below.
45
+ const deadline = take ? Date.now() + Math.max(1000, timeoutMs) : 0;
46
+ const isActionable = (st) => ACTIONABLE.has(st) || (includeIdle && st === 'idle');
47
+ const passes = (st) => !statuses?.length || statuses.includes(st);
48
+ const anyPath = () => {
49
+ for (const f of state.values())
50
+ if (f.path.length)
51
+ return f.path;
52
+ return [];
53
+ };
54
+ const readCursor = (id) => (self ? followCursor(self, id) : memo.get(id) ?? null);
55
+ const writeCursor = (id, to) => { if (self)
56
+ advanceFollow(self, id, to);
57
+ else
58
+ memo.set(id, to); };
59
+ const line = (id, st, note, first) => {
60
+ const s = row?.(id, st, note);
61
+ if (!s)
62
+ return;
63
+ if (first)
64
+ emit(tag + launchEvent(s));
65
+ else if (passes(st) && isActionable(st))
66
+ emit(tag + sessionEvent(s));
67
+ };
68
+ let first = true;
69
+ let shown = -1; // stream mode: the last inbox line this process printed (see the INBOX read below)
70
+ for (;;) {
71
+ const ids = targets();
72
+ const seen = new Set();
73
+ for (const id of ids) {
74
+ if (id === self)
75
+ continue; // the follower's own log is its INBOX, followed below on its own cursor rule
76
+ seen.add(id);
77
+ if (!existsSync(sessionStoreDir(id))) {
78
+ if (state.delete(id))
79
+ emit(`${tag}[spex] closed · removed [id ${id}]`);
80
+ if (take)
81
+ return { gone: id };
82
+ continue;
83
+ }
84
+ let f = state.get(id);
85
+ if (!f) {
86
+ const evs = timelineEvents(id);
87
+ // A stored cursor always wins — that IS the resume, losing nothing across a follower's death. With
88
+ // none: a target already present when the follow started predates it, so we begin at the log's END
89
+ // (its history is not news); a target that appears on a LATER tick genuinely just launched, so its
90
+ // whole log is read from 0 and its `launched` line reaches the feed.
91
+ const pos = readCursor(id) ?? (first ? evs.length : 0);
92
+ // The ARRIVAL state is the last status line BEFORE the cursor — a fact on the log, not whatever a poll
93
+ // happened to catch. With no such line the first status we read is the arrival instead.
94
+ let prev = null;
95
+ for (let i = Math.min(pos, evs.length) - 1; i >= 0; i--) {
96
+ const e = evs[i];
97
+ if (e.kind === 'status') {
98
+ prev = timelineDisplay(e);
99
+ break;
100
+ }
101
+ }
102
+ f = { pos, prev, path: prev ? [prev] : [], stamp: null };
103
+ state.set(id, f);
104
+ if (prev) {
105
+ onObserved?.(id, prev, null);
106
+ line(id, prev, null, true);
107
+ }
108
+ }
109
+ const stamp = timelineStamp(id);
110
+ if (stamp !== null && stamp === f.stamp)
111
+ continue; // THE cheap tick: nothing appended, nothing parsed
112
+ f.stamp = stamp;
113
+ const evs = timelineEvents(id);
114
+ const slice = unreadSince(evs, f.pos);
115
+ let hit = null;
116
+ for (let k = 0; k < slice.events.length && !hit; k++) {
117
+ const e = slice.events[k];
118
+ if (e.kind === 'sent') {
119
+ const s = row?.(id, f.prev ?? 'unknown', null);
120
+ if (s)
121
+ emit(`${tag}[spex] message · ${sessionTitle(s)} — from ${e.from ?? 'human'}: ${trunc(e.text, 120)} [id ${id}]`);
122
+ continue;
123
+ }
124
+ const st = timelineDisplay(e);
125
+ const was = f.prev;
126
+ f.prev = st;
127
+ f.path.push(st);
128
+ onObserved?.(id, st, was);
129
+ line(id, st, e.note, was === null);
130
+ // THE edge: a previously-observed NON-actionable status moving INTO an actionable one. An actionable
131
+ // ARRIVAL (was === null) is not an edge — that standing level is what a level-triggered wait falsely
132
+ // returned on; nor is an actionable→actionable hop (review→done). The rise OUT of non-actionable is the
133
+ // one signal that means "the target needs you AGAIN". The previous state is the previous LINE, so two
134
+ // moves inside one tick are two edges rather than one collapsed sample.
135
+ if (take && was !== null && !isActionable(was) && isActionable(st))
136
+ hit = { reached: st, id, path: f.path };
137
+ // Consume exactly up to the event we stopped on, so the lines behind it stay unread for the next wait.
138
+ if (hit) {
139
+ f.pos = slice.at[k] + 1;
140
+ writeCursor(id, f.pos);
141
+ }
142
+ }
143
+ if (hit)
144
+ return hit;
145
+ f.pos = slice.next;
146
+ writeCursor(id, f.pos);
147
+ }
148
+ for (const id of [...state.keys()]) {
149
+ if (seen.has(id))
150
+ continue;
151
+ state.delete(id);
152
+ emit(`${tag}[spex] closed · removed [id ${id}]`);
153
+ }
154
+ // THE FOLLOWER'S OWN LOG — watched exactly like any other target, on its own entry in `cursors.json`. It is
155
+ // a WATCH, not a delivery: a message reaches the agent as a prompt through the adapter ([[delivery-queue]]),
156
+ // so this position only decides what THIS process has already reported and can never make an agent miss
157
+ // mail. Never advanced here in take mode — the waiter stops on the event and the next wait resumes on it.
158
+ if (self && existsSync(sessionStoreDir(self))) {
159
+ const mine = unreadSince(timelineEvents(self), followCursor(self, self) ?? 0);
160
+ for (let k = 0; k < mine.events.length; k++) {
161
+ const e = mine.events[k];
162
+ if (e.kind !== 'sent')
163
+ continue;
164
+ if (take)
165
+ return { mail: { from: e.from, text: e.text } };
166
+ // a stream has no turn boundary to advance that cursor, so its own high-water mark — process-local,
167
+ // never written — is what keeps an unread line from being re-printed on every tick.
168
+ if (mine.at[k] <= shown)
169
+ continue;
170
+ shown = mine.at[k];
171
+ emit(`${tag}[spex] message · you — from ${e.from ?? 'human'}: ${trunc(e.text, 120)} [id ${self}]`);
172
+ }
173
+ }
174
+ first = false;
175
+ if (take && Date.now() >= deadline)
176
+ return { timedOut: true, path: anyPath() };
177
+ await sleep(intervalMs);
178
+ }
179
+ }