@codyswann/lisa 4.74.9 → 4.75.1

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 (805) hide show
  1. package/.claude-plugin/marketplace.json +60 -0
  2. package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +73 -40
  3. package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +54 -0
  4. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +3 -2
  5. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  6. package/dist/core/lisa-owned-hash-ledger.js +6 -0
  7. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  8. package/dist/core/lisa-skill-sources.d.ts +3 -3
  9. package/dist/core/lisa-skill-sources.d.ts.map +1 -1
  10. package/dist/core/lisa-skill-sources.js +3 -2
  11. package/dist/core/lisa-skill-sources.js.map +1 -1
  12. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  13. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  14. package/dist/core/upstream-evidence-manifest.js +735 -8
  15. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  16. package/package.json +4 -4
  17. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa-agy/plugin.json +1 -1
  20. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  23. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +11 -0
  26. package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +8 -0
  27. package/plugins/lisa-cdk-devin/skills/setup-aws-accounts/SKILL.md +292 -0
  28. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-devin/.claude-plugin/plugin.json +8 -0
  31. package/plugins/lisa-devin/.devin-plugin/plugin.json +8 -0
  32. package/plugins/lisa-devin/.mcp.json +8 -0
  33. package/plugins/lisa-devin/agents/architecture-specialist.md +32 -0
  34. package/plugins/lisa-devin/agents/bug-fixer.md +43 -0
  35. package/plugins/lisa-devin/agents/builder.md +44 -0
  36. package/plugins/lisa-devin/agents/confluence-prd-intake.md +65 -0
  37. package/plugins/lisa-devin/agents/debug-specialist.md +34 -0
  38. package/plugins/lisa-devin/agents/eval-specialist.md +33 -0
  39. package/plugins/lisa-devin/agents/git-history-analyzer.md +183 -0
  40. package/plugins/lisa-devin/agents/github-agent.md +155 -0
  41. package/plugins/lisa-devin/agents/github-build-intake.md +64 -0
  42. package/plugins/lisa-devin/agents/github-prd-intake.md +66 -0
  43. package/plugins/lisa-devin/agents/jira-agent.md +135 -0
  44. package/plugins/lisa-devin/agents/jira-build-intake.md +64 -0
  45. package/plugins/lisa-devin/agents/learner.md +85 -0
  46. package/plugins/lisa-devin/agents/learning-judge.md +119 -0
  47. package/plugins/lisa-devin/agents/learnings-synthesizer.md +155 -0
  48. package/plugins/lisa-devin/agents/linear-agent.md +138 -0
  49. package/plugins/lisa-devin/agents/linear-build-intake.md +64 -0
  50. package/plugins/lisa-devin/agents/linear-prd-intake.md +66 -0
  51. package/plugins/lisa-devin/agents/notion-prd-intake.md +63 -0
  52. package/plugins/lisa-devin/agents/performance-specialist.md +26 -0
  53. package/plugins/lisa-devin/agents/pr-mining-specialist.md +85 -0
  54. package/plugins/lisa-devin/agents/product-specialist.md +30 -0
  55. package/plugins/lisa-devin/agents/quality-specialist.md +30 -0
  56. package/plugins/lisa-devin/agents/security-specialist.md +43 -0
  57. package/plugins/lisa-devin/agents/skill-evaluator.md +205 -0
  58. package/plugins/lisa-devin/agents/spec-conformance-specialist.md +56 -0
  59. package/plugins/lisa-devin/agents/test-specialist.md +32 -0
  60. package/plugins/lisa-devin/agents/tracker-mining-specialist.md +85 -0
  61. package/plugins/lisa-devin/agents/verification-specialist.md +145 -0
  62. package/plugins/lisa-devin/hooks/auto-update.mjs +824 -0
  63. package/plugins/lisa-devin/hooks/auto-update.sh +31 -0
  64. package/plugins/lisa-devin/hooks/block-blind-automerge.sh +1242 -0
  65. package/plugins/lisa-devin/hooks/block-direct-issue-create.sh +2603 -0
  66. package/plugins/lisa-devin/hooks/block-host-name-leak.mjs +412 -0
  67. package/plugins/lisa-devin/hooks/block-host-name-leak.sh +61 -0
  68. package/plugins/lisa-devin/hooks/block-instruction-file-edits.sh +207 -0
  69. package/plugins/lisa-devin/hooks/block-managed-file-edits.sh +1175 -0
  70. package/plugins/lisa-devin/hooks/block-no-verify.sh +2288 -0
  71. package/plugins/lisa-devin/hooks/block-shell-json-parsing.sh +263 -0
  72. package/plugins/lisa-devin/hooks/cleanup-stale-worktrees.sh +122 -0
  73. package/plugins/lisa-devin/hooks/devin-tool-name-shim.sh +30 -0
  74. package/plugins/lisa-devin/hooks/discharge-work-item-gates.sh +90 -0
  75. package/plugins/lisa-devin/hooks/enforce-verification-gate.sh +506 -0
  76. package/plugins/lisa-devin/hooks/enforcement-vintage-npm.mjs +320 -0
  77. package/plugins/lisa-devin/hooks/enforcement-vintage.mjs +472 -0
  78. package/plugins/lisa-devin/hooks/enforcement-vintage.sh +48 -0
  79. package/plugins/lisa-devin/hooks/failure-signature-index.mjs +717 -0
  80. package/plugins/lisa-devin/hooks/failure-signature-index.sh +26 -0
  81. package/plugins/lisa-devin/hooks/guard-dedupe.bash +374 -0
  82. package/plugins/lisa-devin/hooks/inject-resolved-config.mjs +903 -0
  83. package/plugins/lisa-devin/hooks/inject-resolved-config.sh +107 -0
  84. package/plugins/lisa-devin/hooks/inject-rules.sh +49 -0
  85. package/plugins/lisa-devin/hooks/install-pkgs.sh +84 -0
  86. package/plugins/lisa-devin/hooks/operational-hazards.mjs +736 -0
  87. package/plugins/lisa-devin/hooks/operational-hazards.sh +28 -0
  88. package/plugins/lisa-devin/hooks/parity-safety-net-heredoc.py +1004 -0
  89. package/plugins/lisa-devin/hooks/parity-safety-net.sh +3275 -0
  90. package/plugins/lisa-devin/hooks/secrets-preflight.sh +72 -0
  91. package/plugins/lisa-devin/hooks/setup-jira-cli.sh +205 -0
  92. package/plugins/lisa-devin/hooks/shell-write-nudge.sh +86 -0
  93. package/plugins/lisa-devin/hooks/sonar-secrets.sh +205 -0
  94. package/plugins/lisa-devin/hooks/threshold-ratchet-compare.mjs +518 -0
  95. package/plugins/lisa-devin/hooks/threshold-ratchet-families.mjs +543 -0
  96. package/plugins/lisa-devin/hooks/threshold-ratchet.mjs +568 -0
  97. package/plugins/lisa-devin/hooks/threshold-ratchet.sh +22 -0
  98. package/plugins/lisa-devin/hooks/withdrawn-rulings.mjs +600 -0
  99. package/plugins/lisa-devin/hooks/withdrawn-rulings.sh +24 -0
  100. package/plugins/lisa-devin/hooks/worktree-binding-guard.mjs +1383 -0
  101. package/plugins/lisa-devin/hooks/worktree-binding-guard.sh +79 -0
  102. package/plugins/lisa-devin/hooks.json +247 -0
  103. package/plugins/lisa-devin/rules/eager/00-rule-index.md +82 -0
  104. package/plugins/lisa-devin/rules/eager/base-rules.md +85 -0
  105. package/plugins/lisa-devin/rules/eager/coding-philosophy.md +27 -0
  106. package/plugins/lisa-devin/rules/eager/config-resolution.md +37 -0
  107. package/plugins/lisa-devin/rules/eager/do-it-now.md +11 -0
  108. package/plugins/lisa-devin/rules/eager/empirical-inquiry.md +24 -0
  109. package/plugins/lisa-devin/rules/eager/falsifiable-checks.md +48 -0
  110. package/plugins/lisa-devin/rules/eager/history-audit.md +27 -0
  111. package/plugins/lisa-devin/rules/eager/integration-access-layer.md +19 -0
  112. package/plugins/lisa-devin/rules/eager/intent-routing.md +18 -0
  113. package/plugins/lisa-devin/rules/eager/learnings-ladder.md +18 -0
  114. package/plugins/lisa-devin/rules/eager/local-ci-first.md +15 -0
  115. package/plugins/lisa-devin/rules/eager/measurement-integrity.md +18 -0
  116. package/plugins/lisa-devin/rules/eager/never-name-a-downstream-project.md +9 -0
  117. package/plugins/lisa-devin/rules/eager/not-blocked-just-waiting.md +13 -0
  118. package/plugins/lisa-devin/rules/eager/operational-hazards.md +50 -0
  119. package/plugins/lisa-devin/rules/eager/report-actionability.md +26 -0
  120. package/plugins/lisa-devin/rules/eager/security-audit-handling.md +40 -0
  121. package/plugins/lisa-devin/rules/eager/session-status-updates.md +20 -0
  122. package/plugins/lisa-devin/rules/eager/settled-decisions.md +75 -0
  123. package/plugins/lisa-devin/rules/eager/stale-state-claims.md +26 -0
  124. package/plugins/lisa-devin/rules/eager/tool-access-gate.md +23 -0
  125. package/plugins/lisa-devin/rules/eager/tracked-work.md +9 -0
  126. package/plugins/lisa-devin/rules/eager/upstream-to-lisa.md +14 -0
  127. package/plugins/lisa-devin/rules/eager/verification.md +33 -0
  128. package/plugins/lisa-devin/rules/eager/wiki-knowledge-source.md +13 -0
  129. package/plugins/lisa-devin/rules/eager/withdrawn-rulings.md +102 -0
  130. package/plugins/lisa-devin/rules/reference/automation-runbook-contract.md +355 -0
  131. package/plugins/lisa-devin/rules/reference/base-rules.md +136 -0
  132. package/plugins/lisa-devin/rules/reference/bdd-e2e-coverage.md +301 -0
  133. package/plugins/lisa-devin/rules/reference/blocker-containment.md +165 -0
  134. package/plugins/lisa-devin/rules/reference/claim-archaeology.md +184 -0
  135. package/plugins/lisa-devin/rules/reference/claim-evidence-mapping.md +362 -0
  136. package/plugins/lisa-devin/rules/reference/claim-time-guards.md +135 -0
  137. package/plugins/lisa-devin/rules/reference/coding-philosophy.md +428 -0
  138. package/plugins/lisa-devin/rules/reference/config-resolution.md +1225 -0
  139. package/plugins/lisa-devin/rules/reference/control-reachability.md +166 -0
  140. package/plugins/lisa-devin/rules/reference/convergent-review.md +145 -0
  141. package/plugins/lisa-devin/rules/reference/credential-substrate-precedence.md +249 -0
  142. package/plugins/lisa-devin/rules/reference/dependency-decision-records.md +177 -0
  143. package/plugins/lisa-devin/rules/reference/dependency-internalization-kit.md +244 -0
  144. package/plugins/lisa-devin/rules/reference/dependency-trust-classes.md +291 -0
  145. package/plugins/lisa-devin/rules/reference/deployed-state-readback.md +142 -0
  146. package/plugins/lisa-devin/rules/reference/derived-branch-plan.md +214 -0
  147. package/plugins/lisa-devin/rules/reference/design-source-of-truth.md +260 -0
  148. package/plugins/lisa-devin/rules/reference/design-value-binding.md +311 -0
  149. package/plugins/lisa-devin/rules/reference/do-it-now.md +19 -0
  150. package/plugins/lisa-devin/rules/reference/documentation-source-paths.md +31 -0
  151. package/plugins/lisa-devin/rules/reference/empirical-inquiry.md +28 -0
  152. package/plugins/lisa-devin/rules/reference/factory-model.md +111 -0
  153. package/plugins/lisa-devin/rules/reference/falsifiable-checks.md +275 -0
  154. package/plugins/lisa-devin/rules/reference/history-audit.md +42 -0
  155. package/plugins/lisa-devin/rules/reference/integration-access-layer.md +89 -0
  156. package/plugins/lisa-devin/rules/reference/intent-routing.md +428 -0
  157. package/plugins/lisa-devin/rules/reference/leaf-only-lifecycle.md +227 -0
  158. package/plugins/lisa-devin/rules/reference/learnings-ladder.md +41 -0
  159. package/plugins/lisa-devin/rules/reference/local-ci-first.md +34 -0
  160. package/plugins/lisa-devin/rules/reference/measurement-integrity.md +119 -0
  161. package/plugins/lisa-devin/rules/reference/never-name-a-downstream-project.md +37 -0
  162. package/plugins/lisa-devin/rules/reference/not-blocked-just-waiting.md +41 -0
  163. package/plugins/lisa-devin/rules/reference/observability-audit.md +146 -0
  164. package/plugins/lisa-devin/rules/reference/operational-hazards.md +115 -0
  165. package/plugins/lisa-devin/rules/reference/prd-definition-of-ready.md +118 -0
  166. package/plugins/lisa-devin/rules/reference/prd-lifecycle-rollup.md +192 -0
  167. package/plugins/lisa-devin/rules/reference/pre-flight-autofill.md +250 -0
  168. package/plugins/lisa-devin/rules/reference/project-learnings.md +159 -0
  169. package/plugins/lisa-devin/rules/reference/promotion-contract.md +148 -0
  170. package/plugins/lisa-devin/rules/reference/readiness-rubric.md +242 -0
  171. package/plugins/lisa-devin/rules/reference/ready-role-filing.md +212 -0
  172. package/plugins/lisa-devin/rules/reference/rejection-detection.md +308 -0
  173. package/plugins/lisa-devin/rules/reference/repo-scope-split.md +119 -0
  174. package/plugins/lisa-devin/rules/reference/report-actionability.md +61 -0
  175. package/plugins/lisa-devin/rules/reference/reset-seed-coverage.md +286 -0
  176. package/plugins/lisa-devin/rules/reference/security-audit-handling.md +55 -0
  177. package/plugins/lisa-devin/rules/reference/session-status-updates.md +111 -0
  178. package/plugins/lisa-devin/rules/reference/settled-decisions.md +39 -0
  179. package/plugins/lisa-devin/rules/reference/stale-state-claims.md +88 -0
  180. package/plugins/lisa-devin/rules/reference/tool-access-gate.md +114 -0
  181. package/plugins/lisa-devin/rules/reference/tracked-work.md +49 -0
  182. package/plugins/lisa-devin/rules/reference/upstream-to-lisa.md +26 -0
  183. package/plugins/lisa-devin/rules/reference/usage-accounting.md +261 -0
  184. package/plugins/lisa-devin/rules/reference/verification.md +278 -0
  185. package/plugins/lisa-devin/rules/reference/wiki-knowledge-source.md +37 -0
  186. package/plugins/lisa-devin/rules/reference/withdrawn-rulings.md +148 -0
  187. package/plugins/lisa-devin/rules/reference/work-item-definition-of-ready.md +177 -0
  188. package/plugins/lisa-devin/rules/reference/work-item-trailer-definition.md +129 -0
  189. package/plugins/lisa-devin/scripts/automation-run-record.mjs +678 -0
  190. package/plugins/lisa-devin/scripts/automation-status-claude-adapter.mjs +804 -0
  191. package/plugins/lisa-devin/scripts/automation-status-codex-adapter.mjs +1081 -0
  192. package/plugins/lisa-devin/scripts/automation-status-contract-drift.mjs +392 -0
  193. package/plugins/lisa-devin/scripts/automation-status-expected-fleet.mjs +508 -0
  194. package/plugins/lisa-devin/scripts/automation-status-report.mjs +200 -0
  195. package/plugins/lisa-devin/scripts/automation-status-run-history.mjs +216 -0
  196. package/plugins/lisa-devin/scripts/automation-status-unrecorded-runs.mjs +127 -0
  197. package/plugins/lisa-devin/scripts/blocker-edge-resolution.mjs +150 -0
  198. package/plugins/lisa-devin/scripts/cross-pollinate.mjs +763 -0
  199. package/plugins/lisa-devin/scripts/design-bindings-probe.mjs +713 -0
  200. package/plugins/lisa-devin/scripts/design-intake-gate.mjs +470 -0
  201. package/plugins/lisa-devin/scripts/design-source-gate.mjs +610 -0
  202. package/plugins/lisa-devin/scripts/design-variable-ids.mjs +567 -0
  203. package/plugins/lisa-devin/scripts/doctor-report.mjs +635 -0
  204. package/plugins/lisa-devin/scripts/install-remote-agent-aws.mjs +443 -0
  205. package/plugins/lisa-devin/scripts/intake-blocker-reprobe.mjs +1065 -0
  206. package/plugins/lisa-devin/scripts/intake-prework-denominator.mjs +249 -0
  207. package/plugins/lisa-devin/scripts/lifecycle-label-trust.mjs +593 -0
  208. package/plugins/lisa-devin/scripts/linear-state-write-target.mjs +362 -0
  209. package/plugins/lisa-devin/scripts/plugin-sync-explain.mjs +654 -0
  210. package/plugins/lisa-devin/scripts/pr-arming-sweep.mjs +561 -0
  211. package/plugins/lisa-devin/scripts/project-ideation-idempotency-harness.mjs +338 -0
  212. package/plugins/lisa-devin/scripts/qa-signal-lifecycle.mjs +523 -0
  213. package/plugins/lisa-devin/scripts/queue-contract-resolution.mjs +583 -0
  214. package/plugins/lisa-devin/scripts/queue-health-classification.mjs +157 -0
  215. package/plugins/lisa-devin/scripts/queue-status-build-readers.mjs +561 -0
  216. package/plugins/lisa-devin/scripts/queue-status-prd-readers.mjs +452 -0
  217. package/plugins/lisa-devin/scripts/remote-agent-aws-setup.sh +677 -0
  218. package/plugins/lisa-devin/scripts/repo-scope-vocabulary-audit.mjs +458 -0
  219. package/plugins/lisa-devin/scripts/resolve-lifecycle-role.mjs +347 -0
  220. package/plugins/lisa-devin/scripts/rollup-blocker-classification.mjs +616 -0
  221. package/plugins/lisa-devin/scripts/scoping-label-audit.mjs +474 -0
  222. package/plugins/lisa-devin/skills/lisa-acceptance-criteria/SKILL.md +73 -0
  223. package/plugins/lisa-devin/skills/lisa-agent-design-best-practices/SKILL.md +242 -0
  224. package/plugins/lisa-devin/skills/lisa-agent-ready/SKILL.md +271 -0
  225. package/plugins/lisa-devin/skills/lisa-analyze-claude-remote/SKILL.md +434 -0
  226. package/plugins/lisa-devin/skills/lisa-atlassian-access/SKILL.md +469 -0
  227. package/plugins/lisa-devin/skills/lisa-atlassian-access/scripts/markdown-to-adf.mjs +236 -0
  228. package/plugins/lisa-devin/skills/lisa-attribute-failure/SKILL.md +142 -0
  229. package/plugins/lisa-devin/skills/lisa-automation-status/SKILL.md +149 -0
  230. package/plugins/lisa-devin/skills/lisa-bug-triage/SKILL.md +23 -0
  231. package/plugins/lisa-devin/skills/lisa-capability-drift/SKILL.md +71 -0
  232. package/plugins/lisa-devin/skills/lisa-codebase-research/SKILL.md +87 -0
  233. package/plugins/lisa-devin/skills/lisa-codify-verification/SKILL.md +213 -0
  234. package/plugins/lisa-devin/skills/lisa-confluence-prd-intake/SKILL.md +417 -0
  235. package/plugins/lisa-devin/skills/lisa-confluence-to-tracker/SKILL.md +469 -0
  236. package/plugins/lisa-devin/skills/lisa-confluence-write-prd/SKILL.md +111 -0
  237. package/plugins/lisa-devin/skills/lisa-cross-pollinate/SKILL.md +175 -0
  238. package/plugins/lisa-devin/skills/lisa-debrief/SKILL.md +96 -0
  239. package/plugins/lisa-devin/skills/lisa-debrief-apply/SKILL.md +106 -0
  240. package/plugins/lisa-devin/skills/lisa-delivery-effectiveness/SKILL.md +62 -0
  241. package/plugins/lisa-devin/skills/lisa-design-intake/SKILL.md +198 -0
  242. package/plugins/lisa-devin/skills/lisa-detect-tooling/SKILL.md +110 -0
  243. package/plugins/lisa-devin/skills/lisa-detect-tooling/scripts/commands.mjs +479 -0
  244. package/plugins/lisa-devin/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +658 -0
  245. package/plugins/lisa-devin/skills/lisa-doctor/SKILL.md +743 -0
  246. package/plugins/lisa-devin/skills/lisa-drive-pr-to-merge/SKILL.md +1587 -0
  247. package/plugins/lisa-devin/skills/lisa-epic-triage/SKILL.md +28 -0
  248. package/plugins/lisa-devin/skills/lisa-evaluation-suite/SKILL.md +63 -0
  249. package/plugins/lisa-devin/skills/lisa-exploratory-qa/SKILL.md +185 -0
  250. package/plugins/lisa-devin/skills/lisa-fix-linter-error/SKILL.md +45 -0
  251. package/plugins/lisa-devin/skills/lisa-generate-claude-remote-build-script/SKILL.md +235 -0
  252. package/plugins/lisa-devin/skills/lisa-git-commit/SKILL.md +53 -0
  253. package/plugins/lisa-devin/skills/lisa-git-prune/SKILL.md +35 -0
  254. package/plugins/lisa-devin/skills/lisa-git-submit-pr/SKILL.md +170 -0
  255. package/plugins/lisa-devin/skills/lisa-github-add-journey/SKILL.md +118 -0
  256. package/plugins/lisa-devin/skills/lisa-github-build-intake/SKILL.md +745 -0
  257. package/plugins/lisa-devin/skills/lisa-github-claim/SKILL.md +27 -0
  258. package/plugins/lisa-devin/skills/lisa-github-create/SKILL.md +105 -0
  259. package/plugins/lisa-devin/skills/lisa-github-evidence/SKILL.md +120 -0
  260. package/plugins/lisa-devin/skills/lisa-github-journey/SKILL.md +134 -0
  261. package/plugins/lisa-devin/skills/lisa-github-prd-intake/SKILL.md +424 -0
  262. package/plugins/lisa-devin/skills/lisa-github-project-v2/SKILL.md +227 -0
  263. package/plugins/lisa-devin/skills/lisa-github-read-issue/SKILL.md +305 -0
  264. package/plugins/lisa-devin/skills/lisa-github-sync/SKILL.md +201 -0
  265. package/plugins/lisa-devin/skills/lisa-github-to-tracker/SKILL.md +456 -0
  266. package/plugins/lisa-devin/skills/lisa-github-validate-issue/SKILL.md +598 -0
  267. package/plugins/lisa-devin/skills/lisa-github-verify/SKILL.md +60 -0
  268. package/plugins/lisa-devin/skills/lisa-github-write-issue/SKILL.md +531 -0
  269. package/plugins/lisa-devin/skills/lisa-github-write-prd/SKILL.md +156 -0
  270. package/plugins/lisa-devin/skills/lisa-health/SKILL.md +142 -0
  271. package/plugins/lisa-devin/skills/lisa-health-drift-cron/SKILL.md +80 -0
  272. package/plugins/lisa-devin/skills/lisa-implement/SKILL.md +372 -0
  273. package/plugins/lisa-devin/skills/lisa-improve-code-complexity/SKILL.md +44 -0
  274. package/plugins/lisa-devin/skills/lisa-improve-harness/SKILL.md +487 -0
  275. package/plugins/lisa-devin/skills/lisa-improve-max-lines/SKILL.md +45 -0
  276. package/plugins/lisa-devin/skills/lisa-improve-max-lines-per-function/SKILL.md +46 -0
  277. package/plugins/lisa-devin/skills/lisa-improve-test-coverage/SKILL.md +47 -0
  278. package/plugins/lisa-devin/skills/lisa-improve-tests/SKILL.md +50 -0
  279. package/plugins/lisa-devin/skills/lisa-intake/SKILL.md +208 -0
  280. package/plugins/lisa-devin/skills/lisa-intake-explain/SKILL.md +279 -0
  281. package/plugins/lisa-devin/skills/lisa-jam-access/SKILL.md +128 -0
  282. package/plugins/lisa-devin/skills/lisa-jira-add-journey/SKILL.md +139 -0
  283. package/plugins/lisa-devin/skills/lisa-jira-build-intake/SKILL.md +556 -0
  284. package/plugins/lisa-devin/skills/lisa-jira-claim/SKILL.md +27 -0
  285. package/plugins/lisa-devin/skills/lisa-jira-create/SKILL.md +157 -0
  286. package/plugins/lisa-devin/skills/lisa-jira-evidence/SKILL.md +106 -0
  287. package/plugins/lisa-devin/skills/lisa-jira-evidence/scripts/post-evidence.sh +262 -0
  288. package/plugins/lisa-devin/skills/lisa-jira-journey/SKILL.md +168 -0
  289. package/plugins/lisa-devin/skills/lisa-jira-journey/scripts/generate-templates.py +239 -0
  290. package/plugins/lisa-devin/skills/lisa-jira-journey/scripts/parse-plan.py +570 -0
  291. package/plugins/lisa-devin/skills/lisa-jira-read-ticket/SKILL.md +199 -0
  292. package/plugins/lisa-devin/skills/lisa-jira-read-ticket/scripts/download-attachment.sh +211 -0
  293. package/plugins/lisa-devin/skills/lisa-jira-sync/SKILL.md +117 -0
  294. package/plugins/lisa-devin/skills/lisa-jira-validate-ticket/SKILL.md +525 -0
  295. package/plugins/lisa-devin/skills/lisa-jira-verify/SKILL.md +61 -0
  296. package/plugins/lisa-devin/skills/lisa-jira-write-ticket/SKILL.md +440 -0
  297. package/plugins/lisa-devin/skills/lisa-jsdoc-best-practices/SKILL.md +491 -0
  298. package/plugins/lisa-devin/skills/lisa-kane-browser/SKILL.md +80 -0
  299. package/plugins/lisa-devin/skills/lisa-learnings-audit/SKILL.md +490 -0
  300. package/plugins/lisa-devin/skills/lisa-linear-access/SKILL.md +483 -0
  301. package/plugins/lisa-devin/skills/lisa-linear-add-journey/SKILL.md +109 -0
  302. package/plugins/lisa-devin/skills/lisa-linear-build-intake/SKILL.md +548 -0
  303. package/plugins/lisa-devin/skills/lisa-linear-claim/SKILL.md +28 -0
  304. package/plugins/lisa-devin/skills/lisa-linear-create/SKILL.md +150 -0
  305. package/plugins/lisa-devin/skills/lisa-linear-evidence/SKILL.md +124 -0
  306. package/plugins/lisa-devin/skills/lisa-linear-journey/SKILL.md +147 -0
  307. package/plugins/lisa-devin/skills/lisa-linear-prd-intake/SKILL.md +411 -0
  308. package/plugins/lisa-devin/skills/lisa-linear-read-issue/SKILL.md +201 -0
  309. package/plugins/lisa-devin/skills/lisa-linear-sync/SKILL.md +164 -0
  310. package/plugins/lisa-devin/skills/lisa-linear-to-tracker/SKILL.md +454 -0
  311. package/plugins/lisa-devin/skills/lisa-linear-validate-issue/SKILL.md +504 -0
  312. package/plugins/lisa-devin/skills/lisa-linear-verify/SKILL.md +65 -0
  313. package/plugins/lisa-devin/skills/lisa-linear-write-issue/SKILL.md +448 -0
  314. package/plugins/lisa-devin/skills/lisa-linear-write-prd/SKILL.md +98 -0
  315. package/plugins/lisa-devin/skills/lisa-monitor/SKILL.md +224 -0
  316. package/plugins/lisa-devin/skills/lisa-nightly-add-test-coverage/SKILL.md +40 -0
  317. package/plugins/lisa-devin/skills/lisa-nightly-improve-tests/SKILL.md +29 -0
  318. package/plugins/lisa-devin/skills/lisa-nightly-lower-code-complexity/SKILL.md +42 -0
  319. package/plugins/lisa-devin/skills/lisa-notion-access/SKILL.md +301 -0
  320. package/plugins/lisa-devin/skills/lisa-notion-prd-intake/SKILL.md +360 -0
  321. package/plugins/lisa-devin/skills/lisa-notion-to-tracker/SKILL.md +467 -0
  322. package/plugins/lisa-devin/skills/lisa-notion-write-prd/SKILL.md +111 -0
  323. package/plugins/lisa-devin/skills/lisa-parity-code-review/SKILL.md +83 -0
  324. package/plugins/lisa-devin/skills/lisa-parity-code-simplifier/SKILL.md +76 -0
  325. package/plugins/lisa-devin/skills/lisa-parity-coderabbit/SKILL.md +87 -0
  326. package/plugins/lisa-devin/skills/lisa-parity-sentry-sdk-setup/SKILL.md +375 -0
  327. package/plugins/lisa-devin/skills/lisa-parity-sentry-seer/SKILL.md +168 -0
  328. package/plugins/lisa-devin/skills/lisa-parity-skill-creator/SKILL.md +155 -0
  329. package/plugins/lisa-devin/skills/lisa-performance-review/SKILL.md +94 -0
  330. package/plugins/lisa-devin/skills/lisa-persist-learning/SKILL.md +247 -0
  331. package/plugins/lisa-devin/skills/lisa-plan/SKILL.md +64 -0
  332. package/plugins/lisa-devin/skills/lisa-plugin-sync-explain/SKILL.md +53 -0
  333. package/plugins/lisa-devin/skills/lisa-posthog-access/SKILL.md +140 -0
  334. package/plugins/lisa-devin/skills/lisa-prd-backlink/SKILL.md +274 -0
  335. package/plugins/lisa-devin/skills/lisa-prd-source-write/SKILL.md +104 -0
  336. package/plugins/lisa-devin/skills/lisa-prd-ticket-coverage/SKILL.md +187 -0
  337. package/plugins/lisa-devin/skills/lisa-product-walkthrough/SKILL.md +106 -0
  338. package/plugins/lisa-devin/skills/lisa-project-ideation/SKILL.md +417 -0
  339. package/plugins/lisa-devin/skills/lisa-project-ideation/examples/evidence-card-format.md +21 -0
  340. package/plugins/lisa-devin/skills/lisa-project-ideation/examples/host-project-only.md +22 -0
  341. package/plugins/lisa-devin/skills/lisa-project-ideation/examples/idempotency-verification-harness.md +57 -0
  342. package/plugins/lisa-devin/skills/lisa-project-ideation/examples/public-external-inspiration.md +22 -0
  343. package/plugins/lisa-devin/skills/lisa-project-ideation/examples/unavailable-data-rejection.md +22 -0
  344. package/plugins/lisa-devin/skills/lisa-pull-request-review/SKILL.md +132 -0
  345. package/plugins/lisa-devin/skills/lisa-quality-review/SKILL.md +63 -0
  346. package/plugins/lisa-devin/skills/lisa-queue-status/SKILL.md +215 -0
  347. package/plugins/lisa-devin/skills/lisa-remote-dispatch/SKILL.md +109 -0
  348. package/plugins/lisa-devin/skills/lisa-remote-dispatch/scripts/dispatch.mjs +535 -0
  349. package/plugins/lisa-devin/skills/lisa-repair-intake/SKILL.md +1621 -0
  350. package/plugins/lisa-devin/skills/lisa-reproduce-bug/SKILL.md +75 -0
  351. package/plugins/lisa-devin/skills/lisa-research/SKILL.md +92 -0
  352. package/plugins/lisa-devin/skills/lisa-review-implementation/SKILL.md +209 -0
  353. package/plugins/lisa-devin/skills/lisa-review-local/SKILL.md +141 -0
  354. package/plugins/lisa-devin/skills/lisa-rework-triage/SKILL.md +206 -0
  355. package/plugins/lisa-devin/skills/lisa-root-cause-analysis/SKILL.md +135 -0
  356. package/plugins/lisa-devin/skills/lisa-secrets-access/SKILL.md +430 -0
  357. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/aws-bootstrap.mjs +433 -0
  358. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +288 -0
  359. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +273 -0
  360. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/envfile.mjs +80 -0
  361. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/inspect-vault.mjs +142 -0
  362. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +1085 -0
  363. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/note-format.mjs +179 -0
  364. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/owner.mjs +96 -0
  365. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +342 -0
  366. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/prompt-secret.mjs +79 -0
  367. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/providers.mjs +805 -0
  368. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +680 -0
  369. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/read-secret-note.mjs +105 -0
  370. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/resolve-expo-token.mjs +90 -0
  371. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/resolve-secret.mjs +285 -0
  372. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/rotate-secret.mjs +297 -0
  373. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/routing-floor.mjs +153 -0
  374. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/surfaces.mjs +480 -0
  375. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/sync-secret-to-ci.mjs +510 -0
  376. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/tools-from-notes.mjs +63 -0
  377. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/validate-config.mjs +550 -0
  378. package/plugins/lisa-devin/skills/lisa-security-review/SKILL.md +124 -0
  379. package/plugins/lisa-devin/skills/lisa-security-zap-scan/SKILL.md +53 -0
  380. package/plugins/lisa-devin/skills/lisa-sentry-access/SKILL.md +140 -0
  381. package/plugins/lisa-devin/skills/lisa-setup-atlassian/SKILL.md +407 -0
  382. package/plugins/lisa-devin/skills/lisa-setup-automations/SKILL.md +430 -0
  383. package/plugins/lisa-devin/skills/lisa-setup-automations/scripts/generate-workflow.mjs +288 -0
  384. package/plugins/lisa-devin/skills/lisa-setup-confluence/SKILL.md +254 -0
  385. package/plugins/lisa-devin/skills/lisa-setup-github/SKILL.md +319 -0
  386. package/plugins/lisa-devin/skills/lisa-setup-github-repo/SKILL.md +128 -0
  387. package/plugins/lisa-devin/skills/lisa-setup-jira/SKILL.md +198 -0
  388. package/plugins/lisa-devin/skills/lisa-setup-kane/SKILL.md +96 -0
  389. package/plugins/lisa-devin/skills/lisa-setup-linear/SKILL.md +375 -0
  390. package/plugins/lisa-devin/skills/lisa-setup-local-env/SKILL.md +95 -0
  391. package/plugins/lisa-devin/skills/lisa-setup-local-env/scripts/local-env.mjs +196 -0
  392. package/plugins/lisa-devin/skills/lisa-setup-notion/SKILL.md +376 -0
  393. package/plugins/lisa-devin/skills/lisa-setup-remote-aws/SKILL.md +149 -0
  394. package/plugins/lisa-devin/skills/lisa-setup-remote-env/SKILL.md +382 -0
  395. package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/materialized-env-authority.mjs +262 -0
  396. package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/session-start.sh +87 -0
  397. package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/setup.sh +216 -0
  398. package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/environment.mjs +227 -0
  399. package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +378 -0
  400. package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +1632 -0
  401. package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/tool-floor.mjs +94 -0
  402. package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +448 -0
  403. package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +250 -0
  404. package/plugins/lisa-devin/skills/lisa-setup-sonar/SKILL.md +153 -0
  405. package/plugins/lisa-devin/skills/lisa-setup-workstation/SKILL.md +143 -0
  406. package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/bounded-child.mjs +168 -0
  407. package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/catalogue.mjs +293 -0
  408. package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/cli.mjs +333 -0
  409. package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/workstation.mjs +511 -0
  410. package/plugins/lisa-devin/skills/lisa-sonarcloud-access/SKILL.md +202 -0
  411. package/plugins/lisa-devin/skills/lisa-spec-conformance/SKILL.md +205 -0
  412. package/plugins/lisa-devin/skills/lisa-starter-sync/SKILL.md +35 -0
  413. package/plugins/lisa-devin/skills/lisa-sync-down/SKILL.md +209 -0
  414. package/plugins/lisa-devin/skills/lisa-task-decomposition/SKILL.md +195 -0
  415. package/plugins/lisa-devin/skills/lisa-task-triage/SKILL.md +23 -0
  416. package/plugins/lisa-devin/skills/lisa-tdd-implementation/SKILL.md +91 -0
  417. package/plugins/lisa-devin/skills/lisa-tear-down-automations/SKILL.md +85 -0
  418. package/plugins/lisa-devin/skills/lisa-test-strategy/SKILL.md +66 -0
  419. package/plugins/lisa-devin/skills/lisa-ticket-triage/SKILL.md +214 -0
  420. package/plugins/lisa-devin/skills/lisa-track/SKILL.md +129 -0
  421. package/plugins/lisa-devin/skills/lisa-tracker-add-journey/SKILL.md +28 -0
  422. package/plugins/lisa-devin/skills/lisa-tracker-build-intake/SKILL.md +134 -0
  423. package/plugins/lisa-devin/skills/lisa-tracker-claim/SKILL.md +35 -0
  424. package/plugins/lisa-devin/skills/lisa-tracker-create/SKILL.md +28 -0
  425. package/plugins/lisa-devin/skills/lisa-tracker-evidence/SKILL.md +63 -0
  426. package/plugins/lisa-devin/skills/lisa-tracker-journey/SKILL.md +27 -0
  427. package/plugins/lisa-devin/skills/lisa-tracker-read/SKILL.md +28 -0
  428. package/plugins/lisa-devin/skills/lisa-tracker-source-artifacts/SKILL.md +111 -0
  429. package/plugins/lisa-devin/skills/lisa-tracker-sync/SKILL.md +150 -0
  430. package/plugins/lisa-devin/skills/lisa-tracker-validate/SKILL.md +37 -0
  431. package/plugins/lisa-devin/skills/lisa-tracker-verify/SKILL.md +29 -0
  432. package/plugins/lisa-devin/skills/lisa-tracker-write/SKILL.md +55 -0
  433. package/plugins/lisa-devin/skills/lisa-usage-accounting/SKILL.md +205 -0
  434. package/plugins/lisa-devin/skills/lisa-usage-accounting/references/effectiveness.md +63 -0
  435. package/plugins/lisa-devin/skills/lisa-use-the-product/SKILL.md +86 -0
  436. package/plugins/lisa-devin/skills/lisa-validate-tracker-mapping/SKILL.md +467 -0
  437. package/plugins/lisa-devin/skills/lisa-verification-lifecycle/SKILL.md +384 -0
  438. package/plugins/lisa-devin/skills/lisa-verify/SKILL.md +55 -0
  439. package/plugins/lisa-devin/skills/lisa-verify-prd/SKILL.md +394 -0
  440. package/plugins/lisa-devin/skills/lisa-verify-workflow-change/SKILL.md +154 -0
  441. package/plugins/lisa-devin/skills/lisa-wiki-install/SKILL.md +101 -0
  442. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  443. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  444. package/plugins/lisa-expo-agy/plugin.json +1 -1
  445. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  446. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  447. package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +11 -0
  448. package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +8 -0
  449. package/plugins/lisa-expo-devin/.mcp.json +8 -0
  450. package/plugins/lisa-expo-devin/THIRD-PARTY-NOTICES.md +84 -0
  451. package/plugins/lisa-expo-devin/agents/ops-specialist.md +124 -0
  452. package/plugins/lisa-expo-devin/hooks/devin-tool-name-shim.sh +30 -0
  453. package/plugins/lisa-expo-devin/hooks/register-maestro-mcp.sh +84 -0
  454. package/plugins/lisa-expo-devin/hooks.json +14 -0
  455. package/plugins/lisa-expo-devin/skills/add-app-clip/SKILL.md +280 -0
  456. package/plugins/lisa-expo-devin/skills/add-app-clip/references/native-module.md +96 -0
  457. package/plugins/lisa-expo-devin/skills/apollo-client/SKILL.md +238 -0
  458. package/plugins/lisa-expo-devin/skills/apollo-client/references/mutation-patterns.md +360 -0
  459. package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/SKILL.md +369 -0
  460. package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
  461. package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
  462. package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
  463. package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/scripts/test_validate_atomic_structure.py +240 -0
  464. package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +438 -0
  465. package/plugins/lisa-expo-devin/skills/building-native-ui/SKILL.md +321 -0
  466. package/plugins/lisa-expo-devin/skills/building-native-ui/references/animations.md +220 -0
  467. package/plugins/lisa-expo-devin/skills/building-native-ui/references/controls.md +272 -0
  468. package/plugins/lisa-expo-devin/skills/building-native-ui/references/form-sheet.md +253 -0
  469. package/plugins/lisa-expo-devin/skills/building-native-ui/references/gradients.md +106 -0
  470. package/plugins/lisa-expo-devin/skills/building-native-ui/references/icons.md +213 -0
  471. package/plugins/lisa-expo-devin/skills/building-native-ui/references/media.md +198 -0
  472. package/plugins/lisa-expo-devin/skills/building-native-ui/references/route-structure.md +229 -0
  473. package/plugins/lisa-expo-devin/skills/building-native-ui/references/search.md +248 -0
  474. package/plugins/lisa-expo-devin/skills/building-native-ui/references/storage.md +121 -0
  475. package/plugins/lisa-expo-devin/skills/building-native-ui/references/tabs.md +433 -0
  476. package/plugins/lisa-expo-devin/skills/building-native-ui/references/toolbar-and-headers.md +284 -0
  477. package/plugins/lisa-expo-devin/skills/building-native-ui/references/visual-effects.md +197 -0
  478. package/plugins/lisa-expo-devin/skills/building-native-ui/references/webgpu-three.md +605 -0
  479. package/plugins/lisa-expo-devin/skills/building-native-ui/references/zoom-transitions.md +158 -0
  480. package/plugins/lisa-expo-devin/skills/container-view-pattern/SKILL.md +355 -0
  481. package/plugins/lisa-expo-devin/skills/container-view-pattern/references/examples.md +749 -0
  482. package/plugins/lisa-expo-devin/skills/container-view-pattern/references/patterns.md +318 -0
  483. package/plugins/lisa-expo-devin/skills/container-view-pattern/scripts/create_component.py +200 -0
  484. package/plugins/lisa-expo-devin/skills/container-view-pattern/scripts/validate_component.py +332 -0
  485. package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/SKILL.md +268 -0
  486. package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/common-issues.md +619 -0
  487. package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
  488. package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/platform-api.md +276 -0
  489. package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +416 -0
  490. package/plugins/lisa-expo-devin/skills/directory-structure/SKILL.md +214 -0
  491. package/plugins/lisa-expo-devin/skills/directory-structure/scripts/validate_structure.py +454 -0
  492. package/plugins/lisa-expo-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
  493. package/plugins/lisa-expo-devin/skills/eas-update-insights/SKILL.md +228 -0
  494. package/plugins/lisa-expo-devin/skills/eas-update-insights/references/channel-insights-schema.md +47 -0
  495. package/plugins/lisa-expo-devin/skills/eas-update-insights/references/update-insights-schema.md +69 -0
  496. package/plugins/lisa-expo-devin/skills/expo-api-routes/SKILL.md +369 -0
  497. package/plugins/lisa-expo-devin/skills/expo-brownfield/SKILL.md +54 -0
  498. package/plugins/lisa-expo-devin/skills/expo-brownfield/references/brownfield-integrated.md +526 -0
  499. package/plugins/lisa-expo-devin/skills/expo-brownfield/references/brownfield-isolated.md +402 -0
  500. package/plugins/lisa-expo-devin/skills/expo-brownfield/references/comparison.md +63 -0
  501. package/plugins/lisa-expo-devin/skills/expo-brownfield/references/troubleshooting.md +88 -0
  502. package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/SKILL.md +92 -0
  503. package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/fetch.js +113 -0
  504. package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/package.json +11 -0
  505. package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/validate.js +85 -0
  506. package/plugins/lisa-expo-devin/skills/expo-deployment/SKILL.md +193 -0
  507. package/plugins/lisa-expo-devin/skills/expo-deployment/references/app-store-metadata.md +479 -0
  508. package/plugins/lisa-expo-devin/skills/expo-deployment/references/ios-app-store.md +355 -0
  509. package/plugins/lisa-expo-devin/skills/expo-deployment/references/play-store.md +264 -0
  510. package/plugins/lisa-expo-devin/skills/expo-deployment/references/testflight.md +58 -0
  511. package/plugins/lisa-expo-devin/skills/expo-deployment/references/workflows.md +200 -0
  512. package/plugins/lisa-expo-devin/skills/expo-dev-client/SKILL.md +164 -0
  513. package/plugins/lisa-expo-devin/skills/expo-env-config/SKILL.md +309 -0
  514. package/plugins/lisa-expo-devin/skills/expo-env-config/references/validation-patterns.md +417 -0
  515. package/plugins/lisa-expo-devin/skills/expo-module/SKILL.md +141 -0
  516. package/plugins/lisa-expo-devin/skills/expo-module/references/config-plugin.md +90 -0
  517. package/plugins/lisa-expo-devin/skills/expo-module/references/create-expo-module.md +206 -0
  518. package/plugins/lisa-expo-devin/skills/expo-module/references/lifecycle.md +127 -0
  519. package/plugins/lisa-expo-devin/skills/expo-module/references/module-config.md +48 -0
  520. package/plugins/lisa-expo-devin/skills/expo-module/references/native-module.md +286 -0
  521. package/plugins/lisa-expo-devin/skills/expo-module/references/native-view.md +171 -0
  522. package/plugins/lisa-expo-devin/skills/expo-router-best-practices/SKILL.md +432 -0
  523. package/plugins/lisa-expo-devin/skills/expo-router-best-practices/references/official-docs.md +290 -0
  524. package/plugins/lisa-expo-devin/skills/expo-router-best-practices/references/static-export-verification.md +54 -0
  525. package/plugins/lisa-expo-devin/skills/expo-router-best-practices/scripts/generate-route.py +171 -0
  526. package/plugins/lisa-expo-devin/skills/expo-tailwind-setup/SKILL.md +487 -0
  527. package/plugins/lisa-expo-devin/skills/expo-ui-jetpack-compose/SKILL.md +40 -0
  528. package/plugins/lisa-expo-devin/skills/expo-ui-swift-ui/SKILL.md +39 -0
  529. package/plugins/lisa-expo-devin/skills/gluestack-nativewind/SKILL.md +419 -0
  530. package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/color-tokens.md +343 -0
  531. package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/component-mapping.md +307 -0
  532. package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
  533. package/plugins/lisa-expo-devin/skills/gluestack-nativewind/scripts/test_validate_styling.py +240 -0
  534. package/plugins/lisa-expo-devin/skills/gluestack-nativewind/scripts/validate_styling.py +424 -0
  535. package/plugins/lisa-expo-devin/skills/jira-add-journey/SKILL.md +140 -0
  536. package/plugins/lisa-expo-devin/skills/jira-create/SKILL.md +161 -0
  537. package/plugins/lisa-expo-devin/skills/jira-evidence/SKILL.md +84 -0
  538. package/plugins/lisa-expo-devin/skills/jira-evidence/scripts/post-evidence.sh +262 -0
  539. package/plugins/lisa-expo-devin/skills/jira-journey/SKILL.md +196 -0
  540. package/plugins/lisa-expo-devin/skills/jira-journey/scripts/generate-templates.py +281 -0
  541. package/plugins/lisa-expo-devin/skills/jira-journey/scripts/parse-plan.py +400 -0
  542. package/plugins/lisa-expo-devin/skills/jira-verify/SKILL.md +27 -0
  543. package/plugins/lisa-expo-devin/skills/local-state/SKILL.md +362 -0
  544. package/plugins/lisa-expo-devin/skills/local-state/references/async-storage.md +505 -0
  545. package/plugins/lisa-expo-devin/skills/local-state/references/persistence-patterns.md +711 -0
  546. package/plugins/lisa-expo-devin/skills/local-state/references/reactive-variables.md +446 -0
  547. package/plugins/lisa-expo-devin/skills/maestro-mcp-setup/SKILL.md +144 -0
  548. package/plugins/lisa-expo-devin/skills/native-data-fetching/SKILL.md +507 -0
  549. package/plugins/lisa-expo-devin/skills/native-data-fetching/references/expo-router-loaders.md +344 -0
  550. package/plugins/lisa-expo-devin/skills/ops-browser-uat/SKILL.md +124 -0
  551. package/plugins/lisa-expo-devin/skills/ops-check-logs/SKILL.md +217 -0
  552. package/plugins/lisa-expo-devin/skills/ops-db-ops/SKILL.md +214 -0
  553. package/plugins/lisa-expo-devin/skills/ops-deploy/SKILL.md +123 -0
  554. package/plugins/lisa-expo-devin/skills/ops-monitor-errors/SKILL.md +99 -0
  555. package/plugins/lisa-expo-devin/skills/ops-performance/SKILL.md +165 -0
  556. package/plugins/lisa-expo-devin/skills/ops-run-local/SKILL.md +169 -0
  557. package/plugins/lisa-expo-devin/skills/ops-verify-health/SKILL.md +101 -0
  558. package/plugins/lisa-expo-devin/skills/owasp-zap/SKILL.md +61 -0
  559. package/plugins/lisa-expo-devin/skills/play-store-access/SKILL.md +226 -0
  560. package/plugins/lisa-expo-devin/skills/playwright-ci-debugging/SKILL.md +140 -0
  561. package/plugins/lisa-expo-devin/skills/playwright-selectors/SKILL.md +496 -0
  562. package/plugins/lisa-expo-devin/skills/reduce-complexity/SKILL.md +251 -0
  563. package/plugins/lisa-expo-devin/skills/reduce-complexity/references/extraction-strategies.md +456 -0
  564. package/plugins/lisa-expo-devin/skills/reduce-complexity/references/refactoring-patterns.md +557 -0
  565. package/plugins/lisa-expo-devin/skills/testing-library/SKILL.md +314 -0
  566. package/plugins/lisa-expo-devin/skills/testing-library/references/async-patterns.md +420 -0
  567. package/plugins/lisa-expo-devin/skills/testing-library/references/expo-router-testing.md +556 -0
  568. package/plugins/lisa-expo-devin/skills/testing-library/references/mocking-patterns.md +612 -0
  569. package/plugins/lisa-expo-devin/skills/testing-library/references/query-priority.md +291 -0
  570. package/plugins/lisa-expo-devin/skills/upgrading-expo/SKILL.md +134 -0
  571. package/plugins/lisa-expo-devin/skills/upgrading-expo/references/expo-av-to-audio.md +132 -0
  572. package/plugins/lisa-expo-devin/skills/upgrading-expo/references/expo-av-to-video.md +160 -0
  573. package/plugins/lisa-expo-devin/skills/upgrading-expo/references/native-tabs.md +124 -0
  574. package/plugins/lisa-expo-devin/skills/upgrading-expo/references/new-architecture.md +79 -0
  575. package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-19.md +79 -0
  576. package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-compiler.md +59 -0
  577. package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-navigation-to-expo-router.md +61 -0
  578. package/plugins/lisa-expo-devin/skills/use-dom/SKILL.md +417 -0
  579. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  580. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  581. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  582. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  583. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  584. package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +11 -0
  585. package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +8 -0
  586. package/plugins/lisa-harper-fabric-devin/generated-artifact-globs.txt +5 -0
  587. package/plugins/lisa-harper-fabric-devin/hooks/block-generated-artifact-edits.sh +141 -0
  588. package/plugins/lisa-harper-fabric-devin/hooks/devin-tool-name-shim.sh +30 -0
  589. package/plugins/lisa-harper-fabric-devin/hooks/enforce-config-extensions.mjs +196 -0
  590. package/plugins/lisa-harper-fabric-devin/hooks/enforce-config-extensions.sh +46 -0
  591. package/plugins/lisa-harper-fabric-devin/hooks/inject-rules.sh +27 -0
  592. package/plugins/lisa-harper-fabric-devin/hooks.json +36 -0
  593. package/plugins/lisa-harper-fabric-devin/rules/eager/harper-fabric.md +31 -0
  594. package/plugins/lisa-harper-fabric-devin/rules/reference/harper-fabric.md +52 -0
  595. package/plugins/lisa-harper-fabric-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
  596. package/plugins/lisa-harper-fabric-devin/skills/harper-auth/SKILL.md +368 -0
  597. package/plugins/lisa-harper-fabric-devin/skills/harper-build-and-deploy/SKILL.md +198 -0
  598. package/plugins/lisa-harper-fabric-devin/skills/harper-caching/SKILL.md +265 -0
  599. package/plugins/lisa-harper-fabric-devin/skills/harper-component-model/SKILL.md +87 -0
  600. package/plugins/lisa-harper-fabric-devin/skills/harper-config-yaml/SKILL.md +283 -0
  601. package/plugins/lisa-harper-fabric-devin/skills/harper-operations/SKILL.md +239 -0
  602. package/plugins/lisa-harper-fabric-devin/skills/harper-realtime/SKILL.md +187 -0
  603. package/plugins/lisa-harper-fabric-devin/skills/harper-resources/SKILL.md +142 -0
  604. package/plugins/lisa-harper-fabric-devin/skills/harper-rest-queries/SKILL.md +403 -0
  605. package/plugins/lisa-harper-fabric-devin/skills/harper-schema-graphql/SKILL.md +123 -0
  606. package/plugins/lisa-harper-fabric-devin/skills/harper-testing/SKILL.md +254 -0
  607. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  608. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  609. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  610. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  611. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  612. package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +11 -0
  613. package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +8 -0
  614. package/plugins/lisa-nestjs-devin/hooks/block-migration-edits.sh +125 -0
  615. package/plugins/lisa-nestjs-devin/hooks/devin-tool-name-shim.sh +30 -0
  616. package/plugins/lisa-nestjs-devin/hooks/lisa-edit-gate.sh +152 -0
  617. package/plugins/lisa-nestjs-devin/hooks.json +15 -0
  618. package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/SKILL.md +176 -0
  619. package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/advanced-features.md +527 -0
  620. package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/project-patterns.md +531 -0
  621. package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/quick-start.md +257 -0
  622. package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
  623. package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/types-scalars.md +513 -0
  624. package/plugins/lisa-nestjs-devin/skills/nestjs-rules/SKILL.md +560 -0
  625. package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/SKILL.md +275 -0
  626. package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
  627. package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/entity-patterns.md +450 -0
  628. package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/observability-patterns.md +536 -0
  629. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  630. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  631. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  632. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  633. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  634. package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +8 -0
  635. package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +8 -0
  636. package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-repo-topic/SKILL.md +163 -0
  637. package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-repo-topic/references/repo-topic-config.md +185 -0
  638. package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/SKILL.md +175 -0
  639. package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/references/platform-routing.md +123 -0
  640. package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/references/prompts.md +78 -0
  641. package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-setup/SKILL.md +141 -0
  642. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  643. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  644. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  645. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  646. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  647. package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +11 -0
  648. package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +8 -0
  649. package/plugins/lisa-phaser-devin/agents/accessibility-advocate.md +55 -0
  650. package/plugins/lisa-phaser-devin/agents/art-director.md +90 -0
  651. package/plugins/lisa-phaser-devin/agents/audio-director.md +65 -0
  652. package/plugins/lisa-phaser-devin/agents/combat-designer.md +61 -0
  653. package/plugins/lisa-phaser-devin/agents/economy-designer.md +59 -0
  654. package/plugins/lisa-phaser-devin/agents/game-designer.md +62 -0
  655. package/plugins/lisa-phaser-devin/agents/game-feel-specialist.md +56 -0
  656. package/plugins/lisa-phaser-devin/agents/level-designer.md +59 -0
  657. package/plugins/lisa-phaser-devin/agents/localization-manager.md +61 -0
  658. package/plugins/lisa-phaser-devin/agents/marketing-strategist.md +59 -0
  659. package/plugins/lisa-phaser-devin/agents/monetization-designer.md +59 -0
  660. package/plugins/lisa-phaser-devin/agents/narrative-designer.md +56 -0
  661. package/plugins/lisa-phaser-devin/agents/onboarding-advocate.md +59 -0
  662. package/plugins/lisa-phaser-devin/agents/player-advocate.md +57 -0
  663. package/plugins/lisa-phaser-devin/agents/producer.md +61 -0
  664. package/plugins/lisa-phaser-devin/agents/product-analyst.md +62 -0
  665. package/plugins/lisa-phaser-devin/agents/publisher.md +59 -0
  666. package/plugins/lisa-phaser-devin/agents/qa-playtester.md +57 -0
  667. package/plugins/lisa-phaser-devin/agents/target-player.md +52 -0
  668. package/plugins/lisa-phaser-devin/agents/ux-ui-designer.md +60 -0
  669. package/plugins/lisa-phaser-devin/hooks/devin-tool-name-shim.sh +30 -0
  670. package/plugins/lisa-phaser-devin/hooks/inject-rules.sh +27 -0
  671. package/plugins/lisa-phaser-devin/hooks.json +14 -0
  672. package/plugins/lisa-phaser-devin/rules/eager/phaser.md +44 -0
  673. package/plugins/lisa-phaser-devin/rules/reference/phaser.md +184 -0
  674. package/plugins/lisa-phaser-devin/skills/phaser-accessibility/SKILL.md +135 -0
  675. package/plugins/lisa-phaser-devin/skills/phaser-asset-pipeline/SKILL.md +222 -0
  676. package/plugins/lisa-phaser-devin/skills/phaser-asset-sourcing/SKILL.md +151 -0
  677. package/plugins/lisa-phaser-devin/skills/phaser-build-deploy/SKILL.md +153 -0
  678. package/plugins/lisa-phaser-devin/skills/phaser-i18n/SKILL.md +122 -0
  679. package/plugins/lisa-phaser-devin/skills/phaser-project-structure/SKILL.md +89 -0
  680. package/plugins/lisa-phaser-devin/skills/phaser-services/SKILL.md +208 -0
  681. package/plugins/lisa-phaser-devin/skills/phaser-testing/SKILL.md +192 -0
  682. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  683. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  684. package/plugins/lisa-rails-agy/plugin.json +1 -1
  685. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  686. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  687. package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +11 -0
  688. package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +8 -0
  689. package/plugins/lisa-rails-devin/agents/ops-specialist.md +226 -0
  690. package/plugins/lisa-rails-devin/hooks/devin-tool-name-shim.sh +30 -0
  691. package/plugins/lisa-rails-devin/hooks/inject-rules.sh +40 -0
  692. package/plugins/lisa-rails-devin/hooks/lisa-edit-gate.sh +152 -0
  693. package/plugins/lisa-rails-devin/hooks/rubocop-on-edit.sh +101 -0
  694. package/plugins/lisa-rails-devin/hooks/sg-scan-on-edit.sh +97 -0
  695. package/plugins/lisa-rails-devin/hooks.json +29 -0
  696. package/plugins/lisa-rails-devin/rules/eager/rails-conventions.md +23 -0
  697. package/plugins/lisa-rails-devin/rules/reference/rails-conventions.md +176 -0
  698. package/plugins/lisa-rails-devin/skills/action-controller-best-practices/SKILL.md +374 -0
  699. package/plugins/lisa-rails-devin/skills/action-view-best-practices/SKILL.md +335 -0
  700. package/plugins/lisa-rails-devin/skills/active-record-model-best-practices/SKILL.md +166 -0
  701. package/plugins/lisa-rails-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
  702. package/plugins/lisa-rails-devin/skills/fix-linter-error/SKILL.md +45 -0
  703. package/plugins/lisa-rails-devin/skills/improve-code-complexity/SKILL.md +51 -0
  704. package/plugins/lisa-rails-devin/skills/improve-max-lines/SKILL.md +46 -0
  705. package/plugins/lisa-rails-devin/skills/improve-max-lines-per-function/SKILL.md +46 -0
  706. package/plugins/lisa-rails-devin/skills/improve-test-coverage/SKILL.md +48 -0
  707. package/plugins/lisa-rails-devin/skills/jira-add-journey/SKILL.md +79 -0
  708. package/plugins/lisa-rails-devin/skills/jira-create/SKILL.md +100 -0
  709. package/plugins/lisa-rails-devin/skills/jira-evidence/SKILL.md +76 -0
  710. package/plugins/lisa-rails-devin/skills/jira-evidence/scripts/post-evidence.sh +262 -0
  711. package/plugins/lisa-rails-devin/skills/jira-journey/SKILL.md +65 -0
  712. package/plugins/lisa-rails-devin/skills/jira-verify/SKILL.md +26 -0
  713. package/plugins/lisa-rails-devin/skills/ops-check-logs/SKILL.md +191 -0
  714. package/plugins/lisa-rails-devin/skills/ops-deploy/SKILL.md +153 -0
  715. package/plugins/lisa-rails-devin/skills/ops-run-local/SKILL.md +169 -0
  716. package/plugins/lisa-rails-devin/skills/ops-verify-jobs/SKILL.md +157 -0
  717. package/plugins/lisa-rails-devin/skills/ops-verify-telemetry/SKILL.md +226 -0
  718. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  719. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  720. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  721. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  722. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  723. package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +11 -0
  724. package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +8 -0
  725. package/plugins/lisa-typescript-devin/hooks/block-suppress-directives.sh +141 -0
  726. package/plugins/lisa-typescript-devin/hooks/devin-tool-name-shim.sh +30 -0
  727. package/plugins/lisa-typescript-devin/hooks/format-on-edit.sh +102 -0
  728. package/plugins/lisa-typescript-devin/hooks/lint-on-edit.sh +117 -0
  729. package/plugins/lisa-typescript-devin/hooks/lisa-edit-gate.sh +152 -0
  730. package/plugins/lisa-typescript-devin/hooks/sg-scan-on-edit.sh +94 -0
  731. package/plugins/lisa-typescript-devin/hooks.json +34 -0
  732. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  733. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  734. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  735. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  736. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  737. package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +8 -0
  738. package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +8 -0
  739. package/plugins/lisa-wiki-devin/ci/lisa-wiki-validate.yml +33 -0
  740. package/plugins/lisa-wiki-devin/schema/lisa-wiki-config.schema.json +162 -0
  741. package/plugins/lisa-wiki-devin/schema/wiki-structure.schema.json +51 -0
  742. package/plugins/lisa-wiki-devin/scripts/_wiki-lib.mjs +204 -0
  743. package/plugins/lisa-wiki-devin/scripts/diff-guard.mjs +117 -0
  744. package/plugins/lisa-wiki-devin/scripts/ensure-gitignore.mjs +131 -0
  745. package/plugins/lisa-wiki-devin/scripts/ensure-wiki.mjs +268 -0
  746. package/plugins/lisa-wiki-devin/scripts/ingest-git.mjs +198 -0
  747. package/plugins/lisa-wiki-devin/scripts/ingest-memory.mjs +135 -0
  748. package/plugins/lisa-wiki-devin/scripts/ingest-roles.mjs +95 -0
  749. package/plugins/lisa-wiki-devin/scripts/ingest_slack_channel.py +361 -0
  750. package/plugins/lisa-wiki-devin/scripts/lib/bounded-child.mjs +159 -0
  751. package/plugins/lisa-wiki-devin/scripts/lint-wiki.mjs +513 -0
  752. package/plugins/lisa-wiki-devin/scripts/mcp-doctor.mjs +72 -0
  753. package/plugins/lisa-wiki-devin/scripts/render-contract.mjs +107 -0
  754. package/plugins/lisa-wiki-devin/scripts/rewrite-refs.mjs +144 -0
  755. package/plugins/lisa-wiki-devin/scripts/slack_oauth_user.py +179 -0
  756. package/plugins/lisa-wiki-devin/scripts/validate-config.mjs +321 -0
  757. package/plugins/lisa-wiki-devin/scripts/verify-migration.mjs +203 -0
  758. package/plugins/lisa-wiki-devin/scripts/verify-wiki-safety.mjs +126 -0
  759. package/plugins/lisa-wiki-devin/scripts/wiki-safety.mjs +378 -0
  760. package/plugins/lisa-wiki-devin/scripts/wiki-status.mjs +345 -0
  761. package/plugins/lisa-wiki-devin/skills/lisa-wiki-add-ingest/SKILL.md +34 -0
  762. package/plugins/lisa-wiki-devin/skills/lisa-wiki-add-role/SKILL.md +35 -0
  763. package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-confluence/SKILL.md +25 -0
  764. package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-docs/SKILL.md +30 -0
  765. package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-git/SKILL.md +25 -0
  766. package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-jira/SKILL.md +28 -0
  767. package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-memory/SKILL.md +28 -0
  768. package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-notion/SKILL.md +25 -0
  769. package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-roles/SKILL.md +22 -0
  770. package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-slack/SKILL.md +30 -0
  771. package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-web/SKILL.md +23 -0
  772. package/plugins/lisa-wiki-devin/skills/lisa-wiki-doctor/SKILL.md +47 -0
  773. package/plugins/lisa-wiki-devin/skills/lisa-wiki-ingest/SKILL.md +106 -0
  774. package/plugins/lisa-wiki-devin/skills/lisa-wiki-lint/SKILL.md +32 -0
  775. package/plugins/lisa-wiki-devin/skills/lisa-wiki-migrate/SKILL.md +43 -0
  776. package/plugins/lisa-wiki-devin/skills/lisa-wiki-onboard-me/SKILL.md +33 -0
  777. package/plugins/lisa-wiki-devin/skills/lisa-wiki-query/SKILL.md +40 -0
  778. package/plugins/lisa-wiki-devin/skills/lisa-wiki-setup/SKILL.md +75 -0
  779. package/plugins/lisa-wiki-devin/skills/lisa-wiki-setup-automations/SKILL.md +83 -0
  780. package/plugins/lisa-wiki-devin/skills/lisa-wiki-status/SKILL.md +47 -0
  781. package/plugins/lisa-wiki-devin/skills/lisa-wiki-tear-down-automations/SKILL.md +34 -0
  782. package/plugins/lisa-wiki-devin/skills/lisa-wiki-usage/SKILL.md +50 -0
  783. package/plugins/lisa-wiki-devin/templates/agents/role-agent.claude.md +16 -0
  784. package/plugins/lisa-wiki-devin/templates/agents/role-agent.codex.toml +15 -0
  785. package/plugins/lisa-wiki-devin/templates/index.md +17 -0
  786. package/plugins/lisa-wiki-devin/templates/llm-wiki-contract.md +72 -0
  787. package/plugins/lisa-wiki-devin/templates/log.md +8 -0
  788. package/plugins/lisa-wiki-devin/templates/page-types/architecture.md +18 -0
  789. package/plugins/lisa-wiki-devin/templates/page-types/concept.md +18 -0
  790. package/plugins/lisa-wiki-devin/templates/page-types/decision.md +18 -0
  791. package/plugins/lisa-wiki-devin/templates/page-types/entity.md +19 -0
  792. package/plugins/lisa-wiki-devin/templates/page-types/open-question.md +18 -0
  793. package/plugins/lisa-wiki-devin/templates/page-types/playbook.md +18 -0
  794. package/plugins/lisa-wiki-devin/templates/page-types/project.md +19 -0
  795. package/plugins/lisa-wiki-devin/templates/page-types/requirement.md +19 -0
  796. package/plugins/lisa-wiki-devin/templates/page-types/staff.md +26 -0
  797. package/plugins/lisa-wiki-devin/templates/start-here.md +24 -0
  798. package/plugins/lisa-wiki-devin/templates/state-readme.md +20 -0
  799. package/plugins/lisa-wiki-devin/templates/wrapper-gitignore.txt +19 -0
  800. package/scripts/build-plugins.sh +2 -2
  801. package/scripts/deployed-guard-advice.mjs +1 -0
  802. package/scripts/generate-devin-plugin-artifacts.mjs +263 -0
  803. package/scripts/lib/hook-registration-audit.mjs +3 -1
  804. package/scripts/lib/per-agent-hook-filter.mjs +174 -5
  805. package/scripts/lib/plugin-rule-citations.mjs +2 -2
