@codyswann/lisa 4.48.0 → 4.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/all/copy-overwrite/scripts/check-conflict-markers.mjs +4 -1
  2. package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +163 -0
  3. package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +178 -1
  4. package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +89 -10
  5. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +44 -0
  6. package/all/copy-overwrite/scripts/lisa-lint-staged-preflight.mjs +1 -1
  7. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +24 -24
  8. package/all/copy-overwrite/scripts/lisa-work-item.mjs +490 -81
  9. package/cdk/package-lisa/package.lisa.json +45 -15
  10. package/dist/configs/vitest/base.d.ts +71 -1
  11. package/dist/configs/vitest/base.d.ts.map +1 -1
  12. package/dist/configs/vitest/base.js +141 -23
  13. package/dist/configs/vitest/base.js.map +1 -1
  14. package/dist/configs/vitest/fleet-admission-global-setup.d.ts +34 -0
  15. package/dist/configs/vitest/fleet-admission-global-setup.d.ts.map +1 -0
  16. package/dist/configs/vitest/fleet-admission-global-setup.js +123 -0
  17. package/dist/configs/vitest/fleet-admission-global-setup.js.map +1 -0
  18. package/dist/configs/vitest/fleet-admission.d.ts +129 -0
  19. package/dist/configs/vitest/fleet-admission.d.ts.map +1 -0
  20. package/dist/configs/vitest/fleet-admission.js +239 -0
  21. package/dist/configs/vitest/fleet-admission.js.map +1 -0
  22. package/dist/configs/vitest/synthesis-scratch-reclaim.d.ts +86 -0
  23. package/dist/configs/vitest/synthesis-scratch-reclaim.d.ts.map +1 -0
  24. package/dist/configs/vitest/synthesis-scratch-reclaim.js +226 -0
  25. package/dist/configs/vitest/synthesis-scratch-reclaim.js.map +1 -0
  26. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  27. package/dist/core/lisa-owned-hash-ledger.js +76 -0
  28. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  29. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  30. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  31. package/dist/core/upstream-evidence-manifest.js +129 -228
  32. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  33. package/dist/opencode/plugin-templates/lisa-block-direct-issue-create.ts +45 -1
  34. package/dist/opencode/plugin-templates/parity-safety-net.sh +44 -0
  35. package/expo/package-lisa/package.lisa.json +83 -53
  36. package/harper-fabric/package-lisa/package.lisa.json +62 -32
  37. package/nestjs/package-lisa/package.lisa.json +57 -27
  38. package/npm-package/package-lisa/package.lisa.json +43 -7
  39. package/package.json +11 -7
  40. package/phaser/copy-overwrite/ast-grep/rules/no-day-truncating-file-age-predicate.yml +50 -0
  41. package/phaser/package-lisa/package.lisa.json +62 -32
  42. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
  45. package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +4 -0
  46. package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +4 -0
  47. package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +4 -0
  48. package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +4 -1
  49. package/plugins/lisa/hooks/block-direct-issue-create.sh +89 -10
  50. package/plugins/lisa/hooks/discharge-work-item-gates.sh +7 -1
  51. package/plugins/lisa/hooks/parity-safety-net.sh +44 -0
  52. package/plugins/lisa/hooks/threshold-ratchet-families.mjs +12 -0
  53. package/plugins/lisa/rules/eager/00-rule-index.md +79 -0
  54. package/plugins/lisa/rules/eager/base-rules.md +10 -0
  55. package/plugins/lisa/rules/eager/config-resolution.md +1 -88
  56. package/plugins/lisa/rules/reference/automation-runbook-contract.md +97 -0
  57. package/plugins/lisa/rules/reference/bdd-e2e-coverage.md +56 -0
  58. package/plugins/lisa/rules/reference/blocker-containment.md +58 -0
  59. package/plugins/lisa/rules/reference/claim-archaeology.md +42 -0
  60. package/plugins/lisa/rules/reference/claim-evidence-mapping.md +83 -0
  61. package/plugins/lisa/rules/reference/claim-time-guards.md +37 -0
  62. package/plugins/lisa/rules/reference/config-resolution.md +97 -0
  63. package/plugins/lisa/rules/reference/control-reachability.md +44 -0
  64. package/plugins/lisa/rules/reference/convergent-review.md +31 -0
  65. package/plugins/lisa/rules/reference/credential-substrate-precedence.md +57 -0
  66. package/plugins/lisa/rules/reference/dependency-decision-records.md +37 -0
  67. package/plugins/lisa/rules/reference/dependency-internalization-kit.md +47 -0
  68. package/plugins/lisa/rules/reference/dependency-trust-classes.md +48 -0
  69. package/plugins/lisa/rules/reference/deployed-state-readback.md +52 -0
  70. package/plugins/lisa/rules/reference/derived-branch-plan.md +52 -0
  71. package/plugins/lisa/rules/reference/design-source-of-truth.md +43 -0
  72. package/plugins/lisa/rules/reference/design-value-binding.md +103 -0
  73. package/plugins/lisa/rules/reference/documentation-source-paths.md +18 -0
  74. package/plugins/lisa/rules/reference/leaf-only-lifecycle.md +75 -0
  75. package/plugins/lisa/rules/reference/observability-audit.md +20 -0
  76. package/plugins/lisa/rules/reference/prd-definition-of-ready.md +37 -0
  77. package/plugins/lisa/rules/reference/prd-lifecycle-rollup.md +36 -0
  78. package/plugins/lisa/rules/reference/pre-flight-autofill.md +88 -0
  79. package/plugins/lisa/rules/reference/promotion-contract.md +21 -0
  80. package/plugins/lisa/rules/reference/readiness-rubric.md +63 -0
  81. package/plugins/lisa/rules/reference/ready-role-filing.md +34 -0
  82. package/plugins/lisa/rules/reference/rejection-detection.md +69 -0
  83. package/plugins/lisa/rules/reference/repo-scope-split.md +48 -0
  84. package/plugins/lisa/rules/reference/reset-seed-coverage.md +55 -0
  85. package/plugins/lisa/rules/reference/usage-accounting.md +38 -0
  86. package/plugins/lisa/rules/reference/work-item-definition-of-ready.md +32 -0
  87. package/plugins/lisa/scripts/intake-blocker-reprobe.mjs +158 -1
  88. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
  89. package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +4 -0
  90. package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +4 -0
  91. package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +4 -0
  92. package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +4 -1
  93. package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +89 -10
  94. package/plugins/lisa-agy/hooks/parity-safety-net.sh +44 -0
  95. package/plugins/lisa-agy/plugin.json +1 -1
  96. package/plugins/lisa-agy/scripts/intake-blocker-reprobe.mjs +158 -1
  97. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
  98. package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +4 -0
  99. package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +4 -0
  100. package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +4 -0
  101. package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +4 -1
  102. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  104. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  105. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  106. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-copilot/hooks/block-direct-issue-create.sh +89 -10
  109. package/plugins/lisa-copilot/hooks/discharge-work-item-gates.sh +7 -1
  110. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +44 -0
  111. package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +12 -0
  112. package/plugins/lisa-copilot/rules/eager/00-rule-index.md +79 -0
  113. package/plugins/lisa-copilot/rules/eager/base-rules.md +10 -0
  114. package/plugins/lisa-copilot/rules/eager/config-resolution.md +1 -88
  115. package/plugins/lisa-copilot/rules/reference/automation-runbook-contract.md +97 -0
  116. package/plugins/lisa-copilot/rules/reference/bdd-e2e-coverage.md +56 -0
  117. package/plugins/lisa-copilot/rules/reference/blocker-containment.md +58 -0
  118. package/plugins/lisa-copilot/rules/reference/claim-archaeology.md +42 -0
  119. package/plugins/lisa-copilot/rules/reference/claim-evidence-mapping.md +83 -0
  120. package/plugins/lisa-copilot/rules/reference/claim-time-guards.md +37 -0
  121. package/plugins/lisa-copilot/rules/reference/config-resolution.md +97 -0
  122. package/plugins/lisa-copilot/rules/reference/control-reachability.md +44 -0
  123. package/plugins/lisa-copilot/rules/reference/convergent-review.md +31 -0
  124. package/plugins/lisa-copilot/rules/reference/credential-substrate-precedence.md +57 -0
  125. package/plugins/lisa-copilot/rules/reference/dependency-decision-records.md +37 -0
  126. package/plugins/lisa-copilot/rules/reference/dependency-internalization-kit.md +47 -0
  127. package/plugins/lisa-copilot/rules/reference/dependency-trust-classes.md +48 -0
  128. package/plugins/lisa-copilot/rules/reference/deployed-state-readback.md +52 -0
  129. package/plugins/lisa-copilot/rules/reference/derived-branch-plan.md +52 -0
  130. package/plugins/lisa-copilot/rules/reference/design-source-of-truth.md +43 -0
  131. package/plugins/lisa-copilot/rules/reference/design-value-binding.md +103 -0
  132. package/plugins/lisa-copilot/rules/reference/documentation-source-paths.md +18 -0
  133. package/plugins/lisa-copilot/rules/reference/leaf-only-lifecycle.md +75 -0
  134. package/plugins/lisa-copilot/rules/reference/observability-audit.md +20 -0
  135. package/plugins/lisa-copilot/rules/reference/prd-definition-of-ready.md +37 -0
  136. package/plugins/lisa-copilot/rules/reference/prd-lifecycle-rollup.md +36 -0
  137. package/plugins/lisa-copilot/rules/reference/pre-flight-autofill.md +88 -0
  138. package/plugins/lisa-copilot/rules/reference/promotion-contract.md +21 -0
  139. package/plugins/lisa-copilot/rules/reference/readiness-rubric.md +63 -0
  140. package/plugins/lisa-copilot/rules/reference/ready-role-filing.md +34 -0
  141. package/plugins/lisa-copilot/rules/reference/rejection-detection.md +69 -0
  142. package/plugins/lisa-copilot/rules/reference/repo-scope-split.md +48 -0
  143. package/plugins/lisa-copilot/rules/reference/reset-seed-coverage.md +55 -0
  144. package/plugins/lisa-copilot/rules/reference/usage-accounting.md +38 -0
  145. package/plugins/lisa-copilot/rules/reference/work-item-definition-of-ready.md +32 -0
  146. package/plugins/lisa-copilot/scripts/intake-blocker-reprobe.mjs +158 -1
  147. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
  148. package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +4 -0
  149. package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +4 -0
  150. package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +4 -0
  151. package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +4 -1
  152. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  153. package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +89 -10
  154. package/plugins/lisa-cursor/hooks/discharge-work-item-gates.sh +7 -1
  155. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +44 -0
  156. package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +12 -0
  157. package/plugins/lisa-cursor/rules/00-rule-index.mdc +84 -0
  158. package/plugins/lisa-cursor/rules/automation-runbook-contract-reference.mdc +97 -0
  159. package/plugins/lisa-cursor/rules/base-rules.mdc +10 -0
  160. package/plugins/lisa-cursor/rules/bdd-e2e-coverage-reference.mdc +56 -0
  161. package/plugins/lisa-cursor/rules/blocker-containment-reference.mdc +58 -0
  162. package/plugins/lisa-cursor/rules/claim-archaeology-reference.mdc +42 -0
  163. package/plugins/lisa-cursor/rules/claim-evidence-mapping-reference.mdc +83 -0
  164. package/plugins/lisa-cursor/rules/claim-time-guards-reference.mdc +37 -0
  165. package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +97 -0
  166. package/plugins/lisa-cursor/rules/config-resolution.mdc +1 -88
  167. package/plugins/lisa-cursor/rules/control-reachability-reference.mdc +44 -0
  168. package/plugins/lisa-cursor/rules/convergent-review-reference.mdc +31 -0
  169. package/plugins/lisa-cursor/rules/credential-substrate-precedence-reference.mdc +57 -0
  170. package/plugins/lisa-cursor/rules/dependency-decision-records-reference.mdc +37 -0
  171. package/plugins/lisa-cursor/rules/dependency-internalization-kit-reference.mdc +47 -0
  172. package/plugins/lisa-cursor/rules/dependency-trust-classes-reference.mdc +48 -0
  173. package/plugins/lisa-cursor/rules/deployed-state-readback-reference.mdc +52 -0
  174. package/plugins/lisa-cursor/rules/derived-branch-plan-reference.mdc +52 -0
  175. package/plugins/lisa-cursor/rules/design-source-of-truth-reference.mdc +43 -0
  176. package/plugins/lisa-cursor/rules/design-value-binding-reference.mdc +103 -0
  177. package/plugins/lisa-cursor/rules/documentation-source-paths-reference.mdc +18 -0
  178. package/plugins/lisa-cursor/rules/leaf-only-lifecycle-reference.mdc +75 -0
  179. package/plugins/lisa-cursor/rules/observability-audit-reference.mdc +20 -0
  180. package/plugins/lisa-cursor/rules/prd-definition-of-ready-reference.mdc +37 -0
  181. package/plugins/lisa-cursor/rules/prd-lifecycle-rollup-reference.mdc +36 -0
  182. package/plugins/lisa-cursor/rules/pre-flight-autofill-reference.mdc +88 -0
  183. package/plugins/lisa-cursor/rules/promotion-contract-reference.mdc +21 -0
  184. package/plugins/lisa-cursor/rules/readiness-rubric-reference.mdc +63 -0
  185. package/plugins/lisa-cursor/rules/ready-role-filing-reference.mdc +34 -0
  186. package/plugins/lisa-cursor/rules/rejection-detection-reference.mdc +69 -0
  187. package/plugins/lisa-cursor/rules/repo-scope-split-reference.mdc +48 -0
  188. package/plugins/lisa-cursor/rules/reset-seed-coverage-reference.mdc +55 -0
  189. package/plugins/lisa-cursor/rules/usage-accounting-reference.mdc +38 -0
  190. package/plugins/lisa-cursor/rules/work-item-definition-of-ready-reference.mdc +32 -0
  191. package/plugins/lisa-cursor/scripts/intake-blocker-reprobe.mjs +158 -1
  192. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
  193. package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +4 -0
  194. package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +4 -0
  195. package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +4 -0
  196. package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +4 -1
  197. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  198. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  199. package/plugins/lisa-expo-agy/plugin.json +1 -1
  200. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  201. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  202. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  203. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  204. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  205. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  206. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  207. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  208. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  209. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  210. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  211. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  212. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  213. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  214. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  215. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  216. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  217. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  218. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  219. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  220. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  221. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  222. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  223. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  224. package/plugins/lisa-rails-agy/plugin.json +1 -1
  225. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  226. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  227. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  228. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  229. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  230. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  231. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  232. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  233. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  234. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  235. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  236. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  237. package/plugins/src/base/hooks/block-direct-issue-create.sh +89 -10
  238. package/plugins/src/base/hooks/discharge-work-item-gates.sh +7 -1
  239. package/plugins/src/base/hooks/parity-safety-net.sh +44 -0
  240. package/plugins/src/base/hooks/threshold-ratchet-families.mjs +12 -0
  241. package/plugins/src/base/rules/eager/00-rule-index.md +79 -0
  242. package/plugins/src/base/rules/eager/base-rules.md +10 -0
  243. package/plugins/src/base/rules/eager/config-resolution.md +1 -88
  244. package/plugins/src/base/rules/reference/automation-runbook-contract.md +97 -0
  245. package/plugins/src/base/rules/reference/bdd-e2e-coverage.md +56 -0
  246. package/plugins/src/base/rules/reference/blocker-containment.md +58 -0
  247. package/plugins/src/base/rules/reference/claim-archaeology.md +42 -0
  248. package/plugins/src/base/rules/reference/claim-evidence-mapping.md +83 -0
  249. package/plugins/src/base/rules/reference/claim-time-guards.md +37 -0
  250. package/plugins/src/base/rules/reference/config-resolution.md +97 -0
  251. package/plugins/src/base/rules/reference/control-reachability.md +44 -0
  252. package/plugins/src/base/rules/reference/convergent-review.md +31 -0
  253. package/plugins/src/base/rules/reference/credential-substrate-precedence.md +57 -0
  254. package/plugins/src/base/rules/reference/dependency-decision-records.md +37 -0
  255. package/plugins/src/base/rules/reference/dependency-internalization-kit.md +47 -0
  256. package/plugins/src/base/rules/reference/dependency-trust-classes.md +48 -0
  257. package/plugins/src/base/rules/reference/deployed-state-readback.md +52 -0
  258. package/plugins/src/base/rules/reference/derived-branch-plan.md +52 -0
  259. package/plugins/src/base/rules/reference/design-source-of-truth.md +43 -0
  260. package/plugins/src/base/rules/reference/design-value-binding.md +103 -0
  261. package/plugins/src/base/rules/reference/documentation-source-paths.md +18 -0
  262. package/plugins/src/base/rules/reference/leaf-only-lifecycle.md +75 -0
  263. package/plugins/src/base/rules/reference/observability-audit.md +20 -0
  264. package/plugins/src/base/rules/reference/prd-definition-of-ready.md +37 -0
  265. package/plugins/src/base/rules/reference/prd-lifecycle-rollup.md +36 -0
  266. package/plugins/src/base/rules/reference/pre-flight-autofill.md +88 -0
  267. package/plugins/src/base/rules/reference/promotion-contract.md +21 -0
  268. package/plugins/src/base/rules/reference/readiness-rubric.md +63 -0
  269. package/plugins/src/base/rules/reference/ready-role-filing.md +34 -0
  270. package/plugins/src/base/rules/reference/rejection-detection.md +69 -0
  271. package/plugins/src/base/rules/reference/repo-scope-split.md +48 -0
  272. package/plugins/src/base/rules/reference/reset-seed-coverage.md +55 -0
  273. package/plugins/src/base/rules/reference/usage-accounting.md +38 -0
  274. package/plugins/src/base/rules/reference/work-item-definition-of-ready.md +32 -0
  275. package/plugins/src/base/scripts/intake-blocker-reprobe.mjs +158 -1
  276. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
  277. package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +4 -0
  278. package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +4 -0
  279. package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +4 -0
  280. package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +4 -1
  281. package/rails/copy-overwrite/ast-grep/rules/no-day-truncating-file-age-predicate.yml +50 -0
  282. package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +12 -0
  283. package/scripts/check-eager-rule-budget.mjs +176 -0
  284. package/scripts/check-generated-artifact-merge-coverage.mjs +7 -0
  285. package/scripts/check-rules-pairing.sh +45 -14
  286. package/scripts/check-shipped-surface-removals.mjs +851 -0
  287. package/scripts/cleanup-worktrees.sh +69 -16
  288. package/scripts/generate-export-surface.mjs +318 -0
  289. package/scripts/lib/shipped-surface.mjs +212 -0
  290. package/scripts/lisa-enforcement-fallback.sh +178 -1
  291. package/scripts/remedy-conformance.mjs +435 -0
  292. package/scripts/two-channel-couplings.json +2 -2
  293. package/typescript/copy-contents/.husky/pre-push +147 -2
  294. package/typescript/copy-overwrite/ast-grep/rules/no-day-truncating-file-age-predicate.yml +50 -0
  295. package/typescript/copy-overwrite/audit.ignore.config.json +0 -10
  296. package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +180 -29
  297. package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +12 -0
  298. package/typescript/package-lisa/package.lisa.json +72 -42
  299. package/plugins/lisa/rules/eager/automation-runbook-contract.md +0 -92
  300. package/plugins/lisa/rules/eager/bdd-e2e-coverage.md +0 -51
  301. package/plugins/lisa/rules/eager/blocker-containment.md +0 -53
  302. package/plugins/lisa/rules/eager/claim-archaeology.md +0 -37
  303. package/plugins/lisa/rules/eager/claim-evidence-mapping.md +0 -78
  304. package/plugins/lisa/rules/eager/claim-time-guards.md +0 -32
  305. package/plugins/lisa/rules/eager/control-reachability.md +0 -39
  306. package/plugins/lisa/rules/eager/convergent-review.md +0 -24
  307. package/plugins/lisa/rules/eager/credential-substrate-precedence.md +0 -54
  308. package/plugins/lisa/rules/eager/dependency-decision-records.md +0 -32
  309. package/plugins/lisa/rules/eager/dependency-internalization-kit.md +0 -42
  310. package/plugins/lisa/rules/eager/dependency-trust-classes.md +0 -43
  311. package/plugins/lisa/rules/eager/deployed-state-readback.md +0 -47
  312. package/plugins/lisa/rules/eager/derived-branch-plan.md +0 -47
  313. package/plugins/lisa/rules/eager/design-source-of-truth.md +0 -38
  314. package/plugins/lisa/rules/eager/design-value-binding.md +0 -98
  315. package/plugins/lisa/rules/eager/documentation-source-paths.md +0 -13
  316. package/plugins/lisa/rules/eager/factory-model.md +0 -41
  317. package/plugins/lisa/rules/eager/leaf-only-lifecycle.md +0 -70
  318. package/plugins/lisa/rules/eager/observability-audit.md +0 -15
  319. package/plugins/lisa/rules/eager/prd-definition-of-ready.md +0 -32
  320. package/plugins/lisa/rules/eager/prd-lifecycle-rollup.md +0 -31
  321. package/plugins/lisa/rules/eager/pre-flight-autofill.md +0 -84
  322. package/plugins/lisa/rules/eager/project-learnings.md +0 -22
  323. package/plugins/lisa/rules/eager/promotion-contract.md +0 -18
  324. package/plugins/lisa/rules/eager/readiness-rubric.md +0 -58
  325. package/plugins/lisa/rules/eager/ready-role-filing.md +0 -29
  326. package/plugins/lisa/rules/eager/rejection-detection.md +0 -64
  327. package/plugins/lisa/rules/eager/repo-scope-split.md +0 -43
  328. package/plugins/lisa/rules/eager/reset-seed-coverage.md +0 -50
  329. package/plugins/lisa/rules/eager/usage-accounting.md +0 -33
  330. package/plugins/lisa/rules/eager/work-item-definition-of-ready.md +0 -27
  331. package/plugins/lisa-copilot/rules/eager/automation-runbook-contract.md +0 -92
  332. package/plugins/lisa-copilot/rules/eager/bdd-e2e-coverage.md +0 -51
  333. package/plugins/lisa-copilot/rules/eager/blocker-containment.md +0 -53
  334. package/plugins/lisa-copilot/rules/eager/claim-archaeology.md +0 -37
  335. package/plugins/lisa-copilot/rules/eager/claim-evidence-mapping.md +0 -78
  336. package/plugins/lisa-copilot/rules/eager/claim-time-guards.md +0 -32
  337. package/plugins/lisa-copilot/rules/eager/control-reachability.md +0 -39
  338. package/plugins/lisa-copilot/rules/eager/convergent-review.md +0 -24
  339. package/plugins/lisa-copilot/rules/eager/credential-substrate-precedence.md +0 -54
  340. package/plugins/lisa-copilot/rules/eager/dependency-decision-records.md +0 -32
  341. package/plugins/lisa-copilot/rules/eager/dependency-internalization-kit.md +0 -42
  342. package/plugins/lisa-copilot/rules/eager/dependency-trust-classes.md +0 -43
  343. package/plugins/lisa-copilot/rules/eager/deployed-state-readback.md +0 -47
  344. package/plugins/lisa-copilot/rules/eager/derived-branch-plan.md +0 -47
  345. package/plugins/lisa-copilot/rules/eager/design-source-of-truth.md +0 -38
  346. package/plugins/lisa-copilot/rules/eager/design-value-binding.md +0 -98
  347. package/plugins/lisa-copilot/rules/eager/documentation-source-paths.md +0 -13
  348. package/plugins/lisa-copilot/rules/eager/factory-model.md +0 -41
  349. package/plugins/lisa-copilot/rules/eager/leaf-only-lifecycle.md +0 -70
  350. package/plugins/lisa-copilot/rules/eager/observability-audit.md +0 -15
  351. package/plugins/lisa-copilot/rules/eager/prd-definition-of-ready.md +0 -32
  352. package/plugins/lisa-copilot/rules/eager/prd-lifecycle-rollup.md +0 -31
  353. package/plugins/lisa-copilot/rules/eager/pre-flight-autofill.md +0 -84
  354. package/plugins/lisa-copilot/rules/eager/project-learnings.md +0 -22
  355. package/plugins/lisa-copilot/rules/eager/promotion-contract.md +0 -18
  356. package/plugins/lisa-copilot/rules/eager/readiness-rubric.md +0 -58
  357. package/plugins/lisa-copilot/rules/eager/ready-role-filing.md +0 -29
  358. package/plugins/lisa-copilot/rules/eager/rejection-detection.md +0 -64
  359. package/plugins/lisa-copilot/rules/eager/repo-scope-split.md +0 -43
  360. package/plugins/lisa-copilot/rules/eager/reset-seed-coverage.md +0 -50
  361. package/plugins/lisa-copilot/rules/eager/usage-accounting.md +0 -33
  362. package/plugins/lisa-copilot/rules/eager/work-item-definition-of-ready.md +0 -27
  363. package/plugins/lisa-cursor/rules/automation-runbook-contract.mdc +0 -97
  364. package/plugins/lisa-cursor/rules/bdd-e2e-coverage.mdc +0 -56
  365. package/plugins/lisa-cursor/rules/blocker-containment.mdc +0 -58
  366. package/plugins/lisa-cursor/rules/claim-archaeology.mdc +0 -42
  367. package/plugins/lisa-cursor/rules/claim-evidence-mapping.mdc +0 -83
  368. package/plugins/lisa-cursor/rules/claim-time-guards.mdc +0 -37
  369. package/plugins/lisa-cursor/rules/control-reachability.mdc +0 -44
  370. package/plugins/lisa-cursor/rules/convergent-review.mdc +0 -29
  371. package/plugins/lisa-cursor/rules/credential-substrate-precedence.mdc +0 -59
  372. package/plugins/lisa-cursor/rules/dependency-decision-records.mdc +0 -37
  373. package/plugins/lisa-cursor/rules/dependency-internalization-kit.mdc +0 -47
  374. package/plugins/lisa-cursor/rules/dependency-trust-classes.mdc +0 -48
  375. package/plugins/lisa-cursor/rules/deployed-state-readback.mdc +0 -52
  376. package/plugins/lisa-cursor/rules/derived-branch-plan.mdc +0 -52
  377. package/plugins/lisa-cursor/rules/design-source-of-truth.mdc +0 -43
  378. package/plugins/lisa-cursor/rules/design-value-binding.mdc +0 -103
  379. package/plugins/lisa-cursor/rules/documentation-source-paths.mdc +0 -18
  380. package/plugins/lisa-cursor/rules/factory-model.mdc +0 -46
  381. package/plugins/lisa-cursor/rules/leaf-only-lifecycle.mdc +0 -75
  382. package/plugins/lisa-cursor/rules/observability-audit.mdc +0 -20
  383. package/plugins/lisa-cursor/rules/prd-definition-of-ready.mdc +0 -37
  384. package/plugins/lisa-cursor/rules/prd-lifecycle-rollup.mdc +0 -36
  385. package/plugins/lisa-cursor/rules/pre-flight-autofill.mdc +0 -89
  386. package/plugins/lisa-cursor/rules/project-learnings.mdc +0 -27
  387. package/plugins/lisa-cursor/rules/promotion-contract.mdc +0 -23
  388. package/plugins/lisa-cursor/rules/readiness-rubric.mdc +0 -63
  389. package/plugins/lisa-cursor/rules/ready-role-filing.mdc +0 -34
  390. package/plugins/lisa-cursor/rules/rejection-detection.mdc +0 -69
  391. package/plugins/lisa-cursor/rules/repo-scope-split.mdc +0 -48
  392. package/plugins/lisa-cursor/rules/reset-seed-coverage.mdc +0 -55
  393. package/plugins/lisa-cursor/rules/usage-accounting.mdc +0 -38
  394. package/plugins/lisa-cursor/rules/work-item-definition-of-ready.mdc +0 -32
  395. package/plugins/src/base/rules/eager/automation-runbook-contract.md +0 -92
  396. package/plugins/src/base/rules/eager/bdd-e2e-coverage.md +0 -51
  397. package/plugins/src/base/rules/eager/blocker-containment.md +0 -53
  398. package/plugins/src/base/rules/eager/claim-archaeology.md +0 -37
  399. package/plugins/src/base/rules/eager/claim-evidence-mapping.md +0 -78
  400. package/plugins/src/base/rules/eager/claim-time-guards.md +0 -32
  401. package/plugins/src/base/rules/eager/control-reachability.md +0 -39
  402. package/plugins/src/base/rules/eager/convergent-review.md +0 -24
  403. package/plugins/src/base/rules/eager/credential-substrate-precedence.md +0 -54
  404. package/plugins/src/base/rules/eager/dependency-decision-records.md +0 -32
  405. package/plugins/src/base/rules/eager/dependency-internalization-kit.md +0 -42
  406. package/plugins/src/base/rules/eager/dependency-trust-classes.md +0 -43
  407. package/plugins/src/base/rules/eager/deployed-state-readback.md +0 -47
  408. package/plugins/src/base/rules/eager/derived-branch-plan.md +0 -47
  409. package/plugins/src/base/rules/eager/design-source-of-truth.md +0 -38
  410. package/plugins/src/base/rules/eager/design-value-binding.md +0 -98
  411. package/plugins/src/base/rules/eager/documentation-source-paths.md +0 -13
  412. package/plugins/src/base/rules/eager/factory-model.md +0 -41
  413. package/plugins/src/base/rules/eager/leaf-only-lifecycle.md +0 -70
  414. package/plugins/src/base/rules/eager/observability-audit.md +0 -15
  415. package/plugins/src/base/rules/eager/prd-definition-of-ready.md +0 -32
  416. package/plugins/src/base/rules/eager/prd-lifecycle-rollup.md +0 -31
  417. package/plugins/src/base/rules/eager/pre-flight-autofill.md +0 -84
  418. package/plugins/src/base/rules/eager/project-learnings.md +0 -22
  419. package/plugins/src/base/rules/eager/promotion-contract.md +0 -18
  420. package/plugins/src/base/rules/eager/readiness-rubric.md +0 -58
  421. package/plugins/src/base/rules/eager/ready-role-filing.md +0 -29
  422. package/plugins/src/base/rules/eager/rejection-detection.md +0 -64
  423. package/plugins/src/base/rules/eager/repo-scope-split.md +0 -43
  424. package/plugins/src/base/rules/eager/reset-seed-coverage.md +0 -50
  425. package/plugins/src/base/rules/eager/usage-accounting.md +0 -33
  426. package/plugins/src/base/rules/eager/work-item-definition-of-ready.md +0 -27
