@codyswann/lisa 4.22.8 → 4.22.10

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 (278) hide show
  1. package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +127 -0
  2. package/all/copy-overwrite/scripts/lisa-floor-collisions.mjs +63 -8
  3. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +36 -6
  4. package/all/copy-overwrite/scripts/lisa-postinstall.mjs +35 -13
  5. package/all/copy-overwrite/scripts/lisa-reconcile-policy.mjs +11 -3
  6. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +41 -8
  7. package/all/copy-overwrite/scripts/lisa-schema-validate.mjs +2 -2
  8. package/all/copy-overwrite/scripts/lisa-work-item.mjs +2 -1
  9. package/cdk/package-lisa/package.lisa.json +1 -1
  10. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  11. package/dist/core/lisa-owned-hash-ledger.js +24 -0
  12. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  13. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  14. package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
  15. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  16. package/dist/core/upstream-evidence-manifest.js +61 -51
  17. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  18. package/dist/opencode/plugin-templates/parity-safety-net.sh +36 -6
  19. package/nestjs/package-lisa/package.lisa.json +1 -1
  20. package/package.json +3 -3
  21. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  23. package/plugins/lisa/.codex-plugin/skills/lisa-atlassian-access/SKILL.md +10 -13
  24. package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +4 -2
  25. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +29 -4
  26. package/plugins/lisa/.codex-plugin/skills/lisa-github-sync/SKILL.md +8 -3
  27. package/plugins/lisa/.codex-plugin/skills/lisa-jam-access/SKILL.md +7 -10
  28. package/plugins/lisa/.codex-plugin/skills/lisa-jira-journey/SKILL.md +5 -0
  29. package/plugins/lisa/.codex-plugin/skills/lisa-jira-journey/scripts/parse-plan.py +36 -2
  30. package/plugins/lisa/.codex-plugin/skills/lisa-jira-sync/SKILL.md +1 -1
  31. package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +8 -12
  32. package/plugins/lisa/.codex-plugin/skills/lisa-linear-sync/SKILL.md +1 -1
  33. package/plugins/lisa/.codex-plugin/skills/lisa-nightly-lower-code-complexity/SKILL.md +7 -1
  34. package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +7 -10
  35. package/plugins/lisa/.codex-plugin/skills/lisa-posthog-access/SKILL.md +7 -10
  36. package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +19 -3
  37. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +10 -6
  38. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +95 -44
  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-secrets-access/scripts/publish-aws-bootstrap.mjs +95 -44
  74. package/plugins/lisa/skills/lisa-sentry-access/SKILL.md +7 -10
  75. package/plugins/lisa/skills/lisa-setup-atlassian/SKILL.md +7 -10
  76. package/plugins/lisa/skills/lisa-setup-linear/SKILL.md +9 -12
  77. package/plugins/lisa/skills/lisa-setup-notion/SKILL.md +7 -10
  78. package/plugins/lisa/skills/lisa-setup-remote-aws/SKILL.md +7 -2
  79. package/plugins/lisa/skills/lisa-setup-remote-env/SKILL.md +6 -1
  80. package/plugins/lisa/skills/lisa-setup-remote-env/assets/session-start.sh +19 -0
  81. package/plugins/lisa/skills/lisa-setup-remote-env/assets/setup.sh +1 -0
  82. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +25 -4
  83. package/plugins/lisa/skills/lisa-setup-workstation/scripts/bounded-child.mjs +9 -0
  84. package/plugins/lisa/skills/lisa-setup-workstation/scripts/workstation.mjs +29 -21
  85. package/plugins/lisa/skills/lisa-sonarcloud-access/SKILL.md +7 -10
  86. package/plugins/lisa/skills/lisa-tracker-sync/SKILL.md +1 -1
  87. package/plugins/lisa-agy/hooks/parity-safety-net.sh +36 -6
  88. package/plugins/lisa-agy/plugin.json +1 -1
  89. package/plugins/lisa-agy/scripts/install-remote-agent-aws.mjs +5 -1
  90. package/plugins/lisa-agy/scripts/rollup-blocker-classification.mjs +18 -3
  91. package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +10 -13
  92. package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +4 -2
  93. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +29 -4
  94. package/plugins/lisa-agy/skills/lisa-github-sync/SKILL.md +8 -3
  95. package/plugins/lisa-agy/skills/lisa-jam-access/SKILL.md +7 -10
  96. package/plugins/lisa-agy/skills/lisa-jira-journey/SKILL.md +5 -0
  97. package/plugins/lisa-agy/skills/lisa-jira-journey/scripts/parse-plan.py +36 -2
  98. package/plugins/lisa-agy/skills/lisa-jira-sync/SKILL.md +1 -1
  99. package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +8 -12
  100. package/plugins/lisa-agy/skills/lisa-linear-sync/SKILL.md +1 -1
  101. package/plugins/lisa-agy/skills/lisa-nightly-lower-code-complexity/SKILL.md +7 -1
  102. package/plugins/lisa-agy/skills/lisa-notion-access/SKILL.md +7 -10
  103. package/plugins/lisa-agy/skills/lisa-posthog-access/SKILL.md +7 -10
  104. package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +19 -3
  105. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +10 -6
  106. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +95 -44
  107. package/plugins/lisa-agy/skills/lisa-sentry-access/SKILL.md +7 -10
  108. package/plugins/lisa-agy/skills/lisa-setup-atlassian/SKILL.md +7 -10
  109. package/plugins/lisa-agy/skills/lisa-setup-linear/SKILL.md +9 -12
  110. package/plugins/lisa-agy/skills/lisa-setup-notion/SKILL.md +7 -10
  111. package/plugins/lisa-agy/skills/lisa-setup-remote-aws/SKILL.md +7 -2
  112. package/plugins/lisa-agy/skills/lisa-setup-remote-env/SKILL.md +6 -1
  113. package/plugins/lisa-agy/skills/lisa-setup-remote-env/assets/session-start.sh +19 -0
  114. package/plugins/lisa-agy/skills/lisa-setup-remote-env/assets/setup.sh +1 -0
  115. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +25 -4
  116. package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/bounded-child.mjs +9 -0
  117. package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/workstation.mjs +29 -21
  118. package/plugins/lisa-agy/skills/lisa-sonarcloud-access/SKILL.md +7 -10
  119. package/plugins/lisa-agy/skills/lisa-tracker-sync/SKILL.md +1 -1
  120. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  121. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  122. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  123. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  124. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  125. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  126. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +36 -6
  127. package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +31 -7
  128. package/plugins/lisa-copilot/rules/reference/base-rules.md +1 -1
  129. package/plugins/lisa-copilot/rules/reference/config-resolution.md +5 -14
  130. package/plugins/lisa-copilot/scripts/install-remote-agent-aws.mjs +5 -1
  131. package/plugins/lisa-copilot/scripts/rollup-blocker-classification.mjs +18 -3
  132. package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +10 -13
  133. package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +4 -2
  134. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +29 -4
  135. package/plugins/lisa-copilot/skills/lisa-github-sync/SKILL.md +8 -3
  136. package/plugins/lisa-copilot/skills/lisa-jam-access/SKILL.md +7 -10
  137. package/plugins/lisa-copilot/skills/lisa-jira-journey/SKILL.md +5 -0
  138. package/plugins/lisa-copilot/skills/lisa-jira-journey/scripts/parse-plan.py +36 -2
  139. package/plugins/lisa-copilot/skills/lisa-jira-sync/SKILL.md +1 -1
  140. package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +8 -12
  141. package/plugins/lisa-copilot/skills/lisa-linear-sync/SKILL.md +1 -1
  142. package/plugins/lisa-copilot/skills/lisa-nightly-lower-code-complexity/SKILL.md +7 -1
  143. package/plugins/lisa-copilot/skills/lisa-notion-access/SKILL.md +7 -10
  144. package/plugins/lisa-copilot/skills/lisa-posthog-access/SKILL.md +7 -10
  145. package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +19 -3
  146. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +10 -6
  147. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +95 -44
  148. package/plugins/lisa-copilot/skills/lisa-sentry-access/SKILL.md +7 -10
  149. package/plugins/lisa-copilot/skills/lisa-setup-atlassian/SKILL.md +7 -10
  150. package/plugins/lisa-copilot/skills/lisa-setup-linear/SKILL.md +9 -12
  151. package/plugins/lisa-copilot/skills/lisa-setup-notion/SKILL.md +7 -10
  152. package/plugins/lisa-copilot/skills/lisa-setup-remote-aws/SKILL.md +7 -2
  153. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/SKILL.md +6 -1
  154. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/assets/session-start.sh +19 -0
  155. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/assets/setup.sh +1 -0
  156. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +25 -4
  157. package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/bounded-child.mjs +9 -0
  158. package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/workstation.mjs +29 -21
  159. package/plugins/lisa-copilot/skills/lisa-sonarcloud-access/SKILL.md +7 -10
  160. package/plugins/lisa-copilot/skills/lisa-tracker-sync/SKILL.md +1 -1
  161. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  162. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +36 -6
  163. package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +31 -7
  164. package/plugins/lisa-cursor/rules/base-rules-reference.mdc +1 -1
  165. package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +5 -14
  166. package/plugins/lisa-cursor/scripts/install-remote-agent-aws.mjs +5 -1
  167. package/plugins/lisa-cursor/scripts/rollup-blocker-classification.mjs +18 -3
  168. package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +10 -13
  169. package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +4 -2
  170. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +29 -4
  171. package/plugins/lisa-cursor/skills/lisa-github-sync/SKILL.md +8 -3
  172. package/plugins/lisa-cursor/skills/lisa-jam-access/SKILL.md +7 -10
  173. package/plugins/lisa-cursor/skills/lisa-jira-journey/SKILL.md +5 -0
  174. package/plugins/lisa-cursor/skills/lisa-jira-journey/scripts/parse-plan.py +36 -2
  175. package/plugins/lisa-cursor/skills/lisa-jira-sync/SKILL.md +1 -1
  176. package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +8 -12
  177. package/plugins/lisa-cursor/skills/lisa-linear-sync/SKILL.md +1 -1
  178. package/plugins/lisa-cursor/skills/lisa-nightly-lower-code-complexity/SKILL.md +7 -1
  179. package/plugins/lisa-cursor/skills/lisa-notion-access/SKILL.md +7 -10
  180. package/plugins/lisa-cursor/skills/lisa-posthog-access/SKILL.md +7 -10
  181. package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +19 -3
  182. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +10 -6
  183. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +95 -44
  184. package/plugins/lisa-cursor/skills/lisa-sentry-access/SKILL.md +7 -10
  185. package/plugins/lisa-cursor/skills/lisa-setup-atlassian/SKILL.md +7 -10
  186. package/plugins/lisa-cursor/skills/lisa-setup-linear/SKILL.md +9 -12
  187. package/plugins/lisa-cursor/skills/lisa-setup-notion/SKILL.md +7 -10
  188. package/plugins/lisa-cursor/skills/lisa-setup-remote-aws/SKILL.md +7 -2
  189. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/SKILL.md +6 -1
  190. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/assets/session-start.sh +19 -0
  191. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/assets/setup.sh +1 -0
  192. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +25 -4
  193. package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/bounded-child.mjs +9 -0
  194. package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/workstation.mjs +29 -21
  195. package/plugins/lisa-cursor/skills/lisa-sonarcloud-access/SKILL.md +7 -10
  196. package/plugins/lisa-cursor/skills/lisa-tracker-sync/SKILL.md +1 -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/parity-safety-net.sh +36 -6
  238. package/plugins/src/base/hooks/threshold-ratchet-families.mjs +31 -7
  239. package/plugins/src/base/rules/reference/base-rules.md +1 -1
  240. package/plugins/src/base/rules/reference/config-resolution.md +5 -14
  241. package/plugins/src/base/scripts/install-remote-agent-aws.mjs +5 -1
  242. package/plugins/src/base/scripts/rollup-blocker-classification.mjs +18 -3
  243. package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +10 -13
  244. package/plugins/src/base/skills/lisa-doctor/SKILL.md +4 -2
  245. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +29 -4
  246. package/plugins/src/base/skills/lisa-github-sync/SKILL.md +8 -3
  247. package/plugins/src/base/skills/lisa-jam-access/SKILL.md +7 -10
  248. package/plugins/src/base/skills/lisa-jira-journey/SKILL.md +5 -0
  249. package/plugins/src/base/skills/lisa-jira-journey/scripts/parse-plan.py +36 -2
  250. package/plugins/src/base/skills/lisa-jira-sync/SKILL.md +1 -1
  251. package/plugins/src/base/skills/lisa-linear-access/SKILL.md +8 -12
  252. package/plugins/src/base/skills/lisa-linear-sync/SKILL.md +1 -1
  253. package/plugins/src/base/skills/lisa-nightly-lower-code-complexity/SKILL.md +7 -1
  254. package/plugins/src/base/skills/lisa-notion-access/SKILL.md +7 -10
  255. package/plugins/src/base/skills/lisa-posthog-access/SKILL.md +7 -10
  256. package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +19 -3
  257. package/plugins/src/base/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +10 -6
  258. package/plugins/src/base/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +95 -44
  259. package/plugins/src/base/skills/lisa-sentry-access/SKILL.md +7 -10
  260. package/plugins/src/base/skills/lisa-setup-atlassian/SKILL.md +7 -10
  261. package/plugins/src/base/skills/lisa-setup-linear/SKILL.md +9 -12
  262. package/plugins/src/base/skills/lisa-setup-notion/SKILL.md +7 -10
  263. package/plugins/src/base/skills/lisa-setup-remote-aws/SKILL.md +7 -2
  264. package/plugins/src/base/skills/lisa-setup-remote-env/SKILL.md +6 -1
  265. package/plugins/src/base/skills/lisa-setup-remote-env/assets/session-start.sh +19 -0
  266. package/plugins/src/base/skills/lisa-setup-remote-env/assets/setup.sh +1 -0
  267. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +25 -4
  268. package/plugins/src/base/skills/lisa-setup-workstation/scripts/bounded-child.mjs +9 -0
  269. package/plugins/src/base/skills/lisa-setup-workstation/scripts/workstation.mjs +29 -21
  270. package/plugins/src/base/skills/lisa-sonarcloud-access/SKILL.md +7 -10
  271. package/plugins/src/base/skills/lisa-tracker-sync/SKILL.md +1 -1
  272. package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +31 -7
  273. package/scripts/lisa-remote-env/session-start.sh +19 -0
  274. package/scripts/lisa-remote-env/setup.sh +1 -0
  275. package/scripts/two-channel-couplings.json +1 -1
  276. package/typescript/copy-contents/.husky/pre-push +1 -1
  277. package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +31 -7
  278. package/typescript/package-lisa/package.lisa.json +1 -1
