@codyswann/lisa 4.22.9 → 4.22.11

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 (274) hide show
  1. package/all/copy-overwrite/scripts/check-release-package-identity.mjs +51 -41
  2. package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +127 -0
  3. package/all/copy-overwrite/scripts/lisa-floor-collisions.mjs +63 -8
  4. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +36 -6
  5. package/all/copy-overwrite/scripts/lisa-postinstall.mjs +35 -13
  6. package/all/copy-overwrite/scripts/lisa-reconcile-policy.mjs +11 -3
  7. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +41 -8
  8. package/all/copy-overwrite/scripts/lisa-schema-validate.mjs +2 -2
  9. package/all/copy-overwrite/scripts/lisa-work-item.mjs +2 -1
  10. package/cdk/package-lisa/package.lisa.json +1 -1
  11. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  12. package/dist/core/lisa-owned-hash-ledger.js +26 -0
  13. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  14. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  15. package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
  16. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  17. package/dist/core/upstream-evidence-manifest.js +61 -52
  18. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  19. package/dist/opencode/plugin-templates/parity-safety-net.sh +36 -6
  20. package/nestjs/package-lisa/package.lisa.json +1 -1
  21. package/package.json +3 -3
  22. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  24. package/plugins/lisa/.codex-plugin/skills/lisa-atlassian-access/SKILL.md +10 -13
  25. package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +4 -2
  26. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +29 -4
  27. package/plugins/lisa/.codex-plugin/skills/lisa-github-sync/SKILL.md +8 -3
  28. package/plugins/lisa/.codex-plugin/skills/lisa-jam-access/SKILL.md +7 -10
  29. package/plugins/lisa/.codex-plugin/skills/lisa-jira-journey/SKILL.md +5 -0
  30. package/plugins/lisa/.codex-plugin/skills/lisa-jira-journey/scripts/parse-plan.py +36 -2
  31. package/plugins/lisa/.codex-plugin/skills/lisa-jira-sync/SKILL.md +1 -1
  32. package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +8 -12
  33. package/plugins/lisa/.codex-plugin/skills/lisa-linear-sync/SKILL.md +1 -1
  34. package/plugins/lisa/.codex-plugin/skills/lisa-nightly-lower-code-complexity/SKILL.md +7 -1
  35. package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +7 -10
  36. package/plugins/lisa/.codex-plugin/skills/lisa-posthog-access/SKILL.md +7 -10
  37. package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +19 -3
  38. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +10 -6
  39. package/plugins/lisa/.codex-plugin/skills/lisa-sentry-access/SKILL.md +7 -10
  40. package/plugins/lisa/.codex-plugin/skills/lisa-setup-atlassian/SKILL.md +7 -10
  41. package/plugins/lisa/.codex-plugin/skills/lisa-setup-linear/SKILL.md +9 -12
  42. package/plugins/lisa/.codex-plugin/skills/lisa-setup-notion/SKILL.md +7 -10
  43. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-aws/SKILL.md +7 -2
  44. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/SKILL.md +6 -1
  45. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/assets/session-start.sh +19 -0
  46. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/assets/setup.sh +1 -0
  47. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +25 -4
  48. package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/scripts/bounded-child.mjs +9 -0
  49. package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/scripts/workstation.mjs +29 -21
  50. package/plugins/lisa/.codex-plugin/skills/lisa-sonarcloud-access/SKILL.md +7 -10
  51. package/plugins/lisa/.codex-plugin/skills/lisa-tracker-sync/SKILL.md +1 -1
  52. package/plugins/lisa/hooks/parity-safety-net.sh +36 -6
  53. package/plugins/lisa/hooks/threshold-ratchet-families.mjs +31 -7
  54. package/plugins/lisa/rules/reference/base-rules.md +1 -1
  55. package/plugins/lisa/rules/reference/config-resolution.md +5 -14
  56. package/plugins/lisa/scripts/install-remote-agent-aws.mjs +5 -1
  57. package/plugins/lisa/scripts/rollup-blocker-classification.mjs +18 -3
  58. package/plugins/lisa/skills/lisa-atlassian-access/SKILL.md +10 -13
  59. package/plugins/lisa/skills/lisa-doctor/SKILL.md +4 -2
  60. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +29 -4
  61. package/plugins/lisa/skills/lisa-github-sync/SKILL.md +8 -3
  62. package/plugins/lisa/skills/lisa-jam-access/SKILL.md +7 -10
  63. package/plugins/lisa/skills/lisa-jira-journey/SKILL.md +5 -0
  64. package/plugins/lisa/skills/lisa-jira-journey/scripts/parse-plan.py +36 -2
  65. package/plugins/lisa/skills/lisa-jira-sync/SKILL.md +1 -1
  66. package/plugins/lisa/skills/lisa-linear-access/SKILL.md +8 -12
  67. package/plugins/lisa/skills/lisa-linear-sync/SKILL.md +1 -1
  68. package/plugins/lisa/skills/lisa-nightly-lower-code-complexity/SKILL.md +7 -1
  69. package/plugins/lisa/skills/lisa-notion-access/SKILL.md +7 -10
  70. package/plugins/lisa/skills/lisa-posthog-access/SKILL.md +7 -10
  71. package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +19 -3
  72. package/plugins/lisa/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +10 -6
  73. package/plugins/lisa/skills/lisa-sentry-access/SKILL.md +7 -10
  74. package/plugins/lisa/skills/lisa-setup-atlassian/SKILL.md +7 -10
  75. package/plugins/lisa/skills/lisa-setup-linear/SKILL.md +9 -12
  76. package/plugins/lisa/skills/lisa-setup-notion/SKILL.md +7 -10
  77. package/plugins/lisa/skills/lisa-setup-remote-aws/SKILL.md +7 -2
  78. package/plugins/lisa/skills/lisa-setup-remote-env/SKILL.md +6 -1
  79. package/plugins/lisa/skills/lisa-setup-remote-env/assets/session-start.sh +19 -0
  80. package/plugins/lisa/skills/lisa-setup-remote-env/assets/setup.sh +1 -0
  81. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +25 -4
  82. package/plugins/lisa/skills/lisa-setup-workstation/scripts/bounded-child.mjs +9 -0
  83. package/plugins/lisa/skills/lisa-setup-workstation/scripts/workstation.mjs +29 -21
  84. package/plugins/lisa/skills/lisa-sonarcloud-access/SKILL.md +7 -10
  85. package/plugins/lisa/skills/lisa-tracker-sync/SKILL.md +1 -1
  86. package/plugins/lisa-agy/hooks/parity-safety-net.sh +36 -6
  87. package/plugins/lisa-agy/plugin.json +1 -1
  88. package/plugins/lisa-agy/scripts/install-remote-agent-aws.mjs +5 -1
  89. package/plugins/lisa-agy/scripts/rollup-blocker-classification.mjs +18 -3
  90. package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +10 -13
  91. package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +4 -2
  92. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +29 -4
  93. package/plugins/lisa-agy/skills/lisa-github-sync/SKILL.md +8 -3
  94. package/plugins/lisa-agy/skills/lisa-jam-access/SKILL.md +7 -10
  95. package/plugins/lisa-agy/skills/lisa-jira-journey/SKILL.md +5 -0
  96. package/plugins/lisa-agy/skills/lisa-jira-journey/scripts/parse-plan.py +36 -2
  97. package/plugins/lisa-agy/skills/lisa-jira-sync/SKILL.md +1 -1
  98. package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +8 -12
  99. package/plugins/lisa-agy/skills/lisa-linear-sync/SKILL.md +1 -1
  100. package/plugins/lisa-agy/skills/lisa-nightly-lower-code-complexity/SKILL.md +7 -1
  101. package/plugins/lisa-agy/skills/lisa-notion-access/SKILL.md +7 -10
  102. package/plugins/lisa-agy/skills/lisa-posthog-access/SKILL.md +7 -10
  103. package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +19 -3
  104. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +10 -6
  105. package/plugins/lisa-agy/skills/lisa-sentry-access/SKILL.md +7 -10
  106. package/plugins/lisa-agy/skills/lisa-setup-atlassian/SKILL.md +7 -10
  107. package/plugins/lisa-agy/skills/lisa-setup-linear/SKILL.md +9 -12
  108. package/plugins/lisa-agy/skills/lisa-setup-notion/SKILL.md +7 -10
  109. package/plugins/lisa-agy/skills/lisa-setup-remote-aws/SKILL.md +7 -2
  110. package/plugins/lisa-agy/skills/lisa-setup-remote-env/SKILL.md +6 -1
  111. package/plugins/lisa-agy/skills/lisa-setup-remote-env/assets/session-start.sh +19 -0
  112. package/plugins/lisa-agy/skills/lisa-setup-remote-env/assets/setup.sh +1 -0
  113. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +25 -4
  114. package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/bounded-child.mjs +9 -0
  115. package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/workstation.mjs +29 -21
  116. package/plugins/lisa-agy/skills/lisa-sonarcloud-access/SKILL.md +7 -10
  117. package/plugins/lisa-agy/skills/lisa-tracker-sync/SKILL.md +1 -1
  118. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  119. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  120. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  121. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  124. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +36 -6
  125. package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +31 -7
  126. package/plugins/lisa-copilot/rules/reference/base-rules.md +1 -1
  127. package/plugins/lisa-copilot/rules/reference/config-resolution.md +5 -14
  128. package/plugins/lisa-copilot/scripts/install-remote-agent-aws.mjs +5 -1
  129. package/plugins/lisa-copilot/scripts/rollup-blocker-classification.mjs +18 -3
  130. package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +10 -13
  131. package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +4 -2
  132. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +29 -4
  133. package/plugins/lisa-copilot/skills/lisa-github-sync/SKILL.md +8 -3
  134. package/plugins/lisa-copilot/skills/lisa-jam-access/SKILL.md +7 -10
  135. package/plugins/lisa-copilot/skills/lisa-jira-journey/SKILL.md +5 -0
  136. package/plugins/lisa-copilot/skills/lisa-jira-journey/scripts/parse-plan.py +36 -2
  137. package/plugins/lisa-copilot/skills/lisa-jira-sync/SKILL.md +1 -1
  138. package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +8 -12
  139. package/plugins/lisa-copilot/skills/lisa-linear-sync/SKILL.md +1 -1
  140. package/plugins/lisa-copilot/skills/lisa-nightly-lower-code-complexity/SKILL.md +7 -1
  141. package/plugins/lisa-copilot/skills/lisa-notion-access/SKILL.md +7 -10
  142. package/plugins/lisa-copilot/skills/lisa-posthog-access/SKILL.md +7 -10
  143. package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +19 -3
  144. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +10 -6
  145. package/plugins/lisa-copilot/skills/lisa-sentry-access/SKILL.md +7 -10
  146. package/plugins/lisa-copilot/skills/lisa-setup-atlassian/SKILL.md +7 -10
  147. package/plugins/lisa-copilot/skills/lisa-setup-linear/SKILL.md +9 -12
  148. package/plugins/lisa-copilot/skills/lisa-setup-notion/SKILL.md +7 -10
  149. package/plugins/lisa-copilot/skills/lisa-setup-remote-aws/SKILL.md +7 -2
  150. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/SKILL.md +6 -1
  151. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/assets/session-start.sh +19 -0
  152. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/assets/setup.sh +1 -0
  153. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +25 -4
  154. package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/bounded-child.mjs +9 -0
  155. package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/workstation.mjs +29 -21
  156. package/plugins/lisa-copilot/skills/lisa-sonarcloud-access/SKILL.md +7 -10
  157. package/plugins/lisa-copilot/skills/lisa-tracker-sync/SKILL.md +1 -1
  158. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  159. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +36 -6
  160. package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +31 -7
  161. package/plugins/lisa-cursor/rules/base-rules-reference.mdc +1 -1
  162. package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +5 -14
  163. package/plugins/lisa-cursor/scripts/install-remote-agent-aws.mjs +5 -1
  164. package/plugins/lisa-cursor/scripts/rollup-blocker-classification.mjs +18 -3
  165. package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +10 -13
  166. package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +4 -2
  167. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +29 -4
  168. package/plugins/lisa-cursor/skills/lisa-github-sync/SKILL.md +8 -3
  169. package/plugins/lisa-cursor/skills/lisa-jam-access/SKILL.md +7 -10
  170. package/plugins/lisa-cursor/skills/lisa-jira-journey/SKILL.md +5 -0
  171. package/plugins/lisa-cursor/skills/lisa-jira-journey/scripts/parse-plan.py +36 -2
  172. package/plugins/lisa-cursor/skills/lisa-jira-sync/SKILL.md +1 -1
  173. package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +8 -12
  174. package/plugins/lisa-cursor/skills/lisa-linear-sync/SKILL.md +1 -1
  175. package/plugins/lisa-cursor/skills/lisa-nightly-lower-code-complexity/SKILL.md +7 -1
  176. package/plugins/lisa-cursor/skills/lisa-notion-access/SKILL.md +7 -10
  177. package/plugins/lisa-cursor/skills/lisa-posthog-access/SKILL.md +7 -10
  178. package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +19 -3
  179. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +10 -6
  180. package/plugins/lisa-cursor/skills/lisa-sentry-access/SKILL.md +7 -10
  181. package/plugins/lisa-cursor/skills/lisa-setup-atlassian/SKILL.md +7 -10
  182. package/plugins/lisa-cursor/skills/lisa-setup-linear/SKILL.md +9 -12
  183. package/plugins/lisa-cursor/skills/lisa-setup-notion/SKILL.md +7 -10
  184. package/plugins/lisa-cursor/skills/lisa-setup-remote-aws/SKILL.md +7 -2
  185. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/SKILL.md +6 -1
  186. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/assets/session-start.sh +19 -0
  187. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/assets/setup.sh +1 -0
  188. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +25 -4
  189. package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/bounded-child.mjs +9 -0
  190. package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/workstation.mjs +29 -21
  191. package/plugins/lisa-cursor/skills/lisa-sonarcloud-access/SKILL.md +7 -10
  192. package/plugins/lisa-cursor/skills/lisa-tracker-sync/SKILL.md +1 -1
  193. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  194. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  195. package/plugins/lisa-expo-agy/plugin.json +1 -1
  196. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  197. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  198. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  199. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  200. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  201. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  202. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  203. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  204. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  205. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  206. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  207. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  208. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  209. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  210. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  211. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  212. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  213. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  214. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  215. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  216. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  217. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  218. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  219. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  220. package/plugins/lisa-rails-agy/plugin.json +1 -1
  221. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  222. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  223. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  224. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  225. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  226. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  227. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  228. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  229. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  230. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  231. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  232. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  233. package/plugins/src/base/hooks/parity-safety-net.sh +36 -6
  234. package/plugins/src/base/hooks/threshold-ratchet-families.mjs +31 -7
  235. package/plugins/src/base/rules/reference/base-rules.md +1 -1
  236. package/plugins/src/base/rules/reference/config-resolution.md +5 -14
  237. package/plugins/src/base/scripts/install-remote-agent-aws.mjs +5 -1
  238. package/plugins/src/base/scripts/rollup-blocker-classification.mjs +18 -3
  239. package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +10 -13
  240. package/plugins/src/base/skills/lisa-doctor/SKILL.md +4 -2
  241. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +29 -4
  242. package/plugins/src/base/skills/lisa-github-sync/SKILL.md +8 -3
  243. package/plugins/src/base/skills/lisa-jam-access/SKILL.md +7 -10
  244. package/plugins/src/base/skills/lisa-jira-journey/SKILL.md +5 -0
  245. package/plugins/src/base/skills/lisa-jira-journey/scripts/parse-plan.py +36 -2
  246. package/plugins/src/base/skills/lisa-jira-sync/SKILL.md +1 -1
  247. package/plugins/src/base/skills/lisa-linear-access/SKILL.md +8 -12
  248. package/plugins/src/base/skills/lisa-linear-sync/SKILL.md +1 -1
  249. package/plugins/src/base/skills/lisa-nightly-lower-code-complexity/SKILL.md +7 -1
  250. package/plugins/src/base/skills/lisa-notion-access/SKILL.md +7 -10
  251. package/plugins/src/base/skills/lisa-posthog-access/SKILL.md +7 -10
  252. package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +19 -3
  253. package/plugins/src/base/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +10 -6
  254. package/plugins/src/base/skills/lisa-sentry-access/SKILL.md +7 -10
  255. package/plugins/src/base/skills/lisa-setup-atlassian/SKILL.md +7 -10
  256. package/plugins/src/base/skills/lisa-setup-linear/SKILL.md +9 -12
  257. package/plugins/src/base/skills/lisa-setup-notion/SKILL.md +7 -10
  258. package/plugins/src/base/skills/lisa-setup-remote-aws/SKILL.md +7 -2
  259. package/plugins/src/base/skills/lisa-setup-remote-env/SKILL.md +6 -1
  260. package/plugins/src/base/skills/lisa-setup-remote-env/assets/session-start.sh +19 -0
  261. package/plugins/src/base/skills/lisa-setup-remote-env/assets/setup.sh +1 -0
  262. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +25 -4
  263. package/plugins/src/base/skills/lisa-setup-workstation/scripts/bounded-child.mjs +9 -0
  264. package/plugins/src/base/skills/lisa-setup-workstation/scripts/workstation.mjs +29 -21
  265. package/plugins/src/base/skills/lisa-sonarcloud-access/SKILL.md +7 -10
  266. package/plugins/src/base/skills/lisa-tracker-sync/SKILL.md +1 -1
  267. package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +31 -7
  268. package/scripts/check-release-package-identity.mjs +51 -41
  269. package/scripts/lisa-remote-env/session-start.sh +19 -0
  270. package/scripts/lisa-remote-env/setup.sh +1 -0
  271. package/scripts/two-channel-couplings.json +1 -1
  272. package/typescript/copy-contents/.husky/pre-push +1 -1
  273. package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +31 -7
  274. package/typescript/package-lisa/package.lisa.json +1 -1
