spexcode 0.6.2 → 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 (479) hide show
  1. package/README.md +3 -1
  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} +36 -32
  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 +1 -1
  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/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec-first.sh +1 -1
  167. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec-of-file.sh +1 -0
  168. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/spec.md +2 -2
  169. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +31 -29
  170. package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +94 -0
  171. package/node_modules/@spexcode/spec-core/dist/anchors.js +730 -0
  172. package/node_modules/@spexcode/spec-core/dist/git.d.ts +166 -0
  173. package/node_modules/@spexcode/spec-core/dist/git.js +2736 -0
  174. package/node_modules/@spexcode/spec-core/dist/graph-delta.d.ts +44 -0
  175. package/node_modules/@spexcode/spec-core/dist/graph-delta.js +72 -0
  176. package/node_modules/@spexcode/spec-core/dist/graph.d.ts +34 -0
  177. package/node_modules/@spexcode/spec-core/dist/graph.js +237 -0
  178. package/node_modules/@spexcode/spec-core/dist/graphDelta.d.ts +3 -0
  179. package/node_modules/@spexcode/spec-core/dist/graphDelta.js +13 -0
  180. package/node_modules/@spexcode/spec-core/dist/harness-identity.d.ts +31 -0
  181. package/node_modules/@spexcode/spec-core/dist/harness-identity.js +20 -0
  182. package/node_modules/@spexcode/spec-core/dist/identity-presets.d.ts +152 -0
  183. package/node_modules/@spexcode/spec-core/dist/identity-presets.js +132 -0
  184. package/node_modules/@spexcode/spec-core/dist/index.d.ts +45 -0
  185. package/node_modules/@spexcode/spec-core/dist/index.js +19 -0
  186. package/node_modules/@spexcode/spec-core/dist/layout.d.ts +183 -0
  187. package/node_modules/@spexcode/spec-core/dist/layout.js +548 -0
  188. package/node_modules/@spexcode/spec-core/dist/process-identity.d.ts +37 -0
  189. package/node_modules/@spexcode/spec-core/dist/process-identity.js +214 -0
  190. package/node_modules/@spexcode/spec-core/dist/project-identity.d.ts +12 -0
  191. package/node_modules/@spexcode/spec-core/dist/project-identity.js +71 -0
  192. package/node_modules/@spexcode/spec-core/dist/project-store.d.ts +3 -0
  193. package/node_modules/@spexcode/spec-core/dist/project-store.js +14 -0
  194. package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +2 -0
  195. package/node_modules/@spexcode/spec-core/dist/resilience.js +40 -0
  196. package/node_modules/@spexcode/spec-core/dist/review/index.d.ts +3 -0
  197. package/node_modules/@spexcode/spec-core/dist/review/index.js +5 -0
  198. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +77 -0
  199. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +308 -0
  200. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +66 -0
  201. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +180 -0
  202. package/node_modules/@spexcode/spec-core/dist/review/session.d.ts +4 -0
  203. package/node_modules/@spexcode/spec-core/dist/review/session.js +8 -0
  204. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.d.ts +15 -0
  205. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.js +12 -0
  206. package/node_modules/@spexcode/spec-core/dist/root-lru.d.ts +4 -0
  207. package/{spec-cli/src/root-lru.ts → node_modules/@spexcode/spec-core/dist/root-lru.js} +26 -30
  208. package/node_modules/@spexcode/spec-core/dist/specs.d.ts +117 -0
  209. package/node_modules/@spexcode/spec-core/dist/specs.js +489 -0
  210. package/node_modules/@spexcode/spec-core/package.json +28 -0
  211. package/node_modules/@spexcode/spec-eval/dist/cache.d.ts +10 -0
  212. package/node_modules/@spexcode/spec-eval/dist/cache.js +50 -0
  213. package/node_modules/@spexcode/spec-eval/dist/cli.d.ts +22 -0
  214. package/node_modules/@spexcode/spec-eval/dist/cli.js +981 -0
  215. package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +98 -0
  216. package/node_modules/@spexcode/spec-eval/dist/evaltab.js +176 -0
  217. package/node_modules/@spexcode/spec-eval/dist/filing.d.ts +16 -0
  218. package/node_modules/@spexcode/spec-eval/dist/filing.js +36 -0
  219. package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +48 -0
  220. package/node_modules/@spexcode/spec-eval/dist/freshness.js +799 -0
  221. package/node_modules/@spexcode/spec-eval/dist/host.d.ts +46 -0
  222. package/node_modules/@spexcode/spec-eval/dist/host.js +53 -0
  223. package/node_modules/@spexcode/spec-eval/dist/humanok.d.ts +11 -0
  224. package/node_modules/@spexcode/spec-eval/dist/humanok.js +27 -0
  225. package/node_modules/@spexcode/spec-eval/dist/index.d.ts +4 -0
  226. package/node_modules/@spexcode/spec-eval/dist/index.js +4 -0
  227. package/node_modules/@spexcode/spec-eval/dist/remarks.d.ts +31 -0
  228. package/node_modules/@spexcode/spec-eval/dist/remarks.js +1 -0
  229. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.d.ts +13 -0
  230. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.js +303 -0
  231. package/node_modules/@spexcode/spec-eval/dist/scenarios.d.ts +99 -0
  232. package/node_modules/@spexcode/spec-eval/dist/scenarios.js +650 -0
  233. package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +307 -0
  234. package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +1883 -0
  235. package/node_modules/@spexcode/spec-eval/dist/sidecar.d.ts +55 -0
  236. package/node_modules/@spexcode/spec-eval/dist/sidecar.js +82 -0
  237. package/node_modules/@spexcode/spec-eval/dist/timeline.d.ts +25 -0
  238. package/node_modules/@spexcode/spec-eval/dist/timeline.js +65 -0
  239. package/node_modules/@spexcode/spec-eval/dist/ui-path.d.ts +1 -0
  240. package/node_modules/@spexcode/spec-eval/dist/ui-path.js +2 -0
  241. package/node_modules/@spexcode/spec-eval/package.json +36 -0
  242. package/node_modules/@spexcode/spec-forge/dist/cache.d.ts +29 -0
  243. package/node_modules/@spexcode/spec-forge/dist/cache.js +59 -0
  244. package/node_modules/@spexcode/spec-forge/dist/cli.d.ts +1 -0
  245. package/node_modules/@spexcode/spec-forge/dist/cli.js +100 -0
  246. package/node_modules/@spexcode/spec-forge/dist/drivers/github.d.ts +2 -0
  247. package/node_modules/@spexcode/spec-forge/dist/drivers/github.js +116 -0
  248. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.d.ts +7 -0
  249. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.js +144 -0
  250. package/node_modules/@spexcode/spec-forge/dist/drivers.d.ts +10 -0
  251. package/node_modules/@spexcode/spec-forge/dist/drivers.js +83 -0
  252. package/node_modules/@spexcode/spec-forge/dist/index.d.ts +4 -0
  253. package/node_modules/@spexcode/spec-forge/dist/index.js +4 -0
  254. package/node_modules/@spexcode/spec-forge/dist/links.d.ts +15 -0
  255. package/node_modules/@spexcode/spec-forge/dist/links.js +65 -0
  256. package/node_modules/@spexcode/spec-forge/dist/needs-eval.d.ts +10 -0
  257. package/node_modules/@spexcode/spec-forge/dist/needs-eval.js +20 -0
  258. package/node_modules/@spexcode/spec-forge/dist/port.d.ts +53 -0
  259. package/node_modules/@spexcode/spec-forge/dist/port.js +1 -0
  260. package/node_modules/@spexcode/spec-forge/dist/resident.d.ts +7 -0
  261. package/node_modules/@spexcode/spec-forge/dist/resident.js +43 -0
  262. package/node_modules/@spexcode/spec-forge/package.json +32 -0
  263. package/package.json +33 -19
  264. package/spec-cli/bin/spex.mjs +0 -69
  265. package/spec-cli/src/anchors.ts +0 -728
  266. package/spec-cli/src/attach.ts +0 -43
  267. package/spec-cli/src/claude-headless.ts +0 -321
  268. package/spec-cli/src/cli.ts +0 -1258
  269. package/spec-cli/src/client.ts +0 -414
  270. package/spec-cli/src/cockpit.ts +0 -43
  271. package/spec-cli/src/codex-runtime-generations.ts +0 -646
  272. package/spec-cli/src/commit-surgery.ts +0 -68
  273. package/spec-cli/src/contract-filter.ts +0 -202
  274. package/spec-cli/src/delivery-queue.ts +0 -165
  275. package/spec-cli/src/doctor.ts +0 -516
  276. package/spec-cli/src/execution-trace.ts +0 -444
  277. package/spec-cli/src/file-write.ts +0 -22
  278. package/spec-cli/src/gateway-auth.ts +0 -181
  279. package/spec-cli/src/gateway-hub.ts +0 -358
  280. package/spec-cli/src/gateway.ts +0 -461
  281. package/spec-cli/src/git.ts +0 -2556
  282. package/spec-cli/src/graph.ts +0 -271
  283. package/spec-cli/src/graphCache.ts +0 -710
  284. package/spec-cli/src/graphDelta.ts +0 -77
  285. package/spec-cli/src/graphStream.ts +0 -1019
  286. package/spec-cli/src/harness-select.ts +0 -77
  287. package/spec-cli/src/harness.ts +0 -3025
  288. package/spec-cli/src/headless-controller.ts +0 -63
  289. package/spec-cli/src/help.ts +0 -492
  290. package/spec-cli/src/host-resources.ts +0 -762
  291. package/spec-cli/src/host.ts +0 -611
  292. package/spec-cli/src/identity-presets.d.ts +0 -13
  293. package/spec-cli/src/identity-presets.js +0 -138
  294. package/spec-cli/src/index.ts +0 -818
  295. package/spec-cli/src/init.ts +0 -258
  296. package/spec-cli/src/issues-cli.ts +0 -276
  297. package/spec-cli/src/issues.ts +0 -237
  298. package/spec-cli/src/layout.ts +0 -640
  299. package/spec-cli/src/lint.ts +0 -488
  300. package/spec-cli/src/localIssues.ts +0 -538
  301. package/spec-cli/src/materialize.ts +0 -417
  302. package/spec-cli/src/mentions.ts +0 -81
  303. package/spec-cli/src/opencode-headless.ts +0 -205
  304. package/spec-cli/src/pi-headless.ts +0 -153
  305. package/spec-cli/src/plugin-harness.ts +0 -157
  306. package/spec-cli/src/process-identity.ts +0 -207
  307. package/spec-cli/src/project-identity.ts +0 -73
  308. package/spec-cli/src/project-store.ts +0 -17
  309. package/spec-cli/src/pty-bridge.ts +0 -396
  310. package/spec-cli/src/pty-helper.mjs +0 -96
  311. package/spec-cli/src/pty-native-helper.mjs +0 -22
  312. package/spec-cli/src/ranker.ts +0 -191
  313. package/spec-cli/src/reaper.ts +0 -125
  314. package/spec-cli/src/resilience.ts +0 -41
  315. package/spec-cli/src/reviewSnapshot.ts +0 -28
  316. package/spec-cli/src/reviews.ts +0 -434
  317. package/spec-cli/src/runtime-guard.ts +0 -35
  318. package/spec-cli/src/runtime-ownership.ts +0 -88
  319. package/spec-cli/src/search.bench.mjs +0 -80
  320. package/spec-cli/src/search.ts +0 -63
  321. package/spec-cli/src/session-cursors.ts +0 -91
  322. package/spec-cli/src/session-execution.ts +0 -68
  323. package/spec-cli/src/session-files.ts +0 -121
  324. package/spec-cli/src/session-follow.ts +0 -182
  325. package/spec-cli/src/session-reparent.ts +0 -33
  326. package/spec-cli/src/session-timeline.ts +0 -247
  327. package/spec-cli/src/session-web.ts +0 -135
  328. package/spec-cli/src/sessions.ts +0 -3883
  329. package/spec-cli/src/sh.ts +0 -3
  330. package/spec-cli/src/slash-commands.ts +0 -318
  331. package/spec-cli/src/source-files.ts +0 -104
  332. package/spec-cli/src/specs.ts +0 -498
  333. package/spec-cli/src/supervise.ts +0 -245
  334. package/spec-cli/src/tree.ts +0 -118
  335. package/spec-cli/src/tsx-bin.ts +0 -18
  336. package/spec-cli/src/uninstall.ts +0 -171
  337. package/spec-cli/src/uploads.ts +0 -253
  338. package/spec-cli/src/worktree-sources.ts +0 -42
  339. package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +0 -8
  340. package/spec-dashboard/dist/assets/App-B6IbFbGu.js +0 -2
  341. package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
  342. package/spec-dashboard/dist/assets/Dashboard-_sDk7Rcg.js +0 -27
  343. package/spec-dashboard/dist/assets/EvalsPage-BT357Lyt.js +0 -2
  344. package/spec-dashboard/dist/assets/IssuesPage-BG9rNOgV.js +0 -1
  345. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  346. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  347. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  348. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  349. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  350. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  351. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  352. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  353. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  354. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  355. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  356. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  357. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  358. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  359. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  360. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  361. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  362. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  363. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  364. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  365. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  366. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  367. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  368. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  369. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  370. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  371. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  372. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  373. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  374. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  375. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  376. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  377. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  378. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  379. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  380. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  381. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  382. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  383. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  384. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  385. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  386. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  387. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  388. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  389. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  390. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  391. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  392. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  393. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  394. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  395. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  396. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  397. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  398. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  399. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  400. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  401. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  402. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  403. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  404. package/spec-dashboard/dist/assets/MobileApp-D9yPMR3A.js +0 -2
  405. package/spec-dashboard/dist/assets/Modal-B3EcgeA3.js +0 -1
  406. package/spec-dashboard/dist/assets/PageScroll-CNZOugWV.js +0 -1
  407. package/spec-dashboard/dist/assets/ProjectsPage-C2CRHmvs.js +0 -1
  408. package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
  409. package/spec-dashboard/dist/assets/SessionInterface-DOEjuT3l.js +0 -40
  410. package/spec-dashboard/dist/assets/SessionWindow-1nYgZ4CL.js +0 -1
  411. package/spec-dashboard/dist/assets/Settings-7WeTJn0V.js +0 -1
  412. package/spec-dashboard/dist/assets/Thread-CEbzAFQn.js +0 -13
  413. package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
  414. package/spec-dashboard/dist/assets/data-SlvxNA2r.js +0 -1
  415. package/spec-dashboard/dist/assets/index-DLVeOHL-.js +0 -41
  416. package/spec-dashboard/dist/assets/index-DYLGfPjx.css +0 -1
  417. package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
  418. package/spec-dashboard/dist/assets/launch-yxCYr64x.js +0 -276
  419. package/spec-dashboard/dist/assets/sessionSurface--7l69h4F.js +0 -1
  420. package/spec-dashboard/dist/index.html +0 -29
  421. package/spec-dashboard/src/reviewFilters.js +0 -322
  422. package/spec-dashboard/src/reviewQuery.js +0 -174
  423. package/spec-dashboard/src/session.js +0 -199
  424. package/spec-eval/src/cache.ts +0 -57
  425. package/spec-eval/src/cli.ts +0 -894
  426. package/spec-eval/src/evaltab.ts +0 -264
  427. package/spec-eval/src/filing.ts +0 -39
  428. package/spec-eval/src/freshness.ts +0 -836
  429. package/spec-eval/src/humanok.ts +0 -28
  430. package/spec-eval/src/scenariofresh.ts +0 -244
  431. package/spec-eval/src/scenarios.ts +0 -672
  432. package/spec-eval/src/sessioneval.ts +0 -2220
  433. package/spec-eval/src/sidecar.ts +0 -93
  434. package/spec-eval/src/timeline.ts +0 -54
  435. package/spec-forge/src/__fixtures__/github-forge.json +0 -63
  436. package/spec-forge/src/cache.ts +0 -66
  437. package/spec-forge/src/cli.ts +0 -99
  438. package/spec-forge/src/drivers/github.ts +0 -132
  439. package/spec-forge/src/drivers/gitlab.ts +0 -153
  440. package/spec-forge/src/drivers.ts +0 -73
  441. package/spec-forge/src/links.ts +0 -72
  442. package/spec-forge/src/needs-eval.ts +0 -29
  443. package/spec-forge/src/port.ts +0 -36
  444. package/spec-forge/src/resident.ts +0 -46
  445. /package/{spec-cli → node_modules/@spexcode/spec-cli}/README.md +0 -0
  446. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -0
  447. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-sed-v0.fixture +0 -0
  448. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/commit-msg +0 -0
  449. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-checkout +0 -0
  450. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-merge +0 -0
  451. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/pre-commit +0 -0
  452. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/prepare-commit-msg +0 -0
  453. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/reference-transaction +0 -0
  454. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/extract/spec.md +0 -0
  455. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/regroup/spec.md +0 -0
  456. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/rename/spec.md +0 -0
  457. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/spec.md +0 -0
  458. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/tidy/spec.md +0 -0
  459. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -0
  460. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/idle.sh +0 -0
  461. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/spec.md +0 -0
  462. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/mark-active.sh +0 -0
  463. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/spec.md +0 -0
  464. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/fail.sh +0 -0
  465. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/spec.md +0 -0
  466. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec.md +0 -0
  467. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec.md +0 -0
  468. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec.md +0 -0
  469. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -0
  470. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -0
  471. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -0
  472. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -0
  473. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/spec.md +0 -0
  474. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/digest.mjs +0 -0
  475. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/spec.md +0 -0
  476. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/spec.md +0 -0
  477. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/spec.md +0 -0
  478. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/spec.md +0 -0
  479. /package/{spec-cli → node_modules/@spexcode/spec-core}/templates/spexcode.json +0 -0