@@ -41,9 +41,43 @@ from pathlib import Path
41
41
  LOCAL_EVIDENCE_PATTERN = re.compile(r'\[(SCREENSHOT|EVIDENCE):\s*([^\]]+)\]')
42
42
 
43
43
 
44
+ def jira_config_candidates():
45
+ """Return project-first jira-cli config candidates without duplicates."""
46
+ roots = []
47
+ project_dir = os.environ.get("CLAUDE_PROJECT_DIR", "").strip()
48
+ if project_dir:
49
+ roots.append(Path(project_dir).expanduser().resolve())
50
+
51
+ current = Path.cwd().resolve()
52
+ roots.extend([current, *current.parents])
53
+
54
+ candidates = []
55
+ seen = set()
56
+ for root in roots:
57
+ candidate = root / ".lisa" / "jira-cli" / ".config.yml"
58
+ key = str(candidate)
59
+ if key not in seen:
60
+ candidates.append(candidate)
61
+ seen.add(key)
62
+
63
+ home_candidate = (Path.home() / ".config" / ".jira" / ".config.yml").resolve()
64
+ if str(home_candidate) not in seen:
65
+ candidates.append(home_candidate)
66
+ return candidates
67
+
68
+
69
+ def resolve_jira_config_path():
70
+ """Resolve the first existing project config, then the home fallback."""
71
+ candidates = jira_config_candidates()
72
+ for candidate in candidates:
73
+ if candidate.exists():
74
+ return candidate
75
+ return candidates[-1]
76
+
77
+
44
78
  def get_jira_config():
