spexcode 0.6.1 → 0.6.5

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