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,672 +0,0 @@
1
- import { readFileSync, readdirSync, existsSync } from 'node:fs'
2
- import { readFile, readdir } from 'node:fs/promises'
3
- import { createHash } from 'node:crypto'
4
- import { join, relative, basename } from 'node:path'
5
- import { mintIds, parseFrontmatter } from '../../spec-cli/src/specs.js'
6
- import { parseRelation, type RelationEntry } from '../../spec-cli/src/anchors.js'
7
- import { treeTextFiles } from '../../spec-cli/src/git.js'
8
-
9
- export const EVAL_FILE = 'eval.md'
10
- export const SIDECAR_FILE = 'evals.ndjson'
11
-
12
- export type ScenarioTestReference = {
13
- path: string
14
- name?: string
15
- }
16
-
17
- export type Scenario = {
18
- name: string
19
- description: string
20
- expected: string
21
- tags?: string[]
22
- test?: ScenarioTestReference
23
- code?: string[]
24
- related?: string[]
25
- }
26
-
27
- export const SCENARIO_PROJECTION = 'spex.eval.scenario-index'
28
- export const SCENARIO_SCHEMA_VERSION = 1
29
- export type ScenarioSemanticRow = {
30
- node: string
31
- name: string
32
- description: string
33
- expected: string
34
- scenarioHash: string
35
- code: RelationEntry[]
36
- related: RelationEntry[]
37
- tags: string[]
38
- }
39
- export type ScenarioMeasurementRow = { test: ScenarioTestReference | null }
40
- export type ScenarioProjectionRow = {
41
- semantic: ScenarioSemanticRow
42
- measurement: ScenarioMeasurementRow
43
- }
44
- export type ScenarioProjectionNode = {
45
- id: string
46
- code: RelationEntry[]
47
- related: RelationEntry[]
48
- }
49
- export type ScenarioProjectionProvenance = { head: string | null; treeSha: string | null }
50
- export type ScenarioProjection = {
51
- projection: typeof SCENARIO_PROJECTION
52
- schemaVersion: typeof SCENARIO_SCHEMA_VERSION
53
- provenance: ScenarioProjectionProvenance
54
- nodes: ScenarioProjectionNode[]
55
- semanticIndexHash: string
56
- fullIndexHash: string
57
- planningIndexHash: string
58
- rows: ScenarioProjectionRow[]
59
- }
60
-
61
- export type EvalNode = {
62
- id: string
63
- dir: string
64
- evalPath: string
65
- sidecarPath: string
66
- scenarios: Scenario[]
67
- evalSource?: string
68
- specSource?: string
69
- }
70
-
71
- const SCENARIO_KEYS = ['name', 'description', 'expected', 'tags', 'test', 'code', 'related'] as const
72
- type ScenarioKey = (typeof SCENARIO_KEYS)[number]
73
- const LIST_KEYS: readonly ScenarioKey[] = ['tags', 'code', 'related']
74
- const TEST_KEYS = ['path', 'name'] as const
75
- type TestKey = (typeof TEST_KEYS)[number]
76
-
77
- type RawTestObject = {
78
- fields: Partial<Record<TestKey, string>>
79
- unknownKeys: string[]
80
- duplicateKeys: string[]
81
- malformed: string[]
82
- }
83
-
84
- type RawFieldLocation = { startLine: number; endLine: number; indent: string }
85
-
86
- type RawItem = {
87
- fields: Partial<Record<ScenarioKey, string>>
88
- testObject?: RawTestObject
89
- unknownKeys: string[]
90
- duplicateKeys: string[]
91
- malformed: string[]
92
- locations: Partial<Record<ScenarioKey, RawFieldLocation>>
93
- fieldIndent?: string
94
- }
95
-
96
- const leadingIndent = (line: string): string => line.match(/^[ \t]*/)?.[0] ?? ''
97
-
98
- // Keep parsing and validation on the same structural walk.
99
- function walkScenarios(src: string): { hasFrontmatter: boolean; hasKey: boolean; items: RawItem[]; malformed: string[] } {
100
- const normalized = src.replace(/\r\n?/g, '\n')
101
- const m = normalized.match(/^---\n([\s\S]*?)\n---(?:\n|$)/)
102
- if (!m) return { hasFrontmatter: false, hasKey: false, items: [], malformed: [] }
103
- const lines = m[1].split('\n')
104
- const scenarioKeys = lines.flatMap((line, index) => /^scenarios:\s*$/.test(line) ? [index] : [])
105
- let i = scenarioKeys[0] ?? -1
106
- if (i < 0) return { hasFrontmatter: true, hasKey: false, items: [], malformed: [] }
107
- const items: RawItem[] = []
108
- const malformed = scenarioKeys.length > 1
109
- ? [`duplicate top-level \`scenarios:\` key (${scenarioKeys.length}×) — eval.md must have exactly one declaration list`]
110
- : []
111
- let cur: RawItem | null = null
112
- let itemIndent = -1 // the indent of the `- ` that starts each scenario (set by the first one)
113
- for (i++; i < lines.length; i++) {
114
- const line = lines[i]
115
- if (!line.trim()) continue
116
- const prefix = leadingIndent(line)
117
- if (prefix.includes('\t')) malformed.push(`line ${i + 2}: tab indentation is not valid in an eval.md scenario mapping`)
118
- const indent = prefix.length
119
- if (indent === 0) break // dedented to another top-level key — scenarios block is done
120
- const trimmed = line.trim()
121
- const dash = trimmed.startsWith('- ') || trimmed === '-'
122
- if (dash && (itemIndent < 0 || indent <= itemIndent)) {
123
- cur = {
124
- fields: {}, unknownKeys: [], duplicateKeys: [], malformed: [], locations: {},
125
- ...(trimmed.slice(1).trim() ? { fieldIndent: `${prefix} ` } : {}),
126
- }
127
- items.push(cur)
128
- itemIndent = indent
129
- const inline = trimmed.slice(1).trim() // text after the dash
130
- if (inline) i = assignField(cur, inline, lines, i, indent, true)
131
- continue
132
- }
133
- if (!cur) {
134
- if (!trimmed.startsWith('#')) malformed.push(`invalid scenarios entry \`${trimmed}\` before the first scenario`)
135
- continue
136
- }
137
- if (!trimmed.startsWith('#')) {
138
- if (cur.fieldIndent === undefined) cur.fieldIndent = prefix
139
- else if (prefix !== cur.fieldIndent) {
140
- cur.malformed.push(`inconsistent scenario field indentation: expected ${cur.fieldIndent.length} spaces, got ${prefix.length}`)
141
- }
142
- }
143
- i = assignField(cur, trimmed, lines, i, indent)
144
- }
145
- return { hasFrontmatter: true, hasKey: true, items, malformed }
146
- }
147
-
148
- function assignField(cur: RawItem, kv: string, lines: string[], idx: number, keyIndent: number, inline = false): number {
149
- const f = kv.match(/^([A-Za-z_][\w-]*):\s*(.*)$/)
150
- if (!f) {
151
- if (!kv.startsWith('#')) cur.malformed.push(`invalid scenario entry \`${kv}\``)
152
- return idx
153
- }
154
- const key = f[1]
155
- const scenarioKey = (SCENARIO_KEYS as readonly string[]).includes(key) ? key as ScenarioKey : null
156
- const finish = (parserEnd: number, locationEnd = parserEnd): number => {
157
- if (scenarioKey) {
158
- if (cur.locations[scenarioKey]) cur.duplicateKeys.push(key)
159
- else cur.locations[scenarioKey] = {
160
- startLine: idx,
161
- endLine: locationEnd,
162
- indent: `${leadingIndent(lines[idx])}${inline ? ' ' : ''}`,
163
- }
164
- }
165
- return parserEnd
166
- }
167
- if (key === 'test') {
168
- const raw = f[2].trim()
169
- if (!raw) {
170
- const parsed = emptyTestObject()
171
- let childIndent = -1
172
- let lastChild = idx
173
- let j = idx + 1
174
- for (; j < lines.length; j++) {
175
- const line = lines[j]
176
- if (!line.trim()) continue
177
- const prefix = leadingIndent(line)
178
- if (prefix.includes('\t')) parsed.malformed.push(`tab indentation is not valid in nested \`test\` metadata`)
179
- const indent = prefix.length
180
- if (indent <= keyIndent) break
181
- lastChild = j
182
- if (childIndent < 0) childIndent = indent
183
- if (indent !== childIndent) {
184
- parsed.malformed.push(`invalid nested test object entry \`${line.trim()}\``)
185
- continue
186
- }
187
- assignTestField(parsed, line.trim())
188
- }
189
- cur.testObject = parsed
190
- return finish(j - 1, lastChild)
191
- }
192
- if (raw.startsWith('{') || raw.endsWith('}')) {
193
- cur.testObject = parseFlowTestObject(raw)
194
- return finish(idx)
195
- }
196
- }
197
- if ((LIST_KEYS as readonly string[]).includes(key) && f[2].trim() === '') {
198
- const items: string[] = []
199
- let lastItem = idx
200
- let j = idx + 1
201
- for (; j < lines.length; j++) {
202
- const l = lines[j]
203
- if (!l.trim()) continue
204
- const prefix = leadingIndent(l)
205
- if (prefix.includes('\t')) cur.malformed.push(`tab indentation is not valid in \`${key}\` metadata`)
206
- const ind = prefix.length
207
- if (ind <= keyIndent) break
208
- const it = l.trim().match(/^-\s*(.+)$/)
209
- if (!it) break
210
- items.push(unquote(it[1]))
211
- lastItem = j
212
- }
213
- if (items.length) { cur.fields[key as ScenarioKey] = items.join(','); return finish(j - 1, lastItem) }
214
- }
215
- let value: string
216
- let end = idx
217
- const block = f[2].match(/^([|>])[+-]?\s*$/)
218
- if (block) {
219
- const fold = block[1] === '>'
220
- const body: string[] = []
221
- let base = -1, j = idx + 1
222
- for (; j < lines.length; j++) {
223
- const l = lines[j]
224
- const spaces = l.match(/^ */)?.[0] ?? ''
225
- const tabBeforeContent = l[spaces.length] === '\t'
226
- const requiredIndent = base < 0 ? keyIndent + 1 : base
227
- const tabInIndent = tabBeforeContent && spaces.length < requiredIndent
228
- if (tabInIndent) cur.malformed.push(`tab indentation is not valid in block scalar \`${key}\``)
229
- if (!l.trim() && !tabBeforeContent) { body.push(''); continue }
230
- // A tab after the required indent is scalar content, not indentation.
231
- const ind = tabInIndent ? leadingIndent(l).length : spaces.length
232
- if (ind <= keyIndent) break // dedented to a sibling field / next item → the block is done
233
- if (base < 0) base = ind
234
- else if (ind < base) cur.malformed.push(`inconsistent block scalar indentation in \`${key}\`: expected at least ${base} spaces, got ${ind}`)
235
- body.push(l.slice(base))
236
- }
237
- while (body.length && body[body.length - 1] === '') body.pop() // strip trailing blanks
238
- value = fold ? body.join(' ').replace(/\s+/g, ' ').trim() : body.join('\n')
239
- end = j - 1
240
- } else {
241
- value = key === 'test' ? testValue(f[2]) : unquote(f[2])
242
- }
243
- if ((SCENARIO_KEYS as readonly string[]).includes(key)) cur.fields[key as ScenarioKey] = value
244
- else cur.unknownKeys.push(key)
245
- return finish(end)
246
- }
247
-
248
- const unquote = (s: string) => s.replace(/^["'](.*)["']$/, '$1').trim()
249
-
250
- const emptyTestObject = (): RawTestObject => ({ fields: {}, unknownKeys: [], duplicateKeys: [], malformed: [] })
251
-
252
- function testValue(raw: string, opaque = false): string {
253
- const value = raw.trim()
254
- if (value.startsWith('"') && value.endsWith('"')) {
255
- try { return JSON.parse(value) } catch { /* fall through to the parser's legacy quote handling */ }
256
- }
257
- const quoted = value.match(/^(["'])([\s\S]*)\1$/)
258
- return quoted ? quoted[2] : opaque ? value : unquote(value)
259
- }
260
-
261
- function assignTestField(obj: RawTestObject, entry: string): void {
262
- const f = entry.match(/^([A-Za-z_][\w-]*):\s*(.*)$/)
263
- if (!f) { obj.malformed.push(`invalid test object entry \`${entry}\``); return }
264
- const key = f[1]
265
- if (!(TEST_KEYS as readonly string[]).includes(key)) { obj.unknownKeys.push(key); return }
266
- if (key in obj.fields) obj.duplicateKeys.push(key)
267
- obj.fields[key as TestKey] = testValue(f[2], key === 'name')
268
- }
269
-
270
- // Test names may contain commas inside quotes.
271
- function parseFlowTestObject(raw: string): RawTestObject {
272
- const obj = emptyTestObject()
273
- if (!raw.startsWith('{') || !raw.endsWith('}')) {
274
- obj.malformed.push('`test` object must be a mapping with exactly `path` and `name`')
275
- return obj
276
- }
277
- const body = raw.slice(1, -1).trim()
278
- if (!body) return obj
279
- const entries: string[] = []
280
- let start = 0
281
- let quote = ''
282
- let escaped = false
283
- for (let i = 0; i < body.length; i++) {
284
- const ch = body[i]
285
- if (quote) {
286
- if (escaped) { escaped = false; continue }
287
- if (ch === '\\') { escaped = true; continue }
288
- if (ch === quote) quote = ''
289
- continue
290
- }
291
- if (ch === '"' || ch === "'") { quote = ch; continue }
292
- if (ch === ',') { entries.push(body.slice(start, i).trim()); start = i + 1 }
293
- }
294
- if (quote) obj.malformed.push('`test` object has an unterminated quoted value')
295
- entries.push(body.slice(start).trim())
296
- for (const entry of entries) assignTestField(obj, entry)
297
- return obj
298
- }
299
-
300
- function normalizedTest(it: RawItem): ScenarioTestReference | undefined {
301
- if (it.testObject) {
302
- const path = it.testObject.fields.path
303
- if (!path) return undefined
304
- const name = it.testObject.fields.name
305
- return { path, ...(name ? { name } : {}) }
306
- }
307
- const path = it.fields.test
308
- return path ? { path } : undefined
309
- }
310
-
311
- const normSemantic = (s: string) => s.replace(/\s+/g, ' ').trim()
312
- export function scenarioHash(s: Pick<Scenario, 'description' | 'expected'>): string {
313
- return createHash('sha256').update(`${normSemantic(s.description)}\n${normSemantic(s.expected)}`, 'utf8').digest('hex')
314
- }
315
-
316
- export type ScenarioCodeAxis = { entries: RelationEntry[]; paths: string[]; problems: string[] }
317
- export type ScenarioCodeAxisSource = readonly string[] | readonly RelationEntry[]
318
- export function scenarioCodeAxis(scenarioCode: readonly string[] | undefined, nodeCode: ScenarioCodeAxisSource = []): ScenarioCodeAxis {
319
- const parsed = scenarioCode?.length
320
- ? parseRelation([...scenarioCode], 'code')
321
- : nodeCode.length && typeof nodeCode[0] !== 'string'
322
- ? { entries: (nodeCode as readonly RelationEntry[]).map((e) => ({ path: e.path, selectors: [...e.selectors] })), problems: [] }
323
- : parseRelation([...(nodeCode as readonly string[])], 'code')
324
- const { entries, problems } = parsed
325
- return { entries, paths: entries.map((e) => e.path), problems }
326
- }
327
-
328
- function parseCodeList(raw: string): string[] {
329
- return raw.replace(/^\[|\]$/g, '').split(',').map((s) => unquote(s.trim())).filter(Boolean)
330
- }
331
-
332
- export function parseScenarios(src: string): Scenario[] {
333
- return walkScenarios(src).items
334
- .map((it): Scenario => {
335
- const tags = it.fields.tags ? parseCodeList(it.fields.tags) : []
336
- const code = it.fields.code ? parseCodeList(it.fields.code) : []
337
- const related = it.fields.related ? parseCodeList(it.fields.related) : []
338
- const test = normalizedTest(it)
339
- return {
340
- name: it.fields.name ?? '',
341
- description: it.fields.description ?? '',
342
- expected: it.fields.expected ?? '',
343
- ...(tags.length ? { tags } : {}),
344
- ...(test ? { test } : {}),
345
- ...(code.length ? { code } : {}),
346
- ...(related.length ? { related } : {}),
347
- }
348
- })
349
- .filter((s) => s.name)
350
- }
351
-
352
- const compareStable = (a: string, b: string): number => a < b ? -1 : a > b ? 1 : 0
353
- const relationRows = (raw: readonly string[] | undefined, relation: 'code' | 'related'): RelationEntry[] =>
354
- parseRelation([...(raw ?? [])], relation).entries.map((entry) => ({ path: entry.path, selectors: [...entry.selectors] }))
355
-
356
- const hashProjection = (value: unknown): string =>
357
- createHash('sha256').update(JSON.stringify(value), 'utf8').digest('hex')
358
-
359
- const semanticOnly = (row: ScenarioProjectionRow): ScenarioSemanticRow => row.semantic
360
- const frontmatterList = (value: string | string[] | undefined): string[] =>
361
- Array.isArray(value) ? value : value ? [value] : []
362
-
363
- function projectionNode(node: Pick<EvalNode, 'id' | 'specSource'>): ScenarioProjectionNode {
364
- const fm = node.specSource ? parseFrontmatter(node.specSource).fm : {}
365
- const code = parseRelation(frontmatterList(fm.code), 'code')
366
- const related = parseRelation(frontmatterList(fm.related), 'related')
367
- const problems = [...code.problems, ...related.problems]
368
- if (problems.length) {
369
- throw new Error(`node '${node.id}' has malformed spec relations:\n${problems.map((e) => ` - ${e}`).join('\n')}`)
370
- }
371
- return {
372
- id: node.id,
373
- code: code.entries.map((entry) => ({ path: entry.path, selectors: [...entry.selectors] })),
374
- related: related.entries.map((entry) => ({ path: entry.path, selectors: [...entry.selectors] })),
375
- }
376
- }
377
-
378
- export function scenarioProjection(
379
- nodes: readonly Pick<EvalNode, 'id' | 'scenarios' | 'evalSource' | 'specSource'>[],
380
- provenance: Partial<ScenarioProjectionProvenance> = {},
381
- ): ScenarioProjection {
382
- const nodeRows = nodes.map(projectionNode).sort((a, b) => compareStable(a.id, b.id))
383
- const rows: ScenarioProjectionRow[] = []
384
- for (const node of nodes) {
385
- if ('evalSource' in node && node.evalSource !== undefined) {
386
- const schemaErrors = validateScenarios(node.evalSource)
387
- const relationErrors = node.scenarios.flatMap((scenario) => [
388
- ...parseRelation(scenario.code ?? [], 'code').problems,
389
- ...parseRelation(scenario.related ?? [], 'related').problems,
390
- ])
391
- const errors = [...schemaErrors, ...relationErrors]
392
- if (errors.length) throw new Error(`node '${node.id}' has malformed eval.md:\n${errors.map((e) => ` - ${e}`).join('\n')}`)
393
- }
394
- for (const scenario of node.scenarios) rows.push({
395
- semantic: {
396
- node: node.id,
397
- name: scenario.name,
398
- description: scenario.description,
399
- expected: scenario.expected,
400
- scenarioHash: scenarioHash(scenario),
401
- code: relationRows(scenario.code, 'code'),
402
- related: relationRows(scenario.related, 'related'),
403
- tags: [...(scenario.tags ?? [])],
404
- },
405
- measurement: { test: scenario.test ? { ...scenario.test } : null },
406
- })
407
- }
408
- rows.sort((a, b) => {
409
- const node = compareStable(a.semantic.node, b.semantic.node)
410
- return node || compareStable(a.semantic.name, b.semantic.name)
411
- })
412
- const semanticRows = rows.map(semanticOnly)
413
- return {
414
- projection: SCENARIO_PROJECTION,
415
- schemaVersion: SCENARIO_SCHEMA_VERSION,
416
- provenance: { head: provenance.head ?? null, treeSha: provenance.treeSha ?? null },
417
- nodes: nodeRows,
418
- semanticIndexHash: hashProjection(semanticRows),
419
- fullIndexHash: hashProjection(rows),
420
- planningIndexHash: hashProjection({ nodes: nodeRows, rows }),
421
- rows,
422
- }
423
- }
424
-
425
- export function validateScenarios(src: string, tagLibrary: string[] = [], pathRoot?: string): string[] {
426
- const { hasFrontmatter, hasKey, items, malformed } = walkScenarios(src)
427
- if (!hasFrontmatter) return ['no frontmatter block — an eval.md must declare a `scenarios:` list']
428
- if (!hasKey) return ['frontmatter has no `scenarios:` key — declare at least one scenario']
429
- if (!items.length) return ['`scenarios:` declares no scenarios — add one (name + description + expected)']
430
- const errs: string[] = [...malformed]
431
- const counts = new Map<string, number>()
432
- const lib = tagLibrary.length ? ` (library: ${tagLibrary.join(', ')})` : ''
433
- items.forEach((it, idx) => {
434
- const label = it.fields.name ? `scenario '${it.fields.name}'` : `scenario #${idx + 1}`
435
- for (const k of ['name', 'description', 'expected'] as const) {
436
- if (!it.fields[k]?.trim()) errs.push(`${label}: missing required field \`${k}\``)
437
- }
438
- const tags = it.fields.tags ? parseCodeList(it.fields.tags) : []
439
- if (!tags.length) {
440
- errs.push(`${label}: missing required field \`tags\` — every scenario needs ≥1 tag from the library${lib}; pick one, or add a new tag to lint.scenarioTags in spexcode.json to create it`)
441
- } else if (tagLibrary.length) {
442
- for (const t of tags) if (!tagLibrary.includes(t)) {
443
- errs.push(`${label}: tag \`${t}\` is not in the configured tag library${lib} — use an existing tag, or add \`${t}\` to lint.scenarioTags in spexcode.json to create it`)
444
- }
445
- }
446
- for (const entry of it.malformed) errs.push(`${label}: ${entry}`)
447
- for (const d of it.duplicateKeys) errs.push(`${label}: duplicate field \`${d}\``)
448
- for (const u of it.unknownKeys) errs.push(`${label}: unknown field \`${u}\` (allowed: ${SCENARIO_KEYS.join(', ')})`)
449
- if (it.testObject) {
450
- for (const u of it.testObject.unknownKeys) errs.push(`${label}: unknown \`test\` field \`${u}\` (allowed: ${TEST_KEYS.join(', ')})`)
451
- for (const d of it.testObject.duplicateKeys) errs.push(`${label}: duplicate \`test.${d}\` field`)
452
- for (const e of it.testObject.malformed) errs.push(`${label}: ${e}`)
453
- for (const k of TEST_KEYS) if (!it.testObject.fields[k]?.length) errs.push(`${label}: \`test\` object missing required field \`${k}\``)
454
- } else if ('test' in it.fields && !it.fields.test?.length) {
455
- errs.push(`${label}: \`test\` scalar path must not be empty`)
456
- }
457
- const test = normalizedTest(it)
458
- if (test && pathRoot && !existsSync(join(pathRoot, test.path))) {
459
- errs.push(`${label}: \`test.path\` not found: ${test.path}`)
460
- }
461
- if (it.fields.name) counts.set(it.fields.name, (counts.get(it.fields.name) ?? 0) + 1)
462
- })
463
- for (const [n, c] of counts) if (c > 1) errs.push(`duplicate scenario name '${n}' (${c}×) — names must be unique within an eval.md`)
464
- return errs
465
- }
466
-
467
- export type ScenarioMeasurementMetadataMutation =
468
- | { scenario: string; insert: { test: string | { path: string; name: string } } }
469
- | { scenario: string; delete: 'test' }
470
-
471
- type ParsedMetadataMutation =
472
- | { scenario: string; action: 'insert'; test: ScenarioTestReference }
473
- | { scenario: string; action: 'delete' }
474
-
475
- const recordOf = (value: unknown): Record<string, unknown> | null =>
476
- value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null
477
-
478
- function parseMetadataMutation(value: unknown): ParsedMetadataMutation {
479
- const mutation = recordOf(value)
480
- if (!mutation || typeof mutation.scenario !== 'string' || !mutation.scenario.trim()) {
481
- throw new Error('a metadata mutation must name exactly one scenario with a non-empty `scenario` string')
482
- }
483
- const unknown = Object.keys(mutation).filter((key) => !['scenario', 'insert', 'delete'].includes(key))
484
- if (unknown.length) throw new Error(`metadata mutation has unknown field(s): ${unknown.join(', ')}`)
485
- const actions = ['insert', 'delete'].filter((key) => key in mutation)
486
- if (actions.length !== 1) throw new Error('a metadata mutation must contain exactly one action: `insert` or `delete`')
487
-
488
- if ('delete' in mutation) {
489
- if (mutation.delete !== 'test') throw new Error('`delete` must name exactly one measurement field: `test`')
490
- return { scenario: mutation.scenario, action: 'delete' }
491
- }
492
-
493
- const insert = recordOf(mutation.insert)
494
- if (!insert || Object.keys(insert).length !== 1 || !('test' in insert)) {
495
- throw new Error('`insert` must contain exactly one measurement field: `test`')
496
- }
497
- if (typeof insert.test === 'string') {
498
- if (!insert.test.trim()) throw new Error('`insert.test` path must be a non-empty string')
499
- return { scenario: mutation.scenario, action: 'insert', test: { path: insert.test } }
500
- }
501
- const test = recordOf(insert.test)
502
- if (!test || Object.keys(test).sort().join(',') !== 'name,path'
503
- || typeof test.path !== 'string' || !test.path.trim()
504
- || typeof test.name !== 'string' || !test.name.trim()) {
505
- throw new Error('`insert.test` must be a path string or an exact `{path,name}` string mapping')
506
- }
507
- return { scenario: mutation.scenario, action: 'insert', test: { path: test.path, name: test.name } }
508
- }
509
-
510
- function declarationLineEnding(source: string): '\n' | '\r\n' {
511
- const withoutCrlf = source.replace(/\r\n/g, '')
512
- if (withoutCrlf.includes('\r')) throw new Error('eval.md uses unsupported bare CR line endings')
513
- if (source.includes('\r\n') && withoutCrlf.includes('\n')) {
514
- throw new Error('eval.md mixes LF and CRLF line endings; normalize it before applying metadata')
515
- }
516
- return source.includes('\r\n') ? '\r\n' : '\n'
517
- }
518
-
519
- function malformedDeclaration(errors: string[]): Error {
520
- return new Error(`malformed eval.md:\n${errors.map((error) => ` - ${error}`).join('\n')}`)
521
- }
522
-
523
- export function writeScenarioMeasurementMetadata(source: string, request: unknown): string {
524
- const mutation = parseMetadataMutation(request)
525
- const beforeErrors = validateScenarios(source)
526
- if (beforeErrors.length) throw malformedDeclaration(beforeErrors)
527
-
528
- const walked = walkScenarios(source)
529
- const matches = walked.items.filter((item) => item.fields.name === mutation.scenario)
530
- if (matches.length !== 1) {
531
- throw new Error(matches.length
532
- ? `scenario '${mutation.scenario}' is ambiguous (${matches.length} declarations)`
533
- : `scenario '${mutation.scenario}' was not found in eval.md`)
534
- }
535
- const item = matches[0]
536
- const lineEnding = declarationLineEnding(source)
537
- const lines = source.split(lineEnding)
538
-
539
- if (mutation.action === 'insert') {
540
- if (item.locations.test) throw new Error(`scenario '${mutation.scenario}' already has \`test\`; refusing to overwrite authoritative metadata`)
541
- const tags = item.locations.tags
542
- if (!tags) throw new Error(`scenario '${mutation.scenario}' has no structural \`tags\` field`)
543
- const keyIndent = tags.indent
544
- const childIndent = `${tags.indent} `
545
- const rendered = mutation.test.name === undefined
546
- ? [`${keyIndent}test: ${JSON.stringify(mutation.test.path)}`]
547
- : [
548
- `${keyIndent}test:`,
549
- `${childIndent}path: ${JSON.stringify(mutation.test.path)}`,
550
- `${childIndent}name: ${JSON.stringify(mutation.test.name)}`,
551
- ]
552
- lines.splice(tags.endLine + 2, 0, ...rendered)
553
- } else {
554
- const test = item.locations.test
555
- if (!test) throw new Error(`scenario '${mutation.scenario}' has no \`test\` field to delete`)
556
- lines.splice(test.startLine + 1, test.endLine - test.startLine + 1)
557
- }
558
-
559
- const proposed = lines.join(lineEnding)
560
- const afterErrors = validateScenarios(proposed)
561
- if (afterErrors.length) throw new Error(`metadata mutation produced ${malformedDeclaration(afterErrors).message}`)
562
- const after = parseScenarios(proposed).filter((scenario) => scenario.name === mutation.scenario)
563
- if (after.length !== 1) throw new Error(`metadata mutation lost the unique scenario '${mutation.scenario}'`)
564
- if (mutation.action === 'insert') {
565
- if (JSON.stringify(after[0].test) !== JSON.stringify(mutation.test)) {
566
- throw new Error(`metadata mutation did not round-trip the exact requested \`test\` mapping for scenario '${mutation.scenario}'`)
567
- }
568
- } else if (after[0].test !== undefined) {
569
- throw new Error(`metadata mutation did not delete \`test\` from scenario '${mutation.scenario}'`)
570
- }
571
- return proposed
572
- }
573
-
574
- function assembleNodes(root: string, specDirs: string[], hits: { dir: string; src: string; specSource?: string }[]): EvalNode[] {
575
- const specBase = join(root, '.spec')
576
- const ids = mintIds(specDirs.map((d) => relative(specBase, d).split(/[/\\]/)))
577
- const idByDir = new Map(specDirs.map((d, i) => [d, ids[i]]))
578
- return hits
579
- .map(({ dir, src, specSource }) => ({
580
- id: idByDir.get(dir) ?? basename(dir),
581
- dir,
582
- evalPath: relative(root, join(dir, EVAL_FILE)),
583
- sidecarPath: join(dir, SIDECAR_FILE),
584
- scenarios: parseScenarios(src),
585
- evalSource: src,
586
- ...(specSource !== undefined ? { specSource } : {}),
587
- }))
588
- .sort((a, b) => a.id.localeCompare(b.id))
589
- }
590
-
591
- export function evalNodes(root: string): EvalNode[] {
592
- const specDir = join(root, '.spec')
593
- const specDirs: string[] = []
594
- const hits: { dir: string; src: string; specSource?: string }[] = []
595
- const stack = existsSync(specDir) ? [specDir] : []
596
- while (stack.length) {
597
- const dir = stack.pop()!
598
- let ents
599
- try { ents = readdirSync(dir, { withFileTypes: true }) } catch { continue }
600
- if (existsSync(join(dir, 'spec.md'))) specDirs.push(dir)
601
- if (existsSync(join(dir, EVAL_FILE))) {
602
- const specPath = join(dir, 'spec.md')
603
- hits.push({
604
- dir,
605
- src: readFileSync(join(dir, EVAL_FILE), 'utf8'),
606
- ...(existsSync(specPath) ? { specSource: readFileSync(specPath, 'utf8') } : {}),
607
- })
608
- }
609
- for (const e of ents) if (e.isDirectory()) stack.push(join(dir, e.name))
610
- }
611
- return assembleNodes(root, specDirs, hits)
612
- }
613
-
614
- export function evalNodesAt(root: string, tip: string): EvalNode[] {
615
- const files = treeTextFiles(root, tip, '.spec')
616
- const paths = [...files.keys()]
617
- const specDirs = paths
618
- .filter((path) => path.endsWith('/spec.md'))
619
- .map((path) => join(root, path.slice(0, -'/spec.md'.length)))
620
- const hits = paths
621
- .filter((path) => path.endsWith(`/${EVAL_FILE}`))
622
- .map((path) => {
623
- const relDir = path.slice(0, -`/${EVAL_FILE}`.length)
624
- const specSource = files.get(`${relDir}/spec.md`)
625
- return {
626
- dir: join(root, relDir),
627
- src: files.get(path)!,
628
- ...(specSource !== undefined ? { specSource } : {}),
629
- }
630
- })
631
- return assembleNodes(root, specDirs, hits)
632
- }
633
-
634
- // async twin of evalNodes for the HOT board build ([[graph-cache]]): reading each eval.md through
635
- // fs/promises YIELDS the event loop between files, so the walk no longer stalls a `/health` probe in one
636
- // ~600ms uninterrupted stretch. Same output (canonical ids, id-sorted) as evalNodes; only buildBoard uses
637
- // it, other callers keep the sync form.
638
- export async function evalNodesAsync(root: string): Promise<EvalNode[]> {
639
- const specDir = join(root, '.spec')
640
- const specDirs: string[] = []
641
- const hits: { dir: string; src: string; specSource?: string }[] = []
642
- const stack = existsSync(specDir) ? [specDir] : []
643
- while (stack.length) {
644
- const dir = stack.pop()!
645
- let ents
646
- try { ents = await readdir(dir, { withFileTypes: true }) } catch { continue }
647
- if (existsSync(join(dir, 'spec.md'))) specDirs.push(dir)
648
- if (existsSync(join(dir, EVAL_FILE))) {
649
- const specPath = join(dir, 'spec.md')
650
- hits.push({
651
- dir,
652
- src: await readFile(join(dir, EVAL_FILE), 'utf8'),
653
- ...(existsSync(specPath) ? { specSource: await readFile(specPath, 'utf8') } : {}),
654
- })
655
- }
656
- for (const e of ents) if (e.isDirectory()) stack.push(join(dir, e.name))
657
- }
658
- return assembleNodes(root, specDirs, hits)
659
- }
660
-
661
- export type EvalResolution<T> = { ok: true; node: T } | { ok: false; ambiguous: boolean; error: string }
662
-
663
- export function resolveEvalNode<T extends Pick<EvalNode, 'id' | 'dir'>>(nodes: T[], ref: string): EvalResolution<T> {
664
- const exact = nodes.find((n) => n.id === ref)
665
- if (exact) return { ok: true, node: exact }
666
- const byLeaf = nodes.filter((n) => basename(n.dir) === ref)
667
- if (byLeaf.length === 1) return { ok: true, node: byLeaf[0] }
668
- if (byLeaf.length > 1) {
669
- return { ok: false, ambiguous: true, error: `'${ref}' is ambiguous — ${byLeaf.length} measurable nodes share that leaf name; use a canonical id: ${byLeaf.map((n) => n.id).sort().join(', ')}` }
670
- }
671
- return { ok: false, ambiguous: false, error: `no measurable node '${ref}' (a node needs an eval.md)` }
672
- }