45
- """Read JIRA server and login from jira-cli config."""
46
- config_path = Path.home() / ".config" / ".jira" / ".config.yml"
79
+ """Read JIRA server and login from the project-first jira-cli config."""
80
+ config_path = resolve_jira_config_path()
47
81
  if not config_path.exists():
48
82
  print(f"ERROR: jira-cli config not found at {config_path}", file=sys.stderr)
49
83
  sys.exit(1)
@@ -94,7 +94,7 @@ When invoked with `--rollup`, this skill **derives a parent/container ticket's s
94
94
  | else any child has **started** (`In Progress` / `Code Review`, or shipped to an env while a sibling has not) | `claimed` | `In Progress` |
95
95
  | else (children exist, none started) | — | unchanged — parent keeps its non-ready container status |
96
96
 
97
- - **Blocked dominates** — a single blocked child surfaces `Blocked` on the parent even while siblings progress. It never says *which* child or *which kind* of hold; run `scripts/rollup-blocker-classification.mjs` over the resolved child graph and carry its per-class report — blocking leaf, path, and who must act — into the rollup note. A non-zero exit means it classified nothing; that is a failure to report, never an all-clear. See `leaf-only-lifecycle` → **Classifying a hold**.
97
+ - **Blocked dominates** — a single blocked child surfaces `Blocked` on the parent even while siblings progress. It never says *which* child or *which kind* of hold; resolve and run the shared classifier exactly as `lisa-tracker-sync` specifies, then carry its per-class report — blocking leaf, path, and who must act — into the rollup note. A missing classifier or non-zero exit is a strict **no-write** result: do not transition the parent and do not post/update a rollup comment; report the failure, never an all-clear. See `leaf-only-lifecycle` → **Classifying a hold**.
98
98
  - **Least-advanced env wins** — the parent reaches an env only when every required child has reached at least that env; it never sits ahead of its laggard child. Apply native terminal resolution (the `leaf-only-lifecycle` Terminal native closure) only when the resolved env is the production `Done`, never at `On Dev`/`On Stg`.
