@intentius/chant 0.101.0 → 0.102.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 (427) 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/discover.d.ts.map +1 -1
  4. package/dist/audit/nginx.d.ts.map +1 -1
  5. package/dist/audit/prior-art.d.ts +48 -0
  6. package/dist/audit/prior-art.d.ts.map +1 -1
  7. package/dist/behaviour-kinds.d.ts.map +1 -1
  8. package/dist/builder.d.ts.map +1 -1
  9. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  10. package/dist/cli/commands/check-lexicon.d.ts +16 -0
  11. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  12. package/dist/cli/commands/import.d.ts +8 -0
  13. package/dist/cli/commands/import.d.ts.map +1 -1
  14. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  15. package/dist/cli/commands/init.d.ts +19 -0
  16. package/dist/cli/commands/init.d.ts.map +1 -1
  17. package/dist/cli/commands/onboard.d.ts +9 -0
  18. package/dist/cli/commands/onboard.d.ts.map +1 -1
  19. package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
  20. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
  21. package/dist/cli/handlers/dev.d.ts.map +1 -1
  22. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  23. package/dist/cli/handlers/misc.d.ts.map +1 -1
  24. package/dist/cli/handlers/operator.d.ts +9 -0
  25. package/dist/cli/handlers/operator.d.ts.map +1 -1
  26. package/dist/cli/handlers/run.d.ts.map +1 -1
  27. package/dist/cli/lsp/server.d.ts.map +1 -1
  28. package/dist/cli/main.d.ts.map +1 -1
  29. package/dist/cli/mcp/server.d.ts.map +1 -1
  30. package/dist/cli/mcp/tools/build.d.ts.map +1 -1
  31. package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
  32. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  33. package/dist/cli/mcp/tools/import.d.ts.map +1 -1
  34. package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
  35. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
  36. package/dist/cli/mcp/tools/search.d.ts.map +1 -1
  37. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  38. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  39. package/dist/cli/registry.d.ts +11 -2
  40. package/dist/cli/registry.d.ts.map +1 -1
  41. package/dist/codegen/fetch.d.ts.map +1 -1
  42. package/dist/codegen/naming.d.ts.map +1 -1
  43. package/dist/codegen/pinned-upgrade.d.ts +67 -3
  44. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  45. package/dist/codegen/registry.d.ts.map +1 -1
  46. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  47. package/dist/components/capability.d.ts.map +1 -1
  48. package/dist/components/driver.d.ts.map +1 -1
  49. package/dist/components/fan-out.d.ts.map +1 -1
  50. package/dist/components/starter-plugin.d.ts.map +1 -1
  51. package/dist/components/verbs/process-runner.d.ts.map +1 -1
  52. package/dist/components/verbs/r2-sync.d.ts.map +1 -1
  53. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  54. package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
  55. package/dist/components/verbs/sign.d.ts.map +1 -1
  56. package/dist/components/verbs/verify.d.ts.map +1 -1
  57. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  58. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  59. package/dist/components/verbs/wrangler.d.ts.map +1 -1
  60. package/dist/composite.d.ts.map +1 -1
  61. package/dist/config.d.ts +19 -7
  62. package/dist/config.d.ts.map +1 -1
  63. package/dist/declarable.d.ts +16 -0
  64. package/dist/declarable.d.ts.map +1 -1
  65. package/dist/discovery/cache.d.ts.map +1 -1
  66. package/dist/discovery/fold-import.d.ts +15 -5
  67. package/dist/discovery/fold-import.d.ts.map +1 -1
  68. package/dist/discovery/param-deps.d.ts +16 -0
  69. package/dist/discovery/param-deps.d.ts.map +1 -1
  70. package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
  71. package/dist/discovery/specifier-resolve.d.ts +37 -0
  72. package/dist/discovery/specifier-resolve.d.ts.map +1 -0
  73. package/dist/errors.d.ts.map +1 -1
  74. package/dist/fold/fold.d.ts +3 -1
  75. package/dist/fold/fold.d.ts.map +1 -1
  76. package/dist/graph-detail.d.ts.map +1 -1
  77. package/dist/graph-ir.d.ts.map +1 -1
  78. package/dist/graph-layout.d.ts.map +1 -1
  79. package/dist/import/base-parser.d.ts.map +1 -1
  80. package/dist/import/embedded.d.ts +7 -0
  81. package/dist/import/embedded.d.ts.map +1 -1
  82. package/dist/import/parser-options.d.ts +14 -0
  83. package/dist/import/parser-options.d.ts.map +1 -0
  84. package/dist/import/parser.d.ts +17 -0
  85. package/dist/import/parser.d.ts.map +1 -1
  86. package/dist/kubectl-context.d.ts.map +1 -1
  87. package/dist/lexicon-output.d.ts.map +1 -1
  88. package/dist/lexicon-schema.d.ts +3 -3
  89. package/dist/lexicon.d.ts +55 -3
  90. package/dist/lexicon.d.ts.map +1 -1
  91. package/dist/lifecycle/assert-live.d.ts.map +1 -1
  92. package/dist/lifecycle/gate-ledger.d.ts +15 -0
  93. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  94. package/dist/lifecycle/git.d.ts.map +1 -1
  95. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  96. package/dist/lifecycle/observe.d.ts.map +1 -1
  97. package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
  98. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  99. package/dist/lifecycle/scenario.d.ts.map +1 -1
  100. package/dist/lifecycle/status.d.ts.map +1 -1
  101. package/dist/lifecycle/work-lease.d.ts +61 -2
  102. package/dist/lifecycle/work-lease.d.ts.map +1 -1
  103. package/dist/lint/config.d.ts +8 -8
  104. package/dist/lint/engine.d.ts +6 -0
  105. package/dist/lint/engine.d.ts.map +1 -1
  106. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  107. package/dist/op/activities/activity-contracts.d.ts +11 -11
  108. package/dist/op/activities/apply.d.ts +79 -9
  109. package/dist/op/activities/apply.d.ts.map +1 -1
  110. package/dist/op/activities/decide.d.ts.map +1 -1
  111. package/dist/op/activities/index.d.ts +1 -1
  112. package/dist/op/activities/index.d.ts.map +1 -1
  113. package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
  114. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
  115. package/dist/op/activity-contract.d.ts +1 -1
  116. package/dist/op/activity-failure.d.ts.map +1 -1
  117. package/dist/op/activity-profiles.d.ts.map +1 -1
  118. package/dist/op/builders.d.ts.map +1 -1
  119. package/dist/op/composites/apply-op.d.ts +12 -5
  120. package/dist/op/composites/apply-op.d.ts.map +1 -1
  121. package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
  122. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
  123. package/dist/op/decide-backend.d.ts.map +1 -1
  124. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  125. package/dist/op/emulator-lifecycle.d.ts +21 -2
  126. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  127. package/dist/op/gate-approval.d.ts +5 -1
  128. package/dist/op/gate-approval.d.ts.map +1 -1
  129. package/dist/op/gate-wait.d.ts.map +1 -1
  130. package/dist/op/gate.d.ts +19 -3
  131. package/dist/op/gate.d.ts.map +1 -1
  132. package/dist/op/index.d.ts +2 -0
  133. package/dist/op/index.d.ts.map +1 -1
  134. package/dist/op/local-executor.d.ts +6 -0
  135. package/dist/op/local-executor.d.ts.map +1 -1
  136. package/dist/op/operator.d.ts.map +1 -1
  137. package/dist/op/run-context.d.ts +39 -0
  138. package/dist/op/run-context.d.ts.map +1 -0
  139. package/dist/op/run-live.d.ts.map +1 -1
  140. package/dist/op/runtimes/local.d.ts.map +1 -1
  141. package/dist/op/source-archive.d.ts.map +1 -1
  142. package/dist/op/steward-beside.d.ts.map +1 -1
  143. package/dist/op/steward-points.d.ts.map +1 -1
  144. package/dist/op/steward.d.ts.map +1 -1
  145. package/dist/op/types.d.ts +8 -3
  146. package/dist/op/types.d.ts.map +1 -1
  147. package/dist/op/work-lease-run.d.ts +24 -4
  148. package/dist/op/work-lease-run.d.ts.map +1 -1
  149. package/dist/project-root.d.ts.map +1 -1
  150. package/dist/provenance.d.ts +21 -0
  151. package/dist/provenance.d.ts.map +1 -1
  152. package/dist/pseudo-parameter.d.ts.map +1 -1
  153. package/dist/reconcile.d.ts.map +1 -1
  154. package/dist/secret-materialization.d.ts.map +1 -1
  155. package/dist/terraform/emit-source.d.ts.map +1 -1
  156. package/dist/terraform/parse.d.ts.map +1 -1
  157. package/dist/testing.d.ts.map +1 -1
  158. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  159. package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
  160. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
  161. package/dist/workspace/agent-cli.d.ts +5 -0
  162. package/dist/workspace/agent-cli.d.ts.map +1 -1
  163. package/dist/workspace/box-factory.d.ts +73 -0
  164. package/dist/workspace/box-factory.d.ts.map +1 -0
  165. package/dist/workspace/changes.d.ts.map +1 -1
  166. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
  167. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  168. package/dist/workspace/checks.d.ts +5 -0
  169. package/dist/workspace/checks.d.ts.map +1 -1
  170. package/dist/workspace/compose-graph.d.ts +10 -0
  171. package/dist/workspace/compose-graph.d.ts.map +1 -1
  172. package/dist/workspace/conformance/index.d.ts +67 -6
  173. package/dist/workspace/conformance/index.d.ts.map +1 -1
  174. package/dist/workspace/conformance/vitest.d.ts +12 -3
  175. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  176. package/dist/workspace/conformance/writer.d.ts +368 -0
  177. package/dist/workspace/conformance/writer.d.ts.map +1 -0
  178. package/dist/workspace/decide.d.ts +1 -1
  179. package/dist/workspace/decide.d.ts.map +1 -1
  180. package/dist/workspace/declaration.d.ts +91 -4
  181. package/dist/workspace/declaration.d.ts.map +1 -1
  182. package/dist/workspace/declaration.schema.json +234 -5
  183. package/dist/workspace/generated-files.d.ts.map +1 -1
  184. package/dist/workspace/graph-cache.d.ts.map +1 -1
  185. package/dist/workspace/graph-cli.d.ts +2 -0
  186. package/dist/workspace/graph-cli.d.ts.map +1 -1
  187. package/dist/workspace/identity.d.ts +111 -0
  188. package/dist/workspace/identity.d.ts.map +1 -0
  189. package/dist/workspace/intent-cli.d.ts +7 -4
  190. package/dist/workspace/intent-cli.d.ts.map +1 -1
  191. package/dist/workspace/intent-record.d.ts +8 -1
  192. package/dist/workspace/intent-record.d.ts.map +1 -1
  193. package/dist/workspace/intent.d.ts +149 -5
  194. package/dist/workspace/intent.d.ts.map +1 -1
  195. package/dist/workspace/kinds.d.ts +18 -4
  196. package/dist/workspace/kinds.d.ts.map +1 -1
  197. package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
  198. package/dist/workspace/lineage-lock.d.ts +2 -2
  199. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  200. package/dist/workspace/links.d.ts.map +1 -1
  201. package/dist/workspace/points.d.ts.map +1 -1
  202. package/dist/workspace/principal-classes.d.ts +114 -0
  203. package/dist/workspace/principal-classes.d.ts.map +1 -0
  204. package/dist/workspace/reason-codes.d.ts +21 -1
  205. package/dist/workspace/reason-codes.d.ts.map +1 -1
  206. package/dist/workspace/record-assets.d.ts.map +1 -1
  207. package/dist/workspace/records-checkout.d.ts +54 -0
  208. package/dist/workspace/records-checkout.d.ts.map +1 -0
  209. package/dist/workspace/records-cli.d.ts +27 -1
  210. package/dist/workspace/records-cli.d.ts.map +1 -1
  211. package/dist/workspace/records-close.d.ts +1 -1
  212. package/dist/workspace/records-close.d.ts.map +1 -1
  213. package/dist/workspace/records-since.d.ts.map +1 -1
  214. package/dist/workspace/records-write.d.ts +5 -4
  215. package/dist/workspace/records-write.d.ts.map +1 -1
  216. package/dist/workspace/records.d.ts +15 -0
  217. package/dist/workspace/records.d.ts.map +1 -1
  218. package/dist/workspace/runs-cli.d.ts +164 -0
  219. package/dist/workspace/runs-cli.d.ts.map +1 -0
  220. package/dist/workspace/runs.d.ts +423 -0
  221. package/dist/workspace/runs.d.ts.map +1 -0
  222. package/dist/workspace/source-block.d.ts +1 -1
  223. package/dist/workspace/source-block.d.ts.map +1 -1
  224. package/dist/workspace/status-gates.d.ts +15 -3
  225. package/dist/workspace/status-gates.d.ts.map +1 -1
  226. package/dist/workspace/status.d.ts +10 -0
  227. package/dist/workspace/status.d.ts.map +1 -1
  228. package/dist/workspace/symbols.d.ts +52 -0
  229. package/dist/workspace/symbols.d.ts.map +1 -0
  230. package/dist/workspace/trailer-joins.d.ts +46 -0
  231. package/dist/workspace/trailer-joins.d.ts.map +1 -0
  232. package/dist/workspace/trailers.d.ts +89 -0
  233. package/dist/workspace/trailers.d.ts.map +1 -0
  234. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  235. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  236. package/dist/workspace/trust/seal.d.ts +33 -0
  237. package/dist/workspace/trust/seal.d.ts.map +1 -1
  238. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  239. package/dist/workspace/work-cli.d.ts +39 -3
  240. package/dist/workspace/work-cli.d.ts.map +1 -1
  241. package/dist/workspace/work-evidence.d.ts +1 -1
  242. package/dist/workspace/work-evidence.d.ts.map +1 -1
  243. package/dist/workspace/work.d.ts +26 -1
  244. package/dist/workspace/work.d.ts.map +1 -1
  245. package/dist/workspace/workspace-principals.schema.json +60 -0
  246. package/dist/workspace/write-scope.d.ts +79 -19
  247. package/dist/workspace/write-scope.d.ts.map +1 -1
  248. package/dist/yaml.d.ts +21 -39
  249. package/dist/yaml.d.ts.map +1 -1
  250. package/package.json +2 -2
  251. package/src/audit/prior-art.ts +15 -0
  252. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
  253. package/src/cli/commands/carve-emit-state.test.ts +4 -0
  254. package/src/cli/commands/carve-emit.ts +5 -19
  255. package/src/cli/commands/check-lexicon.test.ts +62 -1
  256. package/src/cli/commands/check-lexicon.ts +36 -6
  257. package/src/cli/commands/import-live.test.ts +18 -0
  258. package/src/cli/commands/import-parser-options.test.ts +133 -0
  259. package/src/cli/commands/import.ts +21 -3
  260. package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
  261. package/src/cli/commands/init.test.ts +8 -1
  262. package/src/cli/commands/init.ts +23 -8
  263. package/src/cli/commands/lint.test.ts +37 -0
  264. package/src/cli/commands/onboard.test.ts +53 -3
  265. package/src/cli/commands/onboard.ts +52 -4
  266. package/src/cli/commands/pinned-upgrade.ts +73 -33
  267. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
  268. package/src/cli/handlers/components.ts +2 -2
  269. package/src/cli/handlers/dev.ts +8 -6
  270. package/src/cli/handlers/lifecycle.test.ts +67 -0
  271. package/src/cli/handlers/lifecycle.ts +6 -5
  272. package/src/cli/handlers/misc.ts +2 -0
  273. package/src/cli/handlers/operator.ts +87 -3
  274. package/src/cli/handlers/run.test.ts +111 -0
  275. package/src/cli/handlers/run.ts +85 -24
  276. package/src/cli/handlers/scenario.ts +2 -2
  277. package/src/cli/main.test.ts +1 -0
  278. package/src/cli/main.ts +65 -11
  279. package/src/cli/mcp/workspace-tools.test.ts +5 -2
  280. package/src/cli/mcp/workspace-tools.ts +29 -2
  281. package/src/cli/registry.ts +11 -2
  282. package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
  283. package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
  284. package/src/codegen/pinned-upgrade.test.ts +104 -0
  285. package/src/codegen/pinned-upgrade.ts +250 -56
  286. package/src/codegen/testdata/postgres-tags.json +242 -0
  287. package/src/config.test.ts +21 -0
  288. package/src/config.ts +15 -0
  289. package/src/declarable.test.ts +21 -0
  290. package/src/declarable.ts +19 -0
  291. package/src/discovery/fold-composite.test.ts +41 -5
  292. package/src/discovery/fold-import.test.ts +129 -0
  293. package/src/discovery/fold-import.ts +643 -131
  294. package/src/discovery/fold-package-composite.test.ts +264 -0
  295. package/src/discovery/fold-specifiers.test.ts +191 -0
  296. package/src/discovery/param-deps.test.ts +66 -1
  297. package/src/discovery/param-deps.ts +45 -0
  298. package/src/discovery/specifier-resolve.ts +344 -0
  299. package/src/fold/fold.test.ts +39 -1
  300. package/src/fold/fold.ts +83 -9
  301. package/src/fold-provenance.test.ts +140 -0
  302. package/src/graph-ir.test.ts +83 -0
  303. package/src/graph-ir.ts +29 -0
  304. package/src/import/embedded.test.ts +20 -0
  305. package/src/import/embedded.ts +13 -0
  306. package/src/import/parser-options.ts +46 -0
  307. package/src/import/parser.ts +20 -0
  308. package/src/lexicon.ts +55 -3
  309. package/src/lifecycle/gate-ledger.ts +16 -0
  310. package/src/lifecycle/observe.test.ts +18 -0
  311. package/src/lifecycle/observe.ts +6 -4
  312. package/src/lifecycle/snapshot.ts +2 -2
  313. package/src/lifecycle/work-lease.test.ts +1 -1
  314. package/src/lifecycle/work-lease.ts +93 -2
  315. package/src/lint/engine.test.ts +55 -0
  316. package/src/lint/engine.ts +30 -4
  317. package/src/lint/output-docs.test.ts +59 -56
  318. package/src/op/activities/apply.test.ts +189 -5
  319. package/src/op/activities/apply.ts +230 -8
  320. package/src/op/activities/index.ts +3 -0
  321. package/src/op/activities/lexicon-upgrade.test.ts +160 -6
  322. package/src/op/activities/lexicon-upgrade.ts +154 -51
  323. package/src/op/composites/apply-op.ts +12 -5
  324. package/src/op/composites/composites.test.ts +30 -3
  325. package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
  326. package/src/op/composites/lexicon-upgrade-op.ts +29 -8
  327. package/src/op/emulator-lifecycle.test.ts +23 -0
  328. package/src/op/emulator-lifecycle.ts +52 -14
  329. package/src/op/gate-approval.test.ts +74 -0
  330. package/src/op/gate-approval.ts +5 -1
  331. package/src/op/gate.ts +31 -5
  332. package/src/op/index.ts +2 -0
  333. package/src/op/local-executor.test.ts +15 -0
  334. package/src/op/local-executor.ts +40 -12
  335. package/src/op/local-output.ts +1 -1
  336. package/src/op/operator.ts +4 -0
  337. package/src/op/run-context.test.ts +141 -0
  338. package/src/op/run-context.ts +51 -0
  339. package/src/op/runtimes/local.test.ts +14 -0
  340. package/src/op/runtimes/local.ts +2 -0
  341. package/src/op/steward-beside.ts +2 -0
  342. package/src/op/types.ts +8 -3
  343. package/src/op/work-lease-run.test.ts +47 -0
  344. package/src/op/work-lease-run.ts +73 -11
  345. package/src/provenance.ts +35 -0
  346. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  347. package/src/workspace/agent-cli.ts +7 -3
  348. package/src/workspace/agent.schema.json +22 -0
  349. package/src/workspace/box-factory.test.ts +320 -0
  350. package/src/workspace/box-factory.ts +103 -0
  351. package/src/workspace/changes.schema.json +12 -20
  352. package/src/workspace/changes.ts +4 -1
  353. package/src/workspace/checks/boxes.test.ts +2 -0
  354. package/src/workspace/checks.ts +29 -5
  355. package/src/workspace/compose-graph.ts +7 -0
  356. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
  357. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
  358. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
  359. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
  360. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
  361. package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  362. package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  363. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
  364. package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
  365. package/src/workspace/conformance/index.mjs +30 -3
  366. package/src/workspace/conformance/index.ts +103 -15
  367. package/src/workspace/conformance/vitest.ts +44 -2
  368. package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
  369. package/src/workspace/conformance/writer.test.ts +156 -0
  370. package/src/workspace/conformance/writer.ts +880 -0
  371. package/src/workspace/decide.ts +7 -1
  372. package/src/workspace/declaration.schema.json +234 -5
  373. package/src/workspace/declaration.ts +174 -10
  374. package/src/workspace/graph-cli.ts +5 -2
  375. package/src/workspace/graph.schema.json +247 -0
  376. package/src/workspace/identity.test.ts +196 -0
  377. package/src/workspace/identity.ts +186 -0
  378. package/src/workspace/intent-cli.ts +64 -10
  379. package/src/workspace/intent-record.schema.json +208 -0
  380. package/src/workspace/intent-record.ts +35 -1
  381. package/src/workspace/intent-why.test.ts +274 -0
  382. package/src/workspace/intent.schema.json +718 -7
  383. package/src/workspace/intent.test.ts +11 -2
  384. package/src/workspace/intent.ts +495 -32
  385. package/src/workspace/kinds.ts +44 -37
  386. package/src/workspace/points-write.schema.json +1 -0
  387. package/src/workspace/principal-classes.test.ts +175 -0
  388. package/src/workspace/principal-classes.ts +307 -0
  389. package/src/workspace/reason-codes.test.ts +11 -3
  390. package/src/workspace/reason-codes.ts +24 -1
  391. package/src/workspace/records-amend.schema.json +2 -0
  392. package/src/workspace/records-checkout.ts +106 -0
  393. package/src/workspace/records-cli.ts +103 -11
  394. package/src/workspace/records-close.schema.json +1 -0
  395. package/src/workspace/records-new.schema.json +2 -0
  396. package/src/workspace/records-review.schema.json +2 -0
  397. package/src/workspace/records-uncommitted.test.ts +171 -0
  398. package/src/workspace/records-write.ts +28 -6
  399. package/src/workspace/records.schema.json +72 -1
  400. package/src/workspace/records.ts +27 -0
  401. package/src/workspace/runs-cli.ts +381 -0
  402. package/src/workspace/runs-write.schema.json +583 -0
  403. package/src/workspace/runs.schema.json +810 -0
  404. package/src/workspace/runs.test.ts +242 -0
  405. package/src/workspace/runs.ts +709 -0
  406. package/src/workspace/status-contract.test.ts +5 -0
  407. package/src/workspace/status-gates.ts +22 -8
  408. package/src/workspace/status.schema.json +127 -2
  409. package/src/workspace/status.ts +21 -1
  410. package/src/workspace/symbols.ts +180 -0
  411. package/src/workspace/trailer-joins.test.ts +171 -0
  412. package/src/workspace/trailer-joins.ts +82 -0
  413. package/src/workspace/trailers.ts +148 -0
  414. package/src/workspace/trust/seal.ts +52 -1
  415. package/src/workspace/work-cli.e2e.test.ts +28 -1
  416. package/src/workspace/work-cli.ts +133 -8
  417. package/src/workspace/work-evidence.schema.json +2 -0
  418. package/src/workspace/work-history.schema.json +73 -1
  419. package/src/workspace/work-history.test.ts +48 -5
  420. package/src/workspace/work-kind-fields.test.ts +154 -0
  421. package/src/workspace/work-lease.schema.json +14 -1
  422. package/src/workspace/work.ts +82 -0
  423. package/src/workspace/workspace-principals.schema.json +60 -0
  424. package/src/workspace/write-scope.test.ts +128 -2
  425. package/src/workspace/write-scope.ts +170 -45
  426. package/src/yaml.test.ts +111 -1
  427. package/src/yaml.ts +141 -549
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * The intent graph over one region: `chant workspace graph --intent
3
- * <path[:start-end]>` (#2651; #2650 sections B and C1; #2524 D8, D15).
3
+ * <path[:start-end]|path#symbol>` (#2651; #2650 sections B and C1; #2524 D8,
4
+ * D15; #3034).
4
5
  *
5
6
  * A person points at some code and asks how it got this way and who decided
6
7
  * it should be this way. This module gathers what the workspace records about
@@ -31,6 +32,13 @@
31
32
  * runs for every commit in that order. chant emits the
32
33
  * graph and hud renders it (#2524 D8, D15). Git is read through a local
33
34
  * `git` subprocess only: no fetch, no network.
35
+ *
36
+ * A region can name a symbol, `path#symbol`, which `symbols.ts` resolves to
37
+ * its current lines (#3034). For a file region the document also carries
38
+ * `why`: who made the region's current lines, from `git blame`, the agent run
39
+ * behind each (narrowed by the hunks a run recorded when several share a
40
+ * commit), and the decisions governing the region, most relevant first, with
41
+ * `explained: false` and closed gap codes when nothing accounts for it.
34
42
  */
35
43
 
36
44
  import { execFileSync } from "node:child_process";
@@ -48,6 +56,9 @@ import type { DecidedIn } from "./record-decided";
48
56
  import { importKindModule, loadRecordKind, parseFrontMatter, RecordReadError, supersedesTargets, type LoadedRecordKind } from "./records";
49
57
  import { queryRecords, type RecordView } from "./records-cli";
50
58
  import { isPluginCode, type PluginCode, type ReasonCode } from "./reason-codes";
59
+ import { runsForCommits, type RunCost, type RunPin, type RunUsage, type RunView } from "./runs";
60
+ import { resolveSymbol } from "./symbols";
61
+ import { readTrailerJoins, type CommitTrailerJoins } from "./trailer-joins";
51
62
  import { joinPath, skippedDir, type WorkspaceTree } from "./tree";
52
63
  import { activeAttestors, type ProvenanceLevel } from "./trust/attestor";
53
64
  import { commitProvenance, policyAtBase, resolveBase } from "./trust/provenance";
@@ -66,7 +77,7 @@ export const INTENT_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/works
66
77
  export const INTENT_FINDING_CODES = [
67
78
  /** A commit touched the region when no decision constrained it at path granularity. */
68
79
  "intent-commit-undecided",
69
- /** A commit has no unit, no pull request reference and no decision covering the region at its time. */
80
+ /** A commit has no unit, carries no record through chant's trailers, has no pull request reference and no decision covering the region at its time. */
70
81
  "intent-commit-bare",
71
82
  /** A pinned artifact's bytes no longer hash to the pin. */
72
83
  "intent-pin-drifted",
@@ -123,9 +134,28 @@ export const INTENT_ERROR_CODES = [
123
134
  "intent-region-invalid",
124
135
  /** --record names an id no record of a decision kind read has. */
125
136
  "intent-record-unknown",
137
+ /** The region names a symbol in a file no resolver reads (#3034). */
138
+ "intent-symbol-unsupported",
139
+ /** The region names a symbol the file does not declare (#3034). */
140
+ "intent-symbol-unknown",
141
+ /** The region names a symbol that matches more than one declaration (#3034). */
142
+ "intent-symbol-ambiguous",
126
143
  ] as const satisfies readonly ReasonCode[];
127
144
  export type IntentErrorCode = (typeof INTENT_ERROR_CODES)[number];
128
145
 
146
+ /** Why `why` can't account for some or all of a region (#3034). Closed. */
147
+ export const INTENT_WHY_CODES = [
148
+ /** No current decision governs the region, at any granularity, and none is carried out by the commits or runs that made its lines. */
149
+ "intent-why-no-decision",
150
+ /** None of the region's current lines was made by a commit an agent run is joined to. */
151
+ "intent-why-no-run",
152
+ /** Some of the region's lines are not committed, so nothing made them yet. */
153
+ "intent-why-uncommitted",
154
+ /** Some lines come from a commit that several runs made, and no run's recorded hunks say which wrote them. */
155
+ "intent-why-run-ambiguous",
156
+ ] as const satisfies readonly ReasonCode[];
157
+ export type IntentWhyCode = (typeof INTENT_WHY_CODES)[number];
158
+
129
159
  export class IntentError extends Error {
130
160
  constructor(
131
161
  readonly code: IntentErrorCode,
@@ -157,6 +187,8 @@ export interface RegionNode {
157
187
  generated: boolean | null;
158
188
  /** Set when the region was given as a graph node id. */
159
189
  node: string | null;
190
+ /** Set when the region was given as `path#symbol` (#3034): the symbol as given, the declaration it resolved to, and its kind. `lines` holds its current lines. */
191
+ symbol?: { name: string; qualified: string; kind: string } | null;
160
192
  }
161
193
 
162
194
  export interface FileNode {
@@ -196,6 +228,13 @@ export interface CommitNode {
196
228
  * null when no record kind was read.
197
229
  */
198
230
  state: CommitState | null;
231
+ /**
232
+ * What chant's own trailers join the commit to (#3149, ws-075): the agent
233
+ * session, the work lease and the item it was taken on, the agent run, the
234
+ * records it names and the apply it records. A record or a lease item that
235
+ * a kind read has is linked by a `carries` edge.
236
+ */
237
+ joins: CommitTrailerJoins;
199
238
  }
200
239
 
201
240
  export type CommitState = "decided" | "decided-by-window" | "undecided";
@@ -331,7 +370,36 @@ export interface WorkNode {
331
370
  warnings: { code: WorkWarningCode; message: string }[];
332
371
  }
333
372
 
334
- export type IntentNode = RegionNode | FileNode | MemberNode | CommitNode | JoinedNode | EvidenceEntryNode | DecisionNode | WorkNode | ArtifactNode | LinkNode | FindingNode;
373
+ /**
374
+ * An agent run that made a commit in the walk (#3033): from the run ledger on
375
+ * the local `chant/lifecycle` branch. A run a `Chant-Run` trailer names and
376
+ * the ledger lacks is listed with `recorded: false` and nothing else known.
377
+ */
378
+ export interface RunNode {
379
+ id: string;
380
+ kind: "run";
381
+ run: string;
382
+ recorded: boolean;
383
+ state: "running" | "ended" | null;
384
+ harness: { name: string; version: string | null } | null;
385
+ model: string | null;
386
+ provider: string | null;
387
+ by: string | null;
388
+ agent: string | null;
389
+ unit: string | null;
390
+ startedAt: string | null;
391
+ endedAt: string | null;
392
+ outcome: string | null;
393
+ usage: RunUsage | null;
394
+ cost: RunCost | null;
395
+ transcript: RunPin | null;
396
+ /** The instruction the run was given, pinned by hash, with the writer's excerpt when it gave one (#3034). */
397
+ instruction?: RunPin | null;
398
+ /** The fencing token of the work lease it worked under (#3034). */
399
+ lease?: string | null;
400
+ }
401
+
402
+ export type IntentNode = RegionNode | FileNode | MemberNode | CommitNode | JoinedNode | EvidenceEntryNode | DecisionNode | WorkNode | ArtifactNode | LinkNode | FindingNode | RunNode;
335
403
 
336
404
  export type Granularity = "path" | "member" | "contract" | "issue";
337
405
 
@@ -340,7 +408,75 @@ export type IntentEdge =
340
408
  | { kind: "pins"; from: string; to: string; pinnedSha256: string | null; pinState: PinState }
341
409
  | { kind: "touched-by"; from: string; to: string; lines: LineRange[] | null }
342
410
  | { kind: "within"; from: string; to: string; state: "decided" | "decided-by-window" | "worked" }
343
- | { kind: "produced-by" | "serves" | "cites-evidence" | "supersedes" | "links" | "implements" | "needs" | "addressed-by"; from: string; to: string };
411
+ | { kind: "made-by"; from: string; to: string; joinedBy: ("trailer" | "record")[] }
412
+ | { kind: "produced-by" | "serves" | "cites-evidence" | "supersedes" | "links" | "implements" | "needs" | "addressed-by" | "carries" | "worked-on"; from: string; to: string };
413
+
414
+ /** Lines of the region that one commit, and the same runs, last wrote (#3034). */
415
+ export interface WhySpan {
416
+ start: number;
417
+ end: number;
418
+ /** The commit node, or null for lines not committed yet. */
419
+ commit: string | null;
420
+ sha: string | null;
421
+ /** The run nodes behind these lines: the run the commit's Chant-Run names first, then those whose record lists it. */
422
+ runs: string[];
423
+ /** `hunks` when a run's recorded hunks chose among the commit's runs; null when every run that made the commit is listed. */
424
+ narrowedBy: "hunks" | null;
425
+ }
426
+
427
+ /** Why a decision is in `why`, most relevant first (#3034). */
428
+ export type WhyRelevance = "carried" | "path" | "contract" | "issue" | "member" | "related";
429
+
430
+ export interface WhyDecision {
431
+ decision: string;
432
+ /**
433
+ * carried: a commit or run that made the region's current lines carries it
434
+ * out. path, contract, issue, member: it constrains the region that way.
435
+ * related: it is in the graph only through supersession, or because a
436
+ * commit that no longer made any current line carried it out.
437
+ */
438
+ relevance: WhyRelevance;
439
+ /** Not superseded. */
440
+ current: boolean;
441
+ /** Its state is one its kind closes, such as ratified. */
442
+ closed: boolean;
443
+ /** How many of the region's current lines were made as its own work. */
444
+ lines: number;
445
+ }
446
+
447
+ export interface WhyRun {
448
+ run: string;
449
+ /** How many of the region's current lines it wrote. */
450
+ lines: number;
451
+ /** The commit nodes through which it made them. */
452
+ commits: string[];
453
+ /** The work item it worked on, as the run record names it, and its node when a work kind read has it. */
454
+ unit: { id: string; kind: string | null; node: string | null } | null;
455
+ /** The decision nodes it carried out: through its work item's implements, and the decision records it names. */
456
+ decisions: string[];
457
+ }
458
+
459
+ export interface WhyGap {
460
+ code: IntentWhyCode;
461
+ message: string;
462
+ /** The lines the gap is about, when it is about some of them. */
463
+ lines?: LineRange[];
464
+ }
465
+
466
+ /** The answer to "why is it like this" over the region (#3034, hud#700). */
467
+ export interface WhyAnswer {
468
+ /** The lines accounted for: the region's range, the whole file, or null for a directory, which is not blamed. */
469
+ lines: LineRange | null;
470
+ /** The current lines, top to bottom, each span with the commit and runs that last wrote it. Empty for a directory. */
471
+ blame: WhySpan[];
472
+ /** The decisions governing the region, most relevant first. */
473
+ decisions: WhyDecision[];
474
+ /** The runs that wrote the current lines, most lines first; for a directory, every run in the walk, newest first. */
475
+ runs: WhyRun[];
476
+ /** Whether a current decision governs the region or is carried out by what made its lines. */
477
+ explained: boolean;
478
+ gaps: WhyGap[];
479
+ }
344
480
 
345
481
  export interface IntentReason {
346
482
  code: IntentReasonCode;
@@ -364,13 +500,14 @@ export type IntentDocument =
364
500
  edges: IntentEdge[];
365
501
  reasons: IntentReason[];
366
502
  summary: { commits: number; decisions: number; artifacts: number; findings: number };
503
+ why: WhyAnswer;
367
504
  })
368
505
  | (Head & { error: { code: IntentErrorCode; message: string } });
369
506
 
370
507
  export interface IntentQuery {
371
508
  /** Where the walk up to the declaration starts, and what the region path is relative to. */
372
509
  cwd: string;
373
- /** `path`, `path:line` or `path:start-end`, or a graph node id `<member>/<id>`. */
510
+ /** `path`, `path:line`, `path:start-end` or `path#symbol`, or a graph node id `<member>/<id>`. */
374
511
  region: string;
375
512
  at?: string;
376
513
  /**
@@ -442,6 +579,45 @@ export function regionHistory(top: string, rev: string, gitPath: string, type: "
442
579
  .map((sha) => ({ sha, lines: null }));
443
580
  }
444
581
 
582
+ /** One current line of a file, and the commit that last wrote it. */
583
+ export interface BlamedLine {
584
+ line: number;
585
+ /** Null for a line not committed yet. */
586
+ sha: string | null;
587
+ /** The file's path in that commit, from the repository root. */
588
+ origPath: string;
589
+ /** The line's number in that commit's version of the file. */
590
+ origLine: number;
591
+ }
592
+
593
+ const ZERO_SHA = /^0+$/;
594
+
595
+ /**
596
+ * `git blame` over a file's lines (#3034): at `rev`, or in the working tree
597
+ * when `rev` is null, where a line not committed yet has no commit. Undefined
598
+ * when git can't blame the file, such as one never committed.
599
+ */
600
+ export function blameLines(top: string, rev: string | null, gitPath: string, lines: LineRange | null): BlamedLine[] | undefined {
601
+ const out = tryGit(top, ["blame", "--line-porcelain", ...(lines ? [`-L${lines.start},${lines.end}`] : []), ...(rev ? [rev] : []), "--", gitPath]);
602
+ if (out === undefined) return undefined;
603
+ const result: BlamedLine[] = [];
604
+ let current: { sha: string; origLine: number; line: number; origPath: string } | undefined;
605
+ for (const l of out.split("\n")) {
606
+ if (l.startsWith("\t")) {
607
+ if (current) result.push({ line: current.line, sha: ZERO_SHA.test(current.sha) ? null : current.sha, origPath: current.origPath, origLine: current.origLine });
608
+ current = undefined;
609
+ continue;
610
+ }
611
+ const head = l.match(/^([0-9a-f]{40,64}) (\d+) (\d+)(?: \d+)?$/);
612
+ if (head) {
613
+ current = { sha: head[1], origLine: Number(head[2]), line: Number(head[3]), origPath: gitPath };
614
+ continue;
615
+ }
616
+ if (current && l.startsWith("filename ")) current.origPath = l.slice("filename ".length);
617
+ }
618
+ return result;
619
+ }
620
+
445
621
  /** Subject, body, author, date and trailers of each commit. */
446
622
  export function commitDetails(top: string, shas: string[]): Map<string, IntentCommit> {
447
623
  const out = new Map<string, IntentCommit>();
@@ -538,7 +714,8 @@ export function parseRegion(arg: string): { path: string; lines: LineRange | nul
538
714
  return { path: m[1], lines: { start, end } };
539
715
  }
540
716
 
541
- function regionId(path: string, lines: LineRange | null): string {
717
+ function regionId(path: string, lines: LineRange | null, symbol?: string): string {
718
+ if (symbol !== undefined) return `region:${path}#${symbol}`;
542
719
  return `region:${path}${lines ? `:${lines.start}${lines.end === lines.start ? "" : `-${lines.end}`}` : ""}`;
543
720
  }
544
721
 
@@ -649,13 +826,34 @@ export async function loadKinds(query: IntentQuery, top: string): Promise<Loaded
649
826
  return out;
650
827
  }
651
828
 
652
- async function resolveRegion(query: IntentQuery, located: LocatedWorkspace, declaration: Declaration): Promise<{ path: string; lines: LineRange | null; node: string | null }> {
829
+ interface ResolvedRegion {
830
+ path: string;
831
+ lines: LineRange | null;
832
+ node: string | null;
833
+ symbol: { name: string; qualified: string; kind: string } | null;
834
+ }
835
+
836
+ async function resolveRegion(query: IntentQuery, located: LocatedWorkspace, declaration: Declaration): Promise<ResolvedRegion> {
653
837
  const parsed = parseRegion(query.region);
654
838
  if ("error" in parsed) throw new IntentError("intent-region-invalid", parsed.error);
655
- const rel = toPosix(relative(realpathOr(located.rootOnDisk), resolve(realpathOr(query.cwd), parsed.path)));
656
- const path = rel === "" ? "." : rel;
657
- if (path === "." || (isWorkspacePath(path) && !path.startsWith("../") && located.tree.stat(path) !== undefined)) {
658
- return { path, lines: parsed.lines, node: null };
839
+ const inWorkspace = (p: string): string | undefined => {
840
+ const rel = toPosix(relative(realpathOr(located.rootOnDisk), resolve(realpathOr(query.cwd), p)));
841
+ const path = rel === "" ? "." : rel;
842
+ return path === "." || (isWorkspacePath(path) && !path.startsWith("../") && located.tree.stat(path) !== undefined) ? path : undefined;
843
+ };
844
+ const path = inWorkspace(parsed.path);
845
+ if (path !== undefined) return { path, lines: parsed.lines, node: null, symbol: null };
846
+ // path#symbol (#3034): the symbol's current lines in the tree read.
847
+ const hash = parsed.lines ? -1 : query.region.lastIndexOf("#");
848
+ if (hash > 0) {
849
+ const name = query.region.slice(hash + 1);
850
+ const file = inWorkspace(query.region.slice(0, hash));
851
+ if (file !== undefined && name !== "") {
852
+ if (file === "." || located.tree.stat(file) !== "file") throw new IntentError("intent-region-invalid", `${file} is a directory, and a symbol needs a file`);
853
+ const r = resolveSymbol(file, located.tree.read(file), name);
854
+ if (!r.ok) throw new IntentError(r.reason === "unsupported" ? "intent-symbol-unsupported" : r.reason === "ambiguous" ? "intent-symbol-ambiguous" : "intent-symbol-unknown", `${r.message}${located.tree.label}`);
855
+ return { path: file, lines: r.declaration.lines, node: null, symbol: { name, qualified: r.declaration.qualified, kind: r.declaration.kind } };
856
+ }
659
857
  }
660
858
  // A graph node id, <member>/<id>, when no such path exists (#2650 B1).
661
859
  const slash = query.region.indexOf("/");
@@ -664,10 +862,10 @@ async function resolveRegion(query: IntentQuery, located: LocatedWorkspace, decl
664
862
  const loc = await (query.resolveNode ?? resolveNodeFromGraph)(query.cwd, query.at, member.name, query.region);
665
863
  if (loc) {
666
864
  const file = joinPath(member.dir, loc.file);
667
- return { path: file === "" ? "." : file, lines: loc.line ? { start: loc.line, end: loc.line } : null, node: query.region };
865
+ return { path: file === "" ? "." : file, lines: loc.line ? { start: loc.line, end: loc.line } : null, node: query.region, symbol: null };
668
866
  }
669
867
  }
670
- throw new IntentError("intent-region-invalid", `${query.region} is not a path in the workspace${located.tree.label}, or a graph node id`);
868
+ throw new IntentError("intent-region-invalid", `${query.region} is not a path in the workspace${located.tree.label}, a path#symbol, or a graph node id`);
671
869
  }
672
870
 
673
871
  /** Build the intent document. Never throws an {@link IntentError}, {@link WorkspaceReadError} or {@link RecordReadError}. */
@@ -714,8 +912,19 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
714
912
 
715
913
  // The region, its files and its member.
716
914
  const member = region.path === "." ? (declaration.members.find((m) => m.dir === ".")?.name ?? null) : memberHolding(region.path, declaration.members);
717
- const rid = regionId(region.path, region.lines);
718
- add<RegionNode>({ id: rid, kind: "region", path: region.path, lines: region.lines, member, at: located.at, type, generated: type === "file" ? isGeneratedPath(declaration, region.path) : null, node: region.node });
915
+ const rid = regionId(region.path, region.lines, region.symbol?.name);
916
+ add<RegionNode>({
917
+ id: rid,
918
+ kind: "region",
919
+ path: region.path,
920
+ lines: region.lines,
921
+ member,
922
+ at: located.at,
923
+ type,
924
+ generated: type === "file" ? isGeneratedPath(declaration, region.path) : null,
925
+ node: region.node,
926
+ symbol: region.symbol,
927
+ });
719
928
  const files = type === "dir" ? filesUnder(located.tree, region.path) : [];
720
929
  for (const f of files) add<FileNode>({ id: `file:${f}`, kind: "file", path: f, member: memberHolding(f, declaration.members), generated: isGeneratedPath(declaration, f) });
721
930
  const memberNode = (name: string) => {
@@ -731,6 +940,19 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
731
940
  const workspacePrefix = located.root === "." ? "" : located.root;
732
941
  const gitPath = joinPath(workspacePrefix, region.path);
733
942
  const touched = rev ? regionHistory(top, rev, gitPath, type, region.lines) : [];
943
+ // Who last wrote each current line of a file region (#3034). A commit blame
944
+ // names and the history walk missed, as when a move confuses git log -L,
945
+ // joins the commits that touched the region.
946
+ const fileLines = type === "file" ? (region.lines ?? { start: 1, end: Math.max(1, located.tree.read(region.path).replace(/\n$/, "").split("\n").length) }) : null;
947
+ const blamed: BlamedLine[] =
948
+ fileLines === null
949
+ ? []
950
+ : ((rev ? blameLines(top, located.at, gitPath, region.lines) : undefined) ??
951
+ // A file git can't blame in the working tree has never been committed: every line is new.
952
+ (located.at === null ? Array.from({ length: fileLines.end - fileLines.start + 1 }, (_, i) => ({ line: fileLines.start + i, sha: null, origPath: gitPath, origLine: fileLines.start + i })) : []));
953
+ for (const sha of new Set(blamed.map((b) => b.sha).filter((x): x is string => x !== null))) {
954
+ if (!touched.some((t) => t.sha === sha)) touched.push({ sha, lines: null });
955
+ }
734
956
  const details = commitDetails(top, touched.map((t) => t.sha));
735
957
 
736
958
  // Each commit's provenance, judged by the policy at base as records judges a record's (#2547).
@@ -752,10 +974,14 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
752
974
  unit?: string;
753
975
  contracts: string[];
754
976
  authorship: string[];
755
- /** The record ids the commit's units and contracts say they carry out. */
977
+ /** The record ids the commit's units and contracts say they carry out, and the ones its Chant-Record and Chant-Lease trailers name or implement. */
756
978
  decisions: string[];
979
+ /** The nodes of the records the commit's own trailers name (#3149). */
980
+ carries: string[];
757
981
  }
758
982
  const joined = new Map<string, Joined>();
983
+ // chant's own trailers on each commit (#3149), with the items their lease tokens name.
984
+ const trailerJoins = readTrailerJoins(top, [...details.values()]);
759
985
  const pluginFindings: { commit: string; plugin: string; finding: PluginFinding }[] = [];
760
986
  const failedPlugins = new Set<string>();
761
987
  for (const t of touched) {
@@ -769,11 +995,11 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
769
995
  : { level: "unattested" as const, reason: policy.problems.length ? policy.problems.join("; ") : `no signers file (${policy.signersPath}) at base; attestation is off` };
770
996
  const pr = c.subject.match(/\(#([0-9]+)\)\s*$/);
771
997
  const cid = `commit:${c.sha}`;
772
- add<CommitNode>({ id: cid, kind: "commit", sha: c.sha, subject: c.subject, author: c.author, date: c.date, trailers: c.trailers, pullRequest: pr ? Number(pr[1]) : null, signature, lines: t.lines, state: null });
998
+ add<CommitNode>({ id: cid, kind: "commit", sha: c.sha, subject: c.subject, author: c.author, date: c.date, trailers: c.trailers, pullRequest: pr ? Number(pr[1]) : null, signature, lines: t.lines, state: null, joins: trailerJoins.get(c.sha)!.joins });
773
999
  edges.push({ kind: "touched-by", from: rid, to: cid, lines: t.lines });
774
1000
 
775
1001
  // 2. Each commit's origin, from the plugins.
776
- const entry: Joined = { contracts: [], authorship: [], decisions: [] };
1002
+ const entry: Joined = { contracts: [], authorship: [], decisions: [], carries: [] };
777
1003
  joined.set(c.sha, entry);
778
1004
  for (const k of kinds) {
779
1005
  if (!k.joins) continue;
@@ -806,6 +1032,41 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
806
1032
  }
807
1033
  }
808
1034
 
1035
+ // The agent runs that made each commit (#3033): the one its Chant-Run names, and any whose record lists it.
1036
+ const runJoins = await runsForCommits(
1037
+ top,
1038
+ located.rootOnDisk,
1039
+ touched.map((t) => ({ sha: t.sha, run: trailerJoins.get(t.sha)?.joins.run ?? null })),
1040
+ );
1041
+ for (const t of touched) {
1042
+ if (!nodes.has(`commit:${t.sha}`)) continue;
1043
+ for (const ref of runJoins.refs.get(t.sha) ?? []) {
1044
+ const r = runJoins.runs.get(ref.id);
1045
+ const node = add<RunNode>({
1046
+ id: `run:${ref.id}`,
1047
+ kind: "run",
1048
+ run: ref.id,
1049
+ recorded: r !== undefined,
1050
+ state: r?.state ?? null,
1051
+ harness: r?.harness ?? null,
1052
+ model: r?.model ?? null,
1053
+ provider: r?.provider ?? null,
1054
+ by: r?.by ?? null,
1055
+ agent: r?.agent ?? null,
1056
+ unit: r?.unit?.id ?? null,
1057
+ startedAt: r?.startedAt ?? null,
1058
+ endedAt: r?.endedAt ?? null,
1059
+ outcome: r?.outcome ?? null,
1060
+ usage: r?.usage ?? null,
1061
+ cost: r?.cost ?? null,
1062
+ transcript: r?.transcript ?? null,
1063
+ instruction: r?.instruction ?? null,
1064
+ lease: r?.lease ?? null,
1065
+ });
1066
+ edges.push({ kind: "made-by", from: `commit:${t.sha}`, to: node.id, joinedBy: ref.joinedBy });
1067
+ }
1068
+ }
1069
+
809
1070
  // 3. The decisions whose constrains cover the region, and their chains.
810
1071
  const regionFull = region.path === "." ? workspacePrefix : gitPath;
811
1072
  const origin = originRepo(top);
@@ -1099,16 +1360,6 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
1099
1360
  }
1100
1361
  if (node.constrains.length > 0) workCovering.push(h);
1101
1362
  }
1102
- for (const h of workAll.filter((x) => x.node)) {
1103
- for (const id of implementedIds(h)) {
1104
- const hit = decisionById.get(id.slice("record:".length))!;
1105
- edges.push({ kind: "implements", from: h.node!.id, to: decisionNode(hit.kind, hit.view).id });
1106
- }
1107
- for (const n of idList(h.view.data, workSpec(h).needs)) {
1108
- const hit = workByKey.get(`${h.kind.records!.loaded.kind.name}/${n}`);
1109
- if (hit) edges.push({ kind: "needs", from: h.node!.id, to: workNode(hit).id });
1110
- }
1111
- }
1112
1363
  const workWindows = new Map<string, { from: Set<string>; until: Set<string> | null }>();
1113
1364
  if (rev) {
1114
1365
  for (const h of workCovering) {
@@ -1125,6 +1376,102 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
1125
1376
  return !!w && w.from.has(sha) && !w.until?.has(sha);
1126
1377
  };
1127
1378
 
1379
+ // The records chant's own trailers name (#3149): a Chant-Record of a kind
1380
+ // read, and the work item a Chant-Lease was taken on. The commit carries
1381
+ // each, and is the own work of a decision it names or that a work item it
1382
+ // names implements.
1383
+ const recordKinds = new Map(kinds.filter((k) => k.records).map((k) => [k.records!.loaded.kind.name, k]));
1384
+ /** The work item a run worked on and the records it names, among the kinds read, deduplicated. */
1385
+ const runTargets = (r: RunView): { node: string; work?: WorkHit; decision?: string }[] => {
1386
+ const out: { node: string; work?: WorkHit; decision?: string }[] = [];
1387
+ const push = (x: { node: string; work?: WorkHit; decision?: string }) => {
1388
+ if (!out.some((o) => o.node === x.node)) out.push(x);
1389
+ };
1390
+ if (r.unit) {
1391
+ const named = r.unit.kind ? workKinds.filter((k) => k.records!.loaded.kind.name === r.unit!.kind) : workKinds;
1392
+ const hits = named.map((k) => workByKey.get(`${k.records!.loaded.kind.name}/${r.unit!.id}`)).filter((h): h is WorkHit => h !== undefined);
1393
+ if (hits.length === 1) push({ node: workNode(hits[0]).id, work: hits[0] });
1394
+ }
1395
+ for (const ref of r.records) {
1396
+ const k = recordKinds.get(ref.kind);
1397
+ if (!k) continue;
1398
+ if (k.records!.loaded.kind.work) {
1399
+ const h = workByKey.get(`${ref.kind}/${ref.id}`);
1400
+ if (h) push({ node: workNode(h).id, work: h });
1401
+ continue;
1402
+ }
1403
+ const hit = decisionById.get(`${ref.kind}/${ref.id}`);
1404
+ if (hit) push({ node: decisionNode(hit.kind, hit.view).id, decision: `${ref.kind}/${ref.id}` });
1405
+ }
1406
+ return out;
1407
+ };
1408
+ for (const t of touched) {
1409
+ const c = nodes.get(`commit:${t.sha}`) as CommitNode | undefined;
1410
+ const j = joined.get(t.sha);
1411
+ if (!c || !j) continue;
1412
+ const carry = (to: string) => {
1413
+ if (j.carries.includes(to)) return;
1414
+ j.carries.push(to);
1415
+ edges.push({ kind: "carries", from: c.id, to });
1416
+ };
1417
+ const carryWork = (h: WorkHit): string => {
1418
+ const node = workNode(h);
1419
+ carry(node.id);
1420
+ j.decisions.push(...implementedIds(h).map((id) => id.slice("record:".length)));
1421
+ return node.id;
1422
+ };
1423
+ for (const r of c.joins.records) {
1424
+ const k = recordKinds.get(r.kind);
1425
+ if (!k) continue;
1426
+ if (k.records!.loaded.kind.work) {
1427
+ const h = workByKey.get(`${r.kind}/${r.id}`);
1428
+ if (h) r.node = carryWork(h);
1429
+ continue;
1430
+ }
1431
+ const hit = decisionById.get(`${r.kind}/${r.id}`);
1432
+ if (!hit) continue;
1433
+ r.node = decisionNode(hit.kind, hit.view).id;
1434
+ carry(r.node);
1435
+ j.decisions.push(`${r.kind}/${r.id}`);
1436
+ }
1437
+ const item = c.joins.lease?.item;
1438
+ if (item) {
1439
+ const hits = workKinds.map((k) => workByKey.get(`${k.records!.loaded.kind.name}/${item}`)).filter((h): h is WorkHit => h !== undefined);
1440
+ if (hits.length === 1) carryWork(hits[0]);
1441
+ }
1442
+ // The runs that made the commit (#3034): its run's work item and the
1443
+ // records the run names are carried as a Chant-Lease and a Chant-Record are.
1444
+ for (const ref of runJoins.refs.get(t.sha) ?? []) {
1445
+ const r = runJoins.runs.get(ref.id);
1446
+ if (!r) continue;
1447
+ for (const target of runTargets(r)) {
1448
+ if (target.work) carryWork(target.work);
1449
+ else if (target.decision) {
1450
+ carry(target.node);
1451
+ j.decisions.push(target.decision);
1452
+ }
1453
+ }
1454
+ }
1455
+ }
1456
+ // Each recorded run in the walk to the work item and records it worked on (#3034).
1457
+ for (const n of [...nodes.values()]) {
1458
+ if (n.kind !== "run") continue;
1459
+ const r = runJoins.runs.get(n.run);
1460
+ if (!r) continue;
1461
+ for (const target of runTargets(r)) edges.push({ kind: "worked-on", from: n.id, to: target.node });
1462
+ }
1463
+ // Each work item in the graph, those its commits and runs carry included, to the decisions it implements and the items it needs.
1464
+ for (const h of workAll.filter((x) => x.node)) {
1465
+ for (const id of implementedIds(h)) {
1466
+ const hit = decisionById.get(id.slice("record:".length))!;
1467
+ edges.push({ kind: "implements", from: h.node!.id, to: decisionNode(hit.kind, hit.view).id });
1468
+ }
1469
+ for (const n of idList(h.view.data, workSpec(h).needs)) {
1470
+ const hit = workByKey.get(`${h.kind.records!.loaded.kind.name}/${n}`);
1471
+ if (hit) edges.push({ kind: "needs", from: h.node!.id, to: workNode(hit).id });
1472
+ }
1473
+ }
1474
+
1128
1475
  const coveredAt = (sha: string, granularities: Granularity[]) =>
1129
1476
  covering.filter((c) => {
1130
1477
  if (!c.node.constrains.some((x) => granularities.includes(x.granularity))) return false;
@@ -1133,9 +1480,10 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
1133
1480
  });
1134
1481
  const readsRecords = kinds.some((k) => k.records);
1135
1482
  // A decision's own work: a commit whose unit, or that unit's contract, names
1136
- // the decision, or whose unit serves a contract the decision constrains.
1483
+ // the decision, or whose unit serves a contract the decision constrains; or
1484
+ // a commit whose own trailers name the decision, or a work item implementing it.
1137
1485
  const ownWork = (j: Joined, d: DecisionNode) =>
1138
- !!j.unit && (j.decisions.some((x) => x === d.record || x === `${d.recordKind}/${d.record}`) || d.constrains.some((x) => x.granularity === "contract" && j.contracts.includes(x.entry)));
1486
+ (!!j.unit || j.carries.length > 0) && (j.decisions.some((x) => x === d.record || x === `${d.recordKind}/${d.record}`) || d.constrains.some((x) => x.granularity === "contract" && j.contracts.includes(x.entry)));
1139
1487
  for (const t of touched) {
1140
1488
  const c = nodes.get(`commit:${t.sha}`) as CommitNode | undefined;
1141
1489
  if (!c) continue;
@@ -1152,8 +1500,8 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
1152
1500
  if (readsRecords && c.state === "undecided") {
1153
1501
  find("intent-commit-undecided", `${t.sha.slice(0, 8)} changed the region when no decision constrained ${region.path} by path`, [c.id, rid]);
1154
1502
  }
1155
- if (readsRecords && !j.unit && c.pullRequest === null && coveredAt(t.sha, ["path", "member", "contract", "issue"]).length === 0) {
1156
- find("intent-commit-bare", `${t.sha.slice(0, 8)} names no unit, no pull request and no decision`, [c.id]);
1503
+ if (readsRecords && !j.unit && j.carries.length === 0 && c.pullRequest === null && coveredAt(t.sha, ["path", "member", "contract", "issue"]).length === 0) {
1504
+ find("intent-commit-bare", `${t.sha.slice(0, 8)} names no unit, no record, no pull request and no decision`, [c.id]);
1157
1505
  }
1158
1506
  const claims = [...new Set(j.authorship)].filter((key) => hasTrailer(c.trailers, key));
1159
1507
  if (claims.length > 0 && c.signature.level !== "attested") {
@@ -1304,6 +1652,7 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
1304
1652
  }
1305
1653
  }
1306
1654
  for (const f of findings) nodes.set(f.id, f);
1655
+ const why = explain();
1307
1656
  const all = [...nodes.values()];
1308
1657
  return {
1309
1658
  doc: {
@@ -1322,7 +1671,121 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
1322
1671
  artifacts: all.filter((n) => n.kind === "artifact").length,
1323
1672
  findings: findings.length,
1324
1673
  },
1674
+ why,
1325
1675
  },
1326
1676
  failed: reasons.some((r) => r.code === "intent-plugin-failed"),
1327
1677
  };
1678
+
1679
+ /** "Why is it like this" over the region (#3034): who made its current lines, and the decisions governing it, most relevant first. */
1680
+ function explain(): WhyAnswer {
1681
+ const hunksOf = (run: string, sha: string) => runJoins.runs.get(run)?.commits.find((c) => c.sha === sha)?.hunks ?? null;
1682
+ // Each current line: its commit, and the runs that wrote it.
1683
+ const who = blamed.map((b) => {
1684
+ if (b.sha === null) return { line: b.line, sha: null, runs: [] as string[], narrowedBy: null as "hunks" | null };
1685
+ const sha = b.sha;
1686
+ const refs = (runJoins.refs.get(sha) ?? []).map((r) => r.id);
1687
+ const withHunks = refs.filter((id) => hunksOf(id, sha) !== null);
1688
+ if (withHunks.length === 0) return { line: b.line, sha, runs: refs, narrowedBy: null };
1689
+ const wrote = withHunks.filter((id) => hunksOf(id, sha)!.some((h) => h.path === b.origPath && h.start <= b.origLine && b.origLine <= h.end));
1690
+ // A run that recorded its hunks and doesn't cover the line didn't write it; a run that recorded none may have.
1691
+ return { line: b.line, sha, runs: wrote.length > 0 ? wrote : refs.filter((id) => !withHunks.includes(id)), narrowedBy: "hunks" as const };
1692
+ });
1693
+ const spans: WhySpan[] = [];
1694
+ for (const w of who) {
1695
+ const last = spans[spans.length - 1];
1696
+ if (last && last.end === w.line - 1 && last.sha === w.sha && last.narrowedBy === w.narrowedBy && last.runs.join("\0") === w.runs.map((r) => `run:${r}`).join("\0")) {
1697
+ last.end = w.line;
1698
+ continue;
1699
+ }
1700
+ spans.push({ start: w.line, end: w.line, commit: w.sha ? `commit:${w.sha}` : null, sha: w.sha, runs: w.runs.map((r) => `run:${r}`), narrowedBy: w.narrowedBy });
1701
+ }
1702
+ const ranges = (lines: number[]): LineRange[] => {
1703
+ const out: LineRange[] = [];
1704
+ for (const l of lines) {
1705
+ const last = out[out.length - 1];
1706
+ if (last && last.end === l - 1) last.end = l;
1707
+ else out.push({ start: l, end: l });
1708
+ }
1709
+ return out;
1710
+ };
1711
+
1712
+ // The decisions, most relevant first.
1713
+ const decisionNodes = [...nodes.values()].filter((n): n is DecisionNode => n.kind === "decision");
1714
+ const ownLines = (d: DecisionNode) => who.filter((w) => w.sha !== null && ownWork(joined.get(w.sha) ?? { contracts: [], authorship: [], decisions: [], carries: [] }, d)).length;
1715
+ const ORDER: WhyRelevance[] = ["carried", "path", "contract", "issue", "member", "related"];
1716
+ const ranked = decisionNodes.map((d) => {
1717
+ const lines = ownLines(d);
1718
+ const carried = lines > 0 || (fileLines === null && touched.some((t) => ownWork(joined.get(t.sha) ?? { contracts: [], authorship: [], decisions: [], carries: [] }, d)));
1719
+ const granularities = new Set(d.constrains.map((c) => c.granularity));
1720
+ const relevance: WhyRelevance = carried ? "carried" : ((["path", "contract", "issue", "member"] as const).find((g) => granularities.has(g)) ?? "related");
1721
+ // A narrower path constraint is more specific to the region.
1722
+ const specificity = Math.max(0, ...d.constrains.filter((c) => c.granularity === "path").map((c) => c.entry.length));
1723
+ return { d, entry: { decision: d.id, relevance, current: d.supersededBy === null, closed: d.closed, lines } satisfies WhyDecision, specificity };
1724
+ });
1725
+ ranked.sort(
1726
+ (a, b) =>
1727
+ Number(b.entry.current) - Number(a.entry.current) ||
1728
+ ORDER.indexOf(a.entry.relevance) - ORDER.indexOf(b.entry.relevance) ||
1729
+ b.entry.lines - a.entry.lines ||
1730
+ Number(b.entry.closed) - Number(a.entry.closed) ||
1731
+ b.specificity - a.specificity ||
1732
+ (b.d.decidedIn?.date ?? "").localeCompare(a.d.decidedIn?.date ?? "") ||
1733
+ a.d.id.localeCompare(b.d.id),
1734
+ );
1735
+ const decisions = ranked.map((r) => r.entry);
1736
+
1737
+ // The runs that wrote the current lines, most lines first; for a directory, every run in the walk.
1738
+ const runNodes = [...nodes.values()].filter((n): n is RunNode => n.kind === "run");
1739
+ const runEntry = (n: RunNode, lines: number, commits: string[]): WhyRun => {
1740
+ const r = runJoins.runs.get(n.run);
1741
+ const targets = edges.filter((e) => e.kind === "worked-on" && e.from === n.id).map((e) => nodes.get(e.to));
1742
+ const work = r?.unit ? targets.find((t): t is WorkNode => t?.kind === "work" && t.record === r.unit!.id) : undefined;
1743
+ const carried = new Set<string>();
1744
+ for (const t of targets) {
1745
+ if (t?.kind === "decision") carried.add(t.id);
1746
+ if (t?.kind === "work") for (const e of edges) if (e.kind === "implements" && e.from === t.id) carried.add(e.to);
1747
+ }
1748
+ return { run: n.id, lines, commits, unit: r?.unit ? { id: r.unit.id, kind: r.unit.kind, node: work?.id ?? null } : null, decisions: [...carried] };
1749
+ };
1750
+ let runs: WhyRun[];
1751
+ if (fileLines !== null) {
1752
+ const tally = new Map<string, { lines: number; commits: Set<string> }>();
1753
+ for (const w of who) {
1754
+ for (const id of w.runs) {
1755
+ const t = tally.get(id) ?? tally.set(id, { lines: 0, commits: new Set() }).get(id)!;
1756
+ t.lines++;
1757
+ t.commits.add(`commit:${w.sha}`);
1758
+ }
1759
+ }
1760
+ runs = [...tally]
1761
+ .map(([id, t]) => ({ n: nodes.get(`run:${id}`) as RunNode, t }))
1762
+ .filter((x) => x.n !== undefined)
1763
+ .sort((a, b) => b.t.lines - a.t.lines || (b.n.startedAt ?? "").localeCompare(a.n.startedAt ?? "") || a.n.run.localeCompare(b.n.run))
1764
+ .map((x) => runEntry(x.n, x.t.lines, [...x.t.commits]));
1765
+ } else {
1766
+ runs = runNodes
1767
+ .sort((a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? "") || a.run.localeCompare(b.run))
1768
+ .map((n) => runEntry(n, 0, edges.filter((e) => e.kind === "made-by" && e.to === n.id).map((e) => e.from)));
1769
+ }
1770
+
1771
+ const explained = decisions.some((d) => d.current && d.relevance !== "related");
1772
+ const gaps: WhyGap[] = [];
1773
+ if (!explained) {
1774
+ gaps.push({
1775
+ code: "intent-why-no-decision",
1776
+ message: readsRecords ? `no current decision governs ${region.path}, and none is carried out by what made its lines` : `no record kind was read, so no decision can account for ${region.path}`,
1777
+ });
1778
+ }
1779
+ const committed = who.filter((w) => w.sha !== null);
1780
+ if (fileLines !== null ? committed.length > 0 && committed.every((w) => w.runs.length === 0) : touched.length > 0 && runNodes.length === 0) {
1781
+ gaps.push({ code: "intent-why-no-run", message: `no agent run is joined to the commits that made ${fileLines !== null ? "the current lines of " : ""}${region.path}` });
1782
+ }
1783
+ const uncommitted = who.filter((w) => w.sha === null).map((w) => w.line);
1784
+ if (uncommitted.length > 0) gaps.push({ code: "intent-why-uncommitted", message: `${uncommitted.length} ${uncommitted.length === 1 ? "line is" : "lines are"} not committed yet`, lines: ranges(uncommitted) });
1785
+ const ambiguous = who.filter((w) => w.runs.length > 1).map((w) => w.line);
1786
+ if (ambiguous.length > 0) {
1787
+ gaps.push({ code: "intent-why-run-ambiguous", message: `${ambiguous.length} ${ambiguous.length === 1 ? "line comes" : "lines come"} from a commit several runs made, and no run's recorded hunks say which wrote ${ambiguous.length === 1 ? "it" : "them"}`, lines: ranges(ambiguous) });
1788
+ }
1789
+ return { lines: fileLines, blame: spans, decisions, runs, explained, gaps };
1790
+ }
1328
1791
  }