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
@@ -1,557 +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, headSha, worktreeSpecSig, worktreeSpecDelta, 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
- launch_readiness_pending?: '' | RawLaunchReadinessPending
280
- }
281
-
282
- export const SESSION_LIFECYCLES = ['active', 'idle', 'awaiting', 'parked', 'error', 'asking', 'queued'] as const
283
- export const SESSION_PROPOSALS = ['merge', 'nothing', 'close'] as const
284
- export type SessionLifecycle = typeof SESSION_LIFECYCLES[number]
285
- export type SessionProposal = typeof SESSION_PROPOSALS[number]
286
- const sessionLifecycles = new Set<string>(SESSION_LIFECYCLES)
287
- const sessionProposals = new Set<string>(SESSION_PROPOSALS)
288
- export const isSessionLifecycle = (value: unknown): value is SessionLifecycle =>
289
- typeof value === 'string' && sessionLifecycles.has(value)
290
- export const isSessionProposal = (value: unknown): value is SessionProposal =>
291
- typeof value === 'string' && sessionProposals.has(value)
292
-
293
- export type RawLaunchReadinessOriginal = {
294
- status: string
295
- proposal: string | null
296
- note: string | null
297
- stopped: boolean
298
- archived: boolean
299
- cold_proof: string | null
300
- adapter_recovery: string | null
301
- }
302
-
303
- export type RawLaunchReadinessPending = {
304
- version: 1
305
- startedAt: number
306
- original: RawLaunchReadinessOriginal
307
- }
308
-
309
- // A launch candidate is durable before it is public. Readers of the authored lifecycle use this one parser
310
- // so the board and the independent timeline observer cannot disagree about an in-flight resume. Invalid
311
- // pending bytes throw: a damaged publication fence is unknowable state, never permission to project online.
312
- export function rawLaunchReadinessOriginal(raw: RawRecord): RawLaunchReadinessOriginal | null {
313
- const pending = raw.launch_readiness_pending
314
- if (pending == null || pending === '') return null
315
- const original = pending && typeof pending === 'object' ? pending.original : null
316
- if (pending.version !== 1 || !Number.isFinite(pending.startedAt) || !original || typeof original !== 'object'
317
- || !isSessionLifecycle(original.status)
318
- || !(original.proposal === null || original.proposal === '' || isSessionProposal(original.proposal))
319
- || !(typeof original.note === 'string' || original.note === null)
320
- || typeof original.stopped !== 'boolean' || typeof original.archived !== 'boolean'
321
- || !(typeof original.cold_proof === 'string' || original.cold_proof === null)
322
- || !(typeof original.adapter_recovery === 'string' || original.adapter_recovery === null)) {
323
- throw new Error(`session '${raw.session_id}' has an invalid launch_readiness_pending fence`)
324
- }
325
- return original
326
- }
327
-
328
- // the agent's OWN session id from the environment — the only locator now that the record left the worktree.
329
- // Three tiers, in order:
330
- // (1) a harness's per-thread env var (`sessionEnvVar`) RESOLVED VIA THE ALIAS — when it lands on a governed
331
- // record (directly, or through that record's `harness_session_id`), that record's SpexCode id is the
332
- // answer. This MUST win: codex's design-C runs ONE shared per-project app-server whose env carries the
333
- // FIRST launched session's `SPEXCODE_SESSION_ID`, and the agent's shell tool (its `spex session
334
- // done/park/ask`) runs INSIDE that app-server process, so `SPEXCODE_SESSION_ID` is contaminated with the
335
- // wrong session. But codex injects the ACTING thread's id into every spawned command's env as
336
- // CODEX_THREAD_ID (== codex's `sessionEnvVar`), so the per-thread var aliases to the RIGHT record while
337
- // the shared `SPEXCODE_SESSION_ID` does not.
338
- // (2) else `SPEXCODE_SESSION_ID` (the GOVERNED record id the launcher bakes in) — the claude path and the
339
- // non-shared baseline.
340
- // (3) else a harness's env var RAW — a self-launched, non-governed agent's own minted id, which has no
341
- // governed record to alias to (codex CODEX_THREAD_ID / claude CLAUDE_CODE_SESSION_ID). The RAW form must
342
- // stay BELOW (2): an un-aliased codex thread id is not a record key, so it must never beat a real
343
- // `SPEXCODE_SESSION_ID`.
344
- // Claude is UNCHANGED: its `sessionEnvVar` (CLAUDE_CODE_SESSION_ID) already EQUALS its record id, so tier (1)
345
- // resolves to that very id — the same value `SPEXCODE_SESSION_ID` would have returned; there is no shared
346
- // app-server to contaminate it. No worktree fallback. (sessions.ts's `ownSessionId` delegates here; spec-eval
347
- // reads it to resolve the current node.)
348
- export function envSessionId(): string | null {
349
- for (const h of HARNESSES) {
350
- const v = process.env[h.sessionEnvVar]
351
- if (v && v.trim()) { const r = readAliasedRawRecord(v.trim()); if (r) return r.session_id }
352
- }
353
- const o = process.env.SPEXCODE_SESSION_ID
354
- if (o && o.trim()) return o.trim()
355
- for (const h of HARNESSES) { const v = process.env[h.sessionEnvVar]; if (v && v.trim()) return v.trim() }
356
- return null
357
- }
358
- // @@@ RecordEntry - a record read has THREE outcomes, and collapsing them is what let a live session read as
359
- // "no session record". ABSENT (no file) is the legitimate nothing — a self-launched agent that only ever wrote
360
- // spec-discipline sentinels has a store dir and no record. CORRUPT (present but unparseable, or parseable but
361
- // not a record) is a FACT about a session that exists, so it must reach the surfaces as itself instead of
362
- // masquerading as absence: sessions-core refuses every writer on it and the board gives it its own row. Any
363
- // OTHER read failure (permissions, I/O) still THROWS — a transient fault must not read as either.
364
- export type RecordEntry =
365
- | { kind: 'ok'; raw: RawRecord }
366
- | { kind: 'absent' }
367
- | { kind: 'corrupt'; path: string; error: string }
368
-
369
- export type PublicRecordEntry =
370
- | { kind: 'ok'; raw: RawRecord; liveness: 'offline' | null }
371
- | { kind: 'absent' }
372
- | { kind: 'corrupt'; sessionId: string; governed: boolean | null; path: string; error: string; liveness: 'unknown' }
373
-
374
- export function readRecordEntry(id: string): RecordEntry {
375
- const path = sessionRecordPath(id)
376
- let text: string
377
- try { text = readFileSync(path, 'utf8') }
378
- catch (e) { if ((e as NodeJS.ErrnoException).code === 'ENOENT') return { kind: 'absent' }; throw e }
379
- let raw: unknown
380
- try { raw = JSON.parse(text) }
381
- catch (e) { return { kind: 'corrupt', path, error: e instanceof Error ? e.message : String(e) } }
382
- if (!raw || typeof raw !== 'object' || !(raw as RawRecord).session_id)
383
- return { kind: 'corrupt', path, error: 'parsed, but carries no session_id — not a session record' }
384
- return { kind: 'ok', raw: raw as RawRecord }
385
- }
386
-
387
- // The ONE public session-record parser. Internal mutation/readiness code uses readRecordEntry's exact raw
388
- // candidate; every public projection passes through here. A valid pending fence replaces all lifecycle-facing
389
- // fields with its frozen original and forces offline liveness. Malformed pending bytes remain a present,
390
- // corrupt/unknown row instead of leaking candidate state or disappearing as absence.
391
- export function projectPublicRecordEntry(id: string, entry: RecordEntry): PublicRecordEntry {
392
- if (entry.kind === 'absent') return entry
393
- if (entry.kind === 'corrupt') return {
394
- kind: 'corrupt', sessionId: id, governed: null, path: entry.path, error: entry.error, liveness: 'unknown',
395
- }
396
- try {
397
- const original = rawLaunchReadinessOriginal(entry.raw)
398
- if (!original) return { kind: 'ok', raw: entry.raw, liveness: null }
399
- return {
400
- kind: 'ok',
401
- raw: {
402
- ...entry.raw,
403
- status: original.status,
404
- proposal: original.proposal || null,
405
- note: original.note || null,
406
- stopped: original.stopped,
407
- archived: original.archived,
408
- cold_proof: original.cold_proof ?? undefined,
409
- adapter_recovery: original.adapter_recovery ?? undefined,
410
- launch_readiness_pending: '',
411
- },
412
- liveness: 'offline',
413
- }
414
- } catch (error) {
415
- return {
416
- kind: 'corrupt',
417
- sessionId: id,
418
- governed: typeof entry.raw.governed === 'boolean' ? entry.raw.governed : null,
419
- path: sessionRecordPath(id),
420
- error: error instanceof Error ? error.message : String(error),
421
- liveness: 'unknown',
422
- }
423
- }
424
- }
425
-
426
- export function readPublicRecordEntry(id: string): PublicRecordEntry {
427
- return projectPublicRecordEntry(id, readRecordEntry(id))
428
- }
429
- export function readRawRecord(id: string): RawRecord | null {
430
- try { const e = readRecordEntry(id); return e.kind === 'ok' ? e.raw : null }
431
- catch { return null }
432
- }
433
- // resolve a possibly-ALIASED session id to its raw record. A codex hook or spawned command can carry the codex
434
- // THREAD id — payload session_id / CODEX_THREAD_ID — not the SpexCode record id the store is keyed by. Direct id
435
- // wins; else the one record that captured this id as `harness_session_id` (the backend stored it at thread/start,
436
- // before any tool turn).
437
- // Null when neither resolves. Mirrors the shell `hp_store_dir` alias grep — one resolution rule, both layers.
438
- export function readAliasedRawRecord(id: string): RawRecord | null {
439
- const e = readAliasedRecordEntry(id)
440
- return e.kind === 'ok' ? e.raw : null
441
- }
442
- // the same alias resolution, keeping the three-way outcome. A CORRUPT record at the direct id settles the
443
- // question — we found this session and cannot read it; walking on to the alias would report a corrupt record
444
- // as absent, the exact collapse this type exists to prevent.
445
- export function readAliasedRecordEntry(id: string): RecordEntry {
446
- const direct = readRecordEntry(id)
447
- if (direct.kind !== 'absent') return direct
448
- for (const sid of listSessionIds()) {
449
- const r = readRawRecord(sid)
450
- if (r && r.harness_session_id && r.harness_session_id === id) return { kind: 'ok', raw: r }
451
- }
452
- return { kind: 'absent' }
453
- }
454
- // every session_id this project has a record for (the board's enumeration source — replaces `git worktree
455
- // list`). A MISSING store dir means no session ever launched → []. But any OTHER readdir failure THROWS
456
- // (preserving the fail-loud-enumeration invariant `git worktree list` had): a transient FS error must never
457
- // read as "every session vanished" — the watch poll skips the tick on a throw, never emitting a false mass-close.
458
- export function listSessionIds(): string[] {
459
- let ents
460
- try { ents = readdirSync(sessionsRoot(), { withFileTypes: true }) }
461
- catch (e) { if ((e as NodeJS.ErrnoException).code === 'ENOENT') return []; throw e }
462
- return ents.filter((d) => d.isDirectory()).map((d) => d.name)
463
- }
464
-
465
- // @@@ branch -> session, so a moved ref can DERIVE its invalidation scope - a session's evaluation depends
466
- // on its own branch tip, the base branch tip and their merge-base; no other ref participates. Without this
467
- // map a ref watcher knows only that "something under refs/ moved" and the honest fallback is to invalidate
468
- // every session ([[taste]] 19). One record read per session, and only when the session store itself moved.
469
- export function sessionBranchIndex(): Map<string, string> {
470
- const index = new Map<string, string>()
471
- for (const id of listSessionIds()) {
472
- let branch: unknown
473
- try { branch = readRawRecord(id)?.branch } catch { continue } // a corrupt record narrows nothing
474
- if (typeof branch === 'string' && branch) index.set(branch, id)
475
- }
476
- return index
477
- }
478
-
479
- // memo the overlay (4 git diffs/worktree, all .spec-scoped) keyed on fork-point merge-base + HEAD + spec
480
- // sig + MAIN'S TIP ([[worktree-linker]]): the main-tip component is what lets a merge landing identical
481
- // content dissolve a worktree's now-moot ops — the recompute it triggers is cheap because every diff is
482
- // .spec-scoped.
483
- const deltaCache = new Map<string, { key: string; ops: NodeOp[] }>()
484
- const safeHead = (p: string): string => { try { return headSha(p) } catch { return '' } }
485
- const safeMergeBase = async (wtPath: string, mainRef: string): Promise<string> => {
486
- try { return (await gitA(['-C', wtPath, 'merge-base', mainRef, 'HEAD'])).trim() } catch { return '' }
487
- }
488
- let layoutHeadWarned = false
489
- async function cachedDelta(wtPath: string, mainRef: string, mainSha: string): Promise<NodeOp[]> {
490
- const wtHead = safeHead(wtPath)
491
- const base = await safeMergeBase(wtPath, mainRef)
492
- // fail loud, never stale: if the merge-base, HEAD, or main tip can't be read the key is untrustworthy —
493
- // bypass the cache and recompute (warn once) rather than risk serving a delta keyed on an empty sha
494
- // across a real change.
495
- if (!base || !wtHead || !mainSha) {
496
- if (!layoutHeadWarned) { layoutHeadWarned = true; console.warn('spec-cli: layout overlay cache bypassed (unreadable merge-base/HEAD/main tip), recomputing every read') }
497
- return worktreeSpecDelta(wtPath, mainRef)
498
- }
499
- const key = `${base}\0${wtHead}\0${mainSha}\0${worktreeSpecSig(wtPath)}`
500
- const hit = deltaCache.get(wtPath)
501
- if (hit && hit.key === key) return hit.ops
502
- const ops = await worktreeSpecDelta(wtPath, mainRef, base)
503
- deltaCache.set(wtPath, { key, ops })
504
- return ops
505
- }
506
-
507
- export async function resolveLayout(): Promise<Layout> {
508
- const root = repoRoot()
509
- const main = dirname(gitCommonDir()) // the main checkout — same answer from main OR any linked worktree
510
- const cfg = readConfig(main)
511
- const base = mainBranch()
512
- const convention: Convention = {
513
- main: cfg.main || '',
514
- mainBranch: base,
515
- branchPrefix: cfg.branchPrefix ?? 'node/',
516
- }
517
- const mainRef = base
518
- // the board enumerates the GLOBAL per-session store (NOT `git worktree list`): every GOVERNED record this
519
- // project owns, each carrying the worktree_path its spec-delta is computed from. Non-governed (user-self-
520
- // launched) records are excluded — board state is a managed-session concern ([[state]]). Each delta is
521
- // independent → compute (or cache-hit) in parallel, keyed by worktree path as before. guardWorktree wraps
522
- // each: a worktree whose dir was genuinely removed mid-read (a worker self-merged + retired it) is OMITTED;
523
- // one that still exists but hit a transient detail failure is kept as a DEGRADED row from the last cached delta.
524
- const publicEntries = listSessionIds().map((id) => readPublicRecordEntry(id))
525
- .filter((entry) => entry.kind === 'corrupt' ? entry.governed !== false : entry.kind === 'ok' && entry.raw.governed)
526
- const records = publicEntries.flatMap((entry) => entry.kind === 'ok' ? [entry] : [])
527
- // main's tip, resolved ONCE per board read — a component of every worktree's overlay cache key
528
- // ([[worktree-linker]]: landed content must dissolve the ops it made moot).
529
- const mainSha = await (async () => {
530
- try { return (await gitA(['-C', main, 'rev-parse', '--verify', `${mainRef}^{commit}`])).trim() } catch { return '' }
531
- })()
532
- const rows = await Promise.all(records.map(({ raw: r, liveness }) => {
533
- const node = r.node ?? (r.branch && r.branch.startsWith(convention.branchPrefix) ? r.branch.slice(convention.branchPrefix.length) : null)
534
- const base: Worktree = { path: r.worktree_path, branch: r.branch, node, session: r.session_id, status: r.status, isMain: false, ...(liveness ? { liveness } : {}), ops: [] }
535
- // @@@ archived rows cost nothing - a shelved session ([[archive]]) keeps its row (the record is the
536
- // existence truth) but skips the per-worktree spec-delta entirely: that git-history probe is the board's
537
- // dominant per-row cost, and shelving is exactly the human saying "stop spending attention here". So the
538
- // price of a retained archive is one enumerated record, NOT a git walk per poll.
539
- if (r.archived) return Promise.resolve(base)
540
- return guardWorktree<Worktree>(r.worktree_path,
541
- async (): Promise<Worktree> => ({ ...base, ops: await cachedDelta(r.worktree_path, mainRef, mainSha) }),
542
- (): Worktree => ({ ...base, ops: deltaCache.get(r.worktree_path)?.ops ?? [] }))
543
- }))
544
- const corruptRows: Worktree[] = publicEntries.flatMap((entry) => entry.kind === 'corrupt'
545
- ? [{ path: '', branch: null, node: null, session: entry.sessionId, status: 'corrupt', liveness: 'unknown', isMain: false, ops: [] }]
546
- : [])
547
- const sessionWorktrees = [...rows.filter((w): w is Worktree => w !== null), ...corruptRows]
548
- // the main checkout row (isMain) — always present, carries no overlay; it anchors the merged tree the board draws.
549
- const mainRow: Worktree = { path: main, branch: base, node: null, session: null, status: null, isMain: true, ops: [] }
550
- const worktrees = [mainRow, ...sessionWorktrees]
551
- // drop cache entries for worktrees that may no longer hold one — closed sessions (gone from the store) AND
552
- // newly-archived ones (which no longer compute a delta), so archiving SELF-EVICTS its cached ops instead of
553
- // stranding them in a map nothing prunes.
554
- const live = new Set(records.filter(({ raw }) => !raw.archived).map(({ raw }) => raw.worktree_path))
555
- for (const k of [...deltaCache.keys()]) if (!live.has(k)) deltaCache.delete(k)
556
- return { main: convention.main || main || root, convention, worktrees }
557
- }