@intentius/chant 0.124.13 → 0.124.14

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 (907) hide show
  1. package/bin/chant +11 -0
  2. package/dist/agents/checks.js +326 -0
  3. package/dist/agents/discover.js +885 -0
  4. package/dist/agents/importer.js +17 -0
  5. package/dist/agents/index.js +12 -0
  6. package/dist/agents/types.js +19 -0
  7. package/dist/apply.js +151 -0
  8. package/dist/attrref.js +70 -0
  9. package/dist/audit/catalog.js +350 -0
  10. package/dist/audit/core.js +252 -0
  11. package/dist/audit/discover.js +633 -0
  12. package/dist/audit/fetch.js +666 -0
  13. package/dist/audit/lineage.js +91 -0
  14. package/dist/audit/nginx.js +366 -0
  15. package/dist/audit/prior-art.js +132 -0
  16. package/dist/audit/proof.js +321 -0
  17. package/dist/audit/report-html.js +194 -0
  18. package/dist/audit/report-model.js +198 -0
  19. package/dist/audit/report.js +102 -0
  20. package/dist/audit/rules-doc.js +144 -0
  21. package/dist/audit/secrets.js +405 -0
  22. package/dist/audit/terraform-state.js +140 -0
  23. package/dist/audit/wrangler.js +262 -0
  24. package/dist/behaviour-delta.js +401 -0
  25. package/dist/behaviour-engine.js +357 -0
  26. package/dist/behaviour-graph.js +83 -0
  27. package/dist/behaviour-http.js +188 -0
  28. package/dist/behaviour-kinds.js +172 -0
  29. package/dist/behaviour-overlay.js +50 -0
  30. package/dist/behaviour-predict.js +188 -0
  31. package/dist/behaviour-request.js +194 -0
  32. package/dist/behaviour.js +1325 -0
  33. package/dist/build-params.js +108 -0
  34. package/dist/build.js +668 -0
  35. package/dist/builder.js +44 -0
  36. package/dist/cdk/advise.js +30 -0
  37. package/dist/cdk/assembly.js +122 -0
  38. package/dist/cdk/graph.js +444 -0
  39. package/dist/cdk/tier-map.js +64 -0
  40. package/dist/cdk/types.js +24 -0
  41. package/dist/change-set.js +312 -0
  42. package/dist/child-project.js +21 -0
  43. package/dist/claimed-fields.js +135 -0
  44. package/dist/cli/build-options.js +70 -0
  45. package/dist/cli/build-params-cli.js +139 -0
  46. package/dist/cli/command-group.js +123 -0
  47. package/dist/cli/commands/audit-agents.js +326 -0
  48. package/dist/cli/commands/audit.js +571 -0
  49. package/dist/cli/commands/build.js +920 -0
  50. package/dist/cli/commands/carve-apply.js +153 -0
  51. package/dist/cli/commands/carve-bridge.js +201 -0
  52. package/dist/cli/commands/carve-emit.js +332 -0
  53. package/dist/cli/commands/carve-status.js +119 -0
  54. package/dist/cli/commands/carve.js +204 -0
  55. package/dist/cli/commands/check-lexicon-docs.js +79 -0
  56. package/dist/cli/commands/check-lexicon-examples.js +177 -0
  57. package/dist/cli/commands/check-lexicon-intrinsics.js +291 -0
  58. package/dist/cli/commands/check-lexicon-mcp.js +82 -0
  59. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  60. package/dist/cli/commands/check-lexicon-plugin.js +104 -0
  61. package/dist/cli/commands/check-lexicon.js +766 -0
  62. package/dist/cli/commands/describe.js +85 -0
  63. package/dist/cli/commands/diff.js +175 -0
  64. package/dist/cli/commands/doctor.js +350 -0
  65. package/dist/cli/commands/import-agents.js +155 -0
  66. package/dist/cli/commands/import.js +670 -0
  67. package/dist/cli/commands/init-lexicon/templates/codegen.js +194 -0
  68. package/dist/cli/commands/init-lexicon/templates/docs.js +100 -0
  69. package/dist/cli/commands/init-lexicon/templates/examples.js +29 -0
  70. package/dist/cli/commands/init-lexicon/templates/lint.js +34 -0
  71. package/dist/cli/commands/init-lexicon/templates/lsp.js +37 -0
  72. package/dist/cli/commands/init-lexicon/templates/plugin.js +108 -0
  73. package/dist/cli/commands/init-lexicon/templates/project.js +240 -0
  74. package/dist/cli/commands/init-lexicon/templates/spec.js +56 -0
  75. package/dist/cli/commands/init-lexicon/templates/tests.js +66 -0
  76. package/dist/cli/commands/init-lexicon.js +172 -0
  77. package/dist/cli/commands/init.js +381 -0
  78. package/dist/cli/commands/lexicon-rolling-upgrade.js +145 -0
  79. package/dist/cli/commands/lexicon-surface-diff.js +145 -0
  80. package/dist/cli/commands/licenses.js +125 -0
  81. package/dist/cli/commands/lint.d.ts.map +1 -1
  82. package/dist/cli/commands/lint.js +948 -0
  83. package/dist/cli/commands/list.js +63 -0
  84. package/dist/cli/commands/migrate.d.ts.map +1 -1
  85. package/dist/cli/commands/migrate.js +423 -0
  86. package/dist/cli/commands/onboard.js +403 -0
  87. package/dist/cli/commands/pinned-upgrade.js +191 -0
  88. package/dist/cli/commands/update.js +194 -0
  89. package/dist/cli/commands/vendor.js +217 -0
  90. package/dist/cli/conflict-check.js +104 -0
  91. package/dist/cli/debug.js +8 -0
  92. package/dist/cli/format.js +104 -0
  93. package/dist/cli/handlers/build.js +267 -0
  94. package/dist/cli/handlers/carve-apply.js +30 -0
  95. package/dist/cli/handlers/carve-bridge.js +28 -0
  96. package/dist/cli/handlers/carve-emit.js +50 -0
  97. package/dist/cli/handlers/carve-status.js +26 -0
  98. package/dist/cli/handlers/carve.js +40 -0
  99. package/dist/cli/handlers/change-set.js +46 -0
  100. package/dist/cli/handlers/components.js +730 -0
  101. package/dist/cli/handlers/dev.js +113 -0
  102. package/dist/cli/handlers/emulator.js +233 -0
  103. package/dist/cli/handlers/explain.js +54 -0
  104. package/dist/cli/handlers/fan-out.js +422 -0
  105. package/dist/cli/handlers/gate-resume.js +275 -0
  106. package/dist/cli/handlers/generate.js +84 -0
  107. package/dist/cli/handlers/graph.js +969 -0
  108. package/dist/cli/handlers/init.js +102 -0
  109. package/dist/cli/handlers/lifecycle.js +1727 -0
  110. package/dist/cli/handlers/lint.js +62 -0
  111. package/dist/cli/handlers/migrate.js +48 -0
  112. package/dist/cli/handlers/misc.js +391 -0
  113. package/dist/cli/handlers/operator.js +1162 -0
  114. package/dist/cli/handlers/override-approve.js +102 -0
  115. package/dist/cli/handlers/pr.js +503 -0
  116. package/dist/cli/handlers/promote.js +342 -0
  117. package/dist/cli/handlers/run-generate.js +206 -0
  118. package/dist/cli/handlers/run-wave.js +66 -0
  119. package/dist/cli/handlers/run.js +1191 -0
  120. package/dist/cli/handlers/scenario.js +325 -0
  121. package/dist/cli/handlers/search.js +924 -0
  122. package/dist/cli/handlers/serve.js +32 -0
  123. package/dist/cli/handlers/vendor.js +154 -0
  124. package/dist/cli/index.js +3 -0
  125. package/dist/cli/is-entry-point.js +23 -0
  126. package/dist/cli/lsp/capabilities.js +28 -0
  127. package/dist/cli/lsp/diagnostics.js +34 -0
  128. package/dist/cli/lsp/server.js +329 -0
  129. package/dist/cli/main.d.ts.map +1 -1
  130. package/dist/cli/main.js +2540 -0
  131. package/dist/cli/mcp/lifecycle-tools.js +80 -0
  132. package/dist/cli/mcp/op-tools.js +306 -0
  133. package/dist/cli/mcp/resource-handlers.js +258 -0
  134. package/dist/cli/mcp/resources/context.js +85 -0
  135. package/dist/cli/mcp/server.js +353 -0
  136. package/dist/cli/mcp/tools/build.js +111 -0
  137. package/dist/cli/mcp/tools/composites.js +95 -0
  138. package/dist/cli/mcp/tools/explain.js +166 -0
  139. package/dist/cli/mcp/tools/import.js +49 -0
  140. package/dist/cli/mcp/tools/lint.js +41 -0
  141. package/dist/cli/mcp/tools/scaffold.js +111 -0
  142. package/dist/cli/mcp/tools/search.js +112 -0
  143. package/dist/cli/mcp/types.js +1 -0
  144. package/dist/cli/mcp/workspace-plugins.js +91 -0
  145. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  146. package/dist/cli/mcp/workspace-tools.js +564 -0
  147. package/dist/cli/mcp-config.js +70 -0
  148. package/dist/cli/plugins.js +313 -0
  149. package/dist/cli/registry.js +23 -0
  150. package/dist/cli/reporters/stylish.js +329 -0
  151. package/dist/cli/version.js +15 -0
  152. package/dist/cli/watch.js +79 -0
  153. package/dist/codegen/bound-property-types.js +82 -0
  154. package/dist/codegen/coverage.js +119 -0
  155. package/dist/codegen/docs-file-markers.js +60 -0
  156. package/dist/codegen/docs-pages.js +115 -0
  157. package/dist/codegen/docs-rule-scanning.js +164 -0
  158. package/dist/codegen/docs-rules.js +76 -0
  159. package/dist/codegen/docs-sections.js +117 -0
  160. package/dist/codegen/docs-sidebar.js +65 -0
  161. package/dist/codegen/docs-types.js +4 -0
  162. package/dist/codegen/docs.js +356 -0
  163. package/dist/codegen/fetch.js +340 -0
  164. package/dist/codegen/generate-post-synth-barrel.js +63 -0
  165. package/dist/codegen/generate-registry.js +71 -0
  166. package/dist/codegen/generate-runtime-index.js +56 -0
  167. package/dist/codegen/generate-typescript.js +119 -0
  168. package/dist/codegen/generate.js +115 -0
  169. package/dist/codegen/json-patch.js +141 -0
  170. package/dist/codegen/json-schema-to-ts.js +344 -0
  171. package/dist/codegen/json-schema.js +246 -0
  172. package/dist/codegen/lexicon-regen.js +412 -0
  173. package/dist/codegen/naming.js +217 -0
  174. package/dist/codegen/okf-lexicon.js +245 -0
  175. package/dist/codegen/package.js +150 -0
  176. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  177. package/dist/codegen/pinned-upgrade.js +494 -0
  178. package/dist/codegen/registry.js +60 -0
  179. package/dist/codegen/rehype-base-url.mjs +68 -0
  180. package/dist/codegen/rolling-upgrade.js +217 -0
  181. package/dist/codegen/surface-snapshot.js +537 -0
  182. package/dist/codegen/testdata/postgres-tags.json +242 -0
  183. package/dist/codegen/topo-sort.js +36 -0
  184. package/dist/codegen/typecheck.js +115 -0
  185. package/dist/codegen/validate.js +200 -0
  186. package/dist/codegen/version-bump.js +35 -0
  187. package/dist/components/auto-release.js +143 -0
  188. package/dist/components/builders.js +47 -0
  189. package/dist/components/capability-plugin-loader.js +175 -0
  190. package/dist/components/capability-plugin-schema.js +46 -0
  191. package/dist/components/capability-plugin.js +99 -0
  192. package/dist/components/capability.js +60 -0
  193. package/dist/components/changed-files.js +72 -0
  194. package/dist/components/cli-support.js +427 -0
  195. package/dist/components/component.js +120 -0
  196. package/dist/components/component.schema.json +271 -0
  197. package/dist/components/config-defaults.js +130 -0
  198. package/dist/components/deploy-units.js +60 -0
  199. package/dist/components/discover.d.ts.map +1 -1
  200. package/dist/components/discover.js +189 -0
  201. package/dist/components/driver-output.js +86 -0
  202. package/dist/components/driver.js +728 -0
  203. package/dist/components/fan-out-output.js +165 -0
  204. package/dist/components/fan-out-record.js +94 -0
  205. package/dist/components/fan-out-run.js +373 -0
  206. package/dist/components/fan-out-support.js +60 -0
  207. package/dist/components/fan-out.js +273 -0
  208. package/dist/components/gate-plan.js +41 -0
  209. package/dist/components/gated-wave-pipeline.js +46 -0
  210. package/dist/components/index.js +24 -0
  211. package/dist/components/layers.js +28 -0
  212. package/dist/components/manifest-persistence.js +108 -0
  213. package/dist/components/pilots/alb-ecs.pilot.js +59 -0
  214. package/dist/components/pilots/dynamodb.pilot.js +51 -0
  215. package/dist/components/pilots/jar-emr.pilot.js +65 -0
  216. package/dist/components/pilots/lambda.pilot.js +52 -0
  217. package/dist/components/pilots/neo4j-fanout.pilot.js +68 -0
  218. package/dist/components/pilots/project.js +12 -0
  219. package/dist/components/pr-pipeline.js +134 -0
  220. package/dist/components/pr-run.js +363 -0
  221. package/dist/components/presets/ecs-fargate.js +72 -0
  222. package/dist/components/presets/index.js +28 -0
  223. package/dist/components/presets/lambda.js +34 -0
  224. package/dist/components/presets/single-host-compose.js +41 -0
  225. package/dist/components/promote.js +549 -0
  226. package/dist/components/registry.js +34 -0
  227. package/dist/components/run-progress.js +33 -0
  228. package/dist/components/sandbox/driver.d.ts.map +1 -1
  229. package/dist/components/sandbox/driver.js +93 -0
  230. package/dist/components/sandbox/run.js +115 -0
  231. package/dist/components/starter-plugin.js +104 -0
  232. package/dist/components/verbs/__tests__/mock-cloud-executor.js +68 -0
  233. package/dist/components/verbs/__tests__/mock-process-runner.js +45 -0
  234. package/dist/components/verbs/__tests__/mock-sbom-generator.js +56 -0
  235. package/dist/components/verbs/bom-writer.js +287 -0
  236. package/dist/components/verbs/build-archive.js +188 -0
  237. package/dist/components/verbs/build.js +142 -0
  238. package/dist/components/verbs/cloud-executor.js +110 -0
  239. package/dist/components/verbs/component-bom.js +125 -0
  240. package/dist/components/verbs/ensure-secret.js +50 -0
  241. package/dist/components/verbs/index.js +61 -0
  242. package/dist/components/verbs/license-policy.js +75 -0
  243. package/dist/components/verbs/lockfile-sbom-generator.js +191 -0
  244. package/dist/components/verbs/process-runner.js +78 -0
  245. package/dist/components/verbs/r2-sync.js +93 -0
  246. package/dist/components/verbs/reproducibility.js +62 -0
  247. package/dist/components/verbs/run-agent.js +577 -0
  248. package/dist/components/verbs/sbom-generator.js +119 -0
  249. package/dist/components/verbs/sbom.js +97 -0
  250. package/dist/components/verbs/shell.js +32 -0
  251. package/dist/components/verbs/sign.js +221 -0
  252. package/dist/components/verbs/stub.js +28 -0
  253. package/dist/components/verbs/tool-sbom-generator.js +154 -0
  254. package/dist/components/verbs/verify.js +112 -0
  255. package/dist/components/verbs/vex.js +131 -0
  256. package/dist/components/verbs/vuln-gate.js +152 -0
  257. package/dist/components/verbs/vuln-scan.js +288 -0
  258. package/dist/components/verbs/wait-verify.js +106 -0
  259. package/dist/components/verbs/wrangler.js +179 -0
  260. package/dist/components/wave-plan.js +102 -0
  261. package/dist/composite.js +305 -0
  262. package/dist/config-import.d.ts.map +1 -1
  263. package/dist/config-import.js +22 -0
  264. package/dist/config-sandbox.js +122 -0
  265. package/dist/config-static.js +206 -0
  266. package/dist/config.js +605 -0
  267. package/dist/content-digest.js +23 -0
  268. package/dist/declarable.js +114 -0
  269. package/dist/deep-observation.js +437 -0
  270. package/dist/detectLexicon.js +54 -0
  271. package/dist/discovery/cache.js +70 -0
  272. package/dist/discovery/collect.js +208 -0
  273. package/dist/discovery/cycles.js +95 -0
  274. package/dist/discovery/entity-wire-codec.js +391 -0
  275. package/dist/discovery/entity-wire.js +42 -0
  276. package/dist/discovery/files.js +121 -0
  277. package/dist/discovery/fold-import.d.ts.map +1 -1
  278. package/dist/discovery/fold-import.js +4178 -0
  279. package/dist/discovery/fold-rank.js +292 -0
  280. package/dist/discovery/graph.js +115 -0
  281. package/dist/discovery/import.d.ts.map +1 -1
  282. package/dist/discovery/import.js +65 -0
  283. package/dist/discovery/index.js +333 -0
  284. package/dist/discovery/param-deps.js +487 -0
  285. package/dist/discovery/resolve.js +39 -0
  286. package/dist/discovery/sandbox/bundle.js +214 -0
  287. package/dist/discovery/sandbox/child-errors.js +26 -0
  288. package/dist/discovery/sandbox/config-run.js +100 -0
  289. package/dist/discovery/sandbox/config-wire.js +183 -0
  290. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  291. package/dist/discovery/sandbox/driver.js +333 -0
  292. package/dist/discovery/sandbox/fork.js +229 -0
  293. package/dist/discovery/sandbox/policy-run.js +142 -0
  294. package/dist/discovery/sandbox/policy-wire.js +152 -0
  295. package/dist/discovery/sandbox/run.js +77 -0
  296. package/dist/discovery/specifier-resolve.js +320 -0
  297. package/dist/discovery/walk.js +284 -0
  298. package/dist/effect-receipt.js +340 -0
  299. package/dist/env.js +55 -0
  300. package/dist/errors.js +62 -0
  301. package/dist/fold/fold.js +1434 -0
  302. package/dist/fold/foldable-helpers.js +229 -0
  303. package/dist/fold/subset.js +696 -0
  304. package/dist/fold-provenance.js +414 -0
  305. package/dist/gate-decision.js +260 -0
  306. package/dist/gate-resume-run.js +154 -0
  307. package/dist/gated-waves.js +165 -0
  308. package/dist/governance.js +43 -0
  309. package/dist/graph-declared.js +45 -0
  310. package/dist/graph-detail.js +207 -0
  311. package/dist/graph-dot.js +54 -0
  312. package/dist/graph-effective.js +107 -0
  313. package/dist/graph-ir.js +623 -0
  314. package/dist/graph-layout.js +184 -0
  315. package/dist/graph-lens.js +105 -0
  316. package/dist/graph-mermaid.js +83 -0
  317. package/dist/graph-ops.js +39 -0
  318. package/dist/graph-refs.js +156 -0
  319. package/dist/held-elsewhere.js +87 -0
  320. package/dist/identity.js +251 -0
  321. package/dist/import/base-parser.js +34 -0
  322. package/dist/import/embedded.js +255 -0
  323. package/dist/import/generator.js +1 -0
  324. package/dist/import/ir-utils.js +85 -0
  325. package/dist/import/parser-options.js +45 -0
  326. package/dist/import/parser.js +1 -0
  327. package/dist/index.js +118 -0
  328. package/dist/intrinsic-interpolation.js +73 -0
  329. package/dist/intrinsic.js +31 -0
  330. package/dist/join-key.js +35 -0
  331. package/dist/kubectl-context.js +150 -0
  332. package/dist/lexicon-config.js +58 -0
  333. package/dist/lexicon-integrity.js +47 -0
  334. package/dist/lexicon-manifest.js +70 -0
  335. package/dist/lexicon-module.d.ts.map +1 -1
  336. package/dist/lexicon-module.js +241 -0
  337. package/dist/lexicon-output.js +218 -0
  338. package/dist/lexicon-plugin-helpers.js +104 -0
  339. package/dist/lexicon-schema.js +136 -0
  340. package/dist/lexicon.js +130 -0
  341. package/dist/lifecycle/affected.js +224 -0
  342. package/dist/lifecycle/assert-live.js +110 -0
  343. package/dist/lifecycle/build-ledger-store.js +160 -0
  344. package/dist/lifecycle/build-ledger.js +155 -0
  345. package/dist/lifecycle/change-set.js +356 -0
  346. package/dist/lifecycle/converge-ledger.js +161 -0
  347. package/dist/lifecycle/deep-diff.js +233 -0
  348. package/dist/lifecycle/deep-observe.js +147 -0
  349. package/dist/lifecycle/digest.js +143 -0
  350. package/dist/lifecycle/disruption.js +135 -0
  351. package/dist/lifecycle/gate-applied.js +113 -0
  352. package/dist/lifecycle/gate-audit.js +341 -0
  353. package/dist/lifecycle/gate-ledger-select.js +89 -0
  354. package/dist/lifecycle/gate-ledger.js +193 -0
  355. package/dist/lifecycle/gate-origin.js +117 -0
  356. package/dist/lifecycle/gate-pending.js +58 -0
  357. package/dist/lifecycle/git.js +1182 -0
  358. package/dist/lifecycle/identity.js +58 -0
  359. package/dist/lifecycle/index.js +26 -0
  360. package/dist/lifecycle/lease.js +283 -0
  361. package/dist/lifecycle/ledger-port.js +1 -0
  362. package/dist/lifecycle/legacy-digest.js +91 -0
  363. package/dist/lifecycle/live-diff.js +274 -0
  364. package/dist/lifecycle/local-note.js +15 -0
  365. package/dist/lifecycle/member-ledger.js +122 -0
  366. package/dist/lifecycle/observation-baseline.js +155 -0
  367. package/dist/lifecycle/observe.js +375 -0
  368. package/dist/lifecycle/oras-referrer-lookup.js +82 -0
  369. package/dist/lifecycle/override-ledger.js +54 -0
  370. package/dist/lifecycle/plan-digest.js +110 -0
  371. package/dist/lifecycle/plan-ledger.js +129 -0
  372. package/dist/lifecycle/receipt-plan.js +192 -0
  373. package/dist/lifecycle/release-ledger.js +155 -0
  374. package/dist/lifecycle/release-read.js +64 -0
  375. package/dist/lifecycle/replay.js +193 -0
  376. package/dist/lifecycle/rollback.js +102 -0
  377. package/dist/lifecycle/run-ledger.js +183 -0
  378. package/dist/lifecycle/scenario-eval.js +204 -0
  379. package/dist/lifecycle/scenario.js +214 -0
  380. package/dist/lifecycle/snapshot.js +239 -0
  381. package/dist/lifecycle/status.js +447 -0
  382. package/dist/lifecycle/symptoms.js +117 -0
  383. package/dist/lifecycle/teardown.js +255 -0
  384. package/dist/lifecycle/types.js +1 -0
  385. package/dist/lifecycle/unobserved-gate.js +33 -0
  386. package/dist/lifecycle/wave-plans.js +68 -0
  387. package/dist/lifecycle/work-item-id.js +36 -0
  388. package/dist/lifecycle/work-lease.js +393 -0
  389. package/dist/lint/component-checks.js +87 -0
  390. package/dist/lint/config.js +483 -0
  391. package/dist/lint/declarative.js +122 -0
  392. package/dist/lint/discover.d.ts.map +1 -1
  393. package/dist/lint/discover.js +138 -0
  394. package/dist/lint/engine.js +246 -0
  395. package/dist/lint/knowledge-checks.js +64 -0
  396. package/dist/lint/named-checks.js +22 -0
  397. package/dist/lint/output-checks.js +87 -0
  398. package/dist/lint/output-docs.js +163 -0
  399. package/dist/lint/parser.js +33 -0
  400. package/dist/lint/pipeline-change-gate.js +70 -0
  401. package/dist/lint/policy-import.d.ts +1 -1
  402. package/dist/lint/policy-import.d.ts.map +1 -1
  403. package/dist/lint/policy-import.js +62 -0
  404. package/dist/lint/policy-sandbox.js +86 -0
  405. package/dist/lint/policy.js +122 -0
  406. package/dist/lint/post-synth.js +87 -0
  407. package/dist/lint/presets/relaxed.json +26 -0
  408. package/dist/lint/presets/strict.json +24 -24
  409. package/dist/lint/receipt-checks.js +83 -0
  410. package/dist/lint/rule-loader.d.ts.map +1 -1
  411. package/dist/lint/rule-loader.js +89 -0
  412. package/dist/lint/rule-registry.js +86 -0
  413. package/dist/lint/rule.js +1 -0
  414. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.js +7 -0
  415. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.js +7 -0
  416. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.js +9 -0
  417. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.js +11 -0
  418. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.js +9 -0
  419. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.js +25 -0
  420. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.js +8 -0
  421. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.js +21 -0
  422. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.js +8 -0
  423. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.js +24 -0
  424. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.js +17 -0
  425. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.js +8 -0
  426. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.js +18 -0
  427. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.js +20 -0
  428. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.js +8 -0
  429. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.js +12 -0
  430. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.js +16 -0
  431. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.js +8 -0
  432. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.js +12 -0
  433. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.js +11 -0
  434. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.js +11 -0
  435. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.js +10 -0
  436. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.js +16 -0
  437. package/dist/lint/rules/comp/comp001-publishes-never-applies.js +69 -0
  438. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.js +84 -0
  439. package/dist/lint/rules/comp/comp003-mutating-no-rollback.js +103 -0
  440. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.js +70 -0
  441. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.js +109 -0
  442. package/dist/lint/rules/comp/comp006-shell-needs-reason.js +49 -0
  443. package/dist/lint/rules/comp/comp007-composition-sprawl.js +64 -0
  444. package/dist/lint/rules/comp/index.js +33 -0
  445. package/dist/lint/rules/comp/support.js +129 -0
  446. package/dist/lint/rules/composite-scope.js +51 -0
  447. package/dist/lint/rules/cor017-composite-name-match.js +93 -0
  448. package/dist/lint/rules/cor018-composite-prefer-lexicon-type.js +136 -0
  449. package/dist/lint/rules/cor021-env-literal-name.js +100 -0
  450. package/dist/lint/rules/cor022-receipt-leaf.js +118 -0
  451. package/dist/lint/rules/cor024-receipt-secret-pointer.js +199 -0
  452. package/dist/lint/rules/declarable-naming-convention.js +62 -0
  453. package/dist/lint/rules/evl001-non-literal-expression.js +66 -0
  454. package/dist/lint/rules/evl002-control-flow-resource.js +69 -0
  455. package/dist/lint/rules/evl003-dynamic-property-access.js +44 -0
  456. package/dist/lint/rules/evl004-spread-non-const.js +94 -0
  457. package/dist/lint/rules/evl005-resource-block-body.js +41 -0
  458. package/dist/lint/rules/evl007-invalid-siblings.js +105 -0
  459. package/dist/lint/rules/evl009-composite-no-constant.js +158 -0
  460. package/dist/lint/rules/evl010-composite-no-transform.js +59 -0
  461. package/dist/lint/rules/evl011-symbolic-in-template.js +106 -0
  462. package/dist/lint/rules/export-required.js +115 -0
  463. package/dist/lint/rules/file-declarable-limit.js +88 -0
  464. package/dist/lint/rules/flat-declarations.js +62 -0
  465. package/dist/lint/rules/index.js +90 -0
  466. package/dist/lint/rules/no-cyclic-declarable-ref.js +138 -0
  467. package/dist/lint/rules/no-redundant-type-import.js +76 -0
  468. package/dist/lint/rules/no-redundant-value-cast.js +41 -0
  469. package/dist/lint/rules/no-string-ref.js +59 -0
  470. package/dist/lint/rules/no-unused-declarable-import.js +75 -0
  471. package/dist/lint/rules/no-unused-declarable.js +142 -0
  472. package/dist/lint/rules/op/index.js +28 -0
  473. package/dist/lint/rules/op/ops012-activity-contract.js +54 -0
  474. package/dist/lint/rules/op/ops013-step-output-ref.js +88 -0
  475. package/dist/lint/rules/op/ops014-converge-rule-refusals.js +114 -0
  476. package/dist/lint/rules/op/ops015-gate-approval.js +45 -0
  477. package/dist/lint/rules/op/support.js +11 -0
  478. package/dist/lint/rules/op/sys010-key-over-plain-http.js +100 -0
  479. package/dist/lint/rules/property-kind.js +56 -0
  480. package/dist/lint/rules/single-concern-file.js +71 -0
  481. package/dist/lint/rules/sys001-no-literal-key.js +60 -0
  482. package/dist/lint/selectors.js +168 -0
  483. package/dist/lint/suppressions.js +129 -0
  484. package/dist/lint/typescript.js +17 -0
  485. package/dist/live-endpoint.js +113 -0
  486. package/dist/live-read-session.js +76 -0
  487. package/dist/lsp/lexicon-providers.js +163 -0
  488. package/dist/lsp/types.js +5 -0
  489. package/dist/managed-fields.js +293 -0
  490. package/dist/mcp/types.js +4 -0
  491. package/dist/notify-verify.js +0 -1
  492. package/dist/notify.js +0 -1
  493. package/dist/notify.schema.json +72 -0
  494. package/dist/observation.js +266 -0
  495. package/dist/okf-read.js +134 -0
  496. package/dist/okf.js +275 -0
  497. package/dist/op/__fixtures__/decide-stub-backend.js +125 -0
  498. package/dist/op/activities/activity-contracts.js +313 -0
  499. package/dist/op/activities/apply.js +582 -0
  500. package/dist/op/activities/build.js +18 -0
  501. package/dist/op/activities/change-coverage.js +40 -0
  502. package/dist/op/activities/change-set.js +64 -0
  503. package/dist/op/activities/converge.js +492 -0
  504. package/dist/op/activities/decide.js +211 -0
  505. package/dist/op/activities/env-teardown.js +110 -0
  506. package/dist/op/activities/factory.js +591 -0
  507. package/dist/op/activities/forge-pull-request.js +82 -0
  508. package/dist/op/activities/gh-host.js +78 -0
  509. package/dist/op/activities/guard-validate.js +165 -0
  510. package/dist/op/activities/http-check.js +40 -0
  511. package/dist/op/activities/index.js +59 -0
  512. package/dist/op/activities/lexicon-upgrade.js +629 -0
  513. package/dist/op/activities/lifecycle.js +109 -0
  514. package/dist/op/activities/pipeline-audit.js +147 -0
  515. package/dist/op/activities/policy.js +26 -0
  516. package/dist/op/activities/predict-behaviour.js +341 -0
  517. package/dist/op/activities/propose-upgrade.js +165 -0
  518. package/dist/op/activities/reconcile.js +997 -0
  519. package/dist/op/activities/shell.js +118 -0
  520. package/dist/op/activities/source-release.js +97 -0
  521. package/dist/op/activities/source-rollback.js +165 -0
  522. package/dist/op/activities/teardown.js +17 -0
  523. package/dist/op/activities/util.js +6 -0
  524. package/dist/op/activities/wait.js +51 -0
  525. package/dist/op/activities/work-evidence.js +35 -0
  526. package/dist/op/activities/workflow-audit.js +133 -0
  527. package/dist/op/activity-contract-registry.js +106 -0
  528. package/dist/op/activity-contract.js +232 -0
  529. package/dist/op/activity-failure.js +31 -0
  530. package/dist/op/activity-profiles.js +129 -0
  531. package/dist/op/activity-registry.js +105 -0
  532. package/dist/op/activity-runtime.js +28 -0
  533. package/dist/op/builders.js +801 -0
  534. package/dist/op/change-signal.js +118 -0
  535. package/dist/op/composites/apply-op.js +151 -0
  536. package/dist/op/composites/behaviour-op.js +65 -0
  537. package/dist/op/composites/converge-op.js +141 -0
  538. package/dist/op/composites/index.js +19 -0
  539. package/dist/op/composites/lexicon-upgrade-op.js +107 -0
  540. package/dist/op/composites/pipeline-audit-op.js +50 -0
  541. package/dist/op/composites/reconcile-op.js +84 -0
  542. package/dist/op/composites/watch-op.js +69 -0
  543. package/dist/op/composites/workflow-audit-op.js +57 -0
  544. package/dist/op/converge-rule.js +205 -0
  545. package/dist/op/cron.js +146 -0
  546. package/dist/op/decide-backend.js +181 -0
  547. package/dist/op/decide-config.js +68 -0
  548. package/dist/op/decide-read-inputs.js +141 -0
  549. package/dist/op/discover.d.ts.map +1 -1
  550. package/dist/op/discover.js +213 -0
  551. package/dist/op/duration.js +21 -0
  552. package/dist/op/emulator-freshness.js +80 -0
  553. package/dist/op/emulator-lifecycle.js +269 -0
  554. package/dist/op/factory.js +83 -0
  555. package/dist/op/gate-approval.js +172 -0
  556. package/dist/op/gate-name.js +86 -0
  557. package/dist/op/gate-plan-context.js +167 -0
  558. package/dist/op/gate-point-run.js +67 -0
  559. package/dist/op/gate-point.js +111 -0
  560. package/dist/op/gate-resume-plan.js +155 -0
  561. package/dist/op/gate-resume.js +31 -0
  562. package/dist/op/gate-review.js +290 -0
  563. package/dist/op/gate-run-locator.js +85 -0
  564. package/dist/op/gate-summary.js +100 -0
  565. package/dist/op/gate-wait.js +56 -0
  566. package/dist/op/gate.js +449 -0
  567. package/dist/op/generate-pipeline.js +124 -0
  568. package/dist/op/index.js +51 -0
  569. package/dist/op/lifecycle-receipt-store.js +45 -0
  570. package/dist/op/local-executor.js +1164 -0
  571. package/dist/op/local-output.js +140 -0
  572. package/dist/op/op-ir.js +317 -0
  573. package/dist/op/op-verb-class.js +131 -0
  574. package/dist/op/op-waves-notify.js +136 -0
  575. package/dist/op/op-waves-run.js +1657 -0
  576. package/dist/op/op-waves.js +409 -0
  577. package/dist/op/operator.js +743 -0
  578. package/dist/op/receipt-store.js +94 -0
  579. package/dist/op/resource.js +23 -0
  580. package/dist/op/run-context.js +28 -0
  581. package/dist/op/run-live.js +256 -0
  582. package/dist/op/runtime.js +21 -0
  583. package/dist/op/runtimes/local.js +195 -0
  584. package/dist/op/source-archive.js +131 -0
  585. package/dist/op/step-output-ref.js +397 -0
  586. package/dist/op/steward-beside.js +169 -0
  587. package/dist/op/steward-points.js +138 -0
  588. package/dist/op/steward-turn.js +59 -0
  589. package/dist/op/steward.js +317 -0
  590. package/dist/op/types.js +25 -0
  591. package/dist/op/work-lease-decl.js +81 -0
  592. package/dist/op/work-lease-run.js +393 -0
  593. package/dist/override-gate.js +284 -0
  594. package/dist/owner-chain.js +74 -0
  595. package/dist/ownership.js +92 -0
  596. package/dist/params.js +61 -0
  597. package/dist/plan-summary.js +711 -0
  598. package/dist/point-decision.js +211 -0
  599. package/dist/pr-forge.js +810 -0
  600. package/dist/pr-loop.js +203 -0
  601. package/dist/project-codegen.js +191 -0
  602. package/dist/project-root.js +74 -0
  603. package/dist/project-validation.js +50 -0
  604. package/dist/provenance.js +157 -0
  605. package/dist/pseudo-parameter.js +40 -0
  606. package/dist/reconcile.js +450 -0
  607. package/dist/resource-attributes.js +33 -0
  608. package/dist/runtime-adapter.js +77 -0
  609. package/dist/runtime.js +66 -0
  610. package/dist/secret-materialization.js +153 -0
  611. package/dist/secret-provenance.js +250 -0
  612. package/dist/serializer-walker.js +112 -0
  613. package/dist/serializer.js +1 -0
  614. package/dist/sort.js +45 -0
  615. package/dist/stack-output.js +115 -0
  616. package/dist/telemetry-attribution.js +149 -0
  617. package/dist/terraform/__fixtures__/build-graph.js +63 -0
  618. package/dist/terraform/adopt-state.js +40 -0
  619. package/dist/terraform/aws-resources.js +384 -0
  620. package/dist/terraform/bridge.js +204 -0
  621. package/dist/terraform/carve-provider.js +155 -0
  622. package/dist/terraform/carve.js +138 -0
  623. package/dist/terraform/data-source-shape.js +57 -0
  624. package/dist/terraform/emit-source.js +36 -0
  625. package/dist/terraform/excise.js +123 -0
  626. package/dist/terraform/graduate.js +128 -0
  627. package/dist/terraform/graph.js +435 -0
  628. package/dist/terraform/hcl2json.js +60 -0
  629. package/dist/terraform/manifest.js +149 -0
  630. package/dist/terraform/parse.js +81 -0
  631. package/dist/terraform/providers/aws.js +146 -0
  632. package/dist/terraform/providers/gcp.js +294 -0
  633. package/dist/terraform/providers/index.js +17 -0
  634. package/dist/terraform/providers/kubernetes.js +253 -0
  635. package/dist/terraform/score.js +151 -0
  636. package/dist/terraform/state.js +67 -0
  637. package/dist/terraform/tier-map.js +74 -0
  638. package/dist/terraform/types.js +13 -0
  639. package/dist/terraform/unified-diff.js +132 -0
  640. package/dist/testing.js +295 -0
  641. package/dist/toml-emit.js +153 -0
  642. package/dist/toml-parse.js +355 -0
  643. package/dist/toml-utils.js +60 -0
  644. package/dist/toml.js +428 -0
  645. package/dist/ts-loader.d.ts +46 -0
  646. package/dist/ts-loader.d.ts.map +1 -0
  647. package/dist/ts-loader.js +103 -0
  648. package/dist/types.js +1 -0
  649. package/dist/utils.js +61 -0
  650. package/dist/validation.js +20 -0
  651. package/dist/vendor/js-yaml-load.js +1662 -0
  652. package/dist/workspace/__fixtures__/contract-repo.js +103 -0
  653. package/dist/workspace/__fixtures__/decide-workspace.js +112 -0
  654. package/dist/workspace/__fixtures__/sessions.js +91 -0
  655. package/dist/workspace/__fixtures__/two-member-workspace.js +34 -0
  656. package/dist/workspace/agent-cli.js +99 -0
  657. package/dist/workspace/agent.schema.json +390 -0
  658. package/dist/workspace/answers-ledger.js +89 -0
  659. package/dist/workspace/box-cli.js +142 -0
  660. package/dist/workspace/box-factory-write.js +196 -0
  661. package/dist/workspace/box-factory-write.schema.json +342 -0
  662. package/dist/workspace/box-factory.js +88 -0
  663. package/dist/workspace/box-intent.js +126 -0
  664. package/dist/workspace/box-isolation.js +169 -0
  665. package/dist/workspace/box-listing-write.schema.json +204 -0
  666. package/dist/workspace/box-listing.js +298 -0
  667. package/dist/workspace/box-publish.js +352 -0
  668. package/dist/workspace/box-publish.schema.json +498 -0
  669. package/dist/workspace/box-services.js +39 -0
  670. package/dist/workspace/broker-protocol.js +359 -0
  671. package/dist/workspace/broker-protocol.schema.json +228 -0
  672. package/dist/workspace/change-set.schema.json +460 -0
  673. package/dist/workspace/changes-cli.js +56 -0
  674. package/dist/workspace/changes.js +446 -0
  675. package/dist/workspace/changes.schema.json +642 -0
  676. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.js +198 -0
  677. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.js +155 -0
  678. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.js +214 -0
  679. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.js +180 -0
  680. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.js +99 -0
  681. package/dist/workspace/chant-migrations/chud-lexicon-exit.js +847 -0
  682. package/dist/workspace/chant-migrations.js +113 -0
  683. package/dist/workspace/check.schema.json +433 -0
  684. package/dist/workspace/checks/box-isolation.js +62 -0
  685. package/dist/workspace/checks/boxes.js +491 -0
  686. package/dist/workspace/checks/diagrams.js +104 -0
  687. package/dist/workspace/checks/generated.js +419 -0
  688. package/dist/workspace/checks/ledgers.js +212 -0
  689. package/dist/workspace/checks/links.js +191 -0
  690. package/dist/workspace/checks/live.js +100 -0
  691. package/dist/workspace/checks/pipelines.js +188 -0
  692. package/dist/workspace/checks/records.js +169 -0
  693. package/dist/workspace/checks.js +426 -0
  694. package/dist/workspace/ci-green-cli.js +125 -0
  695. package/dist/workspace/ci-green-forge.js +83 -0
  696. package/dist/workspace/ci-green-workflow.js +199 -0
  697. package/dist/workspace/ci-green.js +325 -0
  698. package/dist/workspace/ci-last-green.schema.json +158 -0
  699. package/dist/workspace/compose-graph.js +235 -0
  700. package/dist/workspace/compose-reports.js +146 -0
  701. package/dist/workspace/composites.js +264 -0
  702. package/dist/workspace/composites.schema.json +617 -0
  703. package/dist/workspace/conformance/__fixture__/app/package.json +7 -0
  704. package/dist/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
  705. package/dist/workspace/conformance/__fixture__/decisions/decision.kind.mjs +54 -0
  706. package/dist/workspace/conformance/__fixture__/decisions/decision.schema.json +512 -0
  707. package/dist/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
  708. package/dist/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
  709. package/dist/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
  710. package/dist/workspace/conformance/__fixture__/delivery/package.json +7 -0
  711. package/dist/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
  712. package/dist/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
  713. package/dist/workspace/conformance/__fixtures__/reference-broker.js +205 -0
  714. package/dist/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
  715. package/dist/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
  716. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  717. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  718. package/dist/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
  719. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  720. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  721. package/dist/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
  722. package/dist/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
  723. package/dist/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  724. package/dist/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  725. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
  726. package/dist/workspace/conformance/broker.js +736 -0
  727. package/dist/workspace/conformance/index.js +673 -0
  728. package/dist/workspace/conformance/vitest.js +108 -0
  729. package/dist/workspace/conformance/writer.js +1080 -0
  730. package/dist/workspace/decide.js +879 -0
  731. package/dist/workspace/decision-points.schema.json +135 -135
  732. package/dist/workspace/declaration-write-cli.js +74 -0
  733. package/dist/workspace/declaration-write.js +238 -0
  734. package/dist/workspace/declaration.js +938 -0
  735. package/dist/workspace/declaration.schema.json +1413 -1413
  736. package/dist/workspace/declared-kinds.js +106 -0
  737. package/dist/workspace/environments.js +164 -0
  738. package/dist/workspace/evidence.schema.json +279 -0
  739. package/dist/workspace/export-cli.js +185 -0
  740. package/dist/workspace/export.js +499 -0
  741. package/dist/workspace/factory-rules.js +175 -0
  742. package/dist/workspace/generated-files.js +92 -0
  743. package/dist/workspace/glob.js +45 -0
  744. package/dist/workspace/graph-cache.js +352 -0
  745. package/dist/workspace/graph-cli.js +348 -0
  746. package/dist/workspace/graph.schema.json +1338 -0
  747. package/dist/workspace/identity-gate-rule.js +75 -0
  748. package/dist/workspace/identity.js +126 -0
  749. package/dist/workspace/import.js +283 -0
  750. package/dist/workspace/init.js +620 -0
  751. package/dist/workspace/intent-cli.js +263 -0
  752. package/dist/workspace/intent-joins.js +201 -0
  753. package/dist/workspace/intent-record.js +352 -0
  754. package/dist/workspace/intent-record.schema.json +864 -0
  755. package/dist/workspace/intent.js +1582 -0
  756. package/dist/workspace/intent.schema.json +2420 -0
  757. package/dist/workspace/json-edit.js +221 -0
  758. package/dist/workspace/jsonc.js +235 -0
  759. package/dist/workspace/kind-readers.js +103 -0
  760. package/dist/workspace/kinds.js +587 -0
  761. package/dist/workspace/lexicon-symbols.js +112 -0
  762. package/dist/workspace/lineage-adopt-cli.js +102 -0
  763. package/dist/workspace/lineage-adopt.js +360 -0
  764. package/dist/workspace/lineage-check.js +282 -0
  765. package/dist/workspace/lineage-cli.js +121 -0
  766. package/dist/workspace/lineage-hash-index.js +284 -0
  767. package/dist/workspace/lineage-init.js +507 -0
  768. package/dist/workspace/lineage-lock.js +387 -0
  769. package/dist/workspace/lineage-merge.js +45 -0
  770. package/dist/workspace/lineage-migrations.d.ts.map +1 -1
  771. package/dist/workspace/lineage-migrations.js +329 -0
  772. package/dist/workspace/lineage-provenance.js +36 -0
  773. package/dist/workspace/lineage-update.js +138 -0
  774. package/dist/workspace/lineage-upgrade-cli.js +131 -0
  775. package/dist/workspace/lineage-upgrade.js +889 -0
  776. package/dist/workspace/lineage-vendor.js +171 -0
  777. package/dist/workspace/lineage-version.js +128 -0
  778. package/dist/workspace/lineage-versions.js +275 -0
  779. package/dist/workspace/links.js +330 -0
  780. package/dist/workspace/ls-generated.js +95 -0
  781. package/dist/workspace/ls.js +277 -0
  782. package/dist/workspace/ls.schema.json +333 -0
  783. package/dist/workspace/member-commands.js +508 -0
  784. package/dist/workspace/member-handles.js +216 -0
  785. package/dist/workspace/member-pipeline.js +258 -0
  786. package/dist/workspace/member-run.js +117 -0
  787. package/dist/workspace/member-write.schema.json +193 -0
  788. package/dist/workspace/nested-graph.js +156 -0
  789. package/dist/workspace/nesting.js +38 -0
  790. package/dist/workspace/patch.js +301 -0
  791. package/dist/workspace/patch.schema.json +321 -0
  792. package/dist/workspace/pin-cli.js +55 -0
  793. package/dist/workspace/pin-integrity.js +100 -0
  794. package/dist/workspace/plan-summary.schema.json +155 -0
  795. package/dist/workspace/point-answer.schema.json +129 -129
  796. package/dist/workspace/points-cli.js +291 -0
  797. package/dist/workspace/points-write.schema.json +660 -0
  798. package/dist/workspace/points.js +521 -0
  799. package/dist/workspace/points.schema.json +916 -0
  800. package/dist/workspace/pr-report.schema.json +108 -0
  801. package/dist/workspace/principal-classes.js +245 -0
  802. package/dist/workspace/reason-codes.js +326 -0
  803. package/dist/workspace/record-assets.js +151 -0
  804. package/dist/workspace/record-decided.js +105 -0
  805. package/dist/workspace/record-sessions.js +96 -0
  806. package/dist/workspace/record-source.js +114 -0
  807. package/dist/workspace/records-amend.schema.json +284 -0
  808. package/dist/workspace/records-checkout.js +76 -0
  809. package/dist/workspace/records-cli.js +707 -0
  810. package/dist/workspace/records-close.js +116 -0
  811. package/dist/workspace/records-close.schema.json +278 -0
  812. package/dist/workspace/records-new.schema.json +202 -0
  813. package/dist/workspace/records-review.schema.json +367 -0
  814. package/dist/workspace/records-since.js +317 -0
  815. package/dist/workspace/records-since.schema.json +218 -0
  816. package/dist/workspace/records-write.js +1229 -0
  817. package/dist/workspace/records.js +1345 -0
  818. package/dist/workspace/records.schema.json +604 -0
  819. package/dist/workspace/reference-kinds/app/package.json +10 -0
  820. package/dist/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  821. package/dist/workspace/returns.js +303 -0
  822. package/dist/workspace/root-refusal.js +75 -0
  823. package/dist/workspace/run-statement.schema.json +622 -0
  824. package/dist/workspace/runs-cli.js +584 -0
  825. package/dist/workspace/runs-write.schema.json +994 -0
  826. package/dist/workspace/runs.js +708 -0
  827. package/dist/workspace/runs.schema.json +1257 -0
  828. package/dist/workspace/runtimes.js +76 -0
  829. package/dist/workspace/session-kinds.js +83 -0
  830. package/dist/workspace/signers.schema.json +206 -0
  831. package/dist/workspace/source-block.js +125 -0
  832. package/dist/workspace/squash.js +177 -0
  833. package/dist/workspace/status-gates.js +162 -0
  834. package/dist/workspace/status-stewards.js +330 -0
  835. package/dist/workspace/status.js +514 -0
  836. package/dist/workspace/status.schema.json +998 -0
  837. package/dist/workspace/steward-index.js +191 -0
  838. package/dist/workspace/symbols.js +188 -0
  839. package/dist/workspace/template-manifest.js +227 -0
  840. package/dist/workspace/template-pins.js +115 -0
  841. package/dist/workspace/trailer-joins.js +65 -0
  842. package/dist/workspace/trailers.js +111 -0
  843. package/dist/workspace/tree.js +136 -0
  844. package/dist/workspace/trust/attestor.js +63 -0
  845. package/dist/workspace/trust/dsse.js +108 -0
  846. package/dist/workspace/trust/evidence-cli.js +175 -0
  847. package/dist/workspace/trust/evidence.js +161 -0
  848. package/dist/workspace/trust/gate-seal.js +226 -0
  849. package/dist/workspace/trust/policy.js +190 -0
  850. package/dist/workspace/trust/provenance.js +180 -0
  851. package/dist/workspace/trust/rotation.js +298 -0
  852. package/dist/workspace/trust/run-commits.js +101 -0
  853. package/dist/workspace/trust/run-statement.js +182 -0
  854. package/dist/workspace/trust/seal.js +272 -0
  855. package/dist/workspace/trust/signers-cli.js +152 -0
  856. package/dist/workspace/trust/ssh-commit.js +178 -0
  857. package/dist/workspace/trust/test-repo.js +118 -0
  858. package/dist/workspace/trust/trust-files.js +193 -0
  859. package/dist/workspace/trust/verify-cli.js +63 -0
  860. package/dist/workspace/trust/verify.js +192 -0
  861. package/dist/workspace/which-chant.js +141 -0
  862. package/dist/workspace/wip-cli.js +95 -0
  863. package/dist/workspace/wip-write.schema.json +504 -0
  864. package/dist/workspace/wip.js +631 -0
  865. package/dist/workspace/wip.schema.json +334 -0
  866. package/dist/workspace/work-cli.js +448 -0
  867. package/dist/workspace/work-evidence.js +122 -0
  868. package/dist/workspace/work-evidence.schema.json +222 -0
  869. package/dist/workspace/work-history.schema.json +421 -0
  870. package/dist/workspace/work-lease.schema.json +246 -0
  871. package/dist/workspace/work-size.js +77 -0
  872. package/dist/workspace/work.js +283 -0
  873. package/dist/workspace/workspace-kinds.schema.json +259 -259
  874. package/dist/workspace/workspace-principals.schema.json +53 -53
  875. package/dist/workspace/write-lock-cli.js +105 -0
  876. package/dist/workspace/write-lock.js +424 -0
  877. package/dist/workspace/write-lock.schema.json +172 -0
  878. package/dist/workspace/write-scope.js +484 -0
  879. package/dist/yaml.js +459 -0
  880. package/package.json +2 -2
  881. package/src/cli/commands/check-lexicon-plugin.ts +2 -0
  882. package/src/cli/commands/lint.ts +3 -0
  883. package/src/cli/commands/migrate.ts +2 -0
  884. package/src/cli/compiled-cli.e2e.test.ts +165 -0
  885. package/src/cli/main.ts +121 -27
  886. package/src/cli/mcp/workspace-tools.ts +1 -1
  887. package/src/codegen/pinned-upgrade.ts +2 -0
  888. package/src/components/discover.ts +2 -0
  889. package/src/components/sandbox/driver.ts +3 -1
  890. package/src/config-import.ts +3 -0
  891. package/src/discovery/fold-import.ts +23 -14
  892. package/src/discovery/import.ts +2 -0
  893. package/src/discovery/sandbox/driver.ts +3 -1
  894. package/src/lexicon-module.ts +3 -0
  895. package/src/lint/config.ts +1 -1
  896. package/src/lint/discover.ts +28 -8
  897. package/src/lint/policy-import.ts +4 -1
  898. package/src/lint/rule-loader.ts +2 -0
  899. package/src/op/discover.ts +3 -0
  900. package/src/ts-loader.test.ts +23 -0
  901. package/src/ts-loader.ts +122 -0
  902. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +1 -1
  903. package/src/workspace/declaration.ts +1 -1
  904. package/src/workspace/kinds.ts +1 -1
  905. package/src/workspace/lineage-migrations.ts +2 -0
  906. package/src/workspace/points.ts +1 -1
  907. package/src/workspace/principal-classes.ts +1 -1
