@intentius/chant 0.102.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 (464) hide show
  1. package/dist/audit/core.d.ts +2 -0
  2. package/dist/audit/core.d.ts.map +1 -1
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/change-set.d.ts +232 -0
  5. package/dist/change-set.d.ts.map +1 -0
  6. package/dist/cli/commands/audit.d.ts +7 -0
  7. package/dist/cli/commands/audit.d.ts.map +1 -1
  8. package/dist/cli/commands/build.d.ts.map +1 -1
  9. package/dist/cli/handlers/build.d.ts.map +1 -1
  10. package/dist/cli/handlers/change-set.d.ts +11 -0
  11. package/dist/cli/handlers/change-set.d.ts.map +1 -0
  12. package/dist/cli/handlers/fan-out.d.ts +33 -0
  13. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  14. package/dist/cli/handlers/operator.d.ts +7 -0
  15. package/dist/cli/handlers/operator.d.ts.map +1 -1
  16. package/dist/cli/handlers/pr.d.ts +31 -0
  17. package/dist/cli/handlers/pr.d.ts.map +1 -0
  18. package/dist/cli/handlers/run.d.ts.map +1 -1
  19. package/dist/cli/main.d.ts.map +1 -1
  20. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  21. package/dist/cli/mcp/workspace-tools.d.ts +3 -2
  22. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  23. package/dist/cli/registry.d.ts +63 -1
  24. package/dist/cli/registry.d.ts.map +1 -1
  25. package/dist/components/auto-release.d.ts +16 -0
  26. package/dist/components/auto-release.d.ts.map +1 -1
  27. package/dist/components/capability.d.ts +53 -0
  28. package/dist/components/capability.d.ts.map +1 -1
  29. package/dist/components/changed-files.d.ts +31 -0
  30. package/dist/components/changed-files.d.ts.map +1 -0
  31. package/dist/components/cli-support.d.ts +9 -1
  32. package/dist/components/cli-support.d.ts.map +1 -1
  33. package/dist/components/component.d.ts +24 -0
  34. package/dist/components/component.d.ts.map +1 -1
  35. package/dist/components/deploy-units.d.ts.map +1 -1
  36. package/dist/components/driver.d.ts +13 -0
  37. package/dist/components/driver.d.ts.map +1 -1
  38. package/dist/components/fan-out-output.d.ts +6 -0
  39. package/dist/components/fan-out-output.d.ts.map +1 -1
  40. package/dist/components/fan-out-record.d.ts +41 -0
  41. package/dist/components/fan-out-record.d.ts.map +1 -0
  42. package/dist/components/fan-out-run.d.ts +65 -0
  43. package/dist/components/fan-out-run.d.ts.map +1 -1
  44. package/dist/components/fan-out-support.d.ts +2 -0
  45. package/dist/components/fan-out-support.d.ts.map +1 -1
  46. package/dist/components/fan-out.d.ts +6 -0
  47. package/dist/components/fan-out.d.ts.map +1 -1
  48. package/dist/components/gated-wave-pipeline.d.ts +50 -0
  49. package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
  50. package/dist/components/index.d.ts +1 -1
  51. package/dist/components/index.d.ts.map +1 -1
  52. package/dist/components/layers.d.ts +25 -0
  53. package/dist/components/layers.d.ts.map +1 -0
  54. package/dist/components/pr-pipeline.d.ts +61 -0
  55. package/dist/components/pr-pipeline.d.ts.map +1 -0
  56. package/dist/components/pr-run.d.ts +119 -0
  57. package/dist/components/pr-run.d.ts.map +1 -0
  58. package/dist/components/promote.d.ts.map +1 -1
  59. package/dist/components/verbs/shell.d.ts.map +1 -1
  60. package/dist/components/wave-plan.d.ts +49 -0
  61. package/dist/components/wave-plan.d.ts.map +1 -0
  62. package/dist/gated-waves.d.ts +147 -0
  63. package/dist/gated-waves.d.ts.map +1 -0
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/lexicon.d.ts +48 -0
  67. package/dist/lexicon.d.ts.map +1 -1
  68. package/dist/lifecycle/gate-ledger.d.ts +7 -0
  69. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  70. package/dist/lifecycle/git.d.ts +46 -1
  71. package/dist/lifecycle/git.d.ts.map +1 -1
  72. package/dist/lint/post-synth.d.ts +18 -0
  73. package/dist/lint/post-synth.d.ts.map +1 -1
  74. package/dist/op/activities/activity-contracts.d.ts +126 -0
  75. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  76. package/dist/op/activities/change-set.d.ts +62 -0
  77. package/dist/op/activities/change-set.d.ts.map +1 -0
  78. package/dist/op/activities/decide.d.ts +2 -0
  79. package/dist/op/activities/decide.d.ts.map +1 -1
  80. package/dist/op/activities/factory.d.ts +137 -0
  81. package/dist/op/activities/factory.d.ts.map +1 -0
  82. package/dist/op/activities/index.d.ts +4 -0
  83. package/dist/op/activities/index.d.ts.map +1 -1
  84. package/dist/op/activity-contract.d.ts.map +1 -1
  85. package/dist/op/builders.d.ts +11 -0
  86. package/dist/op/builders.d.ts.map +1 -1
  87. package/dist/op/decide-backend.d.ts +5 -1
  88. package/dist/op/decide-backend.d.ts.map +1 -1
  89. package/dist/op/decide-read-inputs.d.ts +4 -2
  90. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  91. package/dist/op/factory.d.ts +83 -0
  92. package/dist/op/factory.d.ts.map +1 -0
  93. package/dist/op/index.d.ts +4 -1
  94. package/dist/op/index.d.ts.map +1 -1
  95. package/dist/op/op-ir.d.ts +2 -0
  96. package/dist/op/op-ir.d.ts.map +1 -1
  97. package/dist/op/op-verb-class.d.ts.map +1 -1
  98. package/dist/op/types.d.ts +8 -0
  99. package/dist/op/types.d.ts.map +1 -1
  100. package/dist/op/work-lease-run.d.ts.map +1 -1
  101. package/dist/plan-summary.d.ts +207 -0
  102. package/dist/plan-summary.d.ts.map +1 -0
  103. package/dist/pr-forge.d.ts +105 -0
  104. package/dist/pr-forge.d.ts.map +1 -0
  105. package/dist/pr-loop.d.ts +178 -0
  106. package/dist/pr-loop.d.ts.map +1 -0
  107. package/dist/telemetry-attribution.d.ts +31 -0
  108. package/dist/telemetry-attribution.d.ts.map +1 -1
  109. package/dist/terraform/hcl2json.d.ts +45 -0
  110. package/dist/terraform/hcl2json.d.ts.map +1 -0
  111. package/dist/terraform/parse.d.ts +4 -40
  112. package/dist/terraform/parse.d.ts.map +1 -1
  113. package/dist/workspace/box-cli.d.ts +12 -0
  114. package/dist/workspace/box-cli.d.ts.map +1 -0
  115. package/dist/workspace/box-factory.d.ts +27 -3
  116. package/dist/workspace/box-factory.d.ts.map +1 -1
  117. package/dist/workspace/box-listing.d.ts +85 -0
  118. package/dist/workspace/box-listing.d.ts.map +1 -0
  119. package/dist/workspace/box-publish.d.ts +147 -0
  120. package/dist/workspace/box-publish.d.ts.map +1 -0
  121. package/dist/workspace/broker-protocol.d.ts +190 -0
  122. package/dist/workspace/broker-protocol.d.ts.map +1 -0
  123. package/dist/workspace/checks.d.ts.map +1 -1
  124. package/dist/workspace/composites.d.ts +2 -2
  125. package/dist/workspace/composites.d.ts.map +1 -1
  126. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
  127. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
  128. package/dist/workspace/conformance/broker.d.ts +202 -0
  129. package/dist/workspace/conformance/broker.d.ts.map +1 -0
  130. package/dist/workspace/conformance/index.d.ts +2 -1
  131. package/dist/workspace/conformance/index.d.ts.map +1 -1
  132. package/dist/workspace/conformance/vitest.d.ts +9 -2
  133. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  134. package/dist/workspace/conformance/writer.d.ts +108 -14
  135. package/dist/workspace/conformance/writer.d.ts.map +1 -1
  136. package/dist/workspace/decide.d.ts +126 -12
  137. package/dist/workspace/decide.d.ts.map +1 -1
  138. package/dist/workspace/decision-points.schema.json +11 -4
  139. package/dist/workspace/declaration.d.ts +65 -0
  140. package/dist/workspace/declaration.d.ts.map +1 -1
  141. package/dist/workspace/declaration.schema.json +153 -4
  142. package/dist/workspace/environments.d.ts +36 -10
  143. package/dist/workspace/environments.d.ts.map +1 -1
  144. package/dist/workspace/factory-rules.d.ts +180 -0
  145. package/dist/workspace/factory-rules.d.ts.map +1 -0
  146. package/dist/workspace/init.d.ts +21 -0
  147. package/dist/workspace/init.d.ts.map +1 -1
  148. package/dist/workspace/intent-cli.d.ts +1 -1
  149. package/dist/workspace/intent-cli.d.ts.map +1 -1
  150. package/dist/workspace/intent-record.d.ts +28 -1
  151. package/dist/workspace/intent-record.d.ts.map +1 -1
  152. package/dist/workspace/intent.d.ts +102 -5
  153. package/dist/workspace/intent.d.ts.map +1 -1
  154. package/dist/workspace/json-edit.d.ts +24 -0
  155. package/dist/workspace/json-edit.d.ts.map +1 -0
  156. package/dist/workspace/jsonc.d.ts +11 -0
  157. package/dist/workspace/jsonc.d.ts.map +1 -1
  158. package/dist/workspace/kinds.d.ts +48 -0
  159. package/dist/workspace/kinds.d.ts.map +1 -1
  160. package/dist/workspace/lexicon-symbols.d.ts +29 -0
  161. package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
  162. package/dist/workspace/lineage-upgrade.d.ts +31 -2
  163. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  164. package/dist/workspace/point-answer.schema.json +42 -2
  165. package/dist/workspace/points-cli.d.ts +10 -3
  166. package/dist/workspace/points-cli.d.ts.map +1 -1
  167. package/dist/workspace/points.d.ts +66 -5
  168. package/dist/workspace/points.d.ts.map +1 -1
  169. package/dist/workspace/reason-codes.d.ts +33 -4
  170. package/dist/workspace/reason-codes.d.ts.map +1 -1
  171. package/dist/workspace/record-sessions.d.ts +5 -7
  172. package/dist/workspace/record-sessions.d.ts.map +1 -1
  173. package/dist/workspace/records-cli.d.ts +7 -0
  174. package/dist/workspace/records-cli.d.ts.map +1 -1
  175. package/dist/workspace/records-close.d.ts +7 -3
  176. package/dist/workspace/records-close.d.ts.map +1 -1
  177. package/dist/workspace/records-write.d.ts +66 -6
  178. package/dist/workspace/records-write.d.ts.map +1 -1
  179. package/dist/workspace/records.d.ts +28 -9
  180. package/dist/workspace/records.d.ts.map +1 -1
  181. package/dist/workspace/runs-cli.d.ts +138 -6
  182. package/dist/workspace/runs-cli.d.ts.map +1 -1
  183. package/dist/workspace/runs.d.ts +132 -11
  184. package/dist/workspace/runs.d.ts.map +1 -1
  185. package/dist/workspace/session-kinds.d.ts +1 -1
  186. package/dist/workspace/session-kinds.d.ts.map +1 -1
  187. package/dist/workspace/squash.d.ts +80 -0
  188. package/dist/workspace/squash.d.ts.map +1 -0
  189. package/dist/workspace/status-gates.d.ts +2 -0
  190. package/dist/workspace/status-gates.d.ts.map +1 -1
  191. package/dist/workspace/status.d.ts +54 -0
  192. package/dist/workspace/status.d.ts.map +1 -1
  193. package/dist/workspace/symbols.d.ts +16 -5
  194. package/dist/workspace/symbols.d.ts.map +1 -1
  195. package/dist/workspace/trust/run-commits.d.ts +46 -0
  196. package/dist/workspace/trust/run-commits.d.ts.map +1 -0
  197. package/dist/workspace/trust/run-statement.d.ts +128 -0
  198. package/dist/workspace/trust/run-statement.d.ts.map +1 -0
  199. package/dist/workspace/trust/seal.d.ts +6 -1
  200. package/dist/workspace/trust/seal.d.ts.map +1 -1
  201. package/dist/workspace/trust/verify-cli.d.ts +1 -1
  202. package/dist/workspace/trust/verify.d.ts +16 -2
  203. package/dist/workspace/trust/verify.d.ts.map +1 -1
  204. package/dist/workspace/wip-cli.d.ts +21 -0
  205. package/dist/workspace/wip-cli.d.ts.map +1 -0
  206. package/dist/workspace/wip.d.ts +268 -0
  207. package/dist/workspace/wip.d.ts.map +1 -0
  208. package/dist/workspace/work-cli.d.ts.map +1 -1
  209. package/dist/workspace/work-evidence.d.ts +1 -1
  210. package/dist/workspace/work-evidence.d.ts.map +1 -1
  211. package/dist/workspace/work-size.d.ts +55 -0
  212. package/dist/workspace/work-size.d.ts.map +1 -0
  213. package/dist/workspace/workspace-kinds.schema.json +122 -0
  214. package/dist/workspace/write-lock-cli.d.ts +60 -0
  215. package/dist/workspace/write-lock-cli.d.ts.map +1 -0
  216. package/dist/workspace/write-lock.d.ts +152 -0
  217. package/dist/workspace/write-lock.d.ts.map +1 -0
  218. package/dist/workspace/write-scope.d.ts +6 -2
  219. package/dist/workspace/write-scope.d.ts.map +1 -1
  220. package/package.json +6 -1
  221. package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
  222. package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
  223. package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
  224. package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
  225. package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
  226. package/src/__fixtures__/change-set/warden.plan.json +44 -0
  227. package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
  228. package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
  229. package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
  230. package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
  231. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
  232. package/src/audit/core.test.ts +15 -0
  233. package/src/audit/core.ts +8 -2
  234. package/src/audit/discover.test.ts +11 -0
  235. package/src/audit/discover.ts +7 -2
  236. package/src/change-set-bundle.test.ts +57 -0
  237. package/src/change-set.test.ts +215 -0
  238. package/src/change-set.ts +445 -0
  239. package/src/cli/commands/audit-opt-in.test.ts +54 -0
  240. package/src/cli/commands/audit.ts +29 -2
  241. package/src/cli/commands/build.test.ts +19 -0
  242. package/src/cli/commands/build.ts +3 -2
  243. package/src/cli/handlers/build.ts +13 -1
  244. package/src/cli/handlers/change-set.test.ts +56 -0
  245. package/src/cli/handlers/change-set.ts +44 -0
  246. package/src/cli/handlers/fan-out.test.ts +221 -2
  247. package/src/cli/handlers/fan-out.ts +165 -75
  248. package/src/cli/handlers/graph.ts +3 -0
  249. package/src/cli/handlers/operator.test.ts +21 -0
  250. package/src/cli/handlers/operator.ts +34 -3
  251. package/src/cli/handlers/pr.ts +373 -0
  252. package/src/cli/handlers/run.ts +1 -0
  253. package/src/cli/main.test.ts +20 -0
  254. package/src/cli/main.ts +185 -23
  255. package/src/cli/mcp/never-over-mcp.test.ts +101 -0
  256. package/src/cli/mcp/op-tools.ts +24 -2
  257. package/src/cli/mcp/workspace-tools.test.ts +1 -1
  258. package/src/cli/mcp/workspace-tools.ts +34 -6
  259. package/src/cli/registry.ts +63 -1
  260. package/src/components/auto-release.ts +1 -1
  261. package/src/components/capability.ts +55 -0
  262. package/src/components/changed-files.ts +66 -0
  263. package/src/components/cli-support.ts +28 -5
  264. package/src/components/component.schema.json +17 -0
  265. package/src/components/component.ts +25 -0
  266. package/src/components/deploy-units.ts +3 -0
  267. package/src/components/driver.test.ts +52 -0
  268. package/src/components/driver.ts +31 -13
  269. package/src/components/fan-out-output.ts +42 -1
  270. package/src/components/fan-out-record.ts +69 -0
  271. package/src/components/fan-out-run.test.ts +17 -0
  272. package/src/components/fan-out-run.ts +304 -1
  273. package/src/components/fan-out-support.ts +3 -0
  274. package/src/components/fan-out-waves.test.ts +375 -0
  275. package/src/components/fan-out.ts +18 -17
  276. package/src/components/gated-wave-pipeline.ts +82 -0
  277. package/src/components/index.ts +1 -0
  278. package/src/components/layers.ts +30 -0
  279. package/src/components/pr-pipeline.ts +91 -0
  280. package/src/components/pr-run.test.ts +205 -0
  281. package/src/components/pr-run.ts +280 -0
  282. package/src/components/promote.ts +5 -0
  283. package/src/components/verbs/shell.test.ts +11 -0
  284. package/src/components/verbs/shell.ts +7 -3
  285. package/src/components/wave-plan.ts +124 -0
  286. package/src/discovery/sandbox/bundle.ts +1 -1
  287. package/src/gated-waves-bundle.test.ts +62 -0
  288. package/src/gated-waves.test.ts +118 -0
  289. package/src/gated-waves.ts +240 -0
  290. package/src/index.ts +1 -0
  291. package/src/lexicon.ts +51 -0
  292. package/src/lifecycle/gate-ledger.ts +7 -0
  293. package/src/lifecycle/git.ts +102 -16
  294. package/src/lint/post-synth.test.ts +19 -0
  295. package/src/lint/post-synth.ts +19 -1
  296. package/src/meta/declared-imports.test.ts +1 -1
  297. package/src/op/activities/activity-contracts.ts +84 -0
  298. package/src/op/activities/change-set.ts +114 -0
  299. package/src/op/activities/decide.ts +3 -0
  300. package/src/op/activities/factory.ts +675 -0
  301. package/src/op/activities/index.ts +6 -0
  302. package/src/op/activity-contract.test.ts +21 -1
  303. package/src/op/activity-contract.ts +19 -9
  304. package/src/op/builders.ts +23 -0
  305. package/src/op/decide-backend.ts +5 -1
  306. package/src/op/decide-read-inputs.ts +33 -5
  307. package/src/op/factory.e2e.test.ts +232 -0
  308. package/src/op/factory.ts +118 -0
  309. package/src/op/index.ts +9 -1
  310. package/src/op/op-ir.test.ts +9 -0
  311. package/src/op/op-ir.ts +4 -0
  312. package/src/op/op-verb-class.ts +3 -0
  313. package/src/op/types.ts +8 -0
  314. package/src/op/work-lease-run.ts +3 -0
  315. package/src/plan-summary.test.ts +263 -0
  316. package/src/plan-summary.ts +845 -0
  317. package/src/pr-forge.test.ts +237 -0
  318. package/src/pr-forge.ts +332 -0
  319. package/src/pr-loop-bundle.test.ts +46 -0
  320. package/src/pr-loop.test.ts +171 -0
  321. package/src/pr-loop.ts +324 -0
  322. package/src/telemetry-attribution.test.ts +23 -1
  323. package/src/telemetry-attribution.ts +51 -0
  324. package/src/terraform/hcl2json.test.ts +21 -0
  325. package/src/terraform/hcl2json.ts +81 -0
  326. package/src/terraform/parse.ts +11 -74
  327. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  328. package/src/workspace/app-kind.test.ts +196 -0
  329. package/src/workspace/box-cli.ts +113 -0
  330. package/src/workspace/box-factory.test.ts +4 -1
  331. package/src/workspace/box-factory.ts +37 -4
  332. package/src/workspace/box-listing-write.schema.json +204 -0
  333. package/src/workspace/box-listing.test.ts +234 -0
  334. package/src/workspace/box-listing.ts +324 -0
  335. package/src/workspace/box-publish.schema.json +498 -0
  336. package/src/workspace/box-publish.test.ts +204 -0
  337. package/src/workspace/box-publish.ts +420 -0
  338. package/src/workspace/box-ship.test.ts +96 -0
  339. package/src/workspace/broker-protocol.schema.json +186 -0
  340. package/src/workspace/broker-protocol.test.ts +122 -0
  341. package/src/workspace/broker-protocol.ts +317 -0
  342. package/src/workspace/builder-tiers.test.ts +146 -0
  343. package/src/workspace/change-set.schema.json +460 -0
  344. package/src/workspace/chant-migrations.e2e.test.ts +17 -5
  345. package/src/workspace/checks/boxes.test.ts +3 -0
  346. package/src/workspace/checks.ts +10 -3
  347. package/src/workspace/component-environments.test.ts +105 -0
  348. package/src/workspace/composites.schema.json +23 -4
  349. package/src/workspace/composites.test.ts +8 -8
  350. package/src/workspace/composites.ts +13 -2
  351. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
  352. package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
  353. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +7 -2
  354. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +42 -2
  355. package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  356. package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  357. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +96 -0
  358. package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  359. package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  360. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +14 -8
  361. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +187 -6
  362. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +25 -0
  363. package/src/workspace/conformance/broker.test.ts +122 -0
  364. package/src/workspace/conformance/broker.ts +792 -0
  365. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  366. package/src/workspace/conformance/index.mjs +22 -1
  367. package/src/workspace/conformance/index.ts +5 -1
  368. package/src/workspace/conformance/vitest.ts +33 -1
  369. package/src/workspace/conformance/writer-conformance.e2e.test.ts +49 -3
  370. package/src/workspace/conformance/writer.test.ts +32 -2
  371. package/src/workspace/conformance/writer.ts +339 -28
  372. package/src/workspace/decide.test.ts +208 -4
  373. package/src/workspace/decide.ts +427 -73
  374. package/src/workspace/decision-points.schema.json +11 -4
  375. package/src/workspace/declaration.schema.json +153 -4
  376. package/src/workspace/declaration.ts +141 -0
  377. package/src/workspace/environments.ts +66 -14
  378. package/src/workspace/factory-rules.test.ts +62 -0
  379. package/src/workspace/factory-rules.ts +218 -0
  380. package/src/workspace/graph-cli.ts +2 -2
  381. package/src/workspace/graph.schema.json +67 -0
  382. package/src/workspace/identity.test.ts +23 -0
  383. package/src/workspace/init.ts +82 -2
  384. package/src/workspace/intent-cli.ts +26 -9
  385. package/src/workspace/intent-patch-id.test.ts +162 -0
  386. package/src/workspace/intent-record.schema.json +130 -3
  387. package/src/workspace/intent-record.ts +74 -28
  388. package/src/workspace/intent-reviews.test.ts +76 -0
  389. package/src/workspace/intent-squash.test.ts +213 -0
  390. package/src/workspace/intent-why.test.ts +5 -5
  391. package/src/workspace/intent.schema.json +234 -4
  392. package/src/workspace/intent.ts +260 -66
  393. package/src/workspace/json-edit.test.ts +121 -0
  394. package/src/workspace/json-edit.ts +142 -0
  395. package/src/workspace/jsonc.ts +20 -0
  396. package/src/workspace/kinds.ts +157 -2
  397. package/src/workspace/lexicon-symbols.test.ts +148 -0
  398. package/src/workspace/lexicon-symbols.ts +125 -0
  399. package/src/workspace/lineage-upgrade.test.ts +63 -0
  400. package/src/workspace/lineage-upgrade.ts +146 -5
  401. package/src/workspace/plan-summary.schema.json +155 -0
  402. package/src/workspace/point-answer.schema.json +42 -2
  403. package/src/workspace/points-cli.ts +42 -16
  404. package/src/workspace/points-write.schema.json +162 -4
  405. package/src/workspace/points.schema.json +157 -2
  406. package/src/workspace/points.test.ts +13 -1
  407. package/src/workspace/points.ts +118 -12
  408. package/src/workspace/pr-report.schema.json +95 -0
  409. package/src/workspace/profiles.test.ts +159 -0
  410. package/src/workspace/read-contract.test.ts +2 -2
  411. package/src/workspace/reason-codes.test.ts +13 -1
  412. package/src/workspace/reason-codes.ts +38 -4
  413. package/src/workspace/record-sessions.ts +9 -8
  414. package/src/workspace/records-amend.schema.json +78 -0
  415. package/src/workspace/records-cli.ts +20 -6
  416. package/src/workspace/records-close.schema.json +78 -0
  417. package/src/workspace/records-close.ts +31 -7
  418. package/src/workspace/records-concurrent.e2e.test.ts +135 -0
  419. package/src/workspace/records-concurrent.test.ts +145 -0
  420. package/src/workspace/records-formats.test.ts +46 -10
  421. package/src/workspace/records-new.schema.json +7 -0
  422. package/src/workspace/records-review.schema.json +78 -0
  423. package/src/workspace/records-sessions-write.test.ts +69 -0
  424. package/src/workspace/records-sessions.test.ts +22 -0
  425. package/src/workspace/records-write.ts +192 -25
  426. package/src/workspace/records.schema.json +16 -0
  427. package/src/workspace/records.ts +50 -9
  428. package/src/workspace/reference-kinds/app/package.json +10 -0
  429. package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  430. package/src/workspace/run-statement.schema.json +622 -0
  431. package/src/workspace/runs-cli.ts +343 -14
  432. package/src/workspace/runs-statement.test.ts +239 -0
  433. package/src/workspace/runs-write.schema.json +416 -5
  434. package/src/workspace/runs.schema.json +451 -4
  435. package/src/workspace/runs.test.ts +39 -0
  436. package/src/workspace/runs.ts +296 -35
  437. package/src/workspace/session-kinds.ts +3 -3
  438. package/src/workspace/squash.ts +198 -0
  439. package/src/workspace/status-contract.test.ts +5 -2
  440. package/src/workspace/status-gates.ts +3 -1
  441. package/src/workspace/status.schema.json +126 -3
  442. package/src/workspace/status.ts +90 -1
  443. package/src/workspace/symbols.ts +20 -9
  444. package/src/workspace/trust/run-commits.ts +115 -0
  445. package/src/workspace/trust/run-statement.ts +226 -0
  446. package/src/workspace/trust/seal.ts +8 -2
  447. package/src/workspace/trust/verify-cli.ts +6 -5
  448. package/src/workspace/trust/verify.ts +39 -6
  449. package/src/workspace/wip-cli.ts +97 -0
  450. package/src/workspace/wip-write.schema.json +504 -0
  451. package/src/workspace/wip.schema.json +334 -0
  452. package/src/workspace/wip.test.ts +294 -0
  453. package/src/workspace/wip.ts +753 -0
  454. package/src/workspace/work-cli.ts +3 -0
  455. package/src/workspace/work-evidence.schema.json +3 -0
  456. package/src/workspace/work-size.test.ts +77 -0
  457. package/src/workspace/work-size.ts +93 -0
  458. package/src/workspace/workspace-kinds.schema.json +122 -0
  459. package/src/workspace/write-lock-cli.ts +131 -0
  460. package/src/workspace/write-lock.schema.json +172 -0
  461. package/src/workspace/write-lock.test.ts +189 -0
  462. package/src/workspace/write-lock.ts +474 -0
  463. package/src/workspace/write-scope.test.ts +16 -1
  464. package/src/workspace/write-scope.ts +33 -11
