spexcode 0.6.1 → 0.6.5

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 (480) hide show
  1. package/README.md +99 -149
  2. package/bin/spex.mjs +4 -0
  3. package/node_modules/@spexcode/spec-cli/bin/spex.mjs +76 -0
  4. package/node_modules/@spexcode/spec-cli/dist/attach.d.ts +2 -0
  5. package/node_modules/@spexcode/spec-cli/dist/attach.js +48 -0
  6. package/node_modules/@spexcode/spec-cli/dist/claude-headless.d.ts +33 -0
  7. package/node_modules/@spexcode/spec-cli/dist/claude-headless.js +307 -0
  8. package/node_modules/@spexcode/spec-cli/dist/cli.d.ts +1 -0
  9. package/node_modules/@spexcode/spec-cli/dist/cli.js +1859 -0
  10. package/node_modules/@spexcode/spec-cli/dist/client.d.ts +111 -0
  11. package/node_modules/@spexcode/spec-cli/dist/client.js +476 -0
  12. package/node_modules/@spexcode/spec-cli/dist/cockpit.d.ts +7 -0
  13. package/node_modules/@spexcode/spec-cli/dist/cockpit.js +26 -0
  14. package/node_modules/@spexcode/spec-cli/dist/codex-headless.d.ts +1 -0
  15. package/{spec-cli/src/codex-headless.ts → node_modules/@spexcode/spec-cli/dist/codex-headless.js} +3 -9
  16. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +53 -0
  17. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +686 -0
  18. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.d.ts +1 -0
  19. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.js +90 -0
  20. package/node_modules/@spexcode/spec-cli/dist/contract-filter.d.ts +14 -0
  21. package/node_modules/@spexcode/spec-cli/dist/contract-filter.js +219 -0
  22. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.d.ts +6 -0
  23. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.js +50 -0
  24. package/node_modules/@spexcode/spec-cli/dist/delivery-queue.d.ts +23 -0
  25. package/node_modules/@spexcode/spec-cli/dist/delivery-queue.js +179 -0
  26. package/node_modules/@spexcode/spec-cli/dist/doctor.d.ts +9 -0
  27. package/node_modules/@spexcode/spec-cli/dist/doctor.js +547 -0
  28. package/node_modules/@spexcode/spec-cli/dist/eval-host.d.ts +1 -0
  29. package/node_modules/@spexcode/spec-cli/dist/eval-host.js +19 -0
  30. package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +26 -0
  31. package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +475 -0
  32. package/node_modules/@spexcode/spec-cli/dist/file-write.d.ts +2 -0
  33. package/node_modules/@spexcode/spec-cli/dist/file-write.js +26 -0
  34. package/node_modules/@spexcode/spec-cli/dist/flat.d.ts +89 -0
  35. package/node_modules/@spexcode/spec-cli/dist/flat.js +1076 -0
  36. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.d.ts +56 -0
  37. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.js +168 -0
  38. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.d.ts +28 -0
  39. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +361 -0
  40. package/node_modules/@spexcode/spec-cli/dist/gateway.d.ts +37 -0
  41. package/node_modules/@spexcode/spec-cli/dist/gateway.js +503 -0
  42. package/node_modules/@spexcode/spec-cli/dist/graphCache.d.ts +27 -0
  43. package/node_modules/@spexcode/spec-cli/dist/graphCache.js +716 -0
  44. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.d.ts +14 -0
  45. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +35 -0
  46. package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +85 -0
  47. package/node_modules/@spexcode/spec-cli/dist/graphStream.js +1195 -0
  48. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.d.ts +64 -0
  49. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.js +166 -0
  50. package/node_modules/@spexcode/spec-cli/dist/guide.d.ts +8 -0
  51. package/{spec-cli/src/guide.ts → node_modules/@spexcode/spec-cli/dist/guide.js} +115 -22
  52. package/node_modules/@spexcode/spec-cli/dist/harness-select.d.ts +18 -0
  53. package/node_modules/@spexcode/spec-cli/dist/harness-select.js +62 -0
  54. package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +325 -0
  55. package/node_modules/@spexcode/spec-cli/dist/harness.js +3026 -0
  56. package/node_modules/@spexcode/spec-cli/dist/headless-controller.d.ts +9 -0
  57. package/node_modules/@spexcode/spec-cli/dist/headless-controller.js +47 -0
  58. package/node_modules/@spexcode/spec-cli/dist/help.d.ts +10 -0
  59. package/node_modules/@spexcode/spec-cli/dist/help.js +574 -0
  60. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.d.ts +18 -0
  61. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +122 -0
  62. package/node_modules/@spexcode/spec-cli/dist/hooks.d.ts +1 -0
  63. package/{spec-cli/src/hooks.ts → node_modules/@spexcode/spec-cli/dist/hooks.js} +15 -15
  64. package/node_modules/@spexcode/spec-cli/dist/host-resources.d.ts +97 -0
  65. package/node_modules/@spexcode/spec-cli/dist/host-resources.js +795 -0
  66. package/node_modules/@spexcode/spec-cli/dist/host.d.ts +88 -0
  67. package/node_modules/@spexcode/spec-cli/dist/host.js +663 -0
  68. package/node_modules/@spexcode/spec-cli/dist/index.d.ts +1 -0
  69. package/node_modules/@spexcode/spec-cli/dist/index.js +899 -0
  70. package/node_modules/@spexcode/spec-cli/dist/init.d.ts +1 -0
  71. package/node_modules/@spexcode/spec-cli/dist/init.js +284 -0
  72. package/node_modules/@spexcode/spec-cli/dist/issues-cli.d.ts +6 -0
  73. package/node_modules/@spexcode/spec-cli/dist/issues-cli.js +334 -0
  74. package/node_modules/@spexcode/spec-cli/dist/issues.d.ts +69 -0
  75. package/node_modules/@spexcode/spec-cli/dist/issues.js +189 -0
  76. package/node_modules/@spexcode/spec-cli/dist/lint.d.ts +34 -0
  77. package/node_modules/@spexcode/spec-cli/dist/lint.js +476 -0
  78. package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +7 -0
  79. package/{spec-cli/src/listen.ts → node_modules/@spexcode/spec-cli/dist/listen.js} +13 -17
  80. package/node_modules/@spexcode/spec-cli/dist/localIssues.d.ts +65 -0
  81. package/node_modules/@spexcode/spec-cli/dist/localIssues.js +582 -0
  82. package/node_modules/@spexcode/spec-cli/dist/login-page.d.ts +5 -0
  83. package/{spec-cli/src/login-page.ts → node_modules/@spexcode/spec-cli/dist/login-page.js} +7 -7
  84. package/node_modules/@spexcode/spec-cli/dist/loop-in.d.ts +23 -0
  85. package/{spec-cli/src/loop-in.ts → node_modules/@spexcode/spec-cli/dist/loop-in.js} +35 -45
  86. package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +69 -0
  87. package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +603 -0
  88. package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +18 -0
  89. package/node_modules/@spexcode/spec-cli/dist/materialize.js +468 -0
  90. package/node_modules/@spexcode/spec-cli/dist/mentions.d.ts +50 -0
  91. package/node_modules/@spexcode/spec-cli/dist/mentions.js +117 -0
  92. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.d.ts +4 -0
  93. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.js +208 -0
  94. package/node_modules/@spexcode/spec-cli/dist/opencode.d.ts +3 -0
  95. package/{spec-cli/src/opencode.ts → node_modules/@spexcode/spec-cli/dist/opencode.js} +9 -13
  96. package/node_modules/@spexcode/spec-cli/dist/pi-harness.d.ts +3 -0
  97. package/{spec-cli/src/pi-harness.ts → node_modules/@spexcode/spec-cli/dist/pi-harness.js} +45 -40
  98. package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +25 -0
  99. package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +196 -0
  100. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.d.ts +21 -0
  101. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.js +146 -0
  102. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.d.ts +10 -0
  103. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +404 -0
  104. package/node_modules/@spexcode/spec-cli/dist/pty-helper.d.mts +1 -0
  105. package/node_modules/@spexcode/spec-cli/dist/pty-helper.mjs +101 -0
  106. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.d.mts +2 -0
  107. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.mjs +23 -0
  108. package/node_modules/@spexcode/spec-cli/dist/public-graph.d.ts +46 -0
  109. package/node_modules/@spexcode/spec-cli/dist/public-graph.js +56 -0
  110. package/node_modules/@spexcode/spec-cli/dist/ranker.d.ts +15 -0
  111. package/node_modules/@spexcode/spec-cli/dist/ranker.js +188 -0
  112. package/node_modules/@spexcode/spec-cli/dist/reaper.d.ts +6 -0
  113. package/node_modules/@spexcode/spec-cli/dist/reaper.js +81 -0
  114. package/node_modules/@spexcode/spec-cli/dist/reviews.d.ts +189 -0
  115. package/node_modules/@spexcode/spec-cli/dist/reviews.js +350 -0
  116. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.d.ts +6 -0
  117. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.js +37 -0
  118. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +21 -0
  119. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +84 -0
  120. package/node_modules/@spexcode/spec-cli/dist/search.bench.d.mts +1 -0
  121. package/node_modules/@spexcode/spec-cli/dist/search.bench.mjs +75 -0
  122. package/node_modules/@spexcode/spec-cli/dist/search.d.ts +20 -0
  123. package/node_modules/@spexcode/spec-cli/dist/search.js +61 -0
  124. package/node_modules/@spexcode/spec-cli/dist/session-cursors.d.ts +14 -0
  125. package/node_modules/@spexcode/spec-cli/dist/session-cursors.js +82 -0
  126. package/node_modules/@spexcode/spec-cli/dist/session-declarations.d.ts +13 -0
  127. package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +126 -0
  128. package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +10 -0
  129. package/node_modules/@spexcode/spec-cli/dist/session-execution.js +70 -0
  130. package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +28 -0
  131. package/node_modules/@spexcode/spec-cli/dist/session-files.js +130 -0
  132. package/node_modules/@spexcode/spec-cli/dist/session-follow.d.ts +31 -0
  133. package/node_modules/@spexcode/spec-cli/dist/session-follow.js +179 -0
  134. package/node_modules/@spexcode/spec-cli/dist/session-reparent.d.ts +10 -0
  135. package/node_modules/@spexcode/spec-cli/dist/session-reparent.js +33 -0
  136. package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +51 -0
  137. package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +233 -0
  138. package/node_modules/@spexcode/spec-cli/dist/session-web.d.ts +24 -0
  139. package/node_modules/@spexcode/spec-cli/dist/session-web.js +141 -0
  140. package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +412 -0
  141. package/node_modules/@spexcode/spec-cli/dist/sessions.js +4239 -0
  142. package/node_modules/@spexcode/spec-cli/dist/sh.d.ts +1 -0
  143. package/node_modules/@spexcode/spec-cli/dist/sh.js +3 -0
  144. package/node_modules/@spexcode/spec-cli/dist/shim-runtime.d.ts +1 -0
  145. package/{spec-cli/src/shim-runtime.ts → node_modules/@spexcode/spec-cli/dist/shim-runtime.js} +3 -4
  146. package/node_modules/@spexcode/spec-cli/dist/slash-commands.d.ts +9 -0
  147. package/node_modules/@spexcode/spec-cli/dist/slash-commands.js +307 -0
  148. package/node_modules/@spexcode/spec-cli/dist/source-files.d.ts +9 -0
  149. package/node_modules/@spexcode/spec-cli/dist/source-files.js +113 -0
  150. package/node_modules/@spexcode/spec-cli/dist/supervise.d.ts +1 -0
  151. package/node_modules/@spexcode/spec-cli/dist/supervise.js +308 -0
  152. package/node_modules/@spexcode/spec-cli/dist/tree.d.ts +25 -0
  153. package/node_modules/@spexcode/spec-cli/dist/tree.js +98 -0
  154. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.d.ts +3 -0
  155. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.js +41 -0
  156. package/node_modules/@spexcode/spec-cli/dist/uninstall.d.ts +3 -0
  157. package/node_modules/@spexcode/spec-cli/dist/uninstall.js +177 -0
  158. package/node_modules/@spexcode/spec-cli/dist/uploads.d.ts +36 -0
  159. package/node_modules/@spexcode/spec-cli/dist/uploads.js +243 -0
  160. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.d.ts +1 -0
  161. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.js +47 -0
  162. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/dispatch.sh +5 -5
  163. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/harness.sh +27 -7
  164. package/node_modules/@spexcode/spec-cli/package.json +47 -0
  165. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
  166. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +9 -0
  167. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec-first.sh +1 -1
  168. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec-of-file.sh +1 -0
  169. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/spec.md +22 -0
  170. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/spec.md +2 -2
  171. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +31 -29
  172. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +15 -0
  173. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +10 -0
  174. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +13 -0
  175. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +12 -0
  176. package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +94 -0
  177. package/node_modules/@spexcode/spec-core/dist/anchors.js +730 -0
  178. package/node_modules/@spexcode/spec-core/dist/git.d.ts +166 -0
  179. package/node_modules/@spexcode/spec-core/dist/git.js +2736 -0
  180. package/node_modules/@spexcode/spec-core/dist/graph-delta.d.ts +44 -0
  181. package/node_modules/@spexcode/spec-core/dist/graph-delta.js +72 -0
  182. package/node_modules/@spexcode/spec-core/dist/graph.d.ts +34 -0
  183. package/node_modules/@spexcode/spec-core/dist/graph.js +237 -0
  184. package/node_modules/@spexcode/spec-core/dist/graphDelta.d.ts +3 -0
  185. package/node_modules/@spexcode/spec-core/dist/graphDelta.js +13 -0
  186. package/node_modules/@spexcode/spec-core/dist/harness-identity.d.ts +31 -0
  187. package/node_modules/@spexcode/spec-core/dist/harness-identity.js +20 -0
  188. package/node_modules/@spexcode/spec-core/dist/identity-presets.d.ts +152 -0
  189. package/node_modules/@spexcode/spec-core/dist/identity-presets.js +132 -0
  190. package/node_modules/@spexcode/spec-core/dist/index.d.ts +45 -0
  191. package/node_modules/@spexcode/spec-core/dist/index.js +19 -0
  192. package/node_modules/@spexcode/spec-core/dist/layout.d.ts +183 -0
  193. package/node_modules/@spexcode/spec-core/dist/layout.js +548 -0
  194. package/node_modules/@spexcode/spec-core/dist/process-identity.d.ts +37 -0
  195. package/node_modules/@spexcode/spec-core/dist/process-identity.js +214 -0
  196. package/node_modules/@spexcode/spec-core/dist/project-identity.d.ts +12 -0
  197. package/node_modules/@spexcode/spec-core/dist/project-identity.js +71 -0
  198. package/node_modules/@spexcode/spec-core/dist/project-store.d.ts +3 -0
  199. package/node_modules/@spexcode/spec-core/dist/project-store.js +14 -0
  200. package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +2 -0
  201. package/node_modules/@spexcode/spec-core/dist/resilience.js +40 -0
  202. package/node_modules/@spexcode/spec-core/dist/review/index.d.ts +3 -0
  203. package/node_modules/@spexcode/spec-core/dist/review/index.js +5 -0
  204. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +77 -0
  205. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +308 -0
  206. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +66 -0
  207. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +180 -0
  208. package/node_modules/@spexcode/spec-core/dist/review/session.d.ts +4 -0
  209. package/node_modules/@spexcode/spec-core/dist/review/session.js +8 -0
  210. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.d.ts +15 -0
  211. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.js +12 -0
  212. package/node_modules/@spexcode/spec-core/dist/root-lru.d.ts +4 -0
  213. package/{spec-cli/src/root-lru.ts → node_modules/@spexcode/spec-core/dist/root-lru.js} +26 -30
  214. package/node_modules/@spexcode/spec-core/dist/specs.d.ts +117 -0
  215. package/node_modules/@spexcode/spec-core/dist/specs.js +489 -0
  216. package/node_modules/@spexcode/spec-core/package.json +28 -0
  217. package/node_modules/@spexcode/spec-eval/dist/cache.d.ts +10 -0
  218. package/node_modules/@spexcode/spec-eval/dist/cache.js +50 -0
  219. package/node_modules/@spexcode/spec-eval/dist/cli.d.ts +22 -0
  220. package/node_modules/@spexcode/spec-eval/dist/cli.js +981 -0
  221. package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +98 -0
  222. package/node_modules/@spexcode/spec-eval/dist/evaltab.js +176 -0
  223. package/node_modules/@spexcode/spec-eval/dist/filing.d.ts +16 -0
  224. package/node_modules/@spexcode/spec-eval/dist/filing.js +36 -0
  225. package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +48 -0
  226. package/node_modules/@spexcode/spec-eval/dist/freshness.js +799 -0
  227. package/node_modules/@spexcode/spec-eval/dist/host.d.ts +46 -0
  228. package/node_modules/@spexcode/spec-eval/dist/host.js +53 -0
  229. package/node_modules/@spexcode/spec-eval/dist/humanok.d.ts +11 -0
  230. package/node_modules/@spexcode/spec-eval/dist/humanok.js +27 -0
  231. package/node_modules/@spexcode/spec-eval/dist/index.d.ts +4 -0
  232. package/node_modules/@spexcode/spec-eval/dist/index.js +4 -0
  233. package/node_modules/@spexcode/spec-eval/dist/remarks.d.ts +31 -0
  234. package/node_modules/@spexcode/spec-eval/dist/remarks.js +1 -0
  235. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.d.ts +13 -0
  236. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.js +303 -0
  237. package/node_modules/@spexcode/spec-eval/dist/scenarios.d.ts +99 -0
  238. package/node_modules/@spexcode/spec-eval/dist/scenarios.js +650 -0
  239. package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +307 -0
  240. package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +1883 -0
  241. package/node_modules/@spexcode/spec-eval/dist/sidecar.d.ts +55 -0
  242. package/node_modules/@spexcode/spec-eval/dist/sidecar.js +82 -0
  243. package/node_modules/@spexcode/spec-eval/dist/timeline.d.ts +25 -0
  244. package/node_modules/@spexcode/spec-eval/dist/timeline.js +65 -0
  245. package/node_modules/@spexcode/spec-eval/dist/ui-path.d.ts +1 -0
  246. package/node_modules/@spexcode/spec-eval/dist/ui-path.js +2 -0
  247. package/node_modules/@spexcode/spec-eval/package.json +36 -0
  248. package/node_modules/@spexcode/spec-forge/dist/cache.d.ts +29 -0
  249. package/node_modules/@spexcode/spec-forge/dist/cache.js +59 -0
  250. package/node_modules/@spexcode/spec-forge/dist/cli.d.ts +1 -0
  251. package/node_modules/@spexcode/spec-forge/dist/cli.js +100 -0
  252. package/node_modules/@spexcode/spec-forge/dist/drivers/github.d.ts +2 -0
  253. package/node_modules/@spexcode/spec-forge/dist/drivers/github.js +116 -0
  254. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.d.ts +7 -0
  255. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.js +144 -0
  256. package/node_modules/@spexcode/spec-forge/dist/drivers.d.ts +10 -0
  257. package/node_modules/@spexcode/spec-forge/dist/drivers.js +83 -0
  258. package/node_modules/@spexcode/spec-forge/dist/index.d.ts +4 -0
  259. package/node_modules/@spexcode/spec-forge/dist/index.js +4 -0
  260. package/node_modules/@spexcode/spec-forge/dist/links.d.ts +15 -0
  261. package/node_modules/@spexcode/spec-forge/dist/links.js +65 -0
  262. package/node_modules/@spexcode/spec-forge/dist/needs-eval.d.ts +10 -0
  263. package/node_modules/@spexcode/spec-forge/dist/needs-eval.js +20 -0
  264. package/node_modules/@spexcode/spec-forge/dist/port.d.ts +53 -0
  265. package/node_modules/@spexcode/spec-forge/dist/port.js +1 -0
  266. package/node_modules/@spexcode/spec-forge/dist/resident.d.ts +7 -0
  267. package/node_modules/@spexcode/spec-forge/dist/resident.js +43 -0
  268. package/node_modules/@spexcode/spec-forge/package.json +32 -0
  269. package/package.json +33 -19
  270. package/spec-cli/bin/spex.mjs +0 -69
  271. package/spec-cli/src/anchors.ts +0 -706
  272. package/spec-cli/src/attach.ts +0 -43
  273. package/spec-cli/src/claude-headless.ts +0 -321
  274. package/spec-cli/src/cli.ts +0 -1115
  275. package/spec-cli/src/client.ts +0 -315
  276. package/spec-cli/src/cockpit.ts +0 -43
  277. package/spec-cli/src/codex-runtime-generations.ts +0 -646
  278. package/spec-cli/src/commit-surgery.ts +0 -68
  279. package/spec-cli/src/contract-filter.ts +0 -202
  280. package/spec-cli/src/delivery-queue.ts +0 -107
  281. package/spec-cli/src/doctor.ts +0 -516
  282. package/spec-cli/src/file-write.ts +0 -22
  283. package/spec-cli/src/gateway-auth.ts +0 -181
  284. package/spec-cli/src/gateway-hub.ts +0 -356
  285. package/spec-cli/src/gateway.ts +0 -368
  286. package/spec-cli/src/git.ts +0 -2180
  287. package/spec-cli/src/graph.ts +0 -221
  288. package/spec-cli/src/graphCache.ts +0 -641
  289. package/spec-cli/src/graphDelta.ts +0 -77
  290. package/spec-cli/src/graphStream.ts +0 -1019
  291. package/spec-cli/src/harness-select.ts +0 -77
  292. package/spec-cli/src/harness.ts +0 -2747
  293. package/spec-cli/src/headless-controller.ts +0 -63
  294. package/spec-cli/src/help.ts +0 -480
  295. package/spec-cli/src/host-resources.ts +0 -707
  296. package/spec-cli/src/host.ts +0 -611
  297. package/spec-cli/src/identity-presets.d.ts +0 -13
  298. package/spec-cli/src/identity-presets.js +0 -128
  299. package/spec-cli/src/index.ts +0 -730
  300. package/spec-cli/src/init.ts +0 -258
  301. package/spec-cli/src/issues-cli.ts +0 -276
  302. package/spec-cli/src/issues.ts +0 -237
  303. package/spec-cli/src/layout.ts +0 -557
  304. package/spec-cli/src/lint.ts +0 -458
  305. package/spec-cli/src/localIssues.ts +0 -538
  306. package/spec-cli/src/materialize.ts +0 -412
  307. package/spec-cli/src/mentions.ts +0 -81
  308. package/spec-cli/src/opencode-headless.ts +0 -205
  309. package/spec-cli/src/pi-headless.ts +0 -153
  310. package/spec-cli/src/plugin-harness.ts +0 -157
  311. package/spec-cli/src/process-identity.ts +0 -207
  312. package/spec-cli/src/project-identity.ts +0 -73
  313. package/spec-cli/src/project-store.ts +0 -17
  314. package/spec-cli/src/pty-bridge.ts +0 -396
  315. package/spec-cli/src/pty-helper.mjs +0 -96
  316. package/spec-cli/src/pty-native-helper.mjs +0 -22
  317. package/spec-cli/src/ranker.ts +0 -191
  318. package/spec-cli/src/reaper.ts +0 -125
  319. package/spec-cli/src/resilience.ts +0 -41
  320. package/spec-cli/src/reviewSnapshot.ts +0 -28
  321. package/spec-cli/src/reviews.ts +0 -405
  322. package/spec-cli/src/runtime-guard.ts +0 -35
  323. package/spec-cli/src/runtime-ownership.ts +0 -88
  324. package/spec-cli/src/search.bench.mjs +0 -80
  325. package/spec-cli/src/search.ts +0 -63
  326. package/spec-cli/src/session-cursors.ts +0 -91
  327. package/spec-cli/src/session-follow.ts +0 -182
  328. package/spec-cli/src/session-timeline.ts +0 -98
  329. package/spec-cli/src/sessions.ts +0 -3403
  330. package/spec-cli/src/sh.ts +0 -3
  331. package/spec-cli/src/slash-commands.ts +0 -318
  332. package/spec-cli/src/source-files.ts +0 -104
  333. package/spec-cli/src/specs.ts +0 -498
  334. package/spec-cli/src/supervise.ts +0 -245
  335. package/spec-cli/src/tree.ts +0 -118
  336. package/spec-cli/src/tsx-bin.ts +0 -18
  337. package/spec-cli/src/uninstall.ts +0 -171
  338. package/spec-cli/src/uploads.ts +0 -253
  339. package/spec-cli/src/worktree-sources.ts +0 -42
  340. package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +0 -8
  341. package/spec-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -28
  342. package/spec-cli/templates/spec/project/.plugins/core/spec.md +0 -23
  343. package/spec-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -32
  344. package/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -9
  345. package/spec-cli/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -15
  346. package/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -18
  347. package/spec-dashboard/dist/assets/App-F9uaAVcH.js +0 -2
  348. package/spec-dashboard/dist/assets/Dashboard-Ba_jhxp1.js +0 -27
  349. package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
  350. package/spec-dashboard/dist/assets/EvalsPage-FixoOg_n.js +0 -2
  351. package/spec-dashboard/dist/assets/IssuesPage-CuKLFhH3.js +0 -1
  352. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  353. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  354. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  355. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  356. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  357. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  358. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  359. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  360. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  361. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  362. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  363. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  364. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  365. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  366. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  367. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  368. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  369. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  370. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  371. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  372. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  373. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  374. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  375. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  376. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  377. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  378. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  379. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  380. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  381. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  382. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  383. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  384. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  385. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  386. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  387. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  388. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  389. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  390. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  391. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  392. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  393. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  394. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  395. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  396. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  397. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  398. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  399. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  400. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  401. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  402. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  403. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  404. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  405. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  406. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  407. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  408. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  409. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  410. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  411. package/spec-dashboard/dist/assets/MobileApp-CHgEHORJ.js +0 -2
  412. package/spec-dashboard/dist/assets/Modal-CQgYymmr.js +0 -1
  413. package/spec-dashboard/dist/assets/PageScroll-hT7UTLvD.js +0 -1
  414. package/spec-dashboard/dist/assets/ProjectsPage-CtXxakF9.js +0 -1
  415. package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
  416. package/spec-dashboard/dist/assets/SessionInterface-Bpie-9fs.js +0 -39
  417. package/spec-dashboard/dist/assets/SessionWindow-CixDi4PI.js +0 -1
  418. package/spec-dashboard/dist/assets/Settings-C2MsucfE.js +0 -1
  419. package/spec-dashboard/dist/assets/Thread-C6Go8HRh.js +0 -13
  420. package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
  421. package/spec-dashboard/dist/assets/data-B-RQmit6.js +0 -1
  422. package/spec-dashboard/dist/assets/index-CsI8DElI.css +0 -1
  423. package/spec-dashboard/dist/assets/index-DrVao0Ep.js +0 -41
  424. package/spec-dashboard/dist/assets/launch-BBH02b1v.js +0 -276
  425. package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
  426. package/spec-dashboard/dist/assets/terminalFont-C6oGNKrf.js +0 -1
  427. package/spec-dashboard/dist/index.html +0 -29
  428. package/spec-dashboard/src/reviewFilters.js +0 -322
  429. package/spec-dashboard/src/reviewQuery.js +0 -174
  430. package/spec-dashboard/src/session.js +0 -199
  431. package/spec-eval/src/cache.ts +0 -57
  432. package/spec-eval/src/cli.ts +0 -889
  433. package/spec-eval/src/evaltab.ts +0 -262
  434. package/spec-eval/src/filing.ts +0 -39
  435. package/spec-eval/src/freshness.ts +0 -557
  436. package/spec-eval/src/humanok.ts +0 -28
  437. package/spec-eval/src/scenariofresh.ts +0 -244
  438. package/spec-eval/src/scenarios.ts +0 -672
  439. package/spec-eval/src/sessioneval.ts +0 -2226
  440. package/spec-eval/src/sidecar.ts +0 -93
  441. package/spec-eval/src/timeline.ts +0 -54
  442. package/spec-forge/src/__fixtures__/github-forge.json +0 -63
  443. package/spec-forge/src/cache.ts +0 -66
  444. package/spec-forge/src/cli.ts +0 -99
  445. package/spec-forge/src/drivers/github.ts +0 -132
  446. package/spec-forge/src/drivers/gitlab.ts +0 -153
  447. package/spec-forge/src/drivers.ts +0 -73
  448. package/spec-forge/src/links.ts +0 -72
  449. package/spec-forge/src/needs-eval.ts +0 -29
  450. package/spec-forge/src/port.ts +0 -36
  451. package/spec-forge/src/resident.ts +0 -46
  452. /package/{spec-cli → node_modules/@spexcode/spec-cli}/README.md +0 -0
  453. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -0
  454. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-sed-v0.fixture +0 -0
  455. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/commit-msg +0 -0
  456. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-checkout +0 -0
  457. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-merge +0 -0
  458. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/pre-commit +0 -0
  459. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/prepare-commit-msg +0 -0
  460. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/reference-transaction +0 -0
  461. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/extract/spec.md +0 -0
  462. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/regroup/spec.md +0 -0
  463. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/rename/spec.md +0 -0
  464. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/spec.md +0 -0
  465. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/tidy/spec.md +0 -0
  466. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/idle.sh +0 -0
  467. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/spec.md +0 -0
  468. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/mark-active.sh +0 -0
  469. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/spec.md +0 -0
  470. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/fail.sh +0 -0
  471. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/spec.md +0 -0
  472. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec.md +0 -0
  473. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec.md +0 -0
  474. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/spec.md +0 -0
  475. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/digest.mjs +0 -0
  476. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/spec.md +0 -0
  477. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/spec.md +0 -0
  478. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/spec.md +0 -0
  479. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/spec.md +0 -0
  480. /package/{spec-cli → node_modules/@spexcode/spec-core}/templates/spexcode.json +0 -0
