spexcode 0.6.2 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. package/README.md +3 -1
  2. package/bin/spex.mjs +4 -0
  3. package/node_modules/@spexcode/session-core/dist/delivery-queue.d.ts +23 -0
  4. package/node_modules/@spexcode/session-core/dist/delivery-queue.js +195 -0
  5. package/node_modules/@spexcode/session-core/dist/index.d.ts +5 -0
  6. package/node_modules/@spexcode/session-core/dist/index.js +5 -0
  7. package/node_modules/@spexcode/session-core/dist/internal.d.ts +3 -0
  8. package/node_modules/@spexcode/session-core/dist/internal.js +3 -0
  9. package/node_modules/@spexcode/session-core/dist/message.d.ts +22 -0
  10. package/node_modules/@spexcode/session-core/dist/message.js +52 -0
  11. package/node_modules/@spexcode/session-core/dist/record-lock.d.ts +7 -0
  12. package/node_modules/@spexcode/session-core/dist/record-lock.js +152 -0
  13. package/node_modules/@spexcode/session-core/dist/runtime-session.d.ts +50 -0
  14. package/node_modules/@spexcode/session-core/dist/runtime-session.js +286 -0
  15. package/node_modules/@spexcode/session-core/dist/session-cursors.d.ts +14 -0
  16. package/node_modules/@spexcode/session-core/dist/session-cursors.js +82 -0
  17. package/node_modules/@spexcode/session-core/dist/session-timeline.d.ts +46 -0
  18. package/node_modules/@spexcode/session-core/dist/session-timeline.js +216 -0
  19. package/node_modules/@spexcode/session-core/package.json +33 -0
  20. package/node_modules/@spexcode/spec-cli/bin/spex.mjs +77 -0
  21. package/node_modules/@spexcode/spec-cli/dist/attach.d.ts +2 -0
  22. package/node_modules/@spexcode/spec-cli/dist/attach.js +48 -0
  23. package/node_modules/@spexcode/spec-cli/dist/claude-headless.d.ts +36 -0
  24. package/node_modules/@spexcode/spec-cli/dist/claude-headless.js +316 -0
  25. package/node_modules/@spexcode/spec-cli/dist/cli.d.ts +1 -0
  26. package/node_modules/@spexcode/spec-cli/dist/cli.js +1908 -0
  27. package/node_modules/@spexcode/spec-cli/dist/client.d.ts +112 -0
  28. package/node_modules/@spexcode/spec-cli/dist/client.js +481 -0
  29. package/node_modules/@spexcode/spec-cli/dist/cockpit.d.ts +7 -0
  30. package/node_modules/@spexcode/spec-cli/dist/cockpit.js +26 -0
  31. package/node_modules/@spexcode/spec-cli/dist/codex-headless.d.ts +1 -0
  32. package/{spec-cli/src/codex-headless.ts → node_modules/@spexcode/spec-cli/dist/codex-headless.js} +3 -9
  33. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +58 -0
  34. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +798 -0
  35. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.d.ts +1 -0
  36. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.js +90 -0
  37. package/node_modules/@spexcode/spec-cli/dist/contract-filter.d.ts +14 -0
  38. package/node_modules/@spexcode/spec-cli/dist/contract-filter.js +219 -0
  39. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.d.ts +6 -0
  40. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.js +50 -0
  41. package/node_modules/@spexcode/spec-cli/dist/doctor.d.ts +9 -0
  42. package/node_modules/@spexcode/spec-cli/dist/doctor.js +553 -0
  43. package/node_modules/@spexcode/spec-cli/dist/eval-host.d.ts +1 -0
  44. package/node_modules/@spexcode/spec-cli/dist/eval-host.js +19 -0
  45. package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +26 -0
  46. package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +475 -0
  47. package/node_modules/@spexcode/spec-cli/dist/file-write.d.ts +2 -0
  48. package/node_modules/@spexcode/spec-cli/dist/file-write.js +26 -0
  49. package/node_modules/@spexcode/spec-cli/dist/flat.d.ts +89 -0
  50. package/node_modules/@spexcode/spec-cli/dist/flat.js +1076 -0
  51. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.d.ts +56 -0
  52. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.js +168 -0
  53. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.d.ts +28 -0
  54. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +363 -0
  55. package/node_modules/@spexcode/spec-cli/dist/gateway.d.ts +38 -0
  56. package/node_modules/@spexcode/spec-cli/dist/gateway.js +527 -0
  57. package/node_modules/@spexcode/spec-cli/dist/graphCache.d.ts +27 -0
  58. package/node_modules/@spexcode/spec-cli/dist/graphCache.js +717 -0
  59. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.d.ts +14 -0
  60. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +36 -0
  61. package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +85 -0
  62. package/node_modules/@spexcode/spec-cli/dist/graphStream.js +1195 -0
  63. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.d.ts +64 -0
  64. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.js +166 -0
  65. package/node_modules/@spexcode/spec-cli/dist/guide.d.ts +8 -0
  66. package/{spec-cli/src/guide.ts → node_modules/@spexcode/spec-cli/dist/guide.js} +36 -32
  67. package/node_modules/@spexcode/spec-cli/dist/harness-select.d.ts +18 -0
  68. package/node_modules/@spexcode/spec-cli/dist/harness-select.js +62 -0
  69. package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +338 -0
  70. package/node_modules/@spexcode/spec-cli/dist/harness.js +3146 -0
  71. package/node_modules/@spexcode/spec-cli/dist/headless-controller.d.ts +9 -0
  72. package/node_modules/@spexcode/spec-cli/dist/headless-controller.js +47 -0
  73. package/node_modules/@spexcode/spec-cli/dist/help.d.ts +15 -0
  74. package/node_modules/@spexcode/spec-cli/dist/help.js +594 -0
  75. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.d.ts +18 -0
  76. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +122 -0
  77. package/node_modules/@spexcode/spec-cli/dist/hooks.d.ts +1 -0
  78. package/{spec-cli/src/hooks.ts → node_modules/@spexcode/spec-cli/dist/hooks.js} +15 -15
  79. package/node_modules/@spexcode/spec-cli/dist/host-resources.d.ts +97 -0
  80. package/node_modules/@spexcode/spec-cli/dist/host-resources.js +795 -0
  81. package/node_modules/@spexcode/spec-cli/dist/host.d.ts +88 -0
  82. package/node_modules/@spexcode/spec-cli/dist/host.js +663 -0
  83. package/node_modules/@spexcode/spec-cli/dist/index.d.ts +1 -0
  84. package/node_modules/@spexcode/spec-cli/dist/index.js +899 -0
  85. package/node_modules/@spexcode/spec-cli/dist/init.d.ts +1 -0
  86. package/node_modules/@spexcode/spec-cli/dist/init.js +284 -0
  87. package/node_modules/@spexcode/spec-cli/dist/issues-cli.d.ts +6 -0
  88. package/node_modules/@spexcode/spec-cli/dist/issues-cli.js +334 -0
  89. package/node_modules/@spexcode/spec-cli/dist/issues.d.ts +69 -0
  90. package/node_modules/@spexcode/spec-cli/dist/issues.js +189 -0
  91. package/node_modules/@spexcode/spec-cli/dist/lint.d.ts +34 -0
  92. package/node_modules/@spexcode/spec-cli/dist/lint.js +476 -0
  93. package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +8 -0
  94. package/node_modules/@spexcode/spec-cli/dist/listen.js +24 -0
  95. package/node_modules/@spexcode/spec-cli/dist/localIssues.d.ts +65 -0
  96. package/node_modules/@spexcode/spec-cli/dist/localIssues.js +582 -0
  97. package/node_modules/@spexcode/spec-cli/dist/login-page.d.ts +5 -0
  98. package/{spec-cli/src/login-page.ts → node_modules/@spexcode/spec-cli/dist/login-page.js} +7 -7
  99. package/node_modules/@spexcode/spec-cli/dist/loop-in.d.ts +23 -0
  100. package/{spec-cli/src/loop-in.ts → node_modules/@spexcode/spec-cli/dist/loop-in.js} +35 -45
  101. package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +69 -0
  102. package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +603 -0
  103. package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +18 -0
  104. package/node_modules/@spexcode/spec-cli/dist/materialize.js +468 -0
  105. package/node_modules/@spexcode/spec-cli/dist/mentions.d.ts +50 -0
  106. package/node_modules/@spexcode/spec-cli/dist/mentions.js +117 -0
  107. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.d.ts +5 -0
  108. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.js +215 -0
  109. package/node_modules/@spexcode/spec-cli/dist/opencode.d.ts +3 -0
  110. package/{spec-cli/src/opencode.ts → node_modules/@spexcode/spec-cli/dist/opencode.js} +9 -13
  111. package/node_modules/@spexcode/spec-cli/dist/pi-harness.d.ts +3 -0
  112. package/{spec-cli/src/pi-harness.ts → node_modules/@spexcode/spec-cli/dist/pi-harness.js} +45 -40
  113. package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +25 -0
  114. package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +196 -0
  115. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.d.ts +21 -0
  116. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.js +146 -0
  117. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.d.ts +10 -0
  118. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +404 -0
  119. package/node_modules/@spexcode/spec-cli/dist/pty-helper.d.mts +1 -0
  120. package/node_modules/@spexcode/spec-cli/dist/pty-helper.mjs +101 -0
  121. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.d.mts +2 -0
  122. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.mjs +23 -0
  123. package/node_modules/@spexcode/spec-cli/dist/public-graph.d.ts +46 -0
  124. package/node_modules/@spexcode/spec-cli/dist/public-graph.js +56 -0
  125. package/node_modules/@spexcode/spec-cli/dist/ranker.d.ts +15 -0
  126. package/node_modules/@spexcode/spec-cli/dist/ranker.js +188 -0
  127. package/node_modules/@spexcode/spec-cli/dist/reaper.d.ts +6 -0
  128. package/node_modules/@spexcode/spec-cli/dist/reaper.js +81 -0
  129. package/node_modules/@spexcode/spec-cli/dist/reviews.d.ts +189 -0
  130. package/node_modules/@spexcode/spec-cli/dist/reviews.js +350 -0
  131. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.d.ts +6 -0
  132. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.js +37 -0
  133. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +21 -0
  134. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +84 -0
  135. package/node_modules/@spexcode/spec-cli/dist/runtime-rotate.d.ts +1 -0
  136. package/node_modules/@spexcode/spec-cli/dist/runtime-rotate.js +58 -0
  137. package/node_modules/@spexcode/spec-cli/dist/search.bench.d.mts +1 -0
  138. package/node_modules/@spexcode/spec-cli/dist/search.bench.mjs +75 -0
  139. package/node_modules/@spexcode/spec-cli/dist/search.d.ts +20 -0
  140. package/node_modules/@spexcode/spec-cli/dist/search.js +61 -0
  141. package/node_modules/@spexcode/spec-cli/dist/session-declarations.d.ts +13 -0
  142. package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +126 -0
  143. package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +10 -0
  144. package/node_modules/@spexcode/spec-cli/dist/session-execution.js +70 -0
  145. package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +28 -0
  146. package/node_modules/@spexcode/spec-cli/dist/session-files.js +130 -0
  147. package/node_modules/@spexcode/spec-cli/dist/session-follow.d.ts +31 -0
  148. package/node_modules/@spexcode/spec-cli/dist/session-follow.js +179 -0
  149. package/node_modules/@spexcode/spec-cli/dist/session-reparent.d.ts +10 -0
  150. package/node_modules/@spexcode/spec-cli/dist/session-reparent.js +33 -0
  151. package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +11 -0
  152. package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +20 -0
  153. package/node_modules/@spexcode/spec-cli/dist/session-web.d.ts +24 -0
  154. package/node_modules/@spexcode/spec-cli/dist/session-web.js +141 -0
  155. package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +433 -0
  156. package/node_modules/@spexcode/spec-cli/dist/sessions.js +4667 -0
  157. package/node_modules/@spexcode/spec-cli/dist/sh.d.ts +1 -0
  158. package/node_modules/@spexcode/spec-cli/dist/sh.js +3 -0
  159. package/node_modules/@spexcode/spec-cli/dist/shim-runtime.d.ts +1 -0
  160. package/{spec-cli/src/shim-runtime.ts → node_modules/@spexcode/spec-cli/dist/shim-runtime.js} +3 -4
  161. package/node_modules/@spexcode/spec-cli/dist/slash-commands.d.ts +9 -0
  162. package/node_modules/@spexcode/spec-cli/dist/slash-commands.js +307 -0
  163. package/node_modules/@spexcode/spec-cli/dist/source-files.d.ts +9 -0
  164. package/node_modules/@spexcode/spec-cli/dist/source-files.js +113 -0
  165. package/node_modules/@spexcode/spec-cli/dist/supervise.d.ts +1 -0
  166. package/node_modules/@spexcode/spec-cli/dist/supervise.js +308 -0
  167. package/node_modules/@spexcode/spec-cli/dist/tree.d.ts +25 -0
  168. package/node_modules/@spexcode/spec-cli/dist/tree.js +98 -0
  169. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.d.ts +3 -0
  170. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.js +41 -0
  171. package/node_modules/@spexcode/spec-cli/dist/uninstall.d.ts +3 -0
  172. package/node_modules/@spexcode/spec-cli/dist/uninstall.js +177 -0
  173. package/node_modules/@spexcode/spec-cli/dist/uploads.d.ts +36 -0
  174. package/node_modules/@spexcode/spec-cli/dist/uploads.js +243 -0
  175. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.d.ts +1 -0
  176. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.js +47 -0
  177. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/dispatch.sh +5 -5
  178. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/harness.sh +1 -1
  179. package/node_modules/@spexcode/spec-cli/package.json +49 -0
  180. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
  181. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec-first.sh +1 -1
  182. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec-of-file.sh +1 -0
  183. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/spec.md +2 -2
  184. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +31 -29
  185. package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +94 -0
  186. package/node_modules/@spexcode/spec-core/dist/anchors.js +730 -0
  187. package/node_modules/@spexcode/spec-core/dist/git.d.ts +170 -0
  188. package/node_modules/@spexcode/spec-core/dist/git.js +2768 -0
  189. package/node_modules/@spexcode/spec-core/dist/graph-delta.d.ts +44 -0
  190. package/node_modules/@spexcode/spec-core/dist/graph-delta.js +72 -0
  191. package/node_modules/@spexcode/spec-core/dist/graph.d.ts +34 -0
  192. package/node_modules/@spexcode/spec-core/dist/graph.js +237 -0
  193. package/node_modules/@spexcode/spec-core/dist/graphDelta.d.ts +3 -0
  194. package/node_modules/@spexcode/spec-core/dist/graphDelta.js +13 -0
  195. package/node_modules/@spexcode/spec-core/dist/harness-identity.d.ts +31 -0
  196. package/node_modules/@spexcode/spec-core/dist/harness-identity.js +20 -0
  197. package/node_modules/@spexcode/spec-core/dist/identity-presets.d.ts +152 -0
  198. package/node_modules/@spexcode/spec-core/dist/identity-presets.js +132 -0
  199. package/node_modules/@spexcode/spec-core/dist/index.d.ts +45 -0
  200. package/node_modules/@spexcode/spec-core/dist/index.js +19 -0
  201. package/node_modules/@spexcode/spec-core/dist/layout.d.ts +187 -0
  202. package/node_modules/@spexcode/spec-core/dist/layout.js +548 -0
  203. package/node_modules/@spexcode/spec-core/dist/process-identity.d.ts +37 -0
  204. package/node_modules/@spexcode/spec-core/dist/process-identity.js +214 -0
  205. package/node_modules/@spexcode/spec-core/dist/project-identity.d.ts +12 -0
  206. package/node_modules/@spexcode/spec-core/dist/project-identity.js +71 -0
  207. package/node_modules/@spexcode/spec-core/dist/project-store.d.ts +3 -0
  208. package/node_modules/@spexcode/spec-core/dist/project-store.js +14 -0
  209. package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +2 -0
  210. package/node_modules/@spexcode/spec-core/dist/resilience.js +40 -0
  211. package/node_modules/@spexcode/spec-core/dist/review/index.d.ts +3 -0
  212. package/node_modules/@spexcode/spec-core/dist/review/index.js +5 -0
  213. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +77 -0
  214. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +308 -0
  215. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +66 -0
  216. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +180 -0
  217. package/node_modules/@spexcode/spec-core/dist/review/session.d.ts +4 -0
  218. package/node_modules/@spexcode/spec-core/dist/review/session.js +8 -0
  219. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.d.ts +15 -0
  220. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.js +12 -0
  221. package/node_modules/@spexcode/spec-core/dist/root-lru.d.ts +4 -0
  222. package/{spec-cli/src/root-lru.ts → node_modules/@spexcode/spec-core/dist/root-lru.js} +26 -30
  223. package/node_modules/@spexcode/spec-core/dist/specs.d.ts +117 -0
  224. package/node_modules/@spexcode/spec-core/dist/specs.js +489 -0
  225. package/node_modules/@spexcode/spec-core/package.json +28 -0
  226. package/node_modules/@spexcode/spec-eval/dist/cache.d.ts +10 -0
  227. package/node_modules/@spexcode/spec-eval/dist/cache.js +50 -0
  228. package/node_modules/@spexcode/spec-eval/dist/cli.d.ts +22 -0
  229. package/node_modules/@spexcode/spec-eval/dist/cli.js +981 -0
  230. package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +98 -0
  231. package/node_modules/@spexcode/spec-eval/dist/evaltab.js +176 -0
  232. package/node_modules/@spexcode/spec-eval/dist/filing.d.ts +16 -0
  233. package/node_modules/@spexcode/spec-eval/dist/filing.js +36 -0
  234. package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +48 -0
  235. package/node_modules/@spexcode/spec-eval/dist/freshness.js +799 -0
  236. package/node_modules/@spexcode/spec-eval/dist/host.d.ts +46 -0
  237. package/node_modules/@spexcode/spec-eval/dist/host.js +53 -0
  238. package/node_modules/@spexcode/spec-eval/dist/humanok.d.ts +11 -0
  239. package/node_modules/@spexcode/spec-eval/dist/humanok.js +27 -0
  240. package/node_modules/@spexcode/spec-eval/dist/index.d.ts +4 -0
  241. package/node_modules/@spexcode/spec-eval/dist/index.js +4 -0
  242. package/node_modules/@spexcode/spec-eval/dist/remarks.d.ts +31 -0
  243. package/node_modules/@spexcode/spec-eval/dist/remarks.js +1 -0
  244. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.d.ts +13 -0
  245. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.js +303 -0
  246. package/node_modules/@spexcode/spec-eval/dist/scenarios.d.ts +99 -0
  247. package/node_modules/@spexcode/spec-eval/dist/scenarios.js +650 -0
  248. package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +307 -0
  249. package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +1883 -0
  250. package/node_modules/@spexcode/spec-eval/dist/sidecar.d.ts +55 -0
  251. package/node_modules/@spexcode/spec-eval/dist/sidecar.js +82 -0
  252. package/node_modules/@spexcode/spec-eval/dist/timeline.d.ts +25 -0
  253. package/node_modules/@spexcode/spec-eval/dist/timeline.js +65 -0
  254. package/node_modules/@spexcode/spec-eval/dist/ui-path.d.ts +1 -0
  255. package/node_modules/@spexcode/spec-eval/dist/ui-path.js +2 -0
  256. package/node_modules/@spexcode/spec-eval/package.json +36 -0
  257. package/node_modules/@spexcode/spec-forge/dist/cache.d.ts +29 -0
  258. package/node_modules/@spexcode/spec-forge/dist/cache.js +59 -0
  259. package/node_modules/@spexcode/spec-forge/dist/cli.d.ts +1 -0
  260. package/node_modules/@spexcode/spec-forge/dist/cli.js +100 -0
  261. package/node_modules/@spexcode/spec-forge/dist/drivers/github.d.ts +2 -0
  262. package/node_modules/@spexcode/spec-forge/dist/drivers/github.js +116 -0
  263. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.d.ts +7 -0
  264. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.js +144 -0
  265. package/node_modules/@spexcode/spec-forge/dist/drivers.d.ts +10 -0
  266. package/node_modules/@spexcode/spec-forge/dist/drivers.js +83 -0
  267. package/node_modules/@spexcode/spec-forge/dist/index.d.ts +4 -0
  268. package/node_modules/@spexcode/spec-forge/dist/index.js +4 -0
  269. package/node_modules/@spexcode/spec-forge/dist/links.d.ts +15 -0
  270. package/node_modules/@spexcode/spec-forge/dist/links.js +65 -0
  271. package/node_modules/@spexcode/spec-forge/dist/needs-eval.d.ts +10 -0
  272. package/node_modules/@spexcode/spec-forge/dist/needs-eval.js +20 -0
  273. package/node_modules/@spexcode/spec-forge/dist/port.d.ts +53 -0
  274. package/node_modules/@spexcode/spec-forge/dist/port.js +1 -0
  275. package/node_modules/@spexcode/spec-forge/dist/resident.d.ts +7 -0
  276. package/node_modules/@spexcode/spec-forge/dist/resident.js +43 -0
  277. package/node_modules/@spexcode/spec-forge/package.json +32 -0
  278. package/package.json +33 -19
  279. package/spec-cli/bin/spex.mjs +0 -69
  280. package/spec-cli/src/anchors.ts +0 -728
  281. package/spec-cli/src/attach.ts +0 -43
  282. package/spec-cli/src/claude-headless.ts +0 -321
  283. package/spec-cli/src/cli.ts +0 -1258
  284. package/spec-cli/src/client.ts +0 -414
  285. package/spec-cli/src/cockpit.ts +0 -43
  286. package/spec-cli/src/codex-runtime-generations.ts +0 -646
  287. package/spec-cli/src/commit-surgery.ts +0 -68
  288. package/spec-cli/src/contract-filter.ts +0 -202
  289. package/spec-cli/src/delivery-queue.ts +0 -165
  290. package/spec-cli/src/doctor.ts +0 -516
  291. package/spec-cli/src/execution-trace.ts +0 -444
  292. package/spec-cli/src/file-write.ts +0 -22
  293. package/spec-cli/src/gateway-auth.ts +0 -181
  294. package/spec-cli/src/gateway-hub.ts +0 -358
  295. package/spec-cli/src/gateway.ts +0 -461
  296. package/spec-cli/src/git.ts +0 -2556
  297. package/spec-cli/src/graph.ts +0 -271
  298. package/spec-cli/src/graphCache.ts +0 -710
  299. package/spec-cli/src/graphDelta.ts +0 -77
  300. package/spec-cli/src/graphStream.ts +0 -1019
  301. package/spec-cli/src/harness-select.ts +0 -77
  302. package/spec-cli/src/harness.ts +0 -3025
  303. package/spec-cli/src/headless-controller.ts +0 -63
  304. package/spec-cli/src/help.ts +0 -492
  305. package/spec-cli/src/host-resources.ts +0 -762
  306. package/spec-cli/src/host.ts +0 -611
  307. package/spec-cli/src/identity-presets.d.ts +0 -13
  308. package/spec-cli/src/identity-presets.js +0 -138
  309. package/spec-cli/src/index.ts +0 -818
  310. package/spec-cli/src/init.ts +0 -258
  311. package/spec-cli/src/issues-cli.ts +0 -276
  312. package/spec-cli/src/issues.ts +0 -237
  313. package/spec-cli/src/layout.ts +0 -640
  314. package/spec-cli/src/lint.ts +0 -488
  315. package/spec-cli/src/listen.ts +0 -28
  316. package/spec-cli/src/localIssues.ts +0 -538
  317. package/spec-cli/src/materialize.ts +0 -417
  318. package/spec-cli/src/mentions.ts +0 -81
  319. package/spec-cli/src/opencode-headless.ts +0 -205
  320. package/spec-cli/src/pi-headless.ts +0 -153
  321. package/spec-cli/src/plugin-harness.ts +0 -157
  322. package/spec-cli/src/process-identity.ts +0 -207
  323. package/spec-cli/src/project-identity.ts +0 -73
  324. package/spec-cli/src/project-store.ts +0 -17
  325. package/spec-cli/src/pty-bridge.ts +0 -396
  326. package/spec-cli/src/pty-helper.mjs +0 -96
  327. package/spec-cli/src/pty-native-helper.mjs +0 -22
  328. package/spec-cli/src/ranker.ts +0 -191
  329. package/spec-cli/src/reaper.ts +0 -125
  330. package/spec-cli/src/resilience.ts +0 -41
  331. package/spec-cli/src/reviewSnapshot.ts +0 -28
  332. package/spec-cli/src/reviews.ts +0 -434
  333. package/spec-cli/src/runtime-guard.ts +0 -35
  334. package/spec-cli/src/runtime-ownership.ts +0 -88
  335. package/spec-cli/src/search.bench.mjs +0 -80
  336. package/spec-cli/src/search.ts +0 -63
  337. package/spec-cli/src/session-cursors.ts +0 -91
  338. package/spec-cli/src/session-execution.ts +0 -68
  339. package/spec-cli/src/session-files.ts +0 -121
  340. package/spec-cli/src/session-follow.ts +0 -182
  341. package/spec-cli/src/session-reparent.ts +0 -33
  342. package/spec-cli/src/session-timeline.ts +0 -247
  343. package/spec-cli/src/session-web.ts +0 -135
  344. package/spec-cli/src/sessions.ts +0 -3883
  345. package/spec-cli/src/sh.ts +0 -3
  346. package/spec-cli/src/slash-commands.ts +0 -318
  347. package/spec-cli/src/source-files.ts +0 -104
  348. package/spec-cli/src/specs.ts +0 -498
  349. package/spec-cli/src/supervise.ts +0 -245
  350. package/spec-cli/src/tree.ts +0 -118
  351. package/spec-cli/src/tsx-bin.ts +0 -18
  352. package/spec-cli/src/uninstall.ts +0 -171
  353. package/spec-cli/src/uploads.ts +0 -253
  354. package/spec-cli/src/worktree-sources.ts +0 -42
  355. package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +0 -8
  356. package/spec-dashboard/dist/assets/App-B6IbFbGu.js +0 -2
  357. package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
  358. package/spec-dashboard/dist/assets/Dashboard-_sDk7Rcg.js +0 -27
  359. package/spec-dashboard/dist/assets/EvalsPage-BT357Lyt.js +0 -2
  360. package/spec-dashboard/dist/assets/IssuesPage-BG9rNOgV.js +0 -1
  361. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  362. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  363. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  364. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  365. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  366. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  367. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  368. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  369. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  370. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  371. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  372. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  373. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  374. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  375. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  376. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  377. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  378. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  379. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  380. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  381. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  382. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  383. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  384. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  385. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  386. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  387. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  388. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  389. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  390. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  391. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  392. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  393. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  394. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  395. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  396. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  397. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  398. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  399. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  400. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  401. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  402. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  403. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  404. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  405. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  406. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  407. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  408. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  409. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  410. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  411. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  412. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  413. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  414. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  415. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  416. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  417. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  418. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  419. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  420. package/spec-dashboard/dist/assets/MobileApp-D9yPMR3A.js +0 -2
  421. package/spec-dashboard/dist/assets/Modal-B3EcgeA3.js +0 -1
  422. package/spec-dashboard/dist/assets/PageScroll-CNZOugWV.js +0 -1
  423. package/spec-dashboard/dist/assets/ProjectsPage-C2CRHmvs.js +0 -1
  424. package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
  425. package/spec-dashboard/dist/assets/SessionInterface-DOEjuT3l.js +0 -40
  426. package/spec-dashboard/dist/assets/SessionWindow-1nYgZ4CL.js +0 -1
  427. package/spec-dashboard/dist/assets/Settings-7WeTJn0V.js +0 -1
  428. package/spec-dashboard/dist/assets/Thread-CEbzAFQn.js +0 -13
  429. package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
  430. package/spec-dashboard/dist/assets/data-SlvxNA2r.js +0 -1
  431. package/spec-dashboard/dist/assets/index-DLVeOHL-.js +0 -41
  432. package/spec-dashboard/dist/assets/index-DYLGfPjx.css +0 -1
  433. package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
  434. package/spec-dashboard/dist/assets/launch-yxCYr64x.js +0 -276
  435. package/spec-dashboard/dist/assets/sessionSurface--7l69h4F.js +0 -1
  436. package/spec-dashboard/dist/index.html +0 -29
  437. package/spec-dashboard/src/reviewFilters.js +0 -322
  438. package/spec-dashboard/src/reviewQuery.js +0 -174
  439. package/spec-dashboard/src/session.js +0 -199
  440. package/spec-eval/src/cache.ts +0 -57
  441. package/spec-eval/src/cli.ts +0 -894
  442. package/spec-eval/src/evaltab.ts +0 -264
  443. package/spec-eval/src/filing.ts +0 -39
  444. package/spec-eval/src/freshness.ts +0 -836
  445. package/spec-eval/src/humanok.ts +0 -28
  446. package/spec-eval/src/scenariofresh.ts +0 -244
  447. package/spec-eval/src/scenarios.ts +0 -672
  448. package/spec-eval/src/sessioneval.ts +0 -2220
  449. package/spec-eval/src/sidecar.ts +0 -93
  450. package/spec-eval/src/timeline.ts +0 -54
  451. package/spec-forge/src/__fixtures__/github-forge.json +0 -63
  452. package/spec-forge/src/cache.ts +0 -66
  453. package/spec-forge/src/cli.ts +0 -99
  454. package/spec-forge/src/drivers/github.ts +0 -132
  455. package/spec-forge/src/drivers/gitlab.ts +0 -153
  456. package/spec-forge/src/drivers.ts +0 -73
  457. package/spec-forge/src/links.ts +0 -72
  458. package/spec-forge/src/needs-eval.ts +0 -29
  459. package/spec-forge/src/port.ts +0 -36
  460. package/spec-forge/src/resident.ts +0 -46
  461. /package/{spec-cli → node_modules/@spexcode/spec-cli}/README.md +0 -0
  462. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -0
  463. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-sed-v0.fixture +0 -0
  464. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/commit-msg +0 -0
  465. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-checkout +0 -0
  466. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-merge +0 -0
  467. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/pre-commit +0 -0
  468. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/prepare-commit-msg +0 -0
  469. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/reference-transaction +0 -0
  470. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/extract/spec.md +0 -0
  471. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/regroup/spec.md +0 -0
  472. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/rename/spec.md +0 -0
  473. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/spec.md +0 -0
  474. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/tidy/spec.md +0 -0
  475. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -0
  476. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/idle.sh +0 -0
  477. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/spec.md +0 -0
  478. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/mark-active.sh +0 -0
  479. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/spec.md +0 -0
  480. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/fail.sh +0 -0
  481. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/spec.md +0 -0
  482. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec.md +0 -0
  483. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec.md +0 -0
  484. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec.md +0 -0
  485. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -0
  486. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -0
  487. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -0
  488. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -0
  489. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/spec.md +0 -0
  490. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/digest.mjs +0 -0
  491. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/spec.md +0 -0
  492. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/spec.md +0 -0
  493. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/spec.md +0 -0
  494. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/spec.md +0 -0
  495. /package/{spec-cli → node_modules/@spexcode/spec-core}/templates/spexcode.json +0 -0
