@intentius/chant 0.102.0 → 0.104.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) 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/vendor/js-yaml-load.d.ts +56 -0
  114. package/dist/vendor/js-yaml-load.d.ts.map +1 -0
  115. package/dist/workspace/box-cli.d.ts +12 -0
  116. package/dist/workspace/box-cli.d.ts.map +1 -0
  117. package/dist/workspace/box-factory.d.ts +27 -3
  118. package/dist/workspace/box-factory.d.ts.map +1 -1
  119. package/dist/workspace/box-listing.d.ts +85 -0
  120. package/dist/workspace/box-listing.d.ts.map +1 -0
  121. package/dist/workspace/box-publish.d.ts +147 -0
  122. package/dist/workspace/box-publish.d.ts.map +1 -0
  123. package/dist/workspace/broker-protocol.d.ts +190 -0
  124. package/dist/workspace/broker-protocol.d.ts.map +1 -0
  125. package/dist/workspace/checks.d.ts.map +1 -1
  126. package/dist/workspace/composites.d.ts +2 -2
  127. package/dist/workspace/composites.d.ts.map +1 -1
  128. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
  129. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
  130. package/dist/workspace/conformance/broker.d.ts +202 -0
  131. package/dist/workspace/conformance/broker.d.ts.map +1 -0
  132. package/dist/workspace/conformance/index.d.ts +2 -1
  133. package/dist/workspace/conformance/index.d.ts.map +1 -1
  134. package/dist/workspace/conformance/vitest.d.ts +9 -2
  135. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  136. package/dist/workspace/conformance/writer.d.ts +108 -14
  137. package/dist/workspace/conformance/writer.d.ts.map +1 -1
  138. package/dist/workspace/decide.d.ts +126 -12
  139. package/dist/workspace/decide.d.ts.map +1 -1
  140. package/dist/workspace/decision-points.schema.json +11 -4
  141. package/dist/workspace/declaration.d.ts +65 -0
  142. package/dist/workspace/declaration.d.ts.map +1 -1
  143. package/dist/workspace/declaration.schema.json +153 -4
  144. package/dist/workspace/environments.d.ts +36 -10
  145. package/dist/workspace/environments.d.ts.map +1 -1
  146. package/dist/workspace/factory-rules.d.ts +180 -0
  147. package/dist/workspace/factory-rules.d.ts.map +1 -0
  148. package/dist/workspace/init.d.ts +21 -0
  149. package/dist/workspace/init.d.ts.map +1 -1
  150. package/dist/workspace/intent-cli.d.ts +1 -1
  151. package/dist/workspace/intent-cli.d.ts.map +1 -1
  152. package/dist/workspace/intent-record.d.ts +28 -1
  153. package/dist/workspace/intent-record.d.ts.map +1 -1
  154. package/dist/workspace/intent.d.ts +102 -5
  155. package/dist/workspace/intent.d.ts.map +1 -1
  156. package/dist/workspace/json-edit.d.ts +24 -0
  157. package/dist/workspace/json-edit.d.ts.map +1 -0
  158. package/dist/workspace/jsonc.d.ts +11 -0
  159. package/dist/workspace/jsonc.d.ts.map +1 -1
  160. package/dist/workspace/kinds.d.ts +48 -0
  161. package/dist/workspace/kinds.d.ts.map +1 -1
  162. package/dist/workspace/lexicon-symbols.d.ts +29 -0
  163. package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
  164. package/dist/workspace/lineage-upgrade.d.ts +31 -2
  165. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  166. package/dist/workspace/point-answer.schema.json +42 -2
  167. package/dist/workspace/points-cli.d.ts +10 -3
  168. package/dist/workspace/points-cli.d.ts.map +1 -1
  169. package/dist/workspace/points.d.ts +66 -5
  170. package/dist/workspace/points.d.ts.map +1 -1
  171. package/dist/workspace/reason-codes.d.ts +33 -4
  172. package/dist/workspace/reason-codes.d.ts.map +1 -1
  173. package/dist/workspace/record-sessions.d.ts +5 -7
  174. package/dist/workspace/record-sessions.d.ts.map +1 -1
  175. package/dist/workspace/records-cli.d.ts +7 -0
  176. package/dist/workspace/records-cli.d.ts.map +1 -1
  177. package/dist/workspace/records-close.d.ts +7 -3
  178. package/dist/workspace/records-close.d.ts.map +1 -1
  179. package/dist/workspace/records-write.d.ts +66 -6
  180. package/dist/workspace/records-write.d.ts.map +1 -1
  181. package/dist/workspace/records.d.ts +28 -9
  182. package/dist/workspace/records.d.ts.map +1 -1
  183. package/dist/workspace/runs-cli.d.ts +138 -6
  184. package/dist/workspace/runs-cli.d.ts.map +1 -1
  185. package/dist/workspace/runs.d.ts +132 -11
  186. package/dist/workspace/runs.d.ts.map +1 -1
  187. package/dist/workspace/session-kinds.d.ts +1 -1
  188. package/dist/workspace/session-kinds.d.ts.map +1 -1
  189. package/dist/workspace/squash.d.ts +80 -0
  190. package/dist/workspace/squash.d.ts.map +1 -0
  191. package/dist/workspace/status-gates.d.ts +2 -0
  192. package/dist/workspace/status-gates.d.ts.map +1 -1
  193. package/dist/workspace/status.d.ts +54 -0
  194. package/dist/workspace/status.d.ts.map +1 -1
  195. package/dist/workspace/symbols.d.ts +16 -5
  196. package/dist/workspace/symbols.d.ts.map +1 -1
  197. package/dist/workspace/trust/run-commits.d.ts +46 -0
  198. package/dist/workspace/trust/run-commits.d.ts.map +1 -0
  199. package/dist/workspace/trust/run-statement.d.ts +128 -0
  200. package/dist/workspace/trust/run-statement.d.ts.map +1 -0
  201. package/dist/workspace/trust/seal.d.ts +6 -1
  202. package/dist/workspace/trust/seal.d.ts.map +1 -1
  203. package/dist/workspace/trust/verify-cli.d.ts +1 -1
  204. package/dist/workspace/trust/verify.d.ts +16 -2
  205. package/dist/workspace/trust/verify.d.ts.map +1 -1
  206. package/dist/workspace/wip-cli.d.ts +21 -0
  207. package/dist/workspace/wip-cli.d.ts.map +1 -0
  208. package/dist/workspace/wip.d.ts +268 -0
  209. package/dist/workspace/wip.d.ts.map +1 -0
  210. package/dist/workspace/work-cli.d.ts.map +1 -1
  211. package/dist/workspace/work-evidence.d.ts +1 -1
  212. package/dist/workspace/work-evidence.d.ts.map +1 -1
  213. package/dist/workspace/work-size.d.ts +55 -0
  214. package/dist/workspace/work-size.d.ts.map +1 -0
  215. package/dist/workspace/workspace-kinds.schema.json +122 -0
  216. package/dist/workspace/write-lock-cli.d.ts +60 -0
  217. package/dist/workspace/write-lock-cli.d.ts.map +1 -0
  218. package/dist/workspace/write-lock.d.ts +152 -0
  219. package/dist/workspace/write-lock.d.ts.map +1 -0
  220. package/dist/workspace/write-scope.d.ts +6 -2
  221. package/dist/workspace/write-scope.d.ts.map +1 -1
  222. package/dist/yaml.d.ts +1 -1
  223. package/dist/yaml.d.ts.map +1 -1
  224. package/package.json +6 -1
  225. package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
  226. package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
  227. package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
  228. package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
  229. package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
  230. package/src/__fixtures__/change-set/warden.plan.json +44 -0
  231. package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
  232. package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
  233. package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
  234. package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
  235. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
  236. package/src/audit/core.test.ts +15 -0
  237. package/src/audit/core.ts +8 -2
  238. package/src/audit/discover.test.ts +11 -0
  239. package/src/audit/discover.ts +7 -2
  240. package/src/change-set-bundle.test.ts +57 -0
  241. package/src/change-set.test.ts +215 -0
  242. package/src/change-set.ts +445 -0
  243. package/src/cli/commands/audit-opt-in.test.ts +54 -0
  244. package/src/cli/commands/audit.ts +29 -2
  245. package/src/cli/commands/build.test.ts +19 -0
  246. package/src/cli/commands/build.ts +3 -2
  247. package/src/cli/handlers/build.ts +13 -1
  248. package/src/cli/handlers/change-set.test.ts +56 -0
  249. package/src/cli/handlers/change-set.ts +44 -0
  250. package/src/cli/handlers/fan-out.test.ts +221 -2
  251. package/src/cli/handlers/fan-out.ts +165 -75
  252. package/src/cli/handlers/graph.ts +3 -0
  253. package/src/cli/handlers/operator.test.ts +21 -0
  254. package/src/cli/handlers/operator.ts +34 -3
  255. package/src/cli/handlers/pr.ts +373 -0
  256. package/src/cli/handlers/run.ts +1 -0
  257. package/src/cli/main.test.ts +20 -0
  258. package/src/cli/main.ts +185 -23
  259. package/src/cli/mcp/never-over-mcp.test.ts +101 -0
  260. package/src/cli/mcp/op-tools.ts +24 -2
  261. package/src/cli/mcp/workspace-tools.test.ts +1 -1
  262. package/src/cli/mcp/workspace-tools.ts +34 -6
  263. package/src/cli/registry.ts +63 -1
  264. package/src/components/auto-release.ts +1 -1
  265. package/src/components/capability.ts +55 -0
  266. package/src/components/changed-files.ts +66 -0
  267. package/src/components/cli-support.ts +28 -5
  268. package/src/components/component.schema.json +17 -0
  269. package/src/components/component.ts +25 -0
  270. package/src/components/deploy-units.ts +3 -0
  271. package/src/components/driver.test.ts +52 -0
  272. package/src/components/driver.ts +31 -13
  273. package/src/components/fan-out-output.ts +42 -1
  274. package/src/components/fan-out-record.ts +69 -0
  275. package/src/components/fan-out-run.test.ts +17 -0
  276. package/src/components/fan-out-run.ts +304 -1
  277. package/src/components/fan-out-support.ts +3 -0
  278. package/src/components/fan-out-waves.test.ts +375 -0
  279. package/src/components/fan-out.ts +18 -17
  280. package/src/components/gated-wave-pipeline.ts +82 -0
  281. package/src/components/index.ts +1 -0
  282. package/src/components/layers.ts +30 -0
  283. package/src/components/pr-pipeline.ts +91 -0
  284. package/src/components/pr-run.test.ts +205 -0
  285. package/src/components/pr-run.ts +280 -0
  286. package/src/components/promote.ts +5 -0
  287. package/src/components/verbs/shell.test.ts +11 -0
  288. package/src/components/verbs/shell.ts +7 -3
  289. package/src/components/wave-plan.ts +124 -0
  290. package/src/discovery/sandbox/bundle.ts +1 -1
  291. package/src/gated-waves-bundle.test.ts +62 -0
  292. package/src/gated-waves.test.ts +118 -0
  293. package/src/gated-waves.ts +240 -0
  294. package/src/index.ts +1 -0
  295. package/src/lexicon.ts +51 -0
  296. package/src/lifecycle/gate-ledger.ts +7 -0
  297. package/src/lifecycle/git.ts +102 -16
  298. package/src/lint/post-synth.test.ts +19 -0
  299. package/src/lint/post-synth.ts +19 -1
  300. package/src/meta/declared-imports.test.ts +1 -1
  301. package/src/op/activities/activity-contracts.ts +84 -0
  302. package/src/op/activities/change-set.ts +114 -0
  303. package/src/op/activities/decide.ts +3 -0
  304. package/src/op/activities/factory.ts +675 -0
  305. package/src/op/activities/index.ts +6 -0
  306. package/src/op/activity-contract.test.ts +21 -1
  307. package/src/op/activity-contract.ts +19 -9
  308. package/src/op/builders.ts +23 -0
  309. package/src/op/decide-backend.ts +5 -1
  310. package/src/op/decide-read-inputs.ts +33 -5
  311. package/src/op/factory.e2e.test.ts +232 -0
  312. package/src/op/factory.ts +118 -0
  313. package/src/op/index.ts +9 -1
  314. package/src/op/op-ir.test.ts +9 -0
  315. package/src/op/op-ir.ts +4 -0
  316. package/src/op/op-verb-class.ts +3 -0
  317. package/src/op/types.ts +8 -0
  318. package/src/op/work-lease-run.ts +3 -0
  319. package/src/plan-summary.test.ts +263 -0
  320. package/src/plan-summary.ts +845 -0
  321. package/src/pr-forge.test.ts +237 -0
  322. package/src/pr-forge.ts +332 -0
  323. package/src/pr-loop-bundle.test.ts +46 -0
  324. package/src/pr-loop.test.ts +171 -0
  325. package/src/pr-loop.ts +324 -0
  326. package/src/telemetry-attribution.test.ts +23 -1
  327. package/src/telemetry-attribution.ts +51 -0
  328. package/src/terraform/hcl2json.test.ts +21 -0
  329. package/src/terraform/hcl2json.ts +81 -0
  330. package/src/terraform/parse.ts +11 -74
  331. package/src/vendor/js-yaml-load.test.ts +115 -0
  332. package/src/vendor/js-yaml-load.ts +1939 -0
  333. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  334. package/src/workspace/app-kind.test.ts +196 -0
  335. package/src/workspace/box-cli.ts +113 -0
  336. package/src/workspace/box-factory.test.ts +4 -1
  337. package/src/workspace/box-factory.ts +37 -4
  338. package/src/workspace/box-listing-write.schema.json +204 -0
  339. package/src/workspace/box-listing.test.ts +234 -0
  340. package/src/workspace/box-listing.ts +324 -0
  341. package/src/workspace/box-publish.schema.json +498 -0
  342. package/src/workspace/box-publish.test.ts +204 -0
  343. package/src/workspace/box-publish.ts +420 -0
  344. package/src/workspace/box-ship.test.ts +96 -0
  345. package/src/workspace/broker-protocol.schema.json +186 -0
  346. package/src/workspace/broker-protocol.test.ts +122 -0
  347. package/src/workspace/broker-protocol.ts +317 -0
  348. package/src/workspace/builder-tiers.test.ts +146 -0
  349. package/src/workspace/change-set.schema.json +460 -0
  350. package/src/workspace/chant-migrations.e2e.test.ts +17 -5
  351. package/src/workspace/checks/boxes.test.ts +3 -0
  352. package/src/workspace/checks.ts +10 -3
  353. package/src/workspace/component-environments.test.ts +105 -0
  354. package/src/workspace/composites.schema.json +23 -4
  355. package/src/workspace/composites.test.ts +8 -8
  356. package/src/workspace/composites.ts +13 -2
  357. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
  358. package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
  359. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +7 -2
  360. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +42 -2
  361. package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  362. package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  363. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +96 -0
  364. package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  365. package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  366. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +14 -8
  367. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +187 -6
  368. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +25 -0
  369. package/src/workspace/conformance/broker.test.ts +122 -0
  370. package/src/workspace/conformance/broker.ts +792 -0
  371. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  372. package/src/workspace/conformance/index.mjs +22 -1
  373. package/src/workspace/conformance/index.ts +5 -1
  374. package/src/workspace/conformance/vitest.ts +33 -1
  375. package/src/workspace/conformance/writer-conformance.e2e.test.ts +49 -3
  376. package/src/workspace/conformance/writer.test.ts +32 -2
  377. package/src/workspace/conformance/writer.ts +339 -28
  378. package/src/workspace/decide.test.ts +208 -4
  379. package/src/workspace/decide.ts +427 -73
  380. package/src/workspace/decision-points.schema.json +11 -4
  381. package/src/workspace/declaration.schema.json +153 -4
  382. package/src/workspace/declaration.ts +141 -0
  383. package/src/workspace/environments.ts +66 -14
  384. package/src/workspace/factory-rules.test.ts +62 -0
  385. package/src/workspace/factory-rules.ts +218 -0
  386. package/src/workspace/graph-cli.ts +2 -2
  387. package/src/workspace/graph.schema.json +67 -0
  388. package/src/workspace/identity.test.ts +23 -0
  389. package/src/workspace/init.ts +82 -2
  390. package/src/workspace/intent-cli.ts +26 -9
  391. package/src/workspace/intent-patch-id.test.ts +162 -0
  392. package/src/workspace/intent-record.schema.json +130 -3
  393. package/src/workspace/intent-record.ts +74 -28
  394. package/src/workspace/intent-reviews.test.ts +76 -0
  395. package/src/workspace/intent-squash.test.ts +213 -0
  396. package/src/workspace/intent-why.test.ts +5 -5
  397. package/src/workspace/intent.schema.json +234 -4
  398. package/src/workspace/intent.ts +260 -66
  399. package/src/workspace/json-edit.test.ts +121 -0
  400. package/src/workspace/json-edit.ts +142 -0
  401. package/src/workspace/jsonc.ts +20 -0
  402. package/src/workspace/kinds.ts +157 -2
  403. package/src/workspace/lexicon-symbols.test.ts +148 -0
  404. package/src/workspace/lexicon-symbols.ts +125 -0
  405. package/src/workspace/lineage-upgrade.test.ts +63 -0
  406. package/src/workspace/lineage-upgrade.ts +146 -5
  407. package/src/workspace/plan-summary.schema.json +155 -0
  408. package/src/workspace/point-answer.schema.json +42 -2
  409. package/src/workspace/points-cli.ts +42 -16
  410. package/src/workspace/points-write.schema.json +162 -4
  411. package/src/workspace/points.schema.json +157 -2
  412. package/src/workspace/points.test.ts +13 -1
  413. package/src/workspace/points.ts +118 -12
  414. package/src/workspace/pr-report.schema.json +95 -0
  415. package/src/workspace/profiles.test.ts +159 -0
  416. package/src/workspace/read-contract.test.ts +2 -2
  417. package/src/workspace/reason-codes.test.ts +13 -1
  418. package/src/workspace/reason-codes.ts +38 -4
  419. package/src/workspace/record-sessions.ts +9 -8
  420. package/src/workspace/records-amend.schema.json +78 -0
  421. package/src/workspace/records-cli.ts +20 -6
  422. package/src/workspace/records-close.schema.json +78 -0
  423. package/src/workspace/records-close.ts +31 -7
  424. package/src/workspace/records-concurrent.e2e.test.ts +135 -0
  425. package/src/workspace/records-concurrent.test.ts +145 -0
  426. package/src/workspace/records-formats.test.ts +46 -10
  427. package/src/workspace/records-new.schema.json +7 -0
  428. package/src/workspace/records-review.schema.json +78 -0
  429. package/src/workspace/records-sessions-write.test.ts +69 -0
  430. package/src/workspace/records-sessions.test.ts +22 -0
  431. package/src/workspace/records-write.ts +192 -25
  432. package/src/workspace/records.schema.json +16 -0
  433. package/src/workspace/records.ts +50 -9
  434. package/src/workspace/reference-kinds/app/package.json +10 -0
  435. package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  436. package/src/workspace/run-statement.schema.json +622 -0
  437. package/src/workspace/runs-cli.ts +343 -14
  438. package/src/workspace/runs-statement.test.ts +239 -0
  439. package/src/workspace/runs-write.schema.json +416 -5
  440. package/src/workspace/runs.schema.json +451 -4
  441. package/src/workspace/runs.test.ts +39 -0
  442. package/src/workspace/runs.ts +296 -35
  443. package/src/workspace/session-kinds.ts +3 -3
  444. package/src/workspace/squash.ts +198 -0
  445. package/src/workspace/status-contract.test.ts +5 -2
  446. package/src/workspace/status-gates.ts +3 -1
  447. package/src/workspace/status.schema.json +126 -3
  448. package/src/workspace/status.ts +90 -1
  449. package/src/workspace/symbols.ts +20 -9
  450. package/src/workspace/trust/run-commits.ts +115 -0
  451. package/src/workspace/trust/run-statement.ts +226 -0
  452. package/src/workspace/trust/seal.ts +8 -2
  453. package/src/workspace/trust/verify-cli.ts +6 -5
  454. package/src/workspace/trust/verify.ts +39 -6
  455. package/src/workspace/wip-cli.ts +97 -0
  456. package/src/workspace/wip-write.schema.json +504 -0
  457. package/src/workspace/wip.schema.json +334 -0
  458. package/src/workspace/wip.test.ts +294 -0
  459. package/src/workspace/wip.ts +753 -0
  460. package/src/workspace/work-cli.ts +3 -0
  461. package/src/workspace/work-evidence.schema.json +3 -0
  462. package/src/workspace/work-size.test.ts +77 -0
  463. package/src/workspace/work-size.ts +93 -0
  464. package/src/workspace/workspace-kinds.schema.json +122 -0
  465. package/src/workspace/write-lock-cli.ts +131 -0
  466. package/src/workspace/write-lock.schema.json +172 -0
  467. package/src/workspace/write-lock.test.ts +189 -0
  468. package/src/workspace/write-lock.ts +474 -0
  469. package/src/workspace/write-scope.test.ts +16 -1
  470. package/src/workspace/write-scope.ts +33 -11
  471. package/src/yaml.ts +31 -21