@@ -0,0 +1,489 @@
1
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
2
+ import { readFile, readdir } from 'node:fs/promises';
3
+ import { join, relative, basename } from 'node:path';
4
+ import { repoRoot, historyIndex, rowsFor, historyStats, pathsStats, driftIndex, driftFor, fileDiffAt, sourceIndexes, treeTextFiles, primeAncestorClosures, ancestorsOf, inAncestors } from './git.js';
5
+ import { parseCodeEntry, parseRelation, relationClaimsPath } from './anchors.js';
6
+ // a node is any directory under .spec holding a spec.md; its parent is the nearest ancestor that also holds one.
7
+ const ROOT = repoRoot();
8
+ const SPEC_DIR = join(ROOT, '.spec');
9
+ // line-based frontmatter: scalars are `key: value`; an empty key followed by `- item` lines is a list (e.g. `code:`).
10
+ export function parseFrontmatter(src) {
11
+ const fm = {};
12
+ let body = src;
13
+ const m = src.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
14
+ if (m) {
15
+ let key = null;
16
+ for (const line of m[1].split('\n')) {
17
+ const item = line.match(/^\s*-\s+(.*)$/);
18
+ if (item && key) {
19
+ if (!Array.isArray(fm[key]))
20
+ fm[key] = fm[key] ? [fm[key]] : [];
21
+ fm[key].push(item[1].trim());
22
+ continue;
23
+ }
24
+ const i = line.indexOf(':');
25
+ if (i > 0) {
26
+ key = line.slice(0, i).trim();
27
+ fm[key] = line.slice(i + 1).trim();
28
+ }
29
+ }
30
+ body = m[2];
31
+ }
32
+ return { fm, body };
33
+ }
34
+ const str = (v, d = '') => (Array.isArray(v) ? v.join(', ') : v ?? d);
35
+ const list = (v) => (Array.isArray(v) ? v : v ? [v] : []);
36
+ const PART_ALIASES = {
37
+ 'raw source': 'rawSource',
38
+ 'expanded spec': 'expandedSpec',
39
+ };
40
+ function parseParts(body) {
41
+ const acc = { rawSource: [], expandedSpec: [] };
42
+ let cur = null;
43
+ let inFence = false;
44
+ let any = false;
45
+ for (const line of body.split('\n')) {
46
+ const fence = /^\s*```/.test(line);
47
+ if (!inFence && !fence) {
48
+ const h2 = line.match(/^##\s+(.+?)\s*$/); // exactly two hashes — `###` won't match
49
+ if (h2) {
50
+ const key = PART_ALIASES[h2[1].trim().toLowerCase()];
51
+ if (key) {
52
+ cur = key;
53
+ any = true;
54
+ continue;
55
+ }
56
+ // an unrecognized `## …` heading is just content of the current part — fall through.
57
+ }
58
+ }
59
+ if (fence)
60
+ inFence = !inFence;
61
+ if (cur === 'rawSource')
62
+ acc.rawSource.push(line);
63
+ else if (cur === 'expandedSpec')
64
+ acc.expandedSpec.push(line);
65
+ }
66
+ if (!any)
67
+ return null;
68
+ const t = (a) => a.join('\n').trim();
69
+ return { rawSource: t(acc.rawSource), expandedSpec: t(acc.expandedSpec) };
70
+ }
71
+ export function deriveStatus(d) {
72
+ if (d.fmStatus === 'pending' && !d.hasCode && d.drift === 0)
73
+ return 'pending';
74
+ if (d.hasOverlay)
75
+ return 'active';
76
+ if (d.drift > 0)
77
+ return 'drift';
78
+ if (d.version > 0)
79
+ return 'merged';
80
+ const fb = d.fmStatus;
81
+ if (fb === 'active' || fb === 'merged' || fb === 'drift')
82
+ return fb;
83
+ return 'pending';
84
+ }
85
+ function walk(dir, parent, acc) {
86
+ let myId = parent;
87
+ if (existsSync(join(dir, 'spec.md'))) {
88
+ myId = basename(dir);
89
+ const relPath = relative(ROOT, join(dir, 'spec.md'));
90
+ const { fm, body } = parseFrontmatter(readFileSync(join(dir, 'spec.md'), 'utf8'));
91
+ acc.push({ id: myId, parent, relPath, fm, body });
92
+ }
93
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
94
+ if (e.isDirectory())
95
+ walk(join(dir, e.name), myId, acc);
96
+ }
97
+ }
98
+ // the id MINT ([[id-url-safe]]): key each node — given its path segments under .spec — to its leaf dir
99
+ // name, or on a leaf collision the shortest globally-unique trailing path-suffix. A node id is a URL-safe
100
+ // single token — never a '/'-joined path, which would break every `:id` route and fetch that treats an id
101
+ // as one path segment. So the disambiguation separator is '_': like '/' it never occurs inside a dir
102
+ // basename (so the join stays unambiguous), but unlike '/' it is a URL/wikilink/DOM-safe unreserved char,
103
+ // so a collision-qualified id (e.g. `.plugins_<id>`) stays a single token everywhere it is resolved.
104
+ // Exported as the ONE mint every id producer shares: spec-eval mints its node ids through this same
105
+ // function over this same universe (every spec node), so a colliding leaf carries one canonical id
106
+ // system-wide instead of a second, diverging bare-leaf scheme.
107
+ export function mintIds(segs) {
108
+ // NFC pins one canonical byte form for a non-ASCII dir name (macOS hands out NFD basenames), so a typed
109
+ // `[[中文节点]]` (NFC, what an IME emits) string-matches the minted id on every platform.
110
+ const suffix = (s, k) => s.slice(s.length - k).join('_').normalize('NFC');
111
+ return segs.map((s, i) => {
112
+ let k = 1;
113
+ while (k < s.length && segs.some((o, j) => j !== i && o.length >= k && suffix(o, k) === suffix(s, k)))
114
+ k++;
115
+ return suffix(s, k);
116
+ });
117
+ }
118
+ // re-key each node via the mint (overrides walk's placeholder basename id/parent); the second loop
119
+ // recomputes parent by path-ancestry.
120
+ function reId(acc) {
121
+ const segs = acc.map((r) => r.relPath.split(/[/\\]/).slice(1, -1)); // path under .spec, minus 'spec.md'
122
+ const ids = mintIds(segs);
123
+ for (let i = 0; i < acc.length; i++)
124
+ acc[i].id = ids[i];
125
+ for (let i = 0; i < acc.length; i++) {
126
+ let best = -1;
127
+ for (let j = 0; j < acc.length; j++) {
128
+ const o = segs[j], s = segs[i];
129
+ if (j !== i && o.length < s.length && o.every((seg, x) => seg === s[x]) && (best < 0 || o.length > segs[best].length))
130
+ best = j;
131
+ }
132
+ acc[i].parent = best >= 0 ? acc[best].id : null;
133
+ }
134
+ }
135
+ function raws() {
136
+ const acc = [];
137
+ if (existsSync(SPEC_DIR))
138
+ walk(SPEC_DIR, null, acc);
139
+ reId(acc);
140
+ return acc;
141
+ }
142
+ // async twin of walk/raws for the HOT board build ([[graph-cache]]): reading each spec.md through
143
+ // fs/promises YIELDS the event loop between files, so a build never stalls a `/health` liveness probe the
144
+ // way the sync walk (one ~450ms uninterrupted stretch) did. Same output as raws() — identical push order
145
+ // (pre-order DFS, dir before children) and the same reId — so every caller reads the same nodes; only
146
+ // loadSpecs (already async, on the hot path) uses it, the light one-shot callers keep the sync raws().
147
+ async function walkAsync(dir, parent, acc, root) {
148
+ let myId = parent;
149
+ if (existsSync(join(dir, 'spec.md'))) {
150
+ myId = basename(dir);
151
+ const relPath = relative(root, join(dir, 'spec.md'));
152
+ const { fm, body } = parseFrontmatter(await readFile(join(dir, 'spec.md'), 'utf8'));
153
+ acc.push({ id: myId, parent, relPath, fm, body });
154
+ }
155
+ for (const e of await readdir(dir, { withFileTypes: true })) {
156
+ if (e.isDirectory())
157
+ await walkAsync(join(dir, e.name), myId, acc, root);
158
+ }
159
+ }
160
+ async function rawsAsync(root, tip = 'HEAD', snapshot) {
161
+ if (snapshot || tip !== 'HEAD') {
162
+ const acc = [];
163
+ const files = snapshot?.files ?? treeTextFiles(root, tip, '.spec');
164
+ for (const [relPath, source] of [...files].sort(([a], [b]) => a.localeCompare(b))) {
165
+ if (!relPath.endsWith('/spec.md'))
166
+ continue;
167
+ const segs = relPath.split('/');
168
+ const { fm, body } = parseFrontmatter(source);
169
+ acc.push({ id: segs[segs.length - 2], parent: null, relPath, fm, body });
170
+ }
171
+ reId(acc);
172
+ return acc;
173
+ }
174
+ const acc = [];
175
+ const specDir = join(root, '.spec');
176
+ if (existsSync(specDir))
177
+ await walkAsync(specDir, null, acc, root);
178
+ reId(acc);
179
+ return acc;
180
+ }
181
+ // the claim rule shared by both relations (exact path, dir-prefix, or *-glob). See [[governed-related]].
182
+ function claimMatcher(file) {
183
+ const rel = file.startsWith('/') ? relative(ROOT, file) : file;
184
+ return (claim) => relationClaimsPath(claim, rel);
185
+ }
186
+ // spec node(s) that GOVERN a file (frontmatter `code:` — source of truth, drives drift + eval freshness); reads only
187
+ // frontmatter (cheap, no git) so a per-edit hook can call it. `scoped` = every claiming entry carries a
188
+ // `#selector` — such a governor still displays, but does not count toward the owners bound ([[code-anchor]]).
189
+ export function specOwners(file) {
190
+ const claims = claimMatcher(file);
191
+ return raws().flatMap((r) => {
192
+ const entries = list(r.fm.code).map(parseCodeEntry).filter((e) => claims(e.path));
193
+ return entries.length ? [{ id: r.id, desc: str(r.fm.desc), scoped: entries.every((e) => e.anchor !== null) }] : [];
194
+ });
195
+ }
196
+ // spec node(s) that REFERENCE a file (frontmatter `related:` — carries coverage, never drift, never eval freshness):
197
+ // [[governed-related]]'s other half, same claim rule, same cheap frontmatter-only read.
198
+ export function specRelated(file) {
199
+ const claims = claimMatcher(file);
200
+ return raws().filter((r) => list(r.fm.related).some((e) => claims(parseCodeEntry(e).path))).map((r) => ({ id: r.id, desc: str(r.fm.desc) }));
201
+ }
202
+ // memo fileDiffAt by (version sha + spec.md path) — a commit's patch is immutable. Keyed by path too: one
203
+ // commit can patch several nodes' spec.md. `{hash:'',patch:''}` for an unversioned node (no git call).
204
+ const diffCache = new Map();
205
+ async function latestDiff(relPath, hash) {
206
+ if (!hash)
207
+ return { hash: '', patch: '' };
208
+ const key = `${hash}\0${relPath}`;
209
+ const hit = diffCache.get(key);
210
+ if (hit)
211
+ return hit;
212
+ const val = { hash, patch: await fileDiffAt(ROOT, relPath, hash) };
213
+ diffCache.set(key, val);
214
+ return val;
215
+ }
216
+ export function loadSpecsLite() {
217
+ return raws().map((r) => ({
218
+ id: r.id,
219
+ title: str(r.fm.title, r.id),
220
+ path: r.relPath,
221
+ desc: str(r.fm.desc),
222
+ body: r.body.trim(),
223
+ }));
224
+ }
225
+ // one node's body + parsed parts, filesystem-only (no git). The board omits both to stay lean
226
+ // ([[graph-lean]]); the detail view fetches them here when a node opens. null when the id isn't a node.
227
+ export function specContent(id) {
228
+ const r = raws().find((x) => x.id === id);
229
+ return r ? { body: r.body.trim(), parts: parseParts(r.body) } : null;
230
+ }
231
+ export async function loadSpecs(root = ROOT, options = {}) {
232
+ // The default pair shares one immutable-event snapshot; explicit sides let callers skip or supply either
233
+ // projection. Every node below is then a pure in-memory lookup.
234
+ const tip = options.tip ?? 'HEAD';
235
+ if (options.snapshot && options.snapshot.tip !== tip) {
236
+ throw new Error(`loadSpecs snapshot tip '${options.snapshot.tip}' does not match requested tip '${tip}'`);
237
+ }
238
+ const indexes = options.history === undefined && options.drift === undefined
239
+ ? sourceIndexes(root, tip)
240
+ : Promise.all([
241
+ options.history === null ? Promise.resolve(null) : options.history ?? historyIndex(root, tip),
242
+ options.drift === null ? Promise.resolve(null) : options.drift ?? driftIndex(root, tip),
243
+ ]);
244
+ const [[idx, didx], allRaws] = await Promise.all([indexes, rawsAsync(root, tip, options.snapshot)]);
245
+ const prepared = allRaws.map((r) => ({
246
+ r,
247
+ h: idx ? rowsFor(idx, r.relPath) : [],
248
+ codeRel: parseRelation(list(r.fm.code), 'code'),
249
+ relatedRel: parseRelation(list(r.fm.related), 'related'),
250
+ }));
251
+ if (didx) {
252
+ const queries = [];
253
+ for (const { r, h, codeRel, relatedRel } of prepared) {
254
+ if (!h[0]?.hash || (!codeRel.entries.length && !relatedRel.entries.some((entry) => !entry.selectors.length)))
255
+ continue;
256
+ queries.push({ hash: h[0].hash, node: r.id });
257
+ }
258
+ primeAncestorClosures(didx, queries.map(({ hash }) => hash));
259
+ // Only an ack named for this node and outside its version's ancestry becomes a cover. Discover that
260
+ // exact roster from the now-primed bases instead of retaining closures for older, non-covering acks.
261
+ const covers = [];
262
+ for (const [hash, nodes] of didx.acks)
263
+ if (queries.some(({ hash: baseHash, node }) => {
264
+ const base = ancestorsOf(didx, baseHash);
265
+ return !!base && nodes.has(node) && !inAncestors(didx, base, hash);
266
+ }))
267
+ covers.push(hash);
268
+ primeAncestorClosures(didx, covers);
269
+ }
270
+ const loaded = [];
271
+ for (const { r, h, codeRel, relatedRel } of prepared) {
272
+ // session = the Session: trailer of the node's latest version; frontmatter `session:` is the fallback.
273
+ const fmSession = str(r.fm.session);
274
+ const session = h[0]?.session || (fmSession && fmSession !== 'null' ? fmSession : null);
275
+ // a code:/related: row may pin symbols (`path#fn` — [[code-anchor]]): parseRelation groups each
276
+ // relation per BASE path, so `code`/`related` carry the distinct PATHS (what every path consumer —
277
+ // drift, claims, eval attribution — expects, file-level as before), the scoped entries (path +
278
+ // selectors) ride separately for lint's anchor engine, and structural problems (duplicates,
279
+ // bare/scoped mixing, glob selectors, the code cap) surface as lint integrity errors.
280
+ const codeEntries = codeRel.entries;
281
+ const code = codeEntries.map((e) => e.path);
282
+ const codeScoped = codeEntries.filter((e) => e.selectors.length > 0);
283
+ const relatedEntries = relatedRel.entries;
284
+ const related = relatedEntries.map((e) => e.path);
285
+ const relatedScoped = relatedEntries.filter((e) => e.selectors.length > 0);
286
+ const relationProblems = [...codeRel.problems, ...relatedRel.problems];
287
+ const S = h[0]?.hash || '';
288
+ const driftFiles = [];
289
+ for (const f of code) {
290
+ const d = didx ? { file: f, behind: driftFor(didx, S, f, r.id) } : { file: f, behind: 0 };
291
+ if (d.behind > 0)
292
+ driftFiles.push(d);
293
+ }
294
+ const drift = driftFiles.reduce((a, d) => a + d.behind, 0);
295
+ // related drift is the SOFT tier ([[governed-related]]): same ancestry basis, but it stays OUT of
296
+ // `drift` — it never feeds status, the commit gate, or eval freshness. It surfaces only as a lint warn nudge.
297
+ // A SCOPED related entry is excluded here: its file-level movement is silent by design — only a
298
+ // selector HIT warns, and that verdict needs the anchor engine, so lint derives it, not the loader.
299
+ const relatedDriftFiles = [];
300
+ for (const e of relatedEntries) {
301
+ if (e.selectors.length)
302
+ continue;
303
+ const d = didx ? { file: e.path, behind: driftFor(didx, S, e.path, r.id) } : { file: e.path, behind: 0 };
304
+ if (d.behind > 0)
305
+ relatedDriftFiles.push(d);
306
+ }
307
+ const fmStatus = str(r.fm.status, '') || null;
308
+ loaded.push({
309
+ id: r.id,
310
+ parent: r.parent,
311
+ path: r.relPath,
312
+ title: str(r.fm.title, r.id),
313
+ status: deriveStatus({ version: h.length, drift, hasCode: code.length > 0, fmStatus: fmStatus ?? undefined }),
314
+ fmStatus,
315
+ session,
316
+ hue: Number(str(r.fm.hue, '210')),
317
+ desc: str(r.fm.desc),
318
+ code,
319
+ codeEntries,
320
+ codeScoped,
321
+ related,
322
+ relatedEntries,
323
+ relatedScoped,
324
+ relationProblems,
325
+ version: h.length,
326
+ reason: h[0]?.reason || '',
327
+ // ISO date of the node's latest version commit (h is newest-first), or null if unversioned.
328
+ lastEdited: h[0]?.date || null,
329
+ drift,
330
+ driftFiles,
331
+ relatedDriftFiles,
332
+ // the latest version's spec.md patch is NOT precomputed here (it cost 2 git show forks per node on
333
+ // cold load); the history tab fetches it lazily via specDiffAt. See [[work-pane]].
334
+ body: r.body.trim(),
335
+ parts: parseParts(r.body),
336
+ });
337
+ }
338
+ return loaded;
339
+ }
340
+ // per-node version timeline; each row sums the node's spec.md stat (rename-followed, read on demand) and its
341
+ // governed-code stat (pathsStats) — separate because spec.md needs rename-following a plain `git log -- path` can't do.
342
+ export async function specHistory(id) {
343
+ const node = raws().find((r) => r.id === id);
344
+ if (!node)
345
+ return [];
346
+ const codePaths = [...new Set(list(node.fm.code).map((e) => parseCodeEntry(e).path))];
347
+ // index (cached) and the code-path walk are independent — run them in parallel, both async git.
348
+ const [idx, cStats] = await Promise.all([historyIndex(ROOT), pathsStats(ROOT, codePaths)]);
349
+ const sStats = await historyStats(ROOT, idx, node.relPath);
350
+ return rowsFor(idx, node.relPath).map((v) => {
351
+ const s = sStats.get(v.hash) ?? { additions: 0, deletions: 0, files: 0 };
352
+ const c = cStats.get(v.hash) ?? { additions: 0, deletions: 0, files: 0 };
353
+ return { ...v, additions: s.additions + c.additions, deletions: s.deletions + c.deletions, files: s.files + c.files };
354
+ });
355
+ }
356
+ // the line-diff a specific version introduced to a node's spec.md, by hash; fetched lazily when a history
357
+ // item expands. fileDiffAt resolves the spec.md path AT that commit (reparents). `{hash:'',patch:''}` for
358
+ // an empty hash, null for an unknown id.
359
+ export async function specDiffAt(id, hash) {
360
+ const node = raws().find((r) => r.id === id);
361
+ if (!node)
362
+ return null;
363
+ if (!hash)
364
+ return { hash: '', patch: '' };
365
+ return latestDiff(node.relPath, hash);
366
+ }
367
+ // @@@ public instance root - cross-repository consumers import this identity instead of matching a folder
368
+ // literal. `plugin-system` remains the separate root for this project's system spec.
369
+ export const PLUGIN_INSTANCE_ROOT = '.plugins';
370
+ // field-driven surface - a plugin is a spec node at ANY depth under a plugin root that carries a
371
+ // `surface: system|command|hook|skill|agent|review` frontmatter field naming where it plugs in. There are no
372
+ // `command/`/`system/`/`hook/`/`skill/`/`agent/` bucket dirs (those were graph-invisible grouping dirs with no spec.md, so
373
+ // the spec graph skipped them — path != graph); the surface is a FIELD on the node, so the plugin is a real
374
+ // graph child (a grouping parent like `.plugins/prompts` is itself a spec node, never a bare dir). BOTH plugin roots participate: `.plugins` (the instance — DIY dev-flow plugins) and
375
+ // `plugin-system` (the project system spec). loadConfig gathers the `command` surface, loadSystemConfig the `system`
376
+ // surface, loadHookConfig the `hook` surface, loadSkillConfig the `skill` surface, loadAgentConfig the `agent`
377
+ // surface (sub-agent definitions); each scans the children under every root and filters by the field. The plugins also show on the board as ordinary spec nodes (via loadSpecs).
378
+ // root node - the spec tree's single top-level node: the one directory directly under .spec/ that
379
+ // holds a spec.md. The dogfood repo names it 'spexcode'; a repo scaffolded by `spex init` names it
380
+ // 'project' (or whatever the adopter renames it to). Detected DYNAMICALLY so the config loaders resolve
381
+ // the ACTUAL root's config dirs — never a hardcoded 'spexcode', which silently returned [] in an adopter
382
+ // repo, so their .plugins/core contract never loaded and their launched agents got no system prompt.
383
+ // Returns null when .spec holds no such directory. (resolveLayout's `main` is a checkout PATH, not the
384
+ // root node NAME, so it can't serve this — a tiny filesystem probe is the right seam.)
385
+ function rootNode() {
386
+ if (!existsSync(SPEC_DIR))
387
+ return null;
388
+ for (const e of readdirSync(SPEC_DIR, { withFileTypes: true })) {
389
+ if (e.isDirectory() && existsSync(join(SPEC_DIR, e.name, 'spec.md')))
390
+ return e.name;
391
+ }
392
+ return null;
393
+ }
394
+ // resolved at call time (not module-eval) so it tracks the live tree.
395
+ // @@@ legacy-tree refusal - v0.3.0 renamed the plugin instance root `.config` → `.plugins`. A pre-0.3.0
396
+ // tree would otherwise load an EMPTY plugin surface — no contract block, no hooks, no commands — and the
397
+ // launched agents would silently run ungoverned. So refuse loudly: existence-only probe (never a dual
398
+ // read of legacy content), pointing at the one-shot migrator. Delete this check in 0.4.0.
399
+ function configRoots() {
400
+ const root = rootNode();
401
+ if (!root)
402
+ return [];
403
+ if (existsSync(join(SPEC_DIR, root, '.config')) && !existsSync(join(SPEC_DIR, root, PLUGIN_INSTANCE_ROOT))) {
404
+ throw new Error(`.spec/${root}/.config exists but .spec/${root}/${PLUGIN_INSTANCE_ROOT} does not — this spec tree predates the v0.3.0 ` +
405
+ `plugin rename (.config → ${PLUGIN_INSTANCE_ROOT}). Refusing to load an empty plugin surface (agents would launch ` +
406
+ `ungoverned). Run \`spex doctor --migrate\` to migrate the tree.`);
407
+ }
408
+ return [PLUGIN_INSTANCE_ROOT, 'plugin-system'].map((r) => join(SPEC_DIR, root, r));
409
+ }
410
+ // co-located bundle files = everything under the node folder except its spec.md, repo-relative, recursive.
411
+ function bundleFiles(dir) {
412
+ const out = [];
413
+ const walk = (d) => {
414
+ for (const e of readdirSync(d, { withFileTypes: true })) {
415
+ const p = join(d, e.name);
416
+ if (e.isDirectory())
417
+ walk(p);
418
+ else if (e.name !== 'spec.md')
419
+ out.push(relative(ROOT, p));
420
+ }
421
+ };
422
+ walk(dir);
423
+ return out.sort();
424
+ }
425
+ // gather the preset nodes under a plugin root that declare `surface: <surface>`. The scan is RECURSIVE —
426
+ // `surface` is a FIELD, not a path (the design's core tenet), so a plugin may live at ANY depth: under a
427
+ // surface-less grouping shelf (the auxiliary `surface: system` contracts live under `.plugins/prompts/`),
428
+ // or under a plugin that is itself a grouping parent (`.plugins/core` is a `surface: system` contract whose
429
+ // CHILDREN are `surface: hook` nodes). The field filter keeps it safe: a node only gathers if it declares THIS
430
+ // surface, so descending past a matched node never double-counts (children carry a different surface),
431
+ // and the gather set is path-independent — regrouping a plugin never changes what materializes.
432
+ function loadSurface(surface) {
433
+ const out = [];
434
+ const visit = (nodeDir, name) => {
435
+ if (existsSync(join(nodeDir, 'spec.md'))) {
436
+ const { fm, body } = parseFrontmatter(readFileSync(join(nodeDir, 'spec.md'), 'utf8'));
437
+ // @@@ skip pending - a `status: pending` plugin is DECLARED INTENT, not yet active. It renders on the
438
+ // board (via loadSpecs) but must NOT gather: neither a command preset, nor folded into a system prompt,
439
+ // nor a live hook. Only built/active plugins surface here, so pending stubs stay inert.
440
+ // the surface field may name SEVERAL surfaces (comma-separated or a YAML list) — the node plugs
441
+ // into every one it lists, so the match is membership, not equality.
442
+ const surfaces = list(fm.surface).flatMap((v) => String(v).split(',')).map((v) => v.trim()).filter(Boolean);
443
+ if (surfaces.includes(surface) && str(fm.status) !== 'pending') {
444
+ out.push({
445
+ name,
446
+ title: str(fm.title, name),
447
+ desc: str(fm.desc),
448
+ kind: str(fm.kind, 'mutating'),
449
+ dir: relative(ROOT, nodeDir),
450
+ files: bundleFiles(nodeDir),
451
+ body: body.trim(),
452
+ events: list(fm.events),
453
+ order: Number(str(fm.order, '0')) || 0,
454
+ block: str(fm.block) === 'true',
455
+ tools: list(fm.tools),
456
+ });
457
+ }
458
+ }
459
+ for (const e of readdirSync(nodeDir, { withFileTypes: true })) {
460
+ if (e.isDirectory())
461
+ visit(join(nodeDir, e.name), e.name);
462
+ }
463
+ };
464
+ for (const root of configRoots()) {
465
+ if (!existsSync(root))
466
+ continue;
467
+ for (const e of readdirSync(root, { withFileTypes: true })) {
468
+ if (e.isDirectory())
469
+ visit(join(root, e.name), e.name);
470
+ }
471
+ }
472
+ return out.sort((a, b) => a.name.localeCompare(b.name));
473
+ }
474
+ export function loadConfig() { return loadSurface('command'); }
475
+ export function loadSystemConfig() { return loadSurface('system'); }
476
+ // the hook handlers (compiled into the per-session hook manifest the dispatcher reads). Each carries its
477
+ // `events`/`order`/`block` binding + co-located script `files`.
478
+ export function loadHookConfig() { return loadSurface('hook'); }
479
+ // the skill bundles (materialized into each harness's auto-discovered SKILL.md dir). Each node's `desc` is the
480
+ // load-trigger and its `body` is the on-demand instructions; loadSurface passes the folder basename as `name`.
481
+ export function loadSkillConfig() { return loadSurface('skill'); }
482
+ // the sub-agent definitions (materialized into each harness's auto-discovered agent dir, e.g. claude's
483
+ // .claude/agents/<name>.md). Like a skill, the node's `desc` is the on-demand load-trigger and its `body` is the
484
+ // agent's system prompt; additionally its `tools` field is the harness tool allowlist for the spawned agent.
485
+ export function loadAgentConfig() { return loadSurface('agent'); }
486
+ // the review-track prose presets ([[review-commands]]): offered in the eval detail's remark-composer `/`
487
+ // dropdown; picking one PREFILLS the composer with the node's `body` ({node}/{scenario}/{expected}
488
+ // placeholders filled at insert time). Display+prefill only — the send stays the ordinary remark write.
489
+ export function loadReviewConfig() { return loadSurface('review'); }
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@spexcode/spec-core",
3
+ "version": "0.6.5",
4
+ "type": "module",
5
+ "description": "SpexCode's dependency-minimal spec graph core.",
6
+ "files": [
7
+ "dist",
8
+ "templates"
9
+ ],
10
+ "exports": {
11
+ ".": "./dist/index.js",
12
+ "./graph-delta": "./dist/graph-delta.js",
13
+ "./review": "./dist/review/index.js",
14
+ "./identity": "./dist/identity-presets.js",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "engines": {
18
+ "node": ">=22"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "build": "node ../../scripts/build-dist.mjs",
25
+ "prepublishOnly": "node ../../scripts/release-publish.mjs --from-package-publish",
26
+ "pack:publishable": "node scripts/pack-publishable.mjs"
27
+ }
28
+ }
@@ -0,0 +1,10 @@
1
+ export declare const MISS_BLOB = "miss original file";
2
+ export declare function cacheDir(): string;
3
+ export declare function putBlob(bytes: Buffer, dir?: string): string;
4
+ export declare function blobPath(sha: string, dir?: string): string;
5
+ export declare function hasBlob(sha: string | null, dir?: string): boolean;
6
+ export declare function resolveBlob(sha: string | null, dir?: string): string;
7
+ export declare function listBlobs(dir?: string): string[];
8
+ export declare function gc(keep: Set<string>, dir?: string): string[];
9
+ export declare function getBlob(sha: string | null, dir?: string): Buffer | null;
10
+ export declare function isStrayBlob(path: string): boolean;
@@ -0,0 +1,50 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdirSync, writeFileSync, readFileSync, existsSync, readdirSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { gitCommonDir } from '@spexcode/spec-core';
5
+ export const MISS_BLOB = 'miss original file';
6
+ export function cacheDir() {
7
+ return join(gitCommonDir(), 'spexcode', 'evidence');
8
+ }
9
+ const BLOB_NAME = /^[0-9a-f]{64}$/;
10
+ export function putBlob(bytes, dir = cacheDir()) {
11
+ const sha = createHash('sha256').update(bytes).digest('hex');
12
+ mkdirSync(dir, { recursive: true });
13
+ const p = join(dir, sha);
14
+ if (!existsSync(p))
15
+ writeFileSync(p, bytes);
16
+ return sha;
17
+ }
18
+ export function blobPath(sha, dir = cacheDir()) {
19
+ return join(dir, sha);
20
+ }
21
+ export function hasBlob(sha, dir = cacheDir()) {
22
+ return !!sha && existsSync(blobPath(sha, dir));
23
+ }
24
+ export function resolveBlob(sha, dir = cacheDir()) {
25
+ if (!sha)
26
+ return '';
27
+ return hasBlob(sha, dir) ? blobPath(sha, dir) : MISS_BLOB;
28
+ }
29
+ export function listBlobs(dir = cacheDir()) {
30
+ if (!existsSync(dir))
31
+ return [];
32
+ return readdirSync(dir).filter((n) => BLOB_NAME.test(n)).sort();
33
+ }
34
+ export function gc(keep, dir = cacheDir()) {
35
+ const removed = [];
36
+ for (const name of listBlobs(dir)) {
37
+ if (keep.has(name))
38
+ continue;
39
+ rmSync(blobPath(name, dir));
40
+ removed.push(name);
41
+ }
42
+ return removed;
43
+ }
44
+ export function getBlob(sha, dir = cacheDir()) {
45
+ return hasBlob(sha, dir) ? readFileSync(blobPath(sha, dir)) : null;
46
+ }
47
+ export function isStrayBlob(path) {
48
+ const base = path.slice(path.lastIndexOf('/') + 1);
49
+ return BLOB_NAME.test(base) || path.includes('spexcode/evidence/') || path.includes('/yatsu-blobs/'); // dead-words-ok: archived cache dir name — a stray copy of the retired cache is still rejected
50
+ }
@@ -0,0 +1,22 @@
1
+ import { type EvalNode, type ScenarioTestReference } from './scenarios.js';
2
+ import { type Verdict } from './sidecar.js';
3
+ import { type EvalTimeline } from './evaltab.js';
4
+ export { isUiPath } from './ui-path.js';
5
+ export declare function nodeChanged(dirRel: string, codeFiles: readonly string[], changed: Set<string>, nodeDirs?: Iterable<string>): boolean;
6
+ declare function checkStaged(): number;
7
+ export declare function formatTimeline(tl: EvalTimeline): string;
8
+ export type ScenarioListRow = {
9
+ node: string;
10
+ scenario: string;
11
+ tags?: string[];
12
+ test?: ScenarioTestReference;
13
+ measured: boolean;
14
+ latest?: {
15
+ verdict?: Verdict;
16
+ ts: string;
17
+ };
18
+ };
19
+ export declare function scenarioListRows(nodes: EvalNode[], unmeasuredOnly?: boolean): ScenarioListRow[];
20
+ export declare function runEval(args: string[]): Promise<number>;
21
+ export { checkStaged };
22
+ export declare function runEvidence(args: string[]): Promise<number>;