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,245 +0,0 @@
1
- // SO_REUSEPORT is ENOTSUP on macOS/Node, so the supervisor owns the public port as a raw-TCP proxy and
2
- // runs the Hono server as a child on a private port (raw piping carries WS upgrades too).
3
- import net from 'node:net'
4
- import http from 'node:http'
5
- import { randomUUID } from 'node:crypto'
6
- import { spawn, type ChildProcess } from 'node:child_process'
7
- import type { Dirent } from 'node:fs'
8
- import { stat, readdir } from 'node:fs/promises'
9
- import { fileURLToPath } from 'node:url'
10
- import { dirname, join } from 'node:path'
11
- import { installProcessGuards } from './resilience.js'
12
- import { listenOrExit } from './listen.js'
13
- import { resolvePublicConfig, startGateway, ensureDashboardBuilt, resolveDistDir } from './gateway.js'
14
- import { tsxBin } from './tsx-bin.js'
15
- import { publishEndpoint, dropOwnEndpoint } from './host.js'
16
- import { repoRoot as servedRepoRoot } from './git.js'
17
- import { resolveProjectIdentity } from './project-identity.js'
18
- import { startResourceMonitor } from './host-resources.js'
19
- import { registerBackendInstance, unregisterBackendInstance } from './runtime-ownership.js'
20
- import { sessionIdentityEnvVars } from './harness.js'
21
-
22
- // the supervisor OWNS the public port, so it must outlive any transient throw: an uncaught error here is
23
- // logged and survived, never an exit that closes the port (and the tmux session) and takes the frontend down.
24
- installProcessGuards()
25
-
26
- const here = dirname(fileURLToPath(import.meta.url))
27
- const tsx = tsxBin(join(here, '..')) // tsx's JS entry (dist/cli.mjs), run via `node` below — dev or published
28
- const entry = join(here, 'index.ts') // the real Hono server
29
- const publicPort = Number(process.env.PORT || 8787)
30
-
31
- // @@@ public mode ([[public-mode]]) - with `spex serve --public`, the supervisor is NOT the internet face:
32
- // the gateway is. The raw-TCP proxy retreats to a loopback internal port (the trusted boundary local agents
33
- // reach) and the password-gated TLS gateway takes the public port, proxying /api + WS back to that loopback
34
- // port. Off → unchanged: the proxy itself owns the public port and SPEXCODE_API_URL points there.
35
- const publicCfg = resolvePublicConfig(join(here, '..', '..'))
36
- // the port the raw-TCP proxy actually binds: a private loopback port in public mode, the public port otherwise.
37
- const proxyPort = publicCfg ? await freePort() : publicPort
38
- // what launched agents inherit as their `spex` endpoint — ALWAYS the loopback proxy, so local agents never
39
- // meet the password gate (loopback is the trust boundary). Equals the public port when public mode is off.
40
- const childApiBase = `http://127.0.0.1:${proxyPort}`
41
-
42
- // every package src tree the child imports at runtime; spec-dashboard is absent (its own vite/HMR).
43
- const repoRoot = join(here, '..', '..')
44
- const watchRoots = [
45
- here, // spec-cli/src — the backend's own source
46
- join(repoRoot, 'spec-forge', 'src'),
47
- join(repoRoot, 'spec-eval', 'src'),
48
- ]
49
-
50
- // @@@ instance identity - one id for this serve's whole lifetime, minted at supervisor start and handed to
51
- // every child via env, so the endpoint record and the live backend answer with the SAME identity across
52
- // zero-downtime reloads. The host gateway ([[host-gateway]]) validates a record by comparing this id (and the
53
- // served root) against the live /api/instance answer — a recycled port serving a DIFFERENT project or a
54
- // different serve generation fails the match and is treated as offline, never proxied to.
55
- const instanceId = randomUUID()
56
- const projectRoot = servedRepoRoot() // the actual git tree whose source/spec/config the child serves
57
- // A backend is a project control plane, never the session that happened to invoke `spex serve`. Strip every
58
- // adapter identity before any hot child is spawned; otherwise all backend work is falsely charged to that
59
- // caller long after its session ends.
60
- for (const key of sessionIdentityEnvVars()) delete process.env[key]
61
- process.env.SPEXCODE_PROJECT_ROOT = projectRoot
62
- process.env.SPEXCODE_INSTANCE_ID = instanceId
63
- registerBackendInstance(instanceId, process.pid, projectRoot)
64
-
65
- type Backend = { port: number; child: ChildProcess }
66
- let current: Backend | null = null // which internal port new proxy connections forward to
67
- let reloading = false // single-flight guard for reload()
68
- let pending = false // a code change arrived mid-reload → reload again when done
69
-
70
- // grab an ephemeral port by binding :0, then release it for the child to claim (negligible rebind race).
71
- function freePort(): Promise<number> {
72
- return new Promise((res, rej) => {
73
- const s = net.createServer()
74
- s.once('error', rej)
75
- s.listen(0, '127.0.0.1', () => { const p = (s.address() as net.AddressInfo).port; s.close(() => res(p)) })
76
- })
77
- }
78
-
79
- // poll GET /health until 200; ~15s budget (150 × 100ms) covers a slow tsx+Hono cold start. False → keep old.
80
- function waitHealthy(port: number, tries = 150): Promise<boolean> {
81
- return new Promise((resolve) => {
82
- const retry = (left: number) => { if (left <= 1) resolve(false); else setTimeout(() => attempt(left - 1), 100) }
83
- const attempt = (left: number) => {
84
- const req = http.get({ host: '127.0.0.1', port, path: '/health', timeout: 1000 }, (r) => {
85
- r.resume()
86
- if (r.statusCode === 200) resolve(true); else retry(left)
87
- })
88
- req.on('error', () => retry(left))
89
- req.on('timeout', () => { req.destroy(); retry(left) })
90
- }
91
- attempt(tries)
92
- })
93
- }
94
-
95
- // boot a fresh backend child on a free internal port and wait until it's serving.
96
- async function boot(): Promise<Backend | null> {
97
- const port = await freePort()
98
- // PORT pins the child's PRIVATE bind port; SPEXCODE_API_URL pins everything the child SPAWNS (launched
99
- // sessions + their hooks) at the PUBLIC port, so a launched agent's own `spex` reaches the stable proxy
100
- // and never inherits this ephemeral, soon-retired port. ALWAYS childApiBase, never the ambient
101
- // process.env.SPEXCODE_API_URL: the env this serve itself inherited may carry ANOTHER project's backend
102
- // (the exact misroute [[remote-client]]'s ladder exists to kill), and a worker's env is its routing
103
- // LIFELINE — it must be a deterministic backend-injected fact, not an inheritance gamble.
104
- const child = spawn(process.execPath, [tsx, entry], { stdio: 'inherit', env: { ...process.env, PORT: String(port), SPEXCODE_API_URL: childApiBase, SPEXCODE_INSTANCE_ID: instanceId } })
105
- // if the ACTIVE backend dies unexpectedly (crash, OOM), restart it so the public port keeps serving.
106
- // Planned retirement sets current to the NEW child first, so the old child's exit fails this identity
107
- // check and is ignored. boot()'s ~5s health budget rate-limits any crash loop.
108
- child.on('exit', (code, sig) => {
109
- if (current?.child === child) { console.error(`[supervisor] active backend exited (${code ?? sig}) — restarting`); current = null; void reload('crash') }
110
- })
111
- if (await waitHealthy(port)) return { port, child }
112
- try { child.kill('SIGKILL') } catch { /* already gone */ }
113
- return null
114
- }
115
-
116
- // boot → health-gate → atomic flip → drain old. The flip is a single assignment; the old child is killed
117
- // only after a drain delay, so a connection mid-flip is never refused.
118
- async function reload(reason: string): Promise<void> {
119
- if (reloading) { pending = true; return }
120
- reloading = true
121
- try {
122
- do {
123
- pending = false
124
- const next = await boot()
125
- if (!next) { console.error(`[supervisor] new backend failed health check (${reason}) — keeping current`); break }
126
- const old = current
127
- current = next // atomic flip: new connections now route to `next`
128
- console.log(`[supervisor] reloaded (${reason}) → backend :${next.port}`)
129
- if (old) setTimeout(() => { try { old.child.kill('SIGTERM') } catch { /* already gone */ } }, 500)
130
- } while (pending)
131
- } finally { reloading = false }
132
- }
133
-
134
- // the public port: a raw byte pipe to the current backend. Works for HTTP and WS upgrades alike.
135
- // @@@ connection reaping ([[spec-cli]]) - a raw TCP proxy must tear down the PAIR when EITHER end goes, or it
136
- // leaks the still-open half. The wedge that started the mass-restore incident was exactly this: every
137
- // client-side timeout-kill left the client's UPSTREAM socket half-open to the child (the old handler bailed
138
- // only on `error`, so a clean FIN / a silent drop never reaped the upstream) — 135 leaked conns piled on the
139
- // child and it looked dead. So a close on either side destroys BOTH (idempotent). The abandoned-but-silent
140
- // case (no FIN/RST ever arrives) is reaped from the CHILD instead — its socket-level reaper (reaper.ts)
141
- // destroys an idle/stalled socket, whose close then propagates here — so an active WS/SSE (not idle
142
- // keep-alive) is never mistaken for abandoned and cut.
143
- const proxy = net.createServer((client) => {
144
- const target = current
145
- if (!target) { client.destroy(); return }
146
- const up = net.connect(target.port, '127.0.0.1')
147
- client.setNoDelay(true); up.setNoDelay(true) // proxy a request promptly — don't let Nagle add latency
148
- const bail = () => { client.destroy(); up.destroy() }
149
- client.on('error', bail); up.on('error', bail)
150
- client.once('close', () => up.destroy()) // client abandoned → reap its upstream (THE leak that wedged :8787); nothing left to flush to a gone client
151
- // upstream gone → drop the client half, but ONLY force it when the close was ABNORMAL: on a normal FIN,
152
- // `up.pipe(client)` has already called `client.end()` (writableEnded), so let the client flush the last of a
153
- // large response (e.g. /api/graph) rather than truncate it with a destroy; a crash/half-open close (no prior
154
- // end) still gets reaped.
155
- up.once('close', () => { if (!client.writableEnded) client.destroy() })
156
- client.pipe(up); up.pipe(client)
157
- })
158
-
159
- // @@@ endpoint record - this project's live backend endpoint, published into the per-project runtime tier
160
- // (~/.spexcode/projects/<enc>/backend.json) only AFTER the public bind succeeds. It's what lets a bare
161
- // `spex` run from this project's tree find ITS OWN backend instead of an env URL inherited from another
162
- // project's ([[remote-client]]'s resolution ladder), and what the host gateway ([[host-gateway]]) reconciles
163
- // its project list from. The write is ATOMIC (tmp + rename) and carries the serve's actual git toplevel +
164
- // resolved identity — so a reader can VALIDATE the record against the live /api/instance answer,
165
- // never just trust a URL. Readers health-probe before trusting, so a crashed serve leaves at worst a dead
166
- // record that is ignored — never followed. The recorded URL is the LOOPBACK face local agents reach (equals
167
- // the public port when public mode is off), never the password-gated gateway. A linked worktree therefore
168
- // gets its own encoded slot and can never overwrite the main checkout's endpoint.
169
- function recordEndpoint(url: string): void {
170
- try {
171
- publishEndpoint({
172
- version: 2,
173
- url,
174
- pid: process.pid,
175
- instanceId,
176
- root: projectRoot,
177
- identity: resolveProjectIdentity(projectRoot, projectRoot),
178
- startedAt: new Date().toISOString(),
179
- })
180
- } catch (e) {
181
- console.error(`[supervisor] could not record the backend endpoint (${(e as Error).message}) — cwd-based \`spex\` discovery won't find this backend`)
182
- }
183
- }
184
- // best-effort removal on a clean stop, only if the record is OURS — matched by instanceId, so a newer serve
185
- // that already overwrote the record (or another project's) is never deleted by a retiring one.
186
- function dropEndpoint(): void {
187
- try { dropOwnEndpoint(instanceId, projectRoot) } catch { /* not ours / already gone */ }
188
- }
189
-
190
- const shutdown = () => { dropEndpoint(); unregisterBackendInstance(instanceId); try { current?.child.kill('SIGTERM') } catch { /* */ } process.exit(0) }
191
- process.on('SIGINT', shutdown)
192
- process.on('SIGTERM', shutdown)
193
-
194
- const first = await boot()
195
- if (!first) { console.error('[supervisor] initial backend failed to start'); process.exit(1) }
196
- current = first
197
- // reap the booted child if a port bind fails, so a "can't own my port → exit" never leaves a zombie child.
198
- // SIGTERM (not SIGKILL): the child is a `tsx` wrapper around the real node server — SIGKILL kills the wrapper
199
- // instantly and ORPHANS the server still bound to its port, whereas tsx FORWARDS SIGTERM to it (the same
200
- // signal the reload drain uses). Sent synchronously here, before the process.exit that follows.
201
- const reapChild = () => { unregisterBackendInstance(instanceId); try { current?.child.kill('SIGTERM') } catch { /* already gone */ } }
202
- if (publicCfg) {
203
- // public mode: the raw proxy stays on loopback; the password-gated gateway owns the public port.
204
- const distDir = resolveDistDir() // bundled <pkg>/dashboard-dist when installed, else monorepo spec-dashboard/dist
205
- ensureDashboardBuilt(repoRoot, distDir)
206
- listenOrExit(proxy, proxyPort, { host: '127.0.0.1', label: 'supervisor (loopback proxy)', cleanup: reapChild, onListen: () => { recordEndpoint(childApiBase); console.log(`spec-cli supervisor on loopback :${proxyPort} (zero-downtime reloads, backend :${first.port})`) } })
207
- startGateway({ publicPort, upstreamPort: proxyPort, password: publicCfg.password, tls: publicCfg.tls, distDir, onBindFail: reapChild })
208
- } else {
209
- listenOrExit(proxy, publicPort, { label: 'supervisor', cleanup: reapChild, onListen: () => { recordEndpoint(childApiBase); console.log(`spec-cli supervisor serving on http://localhost:${publicPort} (zero-downtime reloads, backend :${first.port})`) } })
210
- }
211
- startResourceMonitor()
212
-
213
- // watch every imported source tree; debounce a burst of writes (a merge touching several files across
214
- // packages) into one reload. A root that can't be watched (a package absent in some checkout) is logged
215
- // and skipped — the supervisor owns the public port and must never die over a missing watch.
216
- // fs.watch — even recursive — is NOT reliable for a long-lived supervisor: its inotify watches are silently
217
- // dropped when a watched dir is rewritten (a git merge, a `materialize`) and NEVER re-established, so reloads
218
- // quietly stop (observed: the watcher fired a few times then went deaf for hours). A cheap mtime poll can't go
219
- // deaf: every 2s, take the newest .ts/.js/.json mtime across the trees; a jump triggers the debounced reload
220
- // (a burst of writes still lands as one). Polling a few small src trees is negligible cost.
221
- let timer: NodeJS.Timeout | undefined
222
- const newestMtime = async (dir: string): Promise<number> => {
223
- let max = 0
224
- let entries: Dirent[]
225
- try { entries = await readdir(dir, { withFileTypes: true }) } catch { return 0 } // a tree absent in some checkout → skip
226
- for (const e of entries) {
227
- if (e.isDirectory()) { if (e.name !== 'node_modules') max = Math.max(max, await newestMtime(join(dir, e.name))) }
228
- else if (/\.(ts|js|mjs|json)$/.test(e.name)) { try { max = Math.max(max, (await stat(join(dir, e.name))).mtimeMs) } catch { /* raced unlink */ } }
229
- }
230
- return max
231
- }
232
- const scanMtime = async (): Promise<number> => {
233
- let m = 0
234
- for (const root of watchRoots) m = Math.max(m, await newestMtime(root))
235
- return m
236
- }
237
- let lastMtime = await scanMtime() // baseline at boot — only a LATER change reloads
238
- let scanning = false
239
- setInterval(() => {
240
- if (scanning) return
241
- scanning = true
242
- void scanMtime().then((m) => {
243
- if (m > lastMtime) { lastMtime = m; clearTimeout(timer); timer = setTimeout(() => void reload('code change'), 150) }
244
- }).finally(() => { scanning = false })
245
- }, 2000).unref()
@@ -1,118 +0,0 @@
1
- // @@@ spex graph - the CLI's human-readable graph view: the same assembled board the dashboard's
2
- // tidy-tree renders, as an indented terminal tree. Pure presentation over buildBoard()'s nodes —
3
- // no read path of its own; status colours and badge semantics mirror the dashboard (drift /
4
- // stale-eval / open-issues counts). Governed by the spex-tree spec node.
5
-
6
- // the subset of a board node this view consumes (buildBoard attaches more; we read only these).
7
- export type TreeNode = {
8
- id: string
9
- parent: string | null
10
- title: string
11
- status: string
12
- version?: number
13
- drift?: number
14
- ghost?: boolean
15
- reviewSummary?: {
16
- issues?: { open: number }
17
- evals?: { stalePass: number; staleFail: number }
18
- }
19
- }
20
-
21
- export type TreeOpts = { node?: string; depth?: number; color?: boolean }
22
-
23
- // dashboard status palette mapped onto ANSI: merged=green, active=cyan (live/in-flight, distinct
24
- // from the warning yellow), drift=yellow (the dashboard's warning colour), pending=muted grey.
25
- const STATUS_ANSI: Record<string, string> = { merged: '32', active: '36', drift: '33', pending: '90' }
26
-
27
- // stale-eval count: declared scenarios whose LATEST reading exists but is no longer fresh.
28
- // board `evals` is already latest-per-scenario, so this is a straight filter — the same freshness
29
- // axis the dashboard's grey ✓/✗ badges read (score.jsx readingScore).
30
- function staleYatsu(n: TreeNode): number {
31
- const summary = n.reviewSummary?.evals
32
- return summary ? summary.stalePass + summary.staleFail : 0
33
- }
34
-
35
- function childrenIndex(nodes: TreeNode[]): Map<string | null, TreeNode[]> {
36
- const byParent = new Map<string | null, TreeNode[]>()
37
- for (const n of nodes) {
38
- const list = byParent.get(n.parent) ?? []
39
- list.push(n)
40
- byParent.set(n.parent, list)
41
- }
42
- return byParent
43
- }
44
-
45
- // resolve the display roots: --node picks one subtree; default is the forest of parentless nodes
46
- // (a node whose parent id isn't on the board — a ghost mid-add, say — surfaces as a root rather
47
- // than vanishing). Unknown --node throws (fail loud, caller prints + exits 2), never an empty tree.
48
- function roots(nodes: TreeNode[], nodeId?: string): TreeNode[] {
49
- if (nodeId) {
50
- const hit = nodes.find((n) => n.id === nodeId)
51
- if (!hit) throw new Error(`no spec node "${nodeId}" — spex graph lists every id; spex spec search <topic> finds one by intent`)
52
- return [hit]
53
- }
54
- const ids = new Set(nodes.map((n) => n.id))
55
- return nodes.filter((n) => n.parent === null || !ids.has(n.parent))
56
- }
57
-
58
- export function renderTree(nodes: TreeNode[], opts: TreeOpts = {}): string {
59
- const color = opts.color ?? false
60
- const c = (code: string, t: string) => (color ? `\x1b[${code}m${t}\x1b[0m` : t)
61
- const byParent = childrenIndex(nodes)
62
- const lines: string[] = []
63
-
64
- const badges = (n: TreeNode): string => {
65
- const parts: string[] = []
66
- if (n.ghost) parts.push(c('90', 'ghost'))
67
- if (n.drift) parts.push(c('33', `drift:${n.drift}`))
68
- const stale = staleYatsu(n)
69
- if (stale) parts.push(c('90', `stale:${stale}`))
70
- if (n.reviewSummary?.issues?.open) parts.push(c('31', `issues:${n.reviewSummary.issues.open}`))
71
- return parts.length ? ' ' + parts.join(' ') : ''
72
- }
73
-
74
- const line = (n: TreeNode, prefix: string, branch: string) => {
75
- const code = STATUS_ANSI[n.status] ?? '0'
76
- // base state is invisible: merged (~80% of nodes) is the healthy ground state, already carried by
77
- // the dot's colour — only deviations get a text label. Without colour the word IS the signal, so
78
- // every status (merged included) prints in plain-text mode.
79
- const label = color && n.status === 'merged' ? '' : ' ' + c(code, `[${n.status}]`)
80
- const title = n.title && n.title !== n.id ? ' ' + c('90', '· ' + n.title) : ''
81
- lines.push(`${prefix}${branch}${c(code, '●')} ${n.id}${label}${title}${badges(n)}`)
82
- }
83
-
84
- const walk = (n: TreeNode, prefix: string, branch: string, childPrefix: string, depth: number) => {
85
- line(n, prefix, branch)
86
- const kids = byParent.get(n.id) ?? []
87
- if (!kids.length) return
88
- if (opts.depth !== undefined && depth >= opts.depth) {
89
- lines.push(`${childPrefix}${c('90', `└─ … ${kids.length} more (raise --depth)`)}`)
90
- return
91
- }
92
- kids.forEach((k, i) => {
93
- const last = i === kids.length - 1
94
- walk(k, childPrefix, last ? '└─ ' : '├─ ', childPrefix + (last ? ' ' : '│ '), depth + 1)
95
- })
96
- }
97
-
98
- for (const r of roots(nodes, opts.node)) walk(r, '', '', '', 0)
99
- return lines.join('\n')
100
- }
101
-
102
- // the machine exit: the same filtered subtree as NESTED objects, badge counts precomputed — a
103
- // shaped view, not a replacement for `spex graph --json` (which stays the full flat payload). Pruned
104
- // children degrade to their ids, so --depth still tells the machine what exists below the cut.
105
- export function treeJson(nodes: TreeNode[], opts: TreeOpts = {}): object[] {
106
- const byParent = childrenIndex(nodes)
107
- const shape = (n: TreeNode, depth: number): object => {
108
- const kids = byParent.get(n.id) ?? []
109
- const pruned = opts.depth !== undefined && depth >= opts.depth
110
- return {
111
- id: n.id, title: n.title, status: n.status, version: n.version ?? 0,
112
- drift: n.drift ?? 0, staleYatsu: staleYatsu(n), openIssues: n.reviewSummary?.issues?.open ?? 0,
113
- ...(n.ghost ? { ghost: true } : {}),
114
- children: pruned ? kids.map((k) => k.id) : kids.map((k) => shape(k, depth + 1)),
115
- }
116
- }
117
- return roots(nodes, opts.node).map((r) => shape(r, 0))
118
- }
@@ -1,18 +0,0 @@
1
- import { createRequire } from 'node:module'
2
- import { dirname, join } from 'node:path'
3
-
4
- // @@@ tsxBin - tsx's JS ENTRY (dist/cli.mjs), dev-or-published, run through `node` by the caller
5
- // (`spawn(process.execPath, [tsxBin(pkgDir), entry, …])`). In the dev monorepo tsx sits in
6
- // spec-cli/node_modules; in an installed `spexcode` package npm may hoist it to the consumer's node_modules
7
- // — Node's own package resolver from spec-cli covers dev, global, and project-local installs in one rule.
8
- // We resolve the .mjs entry rather than the `.bin/tsx` shim on purpose: the shim is an unspawnable sh
9
- // script on Windows, so `node dist/cli.mjs` (identical to the shim on POSIX) is the one cross-platform form.
10
- // `pkgDir` is the spec-cli directory.
11
- export function tsxBin(pkgDir: string): string {
12
- try {
13
- const req = createRequire(join(pkgDir, 'package.json'))
14
- return join(dirname(req.resolve('tsx/package.json')), 'dist', 'cli.mjs')
15
- } catch {
16
- throw new Error(`tsx runtime not found from ${pkgDir} — run \`npm install\` in the SpexCode package`)
17
- }
18
- }
@@ -1,171 +0,0 @@
1
- import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs'
2
- import { basename, join, resolve, relative } from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
- import { execFileSync } from 'node:child_process'
5
- import { HARNESSES, type HarnessArtifacts } from './harness.js'
6
- import { runtimeRoot, readConfig, mainCheckout } from './layout.js'
7
- import { resolveHarnessTargets } from './harness-select.js'
8
- import { loadSkillConfig, loadAgentConfig } from './specs.js'
9
- import { dematerialize } from './materialize.js'
10
- import { gitBinary } from './git.js'
11
-
12
- // the standard plugin-host folders a host agent scans (in addition to any named in spexcode.json's `harnesses`).
13
- const DEFAULT_PLUGIN_HOSTS = ['.claude', '.codex', '.zcode'] as const
14
-
15
- // Init and uninstall share one ownership source for generated git hooks: the shipped canonical templates.
16
- // Exact bytes prove the destination is still our derivative; any user edit withdraws that ownership.
17
- const HOOK_TEMPLATES = fileURLToPath(new URL('../templates/hooks', import.meta.url))
18
-
19
- // is this dir a SpexCode plugin bundle? Either its folder name is the identity stamp, or its
20
- // `.claude-plugin/plugin.json` declares `name: spexcode`. Read-gated so a user's other plugin is never touched.
21
- function isSpexcodeBundle(dir: string): boolean {
22
- if (basename(dir) === 'spexcode') return true
23
- const manifest = join(dir, '.claude-plugin', 'plugin.json')
24
- if (!existsSync(manifest)) return false
25
- try {
26
- return (JSON.parse(readFileSync(manifest, 'utf8')) as { name?: unknown }).name === 'spexcode'
27
- } catch {
28
- return false
29
- }
30
- }
31
-
32
- // sweep any spexcode-stamped plugin bundle (`<host>/plugins/spexcode`, or a `.claude-plugin/plugin.json` named
33
- // spexcode) under the project's plugin-host folders. The bundle EMITTER is a later node, so a native-only install
34
- // has nothing here today; the sweep keeps uninstall a true inverse once it lands (and cleans a hand-dropped one).
35
- function sweepPluginBundles(proj: string, hosts: readonly string[]): string[] {
36
- const removed: string[] = []
37
- for (const host of new Set(hosts)) {
38
- const pluginsDir = join(proj, host, 'plugins')
39
- if (!existsSync(pluginsDir)) continue
40
- for (const e of readdirSync(pluginsDir, { withFileTypes: true })) {
41
- if (!e.isDirectory()) continue
42
- const dir = join(pluginsDir, e.name)
43
- if (isSpexcodeBundle(dir)) {
44
- rmSync(dir, { recursive: true, force: true })
45
- removed.push(relative(proj, dir))
46
- }
47
- }
48
- }
49
- return removed
50
- }
51
-
52
- // Arbitrary plugin landing folders are the one materialized path set that cannot be reconstructed from stamps
53
- // alone, so materialize records them as data. Read every current per-tree ledger plus the legacy project-global
54
- // ledger before uninstall removes the store; a stale bundle remains removable even after current config stopped
55
- // naming its former host folder.
56
- function pluginLedgerHosts(store: string): string[] {
57
- const ledgers = [join(store, 'plugin-folders')]
58
- const trees = join(store, 'trees')
59
- if (existsSync(trees)) {
60
- for (const e of readdirSync(trees, { withFileTypes: true })) {
61
- if (e.isDirectory()) ledgers.push(join(trees, e.name, 'plugin-folders'))
62
- }
63
- }
64
- const hosts: string[] = []
65
- for (const ledger of ledgers) {
66
- if (!existsSync(ledger)) continue
67
- hosts.push(...readFileSync(ledger, 'utf8').split('\n').map((line) => line.trim()).filter(Boolean))
68
- }
69
- return hosts
70
- }
71
-
72
- // resolve the repo's shared git hooks dir (the common dir's hooks/), or null when <dir> isn't a git repo.
73
- function hooksDir(proj: string): string | null {
74
- try {
75
- const common = execFileSync(gitBinary(process.env), ['-C', proj, 'rev-parse', '--path-format=absolute', '--git-common-dir'], {
76
- encoding: 'utf8',
77
- stdio: ['ignore', 'pipe', 'ignore'],
78
- }).trim()
79
- return join(common, 'hooks')
80
- } catch {
81
- return null
82
- }
83
- }
84
-
85
- // Remove only byte-identical products of the canonical hook templates. Enumerating the same template directory
86
- // init copies keeps every generated hook covered without a second name list; modified and unrelated hooks survive.
87
- function removeHooks(proj: string): string[] {
88
- const dir = hooksDir(proj)
89
- if (!dir) return []
90
- const removed: string[] = []
91
- for (const e of readdirSync(HOOK_TEMPLATES, { withFileTypes: true })) {
92
- if (!e.isFile()) continue
93
- const hook = join(dir, e.name)
94
- if (!existsSync(hook) || !readFileSync(hook).equals(readFileSync(join(HOOK_TEMPLATES, e.name)))) continue
95
- rmSync(hook, { force: true })
96
- removed.push(e.name)
97
- }
98
- return removed
99
- }
100
-
101
- export function uninstall(targetArg: string | undefined, opts: { hooks?: boolean } = {}): void {
102
- const proj = resolve(targetArg ?? process.cwd())
103
- console.log(`spex uninstall → ${proj}`)
104
-
105
- // cwd = the project so the .plugins loaders read THIS tree's surface nodes (the live skill/agent names tell
106
- // each adapter's clean() exactly which name-scoped on-demand files were its to remove).
107
- const prevCwd = process.cwd()
108
- let arts: HarnessArtifacts = { skills: [], agents: [] }
109
- try {
110
- process.chdir(proj)
111
- arts = { skills: loadSkillConfig().map((s) => s.name), agents: loadAgentConfig().map((a) => a.name) }
112
- } catch {
113
- // no readable .plugins (already partly torn down, or never adopted) — clean still strips the harness wiring.
114
- } finally {
115
- process.chdir(prevCwd)
116
- }
117
-
118
- // 1+2. materialize(∅): every harness's artifacts (contract block, shim, trust, skills/agents), the managed
119
- // .gitignore + info/exclude blocks, any legacy skip-worktree bit, and the content filter — the SAME
120
- // erase phase every materialize runs, asserted against the empty policy. One inverse, never a parallel one.
121
- dematerialize(proj, arts)
122
-
123
- // 3. Locate the global per-project store and recover every current/legacy plugin landing folder from its
124
- // ledgers BEFORE deleting it. The store is the runtime tier, not the user's tracked intent asset.
125
- let store: string | null = null
126
- let ledgerHosts: string[] = []
127
- try {
128
- store = runtimeRoot(proj)
129
- } catch {
130
- store = null
131
- }
132
- if (store) ledgerHosts = pluginLedgerHosts(store)
133
-
134
- // 4. Any spexcode-stamped plugin bundle under configured, standard, or ledger-recovered hosts. The ledger
135
- // input is what closes plugin-folder A -> native/folder B even when the previous materialize never finished.
136
- let pluginHosts: string[] = [...DEFAULT_PLUGIN_HOSTS, ...ledgerHosts]
137
- try {
138
- const targets = resolveHarnessTargets(readConfig(mainCheckout(proj)).harnesses)
139
- pluginHosts = [...pluginHosts, ...targets.filter((t) => t.kind === 'plugin').map((t) => (t as { folder: string }).folder)]
140
- } catch {
141
- // an illegal harnesses set doesn't block backout — fall through to the standard hosts.
142
- }
143
- const bundles = sweepPluginBundles(proj, pluginHosts)
144
-
145
- // 5. The whole store: per-tree manifests/hashes/ledgers and sessions.
146
- const removedStores: string[] = []
147
- for (const candidate of new Set([store].filter((path): path is string => !!path))) {
148
- if (!existsSync(candidate)) continue
149
- rmSync(candidate, { recursive: true, force: true })
150
- removedStores.push(candidate)
151
- }
152
-
153
- console.log(`✓ dematerialized (contract blocks, shims, Codex trust, skills, sub-agents, ignore blocks, content filter) for ${HARNESSES.map((h) => h.id).join(', ')}`)
154
- if (removedStores.length) console.log(`✓ removed the global per-project store (${removedStores.join(', ')})`)
155
- if (bundles.length) console.log(`✓ removed plugin bundle(s): ${bundles.join(', ')}`)
156
-
157
- // Git hooks are per-clone and may carry user logic → preserved unless --hooks (and even then only while
158
- // byte-identical to a canonical generated template; a user edit withdraws our ownership).
159
- if (opts.hooks) {
160
- const removed = removeHooks(proj)
161
- if (removed.length) console.log(`✓ removed git hooks (${removed.join(', ')})`)
162
- else console.log('• no spexcode git hooks to remove')
163
- } else {
164
- console.log('• left git hooks in place (per-clone; pass --hooks to remove the spexcode ones)')
165
- }
166
-
167
- console.log(`
168
- SpexCode wiring removed. Your spec data is untouched:
169
- • .spec/ (including .plugins/) and spexcode.json remain — your tracked intent is never deleted by uninstall.
170
- • To re-adopt later: \`spex init\` regenerates the shims, contract, trust, and global store.`)
171
- }