99
99
  - **"Required" children only** — won't-do / optional children do not hold the parent open.
100
100
  - **Recursive** — an Epic reaches an env only when its Stories have themselves rolled up to at least that env; a Story reaches it only when its Sub-tasks have. Evaluate bottom-up.
@@ -85,22 +85,18 @@ All GraphQL calls use:
85
85
  read_linear_key() {
86
86
  [ -n "${LINEAR_API_KEY:-}" ] && { echo "$LINEAR_API_KEY"; return; }
87
87
 
88
- # The ladder is ordered repo-first and ends at the plugin's own copy.
89
- #
90
- # Repo-relative rungs lead because a project that vendors the resolver has
91
- # declared which copy it wants used, and that decision must survive this
92
- # change untouched. The plugin rungs are the floor: `resolve-secret.mjs`
88
+ # The ladder is ordered across trusted machine-managed substrates and ends at
89
+ # the installed package. Checkout-local paths are deliberately absent: a
90
+ # familiar generated destination is still repository-controlled executable
91
+ # code. The plugin rungs are the floor: `resolve-secret.mjs`
93
92
  # ships beside this skill, so a rung pointing at it is reachable from
94
93
  # anywhere the plugin itself is installed. Without one, a repo that vendors
95
94
  # none of the leading paths has no route to the key at all — which is what
96
95
  # every consumer of this skill in a `.opencode`-layout repository actually
97
96
  # hit, and why agents started improvising their own key lookups.
98
- local candidates=(
99
- .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
100
- .agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
101
- .opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
102
- .codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
103
- )
97
+ # Execute only machine-managed plugin/package resolvers. Checkout-local
98
+ # candidates are repository-controlled code, not trusted merely by path.
99
+ local candidates=()
104
100
  if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
105
101
  candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
106
102
  fi
@@ -119,7 +115,7 @@ read_linear_key() {
119
115
  local via_lisa
120
116
  via_lisa=$(node "$resolver" get LINEAR_API_KEY 2>/dev/null) \
121
117
  && [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
122
- break
118
+ # Empty/error means this substrate had no answer; try the next trusted one.
123
119
  fi
124
120
  done
125
121
 
@@ -140,7 +140,7 @@ When the caller passes `--rollup`, this skill **derives a parent/container's wor
140
140
  | else any child has **started** (at the `claimed` state, at the `review` state where the project configures one, or shipped to an env while a sibling has not) | `claimed` | the configured `claimed` state |
141
141
  | else (children exist, none started) | — | unchanged — parent keeps its non-ready container state |
142
142
 
143
- - **Blocked dominates** — one blocked child surfaces `Blocked` on the parent even while siblings progress. It never says *which* child or *which kind* of hold; run `scripts/rollup-blocker-classification.mjs` over the resolved child graph and carry its per-class report — blocking leaf, path, and who must act — into the rollup note. A non-zero exit means it classified nothing; that is a failure to report, never an all-clear. See `leaf-only-lifecycle` → **Classifying a hold**.
143
+ - **Blocked dominates** — one blocked child surfaces `Blocked` on the parent even while siblings progress. It never says *which* child or *which kind* of hold; resolve and run the shared classifier exactly as `lisa-tracker-sync` specifies, then carry its per-class report — blocking leaf, path, and who must act — into the rollup note. A missing classifier or non-zero exit is a strict **no-write** result: do not save parent state and do not post/update a rollup comment; report the failure, never an all-clear. See `leaf-only-lifecycle` → **Classifying a hold**.
144
144
  - **Least-advanced env wins** — the parent reaches an env only when every required child has reached at least that env; it never sits ahead of its laggard child. Native completion (moving the workflow `state` to Done) fires only when the resolved env is the production `Done`, never at `On Dev`/`On Stg`.
145
145
  - **"Required" children only** — won't-do / optional (e.g. `Canceled`) children do not hold the parent open.
146
146
  - **Recursive** — a Project reaches an env only when its Issues have themselves rolled up to at least that env. Evaluate bottom-up.
@@ -25,8 +25,14 @@ The caller provides pre-computed context:
25
25
  9. Run the project's typecheck script with the provided package manager to catch type errors early — same reasoning as step 7, so `npm run typecheck` / `yarn typecheck` / `bun run typecheck`, falling back to `./node_modules/.bin/tsc --noEmit` rather than `npx tsc`:
26
26
 
27
27
  ```sh
28
+ case "$PACKAGE_MANAGER" in
29
+ npm) runner=(npm run) ;;
30
+ yarn) runner=(yarn) ;;
31
+ bun) runner=(bun run) ;;
32
+ *) echo "unsupported package manager: $PACKAGE_MANAGER" >&2; exit 2 ;;
33
+ esac
28
34
  status=0
29
- bun run typecheck >tsc.log 2>&1 || status=$?
35
+ "${runner[@]}" typecheck >tsc.log 2>&1 || status=$?
30
36
  head -n 30 tsc.log; echo "exit=$status"
31
37
  ```
32
38
 
@@ -58,9 +58,9 @@ read_notion_token() {
58
58
  # OS keychain here — a second reader is how the same credential ends up living
59
59
  # in two places and drifting.
60
60
  #
61
- # Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
62
- # lead because a project that vendors the resolver has declared which copy it
63
- # wants used, and that decision must survive this change untouched. The plugin
61
+ # Ordered across trusted machine-managed substrates, ending at the installed
62
+ # package. Checkout-local paths are deliberately absent: a familiar generated
63
+ # destination is still repository-controlled executable code. The plugin
64
64
  # rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
65
65
  # pointing at it is reachable from anywhere the plugin itself is installed.
66
66
  # Without one, a consumer repository that vendors none of the leading paths
@@ -69,12 +69,9 @@ read_notion_token() {
69
69
  # lookups. This LADDER is identical in every skill that resolves a credential
70
70
  # and `credential-resolver-ladder` fails if any copy diverges. Only what
71
71
  # happens AFTER the ladder may differ between them.
72
- local candidates=(
73
- .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
74
- .agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
75
- .opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
76
- .codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
77
- )
72
+ # Execute only machine-managed plugin/package resolvers. Checkout-local
73
+ # candidates are repository-controlled code, not trusted merely by path.
74
+ local candidates=()
78
75
  if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
79
76
  candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
80
77
  fi
@@ -93,7 +90,7 @@ read_notion_token() {
93
90
  local via_lisa
94
91
  via_lisa=$(node "$resolver" get NOTION_API_TOKEN 2>/dev/null) \
95
92
  && [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
96
- break
93
+ # Empty/error means this substrate had no answer; try the next trusted one.
97
94
  fi
98
95
  done
99
96
  # Legacy fallback: the OS keychain written by the guided /lisa:setup:notion
@@ -49,9 +49,9 @@ POSTHOG_HOST=${POSTHOG_HOST:-https://app.posthog.com}
49
49
  read_posthog_key() {
50
50
  [ -n "${POSTHOG_PERSONAL_API_KEY:-}" ] && { echo "$POSTHOG_PERSONAL_API_KEY"; return; }
51
51
  #
52
- # Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
53
- # lead because a project that vendors the resolver has declared which copy it
54
- # wants used, and that decision must survive this change untouched. The plugin
52
+ # Ordered across trusted machine-managed substrates, ending at the installed
53
+ # package. Checkout-local paths are deliberately absent: a familiar generated
54
+ # destination is still repository-controlled executable code. The plugin
55
55
  # rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
56
56
  # pointing at it is reachable from anywhere the plugin itself is installed.
57
57
  # Without one, a consumer repository that vendors none of the leading paths
@@ -60,12 +60,9 @@ read_posthog_key() {
60
60
  # lookups. This LADDER is identical in every skill that resolves a credential
61
61
  # and `credential-resolver-ladder` fails if any copy diverges. Only what
62
62
  # happens AFTER the ladder may differ between them.
63
- local candidates=(
64
- .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
65
- .agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
66
- .opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
67
- .codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
68
- )
63
+ # Execute only machine-managed plugin/package resolvers. Checkout-local
64
+ # candidates are repository-controlled code, not trusted merely by path.
65
+ local candidates=()
69
66
  if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
70
67
  candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
71
68
  fi
@@ -84,7 +81,7 @@ read_posthog_key() {
84
81
  local via_lisa
85
82
  via_lisa=$(node "$resolver" get POSTHOG_PERSONAL_API_KEY 2>/dev/null) \
86
83
  && [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
87
- break
84
+ # Empty/error means this substrate had no answer; try the next trusted one.
88
85
  fi
89
86
  done
90
87
  # Name every path. A bare `return 1` sends the next reader hunting for a
@@ -383,6 +383,11 @@ invisible on that surface — the exact state this recovery path exists to catch
383
383
  only via search (no `Closes #` / native dev link) would otherwise never be discovered, and the leaf
384
384
  would never recover. Read each PR with the vendor's native state, e.g. GitHub
385
385
  `gh pr view <n> --json state,mergedAt,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup,comments,reviews`.
386
+ For `reviews`, derive the effective set from the **latest non-dismissed review
387
+ per reviewer**, ordered by `submitted_at` then id. Never count the raw history:
388
+ an older approval does not satisfy a later `CHANGES_REQUESTED` review by the
389
+ same reviewer. Use the same paginated REST reduction documented by
390
+ `lisa-drive-pr-to-merge` when the `gh pr view` projection is incomplete.
386
391
 
387
392
  **2. PR already merged → recover, don't re-dispatch.** If `state == MERGED`, the build is effectively
388
393
  complete and the only thing missing is the env transition the build-intake never applied (its merge
@@ -695,10 +700,18 @@ left in a status it should not carry, including a stale build-ready `ready`).
695
700
 
696
701
  ```bash
697
702
  ROLLUP_DIR="$(mktemp -d)"
703
+ # Serialize the EXACT graph resolved in step 1 before any classifier reads it.
704
+ # RESOLVED_CHILD_GRAPH_JSON is the in-memory object from that read; do not
705
+ # re-query the tracker here or hand-author a smaller substitute.
706
+ printf '%s\n' "$RESOLVED_CHILD_GRAPH_JSON" \
707
+ | jq -e '{container, children, readError, renderedState, childTally}' \
708
+ > "$ROLLUP_DIR/graph.json"
709
+ test -s "$ROLLUP_DIR/graph.json"
710
+
698
711
  # children[]: { ref, state, labels[], blockedBy[{ref, open}], children[] } — the graph
699
712
  # step 1 already resolved, nested as deep as it was read. The script probes nothing.
700
713
  jq -n --slurpfile g "$ROLLUP_DIR/graph.json" \
701
- '{container: $g[0].container, children: $g[0].children, readError: $g[0].readError}' \
714
+ '{container: $g[0].container, children: $g[0].children, readError: $g[0].readError, renderedState: $g[0].renderedState, childTally: $g[0].childTally}' \
702
715
  > "$ROLLUP_DIR/input.json"
703
716
 
704
717
  node "${CLAUDE_PLUGIN_ROOT}/scripts/rollup-blocker-classification.mjs" \
@@ -707,7 +720,8 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/rollup-blocker-classification.mjs" \
707
720
 
708
721
  It **exits non-zero and classifies nothing** when the tracker could not be read, the container
709
722
  has no children, or no child was readable. That is a repair failure to report, never an
710
- all-clear: do not fall through to "no blocked children" on a non-zero exit. On success its
723
+ all-clear and a strict **no-write** result: do not transition lifecycle state and do not
724
+ post/update the rollup note. Do not fall through to "no blocked children" on a non-zero exit. On success its
711
725
  report names, per class, the blocking leaf, the path to it, and **who must act** — those lines
712
726
  are what goes in the rollup note, verbatim. Never set the `spec_defect` marker yourself and
713
727
  never infer a class from prose; a hold with nothing recorded classifies `unknown`, and the
@@ -717,7 +731,9 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/rollup-blocker-classification.mjs" \
717
731
  lifecycle write (JIRA transition, GitHub/Linear label swap keeping exactly one `status:*`),
718
732
  removing any conflicting stale build lifecycle role — **including a stale `ready`** the parent
719
733
  should never carry. Post an idempotent `[lisa-repair-intake]` rollup note naming the derived
720
- state and the child tally (honor the backoff window + fingerprint). **When the derived state is
734
+ state and the child tally (honor the backoff window + fingerprint). Include that exact rendered
735
+ state and tally in the classifier input: its fingerprint deduplicates the complete note, not only
736
+ the blocker classes. **When the derived state is
721
737
  `blocked`, the note carries the classifier's per-class report** — the blocking leaf, its path,
722
738
  and its actor — so an operator never descends the tree by hand to find out which item and which
723
739
  kind. Use the classifier's `change.summary` as the dedupe test: an unchanged verdict has nothing
@@ -161,12 +161,16 @@ function stripFamilyBlocks(text, family) {
161
161
  const opened = findFamily(out, start);
162
162
  if (opened.index === -1) return out;
163
163
  const closed = findFamily(out, end, opened.index + opened.length);
164
- // A truncated block (opened, never closed) would otherwise swallow the rest
165
- // of the file on every subsequent write.
166
- const after =
167
- closed.index === -1
168
- ? ""
169
- : out.slice(closed.index + closed.length).replace(/^\n/, "");
164
+ // A truncated block (opened, never closed) is not safe to interpret. Any
165
+ // guessed boundary could delete operator-authored content after the marker,
166
+ // so refuse before the caller writes and preserve the original byte-for-byte.
167
+ if (closed.index === -1) {
168
+ throw new Error(
169
+ `Refusing to rewrite a managed ${family} block with no closing marker. ` +
170
+ `Repair the marker pair first; no file was changed.`
171
+ );
172
+ }
173
+ const after = out.slice(closed.index + closed.length).replace(/^\n/, "");
170
174
  out = `${out.slice(0, opened.index)}${after}`;
171
175
  }
172
176
  }
@@ -56,6 +56,11 @@ const ROLE_ARN =
56
56
  /^arn:(?:aws|aws-us-gov|aws-cn):iam::\d{12}:role\/[\w+=,.@/-]+$/;
57
57
  const PUBLICATION_COORDINATION_SCOPE = "AWS_BOOTSTRAP_";
58
58
  const PUBLICATION_LOCK_TTL_MS = 30 * 60 * 1000;
59
+ // Every provider and AWS child has its own 30-second deadline. Stop starting
60
+ // target mutations five minutes before another publisher may reap this lock,
61
+ // leaving ample room for the in-flight child to finish. This is a monotonic
62
+ // duration budget, not a wall clock used to order or expire provider records.
63
+ const PUBLICATION_CRITICAL_SECTION_BUDGET_MS = 25 * 60 * 1000;
59
64
 
60
65
  /**
61
66
  * Validate and normalize one complete bundle without changing its serialized
@@ -257,6 +262,22 @@ function writeProviderValue(cfg, id, value, write) {
257
262
  }
258
263
  }
259
264
 
265
+ function publicationBudgetGuard(startedAt, monotonicNow) {
266
+ return () => {
267
+ const elapsed = monotonicNow() - startedAt;
268
+ if (!Number.isFinite(elapsed) || elapsed < 0) {
269
+ throw new Error(
270
+ "publication lock monotonic timer became invalid; refusing further provider mutation"
271
+ );
272
+ }
273
+ if (elapsed >= PUBLICATION_CRITICAL_SECTION_BUDGET_MS) {
274
+ throw new Error(
275
+ "publication lock execution budget exceeded; refusing further provider mutation"
276
+ );
277
+ }
278
+ };
279
+ }
280
+
260
281
  /**
261
282
  * Enter the provider election for one AWS bootstrap record.
262
283
  *
@@ -427,23 +448,29 @@ export function releasePublicationLock(cfg, lock, operations = {}) {
427
448
  * snapshot can never overwrite a publication that finished while this process
428
449
  * was entering the election.
429
450
  */
430
- function withPublicationLock(cfg, operations, task) {
451
+ function withPublicationLock(cfg, operations, task, completedMessage) {
431
452
  const fetch = operations.fetch ?? fetchAll;
432
453
  const acquire = operations.acquireLock ?? acquirePublicationLock;
433
454
  const release = operations.releaseLock ?? releasePublicationLock;
455
+ const monotonicNow = operations.monotonicNow ?? (() => performance.now());
456
+ const startedAt = monotonicNow();
457
+ const assertWithinBudget = publicationBudgetGuard(startedAt, monotonicNow);
434
458
  const target = currentEntry(cfg, fetch);
435
459
  const lock = acquire(cfg, target, operations);
436
460
  let result;
437
461
  let taskError;
438
462
 
439
463
  try {
464
+ assertWithinBudget();
440
465
  const current = currentEntry(cfg, fetch);
441
466
  if (current.id !== target.id || current.projectId !== target.projectId) {
442
467
  throw new Error(
443
468
  `${BOOTSTRAP_KEY} moved after publication lock acquisition; retry from the new provider record`
444
469
  );
445
470
  }
446
- result = task(current);
471
+ assertWithinBudget();
472
+ result = task(current, assertWithinBudget);
473
+ assertWithinBudget();
447
474
  } catch (error) {
448
475
  taskError = error;
449
476
  }
@@ -455,15 +482,18 @@ function withPublicationLock(cfg, operations, task) {
455
482
  if (taskError) {
456
483
  throw new Error(`${firstLine(taskError)}; ${releaseReason}`);
457
484
  }
458
- throw new Error(releaseReason);
485
+ throw new Error(
486
+ `${completedMessage}; ${releaseReason}; remove the coordination record before the next publication`
487
+ );
459
488
  }
460
489
 
461
490
  if (taskError) throw taskError;
462
491
  return result;
463
492
  }
464
493
 
465
- function preflightUnderLock(cfg, entry, write) {
494
+ function preflightUnderLock(cfg, entry, write, assertWithinBudget) {
466
495
  try {
496
+ assertWithinBudget();
467
497
  writeProviderValue(cfg, entry.id, entry.value, write);
468
498
  } catch (error) {
469
499
  throw new Error(
@@ -482,8 +512,12 @@ function preflightUnderLock(cfg, entry, write) {
482
512
  */
483
513
  export function preflightAwsBootstrap(cfg, operations = {}) {
484
514
  const write = operations.write ?? writeSecret;
485
- return withPublicationLock(cfg, operations, entry =>
486
- preflightUnderLock(cfg, entry, write)
515
+ return withPublicationLock(
516
+ cfg,
517
+ operations,
518
+ (entry, assertWithinBudget) =>
519
+ preflightUnderLock(cfg, entry, write, assertWithinBudget),
520
+ "preflight completed"
487
521
  );
488
522
  }
489
523
 
@@ -496,12 +530,20 @@ export function preflightAwsBootstrap(cfg, operations = {}) {
496
530
  * @param {Function} write Provider writer.
497
531
  * @returns {string} Human-readable rollback outcome with no secret material.
498
532
  */
499
- function rollback(cfg, previous, candidateRaw, fetch, write) {
533
+ function rollback(
534
+ cfg,
535
+ previous,
536
+ candidateRaw,
537
+ fetch,
538
+ write,
539
+ assertWithinBudget
540
+ ) {
500
541
  try {
501
542
  const current = currentEntry(cfg, fetch);
502
543
  if (current.value !== candidateRaw) {
503
544
  return "rollback skipped because the provider changed after this publication";
504
545
  }
546
+ assertWithinBudget();
505
547
  writeProviderValue(cfg, previous.id, previous.value, write);
506
548
  const restored = currentEntry(cfg, fetch);
507
549
  if (restored.value !== previous.value) {
@@ -528,47 +570,56 @@ export function publishAwsBootstrap(raw, cfg, operations = {}) {
528
570
  const write = operations.write ?? writeSecret;
529
571
  const verify = operations.verify ?? verifyAwsBootstrap;
530
572
  const candidate = validateAwsBootstrap(raw);
531
- return withPublicationLock(cfg, operations, previous => {
532
- preflightUnderLock(cfg, previous, write);
533
-
534
- // Verify before the write. A candidate that cannot assume one of its roles
535
- // never becomes the value every new remote session receives.
536
- verify(candidate);
537
-
538
- if (candidate.raw === previous.value) {
539
- return {
540
- changed: false,
541
- profiles: candidate.profiles.map(profile => profile.name),
542
- };
543
- }
573
+ return withPublicationLock(
574
+ cfg,
575
+ operations,
576
+ (previous, assertWithinBudget) => {
577
+ preflightUnderLock(cfg, previous, write, assertWithinBudget);
578
+
579
+ // Verify before the write. A candidate that cannot assume one of its roles
580
+ // never becomes the value every new remote session receives.
581
+ verify(candidate);
582
+ assertWithinBudget();
583
+
584
+ if (candidate.raw === previous.value) {
585
+ return {
586
+ changed: false,
587
+ profiles: candidate.profiles.map(profile => profile.name),
588
+ };
589
+ }
544
590
 
545
- try {
546
- writeProviderValue(cfg, previous.id, candidate.raw, write);
547
- const stored = currentEntry(cfg, fetch);
548
- if (stored.value !== candidate.raw) {
591
+ try {
592
+ assertWithinBudget();
593
+ writeProviderValue(cfg, previous.id, candidate.raw, write);
594
+ const stored = currentEntry(cfg, fetch);
595
+ if (stored.value !== candidate.raw) {
596
+ throw new Error(
597
+ "provider read-back did not match the candidate exactly"
598
+ );
599
+ }
600
+ const storedCandidate = validateAwsBootstrap(stored.value);
601
+ verify(storedCandidate);
602
+ assertWithinBudget();
603
+ return {
604
+ changed: true,
605
+ profiles: storedCandidate.profiles.map(profile => profile.name),
606
+ };
607
+ } catch (error) {
608
+ const rollbackOutcome = rollback(
609
+ cfg,
610
+ previous,
611
+ candidate.raw,
612
+ fetch,
613
+ write,
614
+ assertWithinBudget
615
+ );
549
616
  throw new Error(
550
- "provider read-back did not match the candidate exactly"
617
+ `${BOOTSTRAP_KEY} publication failed: ${firstLine(error)}; ${rollbackOutcome}`
551
618
  );
552
619
  }
553
- const storedCandidate = validateAwsBootstrap(stored.value);
554
- verify(storedCandidate);
555
- return {
556
- changed: true,
557
- profiles: storedCandidate.profiles.map(profile => profile.name),
558
- };
559
- } catch (error) {
560
- const rollbackOutcome = rollback(
561
- cfg,
562
- previous,
563
- candidate.raw,
564
- fetch,
565
- write
566
- );
567
- throw new Error(
568
- `${BOOTSTRAP_KEY} publication failed: ${firstLine(error)}; ${rollbackOutcome}`
569
- );
570
- }
571
- });
620
+ },
621
+ "publication completed"
622
+ );
572
623
  }
573
624
 
574
625
  /** Read a candidate from stdin without ever placing it in argv. */
@@ -48,9 +48,9 @@ interactively and headlessly — which is why it leads. The REST tier uses:
48
48
  read_sentry_token() {
49
49
  [ -n "${SENTRY_AUTH_TOKEN:-}" ] && { echo "$SENTRY_AUTH_TOKEN"; return; }
50
50
  #
51
- # Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
52
- # lead because a project that vendors the resolver has declared which copy it
53
- # wants used, and that decision must survive this change untouched. The plugin
51
+ # Ordered across trusted machine-managed substrates, ending at the installed
52
+ # package. Checkout-local paths are deliberately absent: a familiar generated
53
+ # destination is still repository-controlled executable code. The plugin
54
54
  # rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
55
55
  # pointing at it is reachable from anywhere the plugin itself is installed.
56
56
  # Without one, a consumer repository that vendors none of the leading paths
@@ -59,12 +59,9 @@ read_sentry_token() {
59
59
  # lookups. This LADDER is identical in every skill that resolves a credential
60
60
  # and `credential-resolver-ladder` fails if any copy diverges. Only what
61
61
  # happens AFTER the ladder may differ between them.
62
- local candidates=(
63
- .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
64
- .agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
65
- .opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
66
- .codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
67
- )
62
+ # Execute only machine-managed plugin/package resolvers. Checkout-local
63
+ # candidates are repository-controlled code, not trusted merely by path.
64
+ local candidates=()
68
65
  if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
69
66
  candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
70
67
  fi
@@ -83,7 +80,7 @@ read_sentry_token() {
83
80
  local via_lisa
84
81
  via_lisa=$(node "$resolver" get SENTRY_AUTH_TOKEN 2>/dev/null) \
85
82
  && [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
86
- break
83
+ # Empty/error means this substrate had no answer; try the next trusted one.
87
84
  fi
88
85
  done
89
86
  # Name every path. A bare `return 1` sends the next reader hunting for a
@@ -86,9 +86,9 @@ read_token() {
86
86
  # than "this function" is deliberate — the old wording asked for something
87
87
  # unachievable, and an unachievable rule is one that gets quietly dropped.
88
88
  #
89
- # Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
90
- # lead because a project that vendors the resolver has declared which copy it
91
- # wants used, and that decision must survive this change untouched. The plugin
89
+ # Ordered across trusted machine-managed substrates, ending at the installed
90
+ # package. Checkout-local paths are deliberately absent: a familiar generated
91
+ # destination is still repository-controlled executable code. The plugin
92
92
  # rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
93
93
  # pointing at it is reachable from anywhere the plugin itself is installed.
94
94
  # Without one, a consumer repository that vendors none of the leading paths
@@ -97,12 +97,9 @@ read_token() {
97
97
  # lookups. This LADDER is identical in every skill that resolves a credential
98
98
  # and `credential-resolver-ladder` fails if any copy diverges. Only what
99
99
  # happens AFTER the ladder may differ between them.
100
- local candidates=(
101
- .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
102
- .agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
103
- .opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
104
- .codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
105
- )
100
+ # Execute only machine-managed plugin/package resolvers. Checkout-local
101
+ # candidates are repository-controlled code, not trusted merely by path.
102
+ local candidates=()
106
103
  if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
107
104
  candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
108
105
  fi
@@ -121,7 +118,7 @@ read_token() {
121
118
  local via_lisa
122
119
  via_lisa=$(node "$resolver" get ATLASSIAN_API_TOKEN 2>/dev/null) \
123
120
  && [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
124
- break
121
+ # Empty/error means this substrate had no answer; try the next trusted one.
125
122
  fi
126
123
  done
127
124
  # Legacy fallback: the OS keychain written by the guided flow below, for
@@ -104,17 +104,14 @@ read_linear_key() { # $1=workspace slug
104
104
  # `linear-access` grew to seven, leaving `/lisa:setup:linear` unable to reach
105
105
  # a key that `lisa-linear-access` could read from the same repository.
106
106
  #
107
- # Ordered repo-first, ending at the plugin's own copy. Repo-relative rungs
108
- # lead because a project that vendors the resolver has declared which copy it
109
- # wants used. The plugin rungs are the floor: `resolve-secret.mjs` ships
110
- # beside this skill, so a rung pointing at it is reachable from anywhere the
111
- # plugin itself is installed.
112
- local candidates=(
113
- .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs
114
- .agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs
115
- .opencode/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
116
- .codex/skills/lisa/lisa-secrets-access/scripts/resolve-secret.mjs
117
- )
107
+ # Ordered across trusted machine-managed substrates, ending at the installed
108
+ # package. Checkout-local paths are deliberately absent: a familiar generated
109
+ # destination is still repository-controlled executable code. The plugin
110
+ # rungs are the floor: `resolve-secret.mjs` ships beside this skill, so a rung
111
+ # pointing at it is reachable from anywhere the plugin itself is installed.
112
+ # Execute only machine-managed plugin/package resolvers. Checkout-local
113
+ # candidates are repository-controlled code, not trusted merely by path.
114
+ local candidates=()
118
115
  if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
119
116
  candidates+=("$CLAUDE_PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
120
117
  fi
@@ -133,7 +130,7 @@ read_linear_key() { # $1=workspace slug
133
130
  local via_lisa
134
131
  via_lisa=$(node "$resolver" get LINEAR_API_KEY 2>/dev/null) \
135
132
  && [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
136
- break
133
+ # Empty/error means this substrate had no answer; try the next trusted one.
137
134
  fi
138
135
  done
139
136
  # Legacy fallback: the OS keychain written by the guided flow below, for