@@ -55,6 +55,7 @@ import { declaredKindFiles, readRecordsFor } from "./records-cli";
55
55
  import { loadRecordKind, RecordReadError, type LoadedRecordKind, type RecordEntry } from "./records";
56
56
  import { gitRoot } from "./record-source";
57
57
  import type { ReasonCode } from "./reason-codes";
58
+ import { replicateAfter } from "./wip";
58
59
 
59
60
  /** The version of the `work` output this chant writes. */
60
61
  export const WORK_LEASE_CONTRACT_VERSION = 1;
@@ -223,6 +224,8 @@ export async function workLease(req: WorkLeaseRequest): Promise<WorkLeaseDocumen
223
224
  const heldBy = result.heldBy ? strip(result.heldBy) : null;
224
225
  return { ...head, refused: { code: result.reason, message: result.message, heldBy } };
225
226
  }
227
+ // #3172: a box's replicate policy may push its work in progress once a lease is released. Best effort; status shows what is behind.
228
+ if (req.verb === "release") replicateAfter("release", req.cwd);
226
229
  return { ...head, lease: strip(result.lease), history: result.history };
227
230
  } catch (err) {
228
231
  if (err instanceof WorkError) return { ...doc, error: { code: err.code, message: err.message } };
@@ -182,6 +182,9 @@
182
182
  "amend-supersede-instead",
183
183
  "record-sign-failed",
184
184
  "ratify-quorum-not-met",
185
+ "record-conflict",
186
+ "write-lock-timeout",
187
+ "write-lock-not-held",
185
188
  "write-scope-member",
186
189
  "write-scope-kind",
187
190
  "write-scope-class-unknown",
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The slice-tier point's sizes and the understand point (#3150): chant
3
+ * measures a work item the same way for every orchestrator, the decide
4
+ * activity reads the sizes with the item, and the reference workspace
5
+ * declares understand.
6
+ */
7
+
8
+ import { cpSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { afterAll, describe, expect, test } from "vitest";
11
+ import { readInputs } from "../op/decide-read-inputs";
12
+ import { cleanScratch, git, REPO, scratchDir } from "./__fixtures__/contract-repo";
13
+ import { parsePoints } from "./points";
14
+ import { DEFAULT_TIER_LIMITS, fitsTier, measureWorkItem, sizeFields } from "./work-size";
15
+
16
+ afterAll(cleanScratch);
17
+
18
+ const SIZES = ["work-item.criteria", "work-item.files", "work-item.words", "work-item.fits_small", "work-item.fits_medium"];
19
+
20
+ describe("measuring a work item (#3150)", () => {
21
+ test("criteria, backticked paths and words from the ask, the body without headings and the criteria", () => {
22
+ const data = {
23
+ source: { ask: { said: "Make `app/server.ts` answer on `/healthz` and note it in `README.md`.", by: "alice" } },
24
+ acceptance: [{ id: "AC-1", text: "`app/server.ts` answers 200" }, { id: "AC-2", text: "the README says so" }],
25
+ };
26
+ const body = "# Heading words are not counted\n\nThe body names `app/server.ts` again and `docs/`.\n";
27
+ const size = measureWorkItem(data, body);
28
+ // `docs/` and `/healthz` have no part on both sides of a slash, so they are not paths.
29
+ expect(size).toEqual({ criteria: 2, files: 2, words: 24 });
30
+ expect(measureWorkItem({ source: { intent: { answer: "a garden planner" } } }, "")).toEqual({ criteria: 0, files: 0, words: 3 });
31
+ expect(measureWorkItem(null, "")).toEqual({ criteria: 0, files: 0, words: 0 });
32
+ });
33
+
34
+ test("fits_<tier> for each tier with limits, against the defaults or the kind's own", () => {
35
+ expect(sizeFields({ criteria: 11, files: 2, words: 40 })).toEqual({ criteria: 11, files: 2, words: 40, fits_small: false, fits_medium: true });
36
+ expect(sizeFields({ criteria: 1, files: 1, words: 400 })).toMatchObject({ fits_small: false, fits_medium: false });
37
+ expect(sizeFields({ criteria: 1, files: 1, words: 40 }, { tiny: { words: 30 } })).toEqual({ criteria: 1, files: 1, words: 40, fits_tiny: false });
38
+ expect(fitsTier({ criteria: 10, files: 5, words: 150 }, DEFAULT_TIER_LIMITS.small)).toBe(true);
39
+ expect(fitsTier({ criteria: 0, files: 0, words: 0 }, undefined)).toBe(false);
40
+ });
41
+ });
42
+
43
+ describe("the reference workspace (#3150)", () => {
44
+ const ref = join(REPO, "reference-workspace");
45
+
46
+ test("declares understand: four answers, a table that proceeds on an open item, then people", () => {
47
+ const points = parsePoints(readFileSync(join(ref, "decisions", "points.json"), "utf-8"), "points.json");
48
+ const u = points.understand;
49
+ expect(Object.keys(u.question.criteria ?? {})).toEqual(["proceed", "redraft", "ask", "refuse"]);
50
+ expect(Object.keys(u.inputs)).toEqual(["work-item.state", "work-item.title", "work-item.source.ask.said", "work-item.source.ask.by", "work-item.source.ask.via", "work-item.acceptance"]);
51
+ expect(u.deciders.map((d) => d.kind)).toEqual(["table", "quorum"]);
52
+ });
53
+
54
+ test("the decide activity reads a work item's sizes with it, and the work kind's limits win", async () => {
55
+ const copy = (): string => {
56
+ const root = scratchDir("chant-work-size-");
57
+ cpSync(ref, root, { recursive: true, filter: (src) => !/[\\/](node_modules|dist)$/.test(src) });
58
+ git(root, "init", "-q");
59
+ git(root, "add", "-A");
60
+ git(root, "commit", "-q", "-m", "the workspace");
61
+ return root;
62
+ };
63
+ let root = copy();
64
+ const read = await readInputs(SIZES, { "work-item": "W-001" }, root);
65
+ expect(read.missing).toEqual([]);
66
+ expect(read.inputs).toMatchObject({ "work-item.criteria": 2, "work-item.fits_small": true, "work-item.fits_medium": true });
67
+ expect(typeof read.inputs["work-item.words"]).toBe("number");
68
+
69
+ // A fresh copy: a kind file is loaded once per path.
70
+ root = copy();
71
+ const kind = join(root, "work", "work.kind.mjs");
72
+ writeFileSync(kind, readFileSync(kind, "utf-8").replace('tier: { field: "tier", tiers: ["small", "medium", "large"] }', 'tier: { field: "tier", tiers: ["small", "medium", "large"], limits: { small: { criteria: 1 } } }'));
73
+ const narrow = await readInputs(SIZES, { "work-item": "W-001" }, root);
74
+ expect(narrow.inputs["work-item.fits_small"]).toBe(false);
75
+ expect(narrow.missing).toEqual(["work-item.fits_medium"]);
76
+ });
77
+ });
@@ -0,0 +1,93 @@
1
+ /**
2
+ * How much a work item holds, for the `slice-tier` decision point (#3150).
3
+ *
4
+ * The point picks the smallest builder tier that can build an item, from five
5
+ * inputs every orchestrator must compute the same way, so chant computes
6
+ * them. They are read through the `work-item` output like any other field:
7
+ *
8
+ * work-item.criteria the item's acceptance criteria, counted
9
+ * work-item.files distinct paths the item names in backticks
10
+ * work-item.words words in the item's text
11
+ * work-item.fits_<tier> whether criteria, files and words are each within
12
+ * that tier's limits (`fits_small`, `fits_medium`)
13
+ *
14
+ * The item's text is what a person asked (`source.ask.said`, or the answer
15
+ * to the box's intent, `source.intent.answer`), the record's Markdown body
16
+ * with its heading lines left out, and each acceptance criterion's text. A
17
+ * path is a backticked token with a `/` between two parts, such as
18
+ * `app/server.ts` or `design/screens/`, or a file name with an extension of
19
+ * one to five letters, such as `README.md`. Words are runs of non-blank
20
+ * characters.
21
+ *
22
+ * The limits are the work kind's `work.tier.limits`, else
23
+ * {@link DEFAULT_TIER_LIMITS}: small holds at most 10 criteria, 5 files and
24
+ * 150 words, medium 20, 10 and 300. A tier with no limits, such as large,
25
+ * has no `fits_` input; an item that fits neither small nor medium is large.
26
+ * These are the sizing defaults studio's factory used (arugula-salad/studio
27
+ * `queue.mjs` `sizing()`), so a point's answers keep their meaning.
28
+ */
29
+
30
+ /** A field of `value` by dotted path, or undefined. */
31
+ function fieldOf(value: unknown, path: string): unknown {
32
+ let at: unknown = value;
33
+ for (const part of path.split(".")) {
34
+ if (at === null || typeof at !== "object" || !Object.prototype.hasOwnProperty.call(at, part)) return undefined;
35
+ at = (at as Record<string, unknown>)[part];
36
+ }
37
+ return at;
38
+ }
39
+
40
+ /** What one tier may hold. A limit left out is not checked. */
41
+ export interface TierLimit {
42
+ criteria?: number;
43
+ files?: number;
44
+ words?: number;
45
+ }
46
+
47
+ /** The limits when the work kind declares none. */
48
+ export const DEFAULT_TIER_LIMITS: Readonly<Record<string, TierLimit>> = Object.freeze({
49
+ small: { criteria: 10, files: 5, words: 150 },
50
+ medium: { criteria: 20, files: 10, words: 300 },
51
+ });
52
+
53
+ export interface WorkItemSize {
54
+ criteria: number;
55
+ files: number;
56
+ words: number;
57
+ }
58
+
59
+ const HEADING = /^\s*#/;
60
+ const PATH_LIKE = /^[\w@.-]+(\/[\w@.*-]+)+\/?$|^[\w@-][\w@.-]*\.[a-z]{1,5}$/i;
61
+ const str = (v: unknown): string => (typeof v === "string" ? v : "");
62
+
63
+ /**
64
+ * Measure a work item from its front matter (`data`) and Markdown body.
65
+ * `acceptanceField` is the work kind's `work.acceptance.field`, `acceptance`
66
+ * when it names none.
67
+ */
68
+ export function measureWorkItem(data: Record<string, unknown> | null, body: string, acceptanceField = "acceptance"): WorkItemSize {
69
+ const d = data ?? {};
70
+ const acceptance = Array.isArray(d[acceptanceField]) ? (d[acceptanceField] as unknown[]) : [];
71
+ const said = str(fieldOf(d, "source.ask.said")) || str(fieldOf(d, "source.intent.answer"));
72
+ const bodyLines = body.split("\n").filter((l) => !HEADING.test(l));
73
+ const criteriaText = acceptance.map((c) => (typeof c === "string" ? c : str((c as { text?: unknown } | null)?.text)));
74
+ const text = [said, ...bodyLines, ...criteriaText].join("\n");
75
+ const files = new Set([...text.matchAll(/`([^`\s]+)`/g)].map((m) => m[1]).filter((t) => PATH_LIKE.test(t)));
76
+ return { criteria: acceptance.length, files: files.size, words: text.split(/\s+/).filter(Boolean).length };
77
+ }
78
+
79
+ /** Whether `size` is within `limit`: each limit given is at least the measure. */
80
+ export function fitsTier(size: WorkItemSize, limit: TierLimit | undefined): boolean {
81
+ if (!limit) return false;
82
+ return (["criteria", "files", "words"] as const).every((k) => limit[k] === undefined || size[k] <= limit[k]!);
83
+ }
84
+
85
+ /**
86
+ * The slice-tier inputs' fields for a measured item: `criteria`, `files`,
87
+ * `words` and `fits_<tier>` for each tier `limits` names.
88
+ */
89
+ export function sizeFields(size: WorkItemSize, limits: Readonly<Record<string, TierLimit>> = DEFAULT_TIER_LIMITS): Record<string, number | boolean> {
90
+ const out: Record<string, number | boolean> = { criteria: size.criteria, files: size.files, words: size.words };
91
+ for (const [tier, limit] of Object.entries(limits)) out[`fits_${tier}`] = fitsTier(size, limit);
92
+ return out;
93
+ }
@@ -75,6 +75,9 @@
75
75
  },
76
76
  "graph": {
77
77
  "$ref": "#/$defs/graph"
78
+ },
79
+ "fields": {
80
+ "$ref": "#/$defs/fields"
78
81
  }
79
82
  },
80
83
  "patternProperties": {
@@ -123,6 +126,34 @@
123
126
  }
124
127
  },
125
128
  "additionalProperties": false
129
+ },
130
+ "anyJsonKey": {
131
+ "type": "object",
132
+ "description": "The directory passes when a file directly in it, whose name matches one of in, parses as JSON and has a value at one of pointers, each a JSON Pointer such as /scripts/start (#3151). A file that is not JSON is no evidence either way. Added by chant 0.103.0.",
133
+ "required": [
134
+ "in",
135
+ "pointers"
136
+ ],
137
+ "properties": {
138
+ "in": {
139
+ "type": "array",
140
+ "minItems": 1,
141
+ "uniqueItems": true,
142
+ "items": {
143
+ "$ref": "#/$defs/fileName"
144
+ }
145
+ },
146
+ "pointers": {
147
+ "type": "array",
148
+ "minItems": 1,
149
+ "uniqueItems": true,
150
+ "items": {
151
+ "type": "string",
152
+ "pattern": "^(/[^/]+)+$"
153
+ }
154
+ }
155
+ },
156
+ "additionalProperties": false
126
157
  }
127
158
  },
128
159
  "additionalProperties": false
@@ -154,6 +185,97 @@
154
185
  "^x-": true
155
186
  },
156
187
  "additionalProperties": false
188
+ },
189
+ "fields": {
190
+ "type": "object",
191
+ "description": "The fields a member entry of this kind may set under its own fields block, each with a type and, optionally, a default (#3151). chant workspace check fails a member whose fields name one the kind doesn't declare or give a value of the wrong type (WSP005), and chant workspace status --json prints every declared field with the defaults filled in. Added by chant 0.103.0; an older chant can't read a kinds file that uses it.",
192
+ "minProperties": 1,
193
+ "propertyNames": {
194
+ "$ref": "#/$defs/fieldName"
195
+ },
196
+ "additionalProperties": {
197
+ "oneOf": [
198
+ {
199
+ "$ref": "#/$defs/scalarField"
200
+ },
201
+ {
202
+ "$ref": "#/$defs/objectField"
203
+ }
204
+ ]
205
+ }
206
+ },
207
+ "fieldName": {
208
+ "type": "string",
209
+ "pattern": "^[a-z][A-Za-z0-9]{0,39}$"
210
+ },
211
+ "scalarField": {
212
+ "type": "object",
213
+ "required": [
214
+ "type",
215
+ "description"
216
+ ],
217
+ "properties": {
218
+ "type": {
219
+ "enum": [
220
+ "string",
221
+ "integer",
222
+ "boolean"
223
+ ]
224
+ },
225
+ "description": {
226
+ "type": "string",
227
+ "minLength": 1
228
+ },
229
+ "default": {
230
+ "oneOf": [
231
+ {
232
+ "type": "string"
233
+ },
234
+ {
235
+ "type": "integer"
236
+ },
237
+ {
238
+ "type": "boolean"
239
+ }
240
+ ],
241
+ "description": "The value a member that doesn't set the field gets. Its type is the field's. Without it the field reads as null when unset."
242
+ },
243
+ "pattern": {
244
+ "type": "string",
245
+ "minLength": 1,
246
+ "description": "For a string field: a regular expression the value must match."
247
+ }
248
+ },
249
+ "additionalProperties": false
250
+ },
251
+ "objectField": {
252
+ "type": "object",
253
+ "required": [
254
+ "type",
255
+ "description",
256
+ "properties"
257
+ ],
258
+ "properties": {
259
+ "type": {
260
+ "const": "object"
261
+ },
262
+ "description": {
263
+ "type": "string",
264
+ "minLength": 1
265
+ },
266
+ "properties": {
267
+ "type": "object",
268
+ "minProperties": 1,
269
+ "propertyNames": {
270
+ "$ref": "#/$defs/fieldName"
271
+ },
272
+ "additionalProperties": {
273
+ "$ref": "#/$defs/scalarField"
274
+ },
275
+ "description": "The object's own fields, each a string, integer or boolean field. A member sets any of them; the rest take their defaults."
276
+ }
277
+ },
278
+ "additionalProperties": false
157
279
  }
158
280
  }
159
281
  }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * `chant workspace lock` (#3173, ws-089): the working tree's write lock
3
+ * (`write-lock.ts`), seen and held from outside chant.
4
+ *
5
+ * - `lock` prints who holds it now, or that nobody does.
6
+ * - `lock acquire --holder <name> [--ttl <duration>]` takes it for a batch of
7
+ * writes across several chant calls, waiting for it as a write does, and
8
+ * prints the token. Each chant write of the batch runs with
9
+ * `CHANT_WRITE_LOCK=<token>` and goes ahead without waiting; every other
10
+ * writer waits. The lock lasts until it is released or the ttl passes
11
+ * (default 60s, at most 10m), after which any writer may break it.
12
+ * - `lock release --token <token>` gives it back.
13
+ *
14
+ * Each prints one JSON document (`write-lock.schema.json`); a refusal exits 1.
15
+ * The token is printed only to the caller that took the lock: `lock` never
16
+ * prints it, so holding the lock can't be taken over by reading who holds it.
17
+ */
18
+
19
+ import type { CommandContext } from "../cli/registry";
20
+ import { parseDuration } from "../op/duration";
21
+ import { readerVersion } from "./declaration";
22
+ import {
23
+ acquireBatchLock,
24
+ DEFAULT_BATCH_TTL_MS,
25
+ MAX_BATCH_TTL_MS,
26
+ releaseLock,
27
+ writeLockHolder,
28
+ writeLockPath,
29
+ WriteLockError,
30
+ type WriteLockHolder,
31
+ } from "./write-lock";
32
+ import { AGENT_ENV } from "./write-scope";
33
+
34
+ export const WRITE_LOCK_CONTRACT_VERSION = 1;
35
+ export const WRITE_LOCK_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/write-lock/v1/write-lock.schema.json";
36
+
37
+ /** Why a lock command did nothing. Closed. */
38
+ export const WRITE_LOCK_ERROR_CODES = ["write-usage-invalid", "write-lock-timeout", "write-lock-not-held"] as const;
39
+ export type WriteLockErrorCode = (typeof WRITE_LOCK_ERROR_CODES)[number];
40
+
41
+ /** A holder as the documents show it: everything but the token. */
42
+ export type HolderView = Omit<WriteLockHolder, "token">;
43
+
44
+ export type WriteLockDocument =
45
+ | {
46
+ $schema: string;
47
+ contract: number;
48
+ chant: string;
49
+ verb: "status" | "acquire" | "release";
50
+ /** The lock directory. */
51
+ path: string;
52
+ /** Who holds the lock after the command: the batch for acquire, null after release. */
53
+ holder: HolderView | null;
54
+ /** acquire only: the token each write of the batch runs with, as CHANT_WRITE_LOCK. */
55
+ token?: string;
56
+ }
57
+ | { $schema: string; contract: number; chant: string; verb: "status" | "acquire" | "release"; error: { code: WriteLockErrorCode; message: string }; holder: HolderView | null };
58
+
59
+ const USAGE = "chant workspace lock [acquire --holder <name> [--ttl <duration>] | release --token <token>] [--json]";
60
+
61
+ function view(h: WriteLockHolder | null): HolderView | null {
62
+ if (h === null) return null;
63
+ const { token: _token, ...rest } = h;
64
+ return rest;
65
+ }
66
+
67
+ /** Run one lock command and build its document. */
68
+ export async function workspaceLock(req: { cwd: string; verb: string | undefined; holder?: string; ttl?: string; token?: string; agent?: string }): Promise<WriteLockDocument> {
69
+ const verb = req.verb === undefined || req.verb === "status" ? "status" : req.verb === "acquire" || req.verb === "release" ? req.verb : null;
70
+ const head = { $schema: WRITE_LOCK_SCHEMA_ID, contract: WRITE_LOCK_CONTRACT_VERSION, chant: readerVersion() };
71
+ const fail = (v: "status" | "acquire" | "release", code: WriteLockErrorCode, message: string, holder: WriteLockHolder | null = null): WriteLockDocument => ({
72
+ ...head,
73
+ verb: v,
74
+ error: { code, message: code === "write-usage-invalid" ? `${message}\n${USAGE}` : message },
75
+ holder: view(holder),
76
+ });
77
+ if (verb === null) return fail("status", "write-usage-invalid", `lock takes acquire or release, or nothing to say who holds it, not ${req.verb}`);
78
+ if (verb === "status") {
79
+ if (req.holder !== undefined || req.ttl !== undefined || req.token !== undefined) return fail(verb, "write-usage-invalid", "lock with no verb only says who holds the lock, and takes no --holder, --ttl or --token");
80
+ const { path, holder } = writeLockHolder(req.cwd);
81
+ return { ...head, verb, path, holder: view(holder) };
82
+ }
83
+ if (verb === "acquire") {
84
+ if (!req.holder) return fail(verb, "write-usage-invalid", "lock acquire needs --holder <name>: who holds the lock, as the other writers waiting on it are told");
85
+ if (req.token !== undefined) return fail(verb, "write-usage-invalid", "lock acquire hands out a token, and takes none");
86
+ let ttlMs = DEFAULT_BATCH_TTL_MS;
87
+ if (req.ttl !== undefined) {
88
+ const ms = /^\d+$/.test(req.ttl) ? Number(req.ttl) * 1000 : parseDurationOrNull(req.ttl);
89
+ if (ms === null || ms <= 0 || ms > MAX_BATCH_TTL_MS) return fail(verb, "write-usage-invalid", `--ttl takes a duration up to ${MAX_BATCH_TTL_MS / 60_000}m, such as 90s or 5m, not ${JSON.stringify(req.ttl)}`);
90
+ ttlMs = ms;
91
+ }
92
+ try {
93
+ const { path, holder } = await acquireBatchLock(req.cwd, { verb: "batch", by: req.holder, agent: req.agent ?? null }, ttlMs);
94
+ return { ...head, verb, path, holder: view(holder), token: holder.token };
95
+ } catch (err) {
96
+ if (err instanceof WriteLockError) return fail(verb, err.code, err.message, err.holder);
97
+ throw err;
98
+ }
99
+ }
100
+ if (!req.token) return fail(verb, "write-usage-invalid", "lock release needs --token <token>, as lock acquire printed it");
101
+ if (req.holder !== undefined || req.ttl !== undefined) return fail(verb, "write-usage-invalid", "lock release takes only --token");
102
+ const path = writeLockPath(req.cwd);
103
+ if (!releaseLock(path, req.token)) {
104
+ const { holder } = writeLockHolder(req.cwd);
105
+ return fail(verb, "write-lock-not-held", `the token given does not hold the write lock (${holder ? "another writer holds it" : "nobody holds it"}): it was released already, or it expired`, holder);
106
+ }
107
+ return { ...head, verb, path, holder: null };
108
+ }
109
+
110
+ function parseDurationOrNull(raw: string): number | null {
111
+ try {
112
+ return parseDuration(raw);
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ /** `chant workspace lock [acquire|release]`. Prints one JSON document; exits 1 on a refusal. */
119
+ export async function runWorkspaceLock(ctx: CommandContext): Promise<number> {
120
+ const { args } = ctx;
121
+ const doc = await workspaceLock({
122
+ cwd: process.cwd(),
123
+ verb: args.extraPositional,
124
+ holder: args.holder,
125
+ ttl: args.ttl,
126
+ token: args.token,
127
+ agent: process.env[AGENT_ENV] || undefined,
128
+ });
129
+ console.log(JSON.stringify(doc, null, 2));
130
+ return "error" in doc ? 1 : 0;
131
+ }
@@ -0,0 +1,172 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://intentius.io/chant/schemas/workspace/write-lock/v1/write-lock.schema.json",
4
+ "title": "chant workspace lock output",
5
+ "description": "What `chant workspace lock [acquire --holder <name> [--ttl <duration>] | release --token <token>]` prints (#3173, ws-089): who holds the working tree's write lock, the token of a batch that took it, or the reason the command did nothing. Every chant write to the working tree (records new, amend, review and close, points ask and answer, box listing set) holds this lock from its first read to its last write. A batch holds it across chant calls: each call runs with CHANT_WRITE_LOCK=<token> and goes ahead without waiting while the token holds it. Readers ignore fields they do not know; a field is only ever added within a version. The error codes are a closed list, each in the one closed list of `reason-codes.ts`.",
6
+ "oneOf": [
7
+ {
8
+ "$ref": "#/$defs/result"
9
+ },
10
+ {
11
+ "$ref": "#/$defs/failure"
12
+ }
13
+ ],
14
+ "$defs": {
15
+ "result": {
16
+ "type": "object",
17
+ "description": "Exit code 0.",
18
+ "required": [
19
+ "$schema",
20
+ "contract",
21
+ "chant",
22
+ "verb",
23
+ "path",
24
+ "holder"
25
+ ],
26
+ "properties": {
27
+ "$schema": {
28
+ "const": "https://intentius.io/chant/schemas/workspace/write-lock/v1/write-lock.schema.json"
29
+ },
30
+ "contract": {
31
+ "const": 1
32
+ },
33
+ "chant": {
34
+ "type": "string",
35
+ "description": "The chant version that printed it."
36
+ },
37
+ "verb": {
38
+ "enum": [
39
+ "status",
40
+ "acquire",
41
+ "release"
42
+ ]
43
+ },
44
+ "path": {
45
+ "type": "string",
46
+ "description": "The lock directory: chant-write.lock in the working tree's git directory, or a directory under the system's temporary directory outside git."
47
+ },
48
+ "holder": {
49
+ "$ref": "#/$defs/holder"
50
+ },
51
+ "token": {
52
+ "type": "string",
53
+ "pattern": "^[0-9a-f]{32}$",
54
+ "description": "acquire only: the token each write of the batch runs with, as CHANT_WRITE_LOCK, and lock release takes. Never printed by status."
55
+ },
56
+ "error": false
57
+ }
58
+ },
59
+ "failure": {
60
+ "type": "object",
61
+ "description": "Nothing was done. Exit code 1.",
62
+ "required": [
63
+ "$schema",
64
+ "contract",
65
+ "chant",
66
+ "verb",
67
+ "error",
68
+ "holder"
69
+ ],
70
+ "properties": {
71
+ "$schema": {
72
+ "const": "https://intentius.io/chant/schemas/workspace/write-lock/v1/write-lock.schema.json"
73
+ },
74
+ "contract": {
75
+ "const": 1
76
+ },
77
+ "chant": {
78
+ "type": "string"
79
+ },
80
+ "verb": {
81
+ "enum": [
82
+ "status",
83
+ "acquire",
84
+ "release"
85
+ ]
86
+ },
87
+ "error": {
88
+ "type": "object",
89
+ "required": [
90
+ "code",
91
+ "message"
92
+ ],
93
+ "properties": {
94
+ "code": {
95
+ "enum": [
96
+ "write-usage-invalid",
97
+ "write-lock-timeout",
98
+ "write-lock-not-held"
99
+ ]
100
+ },
101
+ "message": {
102
+ "type": "string"
103
+ }
104
+ }
105
+ },
106
+ "holder": {
107
+ "$ref": "#/$defs/holder"
108
+ },
109
+ "token": false
110
+ }
111
+ },
112
+ "holder": {
113
+ "description": "Who holds the lock: everything owner.json in the lock directory says but the token.",
114
+ "oneOf": [
115
+ {
116
+ "type": "null"
117
+ },
118
+ {
119
+ "type": "object",
120
+ "required": [
121
+ "pid",
122
+ "host",
123
+ "verb",
124
+ "by",
125
+ "agent",
126
+ "acquiredAt",
127
+ "expiresAt"
128
+ ],
129
+ "properties": {
130
+ "pid": {
131
+ "type": [
132
+ "integer",
133
+ "null"
134
+ ],
135
+ "description": "The process holding it, or null for a batch held across chant calls."
136
+ },
137
+ "host": {
138
+ "type": "string",
139
+ "description": "The host the holder runs on."
140
+ },
141
+ "verb": {
142
+ "type": "string",
143
+ "description": "What holds it: a write command, such as records amend, or batch."
144
+ },
145
+ "by": {
146
+ "type": [
147
+ "string",
148
+ "null"
149
+ ],
150
+ "description": "Who the write or batch names: --by, or lock acquire's --holder."
151
+ },
152
+ "agent": {
153
+ "type": [
154
+ "string",
155
+ "null"
156
+ ],
157
+ "description": "The agent session (CHANT_AGENT), or null."
158
+ },
159
+ "acquiredAt": {
160
+ "type": "string",
161
+ "description": "When it was taken, as an ISO 8601 time."
162
+ },
163
+ "expiresAt": {
164
+ "type": "string",
165
+ "description": "When any writer may break it, as an ISO 8601 time."
166
+ }
167
+ }
168
+ }
169
+ ]
170
+ }
171
+ }
172
+ }