@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
@@ -446,8 +446,11 @@ export function parseArgs(argv) {
446
446
  if (arg === "--json") {
447
447
  json = true;
448
448
  } else if (arg === "--root") {
449
+ if (i + 1 >= argv.length) {
450
+ throw new UsageError("--root requires a value");
451
+ }
449
452
  const next = argv[i + 1];
450
- if (next === undefined || next.startsWith("--")) {
453
+ if (next.startsWith("--")) {
451
454
  throw new UsageError("--root requires a value");
452
455
  }
453
456
  root = next;
@@ -358,6 +358,169 @@ function noTestsVerdict(output) {
358
358
  };
359
359
  }
360
360
 
361
+ /**
362
+ * What a tool prints when it exited SUCCESSFULLY having done nothing.
363
+ *
364
+ * `states` is the tool's own positive statement of emptiness; `contradicted`
365
+ * is the line that same tool prints whenever it DID reach a verdict. Both are
366
+ * required. The second is what stops a parent transcript that merely quotes a
367
+ * child's empty lane from being read as empty itself — the same guard, and the
368
+ * same known limit, as the failure path's use of {@link SUMMARY_PATTERN}.
369
+ *
370
+ * A table rather than two conditionals, because the defect is not vitest's. It
371
+ * is "exit 0 attests to work that never happened", and the extension point is
372
+ * one row per tool that can say so. A tool which prints NO count at all —
373
+ * `tsc`, whose silent exit 0 says nothing about how much it compiled — cannot
374
+ * be given a row until it is first made to state what it compiled.
375
+ * CodySwannGT/lisa#3811 is the work that makes it state one.
376
+ *
377
+ * **A row there does NOT close #3811**, and reading it that way would be a
378
+ * costly mistake. That gate compiles 1,210 files, none of them under `tests/`:
379
+ * a WRONG NON-ZERO, not an emptiness. No row keyed on a positive statement can
380
+ * reach it, and the only row that could is one keyed on "I saw no test path in
381
+ * the file list" — the absence-inference this table refuses on principle, since
382
+ * it cannot tell "nothing was compiled" from "I did not look". The row and that
383
+ * ticket's config fix are complementary, not sequential.
384
+ *
385
+ * Both rows are measured, not recalled: vitest 4.1.9 and jest, each run with
386
+ * `--passWithNoTests` against a directory holding no tests.
387
+ */
388
+ const ZERO_WORK_SIGNATURES = Object.freeze([
389
+ Object.freeze({
390
+ contradicted: SUMMARY_PATTERN,
391
+ // The same pattern the failure path reads, deliberately, so a wording
392
+ // change in the tool breaks BOTH readings visibly instead of silently
393
+ // retiring this one. A tighter pattern keyed on the `, exiting with code
394
+ // 0` suffix would be more precise and would go quietly inert the day that
395
+ // suffix moves — which is the defect class this whole file is about.
396
+ states: NO_TESTS_PATTERN,
397
+ tool: "vitest",
398
+ }),
399
+ Object.freeze({
400
+ /** jest's tally line: `Tests: 3 passed, 3 total`. */
401
+ contradicted: /^[ \t]*Tests:[ \t]/m,
402
+ /** jest's own line: `No tests found, exiting with code 0`. */
403
+ states: /^[ \t]*No tests found/m,
404
+ tool: "jest",
405
+ }),
406
+ ]);
407
+
408
+ /**
409
+ * The root a runner names for itself: ` RUN v4.1.9 /path/to/root`.
410
+ *
411
+ * The capture takes the rest of the line rather than the next run of non-space,
412
+ * because a checkout path may contain spaces and the truncated answer is worse
413
+ * than no answer: `/repo/consumer project` reported as `/repo/consumer` names a
414
+ * tree that exists, so the operator checks the wrong one and the line that was
415
+ * added to end that guessing causes it instead.
416
+ *
417
+ * The capture is one greedy negated class and the trailing whitespace comes off
418
+ * in `rootLine` rather than in the pattern. A lazy `(.+?)` followed by `[ \t]*$`
419
+ * expresses the same intent and is a super-linear backtracking hazard, which
420
+ * the shipped ruleset rejects outright (`sonarjs/slow-regex`) — this module
421
+ * reads gate transcripts, which are exactly the untrusted, arbitrarily long
422
+ * input that makes that rule worth obeying.
423
+ */
424
+ const RUN_ROOT_PATTERN = /^[ \t]*RUN[ \t]+v\S+[ \t]+([^\r\n]+)/m;
425
+
426
+ /**
427
+ * Where this run happened — always answered, never omitted.
428
+ *
429
+ * The question an operator asks first of an empty run is WHICH TREE it ran in,
430
+ * and several agents lost time inferring it from an absent file count
431
+ * (CodySwannGT/lisa#3715, direction 3).
432
+ *
433
+ * Two sources, and the line says which one it used. The tool's own header is
434
+ * preferred because it is the tool's account of itself; the gate's working
435
+ * directory is the fallback because it is a fact the runner always has. An
436
+ * earlier version quoted ONLY the header and fell silent when it was absent —
437
+ * measured on CI, where the header did not appear in the captured transcript
438
+ * and jest never prints one at all. A direction that reads "make the run state
439
+ * its root" is not satisfied by a line that appears when the tool feels like
440
+ * it; that is the same defect one field over.
441
+ * @param {string} output The command's combined output.
442
+ * @param {string} cwd The directory the gate command ran in.
443
+ * @returns {string} An evidence line naming the root and its source.
444
+ */
445
+ function rootLine(output, cwd) {
446
+ const match = RUN_ROOT_PATTERN.exec(output);
447
+ return match
448
+ ? `the run's root was ${match[1].trimEnd()}, as the tool reported it`
449
+ : `the run's root was ${cwd}, the directory the gate ran in — the tool printed none`;
450
+ }
451
+
452
+ /**
453
+ * What an operator does about a gate that collected nothing.
454
+ *
455
+ * Two remedies and no third, because there are exactly two true situations: the
456
+ * suite exists and the invocation missed it, or the suite does not exist and
457
+ * the gate should not have been declared. `--passWithNoTests` is named outright
458
+ * because it is the flag that converts the second into a green report, and it
459
+ * ships in the integration script of several stack templates for a real reason
460
+ * — a project may genuinely have no integration tests yet. That reason is an
461
+ * argument for declaring the gate `off`, never for a gate reporting PASSED.
462
+ */
463
+ const ZERO_WORK_REMEDY =
464
+ "declaring this gate means declaring that work exists — either point the " +
465
+ "command at the suite it was meant to run, or declare the gate `off` for " +
466
+ "this moment; `--passWithNoTests` on a gate command turns a missing suite " +
467
+ "into a green report";
468
+
469
+ /**
470
+ * The verdict for a command that exited 0 while stating it did no work.
471
+ *
472
+ * The success branch is where this defect lives, and it is the one branch the
473
+ * zero-collection diagnosis could not reach: `execute()` returned PASSED on
474
+ * `code === 0` without ever reading the output, so every pattern in this file
475
+ * — including {@link noTestsVerdict}, written for exactly this event — was
476
+ * unreachable on the path that needed it (CodySwannGT/lisa#3715).
477
+ *
478
+ * A separate entry point from {@link diagnoseFailure} rather than a widening of
479
+ * it, because the two are asked different questions. `diagnoseFailure` is asked
480
+ * "what went wrong", and always has an answer. This is asked "did anything
481
+ * happen at all", and its ordinary answer is null: silence means the command
482
+ * said nothing about a count, which stays PASSED and keeps the blast radius off
483
+ * every non-test gate.
484
+ * @param {string|null|undefined} output The command's combined output, or null
485
+ * when the runner could not capture it. An uncaptured success stays a
486
+ * success — capture is on by default, and its loss is not evidence.
487
+ * @param {string} [cwd] The directory the gate command ran in, quoted when the
488
+ * tool printed no root of its own. Defaults to this process's; injected by
489
+ * tests so a fixture needs no directory on disk.
490
+ * @returns {Diagnosis|null} The verdict, or null when the output carries no
491
+ * positive statement that nothing ran.
492
+ */
493
+ export function diagnoseHollowSuccess(output, cwd = process.cwd()) {
494
+ // A type-contract check at the module boundary, and deliberately the only
495
+ // guard here: an EMPTY transcript states nothing, so the search below
496
+ // already answers null for it and a second check on its length would be a
497
+ // branch no test could ever kill — a poor thing to ship in the file whose
498
+ // subject is controls that cannot fire. This one is kept anyway, and is
499
+ // itself an equivalent mutant under the caller's contract: `normaliseExec`
500
+ // hands over `string | null`, and a null would fall through the search too,
501
+ // by way of `RegExp.test` coercing it to the characters "null". Relying on
502
+ // that coincidence is worse than stating the type.
503
+ if (typeof output !== "string") return null;
504
+ const empty = ZERO_WORK_SIGNATURES.find(
505
+ signature =>
506
+ signature.states.test(output) && !signature.contradicted.test(output)
507
+ );
508
+ if (!empty) return null;
509
+ const stated = output
510
+ .split("\n")
511
+ .map(line => line.trim())
512
+ .filter(line => empty.states.test(line));
513
+ return {
514
+ evidence: capped([...stated, rootLine(output, cwd), ZERO_WORK_REMEDY]),
515
+ kind: DIAGNOSIS.NO_TESTS_RAN,
516
+ proves: ATTRIBUTION[DIAGNOSIS.NO_TESTS_RAN] ?? null,
517
+ summary:
518
+ `the command exited 0 and ${empty.tool} said it collected ZERO test ` +
519
+ "files, so this gate is attesting to work that never happened — exit 0 " +
520
+ "here reports that nothing ran, NOT that anything passed",
521
+ };
522
+ }
523
+
361
524
  /**
362
525
  * Which gate's property each kind of failure actually belongs to.
363
526
  *
@@ -276,6 +276,178 @@ resolve_vintages() {
276
276
  plugin_tree_version="$json_version"
277
277
  fi
278
278
  note_version "$plugin_tree_version" "$plugin_tree"
279
+
280
+ # The channel this dispatcher races. Deliberately NOT folded into
281
+ # `note_version`: the newest-on-disk maximum answers "is this copy behind
282
+ # something local", and the plugin channel's age is a separate question
283
+ # about a copy that runs in parallel rather than instead.
284
+ resolve_plugin_channel || true
285
+ }
286
+
287
+ # ---------------------------------------------------------------------------
288
+ # The other channel
289
+ #
290
+ # YOU CANNOT RETIRE A REFUSAL BY SHIPPING A FIX.
291
+ #
292
+ # These guards reach an agent by two independent channels: this dispatcher,
293
+ # registered in `.claude/settings.json`, and the plugin manifest, which
294
+ # registers the same guards individually. An agent sees the UNION of the two
295
+ # verdicts, so a TIGHTENING on either channel takes effect at once while a
296
+ # RELAXATION is inert until the slower channel catches up — the stale copy goes
297
+ # on refusing, and its refusal wins. What the operator sees is a guard blocking
298
+ # something `main` already permits, which reads as the guard being WRONG rather
299
+ # than OLD, and the move that reading suggests is to route around it.
300
+ #
301
+ # The two channels are two different files refreshed by two different
302
+ # mechanisms: `scripts/lisa-hooks/` when `lisa apply` runs, the plugin's own
303
+ # `hooks/` when the plugin updates. They drift. Measured on one machine on one
304
+ # day, two checkouts of the SAME repository resolved plugin 4.32.2 and 4.47.0.
305
+ #
306
+ # `plugin_tree` above cannot see any of that: it is repo-relative, so it exists
307
+ # only inside the Lisa monorepo, and in a host project the plugin actually in
308
+ # force is somewhere else entirely. The vintage machinery written for #3205
309
+ # was therefore blind to the channel it races. This resolves it properly, from
310
+ # the record the runtime itself keeps.
311
+ #
312
+ # WHAT THIS RECORD CAN AND CANNOT ANSWER. The header above explains why a
313
+ # previous stand-down keyed on `installed_plugins.json` was removed: the record
314
+ # is project-blind as it was read then, enablement-blind (`enabledPlugins` can
315
+ # switch a plugin off without removing its entry), and session-blind (hooks load
316
+ # at session start; the record is rewritten on any install). Those defeat a
317
+ # LIVENESS question — "are the plugin's guards running in this session?" — and
318
+ # nothing on disk answers that one.
319
+ #
320
+ # This asks a different question: "what VINTAGE is the plugin installed for this
321
+ # project?" The record answers that directly, and the project-blindness is
322
+ # resolved by keying the lookup on this repo root rather than on the plugin name.
323
+ # The remaining two blindnesses bound the claim rather than break it: a skew
324
+ # reported here is a real difference between two copies on disk, but a disabled
325
+ # or not-yet-loaded plugin may mean those copies are not both firing right now.
326
+ # So the wording below reports what is INSTALLED and what the union WOULD mean,
327
+ # and never asserts that both channels are live. Reporting is also why this may
328
+ # use the record at all where a stand-down may not: an over-reported skew costs
329
+ # a line of notice, while an over-confident stand-down costs enforcement.
330
+ plugin_channel_version=""
331
+ plugin_channel_path=""
332
+
333
+ # Resolve the vintage of the guard channel the PLUGIN MANIFEST runs.
334
+ #
335
+ # `plugins/installed_plugins.json` records installs PER PROJECT DIRECTORY —
336
+ # which is exactly why one checkout sits versions behind another on the same
337
+ # disk — so the lookup is keyed on this repo root, not on the machine.
338
+ #
339
+ # A record is claimed only when its `installPath` sits under a `lisa/lisa/`
340
+ # marketplace cache. A locally-installed plugin does not match and is left
341
+ # UNRESOLVED on purpose: reporting "agree" about a copy that was never read is
342
+ # the failure this whole section exists to end, and an honest "I could not
343
+ # tell" is the safe direction.
344
+ #
345
+ # `grep` does the scanning because the record runs to tens of thousands of
346
+ # lines; the bash loop then sees a handful. Called only from resolve_vintages,
347
+ # so it inherits that latch and never runs on the silent allow path.
348
+ resolve_plugin_channel() {
349
+ local config_dir="${CLAUDE_CONFIG_DIR-}"
350
+ [ -n "$config_dir" ] || config_dir="${HOME-}/.claude"
351
+ local record="$config_dir/plugins/installed_plugins.json"
352
+ [ -f "$record" ] || return 1
353
+
354
+ local install_pattern="^[[:space:]]*\"installPath\"[[:space:]]*:[[:space:]]*\"(.*/lisa/lisa/[^\"]+)\""
355
+ local version_pattern="^[[:space:]]*\"version\"[[:space:]]*:[[:space:]]*\"([^\"]+)\""
356
+ local line
357
+ local candidate=""
358
+ while IFS= read -r line || [ -n "$line" ]; do
359
+ # `grep -A` separates non-adjacent groups with `--`. A candidate must not
360
+ # survive across that boundary or one plugin's installPath answers for
361
+ # another plugin's version.
362
+ if [ "$line" = "--" ]; then
363
+ candidate=""
364
+ continue
365
+ fi
366
+ if [[ "$line" =~ $install_pattern ]]; then
367
+ candidate="${BASH_REMATCH[1]}"
368
+ continue
369
+ fi
370
+ if [ -n "$candidate" ] && [[ "$line" =~ $version_pattern ]]; then
371
+ plugin_channel_path="$candidate"
372
+ plugin_channel_version="${BASH_REMATCH[1]}"
373
+ return 0
374
+ fi
375
+ done < <(grep -F -A4 "\"projectPath\": \"$repo_root\"," "$record" 2>/dev/null)
376
+ return 1
377
+ }
378
+
379
+ # Verdict of the last classify_channel_skew call: agree | skew | undetermined.
380
+ #
381
+ # Three answers, never two. "The channels agree" and "I could not read one of
382
+ # them" are DIFFERENT facts, and collapsing them is how a probe reports success
383
+ # while measuring nothing.
384
+ #
385
+ # THIS PROBE REPORTS. IT NEVER BLOCKS. A permitted command stays permitted
386
+ # whatever the verdict, and the finding goes into the once-per-session notice.
387
+ # That is a decision, not an oversight, so here is the reasoning for whoever
388
+ # comes to change it:
389
+ #
390
+ # - The two error directions have asymmetric costs. An over-reported skew
391
+ # costs a line of notice; an over-confident stand-down costs enforcement.
392
+ # When the costs are that lopsided the direction is settled without needing
393
+ # to argue about likelihood.
394
+ # - Failing closed on `undetermined` would refuse work on every host with no
395
+ # `installed_plugins.json` — containers, fresh clones, non-Claude runtimes.
396
+ # That is a large population with nothing wrong with it, and refusing them
397
+ # is a bigger fault than the one being detected.
398
+ # - What is detected is two copies at different AGES, not a compromised
399
+ # guard. Blocking on it turns a diagnostic into an outage.
400
+ # - And the practical argument, which beats the principled one: skew is the
401
+ # common case on a developer machine right now, so as a blocker this would
402
+ # be a permanent stop-work and the first response would be to switch it
403
+ # off. A blocker everyone turns off protects nothing.
404
+ #
405
+ # Pinned by the test named "lets a permitted command through whatever the
406
+ # verdict is", which asserts exit 0 on both the skew and undetermined arms.
407
+ channel_skew_verdict="undetermined"
408
+ channel_skew_line=""
409
+
410
+ # Compare the vintage of the channel this dispatcher runs against the vintage
411
+ # of the channel the plugin manifest runs.
412
+ classify_channel_skew() {
413
+ local mine=""
414
+ if [ "$host_tree_used" -eq 1 ]; then
415
+ mine="$host_tree_version"
416
+ elif [ "$plugin_tree_used" -eq 1 ]; then
417
+ mine="$plugin_tree_version"
418
+ fi
419
+
420
+ if [ -z "$mine" ] || [ -z "$plugin_channel_version" ]; then
421
+ channel_skew_verdict="undetermined"
422
+ channel_skew_line=" cross-channel vintage UNDETERMINED — this dispatcher's guards could not be
423
+ compared with the plugin manifest's copies. Not agreement: an unread copy
424
+ can be any age, and a relaxation shipped to one channel stays inert until
425
+ the other catches up.
426
+ "
427
+ return 0
428
+ fi
429
+
430
+ if [ "$mine" = "$plugin_channel_version" ]; then
431
+ channel_skew_verdict="agree"
432
+ channel_skew_line=""
433
+ return 0
434
+ fi
435
+
436
+ channel_skew_verdict="skew"
437
+ # The sentence is kept whole on one line on purpose: it is the finding, and
438
+ # an operator greps for it.
439
+ channel_skew_line=" cross-channel vintage SKEW — this dispatcher runs lisa $mine; the plugin
440
+ installed for this project is lisa $plugin_channel_version at $plugin_channel_path.
441
+ When both fire on one tool call the agent sees the UNION of their verdicts,
442
+ which means:
443
+ YOU CANNOT RETIRE A REFUSAL BY SHIPPING A FIX.
444
+ A relaxation on the newer channel does nothing until the older one is
445
+ refreshed, so a block you cannot explain from \`main\` is the older copy
446
+ still enforcing.
447
+ repair: refresh both — \`npx @codyswann/lisa apply\` for this checkout's
448
+ guards, and update the installed plugin for the manifest's copies.
449
+ "
450
+ return 0
279
451
  }
280
452
 
281
453
  # Description of the last describe_vintage call.
@@ -613,8 +785,10 @@ if [ "$notice_due" -eq 1 ]; then
613
785
  if [ "$plugin_tree_used" -eq 1 ]; then
614
786
  note_tree_staleness "$plugin_tree" "$plugin_tree_version" "$PLUGIN_REPAIR"
615
787
  fi
788
+ classify_channel_skew
616
789
 
617
- if [ -n "$stale_notice" ] || [ -n "$shadowed" ] || [ -n "$missing" ]; then
790
+ if [ -n "$stale_notice" ] || [ -n "$shadowed" ] || [ -n "$missing" ] || \
791
+ [ -n "$channel_skew_line" ]; then
618
792
  # Claim the session BEFORE printing. A failed `mkdir` suppresses this copy
619
793
  # only when another process left the expected real directory behind. Every
620
794
  # other failure leaves the claim unproven and prints, so an unwritable state
@@ -650,6 +824,9 @@ if [ "$notice_due" -eq 1 ]; then
650
824
  if [ -n "$missing" ]; then
651
825
  printf ' unresolved guards: %s (no copy was dispatched)\n' "$missing"
652
826
  fi
827
+ if [ -n "$channel_skew_line" ]; then
828
+ printf '%s' "$channel_skew_line"
829
+ fi
653
830
  } >&2
654
831
  fi
655
832
  fi
@@ -497,6 +497,82 @@ project_dir = os.environ.get("LISA_GUARD_PROJECT_DIR", "")
497
497
  OVERRIDE_NAME = "LISA_ALLOW_DIRECT_ISSUE_CREATE"
498
498
  HUMAN_GATE_MARKER = "[lisa-human-gate]"
499
499
 
500
+ # Decoration a hold declaration may sit behind, and nothing more: whitespace,
501
+ # blockquote arrows, list bullets or numbers, emphasis, an opening HTML
502
+ # comment. One character class plus two optional groups, deliberately not a
503
+ # nested quantifier — this runs over untrusted issue bodies.
504
+ HUMAN_GATE_DECORATION = re.compile(r"^[ \t>*_+#-]*(?:\d+[.)][ \t]*)?(?:<!--[ \t]*)?[ \t]*")
505
+ HUMAN_GATE_FENCE = re.compile(r"```.*?```", re.DOTALL)
506
+ HUMAN_GATE_CODE_SPAN = re.compile(r"`[^`\n]*`")
507
+
508
+
509
+ def declares_human_gate(text):
510
+ """Whether a text DECLARES a hold rather than mentioning one.
511
+
512
+ The comment this replaces said the marker is matched anywhere because it
513
+ "has no other meaning, so its presence in the title or body IS the
514
+ declaration". That was true when written and is false now: the marker
515
+ acquired a second meaning -- being discussed -- the moment the feature
516
+ became something people file tickets about, and a ticket about the marker
517
+ then declares a hold on itself (CodySwannGT/lisa#3815).
518
+
519
+ A declaration is POSITIONAL: the marker leads its line, behind at most the
520
+ decoration above, with fenced blocks and inline code spans removed first
521
+ because that is how the marker is written ABOUT. The same rule the intake
522
+ matcher applies, so the two surfaces cannot disagree about one body.
523
+
524
+ NOTE the direction here differs from the intake matcher's. There a match
525
+ HOLDS an item, so a false positive parks work. Here a match ADMITS a
526
+ filing, so a false positive is a hole -- which makes this the tightening
527
+ direction on both sides at once.
528
+
529
+ Args:
530
+ text: Any candidate text -- one argv value, a body file, a payload.
531
+
532
+ Returns:
533
+ True when some line of the text declares a hold.
534
+ """
535
+ if not text:
536
+ return False
537
+ body = HUMAN_GATE_CODE_SPAN.sub("", HUMAN_GATE_FENCE.sub("", str(text)))
538
+ for line in body.split("\n"):
539
+ if HUMAN_GATE_DECORATION.sub("", line, count=1).startswith(
540
+ HUMAN_GATE_MARKER
541
+ ):
542
+ return True
543
+ if _comment_carries_marker(line):
544
+ return True
545
+ return False
546
+
547
+
548
+ def _comment_carries_marker(line):
549
+ """Whether an HTML comment on this line carries the marker.
550
+
551
+ The second accepted form, and the one this guard needs most: its inputs
552
+ are not markdown documents. A `--body` value is a single line, and a shell
553
+ script writes its declaration as `# <!-- ... -->`, so a purely
554
+ line-leading rule would refuse two declarations people have already
555
+ written -- both of which are in this repository's own test corpus.
556
+
557
+ It is not a loosening. An HTML comment is invisible when rendered, so
558
+ nobody wraps one around the marker in order to TALK about it; a quotation
559
+ has to be visible to be a quotation. Measured over this repository's 42
560
+ matching issue bodies, adding this branch changes not one verdict.
561
+
562
+ Args:
563
+ line: One line of text.
564
+
565
+ Returns:
566
+ True when some comment on the line contains the marker.
567
+ """
568
+ segments = line.split("<!--")
569
+ for segment in segments[1:]:
570
+ close = segment.find("-->")
571
+ inside = segment if close == -1 else segment[:close]
572
+ if HUMAN_GATE_MARKER in inside:
573
+ return True
574
+ return False
575
+
500
576
  # ---------------------------------------------------------------------------
501
577
  # WHY THIS IS A TOKEN SCAN AND NOT A PROGRAM RESOLVER
502
578
  #
@@ -583,7 +659,7 @@ LABEL_FLAGS = {"--label", "--labels", "--add-label", "--status", "--state"}
583
659
  # None — container: state rolls up from children
584
660
  #
585
661
  # It is a marker with no other meaning, so it is matched wherever the payload
586
- # reaches — exactly the asymmetry `HUMAN_GATE_MARKER` gets, and for the same
662
+ # reaches — the asymmetry `HUMAN_GATE_MARKER` gets, and for the related
587
663
  # reason. Reading the declaration the writer already emits is also what keeps
588
664
  # the guard and the skill from drifting apart a second time: there is one
589
665
  # string, defined in one rule, and both read it.
@@ -1226,7 +1302,7 @@ def text_declares_readiness(text):
1226
1302
  Returns:
1227
1303
  True when the text declares build-ready or a human gate.
1228
1304
  """
1229
- if HUMAN_GATE_MARKER in text:
1305
+ if declares_human_gate(text):
1230
1306
  return True
1231
1307
  for role in (ready_role, upstream_ready_role, default_ready_role):
1232
1308
  if not role:
@@ -1270,7 +1346,7 @@ def scope_declaration(text, state_role_ok):
1270
1346
  Returns:
1271
1347
  True when the text carries a whole-scope declaration.
1272
1348
  """
1273
- if HUMAN_GATE_MARKER in text:
1349
+ if declares_human_gate(text):
1274
1350
  return True
1275
1351
  return bool(state_role_ok and LIFECYCLE_ROLE_READY.search(text))
1276
1352
 
@@ -1500,20 +1576,23 @@ def declares_readiness(raw_args, roles, extra="", state_role_ok=False):
1500
1576
  candidates = [part.strip().strip("'\"") for part in raw.split(",")]
1501
1577
  if role in candidates:
1502
1578
  return True
1503
- # The human-gate marker is matched anywhere, and that asymmetry is
1504
- # deliberate: it is a marker with no other meaning, so its presence in the
1505
- # title or body IS the declaration. The build-ready role is an ordinary
1506
- # string that appears in prose about the queue all the time.
1507
- if HUMAN_GATE_MARKER in " ".join(args):
1579
+ # Checked per ARGV VALUE rather than against `" ".join(args)`, and that is
1580
+ # what makes the positional rule mean anything here. Joining first destroys
1581
+ # position: after a join, no value's line start survives except the first
1582
+ # argument's, so "the marker leads its line" would be decided by argument
1583
+ # order. A `--title` value is its own single line, which is exactly the
1584
+ # unit the rule is about -- and titles must keep being read, since this
1585
+ # guard reads them and the intake matcher does not.
1586
+ if any(declares_human_gate(value) for value in args):
1508
1587
  return True
1509
1588
  for path in body_file_paths(args):
1510
1589
  try:
1511
1590
  with open(path, encoding="utf-8", errors="replace") as handle:
1512
- if HUMAN_GATE_MARKER in handle.read():
1591
+ if declares_human_gate(handle.read()):
1513
1592
  return True
1514
1593
  except OSError:
1515
1594
  continue
1516
- if extra and HUMAN_GATE_MARKER in extra:
1595
+ if declares_human_gate(extra):
1517
1596
  return True
1518
1597
  # The state-based path. Scoped by `state_role_ok` rather than checked
1519
1598
  # unconditionally, so this adds a compliant command where none existed and
@@ -29,6 +29,11 @@
29
29
  # divergence: upstream blocks unconditionally; Lisa allows clean-tree resets.
30
30
  # Residual risk (documented, accepted): the dirty check runs in the hook's
31
31
  # cwd at hook time, so a `cd elsewhere && git reset --hard` evades it.
32
+ # - `git read-tree` with an update flag AND `--reset` while the working tree
33
+ # is dirty (issue #3978): the plumbing spelling of the same discard, on the
34
+ # same condition, printing the same remedy. Index-only `read-tree`, and
35
+ # `read-tree -m -u` without `--reset` (which git itself refuses rather than
36
+ # clobber a modified file), stay allowed. Lisa-only guard.
32
37
  # - `git rebase --abort`/`--quit` while the in-progress rebase holds
33
38
  # human-made conflict resolutions (AUTO_MERGE discriminator; issue #1956).
34
39
  # Clean or untouched rebase state stays abortable; the apply backend and a
@@ -1822,6 +1827,45 @@ $DESTRUCTIVE_GUIDANCE"
1822
1827
  fi
1823
1828
  fi
1824
1829
 
1830
+ # 3a. `git read-tree` with BOTH an update flag and `--reset`, on the same dirty
1831
+ # tree — the plumbing spelling of guard 3 (issue #3978). It resets the index
1832
+ # and writes the result into the working tree, discarding tracked
1833
+ # modifications: the identical destruction, reached through a command that
1834
+ # carries no `--hard` and no `reset` subcommand, so every pattern in
1835
+ # guard 3 misses it. A guard an agent can walk around is a guard that stops
1836
+ # the honest caller and not the one looking for a way through, and this
1837
+ # spelling is strictly worse than the blocked one — it discards WITHOUT
1838
+ # preserving, which is exactly the property guard 3's remedy supplies. Both
1839
+ # refusals therefore print the same $PRESERVE_GUIDANCE, so what they teach
1840
+ # cannot drift apart.
1841
+ #
1842
+ # `--reset` is the discriminator, not `-u`, and the pairing is deliberate:
1843
+ #
1844
+ # - `read-tree` with no update flag touches the INDEX only and destroys
1845
+ # nothing in the working tree, so a blanket `read-tree` block would be
1846
+ # over-broad.
1847
+ # - `read-tree -m -u` (a merge without `--reset`) is safe by git's own
1848
+ # construction: it REFUSES rather than overwrite a locally modified file
1849
+ # ("entry would be overwritten by merge"). `--reset` is precisely the flag
1850
+ # that switches that refusal off.
1851
+ #
1852
+ # So the destructive combination is an update flag AND `--reset` together,
1853
+ # in either order. The update flag is matched in the checkout guard's idiom
1854
+ # so a bundled short option (`-iu`) cannot dodge the anchor, with leading
1855
+ # whitespace required so a `--index-output=/tmp/out-u` style value cannot
1856
+ # read as the flag. Same conditional dirty-tree shape and same accepted
1857
+ # residual risk as guard 3: the probe runs in the hook's cwd at hook time.
1858
+ readonly GIT_READ_TREE="${GIT_CMD}read-tree"
1859
+ if matches "${GIT_READ_TREE}"'[^;&|]*[[:space:]]--reset([[:space:]]|$)' \
1860
+ && matches "${GIT_READ_TREE}"'[^;&|]*[[:space:]](-[[:alnum:]]*u[[:alnum:]]*|--update)([[:space:]]|=|$)'; then
1861
+ if git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
1862
+ && [ -n "$(git status --porcelain 2>/dev/null)" ]; then
1863
+ block "git read-tree -u --reset on a dirty working tree would discard uncommitted changes (commit first, or preserve as below)" "$PRESERVE_GUIDANCE
1864
+
1865
+ $DESTRUCTIVE_GUIDANCE"
1866
+ fi
1867
+ fi
1868
+
1825
1869
  # 3b. `git rebase --abort` / `--quit` ONLY while human/agent conflict
1826
1870
  # resolutions exist (issue #1956). `--abort` restores the pre-rebase branch
1827
1871
  # and discards every resolution; `--quit` deletes the rebase bookkeeping
@@ -376,7 +376,7 @@ export const run = async (argv, cwd) => {
376
376
  * @returns {boolean} Whether the CLI body should run.
377
377
  */
378
378
  export const invokedAsScript = (moduleUrl, argv1 = process.argv[1]) => {
379
- if (argv1 === undefined || argv1 === "") return false;
379
+ if (typeof argv1 !== "string" || argv1.length === 0) return false;
380
380
  try {
381
381
  return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
382
382
  } catch {