@@ -90,6 +90,33 @@ function tarMember(tarballPath, member) {
90
90
  }
91
91
  }
92
92
 
93
+ /** Create the one npm archive whose bytes will be published. */
94
+ function createCandidateArchive(root, packDestination) {
95
+ mkdirSync(packDestination, { recursive: true });
96
+ if (readdirSync(packDestination).length !== 0) {
97
+ mismatch(`pack destination must be empty (${packDestination})`);
98
+ }
99
+ try {
100
+ return String(
101
+ boundedExecFileSync(
102
+ "npm",
103
+ [
104
+ "pack",
105
+ "--ignore-scripts",
106
+ "--silent",
107
+ "--pack-destination",
108
+ packDestination,
109
+ ],
110
+ { cwd: root, encoding: "utf8", timeout: 120_000 }
111
+ )
112
+ );
113
+ } catch (error) {
114
+ mismatch(
115
+ `npm pack failed before publication (${error instanceof Error ? error.message : String(error)})`
116
+ );
117
+ }
118
+ }
119
+
93
120
  /** Extract the one workspace package version certified by generated bytes. */
94
121
  function certificateVersion(bytes, label) {
95
122
  const match = String(bytes).match(
@@ -115,18 +142,24 @@ export function assertReleaseCommit(releaseCommit) {
115
142
  * @returns {{headCommit: string, tagCommit: string, releaseCommit: string}}
116
143
  */
117
144
  export function assertCheckoutIdentity({ root, releaseCommit, tag }) {
118
- assertReleaseCommit(releaseCommit);
145
+ const validatedReleaseCommit = assertReleaseCommit(releaseCommit);
119
146
  const headCommit = gitCommit(root, "HEAD");
120
- if (headCommit !== releaseCommit) {
147
+ if (headCommit !== validatedReleaseCommit) {
121
148
  mismatch(
122
- `HEAD ${headCommit} does not match release commit ${releaseCommit}`
149
+ `HEAD ${headCommit} does not match release commit ${validatedReleaseCommit}`
123
150
  );
124
151
  }
125
152
  const tagCommit = gitCommit(root, tag);
126
- if (tagCommit !== releaseCommit) {
127
- mismatch(`tag ${tag} resolves ${tagCommit}, expected ${releaseCommit}`);
153
+ if (tagCommit !== validatedReleaseCommit) {
154
+ mismatch(
155
+ `tag ${tag} resolves ${tagCommit}, expected ${validatedReleaseCommit}`
156
+ );
128
157
  }
129
- return Object.freeze({ headCommit, tagCommit, releaseCommit });
158
+ return Object.freeze({
159
+ headCommit,
160
+ tagCommit,
161
+ releaseCommit: validatedReleaseCommit,
162
+ });
130
163
  }
131
164
 
132
165
  /**
@@ -140,21 +173,21 @@ export function packAndValidateReleaseCandidate({
140
173
  releaseCommit,
141
174
  packDestination,
142
175
  }) {
143
- assertReleaseCommit(releaseCommit);
176
+ const validatedReleaseCommit = assertReleaseCommit(releaseCommit);
144
177
  const packageJson = readJson(path.join(root, "package.json"), "package.json");
145
178
  if (packageJson.version !== version) {
146
179
  mismatch(
147
180
  `package.json version ${JSON.stringify(packageJson.version)} expected ${version}`
148
181
  );
149
182
  }
150
- if (packageJson.lisaReleaseCommit !== releaseCommit) {
183
+ if (packageJson.lisaReleaseCommit !== validatedReleaseCommit) {
151
184
  mismatch(
152
- `package.json lisaReleaseCommit ${JSON.stringify(packageJson.lisaReleaseCommit)} expected ${releaseCommit}`
185
+ `package.json lisaReleaseCommit ${JSON.stringify(packageJson.lisaReleaseCommit)} expected ${validatedReleaseCommit}`
153
186
  );
154
187
  }
155
- if (packageJson.gitHead !== releaseCommit) {
188
+ if (packageJson.gitHead !== validatedReleaseCommit) {
156
189
  mismatch(
157
- `package.json gitHead ${JSON.stringify(packageJson.gitHead)} expected ${releaseCommit}`
190
+ `package.json gitHead ${JSON.stringify(packageJson.gitHead)} expected ${validatedReleaseCommit}`
158
191
  );
159
192
  }
160
193
 
@@ -172,30 +205,7 @@ export function packAndValidateReleaseCandidate({
172
205
  }
173
206
  }
174
207
 
175
- mkdirSync(packDestination, { recursive: true });
176
- if (readdirSync(packDestination).length !== 0) {
177
- mismatch(`pack destination must be empty (${packDestination})`);
178
- }
179
- let packOutput;
180
- try {
181
- packOutput = String(
182
- boundedExecFileSync(
183
- "npm",
184
- [
185
- "pack",
186
- "--ignore-scripts",
187
- "--silent",
188
- "--pack-destination",
189
- packDestination,
190
- ],
191
- { cwd: root, encoding: "utf8", timeout: 120_000 }
192
- )
193
- );
194
- } catch (error) {
195
- mismatch(
196
- `npm pack failed before publication (${error instanceof Error ? error.message : String(error)})`
197
- );
198
- }
208
+ const packOutput = createCandidateArchive(root, packDestination);
199
209
  const candidates = readdirSync(packDestination).filter(file =>
200
210
  file.endsWith(".tgz")
201
211
  );
@@ -223,14 +233,14 @@ export function packAndValidateReleaseCandidate({
223
233
  `packed package version ${JSON.stringify(packedPackage.version)} expected ${version}`
224
234
  );
225
235
  }
226
- if (packedPackage.lisaReleaseCommit !== releaseCommit) {
236
+ if (packedPackage.lisaReleaseCommit !== validatedReleaseCommit) {
227
237
  mismatch(
228
- `packed lisaReleaseCommit ${JSON.stringify(packedPackage.lisaReleaseCommit)} expected ${releaseCommit}`
238
+ `packed lisaReleaseCommit ${JSON.stringify(packedPackage.lisaReleaseCommit)} expected ${validatedReleaseCommit}`
229
239
  );
230
240
  }
231
- if (packedPackage.gitHead !== releaseCommit) {
241
+ if (packedPackage.gitHead !== validatedReleaseCommit) {
232
242
  mismatch(
233
- `packed gitHead ${JSON.stringify(packedPackage.gitHead)} expected ${releaseCommit}`
243
+ `packed gitHead ${JSON.stringify(packedPackage.gitHead)} expected ${validatedReleaseCommit}`
234
244
  );
235
245
  }
236
246
 
@@ -241,7 +251,7 @@ export function packAndValidateReleaseCandidate({
241
251
  packedCertificateVersion = certificateVersion(member, "packed certificate");
242
252
  if (packedCertificateVersion !== version) {
243
253
  mismatch(
244
- `packed certificate names ${packedCertificateVersion}, expected ${version} at release commit ${releaseCommit} (${PACKED_CERTIFICATE})`
254
+ `packed certificate names ${packedCertificateVersion}, expected ${version} at release commit ${validatedReleaseCommit} (${PACKED_CERTIFICATE})`
245
255
  );
246
256
  }
247
257
  certificateMemberSha256 = sha256(member);
@@ -249,7 +259,7 @@ export function packAndValidateReleaseCandidate({
249
259
 
250
260
  return Object.freeze({
251
261
  version,
252
- releaseCommit,
262
+ releaseCommit: validatedReleaseCommit,
253
263
  certificateVersion: packedCertificateVersion,
254
264
  tarballPath,
255
265
  tarballSha256: sha256(tarballBytes),
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ // This file is managed by Lisa and IS replaced on each `lisa` run.
3
+ // Do not edit directly — durable changes belong upstream in Lisa.
4
+
5
+ /**
6
+ * Run one shell command in its own process group and reap the whole group.
7
+ *
8
+ * Node's synchronous timeout signals only the direct child. A gate command is
9
+ * a tree (shell, package manager, test workers), so killing only the shell
10
+ * leaves descendants running against scratch state the caller then removes.
11
+ * This small asynchronous supervisor is invoked through a synchronous parent:
12
+ * it owns the process-group id, applies the deadline, terminates the group, and
13
+ * only then lets the parent continue.
14
+ * @module scripts/lib/process-tree-runner
15
+ */
16
+
17
+ import { spawn, spawnSync } from "node:child_process";
18
+
19
+ import { invokedAsScript } from "./invoked-as-script.mjs";
20
+
21
+ const KILL_GRACE_MS = 750;
22
+
23
+ function parseArguments(argv) {
24
+ const timeoutArg = argv.find(value => value.startsWith("--timeout-ms="));
25
+ const separator = argv.indexOf("--");
26
+ const timeoutMs = Number(timeoutArg?.slice("--timeout-ms=".length));
27
+ const command = separator >= 0 ? argv.slice(separator + 1).join(" ") : "";
28
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || !command) {
29
+ throw new Error(
30
+ "usage: process-tree-runner.mjs --timeout-ms=<positive-ms> -- <command>"
31
+ );
32
+ }
33
+ return { command, timeoutMs };
34
+ }
35
+
36
+ function killTree(pid, signal) {
37
+ if (process.platform === "win32") {
38
+ // Windows has no negative-pid process groups. taskkill /T is the native
39
+ // tree primitive; /F is used only for the escalation.
40
+ const args = ["/pid", String(pid), "/T"];
41
+ if (signal === "SIGKILL") args.push("/F");
42
+ spawnSync("taskkill", args, { stdio: "ignore", timeout: KILL_GRACE_MS });
43
+ return;
44
+ }
45
+ try {
46
+ process.kill(-pid, signal);
47
+ } catch {
48
+ // ESRCH means the whole group already exited.
49
+ }
50
+ }
51
+
52
+ export function supervise(command, timeoutMs) {
53
+ return new Promise((resolve, reject) => {
54
+ const shell =
55
+ process.platform === "win32"
56
+ ? process.env.ComSpec || "cmd.exe"
57
+ : "/bin/sh";
58
+ const shellArgs =
59
+ process.platform === "win32"
60
+ ? ["/d", "/s", "/c", command]
61
+ : ["-c", command];
62
+ const child = spawn(shell, shellArgs, {
63
+ detached: true,
64
+ env: process.env,
65
+ stdio: "inherit",
66
+ });
67
+ const pid = child.pid;
68
+ if (pid === undefined) {
69
+ reject(new Error("gate process tree did not start"));
70
+ return;
71
+ }
72
+
73
+ let timedOut = false;
74
+ let settled = false;
75
+ const finish = (code, signal) => {
76
+ if (settled) return;
77
+ settled = true;
78
+ clearTimeout(deadline);
79
+ // A command may leave a background descendant after its direct shell
80
+ // exits. Reap that residue before returning a verdict too.
81
+ killTree(pid, "SIGTERM");
82
+ resolve({ code, signal });
83
+ };
84
+
85
+ const deadline = setTimeout(() => {
86
+ timedOut = true;
87
+ killTree(pid, "SIGTERM");
88
+ setTimeout(() => {
89
+ killTree(pid, "SIGKILL");
90
+ // A signal-shaped result is the existing gate runner vocabulary for
91
+ // "no verdict". It also prevents an ordinary exit code such as 124
92
+ // from being confused with a user command that returned that code.
93
+ if (process.platform === "win32") {
94
+ process.exit(124);
95
+ } else {
96
+ process.kill(process.pid, "SIGKILL");
97
+ }
98
+ }, KILL_GRACE_MS);
99
+ }, timeoutMs);
100
+
101
+ child.once("error", error => {
102
+ clearTimeout(deadline);
103
+ killTree(pid, "SIGKILL");
104
+ reject(error);
105
+ });
106
+ child.once("close", (code, signal) => {
107
+ // On timeout, wait for the forced group reap above before this supervisor
108
+ // ends. Exiting on the direct shell's close is the race that used to leave
109
+ // its descendants alive.
110
+ if (!timedOut) finish(code, signal);
111
+ });
112
+ });
113
+ }
114
+
115
+ async function main() {
116
+ const { command, timeoutMs } = parseArguments(process.argv.slice(2));
117
+ const result = await supervise(command, timeoutMs);
118
+ if (result.signal) {
119
+ process.exitCode = 128;
120
+ } else {
121
+ process.exitCode = result.code ?? 1;
122
+ }
123
+ }
124
+
125
+ if (invokedAsScript(import.meta.url)) {
126
+ await main();
127
+ }
@@ -85,11 +85,45 @@ const DEPENDENCY_SECTIONS = [
85
85
  * means: `^8` is `>=8.0.0 <9.0.0` and `1.x` is `>=1.0.0 <2.0.0`. Only the lower
86
86
  * bound is read here, so the upper half costs nothing to ignore.
87
87
  *
88
- * Still one left-to-right pass: the optional groups each have a literal `.`
89
- * ahead of them, so there is no position at which the engine has two viable
90
- * ways to match and nothing to backtrack over (S5852).
88
+ * This parser deliberately uses no version-shaped regular expression. The
89
+ * collision gate consumes dependency text supplied by a repository, so even a
90
+ * bounded-looking optional-group expression becomes an avoidable ReDoS review
91
+ * burden. A tiny left-to-right scanner is both clearer and constant-work per
92
+ * input character.
91
93
  */
92
- const BRANCH_VERSION = /^[\sv=><~^]*(\d+)(?:\.(\d+))?(?:\.(\d+))?/;
94
+ function parseBranchVersion(branch) {
95
+ let cursor = branch.trimStart();
96
+ while (cursor.startsWith("v")) cursor = cursor.slice(1);
97
+
98
+ const operator = [">=", ">", "=", "^", "~"].find(candidate =>
99
+ cursor.startsWith(candidate)
100
+ );
101
+ if (operator) cursor = cursor.slice(operator.length);
102
+ cursor = cursor.trimStart();
103
+
104
+ let end = 0;
105
+ while (end < cursor.length) {
106
+ const character = cursor[end];
107
+ const digit = character >= "0" && character <= "9";
108
+ const next = cursor[end + 1];
109
+ const componentSeparator = character === "." && next >= "0" && next <= "9";
110
+ if (!digit && !componentSeparator) break;
111
+ end += 1;
112
+ }
113
+
114
+ const parts = cursor.slice(0, end).split(".");
115
+ if (
116
+ parts.length > 3 ||
117
+ parts.some(
118
+ part =>
119
+ part === "" ||
120
+ [...part].some(character => character < "0" || character > "9")
121
+ )
122
+ ) {
123
+ return null;
124
+ }
125
+ return { operator, parts };
126
+ }
93
127
 
94
128
  /**
95
129
  * Lowest version a single disjunction branch permits.
@@ -104,13 +138,34 @@ const BRANCH_VERSION = /^[\sv=><~^]*(\d+)(?:\.(\d+))?(?:\.(\d+))?/;
104
138
  function branchLowerBound(branch) {
105
139
  const trimmed = branch.trim();
106
140
  if (trimmed === "") return null;
107
- if (/^<=?\s*\d/.test(trimmed)) return [0, 0, 0];
108
- const match = BRANCH_VERSION.exec(trimmed);
109
- if (!match) return null;
141
+ const upperOperand = trimmed
142
+ .slice(trimmed.startsWith("<=") ? 2 : 1)
143
+ .trimStart();
144
+ if (
145
+ trimmed.startsWith("<") &&
146
+ upperOperand[0] >= "0" &&
147
+ upperOperand[0] <= "9"
148
+ ) {
149
+ return [0, 0, 0];
150
+ }
151
+ const parsed = parseBranchVersion(trimmed);
152
+ if (!parsed) return null;
110
153
  // An absent minor or patch is zero, not missing: `^8` is `>=8.0.0`. Number()
111
154
  // of undefined is NaN, which would compare as neither higher nor lower than
112
155
  // anything and quietly disable the comparison it feeds.
113
- return [match[1], match[2] ?? "0", match[3] ?? "0"].map(Number);
156
+ const lower = [...parsed.parts, "0", "0"].slice(0, 3).map(Number);
157
+ if (parsed.operator !== ">") return lower;
158
+
159
+ // npm expands a STRICT comparator on a partial version before comparing:
160
+ // `>1` is `>=2.0.0` and `>1.2` is `>=1.3.0`. Reading either as merely
161
+ // 1.0.0/1.2.0 invents versions the range does not permit and can report an
162
+ // adequate dependency line as weaker than its override.
163
+ const lastSpecified = parsed.parts.length - 1;
164
+ lower[lastSpecified] += 1;
165
+ for (let index = lastSpecified + 1; index < lower.length; index += 1) {
166
+ lower[index] = 0;
167
+ }
168
+ return lower;
114
169
  }
115
170
 
116
171
  /**
@@ -1067,8 +1067,38 @@ readonly AGENT_CREDENTIAL='(\.(claude|codex|cursor|copilot|gemini|antigravity)/[
1067
1067
  # 15d. Dotenv files. `.env.example`, `.sample`, `.template`, `.dist` and
1068
1068
  # `.schema` are checked-in documentation of which keys exist, never the values,
1069
1069
  # so they are excluded — that collision is the one this rule most had to avoid.
1070
- readonly DOTENV_SECRET='(^|[[:space:]='"$qc"'./])\.env(\.[[:alnum:]_-]+)?([[:space:]'"$qc"']|$)'
1071
- readonly DOTENV_PUBLIC='\.env\.(example|sample|template|dist|schema|defaults)([[:space:]'"$qc"']|$)'
1070
+ # Decide dotenv privacy per PATH, never by subtracting one command-wide match
1071
+ # from another. A command that names `.env.example` and `.env` contains both a
1072
+ # public path and a private one; seeing the former cannot authorize the latter.
1073
+ #
1074
+ # The one write-only shape this guard has always allowed remains explicit:
1075
+ # seeding a private dotenv destination from one public template. This is safe
1076
+ # because the private path is the destination, not a value being read back.
1077
+ has_private_dotenv_path() {
1078
+ dotenv_scan_status=0
1079
+ dotenv_candidates="$(printf '%s' "$normalized_command_str" \
1080
+ | grep -Eio -- '(^|[[:space:]='"$qc"'./])\.env(\.[[:alnum:]_-]+)?')" \
1081
+ || dotenv_scan_status=$?
1082
+ if [ "$dotenv_scan_status" -gt 1 ]; then
1083
+ block "dotenv path classification failed; denying the credential-store read fail-closed."
1084
+ fi
1085
+
1086
+ while IFS= read -r candidate; do
1087
+ [ -n "$candidate" ] || continue
1088
+ candidate="$(printf '%s' "$candidate" \
1089
+ | tr '[:upper:]' '[:lower:]' \
1090
+ | grep -Eo -- '\.env(\.[[:alnum:]_-]+)?')"
1091
+ case "$candidate" in
1092
+ .env.example | .env.sample | .env.template | .env.dist | .env.schema | .env.defaults) ;;
1093
+ *) return 0 ;;
1094
+ esac
1095
+ done <<EOF
1096
+ $dotenv_candidates
1097
+ EOF
1098
+ return 1
1099
+ }
1100
+
1101
+ readonly SAFE_DOTENV_SEED='^[[:space:]]*(cp|mv)[[:space:]]+\.env\.(example|sample|template|dist|schema|defaults)[[:space:]]+\.env(\.[[:alnum:]_-]+)?[[:space:]]*$'
1072
1102
 
1073
1103
  if matches "$SECRET_READ_VERB"; then
1074
1104
  if matches "$SSH_PRIVATE_KEY"; then
@@ -1080,10 +1110,10 @@ if matches "$SECRET_READ_VERB"; then
1080
1110
  if matches "$AGENT_CREDENTIAL"; then
1081
1111
  block "reading, copying or transmitting a coding-agent or package-registry credential store. These tokens impersonate the operator to every service the agent can reach."
1082
1112
  fi
1083
- # `.env` only when the path is NOT one of the public example forms. Ordered as
1084
- # an exclusion rather than a narrower pattern so a new example suffix is one
1085
- # word to add, and so the reason a file is exempt stays readable.
1086
- if matches "$DOTENV_SECRET" && ! matches "$DOTENV_PUBLIC"; then
1113
+ # A public path elsewhere in the command never exempts a private path. The
1114
+ # narrow public-template -> private-destination seed is write-only and remains
1115
+ # allowed; every other command containing a private dotenv path is refused.
1116
+ if has_private_dotenv_path && ! matches "$SAFE_DOTENV_SEED"; then
1087
1117
  block "reading, copying or transmitting a dotenv file holding real values. \`.env.example\`, \`.env.sample\`, \`.env.template\` and \`.env.dist\` are readable, and WRITING a .env is unaffected — only reading one back is refused."
1088
1118
  fi
1089
1119
  # Fail-closed, narrowly. A read whose DIRECTORY is a known credential store
@@ -47,7 +47,7 @@
47
47
  import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
48
48
  import { dirname, join } from "node:path";
49
49
 
50
- import { boundedSpawnSync } from "./lib/bounded-spawn.mjs";
50
+ import { boundedSpawnSync, isChildTimeout } from "./lib/bounded-spawn.mjs";
51
51
  import { invokedAsScript } from "./lib/invoked-as-script.mjs";
52
52
 
53
53
  /**
@@ -148,18 +148,40 @@ export function runPostinstall(cwd = process.cwd(), env = process.env) {
148
148
  return;
149
149
  }
150
150
 
151
- const child = boundedSpawnSync(
152
- process.execPath,
153
- [LISA_ENTRY, "--yes", "--skip-git-check", "."],
154
- {
155
- cwd,
156
- env: { ...env, LISA_BOOTSTRAP: "1" },
157
- encoding: "utf8",
158
- // Inherited, NOT captured-and-discarded. The whole point.
159
- stdio: ["ignore", "inherit", "pipe"],
160
- timeout: APPLY_BUDGET_MS,
161
- }
162
- );
151
+ let child;
152
+ try {
153
+ child = boundedSpawnSync(
154
+ process.execPath,
155
+ [LISA_ENTRY, "--yes", "--skip-git-check", "."],
156
+ {
157
+ cwd,
158
+ env: { ...env, LISA_BOOTSTRAP: "1" },
159
+ encoding: "utf8",
160
+ // Inherited, NOT captured-and-discarded. The whole point.
161
+ stdio: ["ignore", "inherit", "pipe"],
162
+ timeout: APPLY_BUDGET_MS,
163
+ }
164
+ );
165
+ } catch (error) {
166
+ // A timeout is a failed APPLY, not a failed INSTALL. The bounded child
167
+ // deliberately throws so ordinary callers fail closed; this caller has a
168
+ // stronger, documented contract to leave a durable marker and let the
169
+ // package manager finish. Unknown throws still escape because converting a
170
+ // broken postinstall module into an apparent apply failure would hide the
171
+ // defect that needs fixing.
172
+ if (!isChildTimeout(error)) throw error;
173
+ child = {
174
+ error: Object.assign(
175
+ new Error(
176
+ "Lisa template apply exceeded its ten-minute deadline and was killed."
177
+ ),
178
+ { code: "ETIMEDOUT" }
179
+ ),
180
+ status: null,
181
+ stderr:
182
+ "Lisa template apply exceeded its ten-minute deadline and was killed.\n",
183
+ };
184
+ }
163
185
 
164
186
  const stderr = child.stderr ?? "";
165
187
  if (stderr) process.stderr.write(stderr);
@@ -718,9 +718,17 @@ export function reconcileContexts({
718
718
  if (record.ruleset !== undefined && entry.ruleset !== record.ruleset) {
719
719
  return false;
720
720
  }
721
- return !(
722
- record.integration_id !== undefined &&
723
- entry.integration_id !== record.integration_id
721
+ // Configured ruleset policy defaults to GitHub Actions when it carries no
722
+ // explicit pin. That is the same default the writer applies, so comparison
723
+ // and repair converge. A gate-derived record names no ruleset and remains
724
+ // name-only: it never declared which app may satisfy it.
725
+ if (record.ruleset !== undefined) {
726
+ const expected = record.integration_id ?? ACTIONS_INTEGRATION_ID;
727
+ return entry.integration_id === expected;
728
+ }
729
+ return (
730
+ record.integration_id === undefined ||
731
+ entry.integration_id === record.integration_id
724
732
  );
725
733
  };
726
734
  const unsatisfied = declarations.filter(
@@ -358,10 +358,9 @@ function normaliseExec(raw) {
358
358
  * ## Two kinds that look eligible and are not
359
359
  *
360
360
  * `uncaptured` is NOT here, and the attempt to include it is what proves the
361
- * distinction. Capture is off by default (`LISA_GATES_CAPTURE=1` turns it on),
362
- * so `uncaptured` is the ORDINARY outcome of an ordinary failing gate — four
363
- * existing cases went red the moment it was added, every one of them a plain
364
- * `lint` gate exiting 1. A gate that exited nonzero did measure something; the
361
+ * distinction. Capture is on by default (`LISA_GATES_CAPTURE=0` turns it off),
362
+ * so `uncaptured` is an explicit loss of diagnostic evidence rather than the
363
+ * ordinary outcome. A gate that exited nonzero did measure something; the
365
364
  * runner merely did not keep the transcript. Missing evidence about a real
366
365
  * failure is not the same claim as a captured transcript that describes no
367
366
  * failure at all, which is what `undiagnosed` means.
@@ -888,6 +887,33 @@ const CAPTURE_TAIL_BYTES = 512 * 1024;
888
887
  */
889
888
  const GATE_COMMAND_BUDGET_MS = 2 * 60 * 60 * 1000;
890
889
 
890
+ /** Extra ceiling for the supervisor to reap descendants after its deadline. */
891
+ const PROCESS_TREE_REAP_BUDGET_MS = 10_000;
892
+
893
+ /** The asynchronous supervisor used behind this synchronous gate API. */
894
+ const PROCESS_TREE_RUNNER = fileURLToPath(
895
+ new URL("./lib/process-tree-runner.mjs", import.meta.url)
896
+ );
897
+
898
+ /**
899
+ * Run a shell command under a process-tree deadline.
900
+ * @param {string} command Shell source.
901
+ * @param {object} options Child options passed to the supervisor.
902
+ * @returns {import("node:child_process").SpawnSyncReturns<string>} Result.
903
+ */
904
+ function runProcessTree(command, options = {}) {
905
+ const timeout = options.timeout ?? GATE_COMMAND_BUDGET_MS;
906
+ const { shell: _shell, ...childOptions } = options;
907
+ return boundedSpawnSync(
908
+ process.execPath,
909
+ [PROCESS_TREE_RUNNER, `--timeout-ms=${timeout}`, "--", command],
910
+ {
911
+ ...childOptions,
912
+ timeout: timeout + PROCESS_TREE_REAP_BUDGET_MS,
913
+ }
914
+ );
915
+ }
916
+
891
917
  /**
892
918
  * Convert a killed child into this file's no-verdict answer, or re-raise.
893
919
  *
@@ -916,8 +942,7 @@ function killedOrRethrow(error) {
916
942
  */
917
943
  function plainExec(command) {
918
944
  try {
919
- const child = boundedSpawnSync(command, [], {
920
- shell: true,
945
+ const child = runProcessTree(command, {
921
946
  stdio: "inherit",
922
947
  timeout: GATE_COMMAND_BUDGET_MS,
923
948
  });
@@ -1015,9 +1040,17 @@ export function spawnExec(command) {
1015
1040
  const statusPath = join(dir, "status");
1016
1041
  // Newline-separated inside a group, so a command ending in a trailing
1017
1042
  // comment or redirection still has `echo` run as its own statement.
1018
- const script = `{\n${command}\necho $? > '${statusPath}'\n} 2>&1 | tee '${logPath}'\n`;
1043
+ // Paths travel as data, never as shell source. A TMPDIR containing a quote,
1044
+ // newline, dollar sign, or command substitution must not be able to break
1045
+ // this wrapper or execute content merely because mkdtemp inherited it.
1046
+ const script = `{\n${command}\necho $? > "$LISA_GATE_STATUS_PATH"\n} 2>&1 | tee "$LISA_GATE_LOG_PATH"\n`;
1019
1047
  try {
1020
- const child = boundedSpawnSync("sh", ["-c", script], {
1048
+ const child = runProcessTree(script, {
1049
+ env: {
1050
+ ...process.env,
1051
+ LISA_GATE_LOG_PATH: logPath,
1052
+ LISA_GATE_STATUS_PATH: statusPath,
1053
+ },
1021
1054
  stdio: "inherit",
1022
1055
  timeout: GATE_COMMAND_BUDGET_MS,
1023
1056
  });
@@ -150,9 +150,9 @@ function resolveRef(ref, root) {
150
150
  const defs = isSchemaObject(root.$defs) ? root.$defs : {};
151
151
  const target =
152
152
  match && Object.hasOwn(defs, match[1]) ? defs[match[1]] : undefined;
153
- if (!target) {
153
+ if (!isSchemaObject(target)) {
154
154
  throw new Error(
155
- `unsupported or unresolvable $ref "${ref}" (only #/$defs/<name> is supported)`
155
+ `unsupported or unresolvable $ref "${ref}" (only #/$defs/<name> resolving to a subschema object is supported)`
156
156
  );
157
157
  }
158
158
  return target;
@@ -536,7 +536,8 @@ function verifyLevel(config) {
536
536
  throw new TrackingError(
537
537
  `Unknown workItem.verify '${raw}'. Expected "trailer" (the default: ` +
538
538
  `prove the Work-Item reference, contact no tracker) or "full" (also ` +
539
- `prove the tracker item's repo scope, claimed state, and PR backlink)`
539
+ `prove the tracker item exists and is open, has repo scope, is a leaf, ` +
540
+ `and carries the PR backlink)`
540
541
  );
541
542
  }
542
543
  return value;
@@ -8,7 +8,7 @@
8
8
  "prepare": "husky install || true",
9
9
  "test": "vitest run",
10
10
  "test:unit": "vitest run --exclude='**/integration/**'",
11
- "test:integration:lisa": "vitest run '.integration.' 'integration/' --passWithNoTests",
11
+ "test:integration:lisa": "vitest run '.integration.' 'integration/'",
12
12
  "test:cov": "vitest run --coverage",
13
13
  "test:mutation": "node scripts/lisa-mutation.mjs",
14
14
  "test:watch": "vitest",
@@ -1 +1 @@
1
- {"version":3,"file":"lisa-owned-hash-ledger.d.ts","sourceRoot":"","sources":["../../src/core/lisa-owned-hash-ledger.ts"],"names":[],"mappings":"AACA,6EAA6E;AAE7E;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAC3C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAitBjC,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CACpD,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAonBjC,CAAC"}
1
+ {"version":3,"file":"lisa-owned-hash-ledger.d.ts","sourceRoot":"","sources":["../../src/core/lisa-owned-hash-ledger.ts"],"names":[],"mappings":"AACA,6EAA6E;AAE7E;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAC3C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CA8tBjC,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CACpD,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAioBjC,CAAC"}