@@ -0,0 +1,1632 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Prepare a remote environment: toolchain, then secrets, then the project hook.
4
+ *
5
+ * This is the script a remote environment's setup **and** maintenance fields
6
+ * call. They are the same script on purpose. A container may be built fresh or
7
+ * resumed from cache, and every step here is idempotent and version-aware, so
8
+ * running it twice is correct.
9
+ *
10
+ * Order matters. The toolchain comes first because the secrets step needs the
11
+ * provider CLI that the toolchain installs. The project hook comes last because
12
+ * it is the only part that may assume everything else is ready.
13
+ *
14
+ * **Which phases run depends on the surface, and the reason is not cosmetic.**
15
+ * On a surface that re-runs this script when a container resumes, materializing
16
+ * here is what picks up a rotated value. On a surface that *skips* it whenever a
17
+ * filesystem cache exists, materializing here would write the value once and
18
+ * then never refresh it — a rotated credential would stay stale until the cache
19
+ * expired. Those surfaces materialize from a session-start hook instead, which
20
+ * runs every session including resumed ones, and `--phase=secrets` is how that
21
+ * hook re-enters this file.
22
+ *
23
+ * The selection comes from the surface's `materializeAt` capability rather than
24
+ * from its name, so adding a surface does not mean editing a branch here.
25
+ *
26
+ * Usage:
27
+ * setup-remote-env.mjs [--dry-run] [--phase=toolchain|secrets|hook]
28
+ * @module setup-remote-env
29
+ */
30
+
31
+ import { createHash } from "node:crypto";
32
+ import {
33
+ chmodSync,
34
+ existsSync,
35
+ mkdirSync,
36
+ readFileSync,
37
+ realpathSync,
38
+ rmSync,
39
+ writeFileSync,
40
+ } from "node:fs";
41
+ import { homedir } from "node:os";
42
+ import { delimiter, dirname, join, resolve } from "node:path";
43
+ import { fileURLToPath, pathToFileURL } from "node:url";
44
+
45
+ // Reaching across to the workstation skill, the same sibling layout
46
+ // `siblingScript` asserts at runtime. Static rather than dynamic because the
47
+ // emitted field is a module-level constant: it must exist before anything can
48
+ // read it, and the alternative is restating the install directories here, where
49
+ // they would drift the first time a tool ships with a new one.
50
+ import { pathDirs } from "../../lisa-setup-workstation/scripts/catalogue.mjs";
51
+
52
+ import { assertPinned, extractVersion, planToolchain } from "./toolchain.mjs";
53
+
54
+ import {
55
+ boundedChildOutput,
56
+ SETUP_OPERATION_BUDGET_MS,
57
+ } from "../../lisa-setup-workstation/scripts/bounded-child.mjs";
58
+
59
+ const HERE = dirname(fileURLToPath(import.meta.url));
60
+
61
+ /**
62
+ * Locate a sibling skill's script.
63
+ *
64
+ * Secrets belong to `lisa-secrets-access`, and this script must not reimplement
65
+ * any part of that contract — the single chokepoint is what makes the one-store
66
+ * rule enforceable. Resolving relative to this file works in every install
67
+ * layout, because skills are always siblings under one `skills/` directory.
68
+ * @param {string} skill Sibling skill slug.
69
+ * @param {string} script Script filename.
70
+ * @returns {string} Absolute path.
71
+ */
72
+ function siblingScript(skill, script) {
73
+ const path = resolve(HERE, "..", "..", skill, "scripts", script);
74
+ if (!existsSync(path)) {
75
+ throw new Error(
76
+ `cannot find ${skill}/scripts/${script} beside this skill.\n` +
77
+ `Remote setup composes with that skill rather than reimplementing it.`
78
+ );
79
+ }
80
+ return path;
81
+ }
82
+
83
+ /**
84
+ * Read the `remoteEnv` block from `.lisa.config.json`.
85
+ * @param {string} [cwd] Directory to look in.
86
+ * @returns {{tools: object, hook: string|null, surfaces: object}} Configuration.
87
+ */
88
+ export function readRemoteEnvConfig(cwd = process.cwd()) {
89
+ const path = join(cwd, ".lisa.config.json");
90
+ const defaults = {
91
+ tools: { require: [], install: [] },
92
+ hook: null,
93
+ surfaces: {},
94
+ };
95
+ if (!existsSync(path)) return defaults;
96
+ const cfg = JSON.parse(readFileSync(path, "utf8")).remoteEnv;
97
+ if (!cfg) return defaults;
98
+ return {
99
+ tools: { require: [], install: [], ...cfg.tools },
100
+ hook: cfg.hook ?? null,
101
+ surfaces: cfg.surfaces ?? {},
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Probe what a tool reports for its version, treating absence as not present.
107
+ *
108
+ * "Not installed" and "dislikes `--version`" are different answers, and only a
109
+ * failure to *spawn* means the first. Info-ZIP's `unzip` — the build shipped by
110
+ * both macOS and Ubuntu — parses `--version` one letter at a time, warns that
111
+ * `-n` and `-o` conflict, and exits 10. Reading that as absence made `require`
112
+ * fail on a machine that had the tool, which is precisely the false alarm the
113
+ * check exists to avoid raising.
114
+ *
115
+ * Version text is still recovered from a failed invocation where there is any,
116
+ * because a tool that refuses the flag often prints its banner anyway — which
117
+ * is how `unzip` still reports 6.00 despite exiting non-zero.
118
+ *
119
+ * The runner is injectable so both branches can be exercised without depending
120
+ * on which quirky binaries happen to exist on the machine running the tests.
121
+ * @param {string} name Executable name.
122
+ * @param {Function} [exec] Command runner, for tests.
123
+ * @returns {{version: string|null, present: boolean, executable: boolean}} Probe result.
124
+ */
125
+ export function probe(name, exec = boundedChildOutput) {
126
+ try {
127
+ const out = exec(name, ["--version"], {
128
+ encoding: "utf8",
129
+ stdio: ["ignore", "pipe", "pipe"],
130
+ });
131
+ return { present: true, executable: true, version: extractVersion(out) };
132
+ } catch (err) {
133
+ if (err.code === "ETIMEDOUT") throw err;
134
+ // A numeric exit status proves the child started, even when --version is
135
+ // unsupported. EACCES and other spawn failures carry no exit status: a
136
+ // file being discoverable does not mean the agent can execute it.
137
+ const executable = typeof err.status === "number";
138
+ const output = `${err.stdout ?? ""}${err.stderr ?? ""}`;
139
+ return {
140
+ present: err.code !== "ENOENT",
141
+ executable,
142
+ version: executable ? extractVersion(output) : null,
143
+ };
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Refuse an archive whose contents are not exactly what was pinned.
149
+ *
150
+ * Hashed in-process rather than by shelling out. `sha256sum` is a GNU coreutils
151
+ * program: it is not present on a stock macOS, where the equivalent is
152
+ * `shasum -a 256`, and it was never in the manifest's `require` list either — so
153
+ * the verification step depended on a tool nothing asserted, on every surface.
154
+ * Doing it here removes the dependency instead of adding a second name to probe
155
+ * for, and the check can then be tested without a real archive or a real binary.
156
+ * @param {string} archive Path to the downloaded file.
157
+ * @param {string} expected Pinned lowercase hex digest.
158
+ * @param {string} name Tool name, for the message.
159
+ */
160
+ export function verifyChecksum(archive, expected, name) {
161
+ const actual = createHash("sha256")
162
+ .update(readFileSync(archive))
163
+ .digest("hex");
164
+ if (actual !== String(expected).trim().toLowerCase()) {
165
+ throw new Error(
166
+ `${name}: checksum mismatch — refusing to install.\n` +
167
+ ` expected ${expected}\n` +
168
+ ` actual ${actual}\n` +
169
+ `The URL served something other than the reviewed artifact. Do not ` +
170
+ `update the pin to match without establishing why it changed.`
171
+ );
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Install a pinned archive, refusing anything whose checksum does not match.
177
+ * @param {object} tool Manifest entry.
178
+ * @param {string} binDir Directory to install into.
179
+ */
180
+ function installReleaseZip(tool, binDir) {
181
+ const temporary = join(binDir, `.${tool.name}-download`);
182
+ mkdirSync(temporary, { recursive: true });
183
+ try {
184
+ const archive = join(temporary, "download.zip");
185
+ boundedChildOutput("curl", ["-fsSL", tool.url, "-o", archive], {
186
+ stdio: "inherit",
187
+ timeout: SETUP_OPERATION_BUDGET_MS,
188
+ });
189
+ // Verify before unpacking, not after. An unexpected archive must fail
190
+ // before any of its contents reach a directory that is on PATH.
191
+ verifyChecksum(archive, tool.sha256, tool.name);
192
+ boundedChildOutput("unzip", ["-q", "-o", archive, "-d", temporary], {
193
+ stdio: "inherit",
194
+ timeout: SETUP_OPERATION_BUDGET_MS,
195
+ });
196
+ const binary = join(temporary, tool.binary ?? tool.name);
197
+ boundedChildOutput(
198
+ "install",
199
+ ["-m", "0755", binary, join(binDir, tool.name)],
200
+ {
201
+ stdio: "inherit",
202
+ timeout: SETUP_OPERATION_BUDGET_MS,
203
+ }
204
+ );
205
+ } finally {
206
+ rmSync(temporary, { recursive: true, force: true });
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Install a pinned tarball, refusing anything whose checksum does not match.
212
+ *
213
+ * Same contract as the zip path and the same ordering — verify, then unpack —
214
+ * because an unexpected archive must fail before any of its contents reach a
215
+ * directory that is on PATH. It exists because several tools worth pinning
216
+ * publish no zip for Linux: gh ships .deb, .rpm and .tar.gz and nothing else.
217
+ * @param {object} tool Manifest entry.
218
+ * @param {string} binDir Directory to install into.
219
+ */
220
+ function installReleaseTar(tool, binDir) {
221
+ const temporary = join(binDir, `.${tool.name}-download`);
222
+ mkdirSync(temporary, { recursive: true });
223
+ try {
224
+ const archive = join(temporary, "download.tar.gz");
225
+ boundedChildOutput("curl", ["-fsSL", tool.url, "-o", archive], {
226
+ stdio: "inherit",
227
+ timeout: SETUP_OPERATION_BUDGET_MS,
228
+ });
229
+ verifyChecksum(archive, tool.sha256, tool.name);
230
+ boundedChildOutput("tar", ["-xzf", archive, "-C", temporary], {
231
+ stdio: "inherit",
232
+ timeout: SETUP_OPERATION_BUDGET_MS,
233
+ });
234
+ // Release tarballs usually nest under a versioned directory, so `binary` is
235
+ // a path within the archive rather than a bare name.
236
+ const binary = join(temporary, tool.binary ?? tool.name);
237
+ boundedChildOutput(
238
+ "install",
239
+ ["-m", "0755", binary, join(binDir, tool.name)],
240
+ {
241
+ stdio: "inherit",
242
+ timeout: SETUP_OPERATION_BUDGET_MS,
243
+ }
244
+ );
245
+ } finally {
246
+ rmSync(temporary, { recursive: true, force: true });
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Where a tree-shaped tool's extracted contents live.
252
+ *
253
+ * Versioned so a re-pin lands beside the old copy rather than on top of it, and
254
+ * outside `binDir` because only the entry point belongs on PATH.
255
+ * @param {object} tool Manifest entry.
256
+ * @param {string} binDir Directory holding the symlink.
257
+ * @returns {string} Absolute prefix for this tool and version.
258
+ */
259
+ export function treePrefix(tool, binDir) {
260
+ return join(binDir, "..", "share", tool.name, String(tool.version));
261
+ }
262
+
263
+ /**
264
+ * Install a pinned archive that is a DIRECTORY, not a single binary.
265
+ *
266
+ * `release-zip` and `release-tar` extract and then copy one file onto PATH,
267
+ * which is right for a static binary and silently wrong for anything that
268
+ * resolves its own siblings at run time. Maestro is the case that forced this:
269
+ *
270
+ * maestro/bin/maestro <- launcher
271
+ * maestro/lib/*.jar <- 100+ MB of classpath
272
+ *
273
+ * and the launcher computes `CLASSPATH=$APP_HOME/lib/*` where `APP_HOME` is the
274
+ * parent of wherever the script itself sits. Copy that launcher to
275
+ * `~/.local/bin` and APP_HOME becomes `~/.local`, so the classpath resolves to
276
+ * an empty directory. The install reports success and the tool fails at first
277
+ * use — the failure this manifest exists to turn into a loud setup error.
278
+ *
279
+ * So the whole tree is extracted and the entry point is SYMLINKED. A symlink,
280
+ * not a copy, precisely so that resolution walks back into the extracted tree.
281
+ * @param {object} tool Manifest entry.
282
+ * @param {string} binDir Directory to link the entry point into.
283
+ */
284
+ function installReleaseTree(tool, binDir) {
285
+ const temporary = join(binDir, `.${tool.name}-download`);
286
+ const prefix = treePrefix(tool, binDir);
287
+ mkdirSync(temporary, { recursive: true });
288
+ try {
289
+ const archive = join(temporary, "download.archive");
290
+ boundedChildOutput("curl", ["-fsSL", tool.url, "-o", archive], {
291
+ stdio: "inherit",
292
+ timeout: SETUP_OPERATION_BUDGET_MS,
293
+ });
294
+ // Verify before unpacking, as everywhere else here: an unexpected archive
295
+ // must fail before any of its contents reach the filesystem.
296
+ verifyChecksum(archive, tool.sha256, tool.name);
297
+
298
+ // Replace rather than layer. Extracting over a previous version leaves both
299
+ // sets of jars on the classpath, which fails in a way that looks like a
300
+ // version bug rather than a stale install.
301
+ rmSync(prefix, { recursive: true, force: true });
302
+ mkdirSync(prefix, { recursive: true });
303
+ if (tool.url.endsWith(".zip")) {
304
+ boundedChildOutput("unzip", ["-q", "-o", archive, "-d", prefix], {
305
+ stdio: "inherit",
306
+ timeout: SETUP_OPERATION_BUDGET_MS,
307
+ });
308
+ } else {
309
+ boundedChildOutput("tar", ["-xzf", archive, "-C", prefix], {
310
+ stdio: "inherit",
311
+ timeout: SETUP_OPERATION_BUDGET_MS,
312
+ });
313
+ }
314
+
315
+ const entry = join(prefix, tool.binary);
316
+ if (!existsSync(entry)) {
317
+ throw new Error(
318
+ `${tool.name}: "binary" points at ${tool.binary}, which is not in the ` +
319
+ `archive.\nList the archive and use the path to the entry point ` +
320
+ `inside it, such as "maestro/bin/maestro".`
321
+ );
322
+ }
323
+ chmodSync(entry, 0o755);
324
+
325
+ // A WRAPPER, not a symlink. Both keep the tree intact, but a symlink only
326
+ // works for launchers that resolve symlinks before computing their own
327
+ // location — gradle's template does, and plenty of others do not. One that
328
+ // does not sees the link's directory as its home and looks for its
329
+ // classpath beside the link instead of beside itself, which is the same
330
+ // broken install this kind exists to prevent, reintroduced for a subset of
331
+ // tools. Exec'ing the absolute path removes the assumption entirely.
332
+ const shim = join(binDir, tool.name);
333
+ rmSync(shim, { force: true });
334
+ writeFileSync(shim, `#!/bin/sh\nexec ${JSON.stringify(entry)} "$@"\n`);
335
+ chmodSync(shim, 0o755);
336
+ } finally {
337
+ rmSync(temporary, { recursive: true, force: true });
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Install a pinned artifact that IS the binary, with no archive around it.
343
+ *
344
+ * Several tools worth pinning publish a raw executable rather than an archive —
345
+ * jq ships `jq-linux-amd64` and a `sha256sum.txt`, which is ideal material for a
346
+ * checksummed pin and fits none of the archive kinds.
347
+ *
348
+ * Without this the only entry that passes `assertPinned` is `release-zip`, which
349
+ * then fails at install when `unzip` is handed a binary. A manifest entry that
350
+ * validates and cannot install is worse than one that is rejected outright: the
351
+ * error arrives during provisioning rather than during review.
352
+ *
353
+ * There is no `binary` field here, deliberately — the download is the binary,
354
+ * so there is nothing inside it to name.
355
+ * @param {object} tool Manifest entry.
356
+ * @param {string} binDir Directory to install into.
357
+ */
358
+ function installReleaseBinary(tool, binDir) {
359
+ const temporary = join(binDir, `.${tool.name}-download`);
360
+ mkdirSync(temporary, { recursive: true });
361
+ try {
362
+ const artifact = join(temporary, tool.name);
363
+ boundedChildOutput("curl", ["-fsSL", tool.url, "-o", artifact], {
364
+ stdio: "inherit",
365
+ timeout: SETUP_OPERATION_BUDGET_MS,
366
+ });
367
+ // Verify before it reaches a directory on PATH. For this kind that ordering
368
+ // matters more than for the archives: the downloaded file is directly
369
+ // executable, so a wrong artifact placed first is a wrong artifact that can
370
+ // be run.
371
+ verifyChecksum(artifact, tool.sha256, tool.name);
372
+ boundedChildOutput(
373
+ "install",
374
+ ["-m", "0755", artifact, join(binDir, tool.name)],
375
+ {
376
+ stdio: "inherit",
377
+ timeout: SETUP_OPERATION_BUDGET_MS,
378
+ }
379
+ );
380
+ } finally {
381
+ rmSync(temporary, { recursive: true, force: true });
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Install a pinned global npm package.
387
+ * @param {object} tool Manifest entry.
388
+ */
389
+ function installNpmGlobal(tool) {
390
+ boundedChildOutput(
391
+ "npm",
392
+ ["install", "--global", `${tool.package}@${tool.version}`],
393
+ {
394
+ stdio: "inherit",
395
+ timeout: SETUP_OPERATION_BUDGET_MS,
396
+ }
397
+ );
398
+ }
399
+
400
+ /**
401
+ * Execute one install decision.
402
+ *
403
+ * Exported because the local-environment flow installs from the same manifest
404
+ * with the same pins and the same checksum refusal. A second installer would be
405
+ * a second thing to keep honest, and the one people run least would rot.
406
+ * @param {object} tool Manifest entry, already resolved to this platform.
407
+ * @param {string} binDir Directory for downloaded binaries.
408
+ */
409
+ export function installTool(tool, binDir) {
410
+ assertPinned(tool);
411
+ if (tool.install === "release-zip") installReleaseZip(tool, binDir);
412
+ else if (tool.install === "release-tar") installReleaseTar(tool, binDir);
413
+ else if (tool.install === "release-tree") installReleaseTree(tool, binDir);
414
+ else if (tool.install === "release-binary")
415
+ installReleaseBinary(tool, binDir);
416
+ else installNpmGlobal(tool);
417
+ }
418
+
419
+ /**
420
+ * Create the directory installs land in, and make sure PATH will find it.
421
+ *
422
+ * Installing a binary somewhere nothing looks is the same as not installing it.
423
+ * `~/.local/bin` is on PATH by default on a developer workstation and is NOT on
424
+ * a minimal container, so the toolchain step reported `install bws`, the file
425
+ * landed at ~/.local/bin/bws mode 755, and the very next step died with
426
+ * `spawnSync bws ENOENT`.
427
+ *
428
+ * This was invisible in every local test because a workstation shell already
429
+ * exports the directory — the environment doing the hiding was the one used to
430
+ * verify the fix.
431
+ *
432
+ * Prepended, not appended: a pinned-and-checksummed binary must win over
433
+ * whatever an image happens to ship under the same name.
434
+ * @returns {string} The directory installs are written to.
435
+ */
436
+ export function ensureBinDir() {
437
+ const binDir = join(process.env.HOME ?? "", ".local", "bin");
438
+ mkdirSync(binDir, { recursive: true, mode: 0o755 });
439
+ if (!pathContains(binDir)) {
440
+ process.env.PATH = `${binDir}${delimiter}${process.env.PATH ?? ""}`;
441
+ }
442
+ return binDir;
443
+ }
444
+
445
+ /**
446
+ * Apply the toolchain plan, reporting every decision.
447
+ * @param {object} tools Manifest.
448
+ * @param {boolean} dryRun Whether to plan only.
449
+ * @returns {Array<object>} The plan that was applied.
450
+ */
451
+ function applyToolchain(tools, dryRun, options = {}) {
452
+ const { surface = "remote", consent = true } = options;
453
+ const plan = planToolchain(tools, probe, surface);
454
+ const blocked = plan.filter(
455
+ p => p.action === "missing" || p.action === "invalid"
456
+ );
457
+ if (blocked.length) {
458
+ throw new Error(blocked.map(p => p.reason).join("\n\n"));
459
+ }
460
+
461
+ const binDir = ensureBinDir();
462
+ const byName = new Map((tools.install ?? []).map(t => [t.name, t]));
463
+
464
+ // Installing is a different act on a laptop than in a container. A container
465
+ // is disposable and provisioning it silently is the whole point; a developer
466
+ // machine belongs to a person, and putting pinned binaries in their
467
+ // ~/.local/bin without being asked is not ours to decide. So the same
468
+ // manifest drives both, and only the consent differs.
469
+ //
470
+ // Reporting still happens either way — knowing the machine diverges from what
471
+ // the project declares is most of the value, and is what nothing did before.
472
+ if (!consent) {
473
+ const missing = plan.filter(step => step.action === "install");
474
+ if (missing.length === 0) {
475
+ console.log(" every declared tool is already present");
476
+ return plan;
477
+ }
478
+ console.log(
479
+ ` ${missing.length} declared tool(s) are missing or below their pin:`
480
+ );
481
+ for (const step of missing) {
482
+ const tool = byName.get(step.name);
483
+ console.log(` ${step.name}${tool?.version ? ` ${tool.version}` : ""}`);
484
+ }
485
+ console.log(
486
+ "\n Not installing without confirmation. Re-run with --install-tools\n" +
487
+ " to provision them, or install them yourself."
488
+ );
489
+ return plan;
490
+ }
491
+
492
+ for (const step of plan) {
493
+ console.log(` ${step.action.padEnd(8)} ${step.reason}`);
494
+ // The planner already resolved this entry to the running platform, so use
495
+ // what it decided rather than looking the raw entry up again. `byName` is
496
+ // the pre-platform shape and would hand the installer a url that belongs to
497
+ // whichever platform happened to be written first.
498
+ if (step.action === "install" && !dryRun)
499
+ installTool(step.tool ?? byName.get(step.name), binDir);
500
+ }
501
+ return plan;
502
+ }
503
+
504
+ /**
505
+ * Whether a directory is already on PATH.
506
+ *
507
+ * Compared entry by entry rather than by substring: a substring test would
508
+ * consider `/root/.local/bin` present because `/root/.local/bin/extra` is, and
509
+ * would then skip the prepend that makes the tool findable.
510
+ * @param {string} dir Directory to look for.
511
+ * @returns {boolean} True when PATH already contains exactly that entry.
512
+ */
513
+ export function pathContains(dir, path = process.env.PATH ?? "") {
514
+ return path.split(delimiter).filter(Boolean).includes(dir);
515
+ }
516
+
517
+ /**
518
+ * Run the project's own setup hook when it has one.
519
+ *
520
+ * A declarative manifest will never cover everything, and without an escape
521
+ * hatch a project's only option is to fork Lisa's script — at which point it
522
+ * stops receiving any of the fixes.
523
+ * @param {string|null} hook Repository-relative path.
524
+ * @param {boolean} dryRun Whether to plan only.
525
+ */
526
+ function runHook(hook, dryRun) {
527
+ if (!hook) return;
528
+ const path = resolve(process.cwd(), hook);
529
+ if (!existsSync(path)) {
530
+ throw new Error(`remoteEnv.hook points at ${hook}, which does not exist`);
531
+ }
532
+ console.log(`\nProject hook: ${hook}`);
533
+ if (dryRun) return;
534
+ chmodSync(path, 0o755);
535
+ boundedChildOutput("bash", [path], {
536
+ stdio: "inherit",
537
+ timeout: SETUP_OPERATION_BUDGET_MS,
538
+ });
539
+ }
540
+
541
+ /** Phases this runner can execute, in the order they must happen. */
542
+ const PHASES = ["toolchain", "secrets", "hook"];
543
+
544
+ /** Where a host project keeps the scripts its remote environment invokes. */
545
+ export const INSTALL_DIR = join("scripts", "lisa-remote-env");
546
+
547
+ /** Assets a host project needs on disk before any remote session can start. */
548
+ const INSTALLABLE = [
549
+ "setup.sh",
550
+ "session-start.sh",
551
+ "materialized-env-authority.mjs",
552
+ ];
553
+
554
+ /**
555
+ * Copy this skill's assets into the host project that will run them.
556
+ *
557
+ * These live in the skill, but the paths that reference them are *repository*
558
+ * paths: a vendor's setup field says `bash scripts/lisa-remote-env/setup.sh`,
559
+ * and a session-start hook is only committed if it is a real file in the repo.
560
+ * Nothing put them there, so every reference resolved to a path that did not
561
+ * exist — the environment came up, ran a missing script, exited non-zero, and
562
+ * the session failed to start with no indication that a setup step was skipped.
563
+ *
564
+ * Copied rather than symlinked or generated: the file must survive in a fresh
565
+ * clone on a container that has never seen the plugin, which is the whole
566
+ * reason the entrypoint is thin enough to copy in the first place.
567
+ * @param {string} [cwd] Repository root.
568
+ * @returns {Array<{name: string, action: string}>} What was written.
569
+ */
570
+ export function installAssets(cwd = process.cwd()) {
571
+ const destination = join(cwd, INSTALL_DIR);
572
+ mkdirSync(destination, { recursive: true, mode: 0o755 });
573
+ return INSTALLABLE.map(name => {
574
+ const source = resolve(HERE, "..", "assets", name);
575
+ if (!existsSync(source)) {
576
+ throw new Error(`asset ${name} is missing from this skill install`);
577
+ }
578
+ const target = join(destination, name);
579
+ const desired = readFileSync(source, "utf8");
580
+ const unchanged =
581
+ existsSync(target) && readFileSync(target, "utf8") === desired;
582
+ if (!unchanged) writeFileSync(target, desired, { mode: 0o755 });
583
+ chmodSync(target, 0o755);
584
+ return { name, action: unchanged ? "current" : "written" };
585
+ });
586
+ }
587
+
588
+ /**
589
+ * The setup field, identical for every project and every surface.
590
+ *
591
+ * Names neither the repository nor its package manager, and deliberately not
592
+ * `$HOME` either: the surfaces disagree about where the field runs. Codex Cloud
593
+ * runs it inside the checkout, Claude Code web runs it from `$HOME` with the
594
+ * checkout one level down, and on Codex Cloud the checkout is not under `$HOME`
595
+ * at all — so a `$HOME` glob matched nothing there and bash was handed a path
596
+ * still containing a literal asterisk.
597
+ *
598
+ * Candidates are therefore tried relative to cwd first, then under `$HOME` and
599
+ * `/workspace` — the container roots the two surfaces actually use — because
600
+ * cwd is not reliably either of them. A Claude environment reported `$HOME` as
601
+ * `/root` with no checkout beneath it, which a cwd-only search cannot reach.
602
+ *
603
+ * EVERY match is prepared rather than the first. A Claude Code web environment
604
+ * can hold more than one checkout, so stopping at the first hit would prepare
605
+ * whichever repository sorts first and silently ignore the rest — arbitrary
606
+ * rather than merely limited. Each script anchors itself on its own repository
607
+ * root and each project materializes under its own `secrets.namespace`, so
608
+ * preparing several is well defined rather than a collision. Matches are
609
+ * deduplicated by resolved directory, since the candidate globs overlap
610
+ * whenever cwd happens to be one of the roots.
611
+ *
612
+ * The status is the first failure and every checkout is still attempted: one
613
+ * broken repository must neither hide the others nor report success.
614
+ *
615
+ * Preparing nothing has two causes, and they are not the same failure:
616
+ *
617
+ * - A checkout is present but carries no entrypoint. That is a
618
+ * misconfiguration, and a `for` loop over a glob that matches nothing exits
619
+ * 0 on its own — the quiet success this has always guarded against. It
620
+ * still fails loudly, printing the layout it found rather than only the
621
+ * path it wanted.
622
+ * - There is NO checkout. A Claude Tag channel session runs as the
623
+ * organization with no user account, and a repository does not enter the
624
+ * session until a request names one, so this is an ordinary state rather
625
+ * than an error. Failing here would be severe: a setup script that exits
626
+ * non-zero stops the session from starting at all, so the old blanket
627
+ * `exit 1` would have killed every repo-less channel session.
628
+ *
629
+ * With no checkout, `LISA_SECRETS_NAMESPACE` decides whether there is anything
630
+ * to do: it names the tenant, which is the one value no default can supply.
631
+ * Given it, the machine itself is prepared — tools and credentials, no repo
632
+ * required. Without it there is nothing to prepare and nothing to fail about.
633
+ *
634
+ * Those two preparation steps report their failures and do not propagate them,
635
+ * which is deliberate and is NOT the same as hiding them. A setup script that
636
+ * exits non-zero stops the session from starting at all, so propagating turns
637
+ * "no credentials this session" into "no session" — strictly worse, and the
638
+ * exact regression that killed every repo-less channel session once already.
639
+ * What was wrong before was the silence: a bare `|| true` let a container come
640
+ * up with a bootstrap token and nothing able to use it, looking identical to
641
+ * success. Each phase's status is now captured and named on stderr, which the
642
+ * vendor surfaces, so the session still starts and the operator can see which
643
+ * half is missing.
644
+ *
645
+ * Failure prints the layout it found rather than only the path it wanted. This
646
+ * field lives in a vendor settings box with a slow edit-and-retry loop, and a
647
+ * miss that reports nothing costs a whole round trip to learn one fact; the
648
+ * listing means the next message names the layout instead of repeating it.
649
+ *
650
+ * A field that named the repository and package manager was a string a human
651
+ * had to get right, in a settings box with no review, no version history and no
652
+ * test — and the logic it encoded belongs in a file that has all three.
653
+ */
654
+ /**
655
+ * The exact Lisa the emitted field runs, pinned to the version that emitted it.
656
+ *
657
+ * `@latest` in a field that executes with `npx -y` is remote code execution
658
+ * against a moving target: whatever is newest when the session starts, run as
659
+ * root before Claude launches. That contradicts the rule this project applies
660
+ * to everything else — `assertPinned` refuses a tool whose version is not fixed
661
+ * and checksummed, and it would be strange to hold third-party binaries to a
662
+ * standard Lisa exempts itself from.
663
+ *
664
+ * Pinning to the emitting version keeps the field honest without freezing it:
665
+ * regenerating the field after an upgrade produces a new pin, which is the same
666
+ * "a version bump moves in one reviewed commit" contract as the toolchain.
667
+ * Falling back to `latest` only when the version cannot be read keeps a broken
668
+ * install from turning into an unrunnable field.
669
+ */
670
+ const SELF_SPEC = (() => {
671
+ try {
672
+ const pkg = JSON.parse(
673
+ readFileSync(resolve(HERE, "../../../../../../package.json"), "utf8")
674
+ );
675
+ return pkg.version ? `@codyswann/lisa@${pkg.version}` : "@codyswann/lisa";
676
+ } catch {
677
+ return "@codyswann/lisa";
678
+ }
679
+ })();
680
+
681
+ /**
682
+ * Every directory the catalogue installs into, as a `$HOME`-relative PATH.
683
+ *
684
+ * The field used to hardcode `~/.local/bin`, which is only where the *pinned*
685
+ * binaries land. A vendor script may install anywhere it likes — SonarQube's
686
+ * puts `sonar` under `~/.local/share/sonarqube-cli/bin` — so the tool installed
687
+ * successfully and then could not be found, which reads to an operator exactly
688
+ * like an install that never happened.
689
+ *
690
+ * The bootstrap already writes these into the shell rc files, and on this
691
+ * surface that is inert: a cloud container's tool shell is not a login shell
692
+ * and reads no profile. Exporting them here is what makes the install visible
693
+ * to the session that asked for it.
694
+ *
695
+ * Derived from the catalogue rather than restated, so a tool added with a new
696
+ * `binDir` reaches this field without anyone remembering to come back here.
697
+ * `$HOME` stays a shell variable — the field runs as a different user, in a
698
+ * different container, than whatever machine emitted it.
699
+ */
700
+ const FIELD_PATH = pathDirs("$HOME").join(":");
701
+
702
+ export const SETUP_FIELD =
703
+ 'n=0; rc=0; seen=""; ' +
704
+ "for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh " +
705
+ '"$HOME"/scripts/lisa-remote-env/setup.sh "$HOME"/*/scripts/lisa-remote-env/setup.sh ' +
706
+ "/workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; " +
707
+ 'do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); ' +
708
+ 'case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; ' +
709
+ // `rc` keeps the FIRST failure, which is what the contract below promises.
710
+ //
711
+ // A bare `rc=$?` keeps the last one, so with two broken checkouts the
712
+ // operator is shown the second and the first is lost — the precise hiding the
713
+ // documented behaviour exists to prevent. A success never overwrites, because
714
+ // `||` only fires on failure, so the bug was confined to the multiple-failure
715
+ // case that the rule was written for.
716
+ 'n=$((n+1)); bash "$f" || { s=$?; [ "$rc" -ne 0 ] || rc=$s; }; done; ' +
717
+ '[ "$n" -gt 0 ] && exit "$rc"; ' +
718
+ 'g=0; for c in ./.git ./*/.git "$HOME"/*/.git /workspace/*/.git; ' +
719
+ 'do [ -e "$c" ] && g=1; done; ' +
720
+ 'if [ "$g" -eq 1 ]; then ' +
721
+ 'echo "lisa-remote-env entrypoint not found, but a checkout is present. ' +
722
+ 'PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; fi; ' +
723
+ 'ten="${LISA_TENANT:-${LISA_SECRETS_NAMESPACE:-}}"; ' +
724
+ 'if [ -n "$ten" ]; then ' +
725
+ 'echo "No checkout; preparing tools and credentials for $ten."; ' +
726
+ "tw=0; tt=0; ts=0; tp=0; " +
727
+ // `--agents=none` is load-bearing, not tidiness.
728
+ //
729
+ // The bootstrap's default is every coding agent it knows: claude, codex,
730
+ // cursor-agent, opencode, agy, copilot. `agy` alone is ~193MB and `gh` ~55MB,
731
+ // and a setup script has roughly five minutes before the vendor kills it and
732
+ // reports "Session failed to start: Setup script failed" — which is exactly
733
+ // what a Claude Tag channel did, killing the session outright rather than
734
+ // starting it without tools.
735
+ //
736
+ // A remote container needs the provider CLI and the tools; it does not need
737
+ // agents, because it IS one. No AGENTS entry is named "none", so the filter
738
+ // selects nothing — pinned by a test, since it reads like a magic word.
739
+ // Two passes, credentials in between, because the vault is what knows which
740
+ // CLIs this container needs — and it cannot be asked until it has been read.
741
+ //
742
+ // The first pass installs only the provider CLI (`--tools=none`, the same
743
+ // idiom as `--agents=none`), because that is all materialization requires.
744
+ `npx -y ${SELF_SPEC} workstation --install --agents=none --tools=none ` +
745
+ '--provider="${LISA_PROVIDER:-${LISA_SECRETS_PROVIDER:-bitwarden}}" || tw=$?; ' +
746
+ // Exported BEFORE the secrets phase, not after: the toolchain installs into
747
+ // ~/.local/bin, and materialization spawns the provider CLI by name. Ordered
748
+ // the other way it gets ENOENT on a binary that is sitting right there.
749
+ `export PATH="${FIELD_PATH}:$PATH"; ` +
750
+ `npx -y ${SELF_SPEC} remote-env --phase=secrets || ts=$?; ` +
751
+ // Now the notes can answer it. A vault that names nothing yields an empty
752
+ // string, and `--tools=` with an empty value selects the whole catalogue —
753
+ // so annotating the notes NARROWS what gets installed, and not annotating
754
+ // them leaves this field behaving exactly as it did before.
755
+ //
756
+ // stderr is dropped and only the last line kept: npx narrates to stderr, and
757
+ // this is a command substitution whose output becomes a flag value.
758
+ //
759
+ // The status is captured BEFORE the pipe, because `tail` exits 0 whatever
760
+ // happened upstream. A failed lookup still falls through to the whole
761
+ // catalogue — installing too much is the safe direction, and it is what this
762
+ // field did before the vault had any say — but it says so, rather than being
763
+ // indistinguishable from a vault that simply names nothing.
764
+ `w=$(npx -y ${SELF_SPEC} remote-env --print-tools 2>/dev/null) || tp=$?; ` +
765
+ 'w=$(printf "%s\\n" "$w" | tail -1); ' +
766
+ '[ "$tp" -eq 0 ] || echo "SETUP INCOMPLETE: could not read the tool list ' +
767
+ 'from the vault (exit $tp). Installing the full catalogue." >&2; ' +
768
+ `npx -y ${SELF_SPEC} workstation --install --agents=none --tools="$w" ` +
769
+ '--provider="${LISA_PROVIDER:-${LISA_SECRETS_PROVIDER:-bitwarden}}" || tt=$?; ' +
770
+ '[ "$tw" -eq 0 ] && [ "$tt" -eq 0 ] || echo "SETUP INCOMPLETE: tool install ' +
771
+ 'failed (exit $tw/$tt). The session will start WITHOUT the pinned tools." >&2; ' +
772
+ '[ "$ts" -eq 0 ] || echo "SETUP INCOMPLETE: secrets did not materialize ' +
773
+ '(exit $ts). The session will start WITHOUT credentials." >&2; ' +
774
+ 'else echo "No checkout and no tenant configured; nothing to prepare."; fi; ' +
775
+ "exit 0";
776
+
777
+ /**
778
+ * Register the session-start hook at USER scope, so it fires wherever the
779
+ * session opens.
780
+ *
781
+ * The committed `.claude/settings.json` hook is *project*-scoped: it loads only
782
+ * when Claude Code's project directory is that repository. A cloud session does
783
+ * not reliably start there. With several repositories it starts in the shared
784
+ * parent, which is not a git repository at all; even with one, the checkout sits
785
+ * at `$HOME/<repo>` while the session may open at `$HOME`. In those sessions no
786
+ * project settings load, the hook never registers, and nothing materializes —
787
+ * the failure that `materializeAt: "both"` was introduced to paper over, and
788
+ * which it could not fix, because the setup phase cannot see the bootstrap
789
+ * credential in the first place.
790
+ *
791
+ * A user-scoped hook has neither problem: `~/.claude/settings.json` is read for
792
+ * every session regardless of project directory, and it runs in-session where
793
+ * the vendor's configured variables ARE present.
794
+ *
795
+ * Only ever on a remote surface. This writes machine state in an ephemeral
796
+ * container, which is fine there and would be an intrusion on a developer's
797
+ * laptop, where the project hook already works and the user owns that file.
798
+ *
799
+ * Merged, never clobbered, and idempotent on the exact command — an existing
800
+ * user settings file may carry hooks this knows nothing about.
801
+ * @param {string} repoRoot Absolute path to the checkout.
802
+ * @param {object} options Home directory, dry-run flag, and file seams.
803
+ * @returns {{action: string, path: string, reason?: string}} What was done.
804
+ */
805
+ export function installUserSessionHook(repoRoot, options = {}) {
806
+ const {
807
+ home = process.env.HOME || homedir(),
808
+ dryRun = false,
809
+ exists = existsSync,
810
+ read = readFileSync,
811
+ write = writeFileSync,
812
+ mkdir = mkdirSync,
813
+ } = options;
814
+
815
+ const script = join(repoRoot, "scripts/lisa-remote-env/session-start.sh");
816
+ if (!exists(script)) {
817
+ return { action: "skipped", path: script, reason: "no session-start.sh" };
818
+ }
819
+
820
+ const dir = join(home, ".claude");
821
+ const settingsPath = join(dir, "settings.json");
822
+ // Settings stores shell source, so the absolute path still needs quoting.
823
+ // Single quotes suppress parameter expansion and command substitution. An
824
+ // embedded quote closes, escapes, and reopens the quoted shell word.
825
+ const command = `bash '${script.replaceAll("'", `'\\''`)}'`;
826
+ const legacyCommand = `bash ${JSON.stringify(script)}`;
827
+
828
+ let settings = {};
829
+ if (exists(settingsPath)) {
830
+ try {
831
+ settings = JSON.parse(String(read(settingsPath, "utf8"))) || {};
832
+ } catch {
833
+ // Refuse rather than overwrite. A settings file that does not parse is
834
+ // still someone's configuration, and replacing it would be the careless
835
+ // destruction this whole function is written to avoid.
836
+ return {
837
+ action: "failed",
838
+ path: settingsPath,
839
+ reason: "existing settings.json is not valid JSON; left untouched",
840
+ };
841
+ }
842
+ }
843
+
844
+ const hooks = settings.hooks ?? {};
845
+ const sessionStart = Array.isArray(hooks.SessionStart)
846
+ ? hooks.SessionStart
847
+ : [];
848
+ let installed = false;
849
+ // Distinct matchers may cover different events. Do not infer regex equivalence.
850
+ const installedMatchers = new Set();
851
+ let migrated = false;
852
+ const normalizedSessionStart = sessionStart.flatMap(entry => {
853
+ if (!Array.isArray(entry?.hooks)) return [entry];
854
+ const normalizedHooks = entry.hooks.flatMap(hook => {
855
+ if (hook?.command !== command && hook?.command !== legacyCommand) {
856
+ return [hook];
857
+ }
858
+ if (installedMatchers.has(entry.matcher)) {
859
+ migrated = true;
860
+ return [];
861
+ }
862
+ installed = true;
863
+ installedMatchers.add(entry.matcher);
864
+ if (hook.command === command) return [hook];
865
+ migrated = true;
866
+ return [{ ...hook, command }];
867
+ });
868
+ return normalizedHooks.length > 0
869
+ ? [{ ...entry, hooks: normalizedHooks }]
870
+ : entry.hooks.length === 0
871
+ ? [entry]
872
+ : [];
873
+ });
874
+
875
+ if (installed) {
876
+ if (!migrated) return { action: "present", path: settingsPath };
877
+ if (dryRun) return { action: "would-migrate", path: settingsPath };
878
+ const updated = {
879
+ ...settings,
880
+ hooks: { ...hooks, SessionStart: normalizedSessionStart },
881
+ };
882
+ mkdir(dir, { recursive: true });
883
+ write(settingsPath, `${JSON.stringify(updated, null, 2)}\n`);
884
+ return { action: "migrated", path: settingsPath };
885
+ }
886
+
887
+ const updated = {
888
+ ...settings,
889
+ hooks: {
890
+ ...hooks,
891
+ SessionStart: [
892
+ ...normalizedSessionStart,
893
+ {
894
+ matcher: "startup|resume",
895
+ hooks: [{ type: "command", command }],
896
+ },
897
+ ],
898
+ },
899
+ };
900
+
901
+ if (dryRun) return { action: "would-register", path: settingsPath };
902
+
903
+ mkdir(dir, { recursive: true });
904
+ write(settingsPath, `${JSON.stringify(updated, null, 2)}\n`);
905
+ return { action: "registered", path: settingsPath };
906
+ }
907
+
908
+ /**
909
+ * The settings block that wires the session-start hook into a repository.
910
+ *
911
+ * Emitted rather than written, because `.claude/settings.json` belongs to the
912
+ * project: it may already carry hooks, and merging someone else's file from
913
+ * here is how a careless tool destroys a configuration it did not understand.
914
+ */
915
+ const SESSION_START_BLOCK = `{
916
+ "hooks": {
917
+ "SessionStart": [
918
+ {
919
+ "matcher": "startup|resume",
920
+ "hooks": [
921
+ {
922
+ "type": "command",
923
+ "command": "bash \\"$CLAUDE_PROJECT_DIR\\"/scripts/lisa-remote-env/session-start.sh"
924
+ }
925
+ ]
926
+ }
927
+ ]
928
+ }
929
+ }`;
930
+
931
+ /**
932
+ * Pin which cloud environment this project's sessions use.
933
+ *
934
+ * `/remote-env` writes `remote.defaultEnvironmentId` into *user* settings, which
935
+ * is one value for the whole machine. That is wrong as soon as a developer has
936
+ * more than one project, because an environment's contents are project-shaped:
937
+ * its setup script is a repository-relative path and its bootstrap is scoped to
938
+ * that project's secrets. Pointing one project's session at another's
939
+ * environment runs a setup script that may not exist there, and a non-zero setup
940
+ * script means the session never starts.
941
+ *
942
+ * Written to `.claude/settings.local.json` for two reasons. It outranks user
943
+ * settings, so the per-project choice wins over the machine-wide default; and it
944
+ * is gitignored, which is correct because an environment belongs to one
945
+ * developer's account and is meaningless in someone else's checkout.
946
+ *
947
+ * Merged rather than replaced — that file commonly holds permission grants a
948
+ * developer has accumulated, and clobbering them to write one key would be a
949
+ * poor trade.
950
+ * @param {string} environmentId Cloud environment identifier.
951
+ * @param {string} [cwd] Repository root.
952
+ * @returns {string} The settings path written.
953
+ */
954
+ export function pinEnvironment(environmentId, cwd = process.cwd()) {
955
+ const path = join(cwd, ".claude", "settings.local.json");
956
+ mkdirSync(dirname(path), { recursive: true });
957
+ const existing = existsSync(path)
958
+ ? JSON.parse(readFileSync(path, "utf8"))
959
+ : {};
960
+ const merged = {
961
+ ...existing,
962
+ remote: { ...existing.remote, defaultEnvironmentId: environmentId },
963
+ };
964
+ writeFileSync(path, `${JSON.stringify(merged, null, 2)}\n`);
965
+ return path;
966
+ }
967
+
968
+ /**
969
+ * The lines an operator puts above the field, for a session with no checkout.
970
+ *
971
+ * Emitted as shell that runs, not as a template with holes: every hole is a
972
+ * hand substitution in a settings box with no review, which is the failure this
973
+ * whole file exists to prevent. So a provider that cannot be bootstrapped by an
974
+ * environment variable gets a sentence explaining that, not an `export` of a
975
+ * placeholder — a line that would be a syntax error if pasted.
976
+ *
977
+ * `LISA_PROVIDER` is exported even though the field defaults it, because the
978
+ * field's default is Bitwarden. A Doppler tenant that omits it gets `bws`
979
+ * installed and its own CLI missing, having configured everything correctly.
980
+ * @param {object} options Resolved identity and display strings.
981
+ * @returns {string[]} Lines to include in the emitted guidance.
982
+ */
983
+ function repoLessExports({ namespace, key, provider, bootstrapKey, tenant }) {
984
+ // Two different reasons the key can be missing, and telling them apart is the
985
+ // whole value of the message. "Nothing was named" is the operator's to fix by
986
+ // re-running; "this provider has no such variable" is not fixable at all and
987
+ // must not read as an omission.
988
+ if (!tenant) {
989
+ return [
990
+ " # Re-run with --tenant=<name> and these are filled in for you.",
991
+ ` export LISA_TENANT=${namespace}`,
992
+ " export <bootstrap variable>='<read from your credential manager>'",
993
+ " export LISA_SECRETS_SURFACE=claude-web",
994
+ ];
995
+ }
996
+ if (!bootstrapKey) {
997
+ return [
998
+ ` # ${provider} has no environment-variable bootstrap, so there is`,
999
+ " # nothing to export for it. Authenticate it the way its own CLI",
1000
+ " # expects, then set the three below.",
1001
+ ` export LISA_TENANT=${namespace}`,
1002
+ ` export LISA_PROVIDER=${provider}`,
1003
+ " export LISA_SECRETS_SURFACE=claude-web",
1004
+ ];
1005
+ }
1006
+ return [
1007
+ ` export LISA_TENANT=${namespace}`,
1008
+ ` export ${key}='<the same value as above>'`,
1009
+ ...(provider ? [` export LISA_PROVIDER=${provider}`] : []),
1010
+ " export LISA_SECRETS_SURFACE=claude-web",
1011
+ ];
1012
+ }
1013
+
1014
+ /**
1015
+ * Produce the configuration a human pastes to provision a Claude cloud surface.
1016
+ *
1017
+ * This surface has no API tier and no console tier to fall back to: a Claude
1018
+ * cloud environment is configured only in the environment dialog, which has no
1019
+ * settings page, no direct URL, and no endpoint. Emit is not a degraded option
1020
+ * here, it is the only one — so the read-back in `verify-remote-env.mjs` is
1021
+ * what makes the result trustworthy, exactly as it would be at any other tier.
1022
+ * @param {{bootstrapKey: string|null, tenant?: string|null, provider?: string}} options Project details.
1023
+ * @returns {string} Text to show the operator.
1024
+ */
1025
+ export function emitClaudeWeb({
1026
+ bootstrapKey,
1027
+ tenant = null,
1028
+ provider = null,
1029
+ }) {
1030
+ const key = bootstrapKey ?? "<not resolved>";
1031
+ const namespace = tenant ?? "<your namespace>";
1032
+ return [
1033
+ "Provisioning tier: EMIT — and for this surface that is the only tier.",
1034
+ " A Claude cloud environment is account-scoped configuration edited in the",
1035
+ " environment selector at claude.ai/code. There is no settings page, no",
1036
+ " direct URL and no API, so nothing here can provision it for you.",
1037
+ "",
1038
+ "Paste into the environment dialog",
1039
+ "---------------------------------",
1040
+ " Network access: Custom plus your package registries, GitHub, cloud SDK",
1041
+ " hosts, and the bootstrap credential manager API",
1042
+ "",
1043
+ " Environment variables:",
1044
+ ` ${key}=<read this from your credential manager>`,
1045
+ "",
1046
+ " Only the bootstrap belongs in this box. Values here are stored as",
1047
+ " plain text and are readable by anyone who uses the environment — on an",
1048
+ " organization-shared environment that is every member of the org. Every",
1049
+ " other credential is materialized by the session-start hook below, which",
1050
+ " is why exactly one value needs to live here.",
1051
+ "",
1052
+ " Setup script:",
1053
+ "",
1054
+ " A setup script sees NONE of the variables configured above — it runs",
1055
+ " while the image is being built, before the environment exists. So a",
1056
+ " session with no checkout has to be told its tenant inside the script",
1057
+ " itself, or the field finds nothing to prepare and exits 0 looking like",
1058
+ " success. Put the lines below FIRST, then the field after them:",
1059
+ "",
1060
+ ...repoLessExports({ namespace, key, provider, bootstrapKey, tenant }),
1061
+ "",
1062
+ " LISA_SECRETS_SURFACE is set explicitly rather than left to detection:",
1063
+ " detection keys off CLAUDE_CODE_REMOTE, and a surface that does not set",
1064
+ " it falls through to `local`, which writes no credentials to disk at all.",
1065
+ "",
1066
+ ` ${SETUP_FIELD}`,
1067
+ "",
1068
+ " This line is identical for every project and every surface — nothing",
1069
+ " in it names this repository or its package manager. The surfaces",
1070
+ " disagree about where the field runs: Codex Cloud runs it inside the",
1071
+ " checkout, Claude Code web runs it from $HOME with the checkout one",
1072
+ " level down. So it tries the checkout itself and then one level down,",
1073
+ " relative to cwd, and only then the same pair under $HOME and",
1074
+ " /workspace, for the case where cwd is neither. The script anchors",
1075
+ " itself on whichever matched and installs dependencies using whichever",
1076
+ " lockfile the project actually commits.",
1077
+ "",
1078
+ " Keeping it generic is the point. A field that named the repository and",
1079
+ " the package manager was a string a human had to get right, in a box",
1080
+ " with no review, no version history and no test.",
1081
+ "",
1082
+ "Commit to the repository",
1083
+ "------------------------",
1084
+ " scripts/lisa-remote-env/session-start.sh (from this skill's assets)",
1085
+ "",
1086
+ " .claude/settings.json — merge this into any hooks already there:",
1087
+ SESSION_START_BLOCK.split("\n")
1088
+ .map(line => ` ${line}`)
1089
+ .join("\n"),
1090
+ "",
1091
+ " The setup script is skipped whenever a cached environment exists, so",
1092
+ " secrets are materialized from this hook instead. It runs every session,",
1093
+ " including a resumed one, which is what keeps a rotated value current.",
1094
+ "",
1095
+ "Worth knowing about this base image",
1096
+ "-----------------------------------",
1097
+ " The GitHub CLI is not pre-installed. If this project's flows shell out to",
1098
+ " `gh`, add it to remoteEnv.tools.install with a pinned version and",
1099
+ " checksum, the same as any other tool.",
1100
+ "",
1101
+ " A credential handled by the GitHub proxy reads as the literal string",
1102
+ ' "proxy-injected" inside the session. Tools that authenticate through the',
1103
+ " proxy work; a script that reads the variable itself gets the placeholder.",
1104
+ "",
1105
+ "Then prove it",
1106
+ "-------------",
1107
+ " Start a session and run the read-back. Whatever provisioned an",
1108
+ " environment, the same verify is what makes it trustworthy:",
1109
+ " node node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs",
1110
+ ].join("\n");
1111
+ }
1112
+
1113
+ /**
1114
+ * Emit the configuration for a surface Lisa cannot reach.
1115
+ *
1116
+ * Dispatch rather than four call sites, so `environment` names a surface and
1117
+ * nothing downstream has to know which of them has an API and which does not.
1118
+ * @param {string} target Surface name.
1119
+ * @param {object} identity Resolved tenant, provider and bootstrap key.
1120
+ * @returns {string} Text to show the operator.
1121
+ */
1122
+ export function emitFor(target, identity) {
1123
+ if (target === "claude-web") return emitClaudeWeb(identity);
1124
+ if (target === "codex-cloud") return emitCodexCloud(identity);
1125
+ if (target === "container") return emitContainer(identity);
1126
+ throw new Error(`no emit template for surface "${target}".`);
1127
+ }
1128
+
1129
+ /**
1130
+ * Produce the configuration a human pastes into a Codex Cloud environment.
1131
+ *
1132
+ * Differs from Claude's in three ways that are properties of the vendor rather
1133
+ * than of Lisa, and each has cost someone a debugging session.
1134
+ * @param {{bootstrapKey: string|null, tenant?: string|null, provider?: string}} options Project details.
1135
+ * @returns {string} Text to show the operator.
1136
+ */
1137
+ export function emitCodexCloud({
1138
+ bootstrapKey,
1139
+ tenant = null,
1140
+ provider = null,
1141
+ }) {
1142
+ const key = bootstrapKey ?? "<not resolved>";
1143
+ const namespace = tenant ?? "<your namespace>";
1144
+ return [
1145
+ "Provisioning tier: EMIT — Codex Cloud exposes no environment API.",
1146
+ " `codex cloud` offers exec, status and list; nothing that writes an",
1147
+ " environment. So this is text for the settings page.",
1148
+ "",
1149
+ "Paste into the environment settings",
1150
+ "-----------------------------------",
1151
+ " Repository: the DEFAULT checkout for this environment",
1152
+ "",
1153
+ " Environment variables — NOT task secrets:",
1154
+ // Written as name=value, not as `export name='value'`. This is a settings
1155
+ // form, not a shell: the quotes would be stored as part of the value, and
1156
+ // the surface named must be this one rather than the shell block's.
1157
+ ` LISA_TENANT=${namespace}`,
1158
+ ...(bootstrapKey
1159
+ ? [` ${key}=<read this from your credential manager>`]
1160
+ : [` (${provider} has no environment-variable bootstrap)`]),
1161
+ ...(provider ? [` LISA_PROVIDER=${provider}`] : []),
1162
+ " LISA_SECRETS_SURFACE=codex-cloud",
1163
+ "",
1164
+ " Setup and cache-resume maintenance both run BEFORE task secrets",
1165
+ " exist, so a bootstrap placed in the secrets box is invisible to the",
1166
+ " only two steps that need it.",
1167
+ "",
1168
+ " Setup script:",
1169
+ ` ${SETUP_FIELD}`,
1170
+ "",
1171
+ " Maintenance script:",
1172
+ " The SAME line. It runs when a container resumes from cache, and every",
1173
+ " step is idempotent and version-aware — so running it again is how a",
1174
+ " rotated value, an edited note, or a changed pin gets picked up.",
1175
+ "",
1176
+ "Watch for",
1177
+ "---------",
1178
+ " CODEX_ENV_NODE_VERSION overrides the repository's own .nvmrc and",
1179
+ " engines. An environment left on an older major breaks setup for a repo",
1180
+ " that requires a newer one, and the error blames the package manager.",
1181
+ "",
1182
+ " Codex clones `main`, not the repository's default branch. If those",
1183
+ " differ here, that is where the confusion starts.",
1184
+ "",
1185
+ " Setup logs are visible only in the Codex UI; no CLI retrieves them.",
1186
+ ].join("\n");
1187
+ }
1188
+
1189
+ /**
1190
+ * Produce an image definition and a run command for a container.
1191
+ *
1192
+ * Split across build and run on purpose: the binaries are baked once, and the
1193
+ * credential arrives at `docker run`. Baking it would put it in an image layer,
1194
+ * readable by anyone who can pull the image and surviving every
1195
+ * `docker history` — so the Dockerfile deliberately contains no secret.
1196
+ * @param {{bootstrapKey: string|null, tenant?: string|null, provider?: string}} options Project details.
1197
+ * @returns {string} Text to show the operator.
1198
+ */
1199
+ export function emitContainer({
1200
+ bootstrapKey,
1201
+ tenant = null,
1202
+ provider = null,
1203
+ }) {
1204
+ const key = bootstrapKey ?? "<not resolved>";
1205
+ const namespace = tenant ?? "<your namespace>";
1206
+ return [
1207
+ "Provisioning tier: EMIT — an image is built from a file you own.",
1208
+ "",
1209
+ "Dockerfile",
1210
+ "----------",
1211
+ " Generate it with the bootstrap's own emitter, so the image and this",
1212
+ " machine install the identical set:",
1213
+ "",
1214
+ " npx -y @codyswann/lisa@latest workstation --print-dockerfile \\",
1215
+ ` --provider=${provider ?? "bitwarden"} > Dockerfile.lisa`,
1216
+ " docker build -f Dockerfile.lisa -t lisa-workstation .",
1217
+ "",
1218
+ " Nothing secret belongs in it. An image layer is readable by anyone who",
1219
+ " can pull the image and survives every `docker history`, so the",
1220
+ " credential arrives at run time instead.",
1221
+ "",
1222
+ "docker run",
1223
+ "----------",
1224
+ " docker run --rm -it \\",
1225
+ ` -e LISA_TENANT=${namespace} \\`,
1226
+ ` -e ${key}="<this image's own access token>" \\`,
1227
+ ...(provider ? [` -e LISA_PROVIDER=${provider} \\`] : []),
1228
+ " -e LISA_SECRETS_SURFACE=container \\",
1229
+ ' -v "$PWD:/work" -w /work \\',
1230
+ " lisa-workstation",
1231
+ "",
1232
+ " LISA_SECRETS_SURFACE=container is what makes credentials materialize.",
1233
+ " Left to detection a container reads as `local`, which is",
1234
+ " materialized:false — right for a human at a keyboard whose provider CLI",
1235
+ " is authenticated, wrong for a container that has no keychain and dies",
1236
+ " with its filesystem.",
1237
+ "",
1238
+ " Give the image its own access token rather than reusing this machine's.",
1239
+ " If the image is shared, so is anything its token can read.",
1240
+ "",
1241
+ "Mounted repositories need nothing further",
1242
+ "-----------------------------------------",
1243
+ " A checkout you have already run `apply` and `sync` on carries its",
1244
+ " .lisa.config.json with it. Cloning INSIDE the container instead means",
1245
+ " running those there.",
1246
+ ].join("\n");
1247
+ }
1248
+
1249
+ /**
1250
+ * Report tooling the project appears to need but has not declared.
1251
+ *
1252
+ * Called before the toolchain is applied, because the manifest is the only
1253
+ * thing that puts a binary on PATH and nothing populates it — so provisioning
1254
+ * "the declared toolchain" happily succeeds while the tool a project actually
1255
+ * uses is absent, and fails later at the moment of use instead of here.
1256
+ *
1257
+ * Informational, never blocking. The detector proposes and a human decides, so
1258
+ * an undeclared tool must not stop a setup that was asked to provision what IS
1259
+ * declared; failing here would make a advisory signal into a gate nobody chose.
1260
+ * Its own absence is silent for the same reason: an older installed copy of the
1261
+ * skills has no detector, and that is not a reason to refuse to provision.
1262
+ * @param {Function} [exec] Seam for tests.
1263
+ */
1264
+ function reportUndeclaredTooling(exec = boundedChildOutput) {
1265
+ let script;
1266
+ try {
1267
+ script = siblingScript("lisa-detect-tooling", "detect-tooling.mjs");
1268
+ } catch {
1269
+ return;
1270
+ }
1271
+ try {
1272
+ const out = exec("node", [script], {
1273
+ encoding: "utf8",
1274
+ stdio: ["ignore", "pipe", "pipe"],
1275
+ });
1276
+ const text = String(out).trim();
1277
+ if (text) console.log(`\n${text}\n`);
1278
+ } catch {
1279
+ // A detector that cannot run is a missing hint, not a failed setup.
1280
+ }
1281
+ }
1282
+
1283
+ /**
1284
+ * Decide which phases this invocation runs.
1285
+ *
1286
+ * An explicit `--phase` always wins, because that is how a session-start hook
1287
+ * asks for the one phase it owns. Otherwise every phase runs except a secrets
1288
+ * step that belongs to a different moment on this surface — running it here
1289
+ * would produce a copy that the surface never refreshes.
1290
+ * @param {string|undefined} requested Value of `--phase`, when given.
1291
+ * @param {string|null} materializeAt When this surface materializes.
1292
+ * @returns {string[]} Phases to run, in order.
1293
+ */
1294
+ export function selectPhases(requested, materializeAt) {
1295
+ if (requested) {
1296
+ if (!PHASES.includes(requested)) {
1297
+ throw new Error(
1298
+ `unknown --phase "${requested}". Known: ${PHASES.join(", ")}.`
1299
+ );
1300
+ }
1301
+ if (requested === "secrets" && !materializesAtSessionStart(materializeAt)) {
1302
+ // Not an error: the hook is committed to the repository and runs on every
1303
+ // surface the project is ever checked out on. Refusing loudly would make
1304
+ // a correct local session look broken every time it started.
1305
+ return [];
1306
+ }
1307
+ return [requested];
1308
+ }
1309
+ return PHASES.filter(
1310
+ phase => phase !== "secrets" || materializesAtSetup(materializeAt)
1311
+ );
1312
+ }
1313
+
1314
+ /**
1315
+ * Whether the setup run itself should materialize.
1316
+ *
1317
+ * `"both"` means the surface materializes in the setup run AND from the
1318
+ * session-start hook, because on that surface either one alone has a hole: the
1319
+ * setup run is skipped when a cached environment is reused, and the hook cannot
1320
+ * fire when Claude Code's project directory is not the repository — which is
1321
+ * every cloud environment configured with more than one repo.
1322
+ * @param {string|null} materializeAt Surface capability.
1323
+ * @returns {boolean} Whether to materialize during setup.
1324
+ */
1325
+ export function materializesAtSetup(materializeAt) {
1326
+ return materializeAt === "setup" || materializeAt === "both";
1327
+ }
1328
+
1329
+ /**
1330
+ * Whether the committed session-start hook should materialize.
1331
+ * @param {string|null} materializeAt Surface capability.
1332
+ * @returns {boolean} Whether the hook does the work.
1333
+ */
1334
+ export function materializesAtSessionStart(materializeAt) {
1335
+ return materializeAt === "session-start" || materializeAt === "both";
1336
+ }
1337
+
1338
+ /**
1339
+ * Read the surface the resolver detects, without duplicating its rules.
1340
+ *
1341
+ * Imported rather than shelled out to, because the answer is a pure function of
1342
+ * the environment and a subprocess would buy nothing. The path is converted to
1343
+ * a file URL first: a bare absolute path is not a portable module specifier.
1344
+ * @returns {Promise<{surface: string, materializeAt: string|null}>} Detected surface.
1345
+ */
1346
+ async function detectSurface() {
1347
+ const script = siblingScript("lisa-secrets-access", "surfaces.mjs");
1348
+ const mod = await import(pathToFileURL(script).href);
1349
+ const surface = mod.detectSurface();
1350
+ return { surface, materializeAt: mod.SURFACES[surface].materializeAt };
1351
+ }
1352
+
1353
+ /**
1354
+ * Read the bootstrap key name, which is the one value the operator must paste.
1355
+ * @param {string} [cwd] Repository root.
1356
+ * @returns {string|null} The configured key name, when there is one.
1357
+ */
1358
+ function readBootstrapKey(cwd = process.cwd()) {
1359
+ const path = join(cwd, ".lisa.config.json");
1360
+ if (!existsSync(path)) return null;
1361
+ return JSON.parse(readFileSync(path, "utf8")).secrets?.bootstrap?.key ?? null;
1362
+ }
1363
+
1364
+ /**
1365
+ * Work out what to tell the operator to export, for a surface that has no repo.
1366
+ *
1367
+ * Emitting used to read the key out of `.lisa.config.json` in the working
1368
+ * directory, which made the command that configures a REPO-LESS environment
1369
+ * require a repository — so it was run from a checkout that happened to be
1370
+ * nearby, or it printed a placeholder the operator substituted by hand.
1371
+ *
1372
+ * Explicit flags win, then the environment, then a checkout if the caller
1373
+ * happens to be standing in one, then the convention. The provider matters
1374
+ * because it decides the variable's name: `bws` reads `BWS_ACCESS_TOKEN` and
1375
+ * `doppler` reads `DOPPLER_TOKEN`, so a tenant on one told to export the
1376
+ * other's name gets "Missing access token" from a CLI it did configure.
1377
+ * @param {string[]} argv Process arguments.
1378
+ * @param {Record<string, string|undefined>} [env] Environment to read.
1379
+ * @param {string} [cwd] Directory to look for a config in.
1380
+ * @returns {Promise<{tenant: string|null, provider: string, bootstrapKey: string|null}>} Resolved identity.
1381
+ */
1382
+ export async function resolveEmitTarget(
1383
+ argv,
1384
+ env = process.env,
1385
+ cwd = process.cwd()
1386
+ ) {
1387
+ const flag = name =>
1388
+ argv.find(a => a.startsWith(`--${name}=`))?.slice(name.length + 3) ?? null;
1389
+
1390
+ const tenant =
1391
+ flag("tenant") ||
1392
+ (env.LISA_TENANT ?? env.LISA_SECRETS_NAMESPACE ?? "").trim() ||
1393
+ null;
1394
+ const provider =
1395
+ flag("provider") ||
1396
+ (env.LISA_PROVIDER ?? env.LISA_SECRETS_PROVIDER ?? "").trim() ||
1397
+ "bitwarden";
1398
+
1399
+ // Validated before it can reach the output, by the same rule the runtime path
1400
+ // applies. This one becomes `export LISA_TENANT=<value>` in a block an
1401
+ // operator pastes verbatim, so `my tenant` or `has$var` would emit shell that
1402
+ // breaks — or worse, expands — where nothing reviews it. Refusing here costs
1403
+ // a re-run; emitting it costs a container nobody can explain.
1404
+ if (tenant) {
1405
+ const { assertNamespace } = await import(
1406
+ pathToFileURL(siblingScript("lisa-secrets-access", "surfaces.mjs")).href
1407
+ );
1408
+ assertNamespace(tenant);
1409
+ }
1410
+
1411
+ // A configured key outranks the convention: a project may legitimately use a
1412
+ // name that is not derivable, and this command must not tell its operator to
1413
+ // set a different one from the one their sessions actually read.
1414
+ const configured = readBootstrapKey(cwd);
1415
+ if (configured) return { tenant, provider, bootstrapKey: configured };
1416
+ if (!tenant) return { tenant, provider, bootstrapKey: null };
1417
+
1418
+ const { bootstrapKeyFor } = await import(
1419
+ pathToFileURL(siblingScript("lisa-secrets-access", "providers.mjs")).href
1420
+ );
1421
+ return { tenant, provider, bootstrapKey: bootstrapKeyFor(provider, tenant) };
1422
+ }
1423
+
1424
+ async function main() {
1425
+ // `--print-tools` answers "which CLIs does this secret set imply", reading
1426
+ // the notes materialized alongside the values.
1427
+ //
1428
+ // It exists so a repo-less setup can install what the container actually
1429
+ // needs. Without a checkout there is no `remoteEnv.tools`, and the fallback
1430
+ // was the whole catalogue — which spends a five-minute setup budget on CLIs
1431
+ // that may never be used, and a blown budget is a session that never starts.
1432
+ //
1433
+ // Printing rather than installing keeps the two skills decoupled: the secrets
1434
+ // side knows what the vault says, the workstation side knows how to install,
1435
+ // and neither grows a dependency on the other.
1436
+ if (process.argv.includes("--print-tools")) {
1437
+ const { readConfig, materializedPaths } = await import(
1438
+ pathToFileURL(siblingScript("lisa-secrets-access", "surfaces.mjs")).href
1439
+ );
1440
+ const { toolsFromNotes } = await import(
1441
+ pathToFileURL(
1442
+ siblingScript("lisa-secrets-access", "tools-from-notes.mjs")
1443
+ ).href
1444
+ );
1445
+ const { TOOLS } = await import(
1446
+ pathToFileURL(siblingScript("lisa-setup-workstation", "catalogue.mjs"))
1447
+ .href
1448
+ );
1449
+
1450
+ const { notesFile } = materializedPaths(readConfig().namespace);
1451
+ // Silence rather than failure when nothing has been materialized: the
1452
+ // caller is a shell substitution in a setup script, and an error there
1453
+ // would abort provisioning over a question that simply has no answer yet.
1454
+ if (!existsSync(notesFile)) return;
1455
+
1456
+ const notes = JSON.parse(readFileSync(notesFile, "utf8")).secrets ?? {};
1457
+ const known = TOOLS.filter(t => t.kind !== "required").map(t => t.name);
1458
+ console.log(toolsFromNotes(notes, known).join(","));
1459
+ return;
1460
+ }
1461
+
1462
+ const dryRun = process.argv.includes("--dry-run");
1463
+ const emit = process.argv
1464
+ .find(arg => arg.startsWith("--emit="))
1465
+ ?.slice("--emit=".length);
1466
+
1467
+ if (process.argv.includes("--install")) {
1468
+ console.log(`Installing remote-environment scripts into ${INSTALL_DIR}/`);
1469
+ for (const { name, action } of installAssets()) {
1470
+ console.log(` ${action.padEnd(8)} ${join(INSTALL_DIR, name)}`);
1471
+ }
1472
+
1473
+ const pin = process.argv
1474
+ .find(arg => arg.startsWith("--pin-env="))
1475
+ ?.slice("--pin-env=".length);
1476
+ if (pin) {
1477
+ console.log(`\nPinned this project to environment ${pin}`);
1478
+ console.log(` written ${pinEnvironment(pin)}`);
1479
+ console.log(
1480
+ " That file is gitignored and outranks the machine-wide default, so\n" +
1481
+ " every project can name its own environment."
1482
+ );
1483
+ }
1484
+ console.log(
1485
+ "\nCommit these. They are repository files by design — a container that " +
1486
+ "has\njust cloned the repo has never seen the plugin they came from."
1487
+ );
1488
+ return;
1489
+ }
1490
+
1491
+ if (emit) {
1492
+ if (emit !== "claude-web") {
1493
+ throw new Error(
1494
+ `no emit template for surface "${emit}".\n` +
1495
+ `Emitting is implemented for claude-web, which has no other tier.`
1496
+ );
1497
+ }
1498
+ console.log(emitClaudeWeb(await resolveEmitTarget(process.argv)));
1499
+ return;
1500
+ }
1501
+
1502
+ const requested = process.argv
1503
+ .find(arg => arg.startsWith("--phase="))
1504
+ ?.slice("--phase=".length);
1505
+ const cfg = readRemoteEnvConfig();
1506
+ const { surface, materializeAt } = await detectSurface();
1507
+ const phases = selectPhases(requested, materializeAt);
1508
+
1509
+ if (!phases.length) {
1510
+ console.log(
1511
+ `Nothing to do: surface "${surface}" does not materialize from a ` +
1512
+ `session-start hook.`
1513
+ );
1514
+ return;
1515
+ }
1516
+
1517
+ if (phases.includes("toolchain")) {
1518
+ reportUndeclaredTooling();
1519
+ console.log("Toolchain:");
1520
+ // A remote container consents by construction: it is disposable, nobody is
1521
+ // watching, and provisioning it is why the script exists. Locally the
1522
+ // operator opts in per run.
1523
+ const remote = Boolean(materializeAt);
1524
+ applyToolchain(cfg.tools, dryRun, {
1525
+ surface: remote ? "remote" : "local",
1526
+ consent: remote || process.argv.includes("--install-tools"),
1527
+ });
1528
+ }
1529
+
1530
+ if (phases.includes("secrets")) {
1531
+ console.log("\nSecrets:");
1532
+ const materialize = siblingScript(
1533
+ "lisa-secrets-access",
1534
+ "materialize-secrets.mjs"
1535
+ );
1536
+ // A failure here is fatal only when nothing else will try again.
1537
+ //
1538
+ // On a surface that materializes at BOTH setup and session start, this run
1539
+ // is the floor and the hook is the authority. The two run in different
1540
+ // environments: a cloud vendor can expose its configured variables to the
1541
+ // agent session while the setup phase — which runs earlier, during image
1542
+ // construction — never sees them. That is not an error state, it is the
1543
+ // normal shape of `materializeAt: "both"`, and the hook materializes
1544
+ // correctly moments later.
1545
+ //
1546
+ // Treating it as fatal cost an entire environment: setup.sh `exec`s this
1547
+ // script, so a non-zero exit fails the vendor's setup step and Claude Code
1548
+ // never starts at all. Trading "secrets arrive one phase later" for "the
1549
+ // container will not boot" is a bad trade, and it regressed a case that
1550
+ // used to work — before this surface materialized at setup, setup simply
1551
+ // never attempted it.
1552
+ //
1553
+ // Every other case keeps the hard failure: an explicit `--phase=secrets`
1554
+ // (the hook's own authoritative run) and a setup-only surface such as
1555
+ // codex-cloud both have no second chance, so a silent pass there would
1556
+ // hand back a session with no credentials and call it ready.
1557
+ const retried = !requested && materializesAtSessionStart(materializeAt);
1558
+
1559
+ // If the hook is what will retry, make sure the hook can actually fire.
1560
+ // Registering it at user scope is what makes the retry real rather than
1561
+ // assumed: the committed project hook does not load when the session opens
1562
+ // above the checkout, which is the norm in a cloud container.
1563
+ // `materializeAt` being set is what "remote surface" means here. The
1564
+ // toolchain phase derives a local `remote` from it, but that binding is
1565
+ // scoped to its own block and is not in scope in this one.
1566
+ if (retried && Boolean(materializeAt)) {
1567
+ const hook = installUserSessionHook(process.cwd(), { dryRun });
1568
+ console.log(` session-start hook: ${hook.action} (${hook.path})`);
1569
+ if (hook.reason) console.log(` ${hook.reason}`);
1570
+ }
1571
+ try {
1572
+ boundedChildOutput(
1573
+ "node",
1574
+ dryRun ? [materialize, "--dry-run"] : [materialize],
1575
+ { stdio: "inherit", timeout: SETUP_OPERATION_BUDGET_MS }
1576
+ );
1577
+ } catch (err) {
1578
+ if (!retried) throw err;
1579
+ console.log(
1580
+ `\n Secrets were not materialized during setup, and that is not fatal ` +
1581
+ `here.\n This surface also materializes at session start, which runs ` +
1582
+ `with the\n session's own environment — the usual reason setup cannot ` +
1583
+ `see the\n bootstrap credential. The session-start hook will retry.\n` +
1584
+ ` If secrets are still missing INSIDE a session, that run is the one ` +
1585
+ `to debug.`
1586
+ );
1587
+ }
1588
+ } else if (!requested) {
1589
+ console.log(
1590
+ `\nSecrets: materialized from a session-start hook on "${surface}", ` +
1591
+ `not here.\n` +
1592
+ ` This script is skipped whenever a cached environment exists, so a ` +
1593
+ `value written\n here would go stale the moment it was rotated. The ` +
1594
+ `hook runs every session.`
1595
+ );
1596
+ }
1597
+
1598
+ if (phases.includes("hook")) runHook(cfg.hook, dryRun);
1599
+ console.log(`\nRemote environment ${dryRun ? "plan complete" : "ready"}.`);
1600
+ }
1601
+
1602
+ /**
1603
+ * Whether this module is the one node was asked to run.
1604
+ *
1605
+ * Both sides are realpath'd: a raw URL comparison answers "no" through a
1606
+ * symlinked checkout, a git worktree, or a /tmp path on macOS, so the module
1607
+ * loads, runs nothing and exits 0 — a silent no-op that reads as success.
1608
+ *
1609
+ * A local copy rather than an import: plugin payload scripts ship standalone,
1610
+ * with no `lib/` sibling to import from once installed.
1611
+ * @param {string} moduleUrl - The caller's own `import.meta.url`.
1612
+ * @param {string | undefined} [argv1] - Entry path; defaults to `process.argv[1]`.
1613
+ * @returns {boolean} Whether the caller should run its CLI body.
1614
+ */
1615
+ function invokedAsScript(moduleUrl, argv1 = process.argv[1]) {
1616
+ if (!argv1) return false;
1617
+ try {
1618
+ return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
1619
+ } catch {
1620
+ return false;
1621
+ }
1622
+ }
1623
+
1624
+ if (invokedAsScript(import.meta.url)) {
1625
+ // Awaited rather than called bare: main is async, so a synchronous try/catch
1626
+ // would let a rejected promise escape as an unhandled rejection and exit 0 —
1627
+ // reporting a prepared environment that was never prepared.
1628
+ main().catch(err => {
1629
+ console.error(err.message);
1630
+ process.exit(1);
1631
+ });
1632
+ }