spexcode 0.6.1 → 0.6.5

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