@@ -23,6 +23,9 @@
23
23
  * the source of its prices, the transcript pinned by hash, and the commits
24
24
  * the run made, each with its `git patch-id --stable` (#3036) and, when
25
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.
26
29
  *
27
30
  * The file name is `_agent-runs`, not `runs`, because an Op run already has
28
31
  * `<env>/runs__<op>.jsonl` (`../lifecycle/run-ledger.ts`).
@@ -33,6 +36,18 @@
33
36
  * run's end can also list commits, which joins a commit that carries no
34
37
  * trailer. `workspace runs` reports both, and `graph --intent` links each
35
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`.
36
51
  */
37
52
 
38
53
  import { execFileSync } from "node:child_process";
@@ -40,10 +55,13 @@ import { createHash, randomBytes } from "node:crypto";
40
55
  import { readFileSync, statSync } from "node:fs";
41
56
  import { resolve } from "node:path";
42
57
  import { z } from "zod";
43
- import { fetchLifecycleStatus, pushLifecycle, readBlobBySha, RefCASConflictError, writeLedgerFiles } from "../lifecycle/git";
58
+ import { describeLifecyclePush, fetchLifecycleStatus, type LifecyclePushStatus, pushLifecycleStatus, readBlobBySha, RefCASConflictError, writeLedgerFiles } from "../lifecycle/git";
44
59
  import { resolveMemberLedger } from "../lifecycle/member-ledger";
45
60
  import { sortedJsonReplacer } from "../utils";
61
+ import { followSquashes, type SquashFollow } from "./squash";
46
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";
47
65
 
48
66
  /** The directory on `chant/lifecycle` that holds the agent runs, under the workspace root's ledger prefix. */
49
67
  export const AGENT_RUNS_DIR = "_agent-runs";
@@ -162,7 +180,16 @@ export interface RunEndLine {
162
180
  commits: RunCommit[];
163
181
  }
164
182
 
165
- export type RunLine = RunStartLine | RunEndLine;
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;
166
193
 
167
194
  // ── The caller's fields ──────────────────────────────────────────────────────
168
195
 
@@ -231,7 +258,7 @@ export type RunRecordInput = z.infer<typeof runRecordInputSchema>;
231
258
  // ── Errors ───────────────────────────────────────────────────────────────────
232
259
 
233
260
  /** Why a run write was refused. */
234
- export type RunWriteRefusal = "run-exists" | "run-unknown" | "run-ended" | "write-input-invalid" | "not-a-git-repository";
261
+ export type RunWriteRefusal = "run-exists" | "run-unknown" | "run-ended" | "run-not-ended" | "run-statement-mismatch" | "write-input-invalid" | "not-a-git-repository";
235
262
 
236
263
  export class RunWriteError extends Error {
237
264
  constructor(
@@ -266,6 +293,40 @@ export function patchIdOf(top: string, sha: string): string | null {
266
293
  return out?.trim().split(/\s+/)[0] || null;
267
294
  }
268
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
+
269
330
  function hashFile(path: string): RunPin {
270
331
  const bytes = readFileSync(path);
271
332
  return { sha256: createHash("sha256").update(bytes).digest("hex"), bytes: statSync(path).size, ref: null };
@@ -366,7 +427,8 @@ export function parseRunFile(content: string, id: string): { lines: RunLine[]; m
366
427
  for (const raw of content.split("\n").map((l) => l.trim()).filter(Boolean)) {
367
428
  try {
368
429
  const v = JSON.parse(raw) as Partial<RunLine>;
369
- const ok = v.version === 1 && (v.event === "start" || v.event === "end") && v.run === id && typeof v.at === "string";
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";
370
432
  if (ok) lines.push(v as RunLine);
371
433
  else malformed++;
372
434
  } catch {
@@ -382,10 +444,10 @@ export async function runsDir(rootOnDisk: string): Promise<string> {
382
444
  return `${prefix}${AGENT_RUNS_DIR}`;
383
445
  }
384
446
 
385
- /** Every run file in `dir` on the local branch, by run id, and the branch tip read. Never fetches. */
386
- export function readRunFiles(top: string, dir: string): { tip: string | null; files: Map<string, { path: string; content: string }> } {
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 }> } {
387
449
  const files = new Map<string, { path: string; content: string }>();
388
- const tip = commitId(top, `refs/heads/${LEDGER_BRANCH}`) ?? null;
450
+ const tip = commitId(top, ref) ?? null;
389
451
  if (!tip) return { tip, files };
390
452
  const listing = git(top, ["ls-tree", "-z", `${tip}:${dir}`]);
391
453
  if (!listing) return { tip, files };
@@ -422,6 +484,8 @@ export interface RunLedgerWrite {
422
484
  commit: string;
423
485
  /** Whether chant/lifecycle reached the remote; false with no remote too. */
424
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;
425
489
  }
426
490
 
427
491
  export interface RunWriteResult {
@@ -459,8 +523,9 @@ async function appendRun(top: string, rootOnDisk: string, id: string, make: (exi
459
523
  }
460
524
  }
461
525
  if (commit === undefined) throw lastErr;
462
- const pushed = await pushLifecycle({ cwd: top }).catch(() => false);
463
- return { id, lines: written, ledger: { branch: LEDGER_BRANCH, path, commit, pushed } };
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 } : {}) } };
464
529
  }
465
530
 
466
531
  export interface RunWriteContext {
@@ -534,16 +599,57 @@ export async function recordRun(fields: unknown, ctx: RunWriteContext): Promise<
534
599
  );
535
600
  }
536
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
+
537
626
  // ── Folding a run ────────────────────────────────────────────────────────────
538
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
+
539
641
  /** A commit a run made, as the read reports it. */
540
642
  export interface RunCommitView {
541
643
  sha: string;
542
644
  patchId: string | null;
543
- /** How the commit joins the run: `trailer` when it carries `Chant-Run`, `record` when the run's end lists it. */
544
- joinedBy: ("trailer" | "record")[];
545
- /** The hunks of the commit the run's end says it wrote, or null when it gives none (#3034). */
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. */
546
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[];
547
653
  }
548
654
 
549
655
  /** One run, its start and end folded together, as `runs --json` and `graph --intent` report it. */
@@ -572,6 +678,12 @@ export interface RunView {
572
678
  decisions: string[];
573
679
  /** Its file on the branch. */
574
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;
575
687
  }
576
688
 
577
689
  /** Fold a run's lines into one view, or undefined when it has no start. */
@@ -601,6 +713,8 @@ export function foldRun(id: string, path: string, lines: RunLine[]): RunView | u
601
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 })),
602
714
  decisions: [],
603
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 })),
604
718
  };
605
719
  }
606
720
 
@@ -650,6 +764,45 @@ export function joinTrailerCommits(runs: Map<string, RunView>, byTrailer: Map<st
650
764
  }
651
765
  }
652
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
+
653
806
  // ── Joining commits to runs, for the intent walks ────────────────────────────
654
807
 
655
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. */
@@ -664,16 +817,39 @@ export interface RunRef {
664
817
  by: string | null;
665
818
  agent: string | null;
666
819
  unit: string | null;
667
- /** `trailer`: the commit carries `Chant-Run` with the id. `record`: the run's end lists the commit. */
668
- joinedBy: ("trailer" | "record")[];
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 }[];
669
840
  }
670
841
 
671
842
  /**
672
843
  * For each commit, the runs that made it: the run its `Chant-Run` trailer
673
- * names, and every run whose end lists it. Reads the run ledger once from the
674
- * local branch; never fetches.
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.
675
847
  */
676
- export async function runsForCommits(top: string, rootOnDisk: string, commits: { sha: string; run: string | null }[]): Promise<{ refs: Map<string, RunRef[]>; runs: Map<string, RunView> }> {
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[] }> {
677
853
  let runs = new Map<string, RunView>();
678
854
  try {
679
855
  runs = (await readRuns(top, rootOnDisk)).runs;
@@ -682,28 +858,113 @@ export async function runsForCommits(top: string, rootOnDisk: string, commits: {
682
858
  }
683
859
  const listing = new Map<string, string[]>();
684
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>();
685
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
+ };
686
882
  for (const c of commits) {
687
- const ids = [...new Set([...(c.run ? [c.run] : []), ...(listing.get(c.sha) ?? [])])];
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);
688
897
  refs.set(
689
898
  c.sha,
690
- ids.map((id) => {
691
- const r = runs.get(id);
692
- const joinedBy: ("trailer" | "record")[] = [...(c.run === id ? ["trailer" as const] : []), ...((listing.get(c.sha) ?? []).includes(id) ? ["record" as const] : [])];
693
- return {
694
- id,
695
- recorded: r !== undefined,
696
- state: r?.state ?? null,
697
- harness: r?.harness.name ?? null,
698
- model: r?.model ?? null,
699
- provider: r?.provider ?? null,
700
- by: r?.by ?? null,
701
- agent: r?.agent ?? null,
702
- unit: r?.unit?.id ?? null,
703
- joinedBy,
704
- };
705
- }),
899
+ [...byRun].map(([id, recordedAs]) => refOf(id, ["patch-id"], recordedAs)),
706
900
  );
707
901
  }
708
- return { refs, runs };
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()];
709
970
  }
@@ -11,7 +11,7 @@ import { execFileSync } from "node:child_process";
11
11
  import { dirname, resolve } from "node:path";
12
12
  import { WorkspaceReadError } from "./declaration";
13
13
  import { gitRoot, resolveRevision } from "./record-source";
14
- import { loadRecordKind, RecordReadError, type LoadedRecordKind } from "./records";
14
+ import { loadRecordKind, RecordReadError, sessionSubjectKinds, type LoadedRecordKind } from "./records";
15
15
  import { declaredKindFiles, realpathOr } from "./records-cli";
16
16
 
17
17
  /** The shape of an id `records --since` looks up as a session before reading it as a revision: `S-0002`, `ws-052`. */
@@ -46,10 +46,10 @@ export async function findSessionKinds(cwd: string, extra: string[] = []): Promi
46
46
  return out;
47
47
  }
48
48
 
49
- /** The session kinds whose `session.subjects.kind` is the kind file `subjectFile`. */
49
+ /** The session kinds one of whose subject kinds (`session.subjects.kind` or `.kinds`) is the kind file `subjectFile`. */
50
50
  export async function sessionKindsFor(subjectFile: string, cwd: string): Promise<LoadedRecordKind[]> {
51
51
  const target = realpathOr(resolve(cwd, subjectFile));
52
- return (await findSessionKinds(cwd)).filter((k) => realpathOr(resolve(dirname(k.file), k.kind.session!.subjects.kind)) === target);
52
+ return (await findSessionKinds(cwd)).filter((k) => sessionSubjectKinds(k.kind.session!).some((f) => realpathOr(resolve(dirname(k.file), f)) === target));
53
53
  }
54
54
 
55
55
  /** The full commit id HEAD names in the repository holding `dir`, or null outside git or before the first commit. */