@intentius/chant 0.101.0 → 0.104.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 (739) hide show
  1. package/dist/agents/checks.d.ts.map +1 -1
  2. package/dist/attrref.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +2 -0
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts.map +1 -1
  6. package/dist/audit/nginx.d.ts.map +1 -1
  7. package/dist/audit/prior-art.d.ts +48 -0
  8. package/dist/audit/prior-art.d.ts.map +1 -1
  9. package/dist/behaviour-kinds.d.ts.map +1 -1
  10. package/dist/builder.d.ts.map +1 -1
  11. package/dist/change-set.d.ts +232 -0
  12. package/dist/change-set.d.ts.map +1 -0
  13. package/dist/cli/commands/audit.d.ts +7 -0
  14. package/dist/cli/commands/audit.d.ts.map +1 -1
  15. package/dist/cli/commands/build.d.ts.map +1 -1
  16. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  17. package/dist/cli/commands/check-lexicon.d.ts +16 -0
  18. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  19. package/dist/cli/commands/import.d.ts +8 -0
  20. package/dist/cli/commands/import.d.ts.map +1 -1
  21. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  22. package/dist/cli/commands/init.d.ts +19 -0
  23. package/dist/cli/commands/init.d.ts.map +1 -1
  24. package/dist/cli/commands/onboard.d.ts +9 -0
  25. package/dist/cli/commands/onboard.d.ts.map +1 -1
  26. package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
  27. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
  28. package/dist/cli/handlers/build.d.ts.map +1 -1
  29. package/dist/cli/handlers/change-set.d.ts +11 -0
  30. package/dist/cli/handlers/change-set.d.ts.map +1 -0
  31. package/dist/cli/handlers/dev.d.ts.map +1 -1
  32. package/dist/cli/handlers/fan-out.d.ts +33 -0
  33. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  34. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  35. package/dist/cli/handlers/misc.d.ts.map +1 -1
  36. package/dist/cli/handlers/operator.d.ts +16 -0
  37. package/dist/cli/handlers/operator.d.ts.map +1 -1
  38. package/dist/cli/handlers/pr.d.ts +31 -0
  39. package/dist/cli/handlers/pr.d.ts.map +1 -0
  40. package/dist/cli/handlers/run.d.ts.map +1 -1
  41. package/dist/cli/lsp/server.d.ts.map +1 -1
  42. package/dist/cli/main.d.ts.map +1 -1
  43. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  44. package/dist/cli/mcp/server.d.ts.map +1 -1
  45. package/dist/cli/mcp/tools/build.d.ts.map +1 -1
  46. package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
  47. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  48. package/dist/cli/mcp/tools/import.d.ts.map +1 -1
  49. package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
  50. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
  51. package/dist/cli/mcp/tools/search.d.ts.map +1 -1
  52. package/dist/cli/mcp/workspace-tools.d.ts +4 -3
  53. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  54. package/dist/cli/registry.d.ts +74 -3
  55. package/dist/cli/registry.d.ts.map +1 -1
  56. package/dist/codegen/fetch.d.ts.map +1 -1
  57. package/dist/codegen/naming.d.ts.map +1 -1
  58. package/dist/codegen/pinned-upgrade.d.ts +67 -3
  59. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  60. package/dist/codegen/registry.d.ts.map +1 -1
  61. package/dist/components/auto-release.d.ts +16 -0
  62. package/dist/components/auto-release.d.ts.map +1 -1
  63. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  64. package/dist/components/capability.d.ts +53 -0
  65. package/dist/components/capability.d.ts.map +1 -1
  66. package/dist/components/changed-files.d.ts +31 -0
  67. package/dist/components/changed-files.d.ts.map +1 -0
  68. package/dist/components/cli-support.d.ts +9 -1
  69. package/dist/components/cli-support.d.ts.map +1 -1
  70. package/dist/components/component.d.ts +24 -0
  71. package/dist/components/component.d.ts.map +1 -1
  72. package/dist/components/deploy-units.d.ts.map +1 -1
  73. package/dist/components/driver.d.ts +13 -0
  74. package/dist/components/driver.d.ts.map +1 -1
  75. package/dist/components/fan-out-output.d.ts +6 -0
  76. package/dist/components/fan-out-output.d.ts.map +1 -1
  77. package/dist/components/fan-out-record.d.ts +41 -0
  78. package/dist/components/fan-out-record.d.ts.map +1 -0
  79. package/dist/components/fan-out-run.d.ts +65 -0
  80. package/dist/components/fan-out-run.d.ts.map +1 -1
  81. package/dist/components/fan-out-support.d.ts +2 -0
  82. package/dist/components/fan-out-support.d.ts.map +1 -1
  83. package/dist/components/fan-out.d.ts +6 -0
  84. package/dist/components/fan-out.d.ts.map +1 -1
  85. package/dist/components/gated-wave-pipeline.d.ts +50 -0
  86. package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
  87. package/dist/components/index.d.ts +1 -1
  88. package/dist/components/index.d.ts.map +1 -1
  89. package/dist/components/layers.d.ts +25 -0
  90. package/dist/components/layers.d.ts.map +1 -0
  91. package/dist/components/pr-pipeline.d.ts +61 -0
  92. package/dist/components/pr-pipeline.d.ts.map +1 -0
  93. package/dist/components/pr-run.d.ts +119 -0
  94. package/dist/components/pr-run.d.ts.map +1 -0
  95. package/dist/components/promote.d.ts.map +1 -1
  96. package/dist/components/starter-plugin.d.ts.map +1 -1
  97. package/dist/components/verbs/process-runner.d.ts.map +1 -1
  98. package/dist/components/verbs/r2-sync.d.ts.map +1 -1
  99. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  100. package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
  101. package/dist/components/verbs/shell.d.ts.map +1 -1
  102. package/dist/components/verbs/sign.d.ts.map +1 -1
  103. package/dist/components/verbs/verify.d.ts.map +1 -1
  104. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  105. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  106. package/dist/components/verbs/wrangler.d.ts.map +1 -1
  107. package/dist/components/wave-plan.d.ts +49 -0
  108. package/dist/components/wave-plan.d.ts.map +1 -0
  109. package/dist/composite.d.ts.map +1 -1
  110. package/dist/config.d.ts +19 -7
  111. package/dist/config.d.ts.map +1 -1
  112. package/dist/declarable.d.ts +16 -0
  113. package/dist/declarable.d.ts.map +1 -1
  114. package/dist/discovery/cache.d.ts.map +1 -1
  115. package/dist/discovery/fold-import.d.ts +15 -5
  116. package/dist/discovery/fold-import.d.ts.map +1 -1
  117. package/dist/discovery/param-deps.d.ts +16 -0
  118. package/dist/discovery/param-deps.d.ts.map +1 -1
  119. package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
  120. package/dist/discovery/specifier-resolve.d.ts +37 -0
  121. package/dist/discovery/specifier-resolve.d.ts.map +1 -0
  122. package/dist/errors.d.ts.map +1 -1
  123. package/dist/fold/fold.d.ts +3 -1
  124. package/dist/fold/fold.d.ts.map +1 -1
  125. package/dist/gated-waves.d.ts +147 -0
  126. package/dist/gated-waves.d.ts.map +1 -0
  127. package/dist/graph-detail.d.ts.map +1 -1
  128. package/dist/graph-ir.d.ts.map +1 -1
  129. package/dist/graph-layout.d.ts.map +1 -1
  130. package/dist/import/base-parser.d.ts.map +1 -1
  131. package/dist/import/embedded.d.ts +7 -0
  132. package/dist/import/embedded.d.ts.map +1 -1
  133. package/dist/import/parser-options.d.ts +14 -0
  134. package/dist/import/parser-options.d.ts.map +1 -0
  135. package/dist/import/parser.d.ts +17 -0
  136. package/dist/import/parser.d.ts.map +1 -1
  137. package/dist/index.d.ts +1 -0
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/kubectl-context.d.ts.map +1 -1
  140. package/dist/lexicon-output.d.ts.map +1 -1
  141. package/dist/lexicon-schema.d.ts +3 -3
  142. package/dist/lexicon.d.ts +103 -3
  143. package/dist/lexicon.d.ts.map +1 -1
  144. package/dist/lifecycle/assert-live.d.ts.map +1 -1
  145. package/dist/lifecycle/gate-ledger.d.ts +22 -0
  146. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  147. package/dist/lifecycle/git.d.ts +46 -1
  148. package/dist/lifecycle/git.d.ts.map +1 -1
  149. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  150. package/dist/lifecycle/observe.d.ts.map +1 -1
  151. package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
  152. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  153. package/dist/lifecycle/scenario.d.ts.map +1 -1
  154. package/dist/lifecycle/status.d.ts.map +1 -1
  155. package/dist/lifecycle/work-lease.d.ts +61 -2
  156. package/dist/lifecycle/work-lease.d.ts.map +1 -1
  157. package/dist/lint/config.d.ts +8 -8
  158. package/dist/lint/engine.d.ts +6 -0
  159. package/dist/lint/engine.d.ts.map +1 -1
  160. package/dist/lint/post-synth.d.ts +18 -0
  161. package/dist/lint/post-synth.d.ts.map +1 -1
  162. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  163. package/dist/op/activities/activity-contracts.d.ts +137 -11
  164. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  165. package/dist/op/activities/apply.d.ts +79 -9
  166. package/dist/op/activities/apply.d.ts.map +1 -1
  167. package/dist/op/activities/change-set.d.ts +62 -0
  168. package/dist/op/activities/change-set.d.ts.map +1 -0
  169. package/dist/op/activities/decide.d.ts +2 -0
  170. package/dist/op/activities/decide.d.ts.map +1 -1
  171. package/dist/op/activities/factory.d.ts +137 -0
  172. package/dist/op/activities/factory.d.ts.map +1 -0
  173. package/dist/op/activities/index.d.ts +5 -1
  174. package/dist/op/activities/index.d.ts.map +1 -1
  175. package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
  176. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
  177. package/dist/op/activity-contract.d.ts +1 -1
  178. package/dist/op/activity-contract.d.ts.map +1 -1
  179. package/dist/op/activity-failure.d.ts.map +1 -1
  180. package/dist/op/activity-profiles.d.ts.map +1 -1
  181. package/dist/op/builders.d.ts +11 -0
  182. package/dist/op/builders.d.ts.map +1 -1
  183. package/dist/op/composites/apply-op.d.ts +12 -5
  184. package/dist/op/composites/apply-op.d.ts.map +1 -1
  185. package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
  186. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
  187. package/dist/op/decide-backend.d.ts +5 -1
  188. package/dist/op/decide-backend.d.ts.map +1 -1
  189. package/dist/op/decide-read-inputs.d.ts +4 -2
  190. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  191. package/dist/op/emulator-lifecycle.d.ts +21 -2
  192. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  193. package/dist/op/factory.d.ts +83 -0
  194. package/dist/op/factory.d.ts.map +1 -0
  195. package/dist/op/gate-approval.d.ts +5 -1
  196. package/dist/op/gate-approval.d.ts.map +1 -1
  197. package/dist/op/gate-wait.d.ts.map +1 -1
  198. package/dist/op/gate.d.ts +19 -3
  199. package/dist/op/gate.d.ts.map +1 -1
  200. package/dist/op/index.d.ts +6 -1
  201. package/dist/op/index.d.ts.map +1 -1
  202. package/dist/op/local-executor.d.ts +6 -0
  203. package/dist/op/local-executor.d.ts.map +1 -1
  204. package/dist/op/op-ir.d.ts +2 -0
  205. package/dist/op/op-ir.d.ts.map +1 -1
  206. package/dist/op/op-verb-class.d.ts.map +1 -1
  207. package/dist/op/operator.d.ts.map +1 -1
  208. package/dist/op/run-context.d.ts +39 -0
  209. package/dist/op/run-context.d.ts.map +1 -0
  210. package/dist/op/run-live.d.ts.map +1 -1
  211. package/dist/op/runtimes/local.d.ts.map +1 -1
  212. package/dist/op/source-archive.d.ts.map +1 -1
  213. package/dist/op/steward-beside.d.ts.map +1 -1
  214. package/dist/op/steward-points.d.ts.map +1 -1
  215. package/dist/op/steward.d.ts.map +1 -1
  216. package/dist/op/types.d.ts +16 -3
  217. package/dist/op/types.d.ts.map +1 -1
  218. package/dist/op/work-lease-run.d.ts +24 -4
  219. package/dist/op/work-lease-run.d.ts.map +1 -1
  220. package/dist/plan-summary.d.ts +207 -0
  221. package/dist/plan-summary.d.ts.map +1 -0
  222. package/dist/pr-forge.d.ts +105 -0
  223. package/dist/pr-forge.d.ts.map +1 -0
  224. package/dist/pr-loop.d.ts +178 -0
  225. package/dist/pr-loop.d.ts.map +1 -0
  226. package/dist/project-root.d.ts.map +1 -1
  227. package/dist/provenance.d.ts +21 -0
  228. package/dist/provenance.d.ts.map +1 -1
  229. package/dist/pseudo-parameter.d.ts.map +1 -1
  230. package/dist/reconcile.d.ts.map +1 -1
  231. package/dist/secret-materialization.d.ts.map +1 -1
  232. package/dist/telemetry-attribution.d.ts +31 -0
  233. package/dist/telemetry-attribution.d.ts.map +1 -1
  234. package/dist/terraform/emit-source.d.ts.map +1 -1
  235. package/dist/terraform/hcl2json.d.ts +45 -0
  236. package/dist/terraform/hcl2json.d.ts.map +1 -0
  237. package/dist/terraform/parse.d.ts +4 -40
  238. package/dist/terraform/parse.d.ts.map +1 -1
  239. package/dist/testing.d.ts.map +1 -1
  240. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  241. package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
  242. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
  243. package/dist/workspace/agent-cli.d.ts +5 -0
  244. package/dist/workspace/agent-cli.d.ts.map +1 -1
  245. package/dist/workspace/box-cli.d.ts +12 -0
  246. package/dist/workspace/box-cli.d.ts.map +1 -0
  247. package/dist/workspace/box-factory.d.ts +97 -0
  248. package/dist/workspace/box-factory.d.ts.map +1 -0
  249. package/dist/workspace/box-listing.d.ts +85 -0
  250. package/dist/workspace/box-listing.d.ts.map +1 -0
  251. package/dist/workspace/box-publish.d.ts +147 -0
  252. package/dist/workspace/box-publish.d.ts.map +1 -0
  253. package/dist/workspace/broker-protocol.d.ts +190 -0
  254. package/dist/workspace/broker-protocol.d.ts.map +1 -0
  255. package/dist/workspace/changes.d.ts.map +1 -1
  256. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
  257. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  258. package/dist/workspace/checks.d.ts +5 -0
  259. package/dist/workspace/checks.d.ts.map +1 -1
  260. package/dist/workspace/compose-graph.d.ts +10 -0
  261. package/dist/workspace/compose-graph.d.ts.map +1 -1
  262. package/dist/workspace/composites.d.ts +2 -2
  263. package/dist/workspace/composites.d.ts.map +1 -1
  264. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
  265. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
  266. package/dist/workspace/conformance/broker.d.ts +202 -0
  267. package/dist/workspace/conformance/broker.d.ts.map +1 -0
  268. package/dist/workspace/conformance/index.d.ts +68 -6
  269. package/dist/workspace/conformance/index.d.ts.map +1 -1
  270. package/dist/workspace/conformance/vitest.d.ts +19 -3
  271. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  272. package/dist/workspace/conformance/writer.d.ts +462 -0
  273. package/dist/workspace/conformance/writer.d.ts.map +1 -0
  274. package/dist/workspace/decide.d.ts +126 -12
  275. package/dist/workspace/decide.d.ts.map +1 -1
  276. package/dist/workspace/decision-points.schema.json +11 -4
  277. package/dist/workspace/declaration.d.ts +156 -4
  278. package/dist/workspace/declaration.d.ts.map +1 -1
  279. package/dist/workspace/declaration.schema.json +383 -5
  280. package/dist/workspace/environments.d.ts +36 -10
  281. package/dist/workspace/environments.d.ts.map +1 -1
  282. package/dist/workspace/factory-rules.d.ts +180 -0
  283. package/dist/workspace/factory-rules.d.ts.map +1 -0
  284. package/dist/workspace/generated-files.d.ts.map +1 -1
  285. package/dist/workspace/graph-cache.d.ts.map +1 -1
  286. package/dist/workspace/graph-cli.d.ts +2 -0
  287. package/dist/workspace/graph-cli.d.ts.map +1 -1
  288. package/dist/workspace/identity.d.ts +111 -0
  289. package/dist/workspace/identity.d.ts.map +1 -0
  290. package/dist/workspace/init.d.ts +21 -0
  291. package/dist/workspace/init.d.ts.map +1 -1
  292. package/dist/workspace/intent-cli.d.ts +7 -4
  293. package/dist/workspace/intent-cli.d.ts.map +1 -1
  294. package/dist/workspace/intent-record.d.ts +36 -2
  295. package/dist/workspace/intent-record.d.ts.map +1 -1
  296. package/dist/workspace/intent.d.ts +249 -8
  297. package/dist/workspace/intent.d.ts.map +1 -1
  298. package/dist/workspace/json-edit.d.ts +24 -0
  299. package/dist/workspace/json-edit.d.ts.map +1 -0
  300. package/dist/workspace/jsonc.d.ts +11 -0
  301. package/dist/workspace/jsonc.d.ts.map +1 -1
  302. package/dist/workspace/kinds.d.ts +66 -4
  303. package/dist/workspace/kinds.d.ts.map +1 -1
  304. package/dist/workspace/lexicon-symbols.d.ts +29 -0
  305. package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
  306. package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
  307. package/dist/workspace/lineage-lock.d.ts +2 -2
  308. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  309. package/dist/workspace/lineage-upgrade.d.ts +31 -2
  310. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  311. package/dist/workspace/links.d.ts.map +1 -1
  312. package/dist/workspace/point-answer.schema.json +42 -2
  313. package/dist/workspace/points-cli.d.ts +10 -3
  314. package/dist/workspace/points-cli.d.ts.map +1 -1
  315. package/dist/workspace/points.d.ts +66 -5
  316. package/dist/workspace/points.d.ts.map +1 -1
  317. package/dist/workspace/principal-classes.d.ts +114 -0
  318. package/dist/workspace/principal-classes.d.ts.map +1 -0
  319. package/dist/workspace/reason-codes.d.ts +53 -4
  320. package/dist/workspace/reason-codes.d.ts.map +1 -1
  321. package/dist/workspace/record-assets.d.ts.map +1 -1
  322. package/dist/workspace/record-sessions.d.ts +5 -7
  323. package/dist/workspace/record-sessions.d.ts.map +1 -1
  324. package/dist/workspace/records-checkout.d.ts +54 -0
  325. package/dist/workspace/records-checkout.d.ts.map +1 -0
  326. package/dist/workspace/records-cli.d.ts +34 -1
  327. package/dist/workspace/records-cli.d.ts.map +1 -1
  328. package/dist/workspace/records-close.d.ts +7 -3
  329. package/dist/workspace/records-close.d.ts.map +1 -1
  330. package/dist/workspace/records-since.d.ts.map +1 -1
  331. package/dist/workspace/records-write.d.ts +68 -7
  332. package/dist/workspace/records-write.d.ts.map +1 -1
  333. package/dist/workspace/records.d.ts +43 -9
  334. package/dist/workspace/records.d.ts.map +1 -1
  335. package/dist/workspace/runs-cli.d.ts +296 -0
  336. package/dist/workspace/runs-cli.d.ts.map +1 -0
  337. package/dist/workspace/runs.d.ts +544 -0
  338. package/dist/workspace/runs.d.ts.map +1 -0
  339. package/dist/workspace/session-kinds.d.ts +1 -1
  340. package/dist/workspace/session-kinds.d.ts.map +1 -1
  341. package/dist/workspace/source-block.d.ts +1 -1
  342. package/dist/workspace/source-block.d.ts.map +1 -1
  343. package/dist/workspace/squash.d.ts +80 -0
  344. package/dist/workspace/squash.d.ts.map +1 -0
  345. package/dist/workspace/status-gates.d.ts +17 -3
  346. package/dist/workspace/status-gates.d.ts.map +1 -1
  347. package/dist/workspace/status.d.ts +64 -0
  348. package/dist/workspace/status.d.ts.map +1 -1
  349. package/dist/workspace/symbols.d.ts +63 -0
  350. package/dist/workspace/symbols.d.ts.map +1 -0
  351. package/dist/workspace/trailer-joins.d.ts +46 -0
  352. package/dist/workspace/trailer-joins.d.ts.map +1 -0
  353. package/dist/workspace/trailers.d.ts +89 -0
  354. package/dist/workspace/trailers.d.ts.map +1 -0
  355. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  356. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  357. package/dist/workspace/trust/run-commits.d.ts +46 -0
  358. package/dist/workspace/trust/run-commits.d.ts.map +1 -0
  359. package/dist/workspace/trust/run-statement.d.ts +128 -0
  360. package/dist/workspace/trust/run-statement.d.ts.map +1 -0
  361. package/dist/workspace/trust/seal.d.ts +38 -0
  362. package/dist/workspace/trust/seal.d.ts.map +1 -1
  363. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  364. package/dist/workspace/trust/verify-cli.d.ts +1 -1
  365. package/dist/workspace/trust/verify.d.ts +16 -2
  366. package/dist/workspace/trust/verify.d.ts.map +1 -1
  367. package/dist/workspace/wip-cli.d.ts +21 -0
  368. package/dist/workspace/wip-cli.d.ts.map +1 -0
  369. package/dist/workspace/wip.d.ts +268 -0
  370. package/dist/workspace/wip.d.ts.map +1 -0
  371. package/dist/workspace/work-cli.d.ts +39 -3
  372. package/dist/workspace/work-cli.d.ts.map +1 -1
  373. package/dist/workspace/work-evidence.d.ts +1 -1
  374. package/dist/workspace/work-evidence.d.ts.map +1 -1
  375. package/dist/workspace/work-size.d.ts +55 -0
  376. package/dist/workspace/work-size.d.ts.map +1 -0
  377. package/dist/workspace/work.d.ts +26 -1
  378. package/dist/workspace/work.d.ts.map +1 -1
  379. package/dist/workspace/workspace-kinds.schema.json +122 -0
  380. package/dist/workspace/workspace-principals.schema.json +60 -0
  381. package/dist/workspace/write-lock-cli.d.ts +60 -0
  382. package/dist/workspace/write-lock-cli.d.ts.map +1 -0
  383. package/dist/workspace/write-lock.d.ts +152 -0
  384. package/dist/workspace/write-lock.d.ts.map +1 -0
  385. package/dist/workspace/write-scope.d.ts +83 -19
  386. package/dist/workspace/write-scope.d.ts.map +1 -1
  387. package/dist/yaml.d.ts +21 -39
  388. package/dist/yaml.d.ts.map +1 -1
  389. package/package.json +7 -2
  390. package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
  391. package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
  392. package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
  393. package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
  394. package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
  395. package/src/__fixtures__/change-set/warden.plan.json +44 -0
  396. package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
  397. package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
  398. package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
  399. package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
  400. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
  401. package/src/audit/core.test.ts +15 -0
  402. package/src/audit/core.ts +8 -2
  403. package/src/audit/discover.test.ts +11 -0
  404. package/src/audit/discover.ts +7 -2
  405. package/src/audit/prior-art.ts +15 -0
  406. package/src/change-set-bundle.test.ts +57 -0
  407. package/src/change-set.test.ts +215 -0
  408. package/src/change-set.ts +445 -0
  409. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
  410. package/src/cli/commands/audit-opt-in.test.ts +54 -0
  411. package/src/cli/commands/audit.ts +29 -2
  412. package/src/cli/commands/build.test.ts +19 -0
  413. package/src/cli/commands/build.ts +3 -2
  414. package/src/cli/commands/carve-emit-state.test.ts +4 -0
  415. package/src/cli/commands/carve-emit.ts +5 -19
  416. package/src/cli/commands/check-lexicon.test.ts +62 -1
  417. package/src/cli/commands/check-lexicon.ts +36 -6
  418. package/src/cli/commands/import-live.test.ts +18 -0
  419. package/src/cli/commands/import-parser-options.test.ts +133 -0
  420. package/src/cli/commands/import.ts +21 -3
  421. package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
  422. package/src/cli/commands/init.test.ts +8 -1
  423. package/src/cli/commands/init.ts +23 -8
  424. package/src/cli/commands/lint.test.ts +37 -0
  425. package/src/cli/commands/onboard.test.ts +53 -3
  426. package/src/cli/commands/onboard.ts +52 -4
  427. package/src/cli/commands/pinned-upgrade.ts +73 -33
  428. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
  429. package/src/cli/handlers/build.ts +13 -1
  430. package/src/cli/handlers/change-set.test.ts +56 -0
  431. package/src/cli/handlers/change-set.ts +44 -0
  432. package/src/cli/handlers/components.ts +2 -2
  433. package/src/cli/handlers/dev.ts +8 -6
  434. package/src/cli/handlers/fan-out.test.ts +221 -2
  435. package/src/cli/handlers/fan-out.ts +165 -75
  436. package/src/cli/handlers/graph.ts +3 -0
  437. package/src/cli/handlers/lifecycle.test.ts +67 -0
  438. package/src/cli/handlers/lifecycle.ts +6 -5
  439. package/src/cli/handlers/misc.ts +2 -0
  440. package/src/cli/handlers/operator.test.ts +21 -0
  441. package/src/cli/handlers/operator.ts +121 -6
  442. package/src/cli/handlers/pr.ts +373 -0
  443. package/src/cli/handlers/run.test.ts +111 -0
  444. package/src/cli/handlers/run.ts +86 -24
  445. package/src/cli/handlers/scenario.ts +2 -2
  446. package/src/cli/main.test.ts +21 -0
  447. package/src/cli/main.ts +246 -30
  448. package/src/cli/mcp/never-over-mcp.test.ts +101 -0
  449. package/src/cli/mcp/op-tools.ts +24 -2
  450. package/src/cli/mcp/workspace-tools.test.ts +5 -2
  451. package/src/cli/mcp/workspace-tools.ts +62 -7
  452. package/src/cli/registry.ts +74 -3
  453. package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
  454. package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
  455. package/src/codegen/pinned-upgrade.test.ts +104 -0
  456. package/src/codegen/pinned-upgrade.ts +250 -56
  457. package/src/codegen/testdata/postgres-tags.json +242 -0
  458. package/src/components/auto-release.ts +1 -1
  459. package/src/components/capability.ts +55 -0
  460. package/src/components/changed-files.ts +66 -0
  461. package/src/components/cli-support.ts +28 -5
  462. package/src/components/component.schema.json +17 -0
  463. package/src/components/component.ts +25 -0
  464. package/src/components/deploy-units.ts +3 -0
  465. package/src/components/driver.test.ts +52 -0
  466. package/src/components/driver.ts +31 -13
  467. package/src/components/fan-out-output.ts +42 -1
  468. package/src/components/fan-out-record.ts +69 -0
  469. package/src/components/fan-out-run.test.ts +17 -0
  470. package/src/components/fan-out-run.ts +304 -1
  471. package/src/components/fan-out-support.ts +3 -0
  472. package/src/components/fan-out-waves.test.ts +375 -0
  473. package/src/components/fan-out.ts +18 -17
  474. package/src/components/gated-wave-pipeline.ts +82 -0
  475. package/src/components/index.ts +1 -0
  476. package/src/components/layers.ts +30 -0
  477. package/src/components/pr-pipeline.ts +91 -0
  478. package/src/components/pr-run.test.ts +205 -0
  479. package/src/components/pr-run.ts +280 -0
  480. package/src/components/promote.ts +5 -0
  481. package/src/components/verbs/shell.test.ts +11 -0
  482. package/src/components/verbs/shell.ts +7 -3
  483. package/src/components/wave-plan.ts +124 -0
  484. package/src/config.test.ts +21 -0
  485. package/src/config.ts +15 -0
  486. package/src/declarable.test.ts +21 -0
  487. package/src/declarable.ts +19 -0
  488. package/src/discovery/fold-composite.test.ts +41 -5
  489. package/src/discovery/fold-import.test.ts +129 -0
  490. package/src/discovery/fold-import.ts +643 -131
  491. package/src/discovery/fold-package-composite.test.ts +264 -0
  492. package/src/discovery/fold-specifiers.test.ts +191 -0
  493. package/src/discovery/param-deps.test.ts +66 -1
  494. package/src/discovery/param-deps.ts +45 -0
  495. package/src/discovery/sandbox/bundle.ts +1 -1
  496. package/src/discovery/specifier-resolve.ts +344 -0
  497. package/src/fold/fold.test.ts +39 -1
  498. package/src/fold/fold.ts +83 -9
  499. package/src/fold-provenance.test.ts +140 -0
  500. package/src/gated-waves-bundle.test.ts +62 -0
  501. package/src/gated-waves.test.ts +118 -0
  502. package/src/gated-waves.ts +240 -0
  503. package/src/graph-ir.test.ts +83 -0
  504. package/src/graph-ir.ts +29 -0
  505. package/src/import/embedded.test.ts +20 -0
  506. package/src/import/embedded.ts +13 -0
  507. package/src/import/parser-options.ts +46 -0
  508. package/src/import/parser.ts +20 -0
  509. package/src/index.ts +1 -0
  510. package/src/lexicon.ts +106 -3
  511. package/src/lifecycle/gate-ledger.ts +23 -0
  512. package/src/lifecycle/git.ts +102 -16
  513. package/src/lifecycle/observe.test.ts +18 -0
  514. package/src/lifecycle/observe.ts +6 -4
  515. package/src/lifecycle/snapshot.ts +2 -2
  516. package/src/lifecycle/work-lease.test.ts +1 -1
  517. package/src/lifecycle/work-lease.ts +93 -2
  518. package/src/lint/engine.test.ts +55 -0
  519. package/src/lint/engine.ts +30 -4
  520. package/src/lint/output-docs.test.ts +59 -56
  521. package/src/lint/post-synth.test.ts +19 -0
  522. package/src/lint/post-synth.ts +19 -1
  523. package/src/meta/declared-imports.test.ts +1 -1
  524. package/src/op/activities/activity-contracts.ts +84 -0
  525. package/src/op/activities/apply.test.ts +189 -5
  526. package/src/op/activities/apply.ts +230 -8
  527. package/src/op/activities/change-set.ts +114 -0
  528. package/src/op/activities/decide.ts +3 -0
  529. package/src/op/activities/factory.ts +675 -0
  530. package/src/op/activities/index.ts +9 -0
  531. package/src/op/activities/lexicon-upgrade.test.ts +160 -6
  532. package/src/op/activities/lexicon-upgrade.ts +154 -51
  533. package/src/op/activity-contract.test.ts +21 -1
  534. package/src/op/activity-contract.ts +19 -9
  535. package/src/op/builders.ts +23 -0
  536. package/src/op/composites/apply-op.ts +12 -5
  537. package/src/op/composites/composites.test.ts +30 -3
  538. package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
  539. package/src/op/composites/lexicon-upgrade-op.ts +29 -8
  540. package/src/op/decide-backend.ts +5 -1
  541. package/src/op/decide-read-inputs.ts +33 -5
  542. package/src/op/emulator-lifecycle.test.ts +23 -0
  543. package/src/op/emulator-lifecycle.ts +52 -14
  544. package/src/op/factory.e2e.test.ts +232 -0
  545. package/src/op/factory.ts +118 -0
  546. package/src/op/gate-approval.test.ts +74 -0
  547. package/src/op/gate-approval.ts +5 -1
  548. package/src/op/gate.ts +31 -5
  549. package/src/op/index.ts +11 -1
  550. package/src/op/local-executor.test.ts +15 -0
  551. package/src/op/local-executor.ts +40 -12
  552. package/src/op/local-output.ts +1 -1
  553. package/src/op/op-ir.test.ts +9 -0
  554. package/src/op/op-ir.ts +4 -0
  555. package/src/op/op-verb-class.ts +3 -0
  556. package/src/op/operator.ts +4 -0
  557. package/src/op/run-context.test.ts +141 -0
  558. package/src/op/run-context.ts +51 -0
  559. package/src/op/runtimes/local.test.ts +14 -0
  560. package/src/op/runtimes/local.ts +2 -0
  561. package/src/op/steward-beside.ts +2 -0
  562. package/src/op/types.ts +16 -3
  563. package/src/op/work-lease-run.test.ts +47 -0
  564. package/src/op/work-lease-run.ts +76 -11
  565. package/src/plan-summary.test.ts +263 -0
  566. package/src/plan-summary.ts +845 -0
  567. package/src/pr-forge.test.ts +237 -0
  568. package/src/pr-forge.ts +332 -0
  569. package/src/pr-loop-bundle.test.ts +46 -0
  570. package/src/pr-loop.test.ts +171 -0
  571. package/src/pr-loop.ts +324 -0
  572. package/src/provenance.ts +35 -0
  573. package/src/telemetry-attribution.test.ts +23 -1
  574. package/src/telemetry-attribution.ts +51 -0
  575. package/src/terraform/hcl2json.test.ts +21 -0
  576. package/src/terraform/hcl2json.ts +81 -0
  577. package/src/terraform/parse.ts +11 -74
  578. package/src/workspace/agent-cli.ts +7 -3
  579. package/src/workspace/agent.schema.json +22 -0
  580. package/src/workspace/app-kind.test.ts +196 -0
  581. package/src/workspace/box-cli.ts +113 -0
  582. package/src/workspace/box-factory.test.ts +323 -0
  583. package/src/workspace/box-factory.ts +136 -0
  584. package/src/workspace/box-listing-write.schema.json +204 -0
  585. package/src/workspace/box-listing.test.ts +234 -0
  586. package/src/workspace/box-listing.ts +324 -0
  587. package/src/workspace/box-publish.schema.json +498 -0
  588. package/src/workspace/box-publish.test.ts +204 -0
  589. package/src/workspace/box-publish.ts +420 -0
  590. package/src/workspace/box-ship.test.ts +96 -0
  591. package/src/workspace/broker-protocol.schema.json +186 -0
  592. package/src/workspace/broker-protocol.test.ts +122 -0
  593. package/src/workspace/broker-protocol.ts +317 -0
  594. package/src/workspace/builder-tiers.test.ts +146 -0
  595. package/src/workspace/change-set.schema.json +460 -0
  596. package/src/workspace/changes.schema.json +12 -20
  597. package/src/workspace/changes.ts +4 -1
  598. package/src/workspace/chant-migrations.e2e.test.ts +17 -5
  599. package/src/workspace/checks/boxes.test.ts +5 -0
  600. package/src/workspace/checks.ts +39 -8
  601. package/src/workspace/component-environments.test.ts +105 -0
  602. package/src/workspace/compose-graph.ts +7 -0
  603. package/src/workspace/composites.schema.json +23 -4
  604. package/src/workspace/composites.test.ts +8 -8
  605. package/src/workspace/composites.ts +13 -2
  606. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
  607. package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
  608. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
  609. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
  610. package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  611. package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  612. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
  613. package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  614. package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  615. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
  616. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
  617. package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  618. package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  619. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
  620. package/src/workspace/conformance/broker.test.ts +122 -0
  621. package/src/workspace/conformance/broker.ts +792 -0
  622. package/src/workspace/conformance/conformance.e2e.test.ts +36 -3
  623. package/src/workspace/conformance/index.mjs +51 -3
  624. package/src/workspace/conformance/index.ts +107 -15
  625. package/src/workspace/conformance/vitest.ts +76 -2
  626. package/src/workspace/conformance/writer-conformance.e2e.test.ts +177 -0
  627. package/src/workspace/conformance/writer.test.ts +186 -0
  628. package/src/workspace/conformance/writer.ts +1191 -0
  629. package/src/workspace/decide.test.ts +208 -4
  630. package/src/workspace/decide.ts +433 -73
  631. package/src/workspace/decision-points.schema.json +11 -4
  632. package/src/workspace/declaration.schema.json +383 -5
  633. package/src/workspace/declaration.ts +315 -10
  634. package/src/workspace/environments.ts +66 -14
  635. package/src/workspace/factory-rules.test.ts +62 -0
  636. package/src/workspace/factory-rules.ts +218 -0
  637. package/src/workspace/graph-cli.ts +6 -3
  638. package/src/workspace/graph.schema.json +314 -0
  639. package/src/workspace/identity.test.ts +219 -0
  640. package/src/workspace/identity.ts +186 -0
  641. package/src/workspace/init.ts +82 -2
  642. package/src/workspace/intent-cli.ts +84 -13
  643. package/src/workspace/intent-patch-id.test.ts +162 -0
  644. package/src/workspace/intent-record.schema.json +336 -1
  645. package/src/workspace/intent-record.ts +96 -16
  646. package/src/workspace/intent-reviews.test.ts +76 -0
  647. package/src/workspace/intent-squash.test.ts +213 -0
  648. package/src/workspace/intent-why.test.ts +274 -0
  649. package/src/workspace/intent.schema.json +950 -9
  650. package/src/workspace/intent.test.ts +11 -2
  651. package/src/workspace/intent.ts +722 -65
  652. package/src/workspace/json-edit.test.ts +121 -0
  653. package/src/workspace/json-edit.ts +142 -0
  654. package/src/workspace/jsonc.ts +20 -0
  655. package/src/workspace/kinds.ts +201 -39
  656. package/src/workspace/lexicon-symbols.test.ts +148 -0
  657. package/src/workspace/lexicon-symbols.ts +125 -0
  658. package/src/workspace/lineage-upgrade.test.ts +63 -0
  659. package/src/workspace/lineage-upgrade.ts +146 -5
  660. package/src/workspace/plan-summary.schema.json +155 -0
  661. package/src/workspace/point-answer.schema.json +42 -2
  662. package/src/workspace/points-cli.ts +42 -16
  663. package/src/workspace/points-write.schema.json +163 -4
  664. package/src/workspace/points.schema.json +157 -2
  665. package/src/workspace/points.test.ts +13 -1
  666. package/src/workspace/points.ts +118 -12
  667. package/src/workspace/pr-report.schema.json +95 -0
  668. package/src/workspace/principal-classes.test.ts +175 -0
  669. package/src/workspace/principal-classes.ts +307 -0
  670. package/src/workspace/profiles.test.ts +159 -0
  671. package/src/workspace/read-contract.test.ts +2 -2
  672. package/src/workspace/reason-codes.test.ts +23 -3
  673. package/src/workspace/reason-codes.ts +61 -4
  674. package/src/workspace/record-sessions.ts +9 -8
  675. package/src/workspace/records-amend.schema.json +80 -0
  676. package/src/workspace/records-checkout.ts +106 -0
  677. package/src/workspace/records-cli.ts +123 -17
  678. package/src/workspace/records-close.schema.json +79 -0
  679. package/src/workspace/records-close.ts +31 -7
  680. package/src/workspace/records-concurrent.e2e.test.ts +135 -0
  681. package/src/workspace/records-concurrent.test.ts +145 -0
  682. package/src/workspace/records-formats.test.ts +46 -10
  683. package/src/workspace/records-new.schema.json +9 -0
  684. package/src/workspace/records-review.schema.json +80 -0
  685. package/src/workspace/records-sessions-write.test.ts +69 -0
  686. package/src/workspace/records-sessions.test.ts +22 -0
  687. package/src/workspace/records-uncommitted.test.ts +171 -0
  688. package/src/workspace/records-write.ts +219 -30
  689. package/src/workspace/records.schema.json +88 -1
  690. package/src/workspace/records.ts +76 -8
  691. package/src/workspace/reference-kinds/app/package.json +10 -0
  692. package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  693. package/src/workspace/run-statement.schema.json +622 -0
  694. package/src/workspace/runs-cli.ts +710 -0
  695. package/src/workspace/runs-statement.test.ts +239 -0
  696. package/src/workspace/runs-write.schema.json +994 -0
  697. package/src/workspace/runs.schema.json +1257 -0
  698. package/src/workspace/runs.test.ts +281 -0
  699. package/src/workspace/runs.ts +970 -0
  700. package/src/workspace/session-kinds.ts +3 -3
  701. package/src/workspace/squash.ts +198 -0
  702. package/src/workspace/status-contract.test.ts +10 -2
  703. package/src/workspace/status-gates.ts +25 -9
  704. package/src/workspace/status.schema.json +252 -4
  705. package/src/workspace/status.ts +110 -1
  706. package/src/workspace/symbols.ts +191 -0
  707. package/src/workspace/trailer-joins.test.ts +171 -0
  708. package/src/workspace/trailer-joins.ts +82 -0
  709. package/src/workspace/trailers.ts +148 -0
  710. package/src/workspace/trust/run-commits.ts +115 -0
  711. package/src/workspace/trust/run-statement.ts +226 -0
  712. package/src/workspace/trust/seal.ts +58 -1
  713. package/src/workspace/trust/verify-cli.ts +6 -5
  714. package/src/workspace/trust/verify.ts +39 -6
  715. package/src/workspace/wip-cli.ts +97 -0
  716. package/src/workspace/wip-write.schema.json +504 -0
  717. package/src/workspace/wip.schema.json +334 -0
  718. package/src/workspace/wip.test.ts +294 -0
  719. package/src/workspace/wip.ts +753 -0
  720. package/src/workspace/work-cli.e2e.test.ts +28 -1
  721. package/src/workspace/work-cli.ts +136 -8
  722. package/src/workspace/work-evidence.schema.json +5 -0
  723. package/src/workspace/work-history.schema.json +73 -1
  724. package/src/workspace/work-history.test.ts +48 -5
  725. package/src/workspace/work-kind-fields.test.ts +154 -0
  726. package/src/workspace/work-lease.schema.json +14 -1
  727. package/src/workspace/work-size.test.ts +77 -0
  728. package/src/workspace/work-size.ts +93 -0
  729. package/src/workspace/work.ts +82 -0
  730. package/src/workspace/workspace-kinds.schema.json +122 -0
  731. package/src/workspace/workspace-principals.schema.json +60 -0
  732. package/src/workspace/write-lock-cli.ts +131 -0
  733. package/src/workspace/write-lock.schema.json +172 -0
  734. package/src/workspace/write-lock.test.ts +189 -0
  735. package/src/workspace/write-lock.ts +474 -0
  736. package/src/workspace/write-scope.test.ts +144 -3
  737. package/src/workspace/write-scope.ts +192 -45
  738. package/src/yaml.test.ts +111 -1
  739. package/src/yaml.ts +141 -549