@@ -0,0 +1,851 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Deterministic gate that nothing disappears from a SHIPPED surface without a
4
+ * record a consumer can act on (CodySwannGT/lisa#3849).
5
+ *
6
+ * ## Why removal is structurally harder than addition here
7
+ *
8
+ * `deepMergeWithArrayUnion` unions arrays and the copy lanes overwrite; none of
9
+ * them delete. A template edit can therefore ADD to a consumer's tree, and can
10
+ * never remove from it. The only mechanisms that reach an installed base are a
11
+ * `deletions.json` entry and a migration under `src/migrations/`. So a removal
12
+ * made upstream with neither is not a partial fix - downstream it does nothing
13
+ * at all, silently, while looking complete in the diff.
14
+ *
15
+ * That asymmetry has two failure shapes and this gate covers both.
16
+ *
17
+ * ### 1. A shipped FILE vanishes upstream
18
+ *
19
+ * Every host that ever applied a release carrying it keeps it, at its
20
+ * destination path, forever. No later upgrade removes it.
21
+ *
22
+ * ### 2. A shipped script's PUBLIC SURFACE shrinks
23
+ *
24
+ * This is the one that was actually reported, and the ticket's first diagnosis
25
+ * of it was wrong in a way worth recording. `SNAPSHOT_MAX_AGE_DAYS` was dropped
26
+ * from `typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs`
27
+ * (commit `f13bb00`, released in v4.31.0) and a consumer's own
28
+ * `scripts/check-required-checks-baseline.mjs` imported it. That importer was
29
+ * never a Lisa file - `git rev-list --all --objects` finds no such path in this
30
+ * repository's entire history - so a shipped-to-shipped import scan would not
31
+ * have caught it, and a deletions entry would have made it WORSE by propagating
32
+ * a removal into a host that had wired the thing to three npm scripts, a
33
+ * workflow, and a test.
34
+ *
35
+ * A missing NAMED export is not a syntax error in either file. It fails only
36
+ * when the module graph is linked, so it passes anything that lints or
37
+ * type-checks the files one at a time and then surfaces in whichever job runs
38
+ * the importer first - in the reported case `lint`, which points nowhere near
39
+ * the export.
40
+ *
41
+ * ## The two dispositions, and why one manifest could not carry both
42
+ *
43
+ * Before declaring any shipped path for deletion the question is not "does
44
+ * upstream still need it" but "could a consumer have wired this into their own
45
+ * scripts?" - and for an executable under `scripts/` the answer is usually yes.
46
+ * Those two answers need opposite handling, and `deletions.json` makes no such
47
+ * distinction:
48
+ *
49
+ * - PROPAGATE - a `deletions.json` entry, for a path no host could have bound
50
+ * to. Nothing further is required; the manifest IS the record.
51
+ * - RETAIN AND NOTIFY - an entry in the removal ledger, for a consumer-
52
+ * bindable executable and for every removed export. Deletion would break a
53
+ * working host capability, so the removal is recorded with a consumer-
54
+ * facing note instead.
55
+ *
56
+ * A consumer-bindable executable may still be propagated, but only with a
57
+ * `force` reason in its `deletions.json` - the same override that already
58
+ * exists for overriding workflow ownership, reused rather than reinvented.
59
+ *
60
+ * ## Both arms, or it is not a gate
61
+ *
62
+ * A removal WITH a correct record passes, an ordinary addition passes, and a
63
+ * healthy import pair passes. A check that flagged every deletion would be
64
+ * disabled inside a week, and this repository has a live precedent for exactly
65
+ * that failure of proportion.
66
+ *
67
+ * ## Baseline
68
+ *
69
+ * The window is `baseline..HEAD`, where `baseline` is a tag pinned in the
70
+ * ledger (the first tag of the supported major line). Advancing it retires
71
+ * obligations, so it is deliberately a one-line, reviewable edit in the ledger
72
+ * diff rather than something derived from the clock. The gate verifies the tag
73
+ * exists and is an ancestor of HEAD; a baseline it cannot resolve is exit 2,
74
+ * never a quiet pass.
75
+ *
76
+ * ## Determinism guarantees
77
+ *
78
+ * - zero third-party dependencies (Node built-ins only),
79
+ * - no network access,
80
+ * - no `Date` / `Math.random`,
81
+ * - the file lists come from `git ls-tree`, so the gate compares what a
82
+ * release actually carried rather than whatever is loose in a worktree.
83
+ *
84
+ * Discovering zero shipped files is exit 2, not a clean pass. A gate that
85
+ * passes because it could not look is the failure mode this file exists to
86
+ * prevent.
87
+ *
88
+ * CLI:
89
+ * node scripts/check-shipped-surface-removals.mjs [--root <dir>] [--since <ref>] [--json]
90
+ *
91
+ * Exit codes:
92
+ * 0 - every removal from a shipped surface is governed.
93
+ * 1 - at least one ungoverned removal, mis-propagated executable, unresolved
94
+ * shipped-to-shipped named import, or self-contradicting ledger entry.
95
+ * 2 - operational error: unknown flag, `--root` absent or not a git
96
+ * repository, git unavailable, an unreadable ledger or deletions
97
+ * manifest, an unresolvable baseline, or zero shipped files discovered.
98
+ *
99
+ * @module scripts/check-shipped-surface-removals
100
+ */
101
+ import fs from "node:fs";
102
+ import path from "node:path";
103
+ import process from "node:process";
104
+ import { fileURLToPath } from "node:url";
105
+
106
+ import {
107
+ DELIVERY_LANES,
108
+ UsageError,
109
+ ancestryChain,
110
+ destinationPath,
111
+ effectiveDeletions,
112
+ matchDeletion,
113
+ } from "./check-delivery-deletion-conflicts.mjs";
114
+ import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
115
+ import { invokedAsScript } from "./lib/invoked-as-script.mjs";
116
+ import {
117
+ hasUsableNote,
118
+ indexRemovals,
119
+ isConsumerBindable,
120
+ parseNamedExports,
121
+ parseRelativeNamedImports,
122
+ removalKey,
123
+ resolveRelative,
124
+ } from "./lib/shipped-surface.mjs";
125
+
126
+ const REPO_ROOT = path.resolve(
127
+ path.dirname(fileURLToPath(import.meta.url)),
128
+ ".."
129
+ );
130
+
131
+ /** The removal ledger, relative to the repository root. */
132
+ export const LEDGER_PATH = "shipped-removals.json";
133
+
134
+ /** Extensions whose export surface this gate reads. */
135
+ const MODULE_EXTENSIONS = Object.freeze([".cjs", ".js", ".mjs"]);
136
+
137
+ /** Max bytes of git output (the full tree listing is ~0.3 MB today). */
138
+ const MAX_GIT_OUTPUT_BYTES = 64 * 1024 * 1024;
139
+
140
+ /**
141
+ * Run one git command under the shared timeout, turning any failure into a
142
+ * `UsageError` so an unverifiable state exits 2 rather than scanning nothing.
143
+ *
144
+ * @param {string} root - repository root.
145
+ * @param {readonly string[]} args - git arguments after `-C root`.
146
+ * @param {string} what - short description, for the error message.
147
+ * @returns {string} stdout.
148
+ */
149
+ function git(root, args, what) {
150
+ try {
151
+ return boundedExecFileSync("git", ["-C", root, ...args], {
152
+ encoding: "utf8",
153
+ maxBuffer: MAX_GIT_OUTPUT_BYTES,
154
+ stdio: ["ignore", "pipe", "ignore"],
155
+ });
156
+ } catch (error) {
157
+ throw new UsageError(`${what}: ${error.message}`);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Split a repo-relative path into its stack, lane, and lane-relative parts,
163
+ * or null when it is not inside a delivery lane.
164
+ *
165
+ * @param {string} file - repo-relative path.
166
+ * @returns {{ stack: string, lane: string, relative: string } | null} the parts.
167
+ */
168
+ export function shippedParts(file) {
169
+ const [stack, lane, ...rest] = file.split("/");
170
+ if (rest.length === 0 || !DELIVERY_LANES.includes(lane)) return null;
171
+ return { lane, relative: rest.join("/"), stack };
172
+ }
173
+
174
+ /**
175
+ * Every tracked file inside a delivery lane at `ref`, keyed by repo path.
176
+ *
177
+ * @param {string} root - repository root.
178
+ * @param {string} ref - a git ref.
179
+ * @returns {Map<string, { stack: string, lane: string, destination: string }>} shipped files.
180
+ */
181
+ export function shippedFilesAt(root, ref) {
182
+ const stdout = git(
183
+ root,
184
+ ["ls-tree", "-r", "-z", "--name-only", ref],
185
+ `could not list the tree at ${ref}`
186
+ );
187
+ const shipped = new Map();
188
+ for (const file of stdout.split("\0")) {
189
+ const parts = file === "" ? null : shippedParts(file);
190
+ if (parts === null) continue;
191
+ shipped.set(file, {
192
+ destination: destinationPath(parts.lane, parts.relative),
193
+ lane: parts.lane,
194
+ stack: parts.stack,
195
+ });
196
+ }
197
+ return shipped;
198
+ }
199
+
200
+ /**
201
+ * Effective deletion set and `force` reasons for every stack at HEAD.
202
+ *
203
+ * @param {string} root - repository root.
204
+ * @returns {Map<string, { deleted: Set<string>, force: Map<string, string> }>} per stack.
205
+ */
206
+ export function readDeletionManifests(root) {
207
+ const manifests = new Map();
208
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
209
+ if (!entry.isDirectory()) continue;
210
+ const label = `${entry.name}/deletions.json`;
211
+ const absolute = path.join(root, label);
212
+ if (!fs.existsSync(absolute)) continue;
213
+ const parsed = parseJson(absolute, label);
214
+ const force = parsed.force ?? {};
215
+ if (force === null || typeof force !== "object" || Array.isArray(force)) {
216
+ throw new UsageError(`${label}: "force" must be an object`);
217
+ }
218
+ manifests.set(entry.name, {
219
+ deleted: effectiveDeletions(parsed, label),
220
+ force: new Map(Object.entries(force)),
221
+ });
222
+ }
223
+ return manifests;
224
+ }
225
+
226
+ /**
227
+ * Read and parse a JSON file, turning any failure into a `UsageError`.
228
+ *
229
+ * @param {string} absolute - absolute path.
230
+ * @param {string} label - repo-relative path, for error messages.
231
+ * @returns {Record<string, unknown>} the parsed object.
232
+ */
233
+ function parseJson(absolute, label) {
234
+ let raw;
235
+ try {
236
+ raw = fs.readFileSync(absolute, "utf8");
237
+ } catch (error) {
238
+ throw new UsageError(`${label}: could not read - ${error.message}`);
239
+ }
240
+ let parsed;
241
+ try {
242
+ parsed = JSON.parse(raw);
243
+ } catch (error) {
244
+ throw new UsageError(`${label}: could not parse - ${error.message}`);
245
+ }
246
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
247
+ throw new UsageError(`${label}: expected a JSON object`);
248
+ }
249
+ return parsed;
250
+ }
251
+
252
+ /**
253
+ * Load and validate the removal ledger.
254
+ *
255
+ * @param {string} root - repository root.
256
+ * @returns {{ baseline: string, removals: Array<Record<string, unknown>> }} the ledger.
257
+ */
258
+ export function loadLedger(root) {
259
+ const parsed = parseJson(path.join(root, LEDGER_PATH), LEDGER_PATH);
260
+ const { baseline, removals } = parsed;
261
+ if (typeof baseline !== "string" || baseline.trim() === "") {
262
+ throw new UsageError(`${LEDGER_PATH}: "baseline" must be a non-empty ref`);
263
+ }
264
+ if (!Array.isArray(removals)) {
265
+ throw new UsageError(`${LEDGER_PATH}: "removals" must be an array`);
266
+ }
267
+ for (const entry of removals) {
268
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
269
+ throw new UsageError(`${LEDGER_PATH}: every removal must be an object`);
270
+ }
271
+ if (typeof entry.path !== "string" || entry.path === "") {
272
+ throw new UsageError(`${LEDGER_PATH}: every removal needs a "path"`);
273
+ }
274
+ if (entry.export !== undefined && typeof entry.export !== "string") {
275
+ throw new UsageError(
276
+ `${LEDGER_PATH}: "export" on ${entry.path} must be a string`
277
+ );
278
+ }
279
+ }
280
+ return { baseline, removals };
281
+ }
282
+
283
+ /**
284
+ * Resolve the baseline ref, refusing anything this gate cannot compare against.
285
+ *
286
+ * @param {string} root - repository root.
287
+ * @param {string} ref - the requested baseline.
288
+ * @returns {string} the resolved commit sha.
289
+ */
290
+ export function resolveBaseline(root, ref) {
291
+ const sha = git(
292
+ root,
293
+ ["rev-parse", "--verify", `${ref}^{commit}`],
294
+ `baseline ${ref} does not resolve to a commit`
295
+ ).trim();
296
+ try {
297
+ boundedExecFileSync(
298
+ "git",
299
+ ["-C", root, "merge-base", "--is-ancestor", sha, "HEAD"],
300
+ { stdio: "ignore" }
301
+ );
302
+ } catch {
303
+ throw new UsageError(
304
+ `baseline ${ref} is not an ancestor of HEAD, so the window is not a ` +
305
+ `release history this gate can read`
306
+ );
307
+ }
308
+ return sha;
309
+ }
310
+
311
+ /**
312
+ * The release a path's removal shipped in, so the report can name the reach of
313
+ * the consequence rather than only the file.
314
+ *
315
+ * @param {string} root - repository root.
316
+ * @param {string} baseline - the baseline sha.
317
+ * @param {string} file - the removed repo path.
318
+ * @param {string} [symbol] - the removed export; when given, the commit is
319
+ * found by pickaxe on that symbol rather than by last touch of the file, so
320
+ * an unrelated later edit does not get the blame.
321
+ * @returns {string} a tag name, or a description of why there is none.
322
+ */
323
+ export function removalRelease(root, baseline, file, symbol) {
324
+ const pickaxe = symbol === undefined ? [] : [`-S${symbol}`];
325
+ const sha = git(
326
+ root,
327
+ ["log", "-1", "--format=%H", ...pickaxe, `${baseline}..HEAD`, "--", file],
328
+ `could not date the removal of ${file}`
329
+ ).trim();
330
+ if (sha === "") return "an unidentified commit";
331
+ try {
332
+ const described = boundedExecFileSync(
333
+ "git",
334
+ ["-C", root, "describe", "--contains", "--match", "v*", sha],
335
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
336
+ );
337
+ return described.trim().split("~")[0].split("^")[0];
338
+ } catch {
339
+ return `${sha.slice(0, 9)} (not yet released)`;
340
+ }
341
+ }
342
+
343
+ /**
344
+ * Whether some stack active alongside `stack` deletes `destination` for every
345
+ * consumer of it, and whether that deletion carries a `force` reason.
346
+ *
347
+ * @param {string} stack - the stack whose lane shipped the path.
348
+ * @param {string} destination - the path in the consumer's tree.
349
+ * @param {Map<string, { deleted: Set<string>, force: Map<string, string> }>} manifests
350
+ * per-stack deletion manifests.
351
+ * @returns {{ by: string, forced: boolean } | null} the governing deletion.
352
+ */
353
+ export function findGoverningDeletion(stack, destination, manifests) {
354
+ for (const candidate of ancestryChain(stack)) {
355
+ const manifest = manifests.get(candidate);
356
+ if (manifest === undefined) continue;
357
+ if (matchDeletion(destination, manifest.deleted) === null) continue;
358
+ return { by: candidate, forced: manifest.force.has(destination) };
359
+ }
360
+ return null;
361
+ }
362
+
363
+ /**
364
+ * Findings for shipped FILES that existed at the baseline and are gone at HEAD.
365
+ *
366
+ * @param {{ root: string, baseline: string, before: Map<string, object>, after: Map<string, object>, manifests: Map<string, object>, ledger: Map<string, object> }} input
367
+ * the resolved scan inputs.
368
+ * @returns {Array<Record<string, unknown>>} one row per ungoverned removal.
369
+ */
370
+ export function findRemovedPaths(input) {
371
+ const rows = [];
372
+ for (const [file, meta] of input.before) {
373
+ if (input.after.has(file)) continue;
374
+ const deletion = findGoverningDeletion(
375
+ meta.stack,
376
+ meta.destination,
377
+ input.manifests
378
+ );
379
+ const noted = hasUsableNote(input.ledger.get(removalKey(file)));
380
+ const verdict = classifyRemovedPath(meta.destination, deletion, noted);
381
+ if (verdict === null) continue;
382
+ rows.push({
383
+ destination: meta.destination,
384
+ kind: verdict.kind,
385
+ path: file,
386
+ reason: verdict.reason,
387
+ release: removalRelease(input.root, input.baseline, file),
388
+ });
389
+ }
390
+ return rows.sort((left, right) => left.path.localeCompare(right.path));
391
+ }
392
+
393
+ /**
394
+ * The verdict on one removed shipped file.
395
+ *
396
+ * @param {string} destination - the path in the consumer's tree.
397
+ * @param {{ by: string, forced: boolean } | null} deletion - the governing deletion.
398
+ * @param {boolean} noted - whether the ledger records the removal.
399
+ * @returns {{ kind: string, reason: string } | null} the finding, or null when governed.
400
+ */
401
+ export function classifyRemovedPath(destination, deletion, noted) {
402
+ const bindable = isConsumerBindable(destination);
403
+ if (bindable && deletion !== null && !deletion.forced) {
404
+ return {
405
+ kind: "propagated-bindable",
406
+ reason:
407
+ `${deletion.by}/deletions.json propagates this removal, but a host may ` +
408
+ `have wired ${destination} into its own package.json - deleting it ` +
409
+ `takes away a working capability. Move the entry to "keep" and record ` +
410
+ `the removal in ${LEDGER_PATH}, or give it a "force" reason saying why ` +
411
+ `the removal overrides host wiring.`,
412
+ };
413
+ }
414
+ if (deletion !== null) return null;
415
+ if (noted) return null;
416
+ return bindable
417
+ ? {
418
+ kind: "unrecorded-bindable",
419
+ reason:
420
+ `A host may have wired ${destination} into its own package.json, so ` +
421
+ `this must NOT be propagated as a deletion. Record it in ` +
422
+ `${LEDGER_PATH} with a consumer-facing note instead.`,
423
+ }
424
+ : {
425
+ kind: "unrecorded",
426
+ reason:
427
+ `No deletions manifest active for this stack removes ${destination}, ` +
428
+ `and ${LEDGER_PATH} does not record it. The copy lanes overwrite and ` +
429
+ `never delete, so every host that applied a release carrying this ` +
430
+ `path keeps it at ${destination} and no later upgrade removes it.`,
431
+ };
432
+ }
433
+
434
+ /**
435
+ * Findings for named EXPORTS present at the baseline and gone at HEAD.
436
+ *
437
+ * @param {{ root: string, baseline: string, before: Map<string, object>, after: Map<string, object>, ledger: Map<string, object>, importers: Map<string, number> }} input
438
+ * the resolved scan inputs.
439
+ * @returns {Array<Record<string, unknown>>} one row per unrecorded export removal.
440
+ */
441
+ export function findRemovedExports(input) {
442
+ const rows = [];
443
+ for (const file of changedModules(input.root, input.baseline)) {
444
+ if (!input.before.has(file) || !input.after.has(file)) continue;
445
+ const was = parseNamedExports(showFile(input.root, input.baseline, file));
446
+ const now = parseNamedExports(showFile(input.root, "HEAD", file));
447
+ for (const name of [...was.names].sort()) {
448
+ if (now.names.has(name)) continue;
449
+ if (hasUsableNote(input.ledger.get(removalKey(file, name)))) continue;
450
+ rows.push({
451
+ export: name,
452
+ importers: input.importers.get(file) ?? 0,
453
+ kind: "unrecorded-export",
454
+ path: file,
455
+ release: removalRelease(input.root, input.baseline, file, name),
456
+ });
457
+ }
458
+ }
459
+ return rows;
460
+ }
461
+
462
+ /**
463
+ * Shipped module files that changed between the baseline and HEAD. Comparing
464
+ * only these keeps the export scan to a handful of blob reads rather than one
465
+ * per shipped script.
466
+ *
467
+ * @param {string} root - repository root.
468
+ * @param {string} baseline - the baseline sha.
469
+ * @returns {string[]} repo-relative paths.
470
+ */
471
+ function changedModules(root, baseline) {
472
+ const stdout = git(
473
+ root,
474
+ ["diff", "--name-only", "-z", `${baseline}..HEAD`],
475
+ "could not diff the baseline against HEAD"
476
+ );
477
+ return stdout
478
+ .split("\0")
479
+ .filter(
480
+ file =>
481
+ file !== "" &&
482
+ shippedParts(file) !== null &&
483
+ MODULE_EXTENSIONS.includes(path.posix.extname(file))
484
+ );
485
+ }
486
+
487
+ /**
488
+ * Read one blob at a ref.
489
+ *
490
+ * @param {string} root - repository root.
491
+ * @param {string} ref - a git ref.
492
+ * @param {string} file - repo-relative path.
493
+ * @returns {string} the file contents.
494
+ */
495
+ function showFile(root, ref, file) {
496
+ return git(root, ["show", `${ref}:${file}`], `could not read ${file}@${ref}`);
497
+ }
498
+
499
+ /**
500
+ * The delivery view a consumer of `stack` sees: destination path to repo path,
501
+ * more specific stacks winning over their ancestors.
502
+ *
503
+ * @param {string} stack - the leaf stack.
504
+ * @param {Map<string, { stack: string, destination: string }>} shipped - files at HEAD.
505
+ * @returns {Map<string, string>} destination path to repo path.
506
+ */
507
+ export function deliveryView(stack, shipped) {
508
+ const chain = ancestryChain(stack);
509
+ const view = new Map();
510
+ for (const specificity of [...chain].reverse()) {
511
+ for (const [file, meta] of shipped) {
512
+ if (meta.stack === specificity) view.set(meta.destination, file);
513
+ }
514
+ }
515
+ return view;
516
+ }
517
+
518
+ /**
519
+ * Every named export a destination offers in one delivery view, following
520
+ * `export *` re-exports so a legitimate indirection is not read as a miss.
521
+ *
522
+ * @param {string} destination - the target's path in the consumer's tree.
523
+ * @param {Map<string, string>} view - destination path to repo path.
524
+ * @param {(file: string) => string} read - reads a repo path at HEAD.
525
+ * @param {Set<string>} [seen] - cycle guard.
526
+ * @returns {Set<string> | null} the names, or null when nothing ships there.
527
+ */
528
+ export function viewExports(destination, view, read, seen = new Set()) {
529
+ const file = view.get(destination);
530
+ if (file === undefined || seen.has(destination)) return null;
531
+ seen.add(destination);
532
+ const parsed = parseNamedExports(read(file));
533
+ for (const specifier of parsed.starFrom) {
534
+ const inherited = viewExports(
535
+ resolveRelative(destination, specifier),
536
+ view,
537
+ read,
538
+ seen
539
+ );
540
+ if (inherited !== null)
541
+ for (const name of inherited) parsed.names.add(name);
542
+ }
543
+ return parsed.names;
544
+ }
545
+
546
+ /**
547
+ * Findings for shipped-to-shipped named imports that do not resolve.
548
+ *
549
+ * A specifier that resolves to nothing shipped is NOT a finding: the consumer's
550
+ * own tree may provide it, and this repository cannot see that. What is a
551
+ * finding is a target that IS shipped and does not export the requested name -
552
+ * the shape that fails at module link time with "does not provide an export
553
+ * named X".
554
+ *
555
+ * @param {{ shipped: Map<string, object>, read: (file: string) => string }} input
556
+ * files at HEAD and a blob reader.
557
+ * @returns {Array<Record<string, unknown>>} one row per missing name.
558
+ */
559
+ export function findUnresolvedImports(input) {
560
+ const rows = new Map();
561
+ for (const stack of new Set([...input.shipped.values()].map(m => m.stack))) {
562
+ const view = deliveryView(stack, input.shipped);
563
+ for (const [destination, file] of view) {
564
+ if (!MODULE_EXTENSIONS.includes(path.posix.extname(file))) continue;
565
+ for (const request of parseRelativeNamedImports(input.read(file))) {
566
+ const target = resolveRelative(destination, request.specifier);
567
+ const offered = viewExports(target, view, input.read);
568
+ if (offered === null) continue;
569
+ for (const name of request.names) {
570
+ if (offered.has(name)) continue;
571
+ const row = {
572
+ export: name,
573
+ path: file,
574
+ stack,
575
+ target: view.get(target),
576
+ };
577
+ rows.set(`${file} ${name} ${stack}`, row);
578
+ }
579
+ }
580
+ }
581
+ }
582
+ return [...rows.values()].sort((left, right) =>
583
+ `${left.path} ${left.export}`.localeCompare(`${right.path} ${right.export}`)
584
+ );
585
+ }
586
+
587
+ /**
588
+ * Ledger entries contradicted by HEAD: a path or symbol recorded as removed
589
+ * that is in fact still shipped. Left unchecked, these accumulate and start
590
+ * pre-authorising removals nobody reviewed.
591
+ *
592
+ * Entries that match no removal inside the window are NOT reported - they are
593
+ * the historical record, and are meant to outlive the baseline.
594
+ *
595
+ * @param {{ removals: ReadonlyArray<Record<string, unknown>>, shipped: Map<string, object>, read: (file: string) => string }} input
596
+ * the ledger and files at HEAD.
597
+ * @returns {Array<Record<string, unknown>>} one row per contradicted entry.
598
+ */
599
+ export function findContradictedLedger(input) {
600
+ const rows = [];
601
+ for (const entry of input.removals) {
602
+ const present = input.shipped.has(entry.path);
603
+ if (entry.export === undefined) {
604
+ if (present) rows.push({ kind: "still-shipped", path: entry.path });
605
+ continue;
606
+ }
607
+ if (!present) continue;
608
+ if (!parseNamedExports(input.read(entry.path)).names.has(entry.export))
609
+ continue;
610
+ rows.push({
611
+ export: entry.export,
612
+ kind: "still-exported",
613
+ path: entry.path,
614
+ });
615
+ }
616
+ return rows;
617
+ }
618
+
619
+ /**
620
+ * Assemble the machine-readable report.
621
+ *
622
+ * @param {{ paths: Array<object>, exports: Array<object>, imports: Array<object>, ledger: Array<object> }} findings
623
+ * every detector's rows.
624
+ * @param {{ root: string, baseline: string, shipped: number, recorded: number }} opts
625
+ * resolved options plus scan size.
626
+ * @returns {{ baseline: string, findings: object, root: string, schemaVersion: number, summary: { contradictedLedgerEntries: number, recordedRemovals: number, shippedFiles: number, unresolvedImports: number, ungovernedExports: number, ungovernedPaths: number, violations: number } }} the report object.
627
+ */
628
+ export function buildReport(findings, opts) {
629
+ const total =
630
+ findings.paths.length +
631
+ findings.exports.length +
632
+ findings.imports.length +
633
+ findings.ledger.length;
634
+ return {
635
+ baseline: opts.baseline,
636
+ findings,
637
+ root: opts.root,
638
+ schemaVersion: 1,
639
+ summary: {
640
+ contradictedLedgerEntries: findings.ledger.length,
641
+ recordedRemovals: opts.recorded,
642
+ shippedFiles: opts.shipped,
643
+ unresolvedImports: findings.imports.length,
644
+ ungovernedExports: findings.exports.length,
645
+ ungovernedPaths: findings.paths.length,
646
+ violations: total,
647
+ },
648
+ };
649
+ }
650
+
651
+ /**
652
+ * Render the human-readable report. Each line names the surface that changed
653
+ * and the consequence for a host, because "removal governance failed" is not
654
+ * something an operator can act on.
655
+ *
656
+ * @param {Record<string, unknown>} report - the report object.
657
+ * @returns {string} the rendered report.
658
+ */
659
+ export function humanReport(report) {
660
+ const { findings, summary } = report;
661
+ if (summary.violations === 0) {
662
+ return [
663
+ `OK ${summary.shippedFiles} shipped file(s) since ${report.baseline}:`,
664
+ ` every removal from a shipped surface is governed`,
665
+ ` (${summary.recordedRemovals} removal(s) recorded in ${LEDGER_PATH})`,
666
+ ].join("\n");
667
+ }
668
+ return [
669
+ ...findings.paths.map(
670
+ row =>
671
+ `FAIL ${row.path}\n` +
672
+ ` removed in ${row.release}; destination ${row.destination}\n` +
673
+ ` ${row.reason}`
674
+ ),
675
+ ...findings.exports.map(
676
+ row =>
677
+ `FAIL ${row.path} no longer exports ${row.export}\n` +
678
+ ` removed in ${row.release}; ${row.importers} shipped importer(s) ` +
679
+ `remain, and host importers cannot be counted from here\n` +
680
+ ` A host importing ${row.export} fails at module link time with ` +
681
+ `"does not provide an export named '${row.export}'", in whichever job ` +
682
+ `runs the importer first. Record it in ${LEDGER_PATH} with a note ` +
683
+ `saying what a host should do instead.`
684
+ ),
685
+ ...findings.imports.map(
686
+ row =>
687
+ `FAIL ${row.path} imports ${row.export} from ${row.target}\n` +
688
+ ` which does not export it (delivery view of stack ${row.stack})`
689
+ ),
690
+ ...findings.ledger.map(
691
+ row =>
692
+ `FAIL ${LEDGER_PATH} records ${removalKey(row.path, row.export)} as ` +
693
+ `removed, but it is still shipped at HEAD`
694
+ ),
695
+ "",
696
+ `${summary.violations} ungoverned change(s) to a shipped surface.`,
697
+ "The copy lanes overwrite and never delete, so a removal made here reaches",
698
+ "an installed base only through a deletions manifest or a migration. A",
699
+ "removal with neither does nothing downstream, silently, while looking",
700
+ "complete in the diff.",
701
+ ].join("\n");
702
+ }
703
+
704
+ /**
705
+ * Parse argv into resolved options. Throws `UsageError` on a bad invocation.
706
+ *
707
+ * @param {readonly string[]} argv - arguments (without node/script prefix).
708
+ * @returns {{ root: string, since: string | null, json: boolean }} options.
709
+ */
710
+ export function parseArgs(argv) {
711
+ let root = null;
712
+ let since = null;
713
+ let json = false;
714
+ for (let index = 0; index < argv.length; index++) {
715
+ const arg = argv[index];
716
+ if (arg === "--json") {
717
+ json = true;
718
+ continue;
719
+ }
720
+ if (arg !== "--root" && arg !== "--since") {
721
+ throw new UsageError(`unknown argument: ${arg}`);
722
+ }
723
+ const next = argv[index + 1];
724
+ if (next === undefined || next.startsWith("--")) {
725
+ throw new UsageError(`${arg} requires a value`);
726
+ }
727
+ if (arg === "--root") root = next;
728
+ else since = next;
729
+ index += 1;
730
+ }
731
+ return { json, root: path.resolve(root ?? REPO_ROOT), since };
732
+ }
733
+
734
+ /**
735
+ * Count, per shipped module, how many DISTINCT other shipped modules import
736
+ * from it.
737
+ *
738
+ * Distinct by importer path, not by resolution: a module reachable from
739
+ * several stack views would otherwise be counted once per view, and the number
740
+ * printed in a finding has to mean "files", because that is how a reader will
741
+ * read it.
742
+ *
743
+ * @param {Map<string, { destination: string, stack: string }>} shipped - files at HEAD.
744
+ * @param {(file: string) => string} read - reads a repo path at HEAD.
745
+ * @returns {Map<string, number>} repo path to importer count.
746
+ */
747
+ export function countImporters(shipped, read) {
748
+ const importers = new Map();
749
+ for (const stack of new Set([...shipped.values()].map(meta => meta.stack))) {
750
+ const view = deliveryView(stack, shipped);
751
+ for (const [destination, file] of view) {
752
+ if (!MODULE_EXTENSIONS.includes(path.posix.extname(file))) continue;
753
+ for (const request of parseRelativeNamedImports(read(file))) {
754
+ const target = view.get(
755
+ resolveRelative(destination, request.specifier)
756
+ );
757
+ if (target === undefined || target === file) continue;
758
+ if (!importers.has(target)) importers.set(target, new Set());
759
+ importers.get(target).add(file);
760
+ }
761
+ }
762
+ }
763
+ return new Map([...importers].map(([file, set]) => [file, set.size]));
764
+ }
765
+
766
+ /**
767
+ * Run every detector against a resolved repository.
768
+ *
769
+ * @param {{ root: string, since: string | null }} opts - resolved options.
770
+ * @returns {Record<string, unknown>} the report object.
771
+ */
772
+ export function scan(opts) {
773
+ const ledger = loadLedger(opts.root);
774
+ const baseline = resolveBaseline(opts.root, opts.since ?? ledger.baseline);
775
+ const before = shippedFilesAt(opts.root, baseline);
776
+ const after = shippedFilesAt(opts.root, "HEAD");
777
+ if (after.size === 0) {
778
+ throw new UsageError(
779
+ `no shipped files found under ${opts.root} - refusing to report a clean ` +
780
+ `scan of a tree this gate could not read`
781
+ );
782
+ }
783
+ const read = file => showFile(opts.root, "HEAD", file);
784
+ const index = indexRemovals(ledger.removals);
785
+ const findings = {
786
+ exports: findRemovedExports({
787
+ after,
788
+ baseline,
789
+ before,
790
+ importers: countImporters(after, read),
791
+ ledger: index,
792
+ root: opts.root,
793
+ }),
794
+ imports: findUnresolvedImports({ read, shipped: after }),
795
+ ledger: findContradictedLedger({
796
+ read,
797
+ removals: ledger.removals,
798
+ shipped: after,
799
+ }),
800
+ paths: findRemovedPaths({
801
+ after,
802
+ baseline,
803
+ before,
804
+ ledger: index,
805
+ manifests: readDeletionManifests(opts.root),
806
+ root: opts.root,
807
+ }),
808
+ };
809
+ return buildReport(findings, {
810
+ baseline: opts.since ?? ledger.baseline,
811
+ recorded: ledger.removals.length,
812
+ root: opts.root,
813
+ shipped: after.size,
814
+ });
815
+ }
816
+
817
+ /**
818
+ * Run the gate. Returns the process exit code (does not call `exit`).
819
+ *
820
+ * @param {readonly string[]} argv - arguments (without node/script prefix).
821
+ * @param {{ stdout?: { write(s: string): void }, stderr?: { write(s: string): void } }} [io]
822
+ * injectable streams (defaults to process streams).
823
+ * @returns {number} exit code (0 clean, 1 finding, 2 usage/unverifiable).
824
+ */
825
+ export function main(argv, io = {}) {
826
+ const out = io.stdout ?? process.stdout;
827
+ const err = io.stderr ?? process.stderr;
828
+ let report;
829
+ try {
830
+ const opts = parseArgs(argv);
831
+ if (!fs.existsSync(opts.root) || !fs.statSync(opts.root).isDirectory()) {
832
+ throw new UsageError(`--root ${opts.root} is not a directory`);
833
+ }
834
+ report = scan(opts);
835
+ } catch (error) {
836
+ if (error instanceof UsageError) {
837
+ err.write(`check-shipped-surface-removals: ${error.message}\n`);
838
+ return 2;
839
+ }
840
+ throw error;
841
+ }
842
+ const rendered = argv.includes("--json")
843
+ ? JSON.stringify(report, null, 2)
844
+ : humanReport(report);
845
+ out.write(`${rendered}\n`);
846
+ return report.summary.violations === 0 ? 0 : 1;
847
+ }
848
+
849
+ if (invokedAsScript(import.meta.url)) {
850
+ process.exitCode = main(process.argv.slice(2));
851
+ }