@bjoernrochel/sevro 1.0.0 → 1.1.1

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 (405) hide show
  1. package/CONTRIBUTING.md +34 -4
  2. package/README.md +7 -6
  3. package/dist/examples/basic/graded.json +13 -0
  4. package/dist/examples/basic/host.js +9 -0
  5. package/dist/examples/basic/host.js.map +1 -0
  6. package/dist/examples/basic/prompt-only.json +7 -0
  7. package/dist/examples/recipes/candidate.js +23 -0
  8. package/dist/examples/recipes/candidate.js.map +1 -0
  9. package/dist/examples/recipes/cases/coding-change.json +21 -0
  10. package/dist/examples/recipes/cases/host-tools.json +21 -0
  11. package/dist/examples/recipes/cases/mise.json +21 -0
  12. package/dist/examples/recipes/cases/node.json +21 -0
  13. package/dist/examples/recipes/cases/python-uv.json +21 -0
  14. package/dist/examples/recipes/cases/semantic.json +17 -0
  15. package/dist/examples/recipes/coding-change/check.js +16 -0
  16. package/dist/examples/recipes/coding-change/check.js.map +1 -0
  17. package/dist/examples/recipes/executable.js +20 -0
  18. package/dist/examples/recipes/executable.js.map +1 -0
  19. package/dist/examples/recipes/extension-artifacts.js +56 -0
  20. package/dist/examples/recipes/extension-artifacts.js.map +1 -0
  21. package/dist/examples/recipes/extension-methods.js +167 -0
  22. package/dist/examples/recipes/extension-methods.js.map +1 -0
  23. package/dist/examples/recipes/extension.js +28 -0
  24. package/dist/examples/recipes/extension.js.map +1 -0
  25. package/dist/examples/recipes/modules.js +9 -0
  26. package/dist/examples/recipes/modules.js.map +1 -0
  27. package/dist/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
  28. package/dist/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
  29. package/dist/examples/recipes/repository-skill/README.md +3 -0
  30. package/dist/examples/recipes/run-inputs.js +144 -0
  31. package/dist/examples/recipes/run-inputs.js.map +1 -0
  32. package/dist/examples/recipes/run-options.js +103 -0
  33. package/dist/examples/recipes/run-options.js.map +1 -0
  34. package/dist/examples/recipes/run.js +62 -0
  35. package/dist/examples/recipes/run.js.map +1 -0
  36. package/dist/examples/recipes/runtime/empty.json +1 -0
  37. package/dist/examples/recipes/runtime/goals.json +1 -0
  38. package/dist/examples/recipes/runtime/mise.json +6 -0
  39. package/dist/examples/recipes/runtime/path.json +1 -0
  40. package/dist/examples/recipes/runtime/plugins.json +4 -0
  41. package/dist/examples/recipes/runtime/python-uv.json +10 -0
  42. package/dist/examples/recipes/semantic-judge.js +27 -0
  43. package/dist/examples/recipes/semantic-judge.js.map +1 -0
  44. package/dist/examples/templates/coding/adapter.js +14 -0
  45. package/dist/examples/templates/coding/adapter.js.map +1 -0
  46. package/dist/examples/templates/coding/case.json +15 -0
  47. package/dist/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
  48. package/dist/examples/templates/repository-skill/extension-data.js +61 -0
  49. package/dist/examples/templates/repository-skill/extension-data.js.map +1 -0
  50. package/dist/examples/templates/repository-skill/extension.js +29 -0
  51. package/dist/examples/templates/repository-skill/extension.js.map +1 -0
  52. package/dist/examples/templates/response/adapter.js +13 -0
  53. package/dist/examples/templates/response/adapter.js.map +1 -0
  54. package/dist/examples/templates/response/case.json +13 -0
  55. package/dist/src/advisory.js +86 -0
  56. package/dist/src/advisory.js.map +1 -0
  57. package/dist/src/candidate-artifacts.js +139 -0
  58. package/dist/src/candidate-artifacts.js.map +1 -0
  59. package/dist/src/candidate-codex-state.js +110 -0
  60. package/dist/src/candidate-codex-state.js.map +1 -0
  61. package/dist/src/case-validation.js +92 -0
  62. package/dist/src/case-validation.js.map +1 -0
  63. package/dist/src/cli/cancellation.js +21 -0
  64. package/dist/src/cli/cancellation.js.map +1 -0
  65. package/dist/src/cli/command.js +60 -0
  66. package/dist/src/cli/command.js.map +1 -0
  67. package/dist/src/cli/completion.js +14 -0
  68. package/dist/src/cli/completion.js.map +1 -0
  69. package/dist/src/cli/demo.js +45 -0
  70. package/dist/src/cli/demo.js.map +1 -0
  71. package/dist/src/cli/evaluation.js +40 -0
  72. package/dist/src/cli/evaluation.js.map +1 -0
  73. package/dist/src/cli/extension.js +54 -0
  74. package/dist/src/cli/extension.js.map +1 -0
  75. package/dist/src/cli/help.js +190 -0
  76. package/dist/src/cli/help.js.map +1 -0
  77. package/dist/src/cli/hosts.js +33 -0
  78. package/dist/src/cli/hosts.js.map +1 -0
  79. package/dist/src/cli/init-settings.js +29 -0
  80. package/dist/src/cli/init-settings.js.map +1 -0
  81. package/dist/src/cli/init.js +82 -0
  82. package/dist/src/cli/init.js.map +1 -0
  83. package/dist/src/cli/invocation.js +431 -0
  84. package/dist/src/cli/invocation.js.map +1 -0
  85. package/dist/src/cli/loading.js +110 -0
  86. package/dist/src/cli/loading.js.map +1 -0
  87. package/dist/src/cli/preparation.js +88 -0
  88. package/dist/src/cli/preparation.js.map +1 -0
  89. package/dist/src/cli/presentation.js +61 -0
  90. package/dist/src/cli/presentation.js.map +1 -0
  91. package/dist/src/cli/progress.js +5 -0
  92. package/dist/src/cli/progress.js.map +1 -0
  93. package/dist/src/cli/run-manifest.js +108 -0
  94. package/dist/src/cli/run-manifest.js.map +1 -0
  95. package/dist/src/cli/run-options.js +367 -0
  96. package/dist/src/cli/run-options.js.map +1 -0
  97. package/dist/src/cli/run-overrides.js +51 -0
  98. package/dist/src/cli/run-overrides.js.map +1 -0
  99. package/dist/src/cli/settings.js +127 -0
  100. package/dist/src/cli/settings.js.map +1 -0
  101. package/dist/src/cli/summary.js +28 -0
  102. package/dist/src/cli/summary.js.map +1 -0
  103. package/dist/src/cli.js +20 -0
  104. package/dist/src/cli.js.map +1 -0
  105. package/dist/src/contract-types.js +2 -0
  106. package/dist/src/contract-types.js.map +1 -0
  107. package/dist/src/engine.js +4 -0
  108. package/dist/src/engine.js.map +1 -0
  109. package/dist/src/evaluation/advisory.js +85 -0
  110. package/dist/src/evaluation/advisory.js.map +1 -0
  111. package/dist/src/evaluation/check-plan.js +292 -0
  112. package/dist/src/evaluation/check-plan.js.map +1 -0
  113. package/dist/src/evaluation/collection.js +61 -0
  114. package/dist/src/evaluation/collection.js.map +1 -0
  115. package/dist/src/evaluation/context.js +2 -0
  116. package/dist/src/evaluation/context.js.map +1 -0
  117. package/dist/src/evaluation/grading-host.js +28 -0
  118. package/dist/src/evaluation/grading-host.js.map +1 -0
  119. package/dist/src/evaluation/grading.js +154 -0
  120. package/dist/src/evaluation/grading.js.map +1 -0
  121. package/dist/src/evaluation/invocation-plan.js +312 -0
  122. package/dist/src/evaluation/invocation-plan.js.map +1 -0
  123. package/dist/src/evaluation/plan.js +376 -0
  124. package/dist/src/evaluation/plan.js.map +1 -0
  125. package/dist/src/evaluation/progress.js +14 -0
  126. package/dist/src/evaluation/progress.js.map +1 -0
  127. package/dist/src/evaluation/resources.js +159 -0
  128. package/dist/src/evaluation/resources.js.map +1 -0
  129. package/dist/src/evaluation/run.js +148 -0
  130. package/dist/src/evaluation/run.js.map +1 -0
  131. package/dist/src/evaluation/scheduler.js +27 -0
  132. package/dist/src/evaluation/scheduler.js.map +1 -0
  133. package/dist/src/evaluation/semantic.js +183 -0
  134. package/dist/src/evaluation/semantic.js.map +1 -0
  135. package/dist/src/evaluation/trial.js +593 -0
  136. package/dist/src/evaluation/trial.js.map +1 -0
  137. package/dist/src/evaluation/types.js +20 -0
  138. package/dist/src/evaluation/types.js.map +1 -0
  139. package/dist/src/evidence/host-results.js +67 -0
  140. package/dist/src/evidence/host-results.js.map +1 -0
  141. package/dist/src/evidence/retained-artifacts.js +17 -0
  142. package/dist/src/evidence/retained-artifacts.js.map +1 -0
  143. package/dist/src/evidence/trial-writer.js +131 -0
  144. package/dist/src/evidence/trial-writer.js.map +1 -0
  145. package/dist/src/evidence/usage.js +14 -0
  146. package/dist/src/evidence/usage.js.map +1 -0
  147. package/dist/src/executable.js +20 -0
  148. package/dist/src/executable.js.map +1 -0
  149. package/dist/src/extension-client.js +212 -0
  150. package/dist/src/extension-client.js.map +1 -0
  151. package/dist/src/extension-session.js +236 -0
  152. package/dist/src/extension-session.js.map +1 -0
  153. package/dist/src/fixtures/advisory.js +206 -0
  154. package/dist/src/fixtures/advisory.js.map +1 -0
  155. package/dist/src/fixtures/bin.js +41 -0
  156. package/dist/src/fixtures/bin.js.map +1 -0
  157. package/dist/src/fixtures/cleanup.js +43 -0
  158. package/dist/src/fixtures/cleanup.js.map +1 -0
  159. package/dist/src/fixtures/create.js +94 -0
  160. package/dist/src/fixtures/create.js.map +1 -0
  161. package/dist/src/fixtures/generated.js +181 -0
  162. package/dist/src/fixtures/generated.js.map +1 -0
  163. package/dist/src/fixtures/git-hooks.js +16 -0
  164. package/dist/src/fixtures/git-hooks.js.map +1 -0
  165. package/dist/src/fixtures/preparation.js +201 -0
  166. package/dist/src/fixtures/preparation.js.map +1 -0
  167. package/dist/src/fixtures/repository.js +255 -0
  168. package/dist/src/fixtures/repository.js.map +1 -0
  169. package/dist/src/fixtures/setup.js +175 -0
  170. package/dist/src/fixtures/setup.js.map +1 -0
  171. package/dist/src/fixtures/tools.js +57 -0
  172. package/dist/src/fixtures/tools.js.map +1 -0
  173. package/dist/src/fixtures/workspace-state.js +155 -0
  174. package/dist/src/fixtures/workspace-state.js.map +1 -0
  175. package/dist/src/generated/cli-result.cjs +2 -0
  176. package/dist/src/generated/extension.cjs +2 -0
  177. package/dist/src/generated/report.cjs +2 -0
  178. package/dist/src/generated/run-evidence.cjs +2 -0
  179. package/dist/src/generated/runtime.cjs +2 -0
  180. package/dist/src/git.js +72 -0
  181. package/dist/src/git.js.map +1 -0
  182. package/dist/src/graders/artifact.js +68 -0
  183. package/dist/src/graders/artifact.js.map +1 -0
  184. package/dist/src/graders/git-head.js +112 -0
  185. package/dist/src/graders/git-head.js.map +1 -0
  186. package/dist/src/graders/output.js +345 -0
  187. package/dist/src/graders/output.js.map +1 -0
  188. package/dist/src/graders/semantic.js +132 -0
  189. package/dist/src/graders/semantic.js.map +1 -0
  190. package/dist/src/graders/shell.js +321 -0
  191. package/dist/src/graders/shell.js.map +1 -0
  192. package/dist/src/host-diagnostic-redaction.js +52 -0
  193. package/dist/src/host-diagnostic-redaction.js.map +1 -0
  194. package/dist/src/host-diagnostics.js +248 -0
  195. package/dist/src/host-diagnostics.js.map +1 -0
  196. package/dist/src/hosts/claude-continuation.js +89 -0
  197. package/dist/src/hosts/claude-continuation.js.map +1 -0
  198. package/dist/src/hosts/claude-credential.js +121 -0
  199. package/dist/src/hosts/claude-credential.js.map +1 -0
  200. package/dist/src/hosts/claude-events.js +136 -0
  201. package/dist/src/hosts/claude-events.js.map +1 -0
  202. package/dist/src/hosts/claude-keychain.js +100 -0
  203. package/dist/src/hosts/claude-keychain.js.map +1 -0
  204. package/dist/src/hosts/claude-login-recovery.js +67 -0
  205. package/dist/src/hosts/claude-login-recovery.js.map +1 -0
  206. package/dist/src/hosts/claude-login-refresh.js +146 -0
  207. package/dist/src/hosts/claude-login-refresh.js.map +1 -0
  208. package/dist/src/hosts/claude-login-store.js +124 -0
  209. package/dist/src/hosts/claude-login-store.js.map +1 -0
  210. package/dist/src/hosts/claude-login.js +74 -0
  211. package/dist/src/hosts/claude-login.js.map +1 -0
  212. package/dist/src/hosts/claude-native-goal.js +138 -0
  213. package/dist/src/hosts/claude-native-goal.js.map +1 -0
  214. package/dist/src/hosts/claude-process.js +10 -0
  215. package/dist/src/hosts/claude-process.js.map +1 -0
  216. package/dist/src/hosts/claude-refresh-lock.js +76 -0
  217. package/dist/src/hosts/claude-refresh-lock.js.map +1 -0
  218. package/dist/src/hosts/claude-repository-invocation.js +229 -0
  219. package/dist/src/hosts/claude-repository-invocation.js.map +1 -0
  220. package/dist/src/hosts/claude-settings.js +158 -0
  221. package/dist/src/hosts/claude-settings.js.map +1 -0
  222. package/dist/src/hosts/claude-skill-activations.js +131 -0
  223. package/dist/src/hosts/claude-skill-activations.js.map +1 -0
  224. package/dist/src/hosts/claude-state.js +237 -0
  225. package/dist/src/hosts/claude-state.js.map +1 -0
  226. package/dist/src/hosts/claude-tool-calls.js +150 -0
  227. package/dist/src/hosts/claude-tool-calls.js.map +1 -0
  228. package/dist/src/hosts/claude.js +373 -0
  229. package/dist/src/hosts/claude.js.map +1 -0
  230. package/dist/src/hosts/codex-app-server.js +764 -0
  231. package/dist/src/hosts/codex-app-server.js.map +1 -0
  232. package/dist/src/hosts/codex-configuration.js +91 -0
  233. package/dist/src/hosts/codex-configuration.js.map +1 -0
  234. package/dist/src/hosts/codex-events.js +104 -0
  235. package/dist/src/hosts/codex-events.js.map +1 -0
  236. package/dist/src/hosts/codex-exec-controls.js +434 -0
  237. package/dist/src/hosts/codex-exec-controls.js.map +1 -0
  238. package/dist/src/hosts/codex-invocation.js +54 -0
  239. package/dist/src/hosts/codex-invocation.js.map +1 -0
  240. package/dist/src/hosts/codex-marketplace.js +185 -0
  241. package/dist/src/hosts/codex-marketplace.js.map +1 -0
  242. package/dist/src/hosts/codex-native-calls.js +516 -0
  243. package/dist/src/hosts/codex-native-calls.js.map +1 -0
  244. package/dist/src/hosts/codex-process.js +10 -0
  245. package/dist/src/hosts/codex-process.js.map +1 -0
  246. package/dist/src/hosts/codex-profile.js +206 -0
  247. package/dist/src/hosts/codex-profile.js.map +1 -0
  248. package/dist/src/hosts/codex-session-files.js +89 -0
  249. package/dist/src/hosts/codex-session-files.js.map +1 -0
  250. package/dist/src/hosts/codex-state.js +310 -0
  251. package/dist/src/hosts/codex-state.js.map +1 -0
  252. package/dist/src/hosts/codex.js +527 -0
  253. package/dist/src/hosts/codex.js.map +1 -0
  254. package/dist/src/hosts/isolation-common.js +43 -0
  255. package/dist/src/hosts/isolation-common.js.map +1 -0
  256. package/dist/src/hosts/isolation-roots.js +64 -0
  257. package/dist/src/hosts/isolation-roots.js.map +1 -0
  258. package/dist/src/hosts/isolation.js +12 -0
  259. package/dist/src/hosts/isolation.js.map +1 -0
  260. package/dist/src/hosts/linux-sandbox.js +127 -0
  261. package/dist/src/hosts/linux-sandbox.js.map +1 -0
  262. package/dist/src/hosts/mac-sandbox.js +137 -0
  263. package/dist/src/hosts/mac-sandbox.js.map +1 -0
  264. package/dist/src/hosts/native-controls.js +151 -0
  265. package/dist/src/hosts/native-controls.js.map +1 -0
  266. package/dist/src/hosts/native-request.js +33 -0
  267. package/dist/src/hosts/native-request.js.map +1 -0
  268. package/dist/src/hosts/process-lifecycle.js +167 -0
  269. package/dist/src/hosts/process-lifecycle.js.map +1 -0
  270. package/dist/src/hosts/protected-worktrees.js +81 -0
  271. package/dist/src/hosts/protected-worktrees.js.map +1 -0
  272. package/dist/src/hosts/skill-hook-catalog.js +100 -0
  273. package/dist/src/hosts/skill-hook-catalog.js.map +1 -0
  274. package/dist/src/hosts/skill-hook-handler.js +228 -0
  275. package/dist/src/hosts/skill-hook-handler.js.map +1 -0
  276. package/dist/src/hosts/skill-hooks.js +423 -0
  277. package/dist/src/hosts/skill-hooks.js.map +1 -0
  278. package/dist/src/hosts/workspace-fingerprint.js +72 -0
  279. package/dist/src/hosts/workspace-fingerprint.js.map +1 -0
  280. package/dist/src/identity.js +167 -0
  281. package/dist/src/identity.js.map +1 -0
  282. package/dist/src/instrumentation.js +83 -0
  283. package/dist/src/instrumentation.js.map +1 -0
  284. package/dist/src/native-transcript-state.js +85 -0
  285. package/dist/src/native-transcript-state.js.map +1 -0
  286. package/dist/src/native-transcript-view.js +65 -0
  287. package/dist/src/native-transcript-view.js.map +1 -0
  288. package/dist/src/native-transcripts.js +208 -0
  289. package/dist/src/native-transcripts.js.map +1 -0
  290. package/dist/src/paths.js +13 -0
  291. package/dist/src/paths.js.map +1 -0
  292. package/dist/src/process-lifecycle.js +101 -0
  293. package/dist/src/process-lifecycle.js.map +1 -0
  294. package/dist/src/provenance.js +263 -0
  295. package/dist/src/provenance.js.map +1 -0
  296. package/dist/src/prune.js +228 -0
  297. package/dist/src/prune.js.map +1 -0
  298. package/dist/src/report.js +276 -0
  299. package/dist/src/report.js.map +1 -0
  300. package/dist/src/resolved-case.js +29 -0
  301. package/dist/src/resolved-case.js.map +1 -0
  302. package/dist/src/results-lock.js +40 -0
  303. package/dist/src/results-lock.js.map +1 -0
  304. package/dist/src/results.js +127 -0
  305. package/dist/src/results.js.map +1 -0
  306. package/dist/src/run-owner.js +283 -0
  307. package/dist/src/run-owner.js.map +1 -0
  308. package/dist/src/runner-paths.js +11 -0
  309. package/dist/src/runner-paths.js.map +1 -0
  310. package/dist/src/runtime/config.js +323 -0
  311. package/dist/src/runtime/config.js.map +1 -0
  312. package/dist/src/runtime/discovery-apple.js +65 -0
  313. package/dist/src/runtime/discovery-apple.js.map +1 -0
  314. package/dist/src/runtime/discovery-providers.js +37 -0
  315. package/dist/src/runtime/discovery-providers.js.map +1 -0
  316. package/dist/src/runtime/discovery.js +40 -0
  317. package/dist/src/runtime/discovery.js.map +1 -0
  318. package/dist/src/runtime/hook-processes.js +81 -0
  319. package/dist/src/runtime/hook-processes.js.map +1 -0
  320. package/dist/src/runtime/hooks.js +304 -0
  321. package/dist/src/runtime/hooks.js.map +1 -0
  322. package/dist/src/runtime/paths.js +244 -0
  323. package/dist/src/runtime/paths.js.map +1 -0
  324. package/dist/src/runtime/query.js +76 -0
  325. package/dist/src/runtime/query.js.map +1 -0
  326. package/dist/src/runtime/seeds.js +98 -0
  327. package/dist/src/runtime/seeds.js.map +1 -0
  328. package/dist/src/runtime/shared-cache.js +81 -0
  329. package/dist/src/runtime/shared-cache.js.map +1 -0
  330. package/dist/src/runtime/state.js +106 -0
  331. package/dist/src/runtime/state.js.map +1 -0
  332. package/dist/src/runtime-process.js +148 -0
  333. package/dist/src/runtime-process.js.map +1 -0
  334. package/dist/src/schema-types.js +2 -0
  335. package/dist/src/schema-types.js.map +1 -0
  336. package/dist/src/schema.js +16 -0
  337. package/dist/src/schema.js.map +1 -0
  338. package/dist/src/shell.js +5 -0
  339. package/dist/src/shell.js.map +1 -0
  340. package/dist/src/sqlite-lock.js +18 -0
  341. package/dist/src/sqlite-lock.js.map +1 -0
  342. package/dist/src/storage.js +21 -0
  343. package/dist/src/storage.js.map +1 -0
  344. package/dist/src/transcript-retention.js +109 -0
  345. package/dist/src/transcript-retention.js.map +1 -0
  346. package/dist/src/value-guards.js +26 -0
  347. package/dist/src/value-guards.js.map +1 -0
  348. package/docs/architecture.md +5 -2
  349. package/docs/creating-cases.md +2 -3
  350. package/docs/development-cli.md +12 -7
  351. package/docs/documentation-quality.md +4 -3
  352. package/docs/evaluation-workflow.md +8 -8
  353. package/docs/getting-started.md +7 -6
  354. package/docs/guide-evaluation.md +1 -1
  355. package/docs/identity-v1.md +4 -1
  356. package/docs/installing.md +31 -13
  357. package/docs/licensing.md +2 -2
  358. package/docs/native-hosts.md +1 -1
  359. package/docs/recipes.md +41 -40
  360. package/docs/releases.md +20 -4
  361. package/docs/run-settings-v1.md +3 -3
  362. package/docs/specs/repository-guide.md +1 -1
  363. package/docs/troubleshooting.md +1 -1
  364. package/docs/typescript-quality.md +18 -1
  365. package/examples/recipes/executable.ts +19 -0
  366. package/examples/recipes/extension-artifacts.ts +1 -1
  367. package/examples/recipes/extension.ts +2 -1
  368. package/examples/recipes/modules.ts +9 -0
  369. package/examples/recipes/run-inputs.ts +14 -19
  370. package/examples/recipes/run-options.ts +9 -8
  371. package/examples/recipes/run.ts +21 -7
  372. package/examples/templates/repository-skill/extension-data.ts +1 -1
  373. package/examples/templates/repository-skill/extension.ts +2 -1
  374. package/package.json +14 -6
  375. package/src/cli/demo.ts +3 -2
  376. package/src/cli/help.ts +4 -3
  377. package/src/cli/init-settings.ts +5 -4
  378. package/src/cli/init.ts +9 -8
  379. package/src/cli/invocation.ts +2 -1
  380. package/src/cli/preparation.ts +2 -2
  381. package/src/cli.ts +1 -1
  382. package/src/executable.ts +19 -0
  383. package/src/extension-client.ts +6 -12
  384. package/src/fixtures/setup.ts +3 -2
  385. package/src/git.ts +2 -1
  386. package/src/graders/git-head.ts +3 -2
  387. package/src/graders/shell.ts +4 -3
  388. package/src/hosts/claude-keychain.ts +3 -2
  389. package/src/hosts/claude-login-refresh.ts +3 -2
  390. package/src/hosts/claude-refresh-lock.ts +2 -1
  391. package/src/hosts/claude-settings.ts +3 -1
  392. package/src/hosts/claude.ts +1 -1
  393. package/src/hosts/isolation-roots.ts +3 -2
  394. package/src/hosts/process-lifecycle.ts +4 -6
  395. package/src/hosts/protected-worktrees.ts +2 -1
  396. package/src/hosts/skill-hook-handler.ts +1 -1
  397. package/src/hosts/skill-hooks.ts +10 -2
  398. package/src/provenance.ts +7 -6
  399. package/src/results-lock.ts +4 -4
  400. package/src/run-owner.ts +15 -9
  401. package/src/runner-paths.ts +21 -0
  402. package/src/runtime/hook-processes.ts +2 -1
  403. package/src/runtime/hooks.ts +9 -7
  404. package/src/runtime-process.ts +194 -0
  405. package/src/sqlite-lock.ts +25 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provenance.js","sourceRoot":"","sources":["provenance.ts"],"names":[],"mappings":"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","sourcesContent":["import { runnerRoot, sourceRuntime } from \"./runner-paths\";\nimport { spawnSync } from \"./runtime-process\";\nimport type { Stats } from \"node:fs\";\nimport { lstat, readFile, readdir, readlink, realpath } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { insideRoot } from \"./paths\";\nimport { pathToFileURL } from \"node:url\";\nimport packageJson from \"../package.json\" with { type: \"json\" };\nimport { hashJson, sha256 } from \"./identity\";\nimport { insideRuntimeRoot } from \"./runtime/paths\";\n\nconst MAX_DIRTY_BYTES = 32 * 1024 * 1024;\nconst MAX_BUILD_BYTES = 64 * 1024 * 1024;\nconst MAX_BUILD_FILES = 4096;\n\ntype BuildFile = { path: string; sha256: string };\ntype ProjectFile = BuildFile & { executable: boolean };\nfunction fileOrder(left: BuildFile, right: BuildFile): number {\n return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;\n}\n\nclass BuildSnapshot {\n readonly files: BuildFile[] = [];\n private bytes = 0;\n constructor(private root: string) {}\n async collect(path: string): Promise<void> {\n const absolute = join(this.root, path),\n info = await lstat(absolute);\n if (info.isSymbolicLink())\n throw new Error(\"package build contains a symbolic link\");\n if (info.isDirectory()) {\n for (const entry of (await readdir(absolute)).sort())\n await this.collect(join(path, entry));\n return;\n }\n await this.collectFile(path, absolute, info);\n }\n private async collectFile(\n path: string,\n absolute: string,\n info: Stats,\n ): Promise<void> {\n if (!info.isFile()) throw new Error(\"package build contains a non-file\");\n const bytes = await readFile(absolute);\n this.bytes += bytes.byteLength;\n if (this.files.length >= MAX_BUILD_FILES || this.bytes > MAX_BUILD_BYTES)\n throw new Error(\"package build exceeds the size limit\");\n this.files.push({ path: path.split(sep).join(\"/\"), sha256: sha256(bytes) });\n }\n}\n\n/** Digest the packaged runtime and public contract files in path order. */\nexport async function packageBuildDigest(root = runnerRoot): Promise<string> {\n const snapshot = new BuildSnapshot(await realpath(root));\n for (const path of [\n \"package.json\",\n \"README.md\",\n \"docs\",\n \"examples\",\n \"schemas\",\n \"src\",\n ...(!sourceRuntime ? [\"dist\"] : []),\n ])\n await snapshot.collect(path);\n return hashJson({\n format: \"sevro.build.v1\",\n files: snapshot.files.sort(fileOrder),\n });\n}\n\nclass ProjectSnapshot {\n readonly files: ProjectFile[] = [];\n private bytes = 0;\n constructor(\n private root: string,\n private excluded: Set<string>,\n ) {}\n private isExcluded(path: string): boolean {\n return path === \".git\" || this.excluded.has(path);\n }\n async collect(path: string): Promise<void> {\n if (this.isExcluded(path)) return;\n const absolute = join(this.root, path),\n info = await lstat(absolute);\n if (info.isSymbolicLink())\n throw new Error(\"project snapshot contains a symbolic link\");\n if (info.isDirectory()) {\n await this.collectDirectory(path, absolute);\n return;\n }\n await this.collectFile(path, absolute, info);\n }\n private async collectDirectory(\n path: string,\n absolute: string,\n ): Promise<void> {\n for (const entry of (await readdir(absolute)).sort())\n await this.collect(path === \"\" ? entry : join(path, entry));\n }\n private async collectFile(\n path: string,\n absolute: string,\n info: Stats,\n ): Promise<void> {\n if (!info.isFile()) throw new Error(\"project snapshot contains a non-file\");\n const bytes = await readFile(absolute);\n this.bytes += bytes.byteLength;\n if (this.files.length >= MAX_BUILD_FILES || this.bytes > MAX_BUILD_BYTES)\n throw new Error(\"project snapshot exceeds the size limit\");\n this.files.push({\n path: path.split(sep).join(\"/\"),\n sha256: sha256(bytes),\n executable: Boolean(info.mode & 0o111),\n });\n }\n}\nexport async function projectIdentityDigest(\n root: string,\n provenance: { revision: string | null; dirtyPatchDigest: string | null },\n excludedRoots: string[] = [],\n): Promise<string> {\n if (provenance.revision)\n return hashJson({\n format: \"sevro.project.v1\",\n revision: provenance.revision,\n dirtyPatchDigest: provenance.dirtyPatchDigest,\n });\n const projectRoot = resolve(root),\n excluded = new Set(\n excludedRoots.map((path) => relative(projectRoot, resolve(path))),\n );\n const snapshot = new ProjectSnapshot(projectRoot, excluded);\n await snapshot.collect(\"\");\n return hashJson({\n format: \"sevro.project.v1\",\n files: snapshot.files.sort(fileOrder),\n });\n}\n\nfunction git(root: string, ...arguments_: string[]): Buffer {\n const result = spawnSync([\"git\", \"-C\", root, ...arguments_], {\n env: { PATH: process.env.PATH ?? \"/usr/bin:/bin\", LC_ALL: \"C\" },\n stdout: \"pipe\",\n stderr: \"ignore\",\n });\n if (result.exitCode !== 0) throw new Error(\"Git identity is unavailable\");\n if (result.stdout.byteLength > MAX_DIRTY_BYTES)\n throw new Error(\"Git identity exceeds the size limit\");\n return Buffer.from(result.stdout);\n}\n\nasync function dirtyPatchDigest(\n gitRoot: string,\n excludedRoots: string[],\n): Promise<string | null> {\n const trackedPatch = git(gitRoot, \"diff\", \"--binary\", \"HEAD\", \"--\");\n const untrackedPaths = git(\n gitRoot,\n \"ls-files\",\n \"--others\",\n \"--exclude-standard\",\n \"-z\",\n )\n .toString(\"utf8\")\n .split(\"\\0\")\n .filter(Boolean)\n .filter(\n (path) =>\n !excludedRoots.some((root) =>\n insideRuntimeRoot(root, resolve(gitRoot, path)),\n ),\n )\n .sort();\n let dirtyBytes = trackedPatch.byteLength;\n const untracked: { path: string; sha256: string }[] = [];\n for (const path of untrackedPaths) {\n const bytes = await untrackedIdentityBytes(gitRoot, path);\n dirtyBytes += bytes.byteLength;\n if (dirtyBytes > MAX_DIRTY_BYTES)\n throw new Error(\"Git identity exceeds the size limit\");\n untracked.push({ path, sha256: sha256(bytes) });\n }\n return trackedPatch.byteLength || untracked.length\n ? hashJson({ trackedPatch: sha256(trackedPatch), untracked })\n : null;\n}\n\nfunction requireSafeUntrackedPath(root: string, absolute: string): void {\n if (!insideRoot(root, absolute))\n throw new Error(\"Git checkout has an unsafe untracked path\");\n}\nasync function untrackedIdentityBytes(\n root: string,\n path: string,\n): Promise<Buffer> {\n const absolute = resolve(root, path);\n requireSafeUntrackedPath(root, absolute);\n const info = await lstat(absolute);\n if (info.isSymbolicLink())\n return Buffer.from(await readlink(absolute), \"utf8\");\n if (info.isFile()) return readFile(absolute);\n throw new Error(\"Git checkout has an unsupported untracked entry\");\n}\n\n/** Local development identity; packaged use never calls Git. */\nexport async function checkoutProvenance(root: string, buildDigest: string) {\n const canonicalRoot = await realpath(root);\n const gitRoot = await realpath(\n git(canonicalRoot, \"rev-parse\", \"--show-toplevel\").toString().trim(),\n );\n if (gitRoot !== canonicalRoot)\n throw new Error(\n \"local Sevro checkout root does not match its Git repository\",\n );\n const revision = git(canonicalRoot, \"rev-parse\", \"HEAD\").toString().trim();\n if (!/^[a-f0-9]{40,64}$/.test(revision))\n throw new Error(\"local Sevro checkout revision is invalid\");\n return {\n source: \"checkout\" as const,\n root: pathToFileURL(canonicalRoot).href,\n revision,\n dirtyPatchDigest: await dirtyPatchDigest(canonicalRoot, []),\n buildDigest,\n };\n}\n\nasync function enclosingGitRoot(root: string): Promise<string | null> {\n let path = root;\n for (;;) {\n if (await gitMetadataAt(path)) return path;\n const parent = dirname(path);\n if (parent === path) return null;\n path = parent;\n }\n}\n\nasync function gitMetadataAt(path: string): Promise<boolean> {\n try {\n const metadataPath = join(path, \".git\");\n const metadata = await lstat(metadataPath);\n return !(await emptyTemporaryGitPlaceholder(path, metadataPath, metadata));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n throw error;\n }\n}\n\nasync function emptyTemporaryGitPlaceholder(\n directory: string,\n metadataPath: string,\n metadata: Stats,\n): Promise<boolean> {\n // Codex's Linux sandbox can leave an empty mount placeholder at /tmp/.git.\n return (\n process.platform === \"linux\" &&\n (directory === \"/tmp\" || directory === tmpdir()) &&\n metadata.isDirectory() &&\n (await readdir(metadataPath)).length === 0\n );\n}\n\nexport async function projectProvenance(\n root: string,\n excludedRoots?: string[],\n) {\n const canonicalRoot = await realpath(root);\n const gitEntryRoot = await enclosingGitRoot(canonicalRoot);\n if (!gitEntryRoot)\n return {\n root: pathToFileURL(canonicalRoot).href,\n revision: null,\n dirtyPatchDigest: null,\n };\n const gitRoot = await realpath(\n git(canonicalRoot, \"rev-parse\", \"--show-toplevel\").toString().trim(),\n );\n if (gitRoot !== gitEntryRoot)\n throw new Error(\"project Git root does not match its repository entry\");\n let revision: string;\n try {\n revision = git(gitRoot, \"rev-parse\", \"HEAD\").toString().trim();\n } catch {\n git(gitRoot, \"status\", \"--porcelain\");\n return {\n root: pathToFileURL(canonicalRoot).href,\n revision: null,\n dirtyPatchDigest: null,\n };\n }\n if (!/^[a-f0-9]{40,64}$/.test(revision))\n throw new Error(\"project Git revision is invalid\");\n return {\n root: pathToFileURL(canonicalRoot).href,\n revision,\n dirtyPatchDigest: await projectDirtyDigest(gitRoot, excludedRoots),\n };\n}\n\nasync function projectDirtyDigest(\n root: string,\n excludedRoots: string[] | undefined,\n) {\n return dirtyPatchDigest(root, excludedRoots ?? []);\n}\n\nexport async function runnerProvenance(\n buildDigest: string,\n checkoutRoot?: string,\n) {\n if (checkoutRoot) {\n const actualRoot = await realpath(runnerRoot);\n if ((await realpath(checkoutRoot)) !== actualRoot)\n throw new Error(\n \"local Sevro checkout root does not match the running package\",\n );\n return checkoutProvenance(actualRoot, buildDigest);\n }\n return {\n source: \"package\" as const,\n packageName: packageJson.name,\n version: packageJson.version,\n buildDigest,\n };\n}\n"]}