@@ -0,0 +1,970 @@
1
+ /**
2
+ * The agent run record (#3033, ws-076): what each agent run cost and which
3
+ * changes it made.
4
+ *
5
+ * chant records runs and never starts one (ws-052). Whatever ran the agent,
6
+ * a steward step, a factory, hud's chat or a lobby that proxies model calls,
7
+ * reports the run through `chant workspace runs start|end|record`, and
8
+ * chant appends it to the run ledger on `chant/lifecycle`, beside the work
9
+ * lease histories (ws-068: a ledger entry is a fact about a run, never
10
+ * revised). Nothing is written to the working tree, and no transcript is
11
+ * copied: the record pins one by sha256.
12
+ *
13
+ * ## Storage
14
+ *
15
+ * `_agent-runs/<run id>.jsonl` in the ledger of the workspace root (flat at
16
+ * the root, under `_members/<member>/` for a nested workspace). One file per
17
+ * run, so concurrent runs never contend for one file, with two kinds of line:
18
+ *
19
+ * - `start`: who the run worked for (`by`), the agent session, the harness,
20
+ * model and provider, the work item and lease it worked under, the records
21
+ * it worked on, and the instruction pinned by hash.
22
+ * - `end`: the outcome, the turns and tokens, the cost with its currency and
23
+ * the source of its prices, the transcript pinned by hash, and the commits
24
+ * the run made, each with its `git patch-id --stable` (#3036) and, when
25
+ * the writer knows them, the hunks of it the run wrote (#3034).
26
+ * - `statement`: after the end, a DSSE envelope a runner or steward key
27
+ * signed over the run (#3192, ./trust/run-statement.ts). A run may have
28
+ * several, such as one per signer; none changes the record they sign.
29
+ *
30
+ * The file name is `_agent-runs`, not `runs`, because an Op run already has
31
+ * `<env>/runs__<op>.jsonl` (`../lifecycle/run-ledger.ts`).
32
+ *
33
+ * ## The join
34
+ *
35
+ * A commit names its run with the `Chant-Run` trailer (#3149, ws-075). The
36
+ * run's end can also list commits, which joins a commit that carries no
37
+ * trailer. `workspace runs` reports both, and `graph --intent` links each
38
+ * commit to its run.
39
+ *
40
+ * When neither holds, as after a rebase or a cherry-pick that dropped the
41
+ * trailer, a commit whose `git patch-id --stable` equals one the run's end
42
+ * recorded joins the run by content (#3036), marked `patch-id`. A content join
43
+ * never overrides a trailer or a record join: it is tried only for a commit
44
+ * that has neither. A squash of several commits has a patch-id of its own, so
45
+ * it never joins this way.
46
+ *
47
+ * A squash merge's commit carries none of its pull request's trailers or
48
+ * SHAs. When a read is asked to follow squashes (#3035, `squash.ts`), the
49
+ * squash commit joins every run its pull request's original commits join,
50
+ * marked `squash` with the originals in `via`.
51
+ */
52
+
53
+ import { execFileSync } from "node:child_process";
54
+ import { createHash, randomBytes } from "node:crypto";
55
+ import { readFileSync, statSync } from "node:fs";
56
+ import { resolve } from "node:path";
57
+ import { z } from "zod";
58
+ import { describeLifecyclePush, fetchLifecycleStatus, type LifecyclePushStatus, pushLifecycleStatus, readBlobBySha, RefCASConflictError, writeLedgerFiles } from "../lifecycle/git";
59
+ import { resolveMemberLedger } from "../lifecycle/member-ledger";
60
+ import { sortedJsonReplacer } from "../utils";
61
+ import { followSquashes, type SquashFollow } from "./squash";
62
+ import { RUN_TRAILER, parseRecordRef, type RecordRef } from "./trailers";
63
+ import type { DsseEnvelope } from "./trust/dsse";
64
+ import { runRecordDigest, type RunAttestation } from "./trust/run-statement";
65
+
66
+ /** The directory on `chant/lifecycle` that holds the agent runs, under the workspace root's ledger prefix. */
67
+ export const AGENT_RUNS_DIR = "_agent-runs";
68
+
69
+ /** The branch the ledger is on. */
70
+ export const LEDGER_BRANCH = "chant/lifecycle";
71
+
72
+ /** A run id: one file name and one trailer value. */
73
+ export const RUN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
74
+
75
+ const SHA256 = /^[0-9a-f]{64}$/;
76
+ const COMMIT = /^[0-9a-f]{4,64}$/;
77
+ const CURRENCY = /^[A-Z]{3}$/;
78
+
79
+ // ── The ledger lines ─────────────────────────────────────────────────────────
80
+
81
+ /** The tokens a run used. Every count is optional, since a harness may report only some. */
82
+ export interface RunUsage {
83
+ turns: number | null;
84
+ inputTokens: number | null;
85
+ outputTokens: number | null;
86
+ cacheReadTokens: number | null;
87
+ cacheWriteTokens: number | null;
88
+ }
89
+
90
+ /** A sum of money, with where its prices came from: a harness's own report, a proxy's price table, a provider's bill. */
91
+ export interface RunCost {
92
+ amount: number;
93
+ /** ISO 4217, such as USD. */
94
+ currency: string;
95
+ /** What priced it, such as `harness:claude-code`, `lobby:list-2026-09` or `provider:billed`. */
96
+ source: string;
97
+ }
98
+
99
+ /** One model's share of a run, when the harness reports a breakdown. */
100
+ export interface RunModelUsage {
101
+ model: string;
102
+ provider: string | null;
103
+ inputTokens: number | null;
104
+ outputTokens: number | null;
105
+ cacheReadTokens: number | null;
106
+ cacheWriteTokens: number | null;
107
+ cost: RunCost | null;
108
+ }
109
+
110
+ /** A file pinned by hash and never copied: the transcript, or the instruction the run was given. */
111
+ export interface RunPin {
112
+ sha256: string;
113
+ /** Its size in bytes, when chant hashed the file itself. */
114
+ bytes: number | null;
115
+ /** Where the caller keeps it, as the caller names it: a path on a box, a URL. chant never reads it back. */
116
+ ref: string | null;
117
+ /**
118
+ * For the instruction only: a short excerpt the writer chose to show beside
119
+ * the hash, such as its first line (#3034). The hash pins the whole
120
+ * instruction; the excerpt is never checked against it. Absent when none was given.
121
+ */
122
+ excerpt?: string;
123
+ }
124
+
125
+ /** Lines of one file that a run wrote in a commit: new-side line numbers in the commit's own version of the file. */
126
+ export interface RunHunk {
127
+ /** From the repository root, as git names it in the commit's diff. */
128
+ path: string;
129
+ start: number;
130
+ end: number;
131
+ }
132
+
133
+ /** A commit a run's end lists. */
134
+ export interface RunCommit {
135
+ sha: string;
136
+ /** `git patch-id --stable` of the commit, or null for a commit with no patch, such as a merge. */
137
+ patchId: string | null;
138
+ /**
139
+ * The hunks of the commit this run wrote, when the writer knows them, such
140
+ * as when several runs share one commit (#3034). Absent when not given: the
141
+ * run is then taken to have made the whole commit.
142
+ */
143
+ hunks?: RunHunk[];
144
+ }
145
+
146
+ /** The `start` line of `_agent-runs/<id>.jsonl`. */
147
+ export interface RunStartLine {
148
+ version: 1;
149
+ event: "start";
150
+ run: string;
151
+ at: string;
152
+ /** The principal the run worked for: the person who prompted it, or the service that asked. */
153
+ by: string | null;
154
+ /** The agent session it ran as (ws-067), the value its commits carry in Chant-Agent. */
155
+ agent: string | null;
156
+ harness: { name: string; version: string | null };
157
+ model: string | null;
158
+ provider: string | null;
159
+ /** The work item it worked on, and the work kind's name when the caller gives it. */
160
+ unit: { id: string; kind: string | null } | null;
161
+ /** The work lease's fencing token. */
162
+ lease: string | null;
163
+ /** Other records it worked on, such as the decision a decide call answered or the review a chat turn replied to. */
164
+ records: RecordRef[];
165
+ instruction: RunPin | null;
166
+ }
167
+
168
+ /** The `end` line of `_agent-runs/<id>.jsonl`. */
169
+ export interface RunEndLine {
170
+ version: 1;
171
+ event: "end";
172
+ run: string;
173
+ at: string;
174
+ /** How the run ended, free text such as done, not_done, failed or cancelled. */
175
+ outcome: string | null;
176
+ usage: RunUsage | null;
177
+ models: RunModelUsage[];
178
+ cost: RunCost | null;
179
+ transcript: RunPin | null;
180
+ commits: RunCommit[];
181
+ }
182
+
183
+ /** A `statement` line of `_agent-runs/<id>.jsonl`: a signed statement over the run (#3192). */
184
+ export interface RunStatementLine {
185
+ version: 1;
186
+ event: "statement";
187
+ run: string;
188
+ at: string;
189
+ envelope: DsseEnvelope;
190
+ }
191
+
192
+ export type RunLine = RunStartLine | RunEndLine | RunStatementLine;
193
+
194
+ // ── The caller's fields ──────────────────────────────────────────────────────
195
+
196
+ const count = z.number().int().nonnegative();
197
+ const nonEmpty = z.string().trim().min(1).refine((s) => !/[\r\n]/.test(s), "one line of text");
198
+ const iso = z.string().refine((s) => !Number.isNaN(Date.parse(s)) && /^\d{4}-\d{2}-\d{2}T/.test(s), "an ISO 8601 date and time");
199
+ const costSchema = z.object({ amount: z.number().nonnegative().finite(), currency: z.string().regex(CURRENCY, "an ISO 4217 code such as USD"), source: nonEmpty }).strict();
200
+ const usageSchema = z.object({ turns: count.optional(), inputTokens: count.optional(), outputTokens: count.optional(), cacheReadTokens: count.optional(), cacheWriteTokens: count.optional() }).strict();
201
+ const pinSchema = z
202
+ .object({ sha256: z.string().regex(SHA256, "64 lower-case hex digits").optional(), path: nonEmpty.optional(), ref: nonEmpty.optional() })
203
+ .strict()
204
+ .refine((p) => (p.sha256 === undefined) !== (p.path === undefined), "give sha256, or a path for chant to hash, not both");
205
+ /** The longest instruction excerpt kept, in characters. */
206
+ export const INSTRUCTION_EXCERPT_MAX = 500;
207
+ const instructionSchema = z
208
+ .object({
209
+ sha256: z.string().regex(SHA256, "64 lower-case hex digits").optional(),
210
+ path: nonEmpty.optional(),
211
+ ref: nonEmpty.optional(),
212
+ excerpt: z.string().trim().min(1).max(INSTRUCTION_EXCERPT_MAX).optional(),
213
+ })
214
+ .strict()
215
+ .refine((p) => (p.sha256 === undefined) !== (p.path === undefined), "give sha256, or a path for chant to hash, not both");
216
+ const lineNumber = z.number().int().positive();
217
+ const hunkSchema = z
218
+ .object({ path: nonEmpty, start: lineNumber, end: lineNumber })
219
+ .strict()
220
+ .refine((h) => h.end >= h.start, "end is not before start")
221
+ .refine((h) => !h.path.startsWith("/") && !h.path.split("/").includes(".."), "a path from the repository root");
222
+ const commitSchema = z.union([z.string().regex(COMMIT, "a commit id"), z.object({ sha: z.string().regex(COMMIT, "a commit id"), hunks: z.array(hunkSchema).min(1) }).strict()]);
223
+
224
+ const startFields = {
225
+ id: z.string().regex(RUN_ID_PATTERN, "letters, digits, '.', '_' and '-', starting with a letter or digit").optional(),
226
+ startedAt: iso.optional(),
227
+ by: nonEmpty.optional(),
228
+ agent: nonEmpty.optional(),
229
+ harness: z.union([nonEmpty, z.object({ name: nonEmpty, version: nonEmpty.optional() }).strict()]),
230
+ model: nonEmpty.optional(),
231
+ provider: nonEmpty.optional(),
232
+ unit: z.union([nonEmpty, z.object({ id: nonEmpty, kind: nonEmpty.optional() }).strict()]).optional(),
233
+ lease: nonEmpty.optional(),
234
+ records: z.array(z.string().refine((s) => parseRecordRef(s) !== undefined, "<kind>:<id>")).optional(),
235
+ instruction: instructionSchema.optional(),
236
+ };
237
+ const endFields = {
238
+ endedAt: iso.optional(),
239
+ outcome: nonEmpty.optional(),
240
+ usage: usageSchema.optional(),
241
+ models: z.array(z.object({ model: nonEmpty, provider: nonEmpty.optional(), inputTokens: count.optional(), outputTokens: count.optional(), cacheReadTokens: count.optional(), cacheWriteTokens: count.optional(), cost: costSchema.optional() }).strict()).optional(),
242
+ cost: costSchema.optional(),
243
+ transcript: pinSchema.optional(),
244
+ commits: z.array(commitSchema).optional(),
245
+ };
246
+
247
+ /** The fields `runs start --from` takes. */
248
+ export const runStartInputSchema = z.object(startFields).strict();
249
+ /** The fields `runs end <id> --from` takes. */
250
+ export const runEndInputSchema = z.object(endFields).strict();
251
+ /** The fields `runs record --from` takes: a start and an end in one. */
252
+ export const runRecordInputSchema = z.object({ ...startFields, ...endFields }).strict();
253
+
254
+ export type RunStartInput = z.infer<typeof runStartInputSchema>;
255
+ export type RunEndInput = z.infer<typeof runEndInputSchema>;
256
+ export type RunRecordInput = z.infer<typeof runRecordInputSchema>;
257
+
258
+ // ── Errors ───────────────────────────────────────────────────────────────────
259
+
260
+ /** Why a run write was refused. */
261
+ export type RunWriteRefusal = "run-exists" | "run-unknown" | "run-ended" | "run-not-ended" | "run-statement-mismatch" | "write-input-invalid" | "not-a-git-repository";
262
+
263
+ export class RunWriteError extends Error {
264
+ constructor(
265
+ readonly code: RunWriteRefusal,
266
+ message: string,
267
+ ) {
268
+ super(message);
269
+ this.name = "RunWriteError";
270
+ }
271
+ }
272
+
273
+ // ── Git ──────────────────────────────────────────────────────────────────────
274
+
275
+ function git(top: string, args: string[], input?: string): string | undefined {
276
+ try {
277
+ return execFileSync("git", args, { cwd: top, encoding: "utf-8", input, stdio: [input === undefined ? "ignore" : "pipe", "pipe", "pipe"], maxBuffer: 512 * 1024 * 1024 });
278
+ } catch {
279
+ return undefined;
280
+ }
281
+ }
282
+
283
+ /** The full id of `rev` as a commit, or undefined. */
284
+ function commitId(top: string, rev: string): string | undefined {
285
+ return git(top, ["rev-parse", "--verify", "--quiet", `${rev}^{commit}`])?.trim() || undefined;
286
+ }
287
+
288
+ /** `git patch-id --stable` of one commit, or null when it has no patch, such as a merge. */
289
+ export function patchIdOf(top: string, sha: string): string | null {
290
+ const patch = git(top, ["show", "--format=", "--patch", "--no-color", "--no-ext-diff", sha]);
291
+ if (!patch || patch.trim() === "") return null;
292
+ const out = git(top, ["patch-id", "--stable"], patch);
293
+ return out?.trim().split(/\s+/)[0] || null;
294
+ }
295
+
296
+ /** Parse `git patch-id` output, `<patch-id> <commit>` per line, into commit to patch-id. */
297
+ function parsePatchIds(out: string | undefined): Map<string, string> {
298
+ const ids = new Map<string, string>();
299
+ for (const line of (out ?? "").split("\n")) {
300
+ const [patchId, sha] = line.trim().split(/\s+/);
301
+ if (patchId && sha && !ids.has(sha)) ids.set(sha, patchId);
302
+ }
303
+ return ids;
304
+ }
305
+
306
+ /**
307
+ * `git patch-id --stable` of each commit, in two git calls however many there
308
+ * are (#3036). A commit with no patch, such as a merge, is left out.
309
+ */
310
+ export function patchIdsOf(top: string, shas: string[]): Map<string, string> {
311
+ if (shas.length === 0) return new Map();
312
+ const patches = git(top, ["log", "--no-walk=unsorted", "--stdin", "--no-merges", "--format=commit %H", "--patch", "--no-color", "--no-ext-diff"], `${shas.join("\n")}\n`);
313
+ if (!patches?.trim()) return new Map();
314
+ return parsePatchIds(git(top, ["patch-id", "--stable"], patches));
315
+ }
316
+
317
+ /**
318
+ * `git patch-id --stable` of each commit reachable from `rev` and committed
319
+ * after `since` (ISO 8601), merges left out (#3036). A rebase or a
320
+ * cherry-pick commits anew, so a rewritten commit is never older than the
321
+ * one it was rewritten from.
322
+ */
323
+ export function patchIdsSince(top: string, rev: string, since: string): Map<string, string> {
324
+ if (!commitId(top, rev)) return new Map();
325
+ const patches = git(top, ["log", rev, `--since=${since}`, "--no-merges", "--format=commit %H", "--patch", "--no-color", "--no-ext-diff"]);
326
+ if (!patches?.trim()) return new Map();
327
+ return parsePatchIds(git(top, ["patch-id", "--stable"], patches));
328
+ }
329
+
330
+ function hashFile(path: string): RunPin {
331
+ const bytes = readFileSync(path);
332
+ return { sha256: createHash("sha256").update(bytes).digest("hex"), bytes: statSync(path).size, ref: null };
333
+ }
334
+
335
+ function pinOf(p: (z.infer<typeof pinSchema> & { excerpt?: string }) | undefined, cwd: string, what: string): RunPin | null {
336
+ if (!p) return null;
337
+ const excerpt = p.excerpt === undefined ? {} : { excerpt: p.excerpt };
338
+ if (p.sha256) return { sha256: p.sha256, bytes: null, ref: p.ref ?? null, ...excerpt };
339
+ const path = resolve(cwd, p.path!);
340
+ let pin: RunPin;
341
+ try {
342
+ pin = hashFile(path);
343
+ } catch (err) {
344
+ throw new RunWriteError("write-input-invalid", `${what}.path ${p.path} can't be read to hash it: ${err instanceof Error ? err.message : String(err)}`);
345
+ }
346
+ return { ...pin, ref: p.ref ?? p.path!, ...excerpt };
347
+ }
348
+
349
+ // ── Building the lines ───────────────────────────────────────────────────────
350
+
351
+ /** A new run id: the start time to the second, and eight random hex digits. */
352
+ export function newRunId(now: Date): string {
353
+ return `${now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z")}-${randomBytes(4).toString("hex")}`;
354
+ }
355
+
356
+ function parseInput<T>(schema: z.ZodType<T>, value: unknown, verb: string): T {
357
+ const parsed = schema.safeParse(value);
358
+ if (!parsed.success) {
359
+ throw new RunWriteError("write-input-invalid", `runs ${verb}: ${parsed.error.issues.map((i) => `${i.path.length ? i.path.join(".") : "the fields"}: ${i.message}`).join("; ")}`);
360
+ }
361
+ return parsed.data;
362
+ }
363
+
364
+ function startLine(input: RunStartInput, id: string, at: string, cwd: string): RunStartLine {
365
+ const harness = typeof input.harness === "string" ? { name: input.harness, version: null } : { name: input.harness.name, version: input.harness.version ?? null };
366
+ const unit = input.unit === undefined ? null : typeof input.unit === "string" ? { id: input.unit, kind: null } : { id: input.unit.id, kind: input.unit.kind ?? null };
367
+ return {
368
+ version: 1,
369
+ event: "start",
370
+ run: id,
371
+ at,
372
+ by: input.by ?? null,
373
+ agent: input.agent ?? null,
374
+ harness,
375
+ model: input.model ?? null,
376
+ provider: input.provider ?? null,
377
+ unit,
378
+ lease: input.lease ?? null,
379
+ records: (input.records ?? []).map((r) => parseRecordRef(r)!),
380
+ instruction: pinOf(input.instruction, cwd, "instruction"),
381
+ };
382
+ }
383
+
384
+ function endLine(input: RunEndInput, id: string, at: string, top: string, cwd: string): RunEndLine {
385
+ const commits: RunCommit[] = [];
386
+ for (const entry of input.commits ?? []) {
387
+ const c = typeof entry === "string" ? entry : entry.sha;
388
+ const sha = commitId(top, c);
389
+ if (!sha) throw new RunWriteError("write-input-invalid", `commits: ${c} names no commit in this repository`);
390
+ const hunks = typeof entry === "string" ? undefined : entry.hunks.map((h) => ({ path: h.path, start: h.start, end: h.end }));
391
+ const listed = commits.find((x) => x.sha === sha);
392
+ if (listed) {
393
+ if (hunks) listed.hunks = [...(listed.hunks ?? []), ...hunks];
394
+ } else commits.push({ sha, patchId: patchIdOf(top, sha), ...(hunks ? { hunks } : {}) });
395
+ }
396
+ const usage = input.usage
397
+ ? { turns: input.usage.turns ?? null, inputTokens: input.usage.inputTokens ?? null, outputTokens: input.usage.outputTokens ?? null, cacheReadTokens: input.usage.cacheReadTokens ?? null, cacheWriteTokens: input.usage.cacheWriteTokens ?? null }
398
+ : null;
399
+ return {
400
+ version: 1,
401
+ event: "end",
402
+ run: id,
403
+ at,
404
+ outcome: input.outcome ?? null,
405
+ usage,
406
+ models: (input.models ?? []).map((m) => ({
407
+ model: m.model,
408
+ provider: m.provider ?? null,
409
+ inputTokens: m.inputTokens ?? null,
410
+ outputTokens: m.outputTokens ?? null,
411
+ cacheReadTokens: m.cacheReadTokens ?? null,
412
+ cacheWriteTokens: m.cacheWriteTokens ?? null,
413
+ cost: m.cost ?? null,
414
+ })),
415
+ cost: input.cost ?? null,
416
+ transcript: pinOf(input.transcript, cwd, "transcript"),
417
+ commits,
418
+ };
419
+ }
420
+
421
+ // ── Reading the ledger ───────────────────────────────────────────────────────
422
+
423
+ /** Parse one run file, oldest line first. Lines that aren't run events are counted and skipped. */
424
+ export function parseRunFile(content: string, id: string): { lines: RunLine[]; malformed: number } {
425
+ const lines: RunLine[] = [];
426
+ let malformed = 0;
427
+ for (const raw of content.split("\n").map((l) => l.trim()).filter(Boolean)) {
428
+ try {
429
+ const v = JSON.parse(raw) as Partial<RunLine>;
430
+ const statement = v.event === "statement" && typeof (v as Partial<RunStatementLine>).envelope === "object" && (v as Partial<RunStatementLine>).envelope !== null;
431
+ const ok = v.version === 1 && (v.event === "start" || v.event === "end" || statement) && v.run === id && typeof v.at === "string";
432
+ if (ok) lines.push(v as RunLine);
433
+ else malformed++;
434
+ } catch {
435
+ malformed++;
436
+ }
437
+ }
438
+ return { lines, malformed };
439
+ }
440
+
441
+ /** The ledger directory of the workspace whose root is `rootOnDisk`, on the branch. */
442
+ export async function runsDir(rootOnDisk: string): Promise<string> {
443
+ const { prefix } = await resolveMemberLedger(rootOnDisk);
444
+ return `${prefix}${AGENT_RUNS_DIR}`;
445
+ }
446
+
447
+ /** Every run file in `dir` on the local branch, or on `ref` when given, by run id, and the branch tip read. Never fetches. */
448
+ export function readRunFiles(top: string, dir: string, ref = `refs/heads/${LEDGER_BRANCH}`): { tip: string | null; files: Map<string, { path: string; content: string }> } {
449
+ const files = new Map<string, { path: string; content: string }>();
450
+ const tip = commitId(top, ref) ?? null;
451
+ if (!tip) return { tip, files };
452
+ const listing = git(top, ["ls-tree", "-z", `${tip}:${dir}`]);
453
+ if (!listing) return { tip, files };
454
+ const entries: { id: string; sha: string }[] = [];
455
+ for (const entry of listing.split("\0").filter(Boolean)) {
456
+ const tab = entry.indexOf("\t");
457
+ const [, type, sha] = entry.slice(0, tab).split(" ");
458
+ const name = entry.slice(tab + 1);
459
+ const m = name.match(/^(.+)\.jsonl$/);
460
+ if (type === "blob" && m && RUN_ID_PATTERN.test(m[1])) entries.push({ id: m[1], sha });
461
+ }
462
+ if (entries.length === 0) return { tip, files };
463
+ const batch = git(top, ["cat-file", "--batch"], `${entries.map((e) => e.sha).join("\n")}\n`) ?? "";
464
+ // Each object: "<sha> blob <size>\n<content>\n".
465
+ let pos = 0;
466
+ for (const e of entries) {
467
+ const nl = batch.indexOf("\n", pos);
468
+ if (nl < 0) break;
469
+ const size = Number(batch.slice(pos, nl).split(" ")[2]);
470
+ const start = nl + 1;
471
+ const content = Buffer.from(batch.slice(start)).subarray(0, size).toString("utf-8");
472
+ files.set(e.id, { path: `${dir}/${e.id}.jsonl`, content });
473
+ pos = start + content.length + 1;
474
+ }
475
+ return { tip, files };
476
+ }
477
+
478
+ // ── Writing ──────────────────────────────────────────────────────────────────
479
+
480
+ /** Where a write went. */
481
+ export interface RunLedgerWrite {
482
+ branch: string;
483
+ path: string;
484
+ commit: string;
485
+ /** Whether chant/lifecycle reached the remote; false with no remote too. */
486
+ pushed: boolean;
487
+ /** Why it did not, when `pushed` is false (#3391): no remote, a remote that moved on, or git's refusal. */
488
+ notPushed?: string;
489
+ }
490
+
491
+ export interface RunWriteResult {
492
+ id: string;
493
+ /** Every line of the run's file after the write. */
494
+ lines: RunLine[];
495
+ ledger: RunLedgerWrite;
496
+ }
497
+
498
+ const APPEND_RETRY_ATTEMPTS = 5;
499
+
500
+ /**
501
+ * Append `make(existing)`'s lines to run `id`'s file, with the read-then-CAS
502
+ * retry the lease history uses, then push `chant/lifecycle` best-effort.
503
+ */
504
+ async function appendRun(top: string, rootOnDisk: string, id: string, make: (existing: RunLine[]) => RunLine[], message: string): Promise<RunWriteResult> {
505
+ await fetchLifecycleStatus({ cwd: top }).catch(() => undefined);
506
+ const dir = await runsDir(rootOnDisk);
507
+ const path = `${dir}/${id}.jsonl`;
508
+ let commit: string | undefined;
509
+ let written: RunLine[] = [];
510
+ let lastErr: unknown;
511
+ for (let attempt = 1; attempt <= APPEND_RETRY_ATTEMPTS && commit === undefined; attempt++) {
512
+ const prior = git(top, ["rev-parse", "--verify", "--quiet", `refs/heads/${LEDGER_BRANCH}:${path}`])?.trim() || null;
513
+ const text = prior ? ((await readBlobBySha(prior, { cwd: top })) ?? "") : "";
514
+ const existing = parseRunFile(text, id).lines;
515
+ const lines = make(existing);
516
+ const content = [text.replace(/\n$/, ""), ...lines.map((l) => JSON.stringify(l, sortedJsonReplacer))].filter(Boolean).join("\n");
517
+ try {
518
+ commit = await writeLedgerFiles([{ path, content, expectPriorSha: prior }], message, { cwd: top });
519
+ written = [...existing, ...lines];
520
+ } catch (err) {
521
+ if (!(err instanceof RefCASConflictError)) throw err;
522
+ lastErr = err;
523
+ }
524
+ }
525
+ if (commit === undefined) throw lastErr;
526
+ const push: LifecyclePushStatus = await pushLifecycleStatus({ cwd: top }).catch((err) => ({ status: "failed" as const, remote: "(unknown)", stderr: err instanceof Error ? err.message : String(err) }));
527
+ const notPushed = describeLifecyclePush(push);
528
+ return { id, lines: written, ledger: { branch: LEDGER_BRANCH, path, commit, pushed: push.status === "pushed", ...(notPushed ? { notPushed } : {}) } };
529
+ }
530
+
531
+ export interface RunWriteContext {
532
+ /** The git top. */
533
+ top: string;
534
+ /** The workspace root on disk, whose ledger holds the runs. */
535
+ rootOnDisk: string;
536
+ /** What relative paths in the fields resolve against. */
537
+ cwd: string;
538
+ now?: () => Date;
539
+ }
540
+
541
+ /** `runs start`: a run's start, with a new id unless the fields give one. Refused with `run-exists` for an id in use. */
542
+ export async function startRun(fields: unknown, ctx: RunWriteContext): Promise<RunWriteResult> {
543
+ const input = parseInput(runStartInputSchema, fields, "start");
544
+ const startedAt = input.startedAt ?? (ctx.now?.() ?? new Date()).toISOString();
545
+ const id = input.id ?? newRunId(new Date(startedAt));
546
+ const line = startLine(input, id, startedAt, ctx.cwd);
547
+ return appendRun(
548
+ ctx.top,
549
+ ctx.rootOnDisk,
550
+ id,
551
+ (existing) => {
552
+ if (existing.length > 0) throw new RunWriteError("run-exists", `the ledger already has an agent run ${id}`);
553
+ return [line];
554
+ },
555
+ `Agent run start: ${id}`,
556
+ );
557
+ }
558
+
559
+ /** `runs end <id>`: a started run's end. Refused with `run-unknown` when it never started and `run-ended` when it already ended. */
560
+ export async function endRun(id: string, fields: unknown, ctx: RunWriteContext): Promise<RunWriteResult> {
561
+ if (!RUN_ID_PATTERN.test(id)) throw new RunWriteError("run-unknown", `${JSON.stringify(id)} is not a run id`);
562
+ const input = parseInput(runEndInputSchema, fields, "end");
563
+ const now = ctx.now?.() ?? new Date();
564
+ const line = endLine(input, id, input.endedAt ?? now.toISOString(), ctx.top, ctx.cwd);
565
+ return appendRun(
566
+ ctx.top,
567
+ ctx.rootOnDisk,
568
+ id,
569
+ (existing) => {
570
+ const start = existing.find((l): l is RunStartLine => l.event === "start");
571
+ if (!start) throw new RunWriteError("run-unknown", `the ledger has no agent run ${id}; start it with runs start, or report a finished run with runs record`);
572
+ if (existing.some((l) => l.event === "end")) throw new RunWriteError("run-ended", `agent run ${id} has already ended`);
573
+ if (Date.parse(line.at) < Date.parse(start.at)) throw new RunWriteError("write-input-invalid", `endedAt ${line.at} is before the run started at ${start.at}`);
574
+ return [line];
575
+ },
576
+ `Agent run end: ${id}`,
577
+ );
578
+ }
579
+
580
+ /** `runs record`: a run that has finished, its start and end in one write. */
581
+ export async function recordRun(fields: unknown, ctx: RunWriteContext): Promise<RunWriteResult> {
582
+ const input = parseInput(runRecordInputSchema, fields, "record");
583
+ const now = ctx.now?.() ?? new Date();
584
+ const endedAt = input.endedAt ?? now.toISOString();
585
+ const startedAt = input.startedAt ?? endedAt;
586
+ if (Date.parse(endedAt) < Date.parse(startedAt)) throw new RunWriteError("write-input-invalid", `endedAt ${endedAt} is before startedAt ${startedAt}`);
587
+ const id = input.id ?? newRunId(new Date(startedAt));
588
+ const start = startLine(input, id, startedAt, ctx.cwd);
589
+ const end = endLine(input, id, endedAt, ctx.top, ctx.cwd);
590
+ return appendRun(
591
+ ctx.top,
592
+ ctx.rootOnDisk,
593
+ id,
594
+ (existing) => {
595
+ if (existing.length > 0) throw new RunWriteError("run-exists", `the ledger already has an agent run ${id}`);
596
+ return [start, end];
597
+ },
598
+ `Agent run: ${id}`,
599
+ );
600
+ }
601
+
602
+ /**
603
+ * `runs sign <id>`: append a statement over an ended run (#3192). The caller
604
+ * has built or checked the envelope against the record whose hash is
605
+ * `recordSha256`; the append refuses if the ledger's record is not that one.
606
+ */
607
+ export async function appendRunStatement(id: string, envelope: DsseEnvelope, recordSha256: string, ctx: RunWriteContext): Promise<RunWriteResult> {
608
+ if (!RUN_ID_PATTERN.test(id)) throw new RunWriteError("run-unknown", `${JSON.stringify(id)} is not a run id`);
609
+ const at = (ctx.now?.() ?? new Date()).toISOString();
610
+ return appendRun(
611
+ ctx.top,
612
+ ctx.rootOnDisk,
613
+ id,
614
+ (existing) => {
615
+ const start = existing.find((l): l is RunStartLine => l.event === "start");
616
+ const end = existing.find((l): l is RunEndLine => l.event === "end");
617
+ if (!start) throw new RunWriteError("run-unknown", `the ledger has no agent run ${id}`);
618
+ if (!end) throw new RunWriteError("run-not-ended", `agent run ${id} has not ended; a statement is signed over a run's whole record, so record its end first`);
619
+ if (runRecordDigest(start, end) !== recordSha256) throw new RunWriteError("run-statement-mismatch", `agent run ${id}'s record changed while it was being signed; sign it again`);
620
+ return [{ version: 1, event: "statement", run: id, at, envelope }];
621
+ },
622
+ `Agent run statement: ${id}`,
623
+ );
624
+ }
625
+
626
+ // ── Folding a run ────────────────────────────────────────────────────────────
627
+
628
+ /**
629
+ * How a commit joins a run: `trailer` when it carries `Chant-Run` with the
630
+ * run's id, `record` when the run's end lists it, `patch-id` when it has
631
+ * neither and its `git patch-id --stable` equals a commit's the run's end
632
+ * recorded (#3036), `squash` when it squashes a pull request whose original
633
+ * commits join the run one of those ways, and the read was asked to follow
634
+ * squashes (#3035).
635
+ */
636
+ export type RunJoin = "trailer" | "record" | "patch-id" | "squash";
637
+
638
+ /** The order a reader lists joins in: the commit's own trailer first, a squash's originals last. */
639
+ export const RUN_JOIN_ORDER: readonly RunJoin[] = ["trailer", "record", "patch-id", "squash"];
640
+
641
+ /** A commit a run made, as the read reports it. */
642
+ export interface RunCommitView {
643
+ sha: string;
644
+ patchId: string | null;
645
+ /** How the commit joins the run. */
646
+ joinedBy: RunJoin[];
647
+ /** The hunks of the commit the run's end says it wrote, or null when it gives none (#3034). Null for a commit joined by patch-id, whose lines may have moved. */
648
+ hunks: RunHunk[] | null;
649
+ /** For a commit joined by patch-id (#3036): the commit the run's end recorded with the same patch-id, which this one rewrites. */
650
+ recordedAs?: string;
651
+ /** For a squash joined to the run (#3035): the pull request's original commits through which it joins. */
652
+ via?: string[];
653
+ }
654
+
655
+ /** One run, its start and end folded together, as `runs --json` and `graph --intent` report it. */
656
+ export interface RunView {
657
+ id: string;
658
+ /** `running` until its end is recorded. */
659
+ state: "running" | "ended";
660
+ startedAt: string;
661
+ endedAt: string | null;
662
+ outcome: string | null;
663
+ by: string | null;
664
+ agent: string | null;
665
+ harness: { name: string; version: string | null };
666
+ model: string | null;
667
+ provider: string | null;
668
+ unit: { id: string; kind: string | null } | null;
669
+ lease: string | null;
670
+ records: RecordRef[];
671
+ instruction: RunPin | null;
672
+ usage: RunUsage | null;
673
+ models: RunModelUsage[];
674
+ cost: RunCost | null;
675
+ transcript: RunPin | null;
676
+ commits: RunCommitView[];
677
+ /** The decisions the run carried out, as `<kind>/<id>`: the ones its work item implements, and the decision records it names. */
678
+ decisions: string[];
679
+ /** Its file on the branch. */
680
+ ledger: string;
681
+ /** The SHA-256 a statement signs: of the start and end lines (#3192). Null until the run ends. */
682
+ record: { sha256: string } | null;
683
+ /** The signed statements the ledger holds for the run, oldest first (#3192). */
684
+ statements: { at: string; envelope: DsseEnvelope }[];
685
+ /** The statements judged against the runner keys at base. Set by the `runs` read and `runs verify`, never by a write. */
686
+ attestation?: RunAttestation;
687
+ }
688
+
689
+ /** Fold a run's lines into one view, or undefined when it has no start. */
690
+ export function foldRun(id: string, path: string, lines: RunLine[]): RunView | undefined {
691
+ const start = lines.find((l): l is RunStartLine => l.event === "start");
692
+ if (!start) return undefined;
693
+ const end = lines.find((l): l is RunEndLine => l.event === "end");
694
+ return {
695
+ id,
696
+ state: end ? "ended" : "running",
697
+ startedAt: start.at,
698
+ endedAt: end?.at ?? null,
699
+ outcome: end?.outcome ?? null,
700
+ by: start.by,
701
+ agent: start.agent,
702
+ harness: start.harness,
703
+ model: start.model,
704
+ provider: start.provider,
705
+ unit: start.unit,
706
+ lease: start.lease,
707
+ records: start.records ?? [],
708
+ instruction: start.instruction ?? null,
709
+ usage: end?.usage ?? null,
710
+ models: end?.models ?? [],
711
+ cost: end?.cost ?? null,
712
+ transcript: end?.transcript ?? null,
713
+ commits: (end?.commits ?? []).map((c) => ({ sha: c.sha, patchId: c.patchId, joinedBy: ["record" as const], hunks: Array.isArray(c.hunks) && c.hunks.length > 0 ? c.hunks : null })),
714
+ decisions: [],
715
+ ledger: path,
716
+ record: end ? { sha256: runRecordDigest(start, end) } : null,
717
+ statements: lines.filter((l): l is RunStatementLine => l.event === "statement").map((l) => ({ at: l.at, envelope: l.envelope })),
718
+ };
719
+ }
720
+
721
+ /** Every run in the ledger of the workspace at `rootOnDisk`, by id, with the malformed line count. Never fetches. */
722
+ export async function readRuns(top: string, rootOnDisk: string): Promise<{ tip: string | null; dir: string; runs: Map<string, RunView>; malformed: number }> {
723
+ const dir = await runsDir(rootOnDisk);
724
+ const { tip, files } = readRunFiles(top, dir);
725
+ const runs = new Map<string, RunView>();
726
+ let malformed = 0;
727
+ for (const [id, f] of files) {
728
+ const parsed = parseRunFile(f.content, id);
729
+ malformed += parsed.malformed;
730
+ const view = foldRun(id, f.path, parsed.lines);
731
+ if (view) runs.set(id, view);
732
+ else malformed += parsed.lines.length;
733
+ }
734
+ return { tip, dir, runs, malformed };
735
+ }
736
+
737
+ /**
738
+ * The commits reachable from `rev` that carry a `Chant-Run` trailer, by run
739
+ * id, newest first. One `git log` over the history; nothing is fetched.
740
+ */
741
+ export function commitsByRunTrailer(top: string, rev = "HEAD"): Map<string, string[]> {
742
+ const out = new Map<string, string[]>();
743
+ if (!commitId(top, rev)) return out;
744
+ const text = git(top, ["log", rev, `--format=%x00%H%x1f%(trailers:key=${RUN_TRAILER},valueonly,separator=%x1e)`]) ?? "";
745
+ for (const record of text.split("\0")) {
746
+ if (!record.trim()) continue;
747
+ const [sha, values] = record.split("\x1f");
748
+ for (const v of (values ?? "").split("\x1e").map((x) => x.trim()).filter(Boolean)) (out.get(v) ?? out.set(v, []).get(v)!).push(sha.trim());
749
+ }
750
+ return out;
751
+ }
752
+
753
+ /** Add the commits that name each run by trailer to its `commits`, after the ones its end lists. */
754
+ export function joinTrailerCommits(runs: Map<string, RunView>, byTrailer: Map<string, string[]>, top: string): void {
755
+ for (const [id, shas] of byTrailer) {
756
+ const run = runs.get(id);
757
+ if (!run) continue;
758
+ for (const sha of shas) {
759
+ const listed = run.commits.find((c) => c.sha === sha);
760
+ if (listed) {
761
+ if (!listed.joinedBy.includes("trailer")) listed.joinedBy.push("trailer");
762
+ } else run.commits.push({ sha, patchId: patchIdOf(top, sha), joinedBy: ["trailer"], hunks: null });
763
+ }
764
+ }
765
+ }
766
+
767
+ /** A day, the slack {@link joinPatchIdCommits} gives a run's start against clocks that disagree. */
768
+ const CLOCK_SLACK_MS = 24 * 60 * 60 * 1000;
769
+
770
+ /** Each recorded patch-id, to the runs whose end listed a commit with it and that commit. */
771
+ export function recordedPatchIds(runs: Iterable<RunView>): Map<string, { run: string; sha: string }[]> {
772
+ const out = new Map<string, { run: string; sha: string }[]>();
773
+ for (const r of runs) {
774
+ for (const c of r.commits) {
775
+ if (!c.patchId || !c.joinedBy.includes("record")) continue;
776
+ const list = out.get(c.patchId) ?? out.set(c.patchId, []).get(c.patchId)!;
777
+ if (!list.some((x) => x.run === r.id && x.sha === c.sha)) list.push({ run: r.id, sha: c.sha });
778
+ }
779
+ }
780
+ return out;
781
+ }
782
+
783
+ /**
784
+ * Add to each run the commits on `rev`'s history that join it by content
785
+ * (#3036): a commit that no run lists, that carries no `Chant-Run`, and whose
786
+ * patch-id equals one the run's end recorded. Only commits made since the
787
+ * earliest such run started are read, less a day for clocks.
788
+ */
789
+ export function joinPatchIdCommits(runs: Map<string, RunView>, byTrailer: Map<string, string[]>, top: string, rev = "HEAD"): void {
790
+ const recorded = recordedPatchIds(runs.values());
791
+ if (recorded.size === 0) return;
792
+ const starts = [...new Set([...recorded.values()].flat().map((x) => x.run))].map((id) => Date.parse(runs.get(id)!.startedAt)).filter((t) => !Number.isNaN(t));
793
+ if (starts.length === 0) return;
794
+ const since = new Date(Math.min(...starts) - CLOCK_SLACK_MS).toISOString();
795
+ const joined = new Set<string>([...byTrailer.values()].flat());
796
+ for (const r of runs.values()) for (const c of r.commits) joined.add(c.sha);
797
+ for (const [sha, patchId] of patchIdsSince(top, rev, since)) {
798
+ if (joined.has(sha)) continue;
799
+ for (const hit of recorded.get(patchId) ?? []) {
800
+ const run = runs.get(hit.run)!;
801
+ if (!run.commits.some((c) => c.sha === sha)) run.commits.push({ sha, patchId, joinedBy: ["patch-id"], hunks: null, recordedAs: hit.sha });
802
+ }
803
+ }
804
+ }
805
+
806
+ // ── Joining commits to runs, for the intent walks ────────────────────────────
807
+
808
+ /** A run as a commit in the intent walks names it: enough to say which model and harness made the change, and for whom. */
809
+ export interface RunRef {
810
+ id: string;
811
+ /** Whether the ledger has the run. A trailer can name a run whose record is missing or not fetched. */
812
+ recorded: boolean;
813
+ state: "running" | "ended" | null;
814
+ harness: string | null;
815
+ model: string | null;
816
+ provider: string | null;
817
+ by: string | null;
818
+ agent: string | null;
819
+ unit: string | null;
820
+ /** `trailer`: the commit carries `Chant-Run` with the id. `record`: the run's end lists the commit. `patch-id`: neither, and the commit's patch-id equals one the run recorded (#3036). */
821
+ joinedBy: RunJoin[];
822
+ /** For a `patch-id` join: the commit the run's end recorded with the same patch-id (#3036). */
823
+ recordedAs?: string;
824
+ /** For a `squash` join: the pull request's original commits that join the run (#3035). */
825
+ via?: string[];
826
+ }
827
+
828
+ /**
829
+ * A commit joined to one run by its trailer or the run's record whose content
830
+ * matches a commit another run recorded (#3036). The content join is not made,
831
+ * since a patch-id join never overrides the others; the intent walk reports it
832
+ * as a finding.
833
+ */
834
+ export interface RunJoinConflict {
835
+ sha: string;
836
+ /** The runs the commit's trailer or the runs' records join it to. */
837
+ joined: string[];
838
+ /** The other runs whose end recorded a commit with the same patch-id, and that commit. */
839
+ content: { run: string; sha: string }[];
840
+ }
841
+
842
+ /**
843
+ * For each commit, the runs that made it: the run its `Chant-Run` trailer
844
+ * names, and every run whose end lists it. A commit with neither joins every
845
+ * run whose end recorded its patch-id (#3036). Reads the run ledger once from
846
+ * the local branch; never fetches.
847
+ */
848
+ export async function runsForCommits(
849
+ top: string,
850
+ rootOnDisk: string,
851
+ commits: { sha: string; run: string | null }[],
852
+ ): Promise<{ refs: Map<string, RunRef[]>; runs: Map<string, RunView>; conflicts: RunJoinConflict[] }> {
853
+ let runs = new Map<string, RunView>();
854
+ try {
855
+ runs = (await readRuns(top, rootOnDisk)).runs;
856
+ } catch {
857
+ // A ledger that can't be located leaves every run unrecorded; the trailers still say which.
858
+ }
859
+ const listing = new Map<string, string[]>();
860
+ for (const r of runs.values()) for (const c of r.commits) (listing.get(c.sha) ?? listing.set(c.sha, []).get(c.sha)!).push(r.id);
861
+ // The content join (#3036): only when some run recorded a patch-id, and only for the commits read.
862
+ const recorded = recordedPatchIds(runs.values());
863
+ const patchIds = recorded.size > 0 ? patchIdsOf(top, commits.map((c) => c.sha)) : new Map<string, string>();
864
+ const refs = new Map<string, RunRef[]>();
865
+ const conflicts: RunJoinConflict[] = [];
866
+ const refOf = (id: string, joinedBy: RunJoin[], recordedAs?: string): RunRef => {
867
+ const r = runs.get(id);
868
+ return {
869
+ id,
870
+ recorded: r !== undefined,
871
+ state: r?.state ?? null,
872
+ harness: r?.harness.name ?? null,
873
+ model: r?.model ?? null,
874
+ provider: r?.provider ?? null,
875
+ by: r?.by ?? null,
876
+ agent: r?.agent ?? null,
877
+ unit: r?.unit?.id ?? null,
878
+ joinedBy,
879
+ ...(recordedAs ? { recordedAs } : {}),
880
+ };
881
+ };
882
+ for (const c of commits) {
883
+ const listed = listing.get(c.sha) ?? [];
884
+ const ids = [...new Set([...(c.run ? [c.run] : []), ...listed])];
885
+ const matches = (recorded.get(patchIds.get(c.sha) ?? "") ?? []).filter((m) => m.sha !== c.sha);
886
+ if (ids.length > 0) {
887
+ const others = matches.filter((m) => !ids.includes(m.run));
888
+ if (others.length > 0) conflicts.push({ sha: c.sha, joined: ids, content: others });
889
+ refs.set(
890
+ c.sha,
891
+ ids.map((id) => refOf(id, [...(c.run === id ? ["trailer" as const] : []), ...(listed.includes(id) ? ["record" as const] : [])])),
892
+ );
893
+ continue;
894
+ }
895
+ const byRun = new Map<string, string>();
896
+ for (const m of matches) if (!byRun.has(m.run)) byRun.set(m.run, m.sha);
897
+ refs.set(
898
+ c.sha,
899
+ [...byRun].map(([id, recordedAs]) => refOf(id, ["patch-id"], recordedAs)),
900
+ );
901
+ }
902
+ return { refs, runs, conflicts };
903
+ }
904
+
905
+ /**
906
+ * Give each followed squash commit the runs its original commits join
907
+ * (#3035): `refs` must hold the originals' runs. A run the squash already
908
+ * joins gets `squash` added; another is added with `joinedBy: ["squash"]`.
909
+ * `via` lists the originals that join it.
910
+ */
911
+ export function addSquashRuns(refs: Map<string, RunRef[]>, follows: Map<string, SquashFollow>): void {
912
+ for (const f of follows.values()) {
913
+ if (!f.followed) continue;
914
+ const list = refs.get(f.sha) ?? refs.set(f.sha, []).get(f.sha)!;
915
+ for (const o of f.commits) {
916
+ for (const r of refs.get(o) ?? []) {
917
+ const have = list.find((x) => x.id === r.id);
918
+ if (have) {
919
+ if (!have.joinedBy.includes("squash")) have.joinedBy.push("squash");
920
+ have.via = [...new Set([...(have.via ?? []), o])];
921
+ continue;
922
+ }
923
+ const { recordedAs: _r, via: _v, ...rest } = r;
924
+ list.push({ ...rest, joinedBy: ["squash"], via: [o] });
925
+ }
926
+ }
927
+ }
928
+ }
929
+
930
+ /**
931
+ * Follow the squash merges on `rev`'s history made since the earliest run
932
+ * started (#3035), and add each to the runs its pull request's original
933
+ * commits join: a commit a run's end lists, or one whose `Chant-Run` names
934
+ * the run. Missing pull request refs are fetched from `origin`. Returns what
935
+ * each squash's follow found, for the read's reasons.
936
+ */
937
+ export function joinSquashCommits(runs: Map<string, RunView>, top: string, rev = "HEAD"): SquashFollow[] {
938
+ const starts = [...runs.values()].map((r) => Date.parse(r.startedAt)).filter((t) => !Number.isNaN(t));
939
+ if (starts.length === 0 || !commitId(top, rev)) return [];
940
+ const since = new Date(Math.min(...starts) - CLOCK_SLACK_MS).toISOString();
941
+ const candidates: { sha: string; subject: string }[] = [];
942
+ for (const line of (git(top, ["log", rev, `--since=${since}`, "--no-merges", "--format=%H%x1f%s"]) ?? "").split("\n")) {
943
+ const [sha, subject] = line.split("\x1f");
944
+ if (sha && subject !== undefined) candidates.push({ sha: sha.trim(), subject });
945
+ }
946
+ const follows = followSquashes(top, candidates, { fetchMissing: true });
947
+ const originals = [...new Set([...follows.values()].flatMap((f) => f.commits))];
948
+ const trailerRun = new Map<string, string[]>();
949
+ if (originals.length > 0) {
950
+ const text = git(top, ["log", "--no-walk=unsorted", "--stdin", `--format=%x00%H%x1f%(trailers:key=${RUN_TRAILER},valueonly,separator=%x1e)`], `${originals.join("\n")}\n`) ?? "";
951
+ for (const record of text.split("\0")) {
952
+ if (!record.trim()) continue;
953
+ const [sha, values] = record.split("\x1f");
954
+ trailerRun.set(sha.trim(), (values ?? "").split("\x1e").map((x) => x.trim()).filter(Boolean));
955
+ }
956
+ }
957
+ for (const f of follows.values()) {
958
+ if (!f.followed) continue;
959
+ for (const r of runs.values()) {
960
+ const via = f.commits.filter((o) => r.commits.some((c) => c.sha === o && c.joinedBy.some((j) => j !== "squash")) || (trailerRun.get(o) ?? []).includes(r.id));
961
+ if (via.length === 0) continue;
962
+ const listed = r.commits.find((c) => c.sha === f.sha);
963
+ if (listed) {
964
+ if (!listed.joinedBy.includes("squash")) listed.joinedBy.push("squash");
965
+ listed.via = [...new Set([...(listed.via ?? []), ...via])];
966
+ } else r.commits.push({ sha: f.sha, patchId: patchIdOf(top, f.sha), joinedBy: ["squash"], hunks: null, via });
967
+ }
968
+ }
969
+ return [...follows.values()];
970
+ }