@@ -1,640 +0,0 @@
1
- import { readFileSync, existsSync, readdirSync } from 'node:fs'
2
- import { join, dirname, resolve } from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
- import { git, repoRoot, gitA, gitAbortError, currentGitBuildAbortSignal, gitInterpretationIdentity, headSha, worktreeSpecSig, worktreeSpecDelta, worktreeSpecDeltas, withGitAbortSignal, type NodeOp } from './git.js'
5
- import { guardWorktree } from './resilience.js'
6
- import { HARNESSES, type HarnessId } from './harness.js'
7
- import { encodeProject, projectRuntimeRoot, spexcodeHome } from './project-store.js'
8
-
9
- export { encodeProject, spexcodeHome } from './project-store.js'
10
-
11
- export type Config = {
12
- main?: string // path to the source-of-truth checkout (default: the `main` worktree)
13
- mainBranch?: string // stable source-of-truth branch stamped by init (default: "main")
14
- branchPrefix?: string // how a branch names its node (default: "node/")
15
- preset?: string // the SELECTED init preset — which cumulative .plugins tier `spex init` seeds (default 'default'; seed-time only, no launcher gate; read by init.ts; see [[init-preset]])
16
- // RETIRED ([[residence]]) — the old three-word footprint vote. Materialized artifacts carry no facts and are never
17
- // tracked now (one residence behavior: the per-clone exclude, plus the content filter for a mixed
18
- // contract file), so the field is IGNORED with a loud non-fatal notice (materialize's retiredAxisNotice);
19
- // it stays in the type only so the notice can read it. The schema deliberately has NO knob for the spec
20
- // DATA: `.spec` + spexcode.json are ALWAYS tracked ("git is the database") — the vocabulary itself makes
21
- // "untrack the spec" unsayable.
22
- render?: string
23
- // RETIRED (residence compat): the old private-overlay toggle — ignored with the same loud notice;
24
- // its data-untrack semantics are long gone. See `spex guide footprint` MIGRATIONS.
25
- private?: boolean
26
- // which harness targets `spex materialize` delivers into — a native HarnessId or a {plugin:"<folder>"}
27
- // bundle; resolved + validated by [[harness-select]] (harness-select.ts). REQUIRED — no default set; `spex init --harness` stamps it.
28
- harnesses?: (string | { plugin?: string })[]
29
- dashboard?: {
30
- apiUrl?: string // the per-project backend the board proxies to (read frontend-side; see api-endpoint)
31
- title?: string // override for the browser-tab name (default: the repo-root basename; see tab-title)
32
- icon?: string // project identity icon: a picker preset id; existing emoji/Iconify/URL values remain supported ([[identity-config]])
33
- showHeadlessLaunchers?: boolean // include headless harness profiles in the dashboard New Session picker (default false; [[launcher-visibility]])
34
- }
35
- uploads?: {
36
- // One resumable attachment policy. Values default from templates/spexcode.json so a project may omit this
37
- // section, while spexcode.local.json can tune the whole top-level section for one machine.
38
- maxBytes?: number // maximum bytes in one attachment (default: templates/spexcode.json)
39
- chunkBytes?: number // raw PATCH payload cap and client slice size (default: templates/spexcode.json)
40
- concurrency?: number // simultaneous attachment streams in one dashboard batch (default: templates/spexcode.json)
41
- requestTimeoutMs?: number // browser timeout for one chunk/complete request (default: templates/spexcode.json)
42
- retryLimit?: number // automatic retries after the first failed transient chunk request (default: templates/spexcode.json)
43
- retryDelayMs?: number // wait between automatic transient-request retries (default: templates/spexcode.json)
44
- incompleteTtlMs?: number // idle staging lifetime before an unfinished transfer expires (default: templates/spexcode.json)
45
- cleanupIntervalMs?: number // reaper interval for stale staging bytes (default: templates/spexcode.json)
46
- minFreeBytes?: number // filesystem capacity retained while reserving attachments (default: templates/spexcode.json)
47
- evidenceMaxBytes?: number // retained ceiling for eval-evidence POST bodies (default: templates/spexcode.json)
48
- }
49
- sessions?: {
50
- maxActive?: number // concurrency cap: max agents AUTONOMOUSLY PROGRESSING at once (default 8; see sessions.ts maxActive)
51
- // named launcher profiles: a session picks ONE by name at create time ([[launcher-select]]), fixing both
52
- // its harness AND its exact launch command; the chosen NAME is persisted on the record so resume reuses the
53
- // same auth. `harness` defaults to 'claude'. Host-specific `cmd`s (abs wrapper paths) belong in the
54
- // gitignored spexcode.local.json — the name is portable, the cmd is a machine fact.
55
- launchers?: { [name: string]: { harness?: HarnessId; cmd: string } }
56
- defaultLauncher?: string // the launcher a create with no explicit --launcher/dropdown pick uses; required for no-choice creates
57
- }
58
- resources?: {
59
- sessionRssMiB?: number // resident-memory budget for one session owner (default 1024)
60
- backendRssMiB?: number // resident-memory budget for this project's backend instance (default 2048)
61
- idleCpuPercent?: number // CPU budget for a non-progressing owner (default 2)
62
- sampleMs?: number // CPU measurement window for an on-demand report (default 1000)
63
- reportIntervalMs?: number // supervisor-owned snapshot cadence (default 60000)
64
- }
65
- serve?: {
66
- // public-exposure config for `spex serve --public` (resolved gateway-side; see [[public-mode]] / gateway.ts).
67
- // The password is NEVER read from here — flag/env only — so this file stays committable.
68
- public?: {
69
- enabled?: boolean // turn public mode on without the --public flag
70
- http?: boolean // drop TLS (the --http escape hatch) — password then travels in cleartext
71
- tls?: { cert?: string; key?: string } // PATHS to your own cert/key; omit for a cached self-signed default
72
- }
73
- }
74
- issues?: {
75
- enabled?: boolean // the [[local-issues]] issues-workflow on/off switch (default ON). OFF silences the post-merge nudge + hides the dashboard view; flip by editing this key (no CLI toggle verb — v0.3.0). A legacy `proposals.enabled` is NOT read; `spex doctor` reports it.
76
- }
77
- forge?: {
78
- host?: string // explicit forge host id ('github'|'gitlab'|…) overriding the origin-remote derivation ([[forge-host]] — read by spec-forge drivers.ts resolveForgeHost, not here). A project fact → committed spexcode.json.
79
- }
80
- }
81
- // the resolved LAYOUT convention — main/mainBranch/branchPrefix filled to defaults. `dashboard`, `sessions`,
82
- // `serve`, `harnesses`, `render`, and `preset` are frontend/runtime/policy concerns (read separately via readConfig —
83
- // preset by init.ts at seed time, harnesses by [[harness-select]]; see api-endpoint / sessions.ts maxActive /
84
- // gateway.ts), NOT layout fields, so they stay out of the convention rather than forcing a default.
85
- type Convention = Required<Omit<Config, 'dashboard' | 'uploads' | 'sessions' | 'resources' | 'serve' | 'harnesses' | 'preset' | 'issues' | 'forge' | 'private' | 'render'>>
86
-
87
- export type Worktree = {
88
- path: string; branch: string | null; node: string | null
89
- session: string | null; status: string | null; isMain: boolean
90
- liveness?: 'offline' | 'unknown'
91
- ops: NodeOp[] // pending spec-node changes this worktree makes vs main (the board's overlay)
92
- }
93
- export type Layout = { main: string; convention: Convention; worktrees: Worktree[] }
94
-
95
- // Read an OPTIONAL JSON config file. An ABSENT file is the legitimate default (return {}); a
96
- // PRESENT-but-malformed one is a user error we must NOT swallow — a typo would otherwise silently
97
- // drop every tuned setting the file holds (lint budgets, launchers, layout) and revert to defaults
98
- // with no diagnostic. Fail LOUD, naming the file and the parse error, so the author sees what broke.
99
- export function readJsonConfig(p: string): any {
100
- if (!existsSync(p)) return {}
101
- try { return JSON.parse(readFileSync(p, 'utf8')) }
102
- catch (e) {
103
- const err = new Error(`malformed ${p}: ${(e as Error).message}\n → its settings were NOT applied. Fix the JSON syntax (an absent file is a fine default; a broken one is not).`)
104
- err.name = 'ConfigError' // rendered message-only at the CLI boundary (like BackendError), not as a stack dump
105
- throw err
106
- }
107
- }
108
- // committed `spexcode.json` with an OPTIONAL machine-local `spexcode.local.json` layered on top (gitignored).
109
- // The local layer is the durable home for HOST-SPECIFIC values that must never be committed — e.g. an
110
- // absolute worker-launcher path (the host-path leak the repo otherwise warns against). Precedence per field:
111
- // local over committed; a targeted env override (e.g. SPEXCODE_CODEX_SERVER_CMD) still wins at its read site.
112
- export function readConfig(root: string): Config {
113
- const committed = readJsonConfig(join(root, 'spexcode.json'))
114
- const local = readJsonConfig(join(root, 'spexcode.local.json'))
115
- const out: any = { ...committed }
116
- for (const k of Object.keys(local)) {
117
- const b = committed[k], o = local[k]
118
- out[k] = (b && o && typeof b === 'object' && typeof o === 'object' && !Array.isArray(o)) ? { ...b, ...o } : o
119
- }
120
- return out
121
- }
122
-
123
- export type UploadPolicy = Required<NonNullable<Config['uploads']>>
124
-
125
- const TEMPLATE_CONFIG = fileURLToPath(new URL('../templates/spexcode.json', import.meta.url))
126
- const MIN_POSITIVE_INTEGER = 1
127
- const MIN_NONNEGATIVE_INTEGER = 0
128
-
129
- function uploadConfigError(field: keyof UploadPolicy, rule: string): never {
130
- const error = new Error(`uploads.${field} must be ${rule}`)
131
- error.name = 'ConfigError'
132
- throw error
133
- }
134
-
135
- function configuredInteger(value: unknown, field: keyof UploadPolicy, minimum: number): number {
136
- if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < minimum) {
137
- uploadConfigError(field, minimum === MIN_POSITIVE_INTEGER ? 'a positive integer' : 'a non-negative integer')
138
- }
139
- return value
140
- }
141
-
142
- function resolveUploadPolicy(values: Record<keyof UploadPolicy, unknown>): UploadPolicy {
143
- const policy: UploadPolicy = {
144
- maxBytes: configuredInteger(values.maxBytes, 'maxBytes', MIN_POSITIVE_INTEGER),
145
- chunkBytes: configuredInteger(values.chunkBytes, 'chunkBytes', MIN_POSITIVE_INTEGER),
146
- concurrency: configuredInteger(values.concurrency, 'concurrency', MIN_POSITIVE_INTEGER),
147
- requestTimeoutMs: configuredInteger(values.requestTimeoutMs, 'requestTimeoutMs', MIN_POSITIVE_INTEGER),
148
- retryLimit: configuredInteger(values.retryLimit, 'retryLimit', MIN_NONNEGATIVE_INTEGER),
149
- retryDelayMs: configuredInteger(values.retryDelayMs, 'retryDelayMs', MIN_NONNEGATIVE_INTEGER),
150
- incompleteTtlMs: configuredInteger(values.incompleteTtlMs, 'incompleteTtlMs', MIN_POSITIVE_INTEGER),
151
- cleanupIntervalMs: configuredInteger(values.cleanupIntervalMs, 'cleanupIntervalMs', MIN_POSITIVE_INTEGER),
152
- minFreeBytes: configuredInteger(values.minFreeBytes, 'minFreeBytes', MIN_NONNEGATIVE_INTEGER),
153
- evidenceMaxBytes: configuredInteger(values.evidenceMaxBytes, 'evidenceMaxBytes', MIN_POSITIVE_INTEGER),
154
- }
155
- if (policy.chunkBytes > policy.maxBytes) uploadConfigError('chunkBytes', 'no greater than uploads.maxBytes')
156
- return policy
157
- }
158
-
159
- export function uploadPolicyDefaults(): UploadPolicy {
160
- return resolveUploadPolicy(readJsonConfig(TEMPLATE_CONFIG).uploads as Record<keyof UploadPolicy, unknown>)
161
- }
162
-
163
- // The seed template is the sole default-value source. Existing projects may omit `uploads`; they receive this
164
- // policy, while a committed/local value overrides it through readConfig's existing one-level merge.
165
- export function readUploadPolicy(root: string): UploadPolicy {
166
- const configured = readConfig(root).uploads
167
- return resolveUploadPolicy({ ...uploadPolicyDefaults(), ...configured } as Record<keyof UploadPolicy, unknown>)
168
- }
169
-
170
- // the shared git common dir (env-stripped git() so a hook's exported GIT_DIR can't misdirect it). Memoized:
171
- // it's a process constant, but mainBranch()/mainRoot() resolve it per call (~60 git rev-parse forks per board build without the cache).
172
- let commonDirCache: string | null = null
173
- const commonDirsByPath = new Map<string, string>()
174
- const topsByPath = new Map<string, string>()
175
- export function gitCommonDir(): string {
176
- if (commonDirCache === null) commonDirCache = git(['rev-parse', '--path-format=absolute', '--git-common-dir']).trim()
177
- return commonDirCache
178
- }
179
- function commonDirFor(proj: string): string {
180
- const key = resolve(proj)
181
- const known = commonDirsByPath.get(key)
182
- if (known) return known
183
- const common = git(['-C', proj, 'rev-parse', '--path-format=absolute', '--git-common-dir']).trim()
184
- commonDirsByPath.set(key, common)
185
- return common
186
- }
187
- function topFor(proj: string): string {
188
- const key = resolve(proj)
189
- const known = topsByPath.get(key)
190
- if (known) return known
191
- const top = git(['-C', proj, 'rev-parse', '--show-toplevel']).trim()
192
- topsByPath.set(key, top)
193
- return top
194
- }
195
-
196
- export function mainBranch(): string {
197
- let checkout: string
198
- try { checkout = mainCheckout() } catch { return 'main' }
199
- return readConfig(checkout).mainBranch?.trim() || 'main'
200
- }
201
-
202
- // the MAIN checkout (the root working tree) for a project — the SAME answer from main OR any linked worktree
203
- // (dirname of the shared git common dir). Codex reads a LINKED worktree's PROJECT hooks from the root checkout's
204
- // `.codex` (codex-rs hooks_config_folder override), NOT the worktree's, so the codex hooks shim + trust
205
- // materialize here while AGENTS.md/skills stay per-worktree — see [[harness-adapter]] (harness.ts).
206
- export function mainCheckout(proj?: string): string {
207
- const gcd = proj ? commonDirFor(proj) : gitCommonDir()
208
- return dirname(gcd)
209
- }
210
-
211
- // @@@ main root - identity checks need creation's configured-main answer without the record/overlay work in resolveLayout.
212
- export function mainRoot(proj?: string): string {
213
- if (proj) {
214
- const checkout = mainCheckout(proj)
215
- const configured = readConfig(checkout).main?.trim()
216
- return configured ? resolve(checkout, configured) : checkout
217
- }
218
- try {
219
- const checkout = mainCheckout()
220
- const configured = readConfig(checkout).main?.trim()
221
- return configured ? resolve(checkout, configured) : checkout
222
- } catch { return repoRoot() }
223
- }
224
-
225
- // @@@ global per-session store - Fork A: NO SpexCode files live in the worktree any more, so the worktree's
226
- // spec/code tree is pristine (zero per-session pollution). Every per-session runtime artifact — the
227
- // structured record (session.json) AND the launcher products (prompt, launch, launch.sh) AND the recorded comms AND
228
- // the spec-discipline sentinels — lives in a per-USER GLOBAL store, keyed by the harness `session_id` so two
229
- // agents in one folder never clobber, and grouped PER PROJECT (mirroring Claude's ~/.claude/projects/<enc>/)
230
- // so the board enumerates ONE directory. This is the single seam that knows where the store sits; sessions.ts
231
- // and the shell hooks resolve through the SAME scheme (the hooks reimplement it in bash, so any change here
232
- // must be mirrored in .plugins/core/*/). SPEXCODE_HOME overrides the root for test isolation.
233
- // encode a project-root path into ONE safe directory segment (Claude's scheme: path separators → '-'). The
234
- // SAME transform runs in TS and in the shell hooks, so a board read and a hook write land on the SAME dir.
235
- // this project's per-PROJECT runtime tier — the sessions/ records AND the per-TREE materialize slots (below) —
236
- // living under the SAME global per-project dir, so NOTHING SpexCode materializes stays in the worktree (the
237
- // worktree holds only the harness-discovered CLAUDE.md/AGENTS.md + shims, which must sit in-tree).
238
- // proj-aware for `spex init <dir>` / materialize(proj); cwd-based default for the hooks/board. The shell
239
- // hooks mirror this as hp_runtime_dir.
240
- export function runtimeRoot(proj?: string): string {
241
- const gcd = proj ? commonDirFor(proj) : gitCommonDir()
242
- return projectRuntimeRoot(gcd)
243
- }
244
- // the per-WORKTREE materialize slot — <runtime>/trees/<enc(worktree-toplevel)> — holding the materialize
245
- // products that are a pure function of ONE tree's .plugins (hooks-manifest, content-hash, plugin-folders).
246
- // Slotted per tree exactly like sessions/<id> is slotted per session: the old single global file made the
247
- // last-materialized tree win, so dispatch ran tree A's compiled hook set inside tree B's sessions
248
- // ([[hook-dispatch]]). Key = the sessions encodeProject transform over `rev-parse --show-toplevel`, the
249
- // SAME derivation dispatch.sh's shell mirror (hp_tree_dir) runs from its own cwd — so writer and reader
250
- // land on the same slot from the same tree, and only from the same tree. Throws when `wt` is not a live
251
- // git tree (fail loud); a best-effort caller (the close-time GC) wraps it.
252
- export function treeSlotDir(wt: string): string {
253
- const top = topFor(wt)
254
- return join(runtimeRoot(wt), 'trees', encodeProject(top || wt))
255
- }
256
- // this project's per-session records dir, one session's dir, its structured record, and a sibling artifact —
257
- // all keyed by session_id under <home>/projects/<enc>/sessions/.
258
- export function sessionsRoot(): string { return join(runtimeRoot(), 'sessions') }
259
- export function sessionStoreDir(id: string): string { return join(sessionsRoot(), id) }
260
- export function sessionRecordPath(id: string): string { return join(sessionStoreDir(id), 'session.json') }
261
- export function sessionArtifactPath(id: string, name: string): string { return join(sessionStoreDir(id), name) }
262
-
263
- // the structured per-session record, as it sits on disk. Written one-field-per-line with EVERY key present
264
- // (see sessions.ts writeRecord) so the hot-path mark-active shell hook can value-replace status/proposal/note
265
- // with sed and never needs jq. Read here for the overlay; sessions.ts owns the full typed read/write.
266
- export type RawRecord = {
267
- session_id: string; governed: boolean; worktree_path: string; branch: string | null
268
- node: string | null; title: string | null; name: string | null; parent?: string | null
269
- status: string; proposal: string | null; merges: number; note: string | null
270
- sortkey: number | null; createdAt: number; harness?: string; harness_session_id?: string
271
- stopped?: boolean
272
- archived?: boolean // the human ARCHIVED this session ([[archive]]) — only a proven cold/offline row; absent → false on old records
273
- cold_proof?: string // durable exact leaf + adapter cold proof; absent on legacy archives, which remain visible hazards
274
- adapter_recovery?: string // explicit lifecycle recovery required after a partial adapter mutation; absent on old records
275
- launcher?: string // the launcher profile this session was created under ([[launcher-select]]); absent/empty only on old records predating launchers
276
- launch_cmd?: string // the RESOLVED base launcher command PINNED at creation, so a resume replays the EXACT launcher (and its config-dir env) that made the conversation, never a since-changed default ([[launcher-select]] resume-launcher-pin); absent → old record, fall back to the launcher name / ambient
277
- create_request_id?: string // SHA-256 digest of the create Idempotency-Key; the raw key is never persisted
278
- create_payload_hash?: string // normalized create payload bound to create_request_id
279
- base?: string // the exact fork point pinned at creation; absent/empty → the auto-detected source-of-truth branch
280
- launch_readiness_pending?: '' | RawLaunchReadinessPending
281
- }
282
-
283
- export const SESSION_LIFECYCLES = ['active', 'idle', 'awaiting', 'parked', 'error', 'asking', 'queued'] as const
284
- export const SESSION_PROPOSALS = ['merge', 'nothing', 'close'] as const
285
- export type SessionLifecycle = typeof SESSION_LIFECYCLES[number]
286
- export type SessionProposal = typeof SESSION_PROPOSALS[number]
287
- const sessionLifecycles = new Set<string>(SESSION_LIFECYCLES)
288
- const sessionProposals = new Set<string>(SESSION_PROPOSALS)
289
- export const isSessionLifecycle = (value: unknown): value is SessionLifecycle =>
290
- typeof value === 'string' && sessionLifecycles.has(value)
291
- export const isSessionProposal = (value: unknown): value is SessionProposal =>
292
- typeof value === 'string' && sessionProposals.has(value)
293
-
294
- export type RawLaunchReadinessOriginal = {
295
- status: string
296
- proposal: string | null
297
- note: string | null
298
- stopped: boolean
299
- archived: boolean
300
- cold_proof: string | null
301
- adapter_recovery: string | null
302
- }
303
-
304
- export type RawLaunchReadinessPending = {
305
- version: 1
306
- startedAt: number
307
- original: RawLaunchReadinessOriginal
308
- }
309
-
310
- // A launch candidate is durable before it is public. Readers of the authored lifecycle use this one parser
311
- // so the board and the independent timeline observer cannot disagree about an in-flight resume. Invalid
312
- // pending bytes throw: a damaged publication fence is unknowable state, never permission to project online.
313
- export function rawLaunchReadinessOriginal(raw: RawRecord): RawLaunchReadinessOriginal | null {
314
- const pending = raw.launch_readiness_pending
315
- if (pending == null || pending === '') return null
316
- const original = pending && typeof pending === 'object' ? pending.original : null
317
- if (pending.version !== 1 || !Number.isFinite(pending.startedAt) || !original || typeof original !== 'object'
318
- || !isSessionLifecycle(original.status)
319
- || !(original.proposal === null || original.proposal === '' || isSessionProposal(original.proposal))
320
- || !(typeof original.note === 'string' || original.note === null)
321
- || typeof original.stopped !== 'boolean' || typeof original.archived !== 'boolean'
322
- || !(typeof original.cold_proof === 'string' || original.cold_proof === null)
323
- || !(typeof original.adapter_recovery === 'string' || original.adapter_recovery === null)) {
324
- throw new Error(`session '${raw.session_id}' has an invalid launch_readiness_pending fence`)
325
- }
326
- return original
327
- }
328
-
329
- // the agent's OWN session id from the environment — the only locator now that the record left the worktree.
330
- // Three tiers, in order:
331
- // (1) a harness's per-thread env var (`sessionEnvVar`) RESOLVED VIA THE ALIAS — when it lands on a governed
332
- // record (directly, or through that record's `harness_session_id`), that record's SpexCode id is the
333
- // answer. This MUST win: codex's design-C runs ONE shared per-project app-server whose env carries the
334
- // FIRST launched session's `SPEXCODE_SESSION_ID`, and the agent's shell tool (its `spex session
335
- // done/park/ask`) runs INSIDE that app-server process, so `SPEXCODE_SESSION_ID` is contaminated with the
336
- // wrong session. But codex injects the ACTING thread's id into every spawned command's env as
337
- // CODEX_THREAD_ID (== codex's `sessionEnvVar`), so the per-thread var aliases to the RIGHT record while
338
- // the shared `SPEXCODE_SESSION_ID` does not.
339
- // (2) else `SPEXCODE_SESSION_ID` (the GOVERNED record id the launcher bakes in) — the claude path and the
340
- // non-shared baseline.
341
- // (3) else a harness's env var RAW — a self-launched, non-governed agent's own minted id, which has no
342
- // governed record to alias to (codex CODEX_THREAD_ID / claude CLAUDE_CODE_SESSION_ID). The RAW form must
343
- // stay BELOW (2): an un-aliased codex thread id is not a record key, so it must never beat a real
344
- // `SPEXCODE_SESSION_ID`.
345
- // Claude is UNCHANGED: its `sessionEnvVar` (CLAUDE_CODE_SESSION_ID) already EQUALS its record id, so tier (1)
346
- // resolves to that very id — the same value `SPEXCODE_SESSION_ID` would have returned; there is no shared
347
- // app-server to contaminate it. No worktree fallback. (sessions.ts's `ownSessionId` delegates here; spec-eval
348
- // reads it to resolve the current node.)
349
- export function envSessionId(): string | null {
350
- for (const h of HARNESSES) {
351
- const v = process.env[h.sessionEnvVar]
352
- if (v && v.trim()) { const r = readAliasedRawRecord(v.trim()); if (r) return r.session_id }
353
- }
354
- const o = process.env.SPEXCODE_SESSION_ID
355
- if (o && o.trim()) return o.trim()
356
- for (const h of HARNESSES) { const v = process.env[h.sessionEnvVar]; if (v && v.trim()) return v.trim() }
357
- return null
358
- }
359
- // @@@ RecordEntry - a record read has THREE outcomes, and collapsing them is what let a live session read as
360
- // "no session record". ABSENT (no file) is the legitimate nothing — a self-launched agent that only ever wrote
361
- // spec-discipline sentinels has a store dir and no record. CORRUPT (present but unparseable, or parseable but
362
- // not a record) is a FACT about a session that exists, so it must reach the surfaces as itself instead of
363
- // masquerading as absence: sessions-core refuses every writer on it and the board gives it its own row. Any
364
- // OTHER read failure (permissions, I/O) still THROWS — a transient fault must not read as either.
365
- export type RecordEntry =
366
- | { kind: 'ok'; raw: RawRecord }
367
- | { kind: 'absent' }
368
- | { kind: 'corrupt'; path: string; error: string }
369
-
370
- export type PublicRecordEntry =
371
- | { kind: 'ok'; raw: RawRecord; liveness: 'offline' | null }
372
- | { kind: 'absent' }
373
- | { kind: 'corrupt'; sessionId: string; governed: boolean | null; path: string; error: string; liveness: 'unknown' }
374
-
375
- export function readRecordEntry(id: string): RecordEntry {
376
- const path = sessionRecordPath(id)
377
- let text: string
378
- try { text = readFileSync(path, 'utf8') }
379
- catch (e) { if ((e as NodeJS.ErrnoException).code === 'ENOENT') return { kind: 'absent' }; throw e }
380
- let raw: unknown
381
- try { raw = JSON.parse(text) }
382
- catch (e) { return { kind: 'corrupt', path, error: e instanceof Error ? e.message : String(e) } }
383
- if (!raw || typeof raw !== 'object' || !(raw as RawRecord).session_id)
384
- return { kind: 'corrupt', path, error: 'parsed, but carries no session_id — not a session record' }
385
- return { kind: 'ok', raw: raw as RawRecord }
386
- }
387
-
388
- // The ONE public session-record parser. Internal mutation/readiness code uses readRecordEntry's exact raw
389
- // candidate; every public projection passes through here. A valid pending fence replaces all lifecycle-facing
390
- // fields with its frozen original and forces offline liveness. Malformed pending bytes remain a present,
391
- // corrupt/unknown row instead of leaking candidate state or disappearing as absence.
392
- export function projectPublicRecordEntry(id: string, entry: RecordEntry): PublicRecordEntry {
393
- if (entry.kind === 'absent') return entry
394
- if (entry.kind === 'corrupt') return {
395
- kind: 'corrupt', sessionId: id, governed: null, path: entry.path, error: entry.error, liveness: 'unknown',
396
- }
397
- try {
398
- const original = rawLaunchReadinessOriginal(entry.raw)
399
- if (!original) return { kind: 'ok', raw: entry.raw, liveness: null }
400
- return {
401
- kind: 'ok',
402
- raw: {
403
- ...entry.raw,
404
- status: original.status,
405
- proposal: original.proposal || null,
406
- note: original.note || null,
407
- stopped: original.stopped,
408
- archived: original.archived,
409
- cold_proof: original.cold_proof ?? undefined,
410
- adapter_recovery: original.adapter_recovery ?? undefined,
411
- launch_readiness_pending: '',
412
- },
413
- liveness: 'offline',
414
- }
415
- } catch (error) {
416
- return {
417
- kind: 'corrupt',
418
- sessionId: id,
419
- governed: typeof entry.raw.governed === 'boolean' ? entry.raw.governed : null,
420
- path: sessionRecordPath(id),
421
- error: error instanceof Error ? error.message : String(error),
422
- liveness: 'unknown',
423
- }
424
- }
425
- }
426
-
427
- export function readPublicRecordEntry(id: string): PublicRecordEntry {
428
- return projectPublicRecordEntry(id, readRecordEntry(id))
429
- }
430
- export function readRawRecord(id: string): RawRecord | null {
431
- try { const e = readRecordEntry(id); return e.kind === 'ok' ? e.raw : null }
432
- catch { return null }
433
- }
434
- // resolve a possibly-ALIASED session id to its raw record. A codex hook or spawned command can carry the codex
435
- // THREAD id — payload session_id / CODEX_THREAD_ID — not the SpexCode record id the store is keyed by. Direct id
436
- // wins; else the one record that captured this id as `harness_session_id` (the backend stored it at thread/start,
437
- // before any tool turn).
438
- // Null when neither resolves. Mirrors the shell `hp_store_dir` alias grep — one resolution rule, both layers.
439
- export function readAliasedRawRecord(id: string): RawRecord | null {
440
- const e = readAliasedRecordEntry(id)
441
- return e.kind === 'ok' ? e.raw : null
442
- }
443
- // the same alias resolution, keeping the three-way outcome. A CORRUPT record at the direct id settles the
444
- // question — we found this session and cannot read it; walking on to the alias would report a corrupt record
445
- // as absent, the exact collapse this type exists to prevent.
446
- export function readAliasedRecordEntry(id: string): RecordEntry {
447
- const direct = readRecordEntry(id)
448
- if (direct.kind !== 'absent') return direct
449
- // @@@ absence splits in two, and only one half is an alias question - an id owning a store dir is already
450
- // one of ours (the sentinel-only agent above), so its emptiness is settled; searching would let an unrelated
451
- // record answer under a live session's own name, and costs a whole-store re-parse per 1s supervisor tick.
452
- if (existsSync(sessionStoreDir(id))) return { kind: 'absent' }
453
- for (const sid of listSessionIds()) {
454
- const r = readRawRecord(sid)
455
- if (r && r.harness_session_id && r.harness_session_id === id) return { kind: 'ok', raw: r }
456
- }
457
- return { kind: 'absent' }
458
- }
459
- // every session_id this project has a record for (the board's enumeration source — replaces `git worktree
460
- // list`). A MISSING store dir means no session ever launched → []. But any OTHER readdir failure THROWS
461
- // (preserving the fail-loud-enumeration invariant `git worktree list` had): a transient FS error must never
462
- // read as "every session vanished" — the watch poll skips the tick on a throw, never emitting a false mass-close.
463
- export function listSessionIds(): string[] {
464
- let ents
465
- try { ents = readdirSync(sessionsRoot(), { withFileTypes: true }) }
466
- catch (e) { if ((e as NodeJS.ErrnoException).code === 'ENOENT') return []; throw e }
467
- return ents.filter((d) => d.isDirectory()).map((d) => d.name)
468
- }
469
-
470
- // @@@ branch -> session, so a moved ref can DERIVE its invalidation scope - a session's evaluation depends
471
- // on its own branch tip, the base branch tip and their merge-base; no other ref participates. Without this
472
- // map a ref watcher knows only that "something under refs/ moved" and the honest fallback is to invalidate
473
- // every session ([[taste]] 19). One record read per session, and only when the session store itself moved.
474
- export function sessionBranchIndex(): Map<string, string> {
475
- const index = new Map<string, string>()
476
- for (const id of listSessionIds()) {
477
- let branch: unknown
478
- try { branch = readRawRecord(id)?.branch } catch { continue } // a corrupt record narrows nothing
479
- if (typeof branch === 'string' && branch) index.set(branch, id)
480
- }
481
- return index
482
- }
483
-
484
- // Retain only completed per-worktree overlays. Interpretation + main tip + HEAD + working signature completely
485
- // determine the merge-base and projection; a landed main tip therefore dissolves now-moot ops on the next read.
486
- const deltaCache = new Map<string, { key: string; ops: NodeOp[] }>()
487
- const safeHead = (p: string): string => { try { return headSha(p) } catch { return '' } }
488
- let layoutHeadWarned = false
489
- type LayoutDeltaOutcome = { ops: NodeOp[] } | { error: unknown }
490
- type LayoutDeltaFlight = {
491
- promise: Promise<Map<string, LayoutDeltaOutcome>>
492
- controller: AbortController
493
- waiters: Set<symbol>
494
- settled: boolean
495
- }
496
- const layoutDeltaFlights = new Map<string, LayoutDeltaFlight>()
497
-
498
- // One exact public layout generation owns the cold overlay computation. The map is only an in-flight join:
499
- // the entry is deleted at settlement and deltaCache remains the sole retained result state.
500
- async function layoutDeltas(paths: string[], main: string, mainRef: string, mainSha: string): Promise<Map<string, LayoutDeltaOutcome>> {
501
- const snapshots = paths.map((path) => ({ path, head: safeHead(path), sig: worktreeSpecSig(path) }))
502
- const interpretation = gitInterpretationIdentity(main)
503
- const flightKey = JSON.stringify([interpretation, mainRef, mainSha, snapshots.map(({ path, head, sig }) => [path, head, sig]).sort((a, b) => a[0].localeCompare(b[0]))])
504
- let flight = layoutDeltaFlights.get(flightKey)
505
- if (flight?.controller.signal.aborted) {
506
- layoutDeltaFlights.delete(flightKey)
507
- flight = undefined
508
- }
509
- if (!flight) {
510
- const controller = new AbortController()
511
- const entry: LayoutDeltaFlight = { promise: Promise.resolve(new Map()), controller, waiters: new Set(), settled: false }
512
- entry.promise = withGitAbortSignal(controller.signal, async () => {
513
- const outcomes = new Map<string, LayoutDeltaOutcome>()
514
- const misses: typeof snapshots = []
515
- for (const snapshot of snapshots) {
516
- const key = `${interpretation}\0${mainSha}\0${snapshot.head}\0${snapshot.sig}`
517
- const hit = snapshot.head && mainSha ? deltaCache.get(snapshot.path) : null
518
- if (hit?.key === key) outcomes.set(snapshot.path, { ops: hit.ops })
519
- else misses.push(snapshot)
520
- }
521
-
522
- const valid = misses.filter(({ head }) => !!head && !!mainSha)
523
- const batched = await worktreeSpecDeltas(main, mainSha, valid.map(({ path, head }) => ({ path, head })), interpretation)
524
- for (const snapshot of valid) {
525
- const outcome = batched.get(snapshot.path) ?? { error: new Error(`layout overlay batch omitted ${snapshot.path}`) }
526
- if ('error' in outcome) outcomes.set(snapshot.path, outcome)
527
- else {
528
- const key = `${interpretation}\0${mainSha}\0${snapshot.head}\0${snapshot.sig}`
529
- deltaCache.set(snapshot.path, { key, ops: outcome.ops })
530
- outcomes.set(snapshot.path, { ops: outcome.ops })
531
- }
532
- }
533
-
534
- await Promise.all(misses.filter(({ head }) => !head || !mainSha).map(async (snapshot) => {
535
- if (!existsSync(snapshot.path)) {
536
- outcomes.set(snapshot.path, { error: new Error(`worktree ${snapshot.path} is absent`) })
537
- return
538
- }
539
- if (!layoutHeadWarned) {
540
- layoutHeadWarned = true
541
- console.warn('spec-cli: layout overlay cache bypassed (unreadable HEAD/main tip), recomputing every read')
542
- }
543
- try { outcomes.set(snapshot.path, { ops: await worktreeSpecDelta(snapshot.path, mainRef) }) }
544
- catch (error) { outcomes.set(snapshot.path, { error }) }
545
- }))
546
- return outcomes
547
- }).finally(() => {
548
- entry.settled = true
549
- if (layoutDeltaFlights.get(flightKey) === entry) layoutDeltaFlights.delete(flightKey)
550
- })
551
- layoutDeltaFlights.set(flightKey, entry)
552
- flight = entry
553
- }
554
-
555
- const token = Symbol(flightKey)
556
- const callerSignal = currentGitBuildAbortSignal()
557
- flight.waiters.add(token)
558
- let onAbort: (() => void) | null = null
559
- try {
560
- if (!callerSignal) return await flight.promise
561
- if (callerSignal.aborted) throw gitAbortError()
562
- return await Promise.race([
563
- flight.promise,
564
- new Promise<Map<string, LayoutDeltaOutcome>>((_, reject) => {
565
- onAbort = () => reject(gitAbortError())
566
- callerSignal.addEventListener('abort', onAbort, { once: true })
567
- }),
568
- ])
569
- } finally {
570
- if (onAbort) callerSignal?.removeEventListener('abort', onAbort)
571
- flight.waiters.delete(token)
572
- if (!flight.settled && flight.waiters.size === 0) flight.controller.abort()
573
- }
574
- }
575
-
576
- export async function resolveLayout(options: { activeSessionIds?: readonly string[] } = {}): Promise<Layout> {
577
- const root = repoRoot()
578
- const main = dirname(gitCommonDir()) // the main checkout — same answer from main OR any linked worktree
579
- const cfg = readConfig(main)
580
- const base = mainBranch()
581
- const convention: Convention = {
582
- main: cfg.main || '',
583
- mainBranch: base,
584
- branchPrefix: cfg.branchPrefix ?? 'node/',
585
- }
586
- const mainRef = base
587
- // the board enumerates the GLOBAL per-session store (NOT `git worktree list`): every GOVERNED record this
588
- // project owns, each carrying the worktree_path its spec-delta is computed from. Non-governed (user-self-
589
- // launched) records are excluded — board state is a managed-session concern ([[state]]). Each delta is
590
- // independent → compute (or cache-hit) in parallel, keyed by worktree path as before. guardWorktree wraps
591
- // each: a worktree whose dir was genuinely removed mid-read (a worker self-merged + retired it) is OMITTED;
592
- // one that still exists but hit a transient detail failure is kept as a DEGRADED row from the last cached delta.
593
- const publicEntries = listSessionIds().map((id) => readPublicRecordEntry(id))
594
- .filter((entry) => entry.kind === 'corrupt' ? entry.governed !== false : entry.kind === 'ok' && entry.raw.governed)
595
- const records = publicEntries.flatMap((entry) => entry.kind === 'ok' ? [entry] : [])
596
- const projectedActive = options.activeSessionIds ? new Set(options.activeSessionIds) : null
597
- const isActive = (record: RawRecord): boolean => projectedActive
598
- ? projectedActive.has(record.session_id)
599
- : !record.archived
600
- // main's tip, resolved ONCE per board read — a component of every worktree's overlay cache key
601
- // ([[worktree-linker]]: landed content must dissolve the ops it made moot).
602
- const mainSha = await (async () => {
603
- try { return (await gitA(['-C', main, 'rev-parse', '--verify', `${mainRef}^{commit}`])).trim() } catch { return '' }
604
- })()
605
- const activePaths = records.filter(({ raw }) => isActive(raw)).map(({ raw }) => raw.worktree_path)
606
- const deltas = await layoutDeltas(activePaths, main, mainRef, mainSha)
607
- const rows = await Promise.all(records.map(({ raw: r, liveness }) => {
608
- const node = r.node ?? (r.branch && r.branch.startsWith(convention.branchPrefix) ? r.branch.slice(convention.branchPrefix.length) : null)
609
- const base: Worktree = { path: r.worktree_path, branch: r.branch, node, session: r.session_id, status: r.status, isMain: false, ...(liveness ? { liveness } : {}), ops: [] }
610
- // @@@ projected shelves cost nothing - a cold archived session ([[archive]]) keeps its record but leaves
611
- // the working-set projection and skips the per-worktree spec delta. An archived runtime hazard is still
612
- // projected active by listSessions, so it deliberately retains the same ops in both full and splice builds.
613
- if (!isActive(r)) return Promise.resolve(base)
614
- return guardWorktree<Worktree>(r.worktree_path,
615
- (): Worktree => {
616
- const outcome = deltas.get(r.worktree_path)
617
- if (!outcome) throw new Error(`layout overlay missing ${r.worktree_path}`)
618
- if ('error' in outcome) throw outcome.error
619
- return { ...base, ops: outcome.ops }
620
- },
621
- (): Worktree => {
622
- const cached = deltaCache.get(r.worktree_path)
623
- if (!cached) throw new Error(`layout overlay failed before ${r.worktree_path} had a last-known result`)
624
- return { ...base, ops: cached.ops }
625
- })
626
- }))
627
- const corruptRows: Worktree[] = publicEntries.flatMap((entry) => entry.kind === 'corrupt'
628
- ? [{ path: '', branch: null, node: null, session: entry.sessionId, status: 'corrupt', liveness: 'unknown', isMain: false, ops: [] }]
629
- : [])
630
- const sessionWorktrees = [...rows.filter((w): w is Worktree => w !== null), ...corruptRows]
631
- // the main checkout row (isMain) — always present, carries no overlay; it anchors the merged tree the board draws.
632
- const mainRow: Worktree = { path: main, branch: base, node: null, session: null, status: null, isMain: true, ops: [] }
633
- const worktrees = [mainRow, ...sessionWorktrees]
634
- // drop cache entries for worktrees that may no longer hold one — closed sessions (gone from the store) AND
635
- // newly-cold archived ones (which no longer compute a delta), so archiving SELF-EVICTS its cached ops instead of
636
- // stranding them in a map nothing prunes.
637
- const live = new Set(records.filter(({ raw }) => isActive(raw)).map(({ raw }) => raw.worktree_path))
638
- for (const k of [...deltaCache.keys()]) if (!live.has(k)) deltaCache.delete(k)
639
- return { main: convention.main || main || root, convention, worktrees }
640
- }