@@ -0,0 +1,228 @@
1
+ import { lstat, readFile, readdir, realpath, rm, stat } from "node:fs/promises";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+ import { parseArgs } from "node:util";
4
+ import { assertCliResult, assertRunEvidence } from "./schema.js";
5
+ import { createReport } from "./report.js";
6
+ import { atomicWriteJson } from "./storage.js";
7
+ import { isRecord } from "./value-guards.js";
8
+ import { acquireResultsLock, retainedFile, withResultsLock, } from "./results-lock.js";
9
+ import { pruneNativeTranscripts } from "./transcript-retention.js";
10
+ const durationUnits = {
11
+ ms: 1,
12
+ s: 1000,
13
+ m: 60_000,
14
+ h: 3_600_000,
15
+ d: 86_400_000,
16
+ w: 604_800_000,
17
+ };
18
+ function duration(value) {
19
+ const match = /^([1-9]\d*)(ms|s|m|h|d|w)$/.exec(value);
20
+ if (!match)
21
+ throw new Error("--older-than requires a positive integer and ms, s, m, h, d or w");
22
+ const amount = Number.parseInt(value, 10) *
23
+ Number(durationUnits[value.replace(/^\d+/, "")]);
24
+ if (!Number.isSafeInteger(amount))
25
+ throw new Error("--older-than exceeds the safe duration range");
26
+ return amount;
27
+ }
28
+ function count(value) {
29
+ const amount = Number(value);
30
+ if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(amount))
31
+ throw new Error("--keep must be a positive safe integer");
32
+ return amount;
33
+ }
34
+ function parseInvocation(args) {
35
+ const { values } = parseArgs({
36
+ args,
37
+ strict: true,
38
+ options: {
39
+ "results-root": { type: "string" },
40
+ keep: { type: "string" },
41
+ latest: { type: "boolean" },
42
+ "older-than": { type: "string" },
43
+ "native-transcripts": { type: "boolean" },
44
+ },
45
+ });
46
+ return values;
47
+ }
48
+ function policy(values) {
49
+ if (values.latest)
50
+ return { keep: 1, cutoff: null };
51
+ if (values["older-than"] !== undefined)
52
+ return { keep: null, cutoff: Date.now() - duration(values["older-than"]) };
53
+ if (values.keep !== undefined)
54
+ return { keep: count(values.keep), cutoff: null };
55
+ return { keep: values["native-transcripts"] ? 0 : 10, cutoff: null };
56
+ }
57
+ function invocation(args) {
58
+ const values = parseInvocation(args);
59
+ const policies = [values.latest, values.keep, values["older-than"]].filter((value) => value !== undefined);
60
+ if (policies.length > 1)
61
+ throw new Error("retention policies are exclusive");
62
+ if (values["results-root"] === "")
63
+ throw new Error("--results-root must not be empty");
64
+ return {
65
+ root: resolve(values["results-root"] ?? ".sevro/results"),
66
+ policy: policy(values),
67
+ nativeTranscripts: values["native-transcripts"] === true,
68
+ };
69
+ }
70
+ async function retainedResults(root, directories) {
71
+ const results = [];
72
+ for (const directory of directories) {
73
+ const path = join(directory, "result.json");
74
+ await retainedFile(path);
75
+ const result = JSON.parse(await readFile(path, "utf8"));
76
+ assertCliResult(result);
77
+ if (!(await finalizedAncestors(root, directory, result.runId)))
78
+ continue;
79
+ if (result.evidencePath !== join(directory, "run.json"))
80
+ throw new Error(`retained result references evidence outside its directory: ${path}`);
81
+ await retainedFile(result.evidencePath);
82
+ const evidence = JSON.parse(await readFile(result.evidencePath, "utf8"));
83
+ assertRunEvidence(evidence);
84
+ await createReport([path]);
85
+ results.push({
86
+ directory,
87
+ result,
88
+ evidence,
89
+ time: (await stat(path)).mtimeMs,
90
+ });
91
+ }
92
+ return results.sort((left, right) => right.time - left.time || left.directory.localeCompare(right.directory));
93
+ }
94
+ async function resultDirectories(parent, storage) {
95
+ let children = await resultChildren(parent);
96
+ if (!storage.roots.includes(parent) &&
97
+ ((await resultsRoot(parent)) || children.results.length > 0)) {
98
+ storage.releases.push(await acquireResultsLock(parent));
99
+ storage.roots.push(parent);
100
+ children = await resultChildren(parent);
101
+ }
102
+ storage.directories.push(...children.results);
103
+ for (const directory of children.containers)
104
+ await resultDirectories(directory, storage);
105
+ }
106
+ async function resultChildren(parent) {
107
+ const results = [];
108
+ const containers = [];
109
+ const entries = await readdir(parent, { withFileTypes: true });
110
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
111
+ if (!entry.isDirectory())
112
+ continue;
113
+ const directory = join(parent, entry.name);
114
+ if (await retainedFile(join(directory, "result.json")))
115
+ results.push(directory);
116
+ else
117
+ containers.push(directory);
118
+ }
119
+ return { results, containers };
120
+ }
121
+ async function resultsRoot(directory) {
122
+ const active = await lstat(join(directory, "active")).catch((cause) => {
123
+ if (cause.code === "ENOENT")
124
+ return null;
125
+ throw cause;
126
+ });
127
+ if (active && !active.isDirectory())
128
+ throw new Error(`retained ownership must be a directory: ${directory}`);
129
+ return Boolean(active || (await retainedFile(join(directory, ".retention-lock.sqlite"))));
130
+ }
131
+ async function finalized(root, runId) {
132
+ if (!runId || basename(runId) !== runId)
133
+ return false;
134
+ const path = join(root, "active", `${runId}.json`);
135
+ if (!(await retainedFile(path)))
136
+ return true;
137
+ const value = JSON.parse(await readFile(path, "utf8"));
138
+ if (!isRecord(value))
139
+ return false;
140
+ return finalizedRecord(value, runId);
141
+ }
142
+ async function finalizedAncestors(root, directory, runId) {
143
+ let ancestor = dirname(directory);
144
+ while (ancestor !== dirname(root)) {
145
+ if (!(await finalized(ancestor, runId)))
146
+ return false;
147
+ ancestor = dirname(ancestor);
148
+ }
149
+ return true;
150
+ }
151
+ function finalizedRecord(value, runId) {
152
+ return (value.format === "sevro.active-run.v1" &&
153
+ value.runId === runId &&
154
+ ["complete", "interrupted", "diagnostic"].includes(String(value.status)));
155
+ }
156
+ function retainedByPolicy(results, policy) {
157
+ const cutoff = policy.cutoff;
158
+ return cutoff === null
159
+ ? results.slice(0, policy.keep)
160
+ : results.filter((result) => result.time >= cutoff);
161
+ }
162
+ async function pruneResults(root, policy, storage) {
163
+ const results = await retainedResults(root, storage.directories);
164
+ const remaining = retainedByPolicy(results, policy);
165
+ for (const resultsRoot of storage.roots) {
166
+ const latest = remaining.find((item) => item.directory.startsWith(`${resultsRoot}/`));
167
+ if (latest)
168
+ await atomicWriteJson(join(resultsRoot, "latest.json"), latest.result);
169
+ else
170
+ await rm(join(resultsRoot, "latest.json"), { force: true });
171
+ }
172
+ const removed = results.filter((result) => !remaining.includes(result));
173
+ for (const result of removed)
174
+ await rm(result.directory, { recursive: true });
175
+ return { removed: removed.length, kept: remaining.length };
176
+ }
177
+ async function pruneTranscripts(root, policy, directories) {
178
+ const results = await retainedResults(root, directories);
179
+ const preserved = retainedByPolicy(results, policy);
180
+ let removed = 0;
181
+ for (const { evidence } of results.filter((item) => !preserved.includes(item))) {
182
+ removed += await pruneNativeTranscripts(evidence);
183
+ }
184
+ return { removed, kept: results.length };
185
+ }
186
+ async function prune(options) {
187
+ const root = await realpath(options.root).catch((cause) => {
188
+ if (cause.code === "ENOENT")
189
+ return null;
190
+ throw cause;
191
+ });
192
+ if (root === null)
193
+ return { removed: 0, kept: 0 };
194
+ return withResultsLock(root, async () => {
195
+ const storage = {
196
+ directories: [],
197
+ roots: [root],
198
+ releases: [],
199
+ };
200
+ try {
201
+ await resultDirectories(root, storage);
202
+ return options.nativeTranscripts
203
+ ? await pruneTranscripts(root, options.policy, storage.directories)
204
+ : await pruneResults(root, options.policy, storage);
205
+ }
206
+ finally {
207
+ for (const release of storage.releases.reverse())
208
+ release();
209
+ }
210
+ });
211
+ }
212
+ /** Explicitly remove old retained runs while keeping report's latest index usable. */
213
+ export async function pruneCommand(args) {
214
+ try {
215
+ const options = invocation(args);
216
+ const result = await prune(options);
217
+ const unit = options.nativeTranscripts
218
+ ? "native transcript bundle(s)"
219
+ : "result(s)";
220
+ process.stdout.write(`Removed ${result.removed} ${unit}; kept ${result.kept}${options.nativeTranscripts ? " result(s)" : ""}.\n`);
221
+ return 0;
222
+ }
223
+ catch (error) {
224
+ process.stderr.write(`sevro prune: ${error instanceof Error ? error.message : "pruning failed"}\n`);
225
+ return 64;
226
+ }
227
+ }
228
+ //# sourceMappingURL=prune.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prune.js","sourceRoot":"","sources":["prune.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstat, readFile, readdir, realpath, rm, stat } from \"node:fs/promises\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { assertCliResult, assertRunEvidence } from \"./schema\";\nimport type { CliResultData, RunEvidenceData } from \"./schema-types\";\nimport { createReport } from \"./report\";\nimport { atomicWriteJson } from \"./storage\";\nimport { isRecord } from \"./value-guards\";\nimport {\n acquireResultsLock,\n retainedFile,\n withResultsLock,\n} from \"./results-lock\";\nimport { pruneNativeTranscripts } from \"./transcript-retention\";\n\ninterface RetainedResult {\n directory: string;\n result: CliResultData;\n evidence: RunEvidenceData;\n time: number;\n}\n\ninterface RetainedStorage {\n directories: string[];\n roots: string[];\n releases: (() => void)[];\n}\n\ntype RetentionPolicy =\n { keep: number; cutoff: null } | { keep: null; cutoff: number };\n\nconst durationUnits: Record<string, number> = {\n ms: 1,\n s: 1000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n w: 604_800_000,\n};\n\nfunction duration(value: string): number {\n const match = /^([1-9]\\d*)(ms|s|m|h|d|w)$/.exec(value);\n if (!match)\n throw new Error(\n \"--older-than requires a positive integer and ms, s, m, h, d or w\",\n );\n const amount =\n Number.parseInt(value, 10) *\n Number(durationUnits[value.replace(/^\\d+/, \"\")]);\n if (!Number.isSafeInteger(amount))\n throw new Error(\"--older-than exceeds the safe duration range\");\n return amount;\n}\n\nfunction count(value: string): number {\n const amount = Number(value);\n if (!/^[1-9]\\d*$/.test(value) || !Number.isSafeInteger(amount))\n throw new Error(\"--keep must be a positive safe integer\");\n return amount;\n}\n\nfunction parseInvocation(args: string[]) {\n const { values } = parseArgs({\n args,\n strict: true,\n options: {\n \"results-root\": { type: \"string\" },\n keep: { type: \"string\" },\n latest: { type: \"boolean\" },\n \"older-than\": { type: \"string\" },\n \"native-transcripts\": { type: \"boolean\" },\n },\n });\n return values;\n}\n\nfunction policy(values: ReturnType<typeof parseInvocation>): RetentionPolicy {\n if (values.latest) return { keep: 1, cutoff: null };\n if (values[\"older-than\"] !== undefined)\n return { keep: null, cutoff: Date.now() - duration(values[\"older-than\"]) };\n if (values.keep !== undefined)\n return { keep: count(values.keep), cutoff: null };\n return { keep: values[\"native-transcripts\"] ? 0 : 10, cutoff: null };\n}\n\nfunction invocation(args: string[]) {\n const values = parseInvocation(args);\n const policies = [values.latest, values.keep, values[\"older-than\"]].filter(\n (value) => value !== undefined,\n );\n if (policies.length > 1) throw new Error(\"retention policies are exclusive\");\n if (values[\"results-root\"] === \"\")\n throw new Error(\"--results-root must not be empty\");\n return {\n root: resolve(values[\"results-root\"] ?? \".sevro/results\"),\n policy: policy(values),\n nativeTranscripts: values[\"native-transcripts\"] === true,\n };\n}\n\nasync function retainedResults(\n root: string,\n directories: string[],\n): Promise<RetainedResult[]> {\n const results: RetainedResult[] = [];\n for (const directory of directories) {\n const path = join(directory, \"result.json\");\n await retainedFile(path);\n const result: unknown = JSON.parse(await readFile(path, \"utf8\"));\n assertCliResult(result);\n if (!(await finalizedAncestors(root, directory, result.runId))) continue;\n if (result.evidencePath !== join(directory, \"run.json\"))\n throw new Error(\n `retained result references evidence outside its directory: ${path}`,\n );\n await retainedFile(result.evidencePath);\n const evidence: unknown = JSON.parse(\n await readFile(result.evidencePath, \"utf8\"),\n );\n assertRunEvidence(evidence);\n await createReport([path]);\n results.push({\n directory,\n result,\n evidence,\n time: (await stat(path)).mtimeMs,\n });\n }\n return results.sort(\n (left, right) =>\n right.time - left.time || left.directory.localeCompare(right.directory),\n );\n}\n\nasync function resultDirectories(\n parent: string,\n storage: RetainedStorage,\n): Promise<void> {\n let children = await resultChildren(parent);\n if (\n !storage.roots.includes(parent) &&\n ((await resultsRoot(parent)) || children.results.length > 0)\n ) {\n storage.releases.push(await acquireResultsLock(parent));\n storage.roots.push(parent);\n children = await resultChildren(parent);\n }\n storage.directories.push(...children.results);\n for (const directory of children.containers)\n await resultDirectories(directory, storage);\n}\n\nasync function resultChildren(parent: string) {\n const results: string[] = [];\n const containers: string[] = [];\n const entries = await readdir(parent, { withFileTypes: true });\n for (const entry of entries.sort((left, right) =>\n left.name.localeCompare(right.name),\n )) {\n if (!entry.isDirectory()) continue;\n const directory = join(parent, entry.name);\n if (await retainedFile(join(directory, \"result.json\")))\n results.push(directory);\n else containers.push(directory);\n }\n return { results, containers };\n}\n\nasync function resultsRoot(directory: string): Promise<boolean> {\n const active = await lstat(join(directory, \"active\")).catch(\n (cause: unknown) => {\n if ((cause as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw cause;\n },\n );\n if (active && !active.isDirectory())\n throw new Error(`retained ownership must be a directory: ${directory}`);\n return Boolean(\n active || (await retainedFile(join(directory, \".retention-lock.sqlite\"))),\n );\n}\n\nasync function finalized(root: string, runId: string | null): Promise<boolean> {\n if (!runId || basename(runId) !== runId) return false;\n const path = join(root, \"active\", `${runId}.json`);\n if (!(await retainedFile(path))) return true;\n const value: unknown = JSON.parse(await readFile(path, \"utf8\"));\n if (!isRecord(value)) return false;\n return finalizedRecord(value, runId);\n}\n\nasync function finalizedAncestors(\n root: string,\n directory: string,\n runId: string | null,\n): Promise<boolean> {\n let ancestor = dirname(directory);\n while (ancestor !== dirname(root)) {\n if (!(await finalized(ancestor, runId))) return false;\n ancestor = dirname(ancestor);\n }\n return true;\n}\n\nfunction finalizedRecord(\n value: Record<string, unknown>,\n runId: string,\n): boolean {\n return (\n value.format === \"sevro.active-run.v1\" &&\n value.runId === runId &&\n [\"complete\", \"interrupted\", \"diagnostic\"].includes(String(value.status))\n );\n}\n\nfunction retainedByPolicy(results: RetainedResult[], policy: RetentionPolicy) {\n const cutoff = policy.cutoff;\n return cutoff === null\n ? results.slice(0, policy.keep)\n : results.filter((result) => result.time >= cutoff);\n}\n\nasync function pruneResults(\n root: string,\n policy: RetentionPolicy,\n storage: RetainedStorage,\n) {\n const results = await retainedResults(root, storage.directories);\n const remaining = retainedByPolicy(results, policy);\n for (const resultsRoot of storage.roots) {\n const latest = remaining.find((item) =>\n item.directory.startsWith(`${resultsRoot}/`),\n );\n if (latest)\n await atomicWriteJson(join(resultsRoot, \"latest.json\"), latest.result);\n else await rm(join(resultsRoot, \"latest.json\"), { force: true });\n }\n const removed = results.filter((result) => !remaining.includes(result));\n for (const result of removed) await rm(result.directory, { recursive: true });\n return { removed: removed.length, kept: remaining.length };\n}\n\nasync function pruneTranscripts(\n root: string,\n policy: RetentionPolicy,\n directories: string[],\n) {\n const results = await retainedResults(root, directories);\n const preserved = retainedByPolicy(results, policy);\n let removed = 0;\n for (const { evidence } of results.filter(\n (item) => !preserved.includes(item),\n )) {\n removed += await pruneNativeTranscripts(evidence);\n }\n return { removed, kept: results.length };\n}\n\nasync function prune(options: ReturnType<typeof invocation>) {\n const root = await realpath(options.root).catch((cause: unknown) => {\n if ((cause as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw cause;\n });\n if (root === null) return { removed: 0, kept: 0 };\n return withResultsLock(root, async () => {\n const storage: RetainedStorage = {\n directories: [],\n roots: [root],\n releases: [],\n };\n try {\n await resultDirectories(root, storage);\n return options.nativeTranscripts\n ? await pruneTranscripts(root, options.policy, storage.directories)\n : await pruneResults(root, options.policy, storage);\n } finally {\n for (const release of storage.releases.reverse()) release();\n }\n });\n}\n\n/** Explicitly remove old retained runs while keeping report's latest index usable. */\nexport async function pruneCommand(args: string[]): Promise<number> {\n try {\n const options = invocation(args);\n const result = await prune(options);\n const unit = options.nativeTranscripts\n ? \"native transcript bundle(s)\"\n : \"result(s)\";\n process.stdout.write(\n `Removed ${result.removed} ${unit}; kept ${result.kept}${options.nativeTranscripts ? \" result(s)\" : \"\"}.\\n`,\n );\n return 0;\n } catch (error) {\n process.stderr.write(\n `sevro prune: ${error instanceof Error ? error.message : \"pruning failed\"}\\n`,\n );\n return 64;\n }\n}\n"]}
@@ -0,0 +1,276 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { isAbsolute, join, resolve } from "node:path";
3
+ import { parseArgs } from "node:util";
4
+ import { canonicalJson } from "./identity.js";
5
+ import { assertCliResult, assertReport, assertRunEvidence } from "./schema.js";
6
+ import { canonicalRuntimeRoot } from "./runtime/paths.js";
7
+ import { nativeTranscriptRetention } from "./transcript-retention.js";
8
+ function sumKnown(values) {
9
+ return values.length && values.every((value) => value != null)
10
+ ? values.reduce((total, value) => total + value, 0)
11
+ : null;
12
+ }
13
+ function passRate(trials) {
14
+ if (!trials.length ||
15
+ trials.some((trial) => trial.execution.status !== "completed" ||
16
+ trial.grading.status !== "completed" ||
17
+ trial.task.verdict === "not_assessed"))
18
+ return null;
19
+ return (trials.filter((trial) => trial.task.verdict === "passed").length /
20
+ trials.length);
21
+ }
22
+ async function loadResult(path) {
23
+ if (!isAbsolute(path))
24
+ throw new Error("report result paths must be absolute");
25
+ const result = JSON.parse(await readFile(path, "utf8"));
26
+ assertCliResult(result);
27
+ return result;
28
+ }
29
+ async function loadEvidence(result) {
30
+ const path = retainedEvidencePath(result);
31
+ if (path === null)
32
+ return null;
33
+ const value = JSON.parse(await readFile(path, "utf8"));
34
+ assertRunEvidence(value);
35
+ const evidence = value;
36
+ if (evidence.runId !== result.runId ||
37
+ canonicalJson(evidence.result) !== canonicalJson(result))
38
+ throw new Error("retained evidence differs from report result");
39
+ return evidence;
40
+ }
41
+ function retainedEvidencePath(result) {
42
+ if (!result.evidencePath) {
43
+ if (result.cases.length || result.exitCode === 0)
44
+ throw new Error("completed result has no retained evidence");
45
+ return null;
46
+ }
47
+ if (!isAbsolute(result.evidencePath))
48
+ throw new Error("evidence path must be absolute");
49
+ return result.evidencePath;
50
+ }
51
+ function trialEvidence(caseId, trials, evidence) {
52
+ if (!evidence)
53
+ return [];
54
+ const matching = evidence.trials.filter((item) => item.caseId === caseId);
55
+ if (matching.length !== trials.length ||
56
+ trials.some((trial) => matching.filter((item) => item.trial === trial.trial).length !== 1))
57
+ throw new Error("retained trial evidence differs from report result");
58
+ return matching;
59
+ }
60
+ function reportRow(path, result, selected, evidence, transcripts) {
61
+ const retained = trialEvidence(selected.caseId, selected.trials, evidence);
62
+ const candidateRoute = retainedCandidateRoute(evidence);
63
+ const usage = retainedUsage(retained);
64
+ return {
65
+ resultFile: path,
66
+ evidencePath: result.evidencePath,
67
+ runId: result.runId,
68
+ caseId: selected.caseId,
69
+ execution: selected.execution.status,
70
+ grading: selected.grading.status,
71
+ task: selected.task.verdict,
72
+ exitCode: result.exitCode,
73
+ trialCount: selected.trials.length,
74
+ taskPassRate: passRate(selected.trials),
75
+ candidateDurationMs: sumKnown(retained.map((trial) => trial.candidateDurationMs)),
76
+ ...usage,
77
+ ...retainedIdentity(evidence),
78
+ candidateRoute,
79
+ nativeTranscripts: {
80
+ retained: transcripts.filter((item) => item.caseId === selected.caseId && !item.pruned).length,
81
+ pruned: transcripts.filter((item) => item.caseId === selected.caseId && item.pruned).length,
82
+ },
83
+ domainOutcomes: selected.trials.flatMap((trial) => (trial.domainOutcomes ?? []).map((outcome) => ({
84
+ trial: trial.trial,
85
+ id: outcome.id,
86
+ status: outcome.status,
87
+ }))),
88
+ };
89
+ }
90
+ function retainedCandidateRoute(evidence) {
91
+ if (!evidence)
92
+ return null;
93
+ const routes = evidence.routes.filter((route) => route.role === "candidate"), [route] = routes;
94
+ if (routes.length !== 1 || route === undefined)
95
+ throw new Error("retained candidate route is ambiguous");
96
+ return { host: route.host, model: route.model, effort: route.effort };
97
+ }
98
+ function retainedUsage(retained) {
99
+ const complete = retained.length > 0 && retained.every((trial) => trial.usage.complete);
100
+ return {
101
+ inputTokens: complete
102
+ ? sumKnown(retained.map((trial) => trial.usage.inputTokens))
103
+ : null,
104
+ outputTokens: complete
105
+ ? sumKnown(retained.map((trial) => trial.usage.outputTokens))
106
+ : null,
107
+ costUsd: complete
108
+ ? sumKnown(retained.map((trial) => trial.usage.costUsd))
109
+ : null,
110
+ };
111
+ }
112
+ function retainedIdentity(evidence) {
113
+ if (!evidence)
114
+ return {
115
+ runnerBuildDigest: null,
116
+ evaluationDigest: null,
117
+ requestedCondition: null,
118
+ actualCondition: null,
119
+ routes: [],
120
+ };
121
+ return {
122
+ runnerBuildDigest: evidence.runner.buildDigest,
123
+ evaluationDigest: evidence.evaluationIdentity.digest,
124
+ requestedCondition: evidence.condition.requested,
125
+ actualCondition: evidence.condition.actual,
126
+ routes: evidence.routes,
127
+ };
128
+ }
129
+ export async function createReport(paths) {
130
+ if (!paths.length || new Set(paths).size !== paths.length)
131
+ throw new Error("report needs distinct result files");
132
+ const rows = [];
133
+ const inputs = [];
134
+ for (const path of paths) {
135
+ const result = await loadResult(path);
136
+ const evidence = await loadEvidence(result);
137
+ const transcripts = await nativeTranscriptRetention(evidence);
138
+ inputs.push({
139
+ resultFile: path,
140
+ execution: result.execution.status,
141
+ grading: result.grading.status,
142
+ task: result.task.verdict,
143
+ exitCode: result.exitCode,
144
+ caseCount: result.cases.length,
145
+ });
146
+ for (const selected of result.cases)
147
+ rows.push(reportRow(path, result, selected, evidence, transcripts));
148
+ }
149
+ const report = {
150
+ format: "sevro.report.v1",
151
+ inputs,
152
+ rows,
153
+ summary: {
154
+ cases: rows.length,
155
+ passed: rows.filter((row) => row.task === "passed").length,
156
+ failed: rows.filter((row) => row.task === "failed").length,
157
+ notAssessed: rows.filter((row) => row.task === "not_assessed").length,
158
+ },
159
+ };
160
+ assertReport(report);
161
+ return report;
162
+ }
163
+ function cell(value) {
164
+ return value.replaceAll("|", "\\|").replaceAll(/[\r\n]/g, " ");
165
+ }
166
+ function measured(value, digits = 0) {
167
+ return value === null ? "unknown" : value.toFixed(digits);
168
+ }
169
+ function seconds(value) {
170
+ return value === null ? "unknown" : `${(value / 1000).toFixed(2)}s`;
171
+ }
172
+ export function renderReport(report) {
173
+ const lines = [
174
+ "# Sevro evaluation report",
175
+ "",
176
+ "Task verdict, execution, and grading are separate. Unknown measurements are not zero. Rows do not claim matched comparisons.",
177
+ "",
178
+ `Cases: ${report.summary.cases}; passed: ${report.summary.passed}; failed: ${report.summary.failed}; not assessed: ${report.summary.notAssessed}.`,
179
+ "",
180
+ "## Input runs",
181
+ "",
182
+ "| Result file | Exit | Execution | Grading | Task | Cases |",
183
+ "| --- | ---: | --- | --- | --- | ---: |",
184
+ ...report.inputs.map((input) => `| ${cell(input.resultFile)} | ${input.exitCode} | ${input.execution} | ${input.grading} | ${input.task} | ${input.caseCount} |`),
185
+ "",
186
+ "## Case outcomes",
187
+ "",
188
+ "| Case | Task | Execution | Grading | Pass rate | Candidate time | Candidate tokens | Candidate cost | Condition | Candidate route |",
189
+ "| --- | --- | --- | --- | ---: | ---: | ---: | ---: | --- | --- |",
190
+ ...report.rows.map(caseOutcomeLine),
191
+ "",
192
+ "## Retained evidence",
193
+ "",
194
+ ...report.rows.map((row) => `- ${cell(row.caseId)}: ${row.evidencePath ?? "unavailable"}${transcriptSummary(row)}`),
195
+ "",
196
+ ];
197
+ const outcomes = report.rows.flatMap((row) => row.domainOutcomes.map((outcome) => ({ caseId: row.caseId, ...outcome })));
198
+ if (outcomes.length)
199
+ lines.push("## Domain outcomes", "", "Domain outcomes are independent of the task verdict.", "", "| Case | Trial | Outcome | Status |", "| --- | ---: | --- | --- |", ...outcomes.map((outcome) => `| ${cell(outcome.caseId)} | ${outcome.trial} | ${cell(outcome.id)} | ${outcome.status} |`), "");
200
+ lines.push("## Routes", "", "| Case | Role | Host | Model | Effort |", "| --- | --- | --- | --- | --- |", ...report.rows.flatMap((row) => row.routes.map((route) => `| ${cell(row.caseId)} | ${route.role} | ${cell(route.host)} | ${cell(route.model)} | ${cell(route.effort)} |`)), "");
201
+ return lines.join("\n");
202
+ }
203
+ function transcriptSummary(row) {
204
+ if (!row.nativeTranscripts)
205
+ return "";
206
+ return `; native transcripts: ${row.nativeTranscripts.retained} retained, ${row.nativeTranscripts.pruned} pruned`;
207
+ }
208
+ function passRateCell(rate) {
209
+ return rate === null ? "unknown" : `${(rate * 100).toFixed(0)}%`;
210
+ }
211
+ function costCell(cost) {
212
+ return cost === null ? "unknown" : `$${cost.toFixed(4)}`;
213
+ }
214
+ function routeCell(route) {
215
+ return route
216
+ ? cell(`${route.host}/${route.model}@${route.effort}`)
217
+ : "unknown";
218
+ }
219
+ function conditionCell(row) {
220
+ return `${row.requestedCondition ?? "unknown"} / ${row.actualCondition ?? "unknown"}`;
221
+ }
222
+ function caseOutcomeLine(row) {
223
+ return `| ${cell(row.caseId)} | ${row.task} | ${row.execution} | ${row.grading} | ${passRateCell(row.taskPassRate)} | ${seconds(row.candidateDurationMs)} | ${measured(row.inputTokens)} / ${measured(row.outputTokens)} | ${costCell(row.costUsd)} | ${conditionCell(row)} | ${routeCell(row.candidateRoute)} |`;
224
+ }
225
+ export async function reportCommand(argv) {
226
+ try {
227
+ const values = reportInvocation(argv);
228
+ const report = await createReport(await reportPaths(values));
229
+ process.stdout.write(values.json ? `${JSON.stringify(report)}\n` : renderReport(report));
230
+ return 0;
231
+ }
232
+ catch (error) {
233
+ process.stderr.write(`${reportErrorMessage(error)}\n`);
234
+ return 64;
235
+ }
236
+ }
237
+ function reportInvocation(argv) {
238
+ const { values, positionals } = parseArgs({
239
+ args: argv,
240
+ allowPositionals: true,
241
+ strict: true,
242
+ options: {
243
+ json: { type: "boolean" },
244
+ "result-file": { type: "string", multiple: true },
245
+ latest: { type: "boolean" },
246
+ "results-root": { type: "string" },
247
+ },
248
+ });
249
+ if (positionals.length !== 1 || positionals[0] !== "report")
250
+ throw new Error("expected the report command");
251
+ return values;
252
+ }
253
+ async function reportPaths(values) {
254
+ validateReportSource(values);
255
+ const paths = selectedReportPaths(values);
256
+ if (paths.some((path) => !path.trim()))
257
+ throw new Error("--result-file must name a readable retained result file");
258
+ return Promise.all(paths.map((path) => canonicalRuntimeRoot(resolve(path))));
259
+ }
260
+ function validateReportSource(values) {
261
+ if (values.latest && values["result-file"] !== undefined)
262
+ throw new Error("--latest and --result-file are exclusive");
263
+ if (values["results-root"] !== undefined && !values.latest)
264
+ throw new Error("--results-root requires --latest");
265
+ }
266
+ function selectedReportPaths(values) {
267
+ return values.latest
268
+ ? [
269
+ join(values["results-root"] ?? join(process.cwd(), ".sevro", "results"), "latest.json"),
270
+ ]
271
+ : (values["result-file"] ?? []);
272
+ }
273
+ function reportErrorMessage(error) {
274
+ return error instanceof Error ? error.message : "invalid report input";
275
+ }
276
+ //# sourceMappingURL=report.js.map