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
@@ -1,434 +0,0 @@
1
- import { createHash } from 'node:crypto'
2
- import { listSessions } from './sessions.js'
3
- import { getBoard, getBoardForForgeRevision } from './graphCache.js'
4
- import { type SessionEvalOrderRow, buildSessionEvals, type SessionEvals } from '../../spec-eval/src/sessioneval.js'
5
- import { evalTimeline } from '../../spec-eval/src/evaltab.js'
6
- import { issuesEnabled as issuesEnabledForReview } from './localIssues.js'
7
- import { issueStores as issueStoresForReview } from './issues.js'
8
- import { hasReviewSnapshot, readReviewSnapshot } from './reviewSnapshot.js'
9
- import { residentForgeRevision, residentForgeState } from '../../spec-forge/src/resident.js'
10
- // @ts-expect-error The dashboard module is deliberately plain JS so the browser and server execute the
11
- // exact same tokenizer/matcher. It is shipped beside the built dashboard by the root package manifest.
12
- import { EVAL_FILTER_KIND, evalFilterModel, evalReviewState, issueFilterModel, tokenFilterState } from '../../spec-dashboard/src/reviewFilters.js'
13
- // @ts-expect-error See the shared-domain note above.
14
- import { EVAL_QUERY_DEFAULT, ISSUE_QUERY_DEFAULT, readToken } from '../../spec-dashboard/src/reviewQuery.js'
15
-
16
- export const REVIEW_PER_PAGE = 25
17
-
18
- type ReviewItem = Record<string, unknown>
19
- // A section count is one number, or — when the domain adapter splits that section — its named buckets,
20
- // which re-add to the same whole population ([[review-filters]] owns the split; Evals splits its measured
21
- // verdicts into {fresh,stale} so the remeasurement debt travels with the count instead of being recomputed
22
- // per surface). The fold happens ONCE, here on the server, over the complete filtered population.
23
- export type ReviewCount = number | Record<string, number>
24
- type ReviewOption = { value: string; label?: string; count?: number }
25
- type ReviewFacet = { key: string; label?: string; value: string; meaningful?: boolean; options: ReviewOption[] }
26
- type EvalNeighbor = { node: string; scenario: string; state: string }
27
-
28
- export type PagedReview<T extends ReviewItem = ReviewItem> = {
29
- items: T[]
30
- page: number
31
- perPage: number
32
- total: number
33
- sourceTotal: number
34
- pageCount: number
35
- prev: number | null
36
- next: number | null
37
- revision: string
38
- counts: Record<string, ReviewCount>
39
- facets: Record<string, ReviewFacet>
40
- section: { key: string; value: string; options: ReviewOption[] } | null
41
- }
42
-
43
- export type EvalDetailReview = {
44
- scope: string | null
45
- requestedScope: string | null
46
- scopeFallback: 'trunk' | null
47
- availability: 'measured' | 'unmeasured' | 'missing'
48
- selected: ReviewItem | null
49
- history: ReviewItem[]
50
- neighbors: {
51
- prev: EvalNeighbor[]
52
- next: EvalNeighbor[]
53
- total: number
54
- index: number | null
55
- order: 'default'
56
- }
57
- revision: string
58
- summary?: SessionEvals['summary']
59
- evalRevision?: SessionEvals['evalRevision']
60
- }
61
-
62
- type EvalDetailMetadata = {
63
- scope?: string | null
64
- requestedScope?: string | null
65
- scopeFallback?: 'trunk' | null
66
- summary?: SessionEvals['summary']
67
- evalRevision?: SessionEvals['evalRevision']
68
- // the scope's whole measured sequence, when `items` deliberately holds only the rendered window
69
- sequence?: { node: string; scenario: string }[]
70
- }
71
-
72
- const revisionOf = (value: unknown): string => createHash('sha256').update(JSON.stringify(value)).digest('hex')
73
-
74
- export function reviewPageNumber(value: unknown): number {
75
- const raw = typeof value === 'string' && /^\d+$/.test(value) ? Number(value) : Number.NaN
76
- return Number.isSafeInteger(raw) && raw > 0 ? raw : 1
77
- }
78
-
79
- function responseModel(model: any): Pick<PagedReview, 'counts' | 'facets' | 'section'> {
80
- const facets = Object.fromEntries(Object.entries(model.facets ?? {}).map(([key, raw]) => {
81
- const facet = raw as any
82
- return [key, {
83
- key,
84
- ...(facet.label != null ? { label: String(facet.label) } : {}),
85
- value: String(facet.value ?? ''),
86
- ...(facet.meaningful != null ? { meaningful: !!facet.meaningful } : {}),
87
- options: (facet.options ?? []).map((option: any) => ({
88
- value: String(option.value ?? ''),
89
- ...(option.label != null ? { label: String(option.label) } : {}),
90
- ...(Number.isFinite(option.count) ? { count: Number(option.count) } : {}),
91
- })),
92
- }]
93
- })) as Record<string, ReviewFacet>
94
- const section = model.section ? {
95
- key: String(model.section.key),
96
- ...(model.section.label != null ? { label: String(model.section.label) } : {}),
97
- value: String(model.section.value ?? ''),
98
- ...(model.section.meaningful != null ? { meaningful: !!model.section.meaningful } : {}),
99
- options: (model.section.options ?? []).map((option: any) => ({
100
- value: String(option.value ?? ''),
101
- ...(option.label != null ? { label: String(option.label) } : {}),
102
- ...(Number.isFinite(option.count) ? { count: Number(option.count) } : {}),
103
- })),
104
- } : null
105
- return { counts: { ...(model.sections ?? {}) }, facets, section }
106
- }
107
-
108
- export function paginateReview<T extends ReviewItem>(
109
- source: T[],
110
- shown: T[],
111
- model: any,
112
- requestedPage: unknown,
113
- revisionInputs: unknown,
114
- ): PagedReview<T> {
115
- const page = reviewPageNumber(requestedPage)
116
- const total = shown.length
117
- const pageCount = Math.ceil(total / REVIEW_PER_PAGE)
118
- const start = (page - 1) * REVIEW_PER_PAGE
119
- const overflow = pageCount > 0 ? page > pageCount : page > 1
120
- return {
121
- items: shown.slice(start, start + REVIEW_PER_PAGE),
122
- page,
123
- perPage: REVIEW_PER_PAGE,
124
- total,
125
- sourceTotal: source.length,
126
- pageCount,
127
- prev: page > 1 ? page - 1 : null,
128
- next: page < pageCount || overflow ? page + 1 : null,
129
- revision: revisionOf({ page, source: revisionInputs }),
130
- ...responseModel(model),
131
- }
132
- }
133
-
134
- const issueOrder = (a: any, b: any): number => String(b.created ?? '').localeCompare(String(a.created ?? ''))
135
- || String(a.id ?? '').localeCompare(String(b.id ?? ''))
136
-
137
- export async function issuesReview(query: string | undefined, requestedPage: unknown) {
138
- // The first request must wait for the first atomic publication. Once one exists, a graph refresh may be
139
- // rebuilding unrelated board/session state; the published review source remains a valid answer and its
140
- // revision/poll path will deliver the next generation without making this page join that flight.
141
- residentForgeState()
142
- if (!hasReviewSnapshot()) await getBoard()
143
- else if (readReviewSnapshot().forgeRevision < residentForgeRevision()) {
144
- await getBoardForForgeRevision(residentForgeRevision())
145
- }
146
- const sessions = await listSessions()
147
- const issues = readReviewSnapshot().issues.slice().sort(issueOrder)
148
- const text = String(query ?? '').trim() || ISSUE_QUERY_DEFAULT
149
- const model = issueFilterModel(issues, tokenFilterState(text, 'issue'), { sessions, defaultSection: '' })
150
- return {
151
- enabled: issuesEnabledForReview(),
152
- stores: issueStoresForReview(),
153
- ...paginateReview(issues, model.shown, model, requestedPage, {
154
- domain: 'issues', issues, sessions: sessions.map((session) => session.id),
155
- }),
156
- }
157
- }
158
-
159
- const byNewest = (a: any, b: any): number => Number(b.filterKind === EVAL_FILTER_KIND.RESULT) - Number(a.filterKind === EVAL_FILTER_KIND.RESULT)
160
- || String(b.ts ?? '').localeCompare(String(a.ts ?? ''))
161
- || String(a.node ?? '').localeCompare(String(b.node ?? ''))
162
- || String(a.scenario ?? '').localeCompare(String(b.scenario ?? ''))
163
-
164
- export function trunkEvalReviewItems(nodes: any[]): ReviewItem[] {
165
- const items: any[] = []
166
- for (const node of nodes ?? []) {
167
- const latest = new Map<string, any>()
168
- for (const reading of node.evals ?? []) if (!latest.has(reading.scenario)) latest.set(reading.scenario, reading)
169
- for (const scenario of node.scenarios ?? []) {
170
- const reading = latest.get(scenario.name)
171
- if (!reading) {
172
- items.push({
173
- scenario: scenario.name,
174
- expected: scenario.expected,
175
- tags: scenario.tags,
176
- node: node.id,
177
- hue: node.hue,
178
- filterKind: EVAL_FILTER_KIND.BLIND,
179
- })
180
- continue
181
- }
182
- items.push({
183
- ...reading,
184
- expected: scenario.expected ?? reading.expected,
185
- tags: scenario.tags,
186
- state: evalReviewState(reading),
187
- node: node.id,
188
- hue: node.hue,
189
- filterKind: EVAL_FILTER_KIND.RESULT,
190
- })
191
- }
192
- }
193
- return items.sort(byNewest)
194
- }
195
-
196
- export function scopedEvalReviewItems(model: SessionEvals): ReviewItem[] {
197
- const items: any[] = []
198
- for (const node of model.nodes ?? []) {
199
- const latest = new Map<string, any>()
200
- for (const reading of node.evals ?? []) if (!latest.has(reading.scenario)) latest.set(reading.scenario, reading)
201
- for (const scenario of node.scenarios ?? []) {
202
- const reading = latest.get(scenario.name)
203
- if (!reading) {
204
- items.push({
205
- scenario: scenario.name,
206
- expected: scenario.expected,
207
- tags: scenario.tags,
208
- impact: scenario.impact,
209
- node: node.id,
210
- hue: node.hue,
211
- filterKind: EVAL_FILTER_KIND.BLIND,
212
- })
213
- continue
214
- }
215
- const item = {
216
- ...reading,
217
- expected: scenario.expected ?? reading.expected,
218
- tags: scenario.tags,
219
- impact: scenario.impact,
220
- state: evalReviewState(reading),
221
- node: node.id,
222
- hue: node.hue,
223
- filterKind: EVAL_FILTER_KIND.RESULT,
224
- }
225
- items.push(item)
226
- }
227
- }
228
- return items.sort(byNewest)
229
- }
230
-
231
- const evalItemKey = (item: any): string => `${String(item?.node ?? '')}\0${String(item?.scenario ?? '')}`
232
-
233
- function evalNeighbor(item: any, stateOf: (row: { node: string; scenario: string }) => string): EvalNeighbor {
234
- return {
235
- node: String(item.node),
236
- scenario: String(item.scenario),
237
- state: stateOf(item),
238
- }
239
- }
240
-
241
- // `sequence` is the whole measured population in list order; `stateOf` answers only for rows whose freshness
242
- // was actually computed. On a focused build those are two different sets — the sequence spans the scope, the
243
- // states cover the selected row and its window — which is exactly why the state is looked up rather than
244
- // carried: a row this response does not render contributes its POSITION and nothing else.
245
- export function boundedEvalNeighbors(
246
- sequence: { node: string; scenario: string }[],
247
- node: string,
248
- scenario: string,
249
- stateOf: (row: { node: string; scenario: string }) => string,
250
- want = 5,
251
- ) {
252
- const items = sequence
253
- const key = `${node}\0${scenario}`
254
- const index = items.findIndex((item) => evalItemKey(item) === key)
255
- if (index < 0) return { prev: [], next: [], total: items.length, index: null, order: 'default' as const }
256
- const before = index
257
- const after = items.length - index - 1
258
- const take = Math.min(want, before + after)
259
- const nextN = Math.min(after, Math.max(Math.ceil(take / 2), take - before))
260
- const prevN = Math.min(before, take - nextN)
261
- return {
262
- prev: items.slice(index - prevN, index).reverse().map((item) => evalNeighbor(item, stateOf)),
263
- next: items.slice(index + 1, index + 1 + nextN).map((item) => evalNeighbor(item, stateOf)),
264
- total: items.length,
265
- index,
266
- order: 'default' as const,
267
- }
268
- }
269
-
270
- export function projectEvalDetail(
271
- items: ReviewItem[],
272
- historySource: ReviewItem[],
273
- node: string,
274
- scenario: string,
275
- metadata: EvalDetailMetadata = {},
276
- ): EvalDetailReview {
277
- const results = items.filter((item: any) => item.filterKind === EVAL_FILTER_KIND.RESULT)
278
- const selected = results.find((item) => evalItemKey(item) === `${node}\0${scenario}`) ?? null
279
- const availability = selected
280
- ? 'measured'
281
- : items.some((item: any) => evalItemKey(item) === `${node}\0${scenario}` && item.filterKind === EVAL_FILTER_KIND.BLIND)
282
- ? 'unmeasured'
283
- : 'missing'
284
- const history = historySource.filter((reading: any) => String(reading.scenario) === scenario)
285
- const stateByKey = new Map(results.map((item: any) => [evalItemKey(item), String(item.state ?? evalReviewState(item))]))
286
- const sequence = metadata.sequence
287
- ?? results.map((item: any) => ({ node: String(item.node), scenario: String(item.scenario) }))
288
- const neighbors = boundedEvalNeighbors(sequence, node, scenario,
289
- (row) => stateByKey.get(evalItemKey(row)) ?? 'empty')
290
- const scope = metadata.scope ?? null
291
- const requestedScope = metadata.requestedScope ?? scope
292
- const scopeFallback = metadata.scopeFallback ?? null
293
- return {
294
- scope,
295
- requestedScope,
296
- scopeFallback,
297
- availability,
298
- selected,
299
- history,
300
- neighbors,
301
- revision: revisionOf({ scope, requestedScope, scopeFallback, availability, selected, history, neighbors, summary: metadata.summary, evalRevision: metadata.evalRevision }),
302
- ...(metadata.summary ? { summary: metadata.summary } : {}),
303
- ...(metadata.evalRevision ? { evalRevision: metadata.evalRevision } : {}),
304
- }
305
- }
306
-
307
- // the measured population in list order, from the freshness-free rows: a filed reading leads (newest first),
308
- // and the tie-breaks are the identity ones — the SAME comparison `byNewest` applies, over the only fields it
309
- // actually reads. Blind rows never enter, exactly as the detail's own `results` filter excludes them.
310
- export function measuredSequence(order: SessionEvalOrderRow[]): { node: string; scenario: string }[] {
311
- return order.filter((row) => row.ts)
312
- .sort((a, b) => String(b.ts ?? '').localeCompare(String(a.ts ?? ''))
313
- || a.node.localeCompare(b.node) || a.scenario.localeCompare(b.scenario))
314
- .map((row) => ({ node: row.node, scenario: row.scenario }))
315
- }
316
-
317
- // the nodes whose verdicts the response will publish: the selected row's, plus a window wide enough to
318
- // contain any neighbour boundedEvalNeighbors can choose (it takes at most five, split around the index).
319
- export function focusNodes(order: SessionEvalOrderRow[], node: string, scenario: string): string[] {
320
- const sequence = measuredSequence(order)
321
- const index = sequence.findIndex((row) => row.node === node && row.scenario === scenario)
322
- if (index < 0) return [node]
323
- return [...new Set([node, ...sequence.slice(Math.max(0, index - 6), index + 7).map((row) => row.node)])]
324
- }
325
-
326
- async function trunkEvalDetailReview(node: string, scenario: string, metadata: EvalDetailMetadata = {}): Promise<EvalDetailReview> {
327
- await getBoard()
328
- const snapshot = readReviewSnapshot()
329
- const sourceNode = snapshot.evalNodes.find((candidate) => candidate.id === node)
330
- return projectEvalDetail(trunkEvalReviewItems(snapshot.evalNodes), sourceNode?.readings ?? [], node, scenario, metadata)
331
- }
332
-
333
- export async function evalDetailReview(node: string, scenario: string, scope?: string | null): Promise<EvalDetailReview> {
334
- if (scope) {
335
- // A detail renders ONE row plus at most five neighbours, but owes the whole population's index/total.
336
- // So name the window from the freshness-free sequence and let only those nodes pay the freshness pass;
337
- // a build that finds a full cached model ignores the pick and answers from it instead.
338
- const model = await buildSessionEvals(scope, (order) => focusNodes(order, node, scenario))
339
- if (!model) return trunkEvalDetailReview(node, scenario, { requestedScope: scope, scopeFallback: 'trunk' })
340
- const sourceNode = model.nodes.find((candidate) => candidate.id === node)
341
- return projectEvalDetail(scopedEvalReviewItems(model), sourceNode?.evals ?? [], node, scenario, {
342
- scope,
343
- requestedScope: scope,
344
- summary: model.summary,
345
- evalRevision: model.evalRevision,
346
- ...(model.order ? { sequence: measuredSequence(model.order) } : {}),
347
- })
348
- }
349
- return trunkEvalDetailReview(node, scenario)
350
- }
351
-
352
- export function timelineEvalReviewItems(timeline: Awaited<ReturnType<typeof evalTimeline>>, node: string): ReviewItem[] {
353
- const declared = new Set(timeline.scenarios.map((scenario) => scenario.name))
354
- const latest = new Map<string, any>()
355
- for (const reading of timeline.readings) {
356
- if (declared.has(reading.scenario) && !latest.has(reading.scenario)) latest.set(reading.scenario, reading)
357
- }
358
- return [
359
- ...timeline.scenarios.filter((scenario) => !latest.has(scenario.name)).map((scenario) => ({
360
- ...scenario,
361
- scenario: scenario.name,
362
- node,
363
- filterKind: EVAL_FILTER_KIND.UNMEASURED,
364
- })),
365
- ...[...latest.values()].map((reading) => ({
366
- ...reading,
367
- state: evalReviewState(reading),
368
- node,
369
- filterKind: EVAL_FILTER_KIND.RESULT,
370
- filterKey: `${EVAL_FILTER_KIND.RESULT}:${reading.scenario}`,
371
- })),
372
- ...(timeline.dangling ?? []).map((track) => ({
373
- ...track,
374
- node,
375
- filterKind: EVAL_FILTER_KIND.DANGLING,
376
- filterKey: `${EVAL_FILTER_KIND.DANGLING}:${track.threadId}`,
377
- })),
378
- ]
379
- }
380
-
381
- async function timelineEvalReview(text: string, requestedPage: unknown) {
382
- const node = readToken(text, 'node')
383
- if (!node) return null
384
- const [timeline, sessions] = await Promise.all([evalTimeline(node), listSessions()])
385
- const items = timelineEvalReviewItems(timeline, node)
386
- const filtered = evalFilterModel(items, tokenFilterState(text, 'eval'), { sessions, defaultKind: 'all', defaultSection: '' })
387
- return {
388
- scope: null,
389
- view: 'timeline',
390
- node,
391
- hasEvalFile: timeline.hasEvalFile,
392
- gates: [],
393
- unknown: 0,
394
- ...paginateReview(items, filtered.shown, filtered, requestedPage, {
395
- domain: 'evals', view: 'timeline', node, timeline, sessions: sessions.map((session) => session.id),
396
- }),
397
- }
398
- }
399
-
400
- export async function evalsReview(query: string | undefined, requestedPage: unknown, options: { view?: string } = {}) {
401
- const text = String(query ?? '').trim() || EVAL_QUERY_DEFAULT
402
- if (options.view === 'timeline') return timelineEvalReview(text, requestedPage)
403
- const scope = readToken(text, 'scope') || null
404
- if (scope) {
405
- const model = await buildSessionEvals(scope)
406
- if (!model) return null
407
- const items = scopedEvalReviewItems(model)
408
- const sessions = await listSessions()
409
- const filtered = evalFilterModel(items, tokenFilterState(text, 'eval'), { sessions, defaultKind: 'all', defaultSection: '' })
410
- return {
411
- scope,
412
- gates: [],
413
- unknown: model.nodes.reduce((count, node) => count + (node.unknownCoverage?.length ?? 0), 0),
414
- summary: model.summary,
415
- evalRevision: model.evalRevision,
416
- ...paginateReview(items, filtered.shown, filtered, requestedPage, {
417
- domain: 'evals', scope, query: text, gates: [], summary: model.summary,
418
- evalRevision: model.evalRevision, sessions: sessions.map((session) => session.id),
419
- }),
420
- }
421
- }
422
- if (!hasReviewSnapshot()) await getBoard()
423
- const sessions = await listSessions()
424
- const items = trunkEvalReviewItems(readReviewSnapshot().evalNodes)
425
- const filtered = evalFilterModel(items, tokenFilterState(text, 'eval'), { sessions, defaultKind: 'all', defaultSection: '' })
426
- return {
427
- scope: null,
428
- gates: [],
429
- unknown: 0,
430
- ...paginateReview(items, filtered.shown, filtered, requestedPage, {
431
- domain: 'evals', items, sessions: sessions.map((session) => session.id),
432
- }),
433
- }
434
- }
@@ -1,35 +0,0 @@
1
- import { spawnSync } from 'node:child_process'
2
-
3
- // tmux presence is the primitive we actually depend on, so probe THAT rather than assuming by platform:
4
- // this also catches a bare POSIX box that simply hasn't installed tmux, not only native Windows.
5
- export function hasTmux(): boolean {
6
- try {
7
- return spawnSync('tmux', ['-V'], { stdio: 'ignore' }).status === 0
8
- } catch {
9
- return false
10
- }
11
- }
12
-
13
- // Pure so it is unit-testable without spawning or exiting: null = runtime OK; otherwise the stderr lines.
14
- // The pointer branches on platform because the honest repair differs — WSL2 on Windows (no POSIX analog at
15
- // all), install-tmux on a POSIX host that merely lacks it.
16
- export function sessionRuntimeBlock(env: { hasTmux: boolean; platform: string }): string[] | null {
17
- if (env.hasTmux) return null
18
- const lines = ['spex: the session runtime needs a POSIX host (tmux, bash, and unix-domain sockets).']
19
- if (env.platform === 'win32') {
20
- lines.push('Native Windows has no analog for these — run SpexCode under WSL2 (a real Linux kernel):')
21
- lines.push(' wsl --install # then, in the Ubuntu shell: nvm install 22 && npm i -g spexcode')
22
- } else {
23
- lines.push('tmux is not on PATH — install it and retry (e.g. `apt install tmux`, `brew install tmux`).')
24
- }
25
- return lines
26
- }
27
-
28
- // Called at the top of the session-launching command path (`spex serve`). Exits 69 (EX_UNAVAILABLE: a
29
- // required support program does not exist) — a distinct, honest code, not a swallowed error or a stacktrace.
30
- export function assertSessionRuntime(): void {
31
- const block = sessionRuntimeBlock({ hasTmux: hasTmux(), platform: process.platform })
32
- if (!block) return
33
- for (const line of block) console.error(line)
34
- process.exit(69)
35
- }
@@ -1,88 +0,0 @@
1
- import { randomUUID } from 'node:crypto'
2
- import { spawn } from 'node:child_process'
3
- import { closeSync, mkdirSync, openSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'
4
- import { dirname, join } from 'node:path'
5
- import { repoRoot } from './git.js'
6
- import { readJsonConfig, runtimeRoot } from './layout.js'
7
- import { processStartToken, writeDetachedRuntimeReceipt, type ProcessIdentity } from './process-identity.js'
8
-
9
- export type BackendInstanceRecord = {
10
- version: 1
11
- instanceId: string
12
- pid: number
13
- startToken: string
14
- projectRoot: string
15
- startedAt: string
16
- }
17
-
18
- const backendInstancesDir = () => join(runtimeRoot(), 'backend-instances')
19
- const backendInstancePath = (instanceId: string) => join(backendInstancesDir(), `${instanceId}.json`)
20
-
21
- const atomicJson = (path: string, value: unknown) => {
22
- mkdirSync(dirname(path), { recursive: true })
23
- const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`
24
- writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 })
25
- renameSync(tmp, path)
26
- }
27
-
28
- export function readBackendInstanceRecords(root = repoRoot()): BackendInstanceRecord[] {
29
- let names: string[]
30
- try { names = readdirSync(backendInstancesDir()) } catch { return [] }
31
- return names.flatMap((name) => {
32
- try {
33
- const rec = readJsonConfig(join(backendInstancesDir(), name)) as BackendInstanceRecord
34
- return rec?.version === 1 && rec.projectRoot === root ? [rec] : []
35
- } catch { return [] }
36
- })
37
- }
38
-
39
- export function registerBackendInstance(instanceId: string, pid = process.pid, root = repoRoot()): BackendInstanceRecord {
40
- const startToken = processStartToken(pid)
41
- if (!startToken) throw new Error(`cannot identify backend supervisor PID ${pid}`)
42
- const record: BackendInstanceRecord = { version: 1, instanceId, pid, startToken, projectRoot: root, startedAt: new Date().toISOString() }
43
- atomicJson(backendInstancePath(instanceId), record)
44
- return record
45
- }
46
-
47
- export function unregisterBackendInstance(instanceId: string, pid = process.pid): void {
48
- try {
49
- const record = readJsonConfig(backendInstancePath(instanceId)) as BackendInstanceRecord
50
- if (record.instanceId === instanceId && record.pid === pid && record.startToken === processStartToken(pid)) rmSync(backendInstancePath(instanceId), { force: true })
51
- } catch { /* not ours / already removed */ }
52
- }
53
-
54
- export function spawnDetachedRuntime(opts: {
55
- cwd: string
56
- logFile: string
57
- pidFile: string
58
- receiptFile: string
59
- command: string
60
- args: string[]
61
- env?: NodeJS.ProcessEnv
62
- }): ProcessIdentity {
63
- mkdirSync(dirname(opts.logFile), { recursive: true })
64
- mkdirSync(dirname(opts.pidFile), { recursive: true })
65
- const log = openSync(opts.logFile, 'a', 0o600)
66
- let child: ReturnType<typeof spawn>
67
- try {
68
- child = spawn(opts.command, opts.args, {
69
- cwd: opts.cwd,
70
- env: opts.env ?? process.env,
71
- detached: true,
72
- stdio: ['ignore', log, log],
73
- })
74
- } finally { closeSync(log) }
75
- child.once('error', () => {})
76
- if (!child.pid) throw new Error(`could not spawn detached shared runtime: ${opts.command}`)
77
- child.unref()
78
- try {
79
- const identity = writeDetachedRuntimeReceipt(child.pid, opts.receiptFile)
80
- writeFileSync(opts.pidFile, `${child.pid}\n`, { mode: 0o600 })
81
- return { pid: identity.pid, startToken: identity.startToken }
82
- } catch (error) {
83
- try { child.kill('SIGTERM') } catch { /* exact just-spawned child already exited */ }
84
- rmSync(opts.pidFile, { force: true })
85
- rmSync(opts.receiptFile, { force: true })
86
- throw error
87
- }
88
- }
@@ -1,80 +0,0 @@
1
- // throwaway benchmark harness for spec-search — drives the REAL `spex spec search --json` over the holdout
2
- // cases and reports recall@1, recall@3, MRR. The cases live in the node's eval.md.
3
- //
4
- // Labels are node LEAF names, matched with the same de-collision rule the loader applies (specs.ts reId):
5
- // a returned id matches a label if it IS the label or ends with `_<label>` — so a bare leaf keeps matching
6
- // after collision-qualification renames it (e.g. a colliding `probe` leaf → `a_probe`). A label may
7
- // also be written pre-qualified to pin one collision branch.
8
- import { execFileSync } from 'node:child_process'
9
- import { fileURLToPath } from 'node:url'
10
- import { dirname, join } from 'node:path'
11
-
12
- const BIN = join(dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'spex.mjs')
13
-
14
- const CASES = [
15
- ['exit-cleanup', "does /exit remove the session's worktree and tmux, or just orphan them?", ['session-console']],
16
- ['owner-at-edit', 'how does an agent learn which spec governs a file it just edited?', ['spec-of-file']],
17
- ['main-block', 'what stops an agent from committing or merging straight into main?', ['main-guard']],
18
- ['main-escape', 'the escape hatch that lets seeding run on the main branch', ['main-guard']],
19
- ['inter-agent-msg', 'how do two running agent sessions send messages to each other?', ['agent-reply-channel', 'comms']],
20
- ['search-hidden-node', 'keyboard shortcut to find a node hidden inside a collapsed subtree', ['keyboard-nav']],
21
- ['session-order', 'how is the order of sessions in the session list decided?', ['session-console']],
22
- ['node-status', 'what makes a node show as pending vs active vs merged vs drift?', ['spec-node-states']],
23
- ['dashboard-backend', 'how does the dashboard reach the backend API and on which port?', ['api-endpoint']],
24
- ['loss-measured', "how is a node's loss measured and its scenarios scored?", ['eval-core']],
25
- ['launch-injection', "what context gets injected into a freshly launched agent's prompt?", ['injected-context']],
26
- ['read-before-code', 'the one-shot nudge that makes an agent read its spec before touching code', ['spec-first']],
27
- ['hot-reload', 'zero-downtime backend reload without dropping connections', ['supervisor']],
28
- ['many-owners', 'can several specs own the same code file, and what happens if too many do?', ['governed-related']],
29
- ['active-spec-search', 'an injected sub-agent that searches specs for the agent, the spec analog of Explore', ['spec-search']],
30
- ['declare-done', 'how does a worker declare it is done', ['state']],
31
- ]
32
-
33
- // de-collision-aware label match: exact id, or the id's trailing `_`-suffix is the label.
34
- const matches = (id, label) => id === label || id.endsWith('_' + label)
35
-
36
- let r1 = 0, r3 = 0, mrr = 0
37
- const rows = []
38
- for (const [name, query, expect] of CASES) {
39
- const out = execFileSync('node', [BIN, 'spec', 'search', query, '--json', '--limit', '10'], { encoding: 'utf8' })
40
- const results = JSON.parse(out)
41
- const ids = results.map((x) => x.id)
42
- const rank = ids.findIndex((id) => expect.some((label) => matches(id, label))) + 1 // 1-based; 0 = not found
43
- const hit1 = rank === 1
44
- const hit3 = rank >= 1 && rank <= 3
45
- if (hit1) r1++
46
- if (hit3) r3++
47
- if (rank >= 1) mrr += 1 / rank
48
- rows.push({ name, rank: rank || '—', top: ids.slice(0, 3).join(', '), expect: expect.join('|') })
49
- }
50
- const n = CASES.length
51
- for (const row of rows) {
52
- const mark = row.rank === 1 ? '✓1' : (typeof row.rank === 'number' && row.rank <= 3 ? '·3' : (row.rank === '—' ? '✗ ' : `·${row.rank}`))
53
- console.log(`${mark} ${row.name.padEnd(20)} want=${row.expect.padEnd(22)} rank=${String(row.rank).padStart(2)} top3: ${row.top}`)
54
- }
55
-
56
- // CJK-positive: the corpus is mostly English but a few nodes carry Chinese prose — the root `spexcode`
57
- // node's body repeats 节点 throughout. A per-CJK-character tokenizer must let a Chinese content word reach
58
- // that node; `spex spec search "节点"` MUST return `spexcode` in results (the bug: the old tokenizer split on
59
- // [^a-z0-9]+ and discarded all CJK, so every Chinese query dead-ended at zero results).
60
- const cjkPos = JSON.parse(execFileSync('node', [BIN, 'spec', 'search', '节点', '--json', '--limit', '10'], { encoding: 'utf8' }))
61
- const cjkPosRank = cjkPos.map((x) => x.id).findIndex((id) => matches(id, 'spexcode')) + 1
62
- const cjkPosPass = cjkPosRank >= 1
63
- console.log(`${cjkPosPass ? `✓${cjkPosRank}` : '✗ '} cjk-positive want=spexcode(节点) rank=${cjkPosRank || '—'} top3: ${cjkPos.slice(0, 3).map((x) => x.id).join(', ')}`)
64
-
65
- // zero-result fail-loud regression: a CJK query whose characters appear NOWHERE in the corpus (会/话) returns
66
- // nothing, and the zero-result message must still carry the corpus-is-English translate-and-retry fact
67
- // (fail-loud) plus the browse-all next step — CJK support does not suppress the honest zero-result route.
68
- // (No nearest titles: a CJK query has nothing to be lexically near.)
69
- const cjk = execFileSync('node', [BIN, 'spec', 'search', '会话'], { encoding: 'utf8' })
70
- const cjkPass = cjk.includes('corpus is English') && cjk.includes('spex graph')
71
- console.log(`${cjkPass ? '✓ ' : '✗ '} cjk-zero-result want=corpus-is-English + spex-tree ${cjkPass ? 'both present' : 'MISSING: ' + cjk.trim()}`)
72
-
73
- // zero-result typo routing: an English typo that matches nothing must surface the nearest node titles
74
- // (per-word edit distance) plus the browse-all next step, so a typo routes forward instead of dead-ending.
75
- const typo = execFileSync('node', [BIN, 'spec', 'search', 'kyeboard'], { encoding: 'utf8' })
76
- const typoPass = typo.includes('nearest titles') && typo.includes('keyboard-nav') && typo.includes('spex graph')
77
- console.log(`${typoPass ? '✓ ' : '✗ '} typo-zero-result want=nearest-titles(keyboard-nav) + spex-tree ${typoPass ? 'both present' : 'MISSING: ' + typo.trim()}`)
78
-
79
- console.log('—'.repeat(72))
80
- console.log(`recall@1 = ${r1}/${n} = ${(r1 / n).toFixed(3)} recall@3 = ${r3}/${n} = ${(r3 / n).toFixed(3)} MRR = ${(mrr / n).toFixed(3)} cjk-positive = ${cjkPosPass ? 'PASS' : 'FAIL'} cjk-hint = ${cjkPass ? 'PASS' : 'FAIL'} typo-route = ${typoPass ? 'PASS' : 'FAIL'}`)