@bjoernrochel/sevro 1.0.0 → 1.1.0

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 (403) 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 +156 -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/isolation-roots.ts +3 -2
  392. package/src/hosts/process-lifecycle.ts +4 -6
  393. package/src/hosts/protected-worktrees.ts +2 -1
  394. package/src/hosts/skill-hook-handler.ts +1 -1
  395. package/src/hosts/skill-hooks.ts +10 -2
  396. package/src/provenance.ts +7 -6
  397. package/src/results-lock.ts +4 -4
  398. package/src/run-owner.ts +15 -9
  399. package/src/runner-paths.ts +21 -0
  400. package/src/runtime/hook-processes.ts +2 -1
  401. package/src/runtime/hooks.ts +9 -7
  402. package/src/runtime-process.ts +194 -0
  403. package/src/sqlite-lock.ts +25 -0
@@ -0,0 +1,206 @@
1
+ import { isMissingFile } from "./tools.js";
2
+ import { lstat, mkdir, readlink, readdir, realpath, rm, symlink, copyFile, } from "node:fs/promises";
3
+ import { allocateWorkspace, removeWorkspace } from "./workspace-state.js";
4
+ import { dirname, isAbsolute, join, resolve } from "node:path";
5
+ import { insideRoot as contained } from "../paths.js";
6
+ import { runGit } from "../git.js";
7
+ import { fixtureParts } from "./preparation.js";
8
+ const MAX_GIT_OUTPUT = 16 * 1024 * 1024;
9
+ const MAX_UNTRACKED_BYTES = 32 * 1024 * 1024;
10
+ const MAX_UNTRACKED_FILES = 1024;
11
+ const HIDDEN_ROOTS = [".agents", ".claude", ".codex", ".git"];
12
+ function excluded(path, roots) {
13
+ return roots.some((root) => path === root || path.startsWith(`${root}/`));
14
+ }
15
+ async function git(cwd, args, input) {
16
+ try {
17
+ return await runGit({
18
+ cwd,
19
+ args,
20
+ input,
21
+ identity: "advisory",
22
+ label: "advisory fixture",
23
+ maxOutputBytes: MAX_GIT_OUTPUT,
24
+ });
25
+ }
26
+ catch (cause) {
27
+ throw new Error("advisory fixture Git operation failed", { cause });
28
+ }
29
+ }
30
+ function requireRelativeSymlink(root, path, target) {
31
+ if (isAbsolute(target) || !contained(root, resolve(dirname(path), target)))
32
+ throw new Error("advisory fixture contains an escaping symlink");
33
+ }
34
+ async function checkSymlink(root, path) {
35
+ const target = await readlink(path);
36
+ requireRelativeSymlink(root, path, target);
37
+ const actual = await realpath(path).catch((cause) => {
38
+ throw new Error("advisory fixture contains a broken symlink", { cause });
39
+ });
40
+ if (!contained(root, actual))
41
+ throw new Error("advisory fixture contains an escaping symlink");
42
+ }
43
+ async function checkAdvisoryEntry(root, path, entry) {
44
+ if (entry.isDirectory())
45
+ await checkSymlinks(root, path);
46
+ else if (entry.isSymbolicLink())
47
+ await checkSymlink(root, path);
48
+ }
49
+ async function checkSymlinks(root, directory = root) {
50
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
51
+ if (directory === root && entry.name === ".git")
52
+ continue;
53
+ await checkAdvisoryEntry(root, join(directory, entry.name), entry);
54
+ }
55
+ }
56
+ async function safeParent(root, parts) {
57
+ let directory = root;
58
+ for (const part of parts.slice(0, -1)) {
59
+ directory = join(directory, part);
60
+ await mkdir(directory, { mode: 0o700 }).catch((error) => {
61
+ if (error.code !== "EEXIST")
62
+ throw error;
63
+ });
64
+ if (!(await lstat(directory)).isDirectory())
65
+ throw new Error("advisory fixture path has a non-directory parent");
66
+ }
67
+ }
68
+ async function copyUntracked(source, destination, exclusions) {
69
+ const output = await git(source, [
70
+ "ls-files",
71
+ "--others",
72
+ "--exclude-standard",
73
+ "-z",
74
+ ]);
75
+ const paths = new TextDecoder("utf-8", { fatal: true })
76
+ .decode(output)
77
+ .split("\0")
78
+ .filter(Boolean)
79
+ .filter((path) => !excluded(path, exclusions));
80
+ if (paths.length > MAX_UNTRACKED_FILES)
81
+ throw new Error("advisory fixture has too many untracked files");
82
+ const state = { totalBytes: 0 };
83
+ for (const path of paths)
84
+ await copyUntrackedPath(path, source, destination, state);
85
+ }
86
+ async function untrackedTargetExists(path) {
87
+ return lstat(path).then(() => true, (error) => {
88
+ if (isMissingFile(error))
89
+ return false;
90
+ throw error;
91
+ });
92
+ }
93
+ async function copyUntrackedPath(path, source, destination, state) {
94
+ const parts = fixtureParts(path), from = join(source, ...parts), to = join(destination, ...parts);
95
+ if (!contained(source, from) || !contained(destination, to))
96
+ throw new Error("advisory fixture path escapes its root");
97
+ const info = await lstat(from);
98
+ state.totalBytes += info.size;
99
+ if (state.totalBytes > MAX_UNTRACKED_BYTES)
100
+ throw new Error("advisory fixture untracked files exceed the size limit");
101
+ await safeParent(destination, parts);
102
+ if (await untrackedTargetExists(to))
103
+ throw new Error("advisory fixture untracked path already exists");
104
+ await copyUntrackedEntry(from, to, destination, info);
105
+ }
106
+ async function copyUntrackedEntry(from, to, destination, info) {
107
+ if (info.isFile()) {
108
+ await copyFile(from, to);
109
+ return;
110
+ }
111
+ if (info.isSymbolicLink()) {
112
+ const target = await readlink(from);
113
+ requireRelativeSymlink(destination, to, target);
114
+ await symlink(target, to);
115
+ return;
116
+ }
117
+ throw new Error("advisory fixture has an unsupported untracked entry");
118
+ }
119
+ /** Capture the candidate fixture's baseline before the host edits it. */
120
+ export async function advisoryBaseRevision(workspace) {
121
+ const revision = (await git(workspace, ["rev-parse", "--verify", "HEAD"]))
122
+ .toString("utf8")
123
+ .trim();
124
+ if (!/^[a-f0-9]{40,64}$/.test(revision))
125
+ throw new Error("advisory fixture has no valid Git baseline");
126
+ return revision;
127
+ }
128
+ /** Rebuild a condition-blind Git view of the complete candidate change. */
129
+ export async function buildBlindAdvisoryFixture(candidateWorkspace, options) {
130
+ requireAdvisorySource(candidateWorkspace, options.baseRevision);
131
+ const source = await realpath(candidateWorkspace);
132
+ const exclusions = advisoryExclusions(options);
133
+ const temporary = await allocateWorkspace();
134
+ try {
135
+ const destination = await realpath(temporary);
136
+ await initializeAdvisoryBaseline(source, destination, options.baseRevision, exclusions);
137
+ await applyCandidateChange(source, destination, options.baseRevision, exclusions);
138
+ return destination;
139
+ }
140
+ catch (error) {
141
+ await removeWorkspace(temporary);
142
+ throw error;
143
+ }
144
+ }
145
+ function requireAdvisorySource(workspace, revision) {
146
+ if (!isAbsolute(workspace) || !/^[a-f0-9]{40,64}$/.test(revision))
147
+ throw new Error("invalid advisory fixture source");
148
+ }
149
+ function advisoryExclusions(options) {
150
+ const paths = options.excludedPaths ?? [];
151
+ for (const path of paths) {
152
+ fixtureParts(path);
153
+ if (path.toLowerCase() === ".git" || path.toLowerCase().startsWith(".git/"))
154
+ throw new Error("invalid advisory exclusion");
155
+ }
156
+ return [...HIDDEN_ROOTS, ...paths];
157
+ }
158
+ async function initializeAdvisoryBaseline(source, destination, baseRevision, exclusions) {
159
+ await git(source, [
160
+ "clone",
161
+ "--local",
162
+ "--no-hardlinks",
163
+ "--no-checkout",
164
+ "--quiet",
165
+ source,
166
+ destination,
167
+ ]);
168
+ await git(destination, ["checkout", "--detach", "--quiet", baseRevision]);
169
+ for (const root of exclusions)
170
+ await rm(join(destination, ...fixtureParts(root)), {
171
+ recursive: true,
172
+ force: true,
173
+ });
174
+ await rm(join(destination, ".git"), { recursive: true, force: true });
175
+ await git(destination, ["init", "--quiet", "--initial-branch=main"]);
176
+ await git(destination, ["add", "-A"]);
177
+ await git(destination, [
178
+ "commit",
179
+ "--quiet",
180
+ "--allow-empty",
181
+ "-m",
182
+ "Advisory baseline",
183
+ ]);
184
+ }
185
+ async function applyCandidateChange(source, destination, baseRevision, exclusions) {
186
+ const pathspec = exclusions.flatMap((path) => [
187
+ `:(exclude)${path}`,
188
+ `:(exclude)${path}/**`,
189
+ ]);
190
+ const patch = await git(source, [
191
+ "diff",
192
+ "--binary",
193
+ "--no-ext-diff",
194
+ "--no-textconv",
195
+ "--no-renames",
196
+ baseRevision,
197
+ "--",
198
+ ".",
199
+ ...pathspec,
200
+ ]);
201
+ if (patch.length)
202
+ await git(destination, ["apply", "--binary", "-"], patch);
203
+ await copyUntracked(source, destination, exclusions);
204
+ await checkSymlinks(destination);
205
+ }
206
+ //# sourceMappingURL=advisory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advisory.js","sourceRoot":"","sources":["advisory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Dirent, Stats } from \"node:fs\";\nimport { isMissingFile } from \"./tools\";\nimport {\n lstat,\n mkdir,\n readlink,\n readdir,\n realpath,\n rm,\n symlink,\n copyFile,\n} from \"node:fs/promises\";\nimport { allocateWorkspace, removeWorkspace } from \"./workspace-state\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { insideRoot as contained } from \"../paths\";\nimport { runGit } from \"../git\";\nimport { fixtureParts } from \"./preparation\";\n\nconst MAX_GIT_OUTPUT = 16 * 1024 * 1024;\nconst MAX_UNTRACKED_BYTES = 32 * 1024 * 1024;\nconst MAX_UNTRACKED_FILES = 1024;\nconst HIDDEN_ROOTS = [\".agents\", \".claude\", \".codex\", \".git\"];\n\nfunction excluded(path: string, roots: string[]): boolean {\n return roots.some((root) => path === root || path.startsWith(`${root}/`));\n}\n\nasync function git(\n cwd: string,\n args: string[],\n input?: Uint8Array,\n): Promise<Buffer> {\n try {\n return await runGit({\n cwd,\n args,\n input,\n identity: \"advisory\",\n label: \"advisory fixture\",\n maxOutputBytes: MAX_GIT_OUTPUT,\n });\n } catch (cause) {\n throw new Error(\"advisory fixture Git operation failed\", { cause });\n }\n}\n\nfunction requireRelativeSymlink(\n root: string,\n path: string,\n target: string,\n): void {\n if (isAbsolute(target) || !contained(root, resolve(dirname(path), target)))\n throw new Error(\"advisory fixture contains an escaping symlink\");\n}\nasync function checkSymlink(root: string, path: string): Promise<void> {\n const target = await readlink(path);\n requireRelativeSymlink(root, path, target);\n const actual = await realpath(path).catch((cause: unknown) => {\n throw new Error(\"advisory fixture contains a broken symlink\", { cause });\n });\n if (!contained(root, actual))\n throw new Error(\"advisory fixture contains an escaping symlink\");\n}\nasync function checkAdvisoryEntry(\n root: string,\n path: string,\n entry: Dirent,\n): Promise<void> {\n if (entry.isDirectory()) await checkSymlinks(root, path);\n else if (entry.isSymbolicLink()) await checkSymlink(root, path);\n}\nasync function checkSymlinks(root: string, directory = root): Promise<void> {\n for (const entry of await readdir(directory, { withFileTypes: true })) {\n if (directory === root && entry.name === \".git\") continue;\n await checkAdvisoryEntry(root, join(directory, entry.name), entry);\n }\n}\n\nasync function safeParent(root: string, parts: string[]): Promise<void> {\n let directory = root;\n for (const part of parts.slice(0, -1)) {\n directory = join(directory, part);\n await mkdir(directory, { mode: 0o700 }).catch((error: unknown) => {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n });\n if (!(await lstat(directory)).isDirectory())\n throw new Error(\"advisory fixture path has a non-directory parent\");\n }\n}\n\nasync function copyUntracked(\n source: string,\n destination: string,\n exclusions: string[],\n): Promise<void> {\n const output = await git(source, [\n \"ls-files\",\n \"--others\",\n \"--exclude-standard\",\n \"-z\",\n ]);\n const paths = new TextDecoder(\"utf-8\", { fatal: true })\n .decode(output)\n .split(\"\\0\")\n .filter(Boolean)\n .filter((path) => !excluded(path, exclusions));\n if (paths.length > MAX_UNTRACKED_FILES)\n throw new Error(\"advisory fixture has too many untracked files\");\n const state = { totalBytes: 0 };\n for (const path of paths)\n await copyUntrackedPath(path, source, destination, state);\n}\n\nasync function untrackedTargetExists(path: string): Promise<boolean> {\n return lstat(path).then(\n () => true,\n (error: unknown) => {\n if (isMissingFile(error)) return false;\n throw error;\n },\n );\n}\nasync function copyUntrackedPath(\n path: string,\n source: string,\n destination: string,\n state: { totalBytes: number },\n): Promise<void> {\n const parts = fixtureParts(path),\n from = join(source, ...parts),\n to = join(destination, ...parts);\n if (!contained(source, from) || !contained(destination, to))\n throw new Error(\"advisory fixture path escapes its root\");\n const info = await lstat(from);\n state.totalBytes += info.size;\n if (state.totalBytes > MAX_UNTRACKED_BYTES)\n throw new Error(\"advisory fixture untracked files exceed the size limit\");\n await safeParent(destination, parts);\n if (await untrackedTargetExists(to))\n throw new Error(\"advisory fixture untracked path already exists\");\n await copyUntrackedEntry(from, to, destination, info);\n}\nasync function copyUntrackedEntry(\n from: string,\n to: string,\n destination: string,\n info: Stats,\n): Promise<void> {\n if (info.isFile()) {\n await copyFile(from, to);\n return;\n }\n if (info.isSymbolicLink()) {\n const target = await readlink(from);\n requireRelativeSymlink(destination, to, target);\n await symlink(target, to);\n return;\n }\n throw new Error(\"advisory fixture has an unsupported untracked entry\");\n}\n\nexport interface BlindAdvisoryOptions {\n /** Fixture HEAD before candidate execution. */\n baseRevision: string;\n /** Additional root-relative evaluator paths to omit from the judge view. */\n excludedPaths?: string[];\n}\n\n/** Capture the candidate fixture's baseline before the host edits it. */\nexport async function advisoryBaseRevision(workspace: string): Promise<string> {\n const revision = (await git(workspace, [\"rev-parse\", \"--verify\", \"HEAD\"]))\n .toString(\"utf8\")\n .trim();\n if (!/^[a-f0-9]{40,64}$/.test(revision))\n throw new Error(\"advisory fixture has no valid Git baseline\");\n return revision;\n}\n\n/** Rebuild a condition-blind Git view of the complete candidate change. */\nexport async function buildBlindAdvisoryFixture(\n candidateWorkspace: string,\n options: BlindAdvisoryOptions,\n): Promise<string> {\n requireAdvisorySource(candidateWorkspace, options.baseRevision);\n const source = await realpath(candidateWorkspace);\n const exclusions = advisoryExclusions(options);\n const temporary = await allocateWorkspace();\n try {\n const destination = await realpath(temporary);\n await initializeAdvisoryBaseline(\n source,\n destination,\n options.baseRevision,\n exclusions,\n );\n await applyCandidateChange(\n source,\n destination,\n options.baseRevision,\n exclusions,\n );\n return destination;\n } catch (error) {\n await removeWorkspace(temporary);\n throw error;\n }\n}\n\nfunction requireAdvisorySource(workspace: string, revision: string): void {\n if (!isAbsolute(workspace) || !/^[a-f0-9]{40,64}$/.test(revision))\n throw new Error(\"invalid advisory fixture source\");\n}\nfunction advisoryExclusions(options: BlindAdvisoryOptions): string[] {\n const paths = options.excludedPaths ?? [];\n for (const path of paths) {\n fixtureParts(path);\n if (path.toLowerCase() === \".git\" || path.toLowerCase().startsWith(\".git/\"))\n throw new Error(\"invalid advisory exclusion\");\n }\n return [...HIDDEN_ROOTS, ...paths];\n}\nasync function initializeAdvisoryBaseline(\n source: string,\n destination: string,\n baseRevision: string,\n exclusions: string[],\n): Promise<void> {\n await git(source, [\n \"clone\",\n \"--local\",\n \"--no-hardlinks\",\n \"--no-checkout\",\n \"--quiet\",\n source,\n destination,\n ]);\n await git(destination, [\"checkout\", \"--detach\", \"--quiet\", baseRevision]);\n for (const root of exclusions)\n await rm(join(destination, ...fixtureParts(root)), {\n recursive: true,\n force: true,\n });\n await rm(join(destination, \".git\"), { recursive: true, force: true });\n await git(destination, [\"init\", \"--quiet\", \"--initial-branch=main\"]);\n await git(destination, [\"add\", \"-A\"]);\n await git(destination, [\n \"commit\",\n \"--quiet\",\n \"--allow-empty\",\n \"-m\",\n \"Advisory baseline\",\n ]);\n}\nasync function applyCandidateChange(\n source: string,\n destination: string,\n baseRevision: string,\n exclusions: string[],\n): Promise<void> {\n const pathspec = exclusions.flatMap((path) => [\n `:(exclude)${path}`,\n `:(exclude)${path}/**`,\n ]);\n const patch = await git(source, [\n \"diff\",\n \"--binary\",\n \"--no-ext-diff\",\n \"--no-textconv\",\n \"--no-renames\",\n baseRevision,\n \"--\",\n \".\",\n ...pathspec,\n ]);\n if (patch.length) await git(destination, [\"apply\", \"--binary\", \"-\"], patch);\n await copyUntracked(source, destination, exclusions);\n await checkSymlinks(destination);\n}\n"]}
@@ -0,0 +1,41 @@
1
+ import { prepareFixtureTools, installFixtureTools, isMissingFile, } from "./tools.js";
2
+ import { lstat, mkdir, realpath } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ /** Validate executable fixture tools without allowing path traversal. */
5
+ export function prepareFixtureBin(value) {
6
+ return prepareFixtureTools(value, /^[A-Za-z][A-Za-z0-9._-]*$/, 64, "invalid fixture binaries");
7
+ }
8
+ /** Install declared tools inside the fixture's Git metadata, after commits. */
9
+ export async function installFixtureBin(bin, workspace) {
10
+ if (!bin || Object.keys(bin).length === 0)
11
+ return;
12
+ const directory = join(workspace, ".git", "fixture-bin");
13
+ await mkdir(directory, { recursive: true, mode: 0o700 });
14
+ await installFixtureTools(bin, directory, {
15
+ directory: "fixture binary directory is invalid",
16
+ target: "fixture binary target is invalid",
17
+ });
18
+ }
19
+ /** Setup may also install trusted tools, such as a repository ticket stub. */
20
+ export async function fixtureBinDirectory(workspace) {
21
+ const path = join(workspace, ".git", "fixture-bin");
22
+ try {
23
+ await validateBinDirectory(path);
24
+ const canonicalWorkspace = await realpath(workspace);
25
+ const canonicalBin = await realpath(path);
26
+ if (canonicalBin !== join(canonicalWorkspace, ".git", "fixture-bin"))
27
+ throw new Error("fixture binary directory escapes the workspace");
28
+ return path;
29
+ }
30
+ catch (error) {
31
+ if (isMissingFile(error))
32
+ return null;
33
+ throw error;
34
+ }
35
+ }
36
+ async function validateBinDirectory(path) {
37
+ const entry = await lstat(path);
38
+ if (!entry.isDirectory() || entry.isSymbolicLink())
39
+ throw new Error("fixture binary directory is invalid");
40
+ }
41
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["bin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,GACd,mBAAgB;AACjB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,yBAAyB;AAC1D,OAAO,EAAE,IAAI,EAAE,kBAAkB;AAEjC,yEAAyE;AACzE,MAAM,UAAU,iBAAiB,CAC/B,KAAc;IAEd,OAAO,mBAAmB,CACxB,KAAK,EACL,2BAA2B,EAC3B,EAAE,EACF,0BAA0B,CAC3B,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAAuC,EACvC,SAAiB;IAEjB,IAAI,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAClD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IACzD,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,MAAM,mBAAmB,CAAC,GAAG,EAAE,SAAS,EAAE;QACxC,SAAS,EAAE,qCAAqC;QAChD,MAAM,EAAE,kCAAkC;KAC3C,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,SAAiB;IAEjB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IACpD,IAAI,CAAC;QACH,MAAM,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,kBAAkB,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,CAAC;QACrD,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,YAAY,KAAK,IAAI,CAAC,kBAAkB,EAAE,MAAM,EAAE,aAAa,CAAC;YAClE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,aAAa,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACtC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,oBAAoB,CAAC,IAAY;IAC9C,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,cAAc,EAAE;QAChD,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["import {\n prepareFixtureTools,\n installFixtureTools,\n isMissingFile,\n} from \"./tools\";\nimport { lstat, mkdir, realpath } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/** Validate executable fixture tools without allowing path traversal. */\nexport function prepareFixtureBin(\n value: unknown,\n): Record<string, string> | null {\n return prepareFixtureTools(\n value,\n /^[A-Za-z][A-Za-z0-9._-]*$/,\n 64,\n \"invalid fixture binaries\",\n );\n}\n\n/** Install declared tools inside the fixture's Git metadata, after commits. */\nexport async function installFixtureBin(\n bin: Record<string, string> | undefined,\n workspace: string,\n): Promise<void> {\n if (!bin || Object.keys(bin).length === 0) return;\n const directory = join(workspace, \".git\", \"fixture-bin\");\n await mkdir(directory, { recursive: true, mode: 0o700 });\n await installFixtureTools(bin, directory, {\n directory: \"fixture binary directory is invalid\",\n target: \"fixture binary target is invalid\",\n });\n}\n\n/** Setup may also install trusted tools, such as a repository ticket stub. */\nexport async function fixtureBinDirectory(\n workspace: string,\n): Promise<string | null> {\n const path = join(workspace, \".git\", \"fixture-bin\");\n try {\n await validateBinDirectory(path);\n const canonicalWorkspace = await realpath(workspace);\n const canonicalBin = await realpath(path);\n if (canonicalBin !== join(canonicalWorkspace, \".git\", \"fixture-bin\"))\n throw new Error(\"fixture binary directory escapes the workspace\");\n return path;\n } catch (error) {\n if (isMissingFile(error)) return null;\n throw error;\n }\n}\n\nasync function validateBinDirectory(path: string): Promise<void> {\n const entry = await lstat(path);\n if (!entry.isDirectory() || entry.isSymbolicLink())\n throw new Error(\"fixture binary directory is invalid\");\n}\n"]}
@@ -0,0 +1,43 @@
1
+ import { chmod, lstat, readdir, rm } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ function permissionFailure(error) {
4
+ const code = error.code;
5
+ return code === "EACCES" || code === "EPERM";
6
+ }
7
+ async function restoreDirectoryAccess(path) {
8
+ const entry = await lstat(path);
9
+ if (!entry.isDirectory())
10
+ return;
11
+ await chmod(path, (entry.mode & 0o7777) | 0o700);
12
+ for (const child of await readdir(path, { withFileTypes: true }))
13
+ if (child.isDirectory())
14
+ await restoreDirectoryAccess(join(path, child.name));
15
+ }
16
+ /** Remove owned disposable state without following cache or fixture symlinks. */
17
+ export async function removeDisposableDirectory(path) {
18
+ try {
19
+ await rm(path, { recursive: true, force: true });
20
+ }
21
+ catch (error) {
22
+ if (error.code === "ENOTDIR")
23
+ return;
24
+ if (!permissionFailure(error))
25
+ throw error;
26
+ await restoreDirectoryAccess(path);
27
+ await rm(path, { recursive: true, force: true });
28
+ }
29
+ }
30
+ /** Clear a completed fixture while preserving its reserved root for active peers. */
31
+ export async function clearFixtureContents(workspace) {
32
+ const root = await lstat(workspace);
33
+ if (!root.isDirectory())
34
+ throw new Error("fixture workspace is not a directory");
35
+ if ((root.mode & 0o700) !== 0o700)
36
+ await chmod(workspace, (root.mode & 0o7777) | 0o700);
37
+ const names = await readdir(workspace);
38
+ await Promise.all(names.map(async (name) => {
39
+ const path = join(workspace, name);
40
+ await removeDisposableDirectory(path);
41
+ }));
42
+ }
43
+ //# sourceMappingURL=cleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup.js","sourceRoot":"","sources":["cleanup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,yBAAyB;AAC7D,OAAO,EAAE,IAAI,EAAE,kBAAkB;AAEjC,SAAS,iBAAiB,CAAC,KAAc;IACvC,MAAM,IAAI,GAAI,KAA+B,CAAC,IAAI,CAAC;IACnD,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,OAAO,CAAC;AAC/C,CAAC;AAED,KAAK,UAAU,sBAAsB,CAAC,IAAY;IAChD,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;QAAE,OAAO;IACjC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC;IACjD,KAAK,MAAM,KAAK,IAAI,MAAM,OAAO,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;QAC9D,IAAI,KAAK,CAAC,WAAW,EAAE;YACrB,MAAM,sBAAsB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,IAAY;IAC1D,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO;QAChE,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;YAAE,MAAM,KAAK,CAAC;QAC3C,MAAM,sBAAsB,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,SAAiB;IAC1D,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;QACrB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,KAAK;QAC/B,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;IACvC,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACnC,MAAM,yBAAyB,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC,CAAC,CACH,CAAC;AACJ,CAAC","sourcesContent":["import { chmod, lstat, readdir, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nfunction permissionFailure(error: unknown): boolean {\n const code = (error as NodeJS.ErrnoException).code;\n return code === \"EACCES\" || code === \"EPERM\";\n}\n\nasync function restoreDirectoryAccess(path: string): Promise<void> {\n const entry = await lstat(path);\n if (!entry.isDirectory()) return;\n await chmod(path, (entry.mode & 0o7777) | 0o700);\n for (const child of await readdir(path, { withFileTypes: true }))\n if (child.isDirectory())\n await restoreDirectoryAccess(join(path, child.name));\n}\n\n/** Remove owned disposable state without following cache or fixture symlinks. */\nexport async function removeDisposableDirectory(path: string): Promise<void> {\n try {\n await rm(path, { recursive: true, force: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOTDIR\") return;\n if (!permissionFailure(error)) throw error;\n await restoreDirectoryAccess(path);\n await rm(path, { recursive: true, force: true });\n }\n}\n\n/** Clear a completed fixture while preserving its reserved root for active peers. */\nexport async function clearFixtureContents(workspace: string): Promise<void> {\n const root = await lstat(workspace);\n if (!root.isDirectory())\n throw new Error(\"fixture workspace is not a directory\");\n if ((root.mode & 0o700) !== 0o700)\n await chmod(workspace, (root.mode & 0o7777) | 0o700);\n const names = await readdir(workspace);\n await Promise.all(\n names.map(async (name) => {\n const path = join(workspace, name);\n await removeDisposableDirectory(path);\n }),\n );\n}\n"]}
@@ -0,0 +1,94 @@
1
+ import { appendFile, mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { allocateWorkspace, removeWorkspace } from "./workspace-state.js";
4
+ import { fixtureBinDirectory, installFixtureBin } from "./bin.js";
5
+ import { installGitHooks } from "./git-hooks.js";
6
+ import { materializeGeneratedFixture, } from "./generated.js";
7
+ import { runFixtureSetup } from "./setup.js";
8
+ import { fixtureParts, safePreparationTarget, } from "./preparation.js";
9
+ import { applyRepositoryOverlay, cloneRepositorySource, } from "./repository.js";
10
+ import { EvaluationConfigurationError, } from "../evaluation/types.js";
11
+ export async function createFixture(source, options) {
12
+ const paths = inlineFixturePaths(source);
13
+ const artifacts = options.artifacts ?? [];
14
+ const workspace = options.reservedWorkspace ?? (await allocateWorkspace());
15
+ try {
16
+ await initializeFixtureHistory(source, options.sources, workspace);
17
+ await installDeclaredFixtureTools(source, workspace);
18
+ await writeInlineFixtureFiles(paths, workspace);
19
+ await setupFixture(options.setup, workspace, options.projectRoot, options.signal, options.candidateRuntimeRoot);
20
+ await mountPreparationArtifacts(artifacts, workspace);
21
+ await excludePreparationArtifacts(artifacts, workspace);
22
+ await fixtureBinDirectory(workspace);
23
+ return workspace;
24
+ }
25
+ catch (error) {
26
+ if (!options.reservedWorkspace)
27
+ await removeWorkspace(workspace);
28
+ throw error;
29
+ }
30
+ }
31
+ function inlineFixturePaths(source) {
32
+ return Object.entries(source.kind === "inline" ? (source.files ?? {}) : {}).map(([path, content]) => ({ path, parts: fixtureParts(path), content }));
33
+ }
34
+ async function cloneFixtureRepository(source, sources, workspace) {
35
+ if (!sources)
36
+ throw new EvaluationConfigurationError("repository source root is required and must be absolute");
37
+ await cloneRepositorySource(source.fixture.sourceRef, sources, source.repository, workspace);
38
+ }
39
+ async function initializeFixtureHistory(source, sources, workspace) {
40
+ switch (source.kind) {
41
+ case "repository":
42
+ await cloneFixtureRepository(source, sources, workspace);
43
+ await applyRepositoryOverlay(source.fixture, workspace);
44
+ break;
45
+ case "generated":
46
+ await materializeGeneratedFixture(source.fixture, workspace);
47
+ break;
48
+ case "inline":
49
+ break;
50
+ }
51
+ }
52
+ async function installDeclaredFixtureTools(source, workspace) {
53
+ const fixture = source.kind === "inline" ? undefined : source.fixture;
54
+ await installFixtureBin(fixture?.bin, workspace);
55
+ await installGitHooks(fixture?.hooks, workspace);
56
+ }
57
+ async function writeInlineFixtureFiles(files, workspace) {
58
+ for (const file of files) {
59
+ const target = join(workspace, ...file.parts);
60
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
61
+ await writeFile(target, file.content, { flag: "wx", mode: 0o600 });
62
+ }
63
+ }
64
+ async function setupFixture(setup, workspace, projectRoot, signal, candidateRuntimeRoot) {
65
+ if (!setup)
66
+ return;
67
+ await runFixtureSetup(setup, {
68
+ workspace,
69
+ projectRoot,
70
+ fixtureBinDir: (await fixtureBinDirectory(workspace)) ?? undefined,
71
+ signal,
72
+ candidateRuntimeRoot,
73
+ });
74
+ }
75
+ async function mountPreparationArtifacts(artifacts, workspace) {
76
+ for (const artifact of artifacts) {
77
+ const target = await safePreparationTarget(workspace, artifact.relativePath);
78
+ await writeFile(target, artifact.bytes, {
79
+ flag: "wx",
80
+ mode: artifact.executable ? 0o700 : 0o600,
81
+ });
82
+ }
83
+ }
84
+ async function excludePreparationArtifacts(artifacts, workspace) {
85
+ const gitExcluded = artifacts.filter((artifact) => artifact.gitExclude);
86
+ if (!gitExcluded.length)
87
+ return;
88
+ const special = new Set(["\\", "*", "?", "[", "]", "#", "!", " "]);
89
+ const patterns = gitExcluded.map((artifact) => Array.from(artifact.relativePath)
90
+ .map((character) => special.has(character) ? `\\${character}` : character)
91
+ .join(""));
92
+ await appendFile(join(workspace, ".git", "info", "exclude"), `\n${patterns.map((path) => `/${path}`).join("\n")}\n`);
93
+ }
94
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.js","sourceRoot":"","sources":["create.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFile, mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { allocateWorkspace, removeWorkspace } from \"./workspace-state\";\nimport { fixtureBinDirectory, installFixtureBin } from \"./bin\";\nimport { installGitHooks } from \"./git-hooks\";\nimport {\n materializeGeneratedFixture,\n type GeneratedFixture,\n} from \"./generated\";\nimport { runFixtureSetup, type FixtureSetup } from \"./setup\";\nimport {\n fixtureParts,\n safePreparationTarget,\n type InlineArtifact,\n type PreparationSources,\n} from \"./preparation\";\nimport {\n applyRepositoryOverlay,\n cloneRepositorySource,\n type RepositoryFixture,\n type RepositorySource,\n} from \"./repository\";\nimport {\n EvaluationConfigurationError,\n type ResolvedCase,\n} from \"../evaluation/types\";\n\nexport type FixtureSource<Repository = RepositorySource> =\n | { kind: \"inline\"; files: ResolvedCase[\"fixture\"][\"files\"] }\n | { kind: \"generated\"; fixture: GeneratedFixture }\n | { kind: \"repository\"; fixture: RepositoryFixture; repository: Repository };\n\ninterface FixtureOptions {\n artifacts?: InlineArtifact[];\n sources?: PreparationSources;\n setup?: FixtureSetup | null;\n projectRoot: string;\n signal?: AbortSignal;\n reservedWorkspace?: string;\n candidateRuntimeRoot?: string;\n}\n\nexport async function createFixture(\n source: FixtureSource,\n options: FixtureOptions,\n): Promise<string> {\n const paths = inlineFixturePaths(source);\n const artifacts = options.artifacts ?? [];\n const workspace = options.reservedWorkspace ?? (await allocateWorkspace());\n try {\n await initializeFixtureHistory(source, options.sources, workspace);\n await installDeclaredFixtureTools(source, workspace);\n await writeInlineFixtureFiles(paths, workspace);\n await setupFixture(\n options.setup,\n workspace,\n options.projectRoot,\n options.signal,\n options.candidateRuntimeRoot,\n );\n await mountPreparationArtifacts(artifacts, workspace);\n await excludePreparationArtifacts(artifacts, workspace);\n await fixtureBinDirectory(workspace);\n return workspace;\n } catch (error) {\n if (!options.reservedWorkspace) await removeWorkspace(workspace);\n throw error;\n }\n}\n\nfunction inlineFixturePaths(source: FixtureSource) {\n return Object.entries(\n source.kind === \"inline\" ? (source.files ?? {}) : {},\n ).map(([path, content]) => ({ path, parts: fixtureParts(path), content }));\n}\n\nasync function cloneFixtureRepository(\n source: Extract<FixtureSource, { kind: \"repository\" }>,\n sources: PreparationSources | undefined,\n workspace: string,\n): Promise<void> {\n if (!sources)\n throw new EvaluationConfigurationError(\n \"repository source root is required and must be absolute\",\n );\n await cloneRepositorySource(\n source.fixture.sourceRef,\n sources,\n source.repository,\n workspace,\n );\n}\n\nasync function initializeFixtureHistory(\n source: FixtureSource,\n sources: PreparationSources | undefined,\n workspace: string,\n): Promise<void> {\n switch (source.kind) {\n case \"repository\":\n await cloneFixtureRepository(source, sources, workspace);\n await applyRepositoryOverlay(source.fixture, workspace);\n break;\n case \"generated\":\n await materializeGeneratedFixture(source.fixture, workspace);\n break;\n case \"inline\":\n break;\n }\n}\n\nasync function installDeclaredFixtureTools(\n source: FixtureSource,\n workspace: string,\n): Promise<void> {\n const fixture = source.kind === \"inline\" ? undefined : source.fixture;\n await installFixtureBin(fixture?.bin, workspace);\n await installGitHooks(fixture?.hooks, workspace);\n}\n\nasync function writeInlineFixtureFiles(\n files: ReturnType<typeof inlineFixturePaths>,\n workspace: string,\n): Promise<void> {\n for (const file of files) {\n const target = join(workspace, ...file.parts);\n await mkdir(dirname(target), { recursive: true, mode: 0o700 });\n await writeFile(target, file.content, { flag: \"wx\", mode: 0o600 });\n }\n}\n\nasync function setupFixture(\n setup: FixtureSetup | null | undefined,\n workspace: string,\n projectRoot: string,\n signal?: AbortSignal,\n candidateRuntimeRoot?: string,\n): Promise<void> {\n if (!setup) return;\n await runFixtureSetup(setup, {\n workspace,\n projectRoot,\n fixtureBinDir: (await fixtureBinDirectory(workspace)) ?? undefined,\n signal,\n candidateRuntimeRoot,\n });\n}\n\nasync function mountPreparationArtifacts(\n artifacts: InlineArtifact[],\n workspace: string,\n): Promise<void> {\n for (const artifact of artifacts) {\n const target = await safePreparationTarget(\n workspace,\n artifact.relativePath,\n );\n await writeFile(target, artifact.bytes, {\n flag: \"wx\",\n mode: artifact.executable ? 0o700 : 0o600,\n });\n }\n}\n\nasync function excludePreparationArtifacts(\n artifacts: InlineArtifact[],\n workspace: string,\n): Promise<void> {\n const gitExcluded = artifacts.filter((artifact) => artifact.gitExclude);\n if (!gitExcluded.length) return;\n const special = new Set([\"\\\\\", \"*\", \"?\", \"[\", \"]\", \"#\", \"!\", \" \"]);\n const patterns = gitExcluded.map((artifact) =>\n Array.from(artifact.relativePath)\n .map((character) =>\n special.has(character) ? `\\\\${character}` : character,\n )\n .join(\"\"),\n );\n await appendFile(\n join(workspace, \".git\", \"info\", \"exclude\"),\n `\\n${patterns.map((path) => `/${path}`).join(\"\\n\")}\\n`,\n );\n}\n"]}
@@ -0,0 +1,181 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { canonicalJson } from "../identity.js";
4
+ import { prepareFixtureBin } from "./bin.js";
5
+ import { prepareGitHooks } from "./git-hooks.js";
6
+ import { fixtureParts } from "./preparation.js";
7
+ import { runGit } from "../git.js";
8
+ const MAX_COMMITS = 128;
9
+ const MAX_FILES = 1024;
10
+ const MAX_BYTES = 32 * 1024 * 1024;
11
+ const MAX_GIT_OUTPUT = 64 * 1024;
12
+ function validFiles(value) {
13
+ return (Boolean(value && typeof value === "object" && !Array.isArray(value)) &&
14
+ Object.values(value).every((item) => typeof item === "string"));
15
+ }
16
+ function validPath(path) {
17
+ const parts = fixtureParts(path);
18
+ if (parts.some((part) => part.toLowerCase() === ".git"))
19
+ throw new Error("generated fixture cannot write repository metadata");
20
+ }
21
+ function fixtureRecord(value) {
22
+ if (!value || typeof value !== "object" || Array.isArray(value))
23
+ throw new Error("invalid generated fixture");
24
+ return value;
25
+ }
26
+ function fixtureKeys(fixture) {
27
+ const keys = [
28
+ "kind",
29
+ "commits",
30
+ "files",
31
+ "staged",
32
+ "commitFiles",
33
+ "hooks",
34
+ "bin",
35
+ ];
36
+ if (fixture.kind !== "generated" ||
37
+ Object.keys(fixture).some((key) => !keys.includes(key)))
38
+ throw new Error("invalid generated fixture");
39
+ }
40
+ function stringArray(value) {
41
+ return (Array.isArray(value) &&
42
+ value.every((item) => typeof item === "string"));
43
+ }
44
+ function fixtureOptionalFields(fixture) {
45
+ if (fixture.files !== undefined && !validFiles(fixture.files))
46
+ throw new Error("invalid generated fixture");
47
+ validateStagedField(fixture.staged);
48
+ if (fixture.commitFiles !== undefined &&
49
+ typeof fixture.commitFiles !== "boolean")
50
+ throw new Error("invalid generated fixture");
51
+ }
52
+ function validateStagedField(value) {
53
+ if (value !== undefined && !stringArray(value))
54
+ throw new Error("invalid generated fixture");
55
+ }
56
+ function fixtureCommits(value) {
57
+ if (!Array.isArray(value) || value.length > MAX_COMMITS)
58
+ throw new Error("invalid generated fixture");
59
+ return value;
60
+ }
61
+ function commitMessage(value) {
62
+ return (typeof value === "string" &&
63
+ Boolean(value.trim()) &&
64
+ Buffer.byteLength(value, "utf8") <= 4096);
65
+ }
66
+ function commitRecord(value) {
67
+ if (!value || typeof value !== "object" || Array.isArray(value))
68
+ throw new Error("invalid generated fixture commit");
69
+ return value;
70
+ }
71
+ function prepareCommit(value) {
72
+ const commit = commitRecord(value);
73
+ if (Object.keys(commit).some((key) => key !== "message" && key !== "files") ||
74
+ !commitMessage(commit.message))
75
+ throw new Error("invalid generated fixture commit");
76
+ if (!validFiles(commit.files) || Object.keys(commit.files).length === 0)
77
+ throw new Error("invalid generated fixture commit");
78
+ return { message: commit.message, files: commit.files };
79
+ }
80
+ function countFiles(contents, files) {
81
+ for (const [path, content] of Object.entries(files)) {
82
+ validPath(path);
83
+ contents.paths.push(path);
84
+ contents.files++;
85
+ contents.bytes +=
86
+ Buffer.byteLength(path, "utf8") + Buffer.byteLength(content, "utf8");
87
+ }
88
+ }
89
+ function validateContents(contents) {
90
+ if (contents.files > MAX_FILES || contents.bytes > MAX_BYTES)
91
+ throw new Error("generated fixture exceeds the size limit");
92
+ const normalized = [...new Set(contents.paths)].map((path) => path.toLowerCase());
93
+ for (const [index, path] of normalized.entries()) {
94
+ if (normalized.slice(0, index).some((other) => pathsCollide(path, other)))
95
+ throw new Error("generated fixture paths collide");
96
+ }
97
+ }
98
+ function pathsCollide(path, other) {
99
+ return (path === other ||
100
+ path.startsWith(`${other}/`) ||
101
+ other.startsWith(`${path}/`));
102
+ }
103
+ function validateStaging(staged, overlay, commitFiles) {
104
+ if (new Set(staged.map((path) => path.toLowerCase())).size !== staged.length ||
105
+ staged.some((path) => !Object.hasOwn(overlay, path)) ||
106
+ emptyOverlayCommit(commitFiles, overlay))
107
+ throw new Error("invalid generated fixture staging");
108
+ }
109
+ function emptyOverlayCommit(commitFiles, overlay) {
110
+ return commitFiles === true && Object.keys(overlay).length === 0;
111
+ }
112
+ /** Freeze a bounded declarative history before any host or fixture work. */
113
+ export function prepareGeneratedFixture(value) {
114
+ const fixture = fixtureRecord(value);
115
+ fixtureKeys(fixture);
116
+ const commits = fixtureCommits(fixture.commits);
117
+ fixtureOptionalFields(fixture);
118
+ prepareGitHooks(fixture.hooks);
119
+ prepareFixtureBin(fixture.bin);
120
+ const contents = { bytes: 0, files: 0, paths: [] };
121
+ for (const value of commits) {
122
+ const commit = prepareCommit(value);
123
+ contents.bytes += Buffer.byteLength(commit.message, "utf8");
124
+ countFiles(contents, commit.files);
125
+ }
126
+ // The optional fields were checked above; preserve absent fields in the frozen declaration.
127
+ const overlay = validFiles(fixture.files) ? fixture.files : {};
128
+ const staged = stringArray(fixture.staged) ? fixture.staged : [];
129
+ countFiles(contents, overlay);
130
+ contents.bytes += staged.reduce((size, path) => size + Buffer.byteLength(path, "utf8"), 0);
131
+ validateContents(contents);
132
+ validateStaging(staged, overlay, fixture.commitFiles);
133
+ return JSON.parse(canonicalJson(fixture));
134
+ }
135
+ async function git(workspace, args) {
136
+ try {
137
+ await runGit({
138
+ cwd: workspace,
139
+ args,
140
+ identity: "fixture",
141
+ label: "generated fixture",
142
+ maxOutputBytes: MAX_GIT_OUTPUT,
143
+ });
144
+ }
145
+ catch (cause) {
146
+ throw new Error("generated fixture Git operation failed", { cause });
147
+ }
148
+ }
149
+ async function writeFiles(workspace, files) {
150
+ for (const [path, content] of Object.entries(files)) {
151
+ const target = join(workspace, ...fixtureParts(path));
152
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
153
+ await writeFile(target, content, { flag: "w", mode: 0o600 });
154
+ }
155
+ }
156
+ /** Build a deterministic local Git history, then apply working-tree state. */
157
+ export async function materializeGeneratedFixture(fixture, workspace) {
158
+ await git(workspace, ["init", "--quiet", "--initial-branch=main"]);
159
+ for (const commit of fixture.commits) {
160
+ await writeFiles(workspace, commit.files);
161
+ await git(workspace, ["add", "--", ...Object.keys(commit.files)]);
162
+ await git(workspace, ["commit", "--quiet", "-m", commit.message]);
163
+ }
164
+ await applyGeneratedOverlay(fixture, workspace);
165
+ }
166
+ async function applyGeneratedOverlay(fixture, workspace) {
167
+ const overlay = fixture.files ?? {};
168
+ await writeFiles(workspace, overlay);
169
+ if (fixture.commitFiles) {
170
+ await git(workspace, ["add", "--", ...Object.keys(overlay)]);
171
+ await git(workspace, [
172
+ "commit",
173
+ "--quiet",
174
+ "-m",
175
+ "Add evaluation scaffolding",
176
+ ]);
177
+ }
178
+ if (fixture.staged?.length)
179
+ await git(workspace, ["add", "--", ...fixture.staged]);
180
+ }
181
+ //# sourceMappingURL=generated.js.map