@@ -1,762 +0,0 @@
1
- import { cpus, platform } from 'node:os'
2
- import { execFileSync } from 'node:child_process'
3
- import { randomUUID } from 'node:crypto'
4
- import { mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs'
5
- import { dirname, join } from 'node:path'
6
- import { defaultHarness, HARNESSES, harnessById, harnessByIdOrNull, sessionIdentityEnvVars, type HarnessLivenessRecord, type SharedRuntimeDescriptor, type SharedRuntimeProbe } from './harness.js'
7
- import { listSessionIds, readConfig, readJsonConfig, readPublicRecordEntry, readRawRecord, runtimeRoot, type PublicRecordEntry, type RawRecord } from './layout.js'
8
- import { repoRoot } from './git.js'
9
- import { endpointRecordPath } from './host.js'
10
- import { detachedRuntimeGenerationToken, parseProcStat, processStartToken, verifyDetachedRuntime, type ProcessIdentity } from './process-identity.js'
11
- import { readBackendInstanceRecords, type BackendInstanceRecord } from './runtime-ownership.js'
12
-
13
- type Proc = ProcessIdentity & {
14
- ppid: number
15
- ticks: number
16
- rssKiB: number
17
- pssKiB: number | null
18
- cpuPercent: number
19
- command: string
20
- env: Record<string, string>
21
- }
22
-
23
- export type ResourceReference = {
24
- sessionId: string | null
25
- threadId: string | null
26
- status: string | null
27
- ownerState: 'governed' | 'unowned' | 'ambiguous'
28
- referenceState: 'loaded' | 'record-only' | 'queued-no-thread'
29
- turnPresence: 'active' | 'idle' | 'unknown' | 'none'
30
- protectsControlPlane: boolean
31
- liveness?: 'offline' | 'unknown'
32
- }
33
-
34
- export type ResourceOwner = {
35
- kind: 'session' | 'shared-runtime' | 'backend' | 'orphan' | 'unattributed'
36
- id: string
37
- label: string
38
- status?: string
39
- liveness?: 'offline' | 'unknown'
40
- proposal?: string | null
41
- note?: string | null
42
- worktreePath?: string
43
- branch?: string | null
44
- stopped?: boolean
45
- archived?: boolean
46
- processes: Array<ProcessIdentity & { command: string; rssMiB: number; pssMiB: number | null; cpuPercent: number }>
47
- rssMiB: number
48
- pssMiB: number | null
49
- cpuPercent: number
50
- budget: { rssMiB: number | null; idleCpuPercent: number | null }
51
- controlPlane?: { healthy: boolean; refCount: number | null; error?: string }
52
- references?: ResourceReference[]
53
- findings: string[]
54
- reclaim?: { eligible: boolean; reason: string }
55
- }
56
-
57
- export type ResourceReport = {
58
- version: 1
59
- measuredAt: string
60
- projectRoot: string
61
- platform: string
62
- available: boolean
63
- unavailableReason?: string
64
- host: {
65
- memoryTotalMiB: number | null
66
- memoryUsedMiB: number | null
67
- memoryAvailableMiB: number | null
68
- swapTotalMiB: number | null
69
- swapUsedMiB: number | null
70
- cpuPercent: number | null
71
- }
72
- budgets: ResourceBudgets
73
- owners: ResourceOwner[]
74
- totals: { rssMiB: number; pssMiB: number | null; cpuPercent: number }
75
- findings: number
76
- }
77
-
78
- export type ResourceBudgets = {
79
- sessionRssMiB: number
80
- backendRssMiB: number
81
- idleCpuPercent: number
82
- sampleMs: number
83
- reportIntervalMs: number
84
- }
85
-
86
- const DEFAULTS: ResourceBudgets = {
87
- sessionRssMiB: 1024,
88
- backendRssMiB: 2048,
89
- idleCpuPercent: 2,
90
- sampleMs: 1000,
91
- reportIntervalMs: 60_000,
92
- }
93
-
94
- export class ResourceConflict extends Error {
95
- constructor(message: string, readonly code = 'resource-conflict') {
96
- super(message)
97
- this.name = 'ResourceConflict'
98
- }
99
- }
100
-
101
- const finitePositive = (value: unknown, fallback: number, name: string): number => {
102
- if (value === undefined) return fallback
103
- const n = Number(value)
104
- if (!Number.isFinite(n) || n <= 0) throw new ResourceConflict(`resources.${name} must be a positive number`)
105
- return n
106
- }
107
-
108
- export function resourceBudgets(root = repoRoot()): ResourceBudgets {
109
- const r = readConfig(root).resources ?? {}
110
- return {
111
- sessionRssMiB: finitePositive(r.sessionRssMiB, DEFAULTS.sessionRssMiB, 'sessionRssMiB'),
112
- backendRssMiB: finitePositive(r.backendRssMiB, DEFAULTS.backendRssMiB, 'backendRssMiB'),
113
- idleCpuPercent: finitePositive(r.idleCpuPercent, DEFAULTS.idleCpuPercent, 'idleCpuPercent'),
114
- sampleMs: Math.max(50, Math.floor(finitePositive(r.sampleMs, DEFAULTS.sampleMs, 'sampleMs'))),
115
- reportIntervalMs: Math.max(5000, Math.floor(finitePositive(r.reportIntervalMs, DEFAULTS.reportIntervalMs, 'reportIntervalMs'))),
116
- }
117
- }
118
-
119
- const readSelectedEnv = (path: string): Record<string, string> => {
120
- let raw: string
121
- try { raw = readFileSync(path, 'utf8') } catch { return {} }
122
- const wanted = new Set([...sessionIdentityEnvVars(), 'SPEXCODE_PROJECT_ROOT', 'SPEXCODE_INSTANCE_ID'])
123
- const out: Record<string, string> = {}
124
- for (const entry of raw.split('\0')) {
125
- const at = entry.indexOf('=')
126
- if (at > 0 && wanted.has(entry.slice(0, at))) out[entry.slice(0, at)] = entry.slice(at + 1)
127
- }
128
- return out
129
- }
130
-
131
- const readPss = (path: string): number | null => {
132
- try {
133
- const hit = readFileSync(path, 'utf8').match(/^Pss:\s+(\d+)\s+kB$/m)
134
- return hit ? Number(hit[1]) : null
135
- } catch { return null }
136
- }
137
-
138
- let pageKiBCache: number | null = null
139
- const pageKiB = () => {
140
- if (pageKiBCache !== null) return pageKiBCache
141
- try { pageKiBCache = Number(execFileSync('getconf', ['PAGESIZE'], { encoding: 'utf8' }).trim()) / 1024 }
142
- catch { pageKiBCache = 4 }
143
- return pageKiBCache
144
- }
145
- const procSnapshot = (procRoot = '/proc', withDetails = true): Map<number, Proc> => {
146
- const out = new Map<number, Proc>()
147
- let dirs: string[]
148
- try { dirs = readdirSync(procRoot).filter((name) => /^\d+$/.test(name)) } catch { return out }
149
- for (const name of dirs) {
150
- const pid = Number(name)
151
- try {
152
- const stat = parseProcStat(readFileSync(join(procRoot, name, 'stat'), 'utf8'))
153
- const command = withDetails
154
- ? readFileSync(join(procRoot, name, 'comm'), 'utf8').trim()
155
- : ''
156
- out.set(pid, {
157
- pid,
158
- ppid: stat.ppid,
159
- ticks: stat.ticks,
160
- startToken: stat.startToken,
161
- rssKiB: stat.rssPages * pageKiB(),
162
- pssKiB: withDetails ? readPss(join(procRoot, name, 'smaps_rollup')) : null,
163
- cpuPercent: 0,
164
- command,
165
- env: withDetails ? readSelectedEnv(join(procRoot, name, 'environ')) : {},
166
- })
167
- } catch { /* process exited or is unreadable during the snapshot */ }
168
- }
169
- return out
170
- }
171
-
172
- const totalCpuTicks = (procRoot = '/proc'): number => {
173
- try {
174
- const line = readFileSync(join(procRoot, 'stat'), 'utf8').split('\n')[0]
175
- return line.split(/\s+/).slice(1).reduce((n, value) => n + Number(value || 0), 0)
176
- } catch { return 0 }
177
- }
178
-
179
- const hostCpuSnapshot = (procRoot = '/proc'): { total: number; idle: number } | null => {
180
- try {
181
- const fields = readFileSync(join(procRoot, 'stat'), 'utf8').split('\n')[0].split(/\s+/).slice(1).map(Number)
182
- return { total: fields.reduce((n, value) => n + (value || 0), 0), idle: (fields[3] || 0) + (fields[4] || 0) }
183
- } catch { return null }
184
- }
185
-
186
- const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
187
-
188
- const descendants = (root: number, procs: Map<number, Proc>): Set<number> => {
189
- const ids = new Set<number>([root])
190
- let changed = true
191
- while (changed) {
192
- changed = false
193
- for (const p of procs.values()) if (!ids.has(p.pid) && ids.has(p.ppid)) { ids.add(p.pid); changed = true }
194
- }
195
- return ids
196
- }
197
-
198
- type PublicRecordInventory = {
199
- entries: PublicRecordEntry[]
200
- records: RawRecord[]
201
- byId: Map<string, PublicRecordEntry>
202
- }
203
- const publicRecordInventory = (): PublicRecordInventory => {
204
- const entries = listSessionIds().map(readPublicRecordEntry)
205
- const byId = new Map<string, PublicRecordEntry>()
206
- for (const entry of entries) {
207
- if (entry.kind === 'ok') byId.set(entry.raw.session_id, entry)
208
- else if (entry.kind === 'corrupt') byId.set(entry.sessionId, entry)
209
- }
210
- return {
211
- entries,
212
- records: entries.flatMap((entry) => entry.kind === 'ok' ? [entry.raw] : []),
213
- byId,
214
- }
215
- }
216
- const rawRecords = (): RawRecord[] => listSessionIds().map(readRawRecord).filter((r): r is RawRecord => !!r)
217
- const runtimePid = (file: string): number | null => {
218
- try { const pid = Number(readFileSync(file, 'utf8').trim()); return Number.isFinite(pid) && pid > 0 ? pid : null }
219
- catch { return null }
220
- }
221
- type SharedEntry = { descriptor: SharedRuntimeDescriptor; recs: RawRecord[] }
222
- // A session record outlives the config that created it, so a removed plugin harness or a renamed harness id
223
- // leaves a governed record this registry cannot resolve. That is a normal long-lived state for a host-wide
224
- // sweep, not a request naming a bad id: the sweep reports the record and keeps every other owner's findings.
225
- const unresolvableHarness = (rec: RawRecord): string | null => {
226
- const id = rec.harness || defaultHarness.id
227
- return harnessByIdOrNull(id) ? null : id
228
- }
229
- // Mirrors the corrupt-record owner: a record the sweep could not fully read stays a public owner so its
230
- // absence from a process-derived group cannot read as health. Here the unknown half is the harness, so
231
- // lifecycle is unknown and reclaim is closed rather than guessed from the record's own status field.
232
- const unresolvedHarnessOwners = (recs: RawRecord[], budgets: ResourceBudgets, have: (sessionId: string) => boolean): ResourceOwner[] => {
233
- const out: ResourceOwner[] = []
234
- for (const rec of recs) {
235
- const unresolved = unresolvableHarness(rec)
236
- if (!unresolved || !rec.governed || rec.stopped || have(rec.session_id)) continue
237
- out.push({
238
- kind: 'session', id: rec.session_id, label: `session ${rec.session_id.slice(0, 8)}`, status: rec.status, liveness: 'unknown',
239
- processes: [], rssMiB: 0, pssMiB: null, cpuPercent: 0,
240
- budget: { rssMiB: budgets.sessionRssMiB, idleCpuPercent: budgets.idleCpuPercent },
241
- findings: [`harness-unresolved:${unresolved}`],
242
- reclaim: { eligible: false, reason: `harness '${unresolved}' is not resolvable by this registry; shared-runtime stop safety cannot be proven` },
243
- })
244
- }
245
- return out
246
- }
247
- const sharedDescriptors = (recs: RawRecord[], retainRegistry = false): Map<string, SharedEntry> => {
248
- const out = new Map<string, { descriptor: SharedRuntimeDescriptor; recs: RawRecord[] }>()
249
- for (const harness of HARNESSES) for (const descriptor of harness.sharedRuntimes?.(runtimeRoot()) ?? []) {
250
- if (!out.has(descriptor.key)) out.set(descriptor.key, { descriptor, recs: [] })
251
- }
252
- for (const rec of recs) {
253
- // Keep archived records in the adapter join so a stale loaded thread is attributed to its real record and
254
- // reported as an archive hazard. Clean archived records carry stopped:true and no loaded reference, so they
255
- // contribute nothing to the active set while still preserving exact ownership if the invariant is violated.
256
- if (!rec.governed) continue
257
- const harness = harnessByIdOrNull(rec.harness || defaultHarness.id)
258
- if (!harness) continue // joined to no descriptor; surfaced as a finding on that record's own session owner
259
- const exactKey = harness.targetDescriptorKey?.({ session: rec.session_id, harnessSessionId: rec.harness_session_id }) ?? null
260
- for (const descriptor of harness.sharedRuntimes?.(runtimeRoot()) ?? []) {
261
- if (exactKey && descriptor.key !== exactKey) continue
262
- const entry = out.get(descriptor.key) ?? { descriptor, recs: [] }
263
- entry.recs.push(rec)
264
- out.set(descriptor.key, entry)
265
- }
266
- }
267
- if (!retainRegistry) for (const [key, entry] of out) if (!entry.recs.length && !runtimePid(entry.descriptor.pidFile)) out.delete(key)
268
- return out
269
- }
270
-
271
- const projectReferences = (entry: SharedEntry, probe: SharedRuntimeProbe, publicById: Map<string, PublicRecordEntry>): ResourceReference[] => {
272
- const byThread = new Map<string, RawRecord[]>()
273
- for (const rec of entry.recs) if (rec.harness_session_id) byThread.set(rec.harness_session_id, [...(byThread.get(rec.harness_session_id) ?? []), rec])
274
- const observed = probe.healthy ? probe.references : []
275
- const live = observed.map((reference): ResourceReference => {
276
- const owners = byThread.get(reference.referenceId) ?? []
277
- if (owners.length !== 1) return {
278
- sessionId: null,
279
- threadId: reference.referenceId,
280
- status: null,
281
- ownerState: owners.length ? 'ambiguous' : 'unowned',
282
- referenceState: 'loaded',
283
- turnPresence: reference.turnPresence,
284
- protectsControlPlane: true,
285
- }
286
- const rec = owners[0]
287
- const publicEntry = publicById.get(rec.session_id)
288
- return {
289
- sessionId: rec.session_id,
290
- threadId: reference.referenceId,
291
- status: rec.status,
292
- ownerState: 'governed',
293
- referenceState: 'loaded',
294
- turnPresence: reference.turnPresence,
295
- protectsControlPlane: true,
296
- ...(publicEntry?.kind === 'ok' && publicEntry.liveness ? { liveness: publicEntry.liveness } : {}),
297
- }
298
- })
299
- const loaded = new Set(observed.map((reference) => reference.referenceId))
300
- for (const rec of entry.recs) {
301
- if (rec.stopped || (rec.harness_session_id && loaded.has(rec.harness_session_id))) continue
302
- const publicEntry = publicById.get(rec.session_id)
303
- live.push({
304
- sessionId: rec.session_id,
305
- threadId: rec.harness_session_id ?? null,
306
- status: rec.status,
307
- ownerState: 'governed',
308
- referenceState: rec.harness_session_id ? 'record-only' : 'queued-no-thread',
309
- turnPresence: 'none',
310
- protectsControlPlane: false,
311
- ...(publicEntry?.kind === 'ok' && publicEntry.liveness ? { liveness: publicEntry.liveness } : {}),
312
- })
313
- }
314
- return live
315
- }
316
-
317
- type Inventory = { procs: Map<number, Proc>; recs: RawRecord[]; publicById: Map<string, PublicRecordEntry>; ownership: Map<number, string>; shared: ReturnType<typeof sharedDescriptors>; backendPid: number | null; backendInstance: string | null; backendInstances: BackendInstanceRecord[] }
318
-
319
- const buildInventory = (procs: Map<number, Proc>, publicRecords = publicRecordInventory()): Inventory => {
320
- const recs = publicRecords.records
321
- const activeRecs = recs.filter((rec) => {
322
- if (!rec.archived) return true
323
- // A legacy archived row with a resident leaf is a visible hazard, not clean cold storage. Keep charging
324
- // that exact process until an explicit archive repair succeeds; truly cold records stay out of the active set.
325
- const pid = runtimePid(join(runtimeRoot(), 'sessions', rec.session_id, 'agent.pid'))
326
- return !!pid && procs.has(pid)
327
- })
328
- const byId = new Map(activeRecs.map((rec) => [rec.session_id, rec.session_id]))
329
- for (const entry of publicRecords.entries) if (entry.kind === 'corrupt') byId.set(entry.sessionId, entry.sessionId)
330
- for (const rec of activeRecs) if (rec.harness_session_id) byId.set(rec.harness_session_id, rec.session_id)
331
- const ownership = new Map<number, string>()
332
- const adapterVars = sessionIdentityEnvVars().filter((v) => v !== 'SPEXCODE_SESSION_ID')
333
- const actingSession = (p: Proc): string | undefined => {
334
- const acting = adapterVars.map((key) => p.env[key]).find((value) => value && byId.has(value))
335
- return acting ? byId.get(acting) : undefined
336
- }
337
- for (const p of procs.values()) {
338
- const acting = actingSession(p)
339
- const fallback = p.env.SPEXCODE_SESSION_ID
340
- const sid = acting ?? (fallback ? byId.get(fallback) : undefined)
341
- if (sid) ownership.set(p.pid, `session:${sid}`)
342
- }
343
- for (const rec of activeRecs) {
344
- const root = runtimePid(join(runtimeRoot(), 'sessions', rec.session_id, 'agent.pid'))
345
- if (root && procs.has(root)) for (const pid of descendants(root, procs)) if (!ownership.has(pid)) ownership.set(pid, `session:${rec.session_id}`)
346
- }
347
-
348
- const shared = sharedDescriptors(recs)
349
- for (const [key, entry] of shared) {
350
- const root = runtimePid(entry.descriptor.pidFile)
351
- if (root && procs.has(root)) for (const pid of descendants(root, procs)) {
352
- const proc = procs.get(pid)!
353
- // A legacy shared daemon may carry its first launcher's fallback session id. The registered root and
354
- // descendants without a known acting thread still belong to the control plane; real per-thread tools
355
- // carry the adapter's thread id and retain their session charge.
356
- if (pid === root || !actingSession(proc)) ownership.set(pid, `shared:${key}`)
357
- }
358
- }
359
-
360
- // Endpoint records are served-tree slots, while sessions/shared runtimes/instance history use the Git
361
- // common-dir project slot. Reuse the endpoint seam instead of assuming both identities have one path.
362
- const backend = readJsonConfig(endpointRecordPath(repoRoot())) as { pid?: number; instanceId?: string }
363
- const backendPid = Number.isFinite(Number(backend.pid)) ? Number(backend.pid) : null
364
- const backendInstance = typeof backend.instanceId === 'string' ? backend.instanceId : null
365
- if (backendPid && procs.has(backendPid)) for (const pid of descendants(backendPid, procs)) {
366
- if (!ownership.has(pid)) ownership.set(pid, `backend:${backendInstance || backendPid}`)
367
- }
368
- const backendInstances = readBackendInstanceRecords(repoRoot())
369
- for (const instance of backendInstances) {
370
- if (processStartToken(instance.pid) !== instance.startToken || !procs.has(instance.pid)) continue
371
- const current = instance.pid === backendPid && instance.instanceId === backendInstance
372
- const key = current ? `backend:${instance.instanceId}` : `orphan:backend:${instance.instanceId}`
373
- for (const pid of descendants(instance.pid, procs)) if (!ownership.has(pid)) ownership.set(pid, key)
374
- }
375
-
376
- const root = repoRoot()
377
- for (const p of procs.values()) {
378
- if (ownership.has(p.pid) || p.env.SPEXCODE_PROJECT_ROOT !== root) continue
379
- const claimed = p.env.SPEXCODE_SESSION_ID
380
- if (claimed && !byId.has(claimed)) ownership.set(p.pid, `orphan:session:${claimed}`)
381
- else if (p.env.SPEXCODE_INSTANCE_ID && p.env.SPEXCODE_INSTANCE_ID !== backendInstance) ownership.set(p.pid, `orphan:backend:${p.env.SPEXCODE_INSTANCE_ID}`)
382
- else ownership.set(p.pid, 'unattributed:project')
383
- }
384
- return { procs, recs, publicById: publicRecords.byId, ownership, shared, backendPid, backendInstance, backendInstances }
385
- }
386
-
387
- const mib = (kib: number) => Math.round((kib / 1024) * 10) / 10
388
- const ownerProcesses = (ids: number[], procs: Map<number, Proc>) => ids.map((pid) => procs.get(pid)!).filter(Boolean).map((p) => ({
389
- pid: p.pid,
390
- startToken: p.startToken,
391
- command: p.command,
392
- rssMiB: mib(p.rssKiB),
393
- pssMiB: p.pssKiB === null ? null : mib(p.pssKiB),
394
- cpuPercent: Math.round(p.cpuPercent * 10) / 10,
395
- }))
396
-
397
- const ownerTotals = (processes: ResourceOwner['processes']) => ({
398
- rssMiB: Math.round(processes.reduce((n, p) => n + p.rssMiB, 0) * 10) / 10,
399
- pssMiB: processes.every((p) => p.pssMiB !== null) ? Math.round(processes.reduce((n, p) => n + (p.pssMiB ?? 0), 0) * 10) / 10 : null,
400
- cpuPercent: Math.round(processes.reduce((n, p) => n + p.cpuPercent, 0) * 10) / 10,
401
- })
402
-
403
- const terminal = (rec: RawRecord | undefined) => !!rec && rec.status === 'awaiting' && (rec.proposal === 'nothing' || rec.proposal === 'close')
404
-
405
- const probeRuntime = async (descriptor: SharedRuntimeDescriptor): Promise<SharedRuntimeProbe> => {
406
- try { return await descriptor.probe() }
407
- catch (error) { return { healthy: false, references: [], error: (error as Error).message } }
408
- }
409
-
410
- const sessionOwnerBlocker = (id: string, harnessId: string | null, recs: RawRecord[]): string | null => {
411
- const targetRecord = recs.find((rec) => rec.session_id === id)
412
- const targetHarness = harnessId ? harnessById(harnessId) : null
413
- const targetThread = targetRecord?.harness_session_id
414
- if (!targetHarness || !targetThread) return null
415
- const ownerCount = recs.filter((rec) => rec.governed && (rec.harness || defaultHarness.id) === targetHarness.id && rec.harness_session_id === targetThread).length
416
- if (ownerCount === 1) return null
417
- const label = targetHarness.sharedRuntimes?.(runtimeRoot())[0]?.label ?? targetHarness.id
418
- return `${label} target thread ${targetThread} has no one exact governed session owner`
419
- }
420
-
421
- export function assertSessionOwnerSafe(id: string, harnessId: string): void {
422
- const blocker = sessionOwnerBlocker(id, harnessId, rawRecords())
423
- if (blocker) throw new ResourceConflict(blocker)
424
- }
425
-
426
- const sessionStopBlocker = async (
427
- id: string,
428
- harnessId: string | null,
429
- recs = rawRecords(),
430
- knownProbes?: Map<string, SharedRuntimeProbe>,
431
- opts: { coldReceipt?: unknown } = {},
432
- ): Promise<string | null> => {
433
- const targetRecord = recs.find((rec) => rec.session_id === id)
434
- const targetHarness = harnessId ? harnessById(harnessId) : null
435
- const targetThread = targetRecord?.harness_session_id
436
- const ownerBlocker = sessionOwnerBlocker(id, harnessId, recs)
437
- if (ownerBlocker) return ownerBlocker
438
- const exactKey = targetHarness?.targetDescriptorKey?.({ session: id, harnessSessionId: targetRecord?.harness_session_id }) ?? null
439
- const targetDescriptors = targetHarness?.sharedRuntimes?.(runtimeRoot()) ?? []
440
- if (targetHarness && targetDescriptors.length === 0) return null
441
- if (targetRecord?.harness_session_id && targetHarness?.targetDescriptorKey && targetDescriptors.length && !exactKey)
442
- return `${targetHarness.id} target has no exact shared-runtime generation binding`
443
- const allowed = targetHarness
444
- ? new Set(exactKey ? [exactKey] : targetDescriptors.map((descriptor) => descriptor.key))
445
- : null
446
- for (const [key, entry] of sharedDescriptors(recs, true)) {
447
- if (allowed && !allowed.has(key)) continue
448
- const descriptor = entry.descriptor
449
- const pid = runtimePid(descriptor.pidFile)
450
- const ownerCounts = new Map<string, number>()
451
- for (const rec of entry.recs) if (rec.harness_session_id) ownerCounts.set(rec.harness_session_id, (ownerCounts.get(rec.harness_session_id) ?? 0) + 1)
452
- const entryTargetThread = entry.recs.find((rec) => rec.session_id === id)?.harness_session_id
453
- if (entryTargetThread && ownerCounts.get(entryTargetThread) !== 1)
454
- return `${descriptor.label} target thread ${entryTargetThread} has no one exact governed session owner`
455
- if (!knownProbes && descriptor.mutationGuard) {
456
- if (!targetThread) return `${descriptor.label} target has no exact governed thread identity`
457
- if (!pid) return `${descriptor.label} target-scoped mutation guard has no readable owner PID`
458
- const identityBefore = verifyDetachedRuntime(pid, descriptor.receiptFile)
459
- if (!identityBefore.ok)
460
- return `${descriptor.label} PID ${pid} has no matching live detached process-boundary record: ${identityBefore.reason}`
461
- let guard
462
- try { guard = await descriptor.mutationGuard(targetThread, opts) }
463
- catch (error) { return `${descriptor.label} target-scoped mutation guard failed: ${(error as Error).message}` }
464
- const identityAfter = verifyDetachedRuntime(pid, descriptor.receiptFile)
465
- if (!identityAfter.ok || detachedRuntimeGenerationToken(identityAfter.identity) !== detachedRuntimeGenerationToken(identityBefore.identity))
466
- return `${descriptor.label} PID/start/detached-receipt identity changed during target-scoped mutation guard${identityAfter.ok ? '' : `: ${identityAfter.reason}`}`
467
- if (guard.descendantIds.length && guard.coldTeardownAuthorized !== true)
468
- return `${descriptor.label} target thread ${targetThread} has owned descendants (${guard.descendantIds.join(', ')})`
469
- if (!guard.healthy)
470
- return `${descriptor.label} target thread ${targetThread} is unknown: ${guard.error || 'target-scoped mutation guard failed'}`
471
- if (guard.targetTurnPresence === 'active') return `${descriptor.label} target thread ${targetThread} has an active turn`
472
- if (guard.targetTurnPresence === 'unknown') return `${descriptor.label} target thread ${targetThread} turn state is unknown`
473
- continue
474
- }
475
- const probe = knownProbes?.get(descriptor.key) ?? await probeRuntime(descriptor)
476
- const targetRef = probe.healthy && targetThread ? probe.references.find((reference) => reference.referenceId === targetThread) : undefined
477
- const siblings = probe.healthy ? probe.references.filter((reference) => !targetThread || reference.referenceId !== targetThread) : []
478
- const liveReason = siblings.length
479
- ? `${siblings.length} live sibling thread(s)`
480
- : probe.healthy ? `${probe.references.length} live thread reference(s)` : 'an unproven live reference set'
481
- if (!pid) return `${descriptor.label} has ${liveReason} but no readable owner PID`
482
- const identity = verifyDetachedRuntime(pid, descriptor.receiptFile)
483
- if (!identity.ok) {
484
- const refs = probe.references.map((reference) => reference.referenceId).join(', ') || 'no loaded threads'
485
- return `${descriptor.label} PID ${pid} serves ${refs} and has no matching live detached process-boundary record: ${identity.reason}`
486
- }
487
- if (!probe.healthy)
488
- return `${descriptor.label} PID ${pid}@${identity.identity.startToken} has an unproven live reference set: ${probe.error || 'unknown probe failure'}`
489
- }
490
- return null
491
- }
492
-
493
- const probeShared = async (shared: Map<string, SharedEntry>): Promise<Map<string, SharedRuntimeProbe>> => new Map(await Promise.all(
494
- [...shared].map(async ([key, entry]) => [key, await probeRuntime(entry.descriptor)] as const),
495
- ))
496
-
497
- const sharedFindings = (references: ResourceReference[], probe: SharedRuntimeProbe): string[] => {
498
- const findings: string[] = []
499
- if (!probe.healthy) findings.push(`control-plane-probe-failed:${probe.error || 'unknown'}`)
500
- if (references.some((ref) => ref.ownerState === 'unowned')) findings.push('unowned-loaded-thread')
501
- if (references.some((ref) => ref.ownerState === 'ambiguous')) findings.push('ambiguous-loaded-thread-owner')
502
- if (references.some((ref) => ref.turnPresence === 'unknown')) findings.push('turn-presence-unknown')
503
- if (references.some((ref) => ref.referenceState !== 'loaded')) findings.push('record-without-live-thread')
504
- return findings
505
- }
506
- const atomicJson = (path: string, value: unknown) => {
507
- mkdirSync(dirname(path), { recursive: true })
508
- const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`
509
- writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 })
510
- renameSync(tmp, path)
511
- }
512
-
513
- const memInfo = (procRoot = '/proc') => {
514
- const values: Record<string, number> = {}
515
- try {
516
- for (const line of readFileSync(join(procRoot, 'meminfo'), 'utf8').split('\n')) {
517
- const m = line.match(/^(\w+):\s+(\d+)/)
518
- if (m) values[m[1]] = Number(m[2])
519
- }
520
- } catch { /* unavailable below */ }
521
- const total = values.MemTotal
522
- const available = values.MemAvailable
523
- const swapTotal = values.SwapTotal
524
- const swapFree = values.SwapFree
525
- return {
526
- memoryTotalMiB: total ? mib(total) : null,
527
- memoryUsedMiB: total && available !== undefined ? mib(total - available) : null,
528
- memoryAvailableMiB: available !== undefined ? mib(available) : null,
529
- swapTotalMiB: swapTotal !== undefined ? mib(swapTotal) : null,
530
- swapUsedMiB: swapTotal !== undefined && swapFree !== undefined ? mib(swapTotal - swapFree) : null,
531
- }
532
- }
533
-
534
- export async function collectResourceReport(opts: { procRoot?: string; persist?: boolean } = {}): Promise<ResourceReport> {
535
- const root = repoRoot()
536
- const budgets = resourceBudgets(root)
537
- if (platform() !== 'linux' && !opts.procRoot) {
538
- const publicRecords = publicRecordInventory()
539
- const recs = publicRecords.records
540
- const owners: ResourceOwner[] = []
541
- const shared = sharedDescriptors(recs)
542
- const probes = await probeShared(shared)
543
- for (const [key, entry] of shared) {
544
- const probe = probes.get(key)!
545
- const refs = projectReferences(entry, probe, publicRecords.byId)
546
- owners.push({ kind: 'shared-runtime', id: key, label: entry.descriptor.label, processes: [], rssMiB: 0, pssMiB: null, cpuPercent: 0, budget: { rssMiB: null, idleCpuPercent: null }, controlPlane: { healthy: probe.healthy, refCount: probe.healthy ? probe.references.length : null, ...(probe.error ? { error: probe.error } : {}) }, references: refs, findings: sharedFindings(refs, probe) })
547
- }
548
- for (const entry of publicRecords.entries) if (entry.kind === 'corrupt') owners.push({
549
- kind: 'session', id: entry.sessionId, label: `session ${entry.sessionId.slice(0, 8)}`, status: 'corrupt', liveness: 'unknown',
550
- processes: [], rssMiB: 0, pssMiB: null, cpuPercent: 0,
551
- budget: { rssMiB: budgets.sessionRssMiB, idleCpuPercent: budgets.idleCpuPercent },
552
- findings: [`session-record-corrupt:${entry.error}`], reclaim: { eligible: false, reason: 'session record is corrupt; ownership and lifecycle are unknown' },
553
- })
554
- owners.push(...unresolvedHarnessOwners(recs, budgets, (sessionId) => owners.some((owner) => owner.kind === 'session' && owner.id === sessionId)))
555
- const report: ResourceReport = { version: 1, measuredAt: new Date().toISOString(), projectRoot: root, platform: platform(), available: false, unavailableReason: `host process metrics are unavailable on ${platform()}; shared runtime references remain visible`, host: { memoryTotalMiB: null, memoryUsedMiB: null, memoryAvailableMiB: null, swapTotalMiB: null, swapUsedMiB: null, cpuPercent: null }, budgets, owners, totals: { rssMiB: 0, pssMiB: null, cpuPercent: 0 }, findings: owners.reduce((n, o) => n + o.findings.length, 0) }
556
- if (opts.persist !== false) atomicJson(join(runtimeRoot(), 'resource-report.json'), report)
557
- return report
558
- }
559
-
560
- const procRoot = opts.procRoot ?? '/proc'
561
- const first = procSnapshot(procRoot)
562
- const hostBefore = hostCpuSnapshot(procRoot)
563
- const cpuBefore = hostBefore?.total ?? totalCpuTicks(procRoot)
564
- await sleep(budgets.sampleMs)
565
- const second = procSnapshot(procRoot, false)
566
- const hostAfter = hostCpuSnapshot(procRoot)
567
- const cpuAfter = hostAfter?.total ?? totalCpuTicks(procRoot)
568
- const totalDelta = Math.max(1, cpuAfter - cpuBefore)
569
- for (const p of first.values()) {
570
- const next = second.get(p.pid)
571
- if (next && next.startToken === p.startToken) p.cpuPercent = Math.max(0, (next.ticks - p.ticks) / totalDelta * cpus().length * 100)
572
- }
573
-
574
- const inv = buildInventory(first)
575
- const sharedProbes = await probeShared(inv.shared)
576
- const grouped = new Map<string, number[]>()
577
- for (const [pid, owner] of inv.ownership) grouped.set(owner, [...(grouped.get(owner) ?? []), pid])
578
- const bySession = new Map(inv.recs.map((rec) => [rec.session_id, rec]))
579
- const owners: ResourceOwner[] = []
580
-
581
- for (const [owner, ids] of grouped) {
582
- const processes = ownerProcesses(ids, first)
583
- const totals = ownerTotals(processes)
584
- let kind: ResourceOwner['kind'] = 'unattributed'
585
- let id = owner
586
- let label = owner
587
- let status: string | undefined
588
- let lifecycle: Pick<ResourceOwner, 'liveness' | 'proposal' | 'note' | 'worktreePath' | 'branch' | 'stopped' | 'archived'> | undefined
589
- let rssBudget: number | null = null
590
- let idleBudget: number | null = null
591
- let references: ResourceReference[] | undefined
592
- let controlPlane: ResourceOwner['controlPlane']
593
- const findings: string[] = []
594
- let reclaim: ResourceOwner['reclaim']
595
-
596
- if (owner.startsWith('session:')) {
597
- kind = 'session'; id = owner.slice(8); label = `session ${id.slice(0, 8)}`
598
- const publicEntry = inv.publicById.get(id)
599
- const rec = bySession.get(id); status = publicEntry?.kind === 'corrupt' ? 'corrupt' : rec?.status; rssBudget = budgets.sessionRssMiB; idleBudget = budgets.idleCpuPercent
600
- if (rec && publicEntry?.kind === 'ok') lifecycle = {
601
- ...(publicEntry.liveness ? { liveness: publicEntry.liveness } : {}),
602
- proposal: rec.proposal, note: rec.note, worktreePath: rec.worktree_path, branch: rec.branch, stopped: rec.stopped, archived: rec.archived,
603
- }
604
- else if (publicEntry?.kind === 'corrupt') {
605
- lifecycle = { liveness: 'unknown' }
606
- findings.push(`session-record-corrupt:${publicEntry.error}`)
607
- }
608
- if (rec?.archived) findings.push('archived-runtime-hazard:leaf-still-resident')
609
- if (totals.rssMiB > rssBudget) findings.push(`rss-over-budget:${Math.round((totals.rssMiB - rssBudget) * 10) / 10}MiB`)
610
- if (status !== 'active' && status !== 'queued' && totals.cpuPercent > idleBudget) findings.push(`idle-cpu-over-budget:${Math.round((totals.cpuPercent - idleBudget) * 10) / 10}%`)
611
- // The stop guard resolves the record's harness fail-loud, which is right for a stop REQUEST and wrong
612
- // here: this sweep reads rows nobody named. An unresolvable harness makes stop safety unprovable, so it
613
- // is reported and reclaim stays closed — never a resolvable-looking zero, never a lost report.
614
- const unresolved = rec ? unresolvableHarness(rec) : null
615
- if (unresolved) findings.push(`harness-unresolved:${unresolved}`)
616
- const stopBlocker = rec && !unresolved ? await sessionStopBlocker(id, rec.harness || defaultHarness.id, inv.recs, sharedProbes) : null
617
- if (unresolved) {
618
- reclaim = { eligible: false, reason: `harness '${unresolved}' is not resolvable by this registry; shared-runtime stop safety cannot be proven` }
619
- } else if (terminal(rec) && totals.cpuPercent <= idleBudget && !stopBlocker) {
620
- reclaim = { eligible: true, reason: `terminal lifecycle ${rec!.status}/${rec!.proposal}; a future exact action must revalidate every fact` }
621
- } else reclaim = { eligible: false, reason: stopBlocker ? `shared runtime unsafe: ${stopBlocker}` : terminal(rec) ? 'terminal record is still consuming CPU; liveness contradicts safe retirement' : 'owner is not terminal; budget age/status alone never authorizes stop' }
622
- } else if (owner.startsWith('shared:')) {
623
- kind = 'shared-runtime'; id = owner.slice(7)
624
- const shared = inv.shared.get(id)!; label = shared.descriptor.label; rssBudget = budgets.backendRssMiB; idleBudget = budgets.idleCpuPercent
625
- const probe = sharedProbes.get(id)!
626
- references = projectReferences(shared, probe, inv.publicById)
627
- if (references.some((reference) => reference.sessionId && bySession.get(reference.sessionId)?.archived)) findings.push('archived-runtime-hazard:loaded-thread')
628
- controlPlane = { healthy: probe.healthy, refCount: probe.healthy ? probe.references.length : null, ...(probe.error ? { error: probe.error } : {}) }
629
- if (totals.rssMiB > rssBudget) findings.push(`rss-over-budget:${Math.round((totals.rssMiB - rssBudget) * 10) / 10}MiB`)
630
- const protectedReferences = references.filter((reference) => reference.protectsControlPlane)
631
- const turnPresenceKnown = !references.some((reference) => reference.turnPresence === 'unknown')
632
- if (probe.healthy && turnPresenceKnown && !references.some((reference) => reference.turnPresence === 'active') && totals.cpuPercent > idleBudget) findings.push(`idle-cpu-over-budget:${Math.round((totals.cpuPercent - idleBudget) * 10) / 10}%`)
633
- if (probe.healthy && protectedReferences.length === 0) findings.push('orphan-shared-runtime:no-live-references')
634
- findings.push(...sharedFindings(references, probe))
635
- if (ids.some((pid) => sessionIdentityEnvVars().some((key) => !!first.get(pid)?.env[key]))) findings.push('identity-leak:project-control-plane-carries-session-id')
636
- reclaim = { eligible: false, reason: !probe.healthy ? 'live reference count unknown; adapter/project teardown only' : protectedReferences.length ? `protected by ${protectedReferences.length} loaded/active thread reference(s)` : 'shared runtime teardown belongs to its adapter/project owner, never session stop' }
637
- } else if (owner.startsWith('backend:')) {
638
- kind = 'backend'; id = owner.slice(8); label = `backend ${id}`; rssBudget = budgets.backendRssMiB; idleBudget = budgets.idleCpuPercent
639
- if (totals.rssMiB > rssBudget) findings.push(`rss-over-budget:${Math.round((totals.rssMiB - rssBudget) * 10) / 10}MiB`)
640
- if (totals.cpuPercent > idleBudget) findings.push(`idle-cpu-over-budget:${Math.round((totals.cpuPercent - idleBudget) * 10) / 10}%`)
641
- if (ids.some((pid) => sessionIdentityEnvVars().some((key) => !!first.get(pid)?.env[key]))) findings.push('identity-leak:project-control-plane-carries-session-id')
642
- reclaim = { eligible: false, reason: 'current endpoint owner; backend teardown is not a session stop' }
643
- } else if (owner.startsWith('orphan:')) {
644
- kind = 'orphan'; id = owner.slice(7); label = owner; findings.push('orphan:owner-record-absent')
645
- if (id.startsWith('session:')) {
646
- const exactOwner = id.slice(8)
647
- id = exactOwner
648
- label = `orphan session ${exactOwner}`
649
- reclaim = { eligible: true, reason: 'process carries this project and session identity, but the owner record is absent' }
650
- } else if (id.startsWith('backend:')) {
651
- const exactOwner = id.slice(8)
652
- id = exactOwner
653
- label = `orphan backend ${exactOwner}`
654
- rssBudget = budgets.backendRssMiB
655
- idleBudget = budgets.idleCpuPercent
656
- if (totals.rssMiB > rssBudget) findings.push(`rss-over-budget:${Math.round((totals.rssMiB - rssBudget) * 10) / 10}MiB`)
657
- if (totals.cpuPercent > idleBudget) findings.push(`idle-cpu-over-budget:${Math.round((totals.cpuPercent - idleBudget) * 10) / 10}%`)
658
- reclaim = { eligible: false, reason: 'superseded backend generation remains owned by backend supervisor teardown, never session stop' }
659
- } else reclaim = { eligible: false, reason: 'runtime teardown belongs to its registered owner' }
660
- } else {
661
- findings.push('unattributed:project-process-without-owner')
662
- reclaim = { eligible: false, reason: 'ownership is insufficient; reporting stays read-only' }
663
- }
664
- owners.push({ kind, id, label, ...(status ? { status } : {}), ...(lifecycle ?? {}), processes, ...totals, budget: { rssMiB: rssBudget, idleCpuPercent: idleBudget }, ...(controlPlane ? { controlPlane } : {}), ...(references ? { references } : {}), findings, ...(reclaim ? { reclaim } : {}) })
665
- }
666
-
667
- // A corrupt record stays a public owner even without a currently attributable process. Its absence from a
668
- // process-derived group cannot turn unknown lifecycle/ownership into disappearance.
669
- for (const entry of inv.publicById.values()) if (entry.kind === 'corrupt'
670
- && !owners.some((owner) => owner.kind === 'session' && owner.id === entry.sessionId)) owners.push({
671
- kind: 'session', id: entry.sessionId, label: `session ${entry.sessionId.slice(0, 8)}`, status: 'corrupt', liveness: 'unknown',
672
- processes: [], rssMiB: 0, pssMiB: null, cpuPercent: 0,
673
- budget: { rssMiB: budgets.sessionRssMiB, idleCpuPercent: budgets.idleCpuPercent },
674
- findings: [`session-record-corrupt:${entry.error}`], reclaim: { eligible: false, reason: 'session record is corrupt; ownership and lifecycle are unknown' },
675
- })
676
-
677
- owners.push(...unresolvedHarnessOwners(inv.recs, budgets, (sessionId) => owners.some((owner) => owner.kind === 'session' && owner.id === sessionId)))
678
-
679
- // A referenced shared runtime with no readable process is still operationally important: keep its live or
680
- // unknown refcount visible instead of silently omitting it from a process-derived report.
681
- for (const [key, shared] of inv.shared) if (!owners.some((o) => o.kind === 'shared-runtime' && o.id === key)) {
682
- const probe = sharedProbes.get(key)!
683
- const references = projectReferences(shared, probe, inv.publicById)
684
- const protectedReferences = references.filter((reference) => reference.protectsControlPlane)
685
- owners.push({ kind: 'shared-runtime', id: key, label: shared.descriptor.label, processes: [], rssMiB: 0, pssMiB: null, cpuPercent: 0, budget: { rssMiB: budgets.backendRssMiB, idleCpuPercent: null }, controlPlane: { healthy: probe.healthy, refCount: probe.healthy ? probe.references.length : null, ...(probe.error ? { error: probe.error } : {}) }, references, findings: ['runtime-process-unavailable', ...sharedFindings(references, probe)], reclaim: { eligible: false, reason: !probe.healthy ? 'live reference count unknown; adapter/project teardown only' : protectedReferences.length ? `protected by ${protectedReferences.length} loaded/active thread reference(s)` : 'adapter/project teardown only' } })
686
- }
687
-
688
- owners.sort((a, b) => b.rssMiB - a.rssMiB || a.id.localeCompare(b.id))
689
- const rssMiB = Math.round(owners.reduce((n, o) => n + o.rssMiB, 0) * 10) / 10
690
- const pssMiB = owners.every((o) => o.pssMiB !== null) ? Math.round(owners.reduce((n, o) => n + (o.pssMiB ?? 0), 0) * 10) / 10 : null
691
- const cpuPercent = Math.round(owners.reduce((n, o) => n + o.cpuPercent, 0) * 10) / 10
692
- const report: ResourceReport = {
693
- version: 1,
694
- measuredAt: new Date().toISOString(),
695
- projectRoot: root,
696
- platform: platform(),
697
- available: true,
698
- host: {
699
- ...memInfo(procRoot),
700
- cpuPercent: hostBefore && hostAfter && hostAfter.total > hostBefore.total
701
- ? Math.round((1 - (hostAfter.idle - hostBefore.idle) / (hostAfter.total - hostBefore.total)) * 1000) / 10
702
- : null,
703
- },
704
- budgets,
705
- owners,
706
- totals: { rssMiB, pssMiB, cpuPercent },
707
- findings: owners.reduce((n, owner) => n + owner.findings.length, 0),
708
- }
709
- if (opts.persist !== false) atomicJson(join(runtimeRoot(), 'resource-report.json'), report)
710
- return report
711
- }
712
-
713
- export async function assertSessionStopSafe(
714
- id: string,
715
- rec: (HarnessLivenessRecord & { harness?: string }) | null,
716
- opts: { coldReceipt?: unknown } = {},
717
- ): Promise<void> {
718
- if (!rec) throw new ResourceConflict(`refusing to stop ${id}: no readable session record proves the adapter or leaf owner`)
719
- const blocker = await sessionStopBlocker(id, rec.harness || null, rawRecords(), undefined, opts)
720
- if (blocker) throw new ResourceConflict(`refusing to stop ${id}: ${blocker}`)
721
- }
722
-
723
- export function formatResourceReport(report: ResourceReport): string {
724
- const host = report.available
725
- ? `host memory ${report.host.memoryUsedMiB}/${report.host.memoryTotalMiB} MiB, swap ${report.host.swapUsedMiB}/${report.host.swapTotalMiB} MiB`
726
- : `host metrics unavailable: ${report.unavailableReason}`
727
- const lines = [`resources @ ${report.measuredAt}`, ` ${host}`, ` attributed PSS ${report.totals.pssMiB ?? 'unavailable'} MiB (RSS ${report.totals.rssMiB} MiB), findings ${report.findings}`]
728
- for (const owner of report.owners) {
729
- const lifecycle = owner.status ? ` state=${owner.status}/${owner.proposal ?? '-'} branch=${owner.branch ?? '-'}` : ''
730
- const refs = owner.references
731
- ? ` refs=${owner.controlPlane?.refCount ?? '?'}/${owner.references.length} active=${owner.references.filter((r) => r.turnPresence === 'active').length} unknown=${owner.references.filter((r) => r.turnPresence === 'unknown').length}`
732
- : ''
733
- lines.push(` ${owner.kind.padEnd(14)} ${owner.id} rss=${owner.rssMiB}MiB pss=${owner.pssMiB ?? '?'}MiB cpu=${owner.cpuPercent}%${lifecycle}${refs}${owner.findings.length ? ` ! ${owner.findings.join(',')}` : ''}`)
734
- }
735
- return lines.join('\n')
736
- }
737
-
738
- let monitorStarted = false
739
- export function startResourceMonitor(): void {
740
- if (monitorStarted) return
741
- monitorStarted = true
742
- const interval = resourceBudgets().reportIntervalMs
743
- let last = new Set<string>()
744
- let running = false
745
- const sample = async () => {
746
- if (running) return
747
- running = true
748
- try {
749
- const report = await collectResourceReport()
750
- const stableFinding = (finding: string) => finding.startsWith('rss-over-budget:') ? 'rss-over-budget'
751
- : finding.startsWith('idle-cpu-over-budget:') ? 'idle-cpu-over-budget'
752
- : finding
753
- const next = new Set(report.owners.flatMap((owner) => owner.findings.map((finding) => `${owner.kind}:${owner.id}:${stableFinding(finding)}`)))
754
- for (const finding of next) if (!last.has(finding)) console.warn(`[resources] entered ${finding}`)
755
- for (const finding of last) if (!next.has(finding)) console.warn(`[resources] cleared ${finding}`)
756
- last = next
757
- } catch (e) { console.error(`[resources] sample failed: ${(e as Error).message}`) }
758
- finally { running = false }
759
- }
760
- setTimeout(() => void sample(), 5000).unref()
761
- setInterval(() => void sample(), interval).unref()
762
- }