@@ -0,0 +1,997 @@
1
+ import { exec } from "node:child_process";
2
+ import { readFile } from "node:fs/promises";
3
+ import { promisify } from "node:util";
4
+ import { ForgeApiError, forgejoForge, githubForge } from "../../pr-forge.js";
5
+ import { ghHostEnv } from "./gh-host.js";
6
+ import { restPullRequestForge } from "./forge-pull-request.js";
7
+ const execAsync = promisify(exec);
8
+ /** Default branch name for a reconcile PR. Deterministic — no timestamp. */
9
+ export function reconcileBranchName(env) {
10
+ const safe = env.replace(/[^a-zA-Z0-9._-]+/g, "-");
11
+ return `chant/reconcile-${safe}`;
12
+ }
13
+ /**
14
+ * Build the markdown body summarizing which change-set entries triggered the
15
+ * reconcile. Pure — used as the PR/issue body and returned in `report` mode.
16
+ */
17
+ export function reconcileSummary(env, entries) {
18
+ const lines = [
19
+ `Reconcile from live environment \`${env}\`.`,
20
+ "",
21
+ "This PR regenerates chant TypeScript from live state to close the gap between the cloud and source. It was triggered by the following change-set entries:",
22
+ "",
23
+ "| Entry | Action | Type |",
24
+ "|---|---|---|",
25
+ ];
26
+ for (const e of entries) {
27
+ lines.push(`| ${e.name} | ${e.action} | ${e.type ?? ""} |`);
28
+ }
29
+ if (entries.length === 0) {
30
+ lines.push("| _(none)_ | | |");
31
+ }
32
+ lines.push("");
33
+ lines.push("Review the diff before merging — live import may surface values that need redaction.");
34
+ return lines.join("\n");
35
+ }
36
+ function shellQuote(s) {
37
+ return `'${s.replace(/'/g, "'\\''")}'`;
38
+ }
39
+ /**
40
+ * The hidden marker that makes a `comment`-mode finding findable across
41
+ * re-runs: written as the comment's first line, matched with `startswith` on
42
+ * the next run. Keyed on `env` (slugified the same way {@link
43
+ * reconcileBranchName} slugifies it, which also keeps the value free of the
44
+ * quotes and backslashes it is interpolated next to), so two Ops over two
45
+ * environments own two comments and each updates in place.
46
+ */
47
+ export function commentMarker(env) {
48
+ return `<!-- chant-reconcile:${markerSlug(env)} -->`;
49
+ }
50
+ /**
51
+ * The hidden marker that makes an `issue`-mode finding findable across re-runs
52
+ * (#2292, #2297): written as the issue description's first line, matched on
53
+ * the next run by a `startswith` check (GitHub/Forgejo) or a server-side
54
+ * `search` plus the same check (GitLab). The same recipe {@link commentMarker}
55
+ * names for the `comment` mode's note, kept as its own function (rather than
56
+ * reused) because the two modes write to different resources and a caller may
57
+ * run both against the same `env`.
58
+ *
59
+ * Keyed on the owning Op *and* the env, not the env alone (#2319). An
60
+ * env-keyed marker was not unique, and once #2311 made this mode edit in place
61
+ * that stopped being merely untidy: `postOrUpdateGithubIssue` PATCHes both the
62
+ * title and the body of whatever it matches, so two Ops resolving to one
63
+ * marker rewrite each other's report on every run and the issue alternates
64
+ * between two unrelated findings. Two in-tree pairings hit it — a stock
65
+ * `TerraformWatchOp` and a `live: true` one over the same root (both pass the
66
+ * root as `env`), and a `ReconcileOp` for a chant environment whose name
67
+ * matches some terraform root — and both are ordinary configurations, not
68
+ * abuse. The Op name is the identifier closest to unique that this activity
69
+ * can be handed: it names the Op's output directory and is what `chant run`
70
+ * takes, so two Ops in a project do not share a raw name.
71
+ * `lexicons/github`'s gate notice reached the same conclusion first, keying
72
+ * its own sticky marker on `$CHANT_OP` alone.
73
+ *
74
+ * Both halves are slugified the same way {@link reconcileBranchName} slugifies
75
+ * an env, which also keeps the value free of the quotes and backslashes it is
76
+ * interpolated next to and of anything URL-encoding should have to escape out
77
+ * of. `/` cannot survive that slugify, so it separates the two halves
78
+ * unambiguously: the marker parses back to exactly one `op` slug and one `env`
79
+ * slug.
80
+ *
81
+ * What that leaves, and what is accepted rather than solved (#2319 pre-merge
82
+ * review): the slug is not injective. {@link markerSlug} collapses every run
83
+ * of characters outside `[A-Za-z0-9._-]` to a single `-`, so `"app watch"` and
84
+ * `"app:watch"` share a slug, and so do `"café"` and `"caf€"`. Two Ops named
85
+ * that way in one project over one env would still collide. Nothing in this
86
+ * repository constrains an Op name's character set today, so this cannot be
87
+ * closed here — it wants a charset rule on `OpConfig.name` (which would also
88
+ * settle the output directory and the branch name, both of which slugify the
89
+ * same way and have the same aliasing). Until then the residual is: distinct
90
+ * raw names, distinct markers, unless the names differ only in characters the
91
+ * slug does not keep.
92
+ */
93
+ export function issueMarker(op, env) {
94
+ return `<!-- chant-reconcile-issue:${markerSlug(op)}/${markerSlug(env)} -->`;
95
+ }
96
+ /**
97
+ * The slugify every marker shares: everything outside `[A-Za-z0-9._-]`
98
+ * collapses to `-`. Exported for the behaviour finding's own marker
99
+ * (`./predict-behaviour.ts`), so a third marker cannot slugify differently
100
+ * from the two here.
101
+ */
102
+ export function markerSlug(s) {
103
+ return s.replace(/[^a-zA-Z0-9._-]+/g, "-");
104
+ }
105
+ /**
106
+ * What any mode says about a supplied `marker` it cannot use safely (#2319
107
+ * pre-merge review).
108
+ *
109
+ * The marker is interpolated raw into the `--jq` filter that finds this Op's
110
+ * own comment or issue, inside a jq string literal: `startswith("<marker>")`.
111
+ * Only the whole filter is shell-quoted, so a `"` or a `\` in the marker
112
+ * closes or escapes past that literal — at best a jq syntax error, at worst a
113
+ * filter that matches something other than what the caller wrote. A control
114
+ * character does the same to the jq program's own line structure.
115
+ *
116
+ * Markers this activity builds itself cannot contain any of the three:
117
+ * {@link markerSlug} keeps them to `[A-Za-z0-9._-]` plus the fixed `<!-- … -->`
118
+ * frame. This checks the one that comes in from outside, and refuses rather
119
+ * than escaping, because a marker is an identity a caller has to be able to
120
+ * predict — silently rewriting it would move the issue this run owns.
121
+ */
122
+ export function unsafeMarkerMessage(marker) {
123
+ return (`reconcilePr was given the marker ${JSON.stringify(marker)}, which it cannot use. The marker goes into ` +
124
+ 'the `--jq` filter that finds this Op\'s own comment or issue, as `startswith("<marker>")`, so a double ' +
125
+ "quote, a backslash or a control character in it either breaks that filter or changes what it matches. " +
126
+ "Markers this activity builds itself are slugified to letters, digits, dot, underscore and hyphen and " +
127
+ "cannot contain any of the three. Keep a supplied marker to printable text without `\"` or `\\`, or drop " +
128
+ "`marker` and pass `op` instead.");
129
+ }
130
+ /**
131
+ * The caller's `marker` as this activity will actually use it, or `undefined`
132
+ * when the caller supplied nothing usable (#2319 pre-merge review).
133
+ *
134
+ * Blank is not a marker. `""` and `" "` used to satisfy the `issue`-mode
135
+ * identity check — `args.marker === undefined` is false for both, and `??`
136
+ * only falls through on nullish — and then built `startswith("")`, which is
137
+ * true of every issue body in the repository. So the step took whichever OPEN
138
+ * non-pull-request issue the forge listed first, very possibly a human's, and
139
+ * PATCHed its title and body. That is the precise failure #2319 exists to
140
+ * close, reachable through the escape hatch #2319 added, which is why the
141
+ * check lives on the field rather than in one mode's branch.
142
+ *
143
+ * Trimming rather than only rejecting: a marker with a trailing space is
144
+ * written and matched consistently either way, and normalizing it here means
145
+ * one definition of "blank" for both modes.
146
+ */
147
+ export function suppliedMarker(marker) {
148
+ const trimmed = marker?.trim();
149
+ if (!trimmed)
150
+ return undefined;
151
+ if (/["\\]|[\u0000-\u001f\u007f]/.test(trimmed))
152
+ throw new Error(unsafeMarkerMessage(trimmed));
153
+ return trimmed;
154
+ }
155
+ /**
156
+ * What an `issue`-mode step says when it was given no identity to key its
157
+ * marker on (#2319).
158
+ *
159
+ * A refusal rather than a fall back to the pre-#2319 env-only marker, because
160
+ * the fallback is the bug: it is silent, it looks like it worked, and what it
161
+ * costs is the *other* Op's report, which nobody is watching the log of. The
162
+ * step that has to change is the one being refused, and the message names the
163
+ * two ways to change it.
164
+ */
165
+ export function noIssueIdentityMessage(env) {
166
+ return ('reconcilePr mode "issue" edits the one OPEN issue carrying this Op\'s hidden marker in place, so the ' +
167
+ `marker has to name the Op. This step supplies env "${env}" and no identity, and an env alone is not ` +
168
+ "unique: two Ops over one env — a stock terraform drift watch and a live one over the same root, or a " +
169
+ "terraform root and a chant environment that happen to share a name — resolve to the same marker, and " +
170
+ "each run then rewrites the other's issue title and body. Pass `op` with the Op's own name (every " +
171
+ "composite in tree does), or pass an explicit `marker` you keep unique yourself.");
172
+ }
173
+ /** What a `comment`-mode step says when the run it is in has no pull request and no merge request. */
174
+ export function noPullRequestContextMessage() {
175
+ return ('reconcilePr mode "comment" posts the finding on the pull request or merge request that triggered the ' +
176
+ "run, and this run has none. On GitHub Actions it needs GITHUB_REPOSITORY plus a pull request number, " +
177
+ "read from the event payload at GITHUB_EVENT_PATH (`.number` / `.pull_request.number`) or from " +
178
+ "GITHUB_REF (`refs/pull/<n>/merge`), which a pull_request event sets and nothing else does. On GitLab " +
179
+ "CI it needs CI_MERGE_REQUEST_IID plus the project (CI_MERGE_REQUEST_PROJECT_ID or CI_PROJECT_ID) and " +
180
+ "the API base (CI_API_V4_URL, or CI_SERVER_URL to derive it), which a merge_request_event pipeline " +
181
+ "sets and nothing else does. Trigger this Op from a pull_request or merge_request pipeline, or give " +
182
+ 'it findingMode "issue" or "report".');
183
+ }
184
+ /**
185
+ * Read the pull request number out of a parsed webhook event payload. Pure.
186
+ * A `pull_request` event carries it top-level as `number` and again under
187
+ * `pull_request.number`; both are accepted, neither is invented.
188
+ */
189
+ function prNumberFromPayload(payload) {
190
+ if (typeof payload !== "object" || payload === null)
191
+ return undefined;
192
+ const p = payload;
193
+ if (typeof p.number === "number")
194
+ return p.number;
195
+ if (typeof p.pull_request?.number === "number")
196
+ return p.pull_request.number;
197
+ return undefined;
198
+ }
199
+ /**
200
+ * Derive the triggering pull request from CI environment variables plus the
201
+ * already-parsed event payload. Pure — exported for testing; the IO (reading
202
+ * `GITHUB_EVENT_PATH`) is {@link resolvePullRequestContext}'s.
203
+ *
204
+ * Returns undefined rather than throwing, so the caller owns the message.
205
+ */
206
+ export function pullRequestContextFrom(env, eventPayload) {
207
+ const repo = env.GITHUB_REPOSITORY;
208
+ if (!repo)
209
+ return undefined;
210
+ const fromRef = /^refs\/pull\/(\d+)\//.exec(env.GITHUB_REF ?? "")?.[1];
211
+ const number = prNumberFromPayload(eventPayload) ?? (fromRef ? Number(fromRef) : undefined);
212
+ if (number === undefined || !Number.isInteger(number) || number <= 0)
213
+ return undefined;
214
+ return { repo, number };
215
+ }
216
+ /**
217
+ * Resolve the triggering pull request, reading and parsing the event payload
218
+ * `GITHUB_EVENT_PATH` names. Throws {@link noPullRequestContextMessage} when
219
+ * the run has no pull request, which is the whole point: a `comment` mode that
220
+ * quietly fell back to an issue would post the finding somewhere nobody asked
221
+ * for it.
222
+ */
223
+ export async function resolvePullRequestContext(env = process.env) {
224
+ let payload;
225
+ const eventPath = env.GITHUB_EVENT_PATH;
226
+ if (eventPath) {
227
+ try {
228
+ payload = JSON.parse(await readFile(eventPath, "utf8"));
229
+ }
230
+ catch {
231
+ // An unreadable or malformed payload is not fatal on its own: GITHUB_REF
232
+ // may still name the pull request. If it does not, the error below says so.
233
+ payload = undefined;
234
+ }
235
+ }
236
+ const ctx = pullRequestContextFrom(env, payload);
237
+ if (!ctx)
238
+ throw new Error(noPullRequestContextMessage());
239
+ return ctx;
240
+ }
241
+ /**
242
+ * The REST base a GitHub-shaped `gh api` call should target, read the same
243
+ * way the runner itself reads it (chant #2291). `GITHUB_API_URL` is a default
244
+ * environment variable every GitHub Actions *and* Forgejo Actions job carries
245
+ * — `https://api.github.com` on github.com, `<host>/api/v3` on GitHub
246
+ * Enterprise Server, and `<host>/api/v1` on Forgejo, which already advertises
247
+ * it correctly (confirmed on a real Forgejo 12.0.4+gitea-1.22.0 instance
248
+ * during INTENTIUS/choudoufu#1027: `$GITHUB_API_URL` read
249
+ * `http://forgejo:3000/api/v1` inside the job).
250
+ *
251
+ * Building the full URL from this rather than handing `gh api` a bare
252
+ * relative path (`repos/…`) is the fix itself: `gh` resolves a relative path
253
+ * by guessing a host-specific prefix of its own — `api.<host>` for
254
+ * `github.com`, `<host>/api/v3` for anything else — and that guess is `/api/v3`
255
+ * for a Forgejo host too, which Forgejo answers 404 for both GET and POST.
256
+ * Handed a full URL, `gh api` uses it verbatim and skips the guess entirely,
257
+ * which is what let the same `gh` binary reach Forgejo's `/api/v1` in the
258
+ * same session, over plain HTTP and over TLS. No Forgejo-specific branch is
259
+ * needed: every host in play (github.com, GHES, Forgejo) sets
260
+ * `GITHUB_API_URL` to the base its own `/repos/...` paths actually live
261
+ * under, so building the URL from it is correct everywhere `gh` already ran,
262
+ * not only on Forgejo.
263
+ */
264
+ export function githubApiBaseFrom(env) {
265
+ return env.GITHUB_API_URL?.trim().replace(/\/+$/, "") || "https://api.github.com";
266
+ }
267
+ /**
268
+ * Resolve the token the `comment` and `issue` modes post with on GitHub and
269
+ * Forgejo, most specific first (chant #2291). The generated workflow sets
270
+ * `GH_TOKEN` and `GITHUB_TOKEN` to `${{ github.token }}` for every
271
+ * non-`report` finding mode — on
272
+ * Forgejo the same way as on GitHub, and the real-instance read confirmed
273
+ * `github.token` is populated there and good for a 200 read and a 201 write,
274
+ * authored as `forgejo-actions`. So the common case needs nothing set beyond
275
+ * what the generator already emits.
276
+ *
277
+ * `CHANT_FORGEJO_TOKEN` is checked first for the case that ambient token
278
+ * cannot cover: a run posting to a Forgejo instance other than the one the
279
+ * job executes on, where `github.token`'s scope stops at its own instance.
280
+ * A run with none of the three is refused by name before any call, in place
281
+ * of the forge's own opaque 401.
282
+ */
283
+ export function commentTokenFrom(env) {
284
+ for (const source of ["CHANT_FORGEJO_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"]) {
285
+ const value = env[source]?.trim();
286
+ if (value)
287
+ return { value, source };
288
+ }
289
+ return undefined;
290
+ }
291
+ /**
292
+ * The environment a `gh api` call carries {@link commentTokenFrom}'s resolved
293
+ * credential in (chant #2333).
294
+ *
295
+ * `GH_TOKEN` alone is not enough, and that is `gh`'s documented behavior
296
+ * rather than a bug in it. `gh` picks the token per *request host*:
297
+ * `GH_TOKEN`/`GITHUB_TOKEN` are scoped to github.com and `ghe.com`
298
+ * subdomains, and every other host — a self-hosted Forgejo, a self-hosted
299
+ * GitHub Enterprise Server — reads `GH_ENTERPRISE_TOKEN`/
300
+ * `GITHUB_ENTERPRISE_TOKEN` instead (`gh help environment`). #2291 fixed the
301
+ * *URL* half of reaching a non-github.com host and left this half untouched,
302
+ * so 0.62.0 and 0.63.0 both shipped a `comment` mode that posts to Forgejo
303
+ * with no `Authorization` header at all.
304
+ *
305
+ * Confirmed against a real Forgejo 12.0.4+gitea-1.22.0 instance under #2333,
306
+ * from a shell whose `GH_CONFIG_DIR` held no `gh auth login` — the condition
307
+ * a fresh Actions checkout starts from, and the one #2304's verification did
308
+ * not reproduce. Driving this function's own POST with `GH_DEBUG=api`:
309
+ * `GH_TOKEN` alone sent no `Authorization` header and got HTTP 401
310
+ * `{"message":"token is required"}`, `GH_TOKEN` with `GITHUB_TOKEN` beside it
311
+ * got the same 401, and `GH_TOKEN` with `GH_HOST` beside it got the same 401
312
+ * again. `GH_ENTERPRISE_TOKEN` sent `Authorization: token …` and got HTTP 201.
313
+ *
314
+ * So the fix is one variable, set to the same value: whichever class `gh`
315
+ * decides the host falls into, the credential it finds there is the one
316
+ * {@link commentTokenFrom} resolved. That is why this does not branch on the
317
+ * forge, and why it needs no host detection of chant's own — the thing that
318
+ * broke was `gh`'s host classification, and setting both classes to one value
319
+ * is what stops chant depending on it.
320
+ *
321
+ * ## Why `GH_HOST` is deliberately not set
322
+ *
323
+ * #2333 proposed `GH_ENTERPRISE_TOKEN` *plus* a `GH_HOST` derived from
324
+ * `GITHUB_API_URL`, on the reading that the pair was needed. It is not: the
325
+ * live instance took `GH_ENTERPRISE_TOKEN` on its own, with no `GH_HOST`
326
+ * anywhere in the environment, because {@link githubApiBaseFrom} already
327
+ * hands `gh api` a full URL and `gh` reads the host off that URL rather than
328
+ * off its default. `GH_HOST` sets the *default* host for calls that name no
329
+ * host — which is what `gh issue create`'s ambient fallback below relies on —
330
+ * so setting it would buy nothing here and put a variable into the
331
+ * environment of calls that do not want one.
332
+ *
333
+ * ## Why github.com is unchanged
334
+ *
335
+ * Both variables carry the same value, so the question is only which one
336
+ * `gh` reads, and github.com reads `GH_TOKEN`. Probed against the real
337
+ * api.github.com the same way: a valid `GH_TOKEN` with a deliberately
338
+ * garbage `GH_ENTERPRISE_TOKEN` beside it succeeded, and a garbage `GH_TOKEN`
339
+ * with a valid `GH_ENTERPRISE_TOKEN` beside it failed `Bad credentials`.
340
+ * `GH_TOKEN` wins on github.com whatever the enterprise variable holds, so
341
+ * the header a github.com call sends is byte-identical to the one it sent
342
+ * before this change.
343
+ *
344
+ * A *self-hosted* GHES host is in the same class as Forgejo and was equally
345
+ * broken — same `gh` code path, same missing header — so it is fixed by the
346
+ * same variable rather than merely preserved. That was inferred from `gh`'s
347
+ * documented scoping in #2332 and is not exercised here: no GHES instance was
348
+ * available to test against.
349
+ */
350
+ export function ghCredentialEnv(base, token) {
351
+ return { ...base, GH_TOKEN: token.value, GH_ENTERPRISE_TOKEN: token.value };
352
+ }
353
+ /** What a `comment`-mode step says on a pull request it has no credential for. */
354
+ export function noCommentTokenMessage(repo, number) {
355
+ return (`reconcilePr mode "comment" has pull request ${repo}#${number} to post its finding on and no token to ` +
356
+ "post it with. GH_TOKEN or GITHUB_TOKEN, set from github.token, already covers this on a GitHub Actions " +
357
+ "or Forgejo Actions run — set CHANT_FORGEJO_TOKEN to post against a different instance than the one the " +
358
+ "job runs on. CHANT_FORGEJO_TOKEN is read first where the two must differ.");
359
+ }
360
+ /** What an `issue`-mode step says on a repository it has no credential for (#2320). */
361
+ export function noIssueTokenMessage(repo) {
362
+ return (`reconcilePr mode "issue" has repository ${repo} to open or edit its finding in and no token to do it ` +
363
+ "with. GH_TOKEN or GITHUB_TOKEN, set from github.token, already covers this on a GitHub Actions or " +
364
+ "Forgejo Actions run — set CHANT_FORGEJO_TOKEN to post against a different instance than the one the " +
365
+ "job runs on. CHANT_FORGEJO_TOKEN is read first where the two must differ.");
366
+ }
367
+ /**
368
+ * The GitHub or Forgejo client a `comment` or `issue` finding posts through
369
+ * (#3757): `../../pr-forge.ts`, the one the pull-request loop already uses,
370
+ * over `fetch`. These two modes used to shell to `gh`, which needed `gh` on
371
+ * the runner and needed it to attach a credential to a host it did not
372
+ * recognise (#2333). The token is the one {@link commentTokenFrom} resolved
373
+ * and the base is {@link githubApiBaseFrom}'s, so nothing about what a step
374
+ * sets has changed.
375
+ *
376
+ * The two forges share every call these modes make and differ on two things
377
+ * the client needs to know: the page parameter (`per_page` against `limit`)
378
+ * and the authorization scheme. A Forgejo run is told apart the way
379
+ * `../gate-resume.ts` tells it apart, by `FORGEJO_ACTIONS` or
380
+ * `GITEA_ACTIONS`, and also by an API base ending in `/api/v1`, which is how
381
+ * a step posting to some other Forgejo instance (`CHANT_FORGEJO_TOKEN`) names
382
+ * it. GitHub serves `/api/v3` or `api.github.com`, never `/api/v1`.
383
+ */
384
+ export function githubShapedForgeFrom(env, repo, token, signal) {
385
+ const apiBase = githubApiBaseFrom(env);
386
+ const forgejo = env.FORGEJO_ACTIONS === "true" || env.GITEA_ACTIONS === "true" || /\/api\/v1$/.test(apiBase);
387
+ // Read at call time, so a test that stubs the global sees its stub.
388
+ const fetchImpl = (url, init) => globalThis.fetch(url, { ...init, ...(signal ? { signal } : {}) });
389
+ const opts = { apiBase, repo, token: token.value, fetch: fetchImpl };
390
+ if (!forgejo)
391
+ return githubForge(opts);
392
+ let host;
393
+ try {
394
+ host = new URL(apiBase).host;
395
+ }
396
+ catch {
397
+ host = apiBase;
398
+ }
399
+ return forgejoForge({ ...opts, host });
400
+ }
401
+ /** A forge's refusal, carrying which variable the token came from, the way the GitLab half's errors do. */
402
+ async function withTokenSource(token, call) {
403
+ try {
404
+ return await call();
405
+ }
406
+ catch (err) {
407
+ if (err instanceof ForgeApiError)
408
+ throw new Error(`${err.message} (token from ${token.source}).`);
409
+ throw err;
410
+ }
411
+ }
412
+ /**
413
+ * Post `body` as one comment on `ctx`'s pull request, or edit the comment this
414
+ * Op already owns there: the comment whose body starts with `marker`, found
415
+ * by paging every comment on the pull request, PATCHed when there is one and
416
+ * POSTed when there is not. The same note recipe `PrPlanReport` and the
417
+ * pull-request loop use. A missing token is refused by name before any call.
418
+ */
419
+ async function postOrUpdateComment(ctx, marker, body, signal) {
420
+ const token = commentTokenFrom(process.env);
421
+ if (!token)
422
+ throw new Error(noCommentTokenMessage(ctx.repo, ctx.number));
423
+ const forge = githubShapedForgeFrom(process.env, ctx.repo, token, signal);
424
+ return withTokenSource(token, () => forge.upsertNote(ctx.number, marker, `${marker}\n\n${body}`));
425
+ }
426
+ /**
427
+ * Open `title`/`body` as one GitHub-shaped issue in `repo`, or edit the issue
428
+ * this caller already owns there (#2297), over `gh api`: find by marker,
429
+ * PATCH when found, POST when not, so a nightly finding carries one issue
430
+ * holding the current state instead of stacking a new one every run.
431
+ * `lexiconUpgrade`'s per-lexicon upgrade-status issue uses it. `reconcilePr`'s
432
+ * `issue` mode used it until #3757 and now keeps the same issue by the same
433
+ * recipe through the forge client's `upsertIssue` (`../../pr-forge.ts`), so
434
+ * the reasoning below covers both.
435
+ *
436
+ * Every call targets a full URL built from {@link githubApiBaseFrom} (#2291),
437
+ * so this reaches GitHub Enterprise Server and Forgejo the same way it
438
+ * reaches github.com.
439
+ *
440
+ * The credential is resolved and forwarded the same way too (chant #2320),
441
+ * through {@link commentTokenFrom} and out through {@link ghCredentialEnv} on
442
+ * every call. It did not used to be: `reconcilePr` handed this `(cmd) => execAsync(cmd, {
443
+ * signal })` with no `env` at all, so `CHANT_FORGEJO_TOKEN` never reached
444
+ * `gh` and the one case that variable exists for — posting to a Forgejo
445
+ * instance other than the one the job runs on, where `github.token`'s scope
446
+ * stops at its own instance — failed with a 401 or a 404 that named nothing.
447
+ * With no token at all the operator got `gh`'s own error rather than chant's.
448
+ * The refusal here is {@link noIssueTokenMessage}, not the `comment` mode's
449
+ * {@link noCommentTokenMessage}: same resolution order, same advice, but that
450
+ * message names a pull request this mode does not have, and this function is
451
+ * shared with `lexiconUpgrade`, which is not `reconcilePr` at all. The same
452
+ * split GitLab already makes between `noGitlabNoteTokenMessage` and
453
+ * `noGitlabIssueTokenMessage`.
454
+ *
455
+ * ## Why plain pagination instead of GitHub's Search API
456
+ *
457
+ * GitHub offers real server-side full-text search — `gh issue list --search`
458
+ * and `GET /search/issues?q=...` both use it, and chant #2297 (the issue that
459
+ * asked for this) names both as candidates. Neither is used here, for three
460
+ * reasons found while building this:
461
+ *
462
+ * 1. **Consistency.** GitHub's search index is documented as eventually
463
+ * consistent: an issue this function just wrote is not guaranteed to be
464
+ * findable by search immediately afterward. That is exactly the
465
+ * read-after-write the sticky recipe depends on — a false "not found"
466
+ * means a duplicate issue, which is the bug this activity exists to fix.
467
+ * 2. **The marker itself.** The hidden marker is deliberately punctuation-
468
+ * heavy (`<!-- chant-reconcile-issue:nightly/app -->`) so it renders
469
+ * invisibly. GitHub's search tokenizer is not documented to preserve that
470
+ * shape through `in:body` matching, and a wrong query would either miss the
471
+ * marker (a duplicate, the same failure mode as above) or over-match and
472
+ * still need the same client-side `startswith` check this function
473
+ * already does — at which point the search bought nothing but risk.
474
+ * 3. **Forgejo.** `gh issue list --search` and `/search/issues` are both
475
+ * absent from Forgejo's API — checked against a live Forgejo instance's
476
+ * own OpenAPI spec while building this: no `/search/issues`, no
477
+ * `/repos/issues/search` path anywhere in it (Forgejo's own issues-list
478
+ * endpoint does carry a real `q` search parameter, but that is a
479
+ * different endpoint shape than GitHub's, which would mean forking this
480
+ * function by forge — exactly what the `comment` path (#2291)
481
+ * deliberately does not do). Chant #2315 confirmed this against a live
482
+ * call rather than only the spec: plain paginated `GET .../issues?state=
483
+ * open` on a real Forgejo 12.0.4+gitea-1.22.0 instance interleaves pull
484
+ * requests with issues exactly as GitHub's endpoint does, and the
485
+ * `.pull_request == null` filter below excludes them correctly. The write
486
+ * calls this function makes did not clear the same instance until chant
487
+ * #2333 gave them a credential `gh` would apply to that host — see the
488
+ * module doc's `issue` bullet and {@link ghCredentialEnv}.
489
+ *
490
+ * So this reuses the recipe `comment` mode already proved for PR
491
+ * comments: list, `--paginate`, filter with `--jq` by an exact `startswith`
492
+ * prefix match, which is correct no matter how many pages it takes. The cost
493
+ * is real — a repository with many thousands of issues pays for every page
494
+ * on every run — and is the accepted trade-off here; narrowing further with
495
+ * GitHub's `creator` filter was considered and left alone, because the
496
+ * identity a `GH_TOKEN` posts as cannot be assumed reliably across a
497
+ * generated workflow and a hand-run one, and a wrong `creator` is a false
498
+ * negative — a duplicate — not just a slower search.
499
+ *
500
+ * ## The close decision (#2297)
501
+ *
502
+ * This never closes the issue, in either branch, matching both GitLab paths
503
+ * (#2256, #2292) — neither of which closes anything either. The search is
504
+ * scoped to `state=open` rather than `state=all`, which makes that decision
505
+ * concrete rather than implicit: a human closing the issue by hand, once the
506
+ * drift it named is actually fixed, is what tells the *next* run to open a
507
+ * fresh issue for a fresh finding instead of silently rewriting the closed
508
+ * one back open-in-substance-but-not-in-state. The alternative — searching
509
+ * `state=all` and reopening on write — would need this activity to guess
510
+ * when a finding is "resolved" with no reliable signal for that (an empty
511
+ * change set this run says nothing about whether the *last* finding was
512
+ * fixed or just not evaluated), and would turn a deliberate human "done" into
513
+ * something the bot silently reverses on its next run.
514
+ *
515
+ * `.pull_request == null` is filtered out of the search because GitHub's
516
+ * issues-list endpoint also returns pull requests, which this function must
517
+ * never mistake for an issue it owns.
518
+ */
519
+ export async function postOrUpdateGithubIssue(repo, marker, title, body, exec) {
520
+ const token = commentTokenFrom(process.env);
521
+ if (!token)
522
+ throw new Error(noIssueTokenMessage(repo));
523
+ const env = ghCredentialEnv(process.env, token);
524
+ const base = githubApiBaseFrom(process.env);
525
+ const listUrl = `${base}/repos/${repo}/issues?state=open`;
526
+ const jq = 'map(select((.pull_request == null) and ((.body // "") | startswith(' +
527
+ `"${marker}"))))` +
528
+ " | .[0].number // empty";
529
+ const { stdout: found } = await exec(`gh api ${shellQuote(listUrl)} --paginate --jq ${shellQuote(jq)}`, { env });
530
+ // `--paginate` prints one `--jq` result per page, so take the first line
531
+ // that is an id and ignore the empty ones the other pages produce.
532
+ const existing = found.split("\n").map((l) => l.trim()).find((l) => /^\d+$/.test(l));
533
+ const titleField = shellQuote(`title=${title}`);
534
+ const bodyField = shellQuote(`body=${marker}\n\n${body}`);
535
+ if (existing) {
536
+ const { stdout } = await exec(`gh api --method PATCH ${shellQuote(`${base}/repos/${repo}/issues/${existing}`)} ` +
537
+ `-f ${titleField} -f ${bodyField} --jq .html_url`, { env });
538
+ return stdout.trim();
539
+ }
540
+ const { stdout } = await exec(`gh api --method POST ${shellQuote(`${base}/repos/${repo}/issues`)} -f ${titleField} -f ${bodyField} --jq .html_url`, { env });
541
+ return stdout.trim();
542
+ }
543
+ /**
544
+ * Derive the triggering merge request from a GitLab job's CI variables. Pure
545
+ * — exported for testing, and the whole forge detection: nothing but a
546
+ * `merge_request_event` pipeline sets `CI_MERGE_REQUEST_IID`, so a run that
547
+ * has it is on GitLab and has a merge request, and a run that does not is
548
+ * neither.
549
+ *
550
+ * The project is the merge request's own (`CI_MERGE_REQUEST_PROJECT_ID`) in
551
+ * preference to the pipeline's (`CI_PROJECT_ID`): a merge request opened from
552
+ * a fork runs its pipeline in the fork, and the note belongs on the target
553
+ * project's merge request rather than on an iid that means something else in
554
+ * the fork.
555
+ *
556
+ * Returns undefined rather than throwing, so the caller owns the message.
557
+ */
558
+ export function mergeRequestContextFrom(env) {
559
+ const rawIid = env.CI_MERGE_REQUEST_IID?.trim();
560
+ if (!rawIid)
561
+ return undefined;
562
+ const iid = Number(rawIid);
563
+ if (!Number.isInteger(iid) || iid <= 0)
564
+ return undefined;
565
+ const server = env.CI_SERVER_URL?.trim().replace(/\/+$/, "");
566
+ const api = env.CI_API_V4_URL?.trim().replace(/\/+$/, "") || (server ? `${server}/api/v4` : "");
567
+ if (!api)
568
+ return undefined;
569
+ const project = env.CI_MERGE_REQUEST_PROJECT_ID?.trim() ||
570
+ env.CI_PROJECT_ID?.trim() ||
571
+ env.CI_MERGE_REQUEST_PROJECT_PATH?.trim() ||
572
+ env.CI_PROJECT_PATH?.trim() ||
573
+ "";
574
+ if (!project)
575
+ return undefined;
576
+ const path = env.CI_MERGE_REQUEST_PROJECT_PATH?.trim() || env.CI_PROJECT_PATH?.trim();
577
+ const webUrl = env.CI_MERGE_REQUEST_PROJECT_URL?.trim() || env.CI_PROJECT_URL?.trim();
578
+ return {
579
+ api,
580
+ project,
581
+ iid,
582
+ ...(path ? { path } : {}),
583
+ ...(webUrl ? { webUrl } : {}),
584
+ };
585
+ }
586
+ /**
587
+ * Derive the GitLab project an `issue`-mode run is in, from the job's own CI
588
+ * variables. Pure — exported for testing, and the whole forge detection:
589
+ * `CI_PROJECT_ID` is set on every GitLab CI job, merge request or not, and
590
+ * nothing outside GitLab CI sets it — a GitHub Actions run never reaches this
591
+ * branch. Unlike {@link mergeRequestContextFrom}, no `CI_MERGE_REQUEST_IID`
592
+ * is required, since `issue` mode's whole point is a cron trigger that has
593
+ * none.
594
+ *
595
+ * Returns undefined rather than throwing, so the caller owns the fallback:
596
+ * a run with no GitLab signal falls through to `gh issue create`.
597
+ */
598
+ export function gitlabProjectContextFrom(env) {
599
+ const project = env.CI_PROJECT_ID?.trim();
600
+ if (!project)
601
+ return undefined;
602
+ const server = env.CI_SERVER_URL?.trim().replace(/\/+$/, "");
603
+ const api = env.CI_API_V4_URL?.trim().replace(/\/+$/, "") || (server ? `${server}/api/v4` : "");
604
+ if (!api)
605
+ return undefined;
606
+ const path = env.CI_PROJECT_PATH?.trim();
607
+ const webUrl = env.CI_PROJECT_URL?.trim();
608
+ return {
609
+ api,
610
+ project,
611
+ ...(path ? { path } : {}),
612
+ ...(webUrl ? { webUrl } : {}),
613
+ };
614
+ }
615
+ /**
616
+ * Resolve the token a merge-request note is written with, most specific
617
+ * first. Pure — exported for testing.
618
+ *
619
+ * Two headers, not one, because GitLab reads two different credentials from
620
+ * two different headers: an access token goes in `PRIVATE-TOKEN`, and the
621
+ * pipeline's own ephemeral `CI_JOB_TOKEN` goes in `JOB-TOKEN`. Sending one in
622
+ * the other's header is a 401, not a fallback.
623
+ *
624
+ * The access token is preferred because it is the one that reliably works:
625
+ * `CI_JOB_TOKEN` reaches only the endpoints GitLab's job-token allowlist
626
+ * names, and the notes API is not among them on current GitLab, so a project
627
+ * that has not widened that allowlist needs a token with `api` scope. It is
628
+ * still accepted last rather than refused, so a project on an instance whose
629
+ * allowlist does cover notes needs no long-lived credential at all.
630
+ */
631
+ export function gitlabNoteTokenFrom(env) {
632
+ for (const source of ["CHANT_GITLAB_TOKEN", "GITLAB_TOKEN"]) {
633
+ const value = env[source]?.trim();
634
+ if (value)
635
+ return { header: "PRIVATE-TOKEN", value, source };
636
+ }
637
+ const jobToken = env.CI_JOB_TOKEN?.trim();
638
+ if (jobToken)
639
+ return { header: "JOB-TOKEN", value: jobToken, source: "CI_JOB_TOKEN" };
640
+ return undefined;
641
+ }
642
+ /** What a `comment`-mode step says on a merge request it has no credential for. */
643
+ export function noGitlabNoteTokenMessage(iid) {
644
+ return (`reconcilePr mode "comment" has merge request !${iid} to post its finding on and no token to post it ` +
645
+ "with. Set a GITLAB_TOKEN CI/CD variable (masked, scope: api) on the project — a project access token " +
646
+ "is enough — or, on an instance whose job-token allowlist covers the notes API, make CI_JOB_TOKEN " +
647
+ "available to the job. CHANT_GITLAB_TOKEN is read first where the two must differ.");
648
+ }
649
+ /** What an `issue`-mode step says on a GitLab project it has no credential for (#2292). */
650
+ export function noGitlabIssueTokenMessage(project) {
651
+ return (`reconcilePr mode "issue" wants to open or update an issue on GitLab project ${project} and has no ` +
652
+ "token to do it with. Set a GITLAB_TOKEN CI/CD variable (masked, scope: api) on the project — a " +
653
+ "project access token is enough — or, on an instance whose job-token allowlist covers the issues API, " +
654
+ "make CI_JOB_TOKEN available to the job. CHANT_GITLAB_TOKEN is read first where the two must differ.");
655
+ }
656
+ /** GitLab's REST paths take a URL-encoded project id or `group%2Fproject` path. */
657
+ function notesEndpoint(ctx) {
658
+ return `${ctx.api}/projects/${encodeURIComponent(ctx.project)}/merge_requests/${ctx.iid}/notes`;
659
+ }
660
+ /** One GitLab REST call, with the failure spelled out rather than swallowed into a parse error. */
661
+ async function gitlabRequest(url, token, init, signal) {
662
+ const res = await fetch(url, {
663
+ ...init,
664
+ ...(signal ? { signal } : {}),
665
+ headers: { [token.header]: token.value, "content-type": "application/json" },
666
+ });
667
+ if (!res.ok) {
668
+ const detail = (await res.text().catch(() => "")).slice(0, 500);
669
+ throw new Error(`GitLab API ${init.method ?? "GET"} ${url} answered ${res.status}${detail ? `: ${detail}` : ""} ` +
670
+ `(token from ${token.source}, sent as ${token.header}).`);
671
+ }
672
+ return res;
673
+ }
674
+ /**
675
+ * Find the note this Op already owns on `ctx`'s merge request, by the same
676
+ * hidden marker `comment` mode looks a GitHub comment up by: the
677
+ * marker is the body's first line and the match is a prefix.
678
+ *
679
+ * Pages the way GitLab pages, following the `x-next-page` response header
680
+ * rather than guessing a page count — an active merge request runs past one
681
+ * page of notes routinely, and a lookup that read page one alone would post
682
+ * a second comment instead of editing the first.
683
+ *
684
+ * GitLab's own system notes are skipped: they are the activity feed
685
+ * ("changed the description"), they are never ours, and they are the bulk of
686
+ * what fills those pages.
687
+ */
688
+ async function findOwnedNote(ctx, token, marker, signal) {
689
+ const endpoint = notesEndpoint(ctx);
690
+ // A merge request with more notes than this has something other than a
691
+ // stale plan comment wrong with it; the bound is what stops a broken
692
+ // `x-next-page` header from looping forever.
693
+ const MAX_PAGES = 50;
694
+ for (let page = 1; page <= MAX_PAGES; page++) {
695
+ const res = await gitlabRequest(`${endpoint}?per_page=100&page=${page}`, token, { method: "GET" }, signal);
696
+ const notes = (await res.json());
697
+ const owned = notes.find((note) => !note.system && (note.body ?? "").startsWith(marker));
698
+ if (owned)
699
+ return owned.id;
700
+ const next = res.headers.get("x-next-page")?.trim();
701
+ if (!next)
702
+ return undefined;
703
+ }
704
+ return undefined;
705
+ }
706
+ /**
707
+ * Post `body` as one note on `ctx`'s merge request, or edit the note this Op
708
+ * already owns there — {@link postOrUpdateComment}'s GitLab half, and the
709
+ * same recipe: find by marker, PUT when there is one, POST when there is not,
710
+ * so a merge request pushed to five times carries one note holding the
711
+ * current finding rather than five stale ones.
712
+ *
713
+ * Over `fetch` rather than a CLI: `glab` is on no GitLab runner by default,
714
+ * and a job whose finding step depended on it would fail on the ordinary
715
+ * `node:22` image the generator emits. The GitHub and Forgejo half
716
+ * follows the same rule since #3757.
717
+ */
718
+ async function postOrUpdateNote(ctx, token, marker, body, signal) {
719
+ const endpoint = notesEndpoint(ctx);
720
+ const existing = await findOwnedNote(ctx, token, marker, signal);
721
+ const payload = JSON.stringify({ body: `${marker}\n\n${body}` });
722
+ const res = existing
723
+ ? await gitlabRequest(`${endpoint}/${existing}`, token, { method: "PUT", body: payload }, signal)
724
+ : await gitlabRequest(endpoint, token, { method: "POST", body: payload }, signal);
725
+ const note = (await res.json());
726
+ // GitLab's note payload carries no web URL, unlike GitHub's comment. The
727
+ // anchor is how the UI itself addresses a note, so it is built rather than
728
+ // read; with no project web URL to build it from, the API path is at least
729
+ // a resolvable address for the thing that was written.
730
+ return ctx.webUrl
731
+ ? `${ctx.webUrl}/-/merge_requests/${ctx.iid}#note_${note.id}`
732
+ : `${endpoint}/${note.id}`;
733
+ }
734
+ /** GitLab's REST path for a project's issues. */
735
+ function issuesEndpoint(ctx) {
736
+ return `${ctx.api}/projects/${encodeURIComponent(ctx.project)}/issues`;
737
+ }
738
+ /**
739
+ * Find the issue this Op already owns in `ctx`'s project, by the same hidden
740
+ * marker `findOwnedNote` looks a merge-request note up by: the marker is the
741
+ * description's first line and the match is a prefix.
742
+ *
743
+ * `search`/`in=description` narrows the request server-side to issues whose
744
+ * description contains the marker, rather than paging every issue the
745
+ * project has ever opened — a long-lived project accumulates issues the way
746
+ * an active merge request accumulates notes, and the marker is exact text a
747
+ * full-text search matches reliably. The `startswith` check after the fetch
748
+ * still decides ownership, the same as the note lookup, since `search` finds
749
+ * the marker anywhere in the field and only a match at the very start is
750
+ * this Op's own issue rather than one that happens to quote it.
751
+ *
752
+ * Paged the way GitLab pages, following the `x-next-page` response header —
753
+ * see {@link findOwnedNote} for why a bounded loop rather than a guessed page
754
+ * count.
755
+ */
756
+ async function findOwnedIssue(ctx, token, marker, signal) {
757
+ const endpoint = issuesEndpoint(ctx);
758
+ const MAX_PAGES = 50;
759
+ for (let page = 1; page <= MAX_PAGES; page++) {
760
+ const res = await gitlabRequest(`${endpoint}?per_page=100&page=${page}&search=${encodeURIComponent(marker)}&in=description`, token, { method: "GET" }, signal);
761
+ const issues = (await res.json());
762
+ const owned = issues.find((issue) => (issue.description ?? "").startsWith(marker));
763
+ if (owned)
764
+ return owned.iid;
765
+ const next = res.headers.get("x-next-page")?.trim();
766
+ if (!next)
767
+ return undefined;
768
+ }
769
+ return undefined;
770
+ }
771
+ /**
772
+ * Open `title`/`body` as one issue in `ctx`'s project, or edit the issue this
773
+ * Op already owns there — {@link postOrUpdateNote}'s issue counterpart, and
774
+ * the same recipe: find by marker, PUT when there is one, POST when there is
775
+ * not, so a cron Op that finds drift every night carries one issue holding
776
+ * the current finding rather than a new one each run (#2292).
777
+ *
778
+ * `title` is written on every call, POST or PUT, so the issue's headline
779
+ * stays current (e.g. an entry count) even though only the description's
780
+ * marker is what makes the issue findable again.
781
+ */
782
+ async function postOrUpdateIssue(ctx, token, marker, title, body, signal) {
783
+ const endpoint = issuesEndpoint(ctx);
784
+ const existing = await findOwnedIssue(ctx, token, marker, signal);
785
+ const payload = JSON.stringify({ title, description: `${marker}\n\n${body}` });
786
+ const res = existing
787
+ ? await gitlabRequest(`${endpoint}/${existing}`, token, { method: "PUT", body: payload }, signal)
788
+ : await gitlabRequest(endpoint, token, { method: "POST", body: payload }, signal);
789
+ const issue = (await res.json());
790
+ // GitLab does return a `web_url` on an issue payload, unlike a note, but
791
+ // building it from the project's own web URL keeps this symmetric with
792
+ // `postOrUpdateNote` and needs no extra field pinned in tests.
793
+ return ctx.webUrl ? `${ctx.webUrl}/-/issues/${issue.iid}` : `${endpoint}/${issue.iid}`;
794
+ }
795
+ /**
796
+ * Map a `chant lifecycle plan --json` ChangeSet to reconcile entries, dropping
797
+ * `noop` entries (nothing to reconcile). Pure — exported for testing.
798
+ */
799
+ export function entriesFromPlan(planJson) {
800
+ const cs = JSON.parse(planJson);
801
+ return (cs.entries ?? [])
802
+ .filter((e) => e.action !== "noop")
803
+ .map((e) => ({ name: e.name, action: e.action, type: e.type }));
804
+ }
805
+ /** Derive reconcile entries from `chant lifecycle plan`. */
806
+ async function derivePlanEntries(env, owned, signal) {
807
+ const ownedFlag = owned ? " --owned" : "";
808
+ const { stdout } = await execAsync(`chant lifecycle plan ${shellQuote(env)}${ownedFlag} --json`, { signal });
809
+ return entriesFromPlan(stdout);
810
+ }
811
+ /**
812
+ * The marker the run will write and find its finding by, for the two modes
813
+ * that have one, or `undefined` for the two that do not (#2319, and its
814
+ * pre-merge review).
815
+ *
816
+ * Pure, and called before anything else in {@link reconcilePr} so that both
817
+ * refusals it can raise — no identity, unusable marker — happen before the
818
+ * activity shells out to anything.
819
+ *
820
+ * `issue` mode requires an identity: the Op's name, or a marker the caller
821
+ * keeps unique. `comment` mode does not, because its marker is scoped to one
822
+ * pull request rather than to the repository — see `ReconcilePrArgs.op` and
823
+ * {@link issueMarker} for that asymmetry and what is tracked separately.
824
+ * Both modes get the same {@link suppliedMarker} treatment of the field,
825
+ * because a blank or unusable marker is a property of the field, not of the
826
+ * mode reading it.
827
+ */
828
+ function resolveMarker(mode, args) {
829
+ if (mode === "issue") {
830
+ const supplied = suppliedMarker(args.marker);
831
+ const op = args.op?.trim();
832
+ if (!supplied && !op)
833
+ throw new Error(noIssueIdentityMessage(args.env));
834
+ return supplied ?? issueMarker(op, args.env);
835
+ }
836
+ if (mode === "comment") {
837
+ return suppliedMarker(args.marker) ?? commentMarker(args.env);
838
+ }
839
+ return undefined;
840
+ }
841
+ /**
842
+ * Reconcile activity: turn regenerated TypeScript into a reviewable artifact.
843
+ *
844
+ * - `report` — return the summary only; no git, no network.
845
+ * - `issue` — open or edit one issue describing the drift (no code change).
846
+ * On a GitHub Actions or Forgejo Actions job, or GitHub Enterprise Server,
847
+ * the OPEN issue already carrying this Op's hidden marker is edited in
848
+ * place and a new one opened only when there is none (#2297). On a GitLab
849
+ * CI job (any trigger — the point of this mode is a cron run that has no
850
+ * merge request), the same recipe against one GitLab issue (#2292). Neither
851
+ * branch ever closes the issue itself — see {@link postOrUpdateGithubIssue}
852
+ * for that decision and why. The marker names the Op as well as the env
853
+ * (#2319), so the step needs `op` or an explicit `marker` and fails by name
854
+ * with neither; an issue left over from the pre-#2319 env-only marker
855
+ * matches no Op's marker now and is left where it is, unedited, rather than
856
+ * adopted by whichever Op happens to run first.
857
+ * - `comment` — post the body as one comment on the pull request that
858
+ * triggered the run, editing that same comment on every re-run rather than
859
+ * stacking a new one (#2231) — on GitHub and on Forgejo alike (#2291), the
860
+ * same GitHub-shaped `issues/{n}/comments` calls against each host's own
861
+ * API base — or, on a GitLab `merge_request_event` pipeline, as one note on
862
+ * that merge request by the same recipe (#2256). Needs a pull-request- or
863
+ * merge-request-triggered run; fails by name when there is none. No code
864
+ * change, and the `pull-requests: write` the generated workflow already
865
+ * grants on that trigger is the whole scope it spends on GitHub and
866
+ * Forgejo; on GitLab the scope is whatever the token it is given carries.
867
+ * - `pull-request` — create a branch, regenerate source via
868
+ * `chant import --from <env>`, commit, push, and open a PR whose diff is the
869
+ * regenerated TypeScript. Never commits to the main branch.
870
+ *
871
+ * The body is {@link reconcileSummary}'s change-set table unless `args.body`
872
+ * supplies one, in which case that text is used verbatim and no plan is
873
+ * derived (chant #2087).
874
+ *
875
+ * Requires `chant` in the environment. `comment` and `issue` reach the forge
876
+ * over `fetch` and need nothing else in CI (#3757); `pull-request` needs `git`
877
+ * and `gh`, and so does an `issue` run outside any CI job, which leaves the
878
+ * credential to `gh auth login`.
879
+ */
880
+ export async function reconcilePr(args, signal) {
881
+ const mode = args.mode ?? "pull-request";
882
+ const owned = args.owned ?? false;
883
+ // Resolved first, ahead of the `chant lifecycle plan` shell-out below
884
+ // (#2319 pre-merge review). The identity refusal used to sit inside the
885
+ // `issue` branch, which is after that derivation, so the arg shape that
886
+ // actually reaches it — `{ env, op, mode, owned }`, `ReconcileOp`'s own,
887
+ // with no `body` to short-circuit the plan — ran a shell command before
888
+ // failing. `chant lifecycle plan` is read-only, so nothing was written; but
889
+ // a plan that fails first hands the operator a plan error in place of the
890
+ // named refusal this design rests on, which is the whole point of refusing
891
+ // by name.
892
+ const resolvedMarker = resolveMarker(mode, args);
893
+ // A caller-supplied body means the finding is already written, so there is
894
+ // nothing for `chant lifecycle plan` to tell us (#2087).
895
+ const entries = args.entries ?? (args.body !== undefined ? [] : await derivePlanEntries(args.env, owned, signal));
896
+ const summary = args.body ?? reconcileSummary(args.env, entries);
897
+ const title = args.title ?? `Reconcile ${args.env}: ${entries.length} change(s) from live`;
898
+ if (mode === "report") {
899
+ return { mode, summary, entries };
900
+ }
901
+ if (mode === "issue") {
902
+ // Non-null because `resolveMarker` returns a string for this mode or
903
+ // throws, and it already ran at the top of the function.
904
+ const marker = resolvedMarker;
905
+ // GitLab first, because its check is the narrow one: `CI_PROJECT_ID` is
906
+ // set on every GitLab CI job, and nothing outside GitLab CI sets it
907
+ // (#2292) — see `gitlabProjectContextFrom`.
908
+ const project = gitlabProjectContextFrom(process.env);
909
+ if (project) {
910
+ const token = gitlabNoteTokenFrom(process.env);
911
+ if (!token)
912
+ throw new Error(noGitlabIssueTokenMessage(project.project));
913
+ const issueUrl = await postOrUpdateIssue(project, token, marker, title, summary, signal);
914
+ return { mode, summary, entries, issueUrl };
915
+ }
916
+ // GitHub, GHES, or Forgejo next: any job that ran under GitHub Actions or
917
+ // Forgejo Actions carries GITHUB_REPOSITORY (#2297). The issue is kept
918
+ // through the forge client (#3757) — see postOrUpdateGithubIssue's doc
919
+ // comment for why the lookup pages rather than searches, and for the
920
+ // close decision.
921
+ const repo = process.env.GITHUB_REPOSITORY;
922
+ if (repo) {
923
+ const token = commentTokenFrom(process.env);
924
+ if (!token)
925
+ throw new Error(noIssueTokenMessage(repo));
926
+ const forge = githubShapedForgeFrom(process.env, repo, token, signal);
927
+ const issueUrl = await withTokenSource(token, () => forge.upsertIssue(marker, title, `${marker}\n\n${summary}`));
928
+ return { mode, summary, entries, issueUrl };
929
+ }
930
+ // Outside any known CI job (a local or manual invocation): fall back to
931
+ // gh's own ambient repo detection, unchanged from before #2297. The
932
+ // marker is still written here — it costs nothing, and means a later CI
933
+ // run of the same Op finds and edits this issue instead of opening a
934
+ // second one.
935
+ //
936
+ // This is the one path that does NOT go through `commentTokenFrom`
937
+ // (#2320), deliberately. It is reached only when the run is outside every
938
+ // CI job chant recognizes, which is where `gh auth login`'s stored
939
+ // credential is the credential — and that is a login `commentTokenFrom`
940
+ // cannot see, so refusing on a missing environment variable here would
941
+ // reject the exact setup the branch exists to serve. `gh` still reads
942
+ // GH_TOKEN and GITHUB_TOKEN off this process's own environment if they
943
+ // are set; what it does not get is CHANT_FORGEJO_TOKEN promoted into
944
+ // GH_TOKEN, because promoting it would silently outrank the login the
945
+ // operator is standing in front of.
946
+ const { stdout } = await execAsync(`gh issue create --title ${shellQuote(title)} --body ${shellQuote(`${marker}\n\n${summary}`)}`, { signal });
947
+ return { mode, summary, entries, issueUrl: stdout.trim() };
948
+ }
949
+ if (mode === "comment") {
950
+ // The trigger context is read here rather than passed in: a step's args
951
+ // are serialized at build time, and the pull request is not known until
952
+ // the run. Missing context is fatal — see `noPullRequestContextMessage`.
953
+ const marker = resolvedMarker;
954
+ // GitLab first, because its check is the narrow one: only a
955
+ // `merge_request_event` pipeline sets `CI_MERGE_REQUEST_IID` (#2256), so
956
+ // a run that has it is unambiguously the GitLab case, and a GitHub run
957
+ // never reaches this branch.
958
+ const mr = mergeRequestContextFrom(process.env);
959
+ if (mr) {
960
+ const token = gitlabNoteTokenFrom(process.env);
961
+ if (!token)
962
+ throw new Error(noGitlabNoteTokenMessage(mr.iid));
963
+ const commentUrl = await postOrUpdateNote(mr, token, marker, summary, signal);
964
+ return {
965
+ mode,
966
+ summary,
967
+ entries,
968
+ commentUrl,
969
+ mergeRequest: `${mr.path ?? mr.project}!${mr.iid}`,
970
+ };
971
+ }
972
+ const ctx = await resolvePullRequestContext();
973
+ const commentUrl = await postOrUpdateComment(ctx, marker, summary, signal);
974
+ return { mode, summary, entries, commentUrl, pullRequest: `${ctx.repo}#${ctx.number}` };
975
+ }
976
+ // pull-request
977
+ const branch = args.branch ?? reconcileBranchName(args.env);
978
+ const output = args.output ?? "./infra";
979
+ const ownedFlag = owned ? " --owned" : "";
980
+ // Never touch the main branch: cut a fresh branch first.
981
+ await execAsync(`git checkout -b ${shellQuote(branch)}`, { signal });
982
+ await execAsync(`chant import --from ${shellQuote(args.env)}${ownedFlag} --output ${shellQuote(output)} --force`, { signal });
983
+ await execAsync(`git add ${shellQuote(output)}`, { signal });
984
+ await execAsync(`git commit -m ${shellQuote(title)}`, { signal });
985
+ await execAsync(`git push -u origin ${shellQuote(branch)}`, { signal });
986
+ // `gh pr` speaks GraphQL, which Forgejo does not serve, and GitLab has no
987
+ // `gh`: a Forgejo or GitLab job opens the pull request over REST (#3798).
988
+ const forge = await restPullRequestForge(process.env, { fetch: (url, init) => globalThis.fetch(url, { ...init, ...(signal ? { signal } : {}) }) });
989
+ if (forge) {
990
+ const pr = await forge.openPullRequest({ head: branch, title, body: summary });
991
+ return { mode, branch, summary, entries, prUrl: pr.url ?? "" };
992
+ }
993
+ // `gh pr create` names no host, so on GitHub Enterprise Server it is told
994
+ // the job's own (#3780). See `./gh-host.ts`.
995
+ const { stdout } = await execAsync(`gh pr create --title ${shellQuote(title)} --body ${shellQuote(summary)} --head ${shellQuote(branch)}`, { signal, env: await ghHostEnv(process.env) });
996
+ return { mode, branch, summary, entries, prUrl: stdout.trim() };
997
+ }