@codyswann/lisa 4.74.8 → 4.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +60 -0
- package/all/copy-overwrite/scripts/lib/npm-update-cancel-origin.mjs +4 -2
- package/all/copy-overwrite/scripts/lib/npm-update-checkpoint-codec.mjs +141 -0
- package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +162 -116
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +1 -0
- package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +9 -60
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +18 -6
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +20 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/lisa-skill-sources.d.ts +3 -3
- package/dist/core/lisa-skill-sources.d.ts.map +1 -1
- package/dist/core/lisa-skill-sources.js +3 -2
- package/dist/core/lisa-skill-sources.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +739 -11
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-cdk-devin/skills/setup-aws-accounts/SKILL.md +292 -0
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/.claude-plugin/plugin.json +8 -0
- package/plugins/lisa-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-devin/.mcp.json +8 -0
- package/plugins/lisa-devin/agents/architecture-specialist.md +32 -0
- package/plugins/lisa-devin/agents/bug-fixer.md +43 -0
- package/plugins/lisa-devin/agents/builder.md +44 -0
- package/plugins/lisa-devin/agents/confluence-prd-intake.md +65 -0
- package/plugins/lisa-devin/agents/debug-specialist.md +34 -0
- package/plugins/lisa-devin/agents/eval-specialist.md +33 -0
- package/plugins/lisa-devin/agents/git-history-analyzer.md +183 -0
- package/plugins/lisa-devin/agents/github-agent.md +155 -0
- package/plugins/lisa-devin/agents/github-build-intake.md +64 -0
- package/plugins/lisa-devin/agents/github-prd-intake.md +66 -0
- package/plugins/lisa-devin/agents/jira-agent.md +135 -0
- package/plugins/lisa-devin/agents/jira-build-intake.md +64 -0
- package/plugins/lisa-devin/agents/learner.md +85 -0
- package/plugins/lisa-devin/agents/learning-judge.md +119 -0
- package/plugins/lisa-devin/agents/learnings-synthesizer.md +155 -0
- package/plugins/lisa-devin/agents/linear-agent.md +138 -0
- package/plugins/lisa-devin/agents/linear-build-intake.md +64 -0
- package/plugins/lisa-devin/agents/linear-prd-intake.md +66 -0
- package/plugins/lisa-devin/agents/notion-prd-intake.md +63 -0
- package/plugins/lisa-devin/agents/performance-specialist.md +26 -0
- package/plugins/lisa-devin/agents/pr-mining-specialist.md +85 -0
- package/plugins/lisa-devin/agents/product-specialist.md +30 -0
- package/plugins/lisa-devin/agents/quality-specialist.md +30 -0
- package/plugins/lisa-devin/agents/security-specialist.md +43 -0
- package/plugins/lisa-devin/agents/skill-evaluator.md +205 -0
- package/plugins/lisa-devin/agents/spec-conformance-specialist.md +56 -0
- package/plugins/lisa-devin/agents/test-specialist.md +32 -0
- package/plugins/lisa-devin/agents/tracker-mining-specialist.md +85 -0
- package/plugins/lisa-devin/agents/verification-specialist.md +145 -0
- package/plugins/lisa-devin/hooks/auto-update.mjs +824 -0
- package/plugins/lisa-devin/hooks/auto-update.sh +31 -0
- package/plugins/lisa-devin/hooks/block-blind-automerge.sh +1242 -0
- package/plugins/lisa-devin/hooks/block-direct-issue-create.sh +2603 -0
- package/plugins/lisa-devin/hooks/block-host-name-leak.mjs +412 -0
- package/plugins/lisa-devin/hooks/block-host-name-leak.sh +61 -0
- package/plugins/lisa-devin/hooks/block-instruction-file-edits.sh +207 -0
- package/plugins/lisa-devin/hooks/block-managed-file-edits.sh +1175 -0
- package/plugins/lisa-devin/hooks/block-no-verify.sh +2288 -0
- package/plugins/lisa-devin/hooks/block-shell-json-parsing.sh +263 -0
- package/plugins/lisa-devin/hooks/cleanup-stale-worktrees.sh +122 -0
- package/plugins/lisa-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-devin/hooks/discharge-work-item-gates.sh +90 -0
- package/plugins/lisa-devin/hooks/enforce-verification-gate.sh +506 -0
- package/plugins/lisa-devin/hooks/enforcement-vintage-npm.mjs +320 -0
- package/plugins/lisa-devin/hooks/enforcement-vintage.mjs +472 -0
- package/plugins/lisa-devin/hooks/enforcement-vintage.sh +48 -0
- package/plugins/lisa-devin/hooks/failure-signature-index.mjs +717 -0
- package/plugins/lisa-devin/hooks/failure-signature-index.sh +26 -0
- package/plugins/lisa-devin/hooks/guard-dedupe.bash +374 -0
- package/plugins/lisa-devin/hooks/inject-resolved-config.mjs +903 -0
- package/plugins/lisa-devin/hooks/inject-resolved-config.sh +107 -0
- package/plugins/lisa-devin/hooks/inject-rules.sh +49 -0
- package/plugins/lisa-devin/hooks/install-pkgs.sh +84 -0
- package/plugins/lisa-devin/hooks/operational-hazards.mjs +736 -0
- package/plugins/lisa-devin/hooks/operational-hazards.sh +28 -0
- package/plugins/lisa-devin/hooks/parity-safety-net-heredoc.py +1004 -0
- package/plugins/lisa-devin/hooks/parity-safety-net.sh +3275 -0
- package/plugins/lisa-devin/hooks/secrets-preflight.sh +72 -0
- package/plugins/lisa-devin/hooks/setup-jira-cli.sh +205 -0
- package/plugins/lisa-devin/hooks/shell-write-nudge.sh +86 -0
- package/plugins/lisa-devin/hooks/sonar-secrets.sh +205 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet-compare.mjs +518 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet-families.mjs +543 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet.mjs +568 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet.sh +22 -0
- package/plugins/lisa-devin/hooks/withdrawn-rulings.mjs +600 -0
- package/plugins/lisa-devin/hooks/withdrawn-rulings.sh +24 -0
- package/plugins/lisa-devin/hooks/worktree-binding-guard.mjs +1383 -0
- package/plugins/lisa-devin/hooks/worktree-binding-guard.sh +79 -0
- package/plugins/lisa-devin/hooks.json +247 -0
- package/plugins/lisa-devin/rules/eager/00-rule-index.md +82 -0
- package/plugins/lisa-devin/rules/eager/base-rules.md +85 -0
- package/plugins/lisa-devin/rules/eager/coding-philosophy.md +27 -0
- package/plugins/lisa-devin/rules/eager/config-resolution.md +37 -0
- package/plugins/lisa-devin/rules/eager/do-it-now.md +11 -0
- package/plugins/lisa-devin/rules/eager/empirical-inquiry.md +24 -0
- package/plugins/lisa-devin/rules/eager/falsifiable-checks.md +48 -0
- package/plugins/lisa-devin/rules/eager/history-audit.md +27 -0
- package/plugins/lisa-devin/rules/eager/integration-access-layer.md +19 -0
- package/plugins/lisa-devin/rules/eager/intent-routing.md +18 -0
- package/plugins/lisa-devin/rules/eager/learnings-ladder.md +18 -0
- package/plugins/lisa-devin/rules/eager/local-ci-first.md +15 -0
- package/plugins/lisa-devin/rules/eager/measurement-integrity.md +18 -0
- package/plugins/lisa-devin/rules/eager/never-name-a-downstream-project.md +9 -0
- package/plugins/lisa-devin/rules/eager/not-blocked-just-waiting.md +13 -0
- package/plugins/lisa-devin/rules/eager/operational-hazards.md +50 -0
- package/plugins/lisa-devin/rules/eager/report-actionability.md +26 -0
- package/plugins/lisa-devin/rules/eager/security-audit-handling.md +40 -0
- package/plugins/lisa-devin/rules/eager/session-status-updates.md +20 -0
- package/plugins/lisa-devin/rules/eager/settled-decisions.md +75 -0
- package/plugins/lisa-devin/rules/eager/stale-state-claims.md +26 -0
- package/plugins/lisa-devin/rules/eager/tool-access-gate.md +23 -0
- package/plugins/lisa-devin/rules/eager/tracked-work.md +9 -0
- package/plugins/lisa-devin/rules/eager/upstream-to-lisa.md +14 -0
- package/plugins/lisa-devin/rules/eager/verification.md +33 -0
- package/plugins/lisa-devin/rules/eager/wiki-knowledge-source.md +13 -0
- package/plugins/lisa-devin/rules/eager/withdrawn-rulings.md +102 -0
- package/plugins/lisa-devin/rules/reference/automation-runbook-contract.md +355 -0
- package/plugins/lisa-devin/rules/reference/base-rules.md +136 -0
- package/plugins/lisa-devin/rules/reference/bdd-e2e-coverage.md +301 -0
- package/plugins/lisa-devin/rules/reference/blocker-containment.md +165 -0
- package/plugins/lisa-devin/rules/reference/claim-archaeology.md +184 -0
- package/plugins/lisa-devin/rules/reference/claim-evidence-mapping.md +362 -0
- package/plugins/lisa-devin/rules/reference/claim-time-guards.md +135 -0
- package/plugins/lisa-devin/rules/reference/coding-philosophy.md +428 -0
- package/plugins/lisa-devin/rules/reference/config-resolution.md +1225 -0
- package/plugins/lisa-devin/rules/reference/control-reachability.md +166 -0
- package/plugins/lisa-devin/rules/reference/convergent-review.md +145 -0
- package/plugins/lisa-devin/rules/reference/credential-substrate-precedence.md +249 -0
- package/plugins/lisa-devin/rules/reference/dependency-decision-records.md +177 -0
- package/plugins/lisa-devin/rules/reference/dependency-internalization-kit.md +244 -0
- package/plugins/lisa-devin/rules/reference/dependency-trust-classes.md +291 -0
- package/plugins/lisa-devin/rules/reference/deployed-state-readback.md +142 -0
- package/plugins/lisa-devin/rules/reference/derived-branch-plan.md +214 -0
- package/plugins/lisa-devin/rules/reference/design-source-of-truth.md +260 -0
- package/plugins/lisa-devin/rules/reference/design-value-binding.md +311 -0
- package/plugins/lisa-devin/rules/reference/do-it-now.md +19 -0
- package/plugins/lisa-devin/rules/reference/documentation-source-paths.md +31 -0
- package/plugins/lisa-devin/rules/reference/empirical-inquiry.md +28 -0
- package/plugins/lisa-devin/rules/reference/factory-model.md +111 -0
- package/plugins/lisa-devin/rules/reference/falsifiable-checks.md +275 -0
- package/plugins/lisa-devin/rules/reference/history-audit.md +42 -0
- package/plugins/lisa-devin/rules/reference/integration-access-layer.md +89 -0
- package/plugins/lisa-devin/rules/reference/intent-routing.md +428 -0
- package/plugins/lisa-devin/rules/reference/leaf-only-lifecycle.md +227 -0
- package/plugins/lisa-devin/rules/reference/learnings-ladder.md +41 -0
- package/plugins/lisa-devin/rules/reference/local-ci-first.md +34 -0
- package/plugins/lisa-devin/rules/reference/measurement-integrity.md +119 -0
- package/plugins/lisa-devin/rules/reference/never-name-a-downstream-project.md +37 -0
- package/plugins/lisa-devin/rules/reference/not-blocked-just-waiting.md +41 -0
- package/plugins/lisa-devin/rules/reference/observability-audit.md +146 -0
- package/plugins/lisa-devin/rules/reference/operational-hazards.md +115 -0
- package/plugins/lisa-devin/rules/reference/prd-definition-of-ready.md +118 -0
- package/plugins/lisa-devin/rules/reference/prd-lifecycle-rollup.md +192 -0
- package/plugins/lisa-devin/rules/reference/pre-flight-autofill.md +250 -0
- package/plugins/lisa-devin/rules/reference/project-learnings.md +159 -0
- package/plugins/lisa-devin/rules/reference/promotion-contract.md +148 -0
- package/plugins/lisa-devin/rules/reference/readiness-rubric.md +242 -0
- package/plugins/lisa-devin/rules/reference/ready-role-filing.md +212 -0
- package/plugins/lisa-devin/rules/reference/rejection-detection.md +308 -0
- package/plugins/lisa-devin/rules/reference/repo-scope-split.md +119 -0
- package/plugins/lisa-devin/rules/reference/report-actionability.md +61 -0
- package/plugins/lisa-devin/rules/reference/reset-seed-coverage.md +286 -0
- package/plugins/lisa-devin/rules/reference/security-audit-handling.md +55 -0
- package/plugins/lisa-devin/rules/reference/session-status-updates.md +111 -0
- package/plugins/lisa-devin/rules/reference/settled-decisions.md +39 -0
- package/plugins/lisa-devin/rules/reference/stale-state-claims.md +88 -0
- package/plugins/lisa-devin/rules/reference/tool-access-gate.md +114 -0
- package/plugins/lisa-devin/rules/reference/tracked-work.md +49 -0
- package/plugins/lisa-devin/rules/reference/upstream-to-lisa.md +26 -0
- package/plugins/lisa-devin/rules/reference/usage-accounting.md +261 -0
- package/plugins/lisa-devin/rules/reference/verification.md +278 -0
- package/plugins/lisa-devin/rules/reference/wiki-knowledge-source.md +37 -0
- package/plugins/lisa-devin/rules/reference/withdrawn-rulings.md +148 -0
- package/plugins/lisa-devin/rules/reference/work-item-definition-of-ready.md +177 -0
- package/plugins/lisa-devin/rules/reference/work-item-trailer-definition.md +129 -0
- package/plugins/lisa-devin/scripts/automation-run-record.mjs +678 -0
- package/plugins/lisa-devin/scripts/automation-status-claude-adapter.mjs +804 -0
- package/plugins/lisa-devin/scripts/automation-status-codex-adapter.mjs +1081 -0
- package/plugins/lisa-devin/scripts/automation-status-contract-drift.mjs +392 -0
- package/plugins/lisa-devin/scripts/automation-status-expected-fleet.mjs +508 -0
- package/plugins/lisa-devin/scripts/automation-status-report.mjs +200 -0
- package/plugins/lisa-devin/scripts/automation-status-run-history.mjs +216 -0
- package/plugins/lisa-devin/scripts/automation-status-unrecorded-runs.mjs +127 -0
- package/plugins/lisa-devin/scripts/blocker-edge-resolution.mjs +150 -0
- package/plugins/lisa-devin/scripts/cross-pollinate.mjs +763 -0
- package/plugins/lisa-devin/scripts/design-bindings-probe.mjs +713 -0
- package/plugins/lisa-devin/scripts/design-intake-gate.mjs +470 -0
- package/plugins/lisa-devin/scripts/design-source-gate.mjs +610 -0
- package/plugins/lisa-devin/scripts/design-variable-ids.mjs +567 -0
- package/plugins/lisa-devin/scripts/doctor-report.mjs +635 -0
- package/plugins/lisa-devin/scripts/install-remote-agent-aws.mjs +443 -0
- package/plugins/lisa-devin/scripts/intake-blocker-reprobe.mjs +1065 -0
- package/plugins/lisa-devin/scripts/intake-prework-denominator.mjs +249 -0
- package/plugins/lisa-devin/scripts/lifecycle-label-trust.mjs +593 -0
- package/plugins/lisa-devin/scripts/linear-state-write-target.mjs +362 -0
- package/plugins/lisa-devin/scripts/plugin-sync-explain.mjs +654 -0
- package/plugins/lisa-devin/scripts/pr-arming-sweep.mjs +561 -0
- package/plugins/lisa-devin/scripts/project-ideation-idempotency-harness.mjs +338 -0
- package/plugins/lisa-devin/scripts/qa-signal-lifecycle.mjs +523 -0
- package/plugins/lisa-devin/scripts/queue-contract-resolution.mjs +583 -0
- package/plugins/lisa-devin/scripts/queue-health-classification.mjs +157 -0
- package/plugins/lisa-devin/scripts/queue-status-build-readers.mjs +561 -0
- package/plugins/lisa-devin/scripts/queue-status-prd-readers.mjs +452 -0
- package/plugins/lisa-devin/scripts/remote-agent-aws-setup.sh +677 -0
- package/plugins/lisa-devin/scripts/repo-scope-vocabulary-audit.mjs +458 -0
- package/plugins/lisa-devin/scripts/resolve-lifecycle-role.mjs +347 -0
- package/plugins/lisa-devin/scripts/rollup-blocker-classification.mjs +616 -0
- package/plugins/lisa-devin/scripts/scoping-label-audit.mjs +474 -0
- package/plugins/lisa-devin/skills/lisa-acceptance-criteria/SKILL.md +73 -0
- package/plugins/lisa-devin/skills/lisa-agent-design-best-practices/SKILL.md +242 -0
- package/plugins/lisa-devin/skills/lisa-agent-ready/SKILL.md +271 -0
- package/plugins/lisa-devin/skills/lisa-analyze-claude-remote/SKILL.md +434 -0
- package/plugins/lisa-devin/skills/lisa-atlassian-access/SKILL.md +469 -0
- package/plugins/lisa-devin/skills/lisa-atlassian-access/scripts/markdown-to-adf.mjs +236 -0
- package/plugins/lisa-devin/skills/lisa-attribute-failure/SKILL.md +142 -0
- package/plugins/lisa-devin/skills/lisa-automation-status/SKILL.md +149 -0
- package/plugins/lisa-devin/skills/lisa-bug-triage/SKILL.md +23 -0
- package/plugins/lisa-devin/skills/lisa-capability-drift/SKILL.md +71 -0
- package/plugins/lisa-devin/skills/lisa-codebase-research/SKILL.md +87 -0
- package/plugins/lisa-devin/skills/lisa-codify-verification/SKILL.md +213 -0
- package/plugins/lisa-devin/skills/lisa-confluence-prd-intake/SKILL.md +417 -0
- package/plugins/lisa-devin/skills/lisa-confluence-to-tracker/SKILL.md +469 -0
- package/plugins/lisa-devin/skills/lisa-confluence-write-prd/SKILL.md +111 -0
- package/plugins/lisa-devin/skills/lisa-cross-pollinate/SKILL.md +175 -0
- package/plugins/lisa-devin/skills/lisa-debrief/SKILL.md +96 -0
- package/plugins/lisa-devin/skills/lisa-debrief-apply/SKILL.md +106 -0
- package/plugins/lisa-devin/skills/lisa-delivery-effectiveness/SKILL.md +62 -0
- package/plugins/lisa-devin/skills/lisa-design-intake/SKILL.md +198 -0
- package/plugins/lisa-devin/skills/lisa-detect-tooling/SKILL.md +110 -0
- package/plugins/lisa-devin/skills/lisa-detect-tooling/scripts/commands.mjs +479 -0
- package/plugins/lisa-devin/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +658 -0
- package/plugins/lisa-devin/skills/lisa-doctor/SKILL.md +743 -0
- package/plugins/lisa-devin/skills/lisa-drive-pr-to-merge/SKILL.md +1587 -0
- package/plugins/lisa-devin/skills/lisa-epic-triage/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-evaluation-suite/SKILL.md +63 -0
- package/plugins/lisa-devin/skills/lisa-exploratory-qa/SKILL.md +185 -0
- package/plugins/lisa-devin/skills/lisa-fix-linter-error/SKILL.md +45 -0
- package/plugins/lisa-devin/skills/lisa-generate-claude-remote-build-script/SKILL.md +235 -0
- package/plugins/lisa-devin/skills/lisa-git-commit/SKILL.md +53 -0
- package/plugins/lisa-devin/skills/lisa-git-prune/SKILL.md +35 -0
- package/plugins/lisa-devin/skills/lisa-git-submit-pr/SKILL.md +170 -0
- package/plugins/lisa-devin/skills/lisa-github-add-journey/SKILL.md +118 -0
- package/plugins/lisa-devin/skills/lisa-github-build-intake/SKILL.md +745 -0
- package/plugins/lisa-devin/skills/lisa-github-claim/SKILL.md +27 -0
- package/plugins/lisa-devin/skills/lisa-github-create/SKILL.md +105 -0
- package/plugins/lisa-devin/skills/lisa-github-evidence/SKILL.md +120 -0
- package/plugins/lisa-devin/skills/lisa-github-journey/SKILL.md +134 -0
- package/plugins/lisa-devin/skills/lisa-github-prd-intake/SKILL.md +424 -0
- package/plugins/lisa-devin/skills/lisa-github-project-v2/SKILL.md +227 -0
- package/plugins/lisa-devin/skills/lisa-github-read-issue/SKILL.md +305 -0
- package/plugins/lisa-devin/skills/lisa-github-sync/SKILL.md +201 -0
- package/plugins/lisa-devin/skills/lisa-github-to-tracker/SKILL.md +456 -0
- package/plugins/lisa-devin/skills/lisa-github-validate-issue/SKILL.md +598 -0
- package/plugins/lisa-devin/skills/lisa-github-verify/SKILL.md +60 -0
- package/plugins/lisa-devin/skills/lisa-github-write-issue/SKILL.md +531 -0
- package/plugins/lisa-devin/skills/lisa-github-write-prd/SKILL.md +156 -0
- package/plugins/lisa-devin/skills/lisa-health/SKILL.md +142 -0
- package/plugins/lisa-devin/skills/lisa-health-drift-cron/SKILL.md +80 -0
- package/plugins/lisa-devin/skills/lisa-implement/SKILL.md +372 -0
- package/plugins/lisa-devin/skills/lisa-improve-code-complexity/SKILL.md +44 -0
- package/plugins/lisa-devin/skills/lisa-improve-harness/SKILL.md +487 -0
- package/plugins/lisa-devin/skills/lisa-improve-max-lines/SKILL.md +45 -0
- package/plugins/lisa-devin/skills/lisa-improve-max-lines-per-function/SKILL.md +46 -0
- package/plugins/lisa-devin/skills/lisa-improve-test-coverage/SKILL.md +47 -0
- package/plugins/lisa-devin/skills/lisa-improve-tests/SKILL.md +50 -0
- package/plugins/lisa-devin/skills/lisa-intake/SKILL.md +208 -0
- package/plugins/lisa-devin/skills/lisa-intake-explain/SKILL.md +279 -0
- package/plugins/lisa-devin/skills/lisa-jam-access/SKILL.md +128 -0
- package/plugins/lisa-devin/skills/lisa-jira-add-journey/SKILL.md +139 -0
- package/plugins/lisa-devin/skills/lisa-jira-build-intake/SKILL.md +556 -0
- package/plugins/lisa-devin/skills/lisa-jira-claim/SKILL.md +27 -0
- package/plugins/lisa-devin/skills/lisa-jira-create/SKILL.md +157 -0
- package/plugins/lisa-devin/skills/lisa-jira-evidence/SKILL.md +106 -0
- package/plugins/lisa-devin/skills/lisa-jira-evidence/scripts/post-evidence.sh +262 -0
- package/plugins/lisa-devin/skills/lisa-jira-journey/SKILL.md +168 -0
- package/plugins/lisa-devin/skills/lisa-jira-journey/scripts/generate-templates.py +239 -0
- package/plugins/lisa-devin/skills/lisa-jira-journey/scripts/parse-plan.py +570 -0
- package/plugins/lisa-devin/skills/lisa-jira-read-ticket/SKILL.md +199 -0
- package/plugins/lisa-devin/skills/lisa-jira-read-ticket/scripts/download-attachment.sh +211 -0
- package/plugins/lisa-devin/skills/lisa-jira-sync/SKILL.md +117 -0
- package/plugins/lisa-devin/skills/lisa-jira-validate-ticket/SKILL.md +525 -0
- package/plugins/lisa-devin/skills/lisa-jira-verify/SKILL.md +61 -0
- package/plugins/lisa-devin/skills/lisa-jira-write-ticket/SKILL.md +440 -0
- package/plugins/lisa-devin/skills/lisa-jsdoc-best-practices/SKILL.md +491 -0
- package/plugins/lisa-devin/skills/lisa-kane-browser/SKILL.md +80 -0
- package/plugins/lisa-devin/skills/lisa-learnings-audit/SKILL.md +490 -0
- package/plugins/lisa-devin/skills/lisa-linear-access/SKILL.md +483 -0
- package/plugins/lisa-devin/skills/lisa-linear-add-journey/SKILL.md +109 -0
- package/plugins/lisa-devin/skills/lisa-linear-build-intake/SKILL.md +548 -0
- package/plugins/lisa-devin/skills/lisa-linear-claim/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-linear-create/SKILL.md +150 -0
- package/plugins/lisa-devin/skills/lisa-linear-evidence/SKILL.md +124 -0
- package/plugins/lisa-devin/skills/lisa-linear-journey/SKILL.md +147 -0
- package/plugins/lisa-devin/skills/lisa-linear-prd-intake/SKILL.md +411 -0
- package/plugins/lisa-devin/skills/lisa-linear-read-issue/SKILL.md +201 -0
- package/plugins/lisa-devin/skills/lisa-linear-sync/SKILL.md +164 -0
- package/plugins/lisa-devin/skills/lisa-linear-to-tracker/SKILL.md +454 -0
- package/plugins/lisa-devin/skills/lisa-linear-validate-issue/SKILL.md +504 -0
- package/plugins/lisa-devin/skills/lisa-linear-verify/SKILL.md +65 -0
- package/plugins/lisa-devin/skills/lisa-linear-write-issue/SKILL.md +448 -0
- package/plugins/lisa-devin/skills/lisa-linear-write-prd/SKILL.md +98 -0
- package/plugins/lisa-devin/skills/lisa-monitor/SKILL.md +224 -0
- package/plugins/lisa-devin/skills/lisa-nightly-add-test-coverage/SKILL.md +40 -0
- package/plugins/lisa-devin/skills/lisa-nightly-improve-tests/SKILL.md +29 -0
- package/plugins/lisa-devin/skills/lisa-nightly-lower-code-complexity/SKILL.md +42 -0
- package/plugins/lisa-devin/skills/lisa-notion-access/SKILL.md +301 -0
- package/plugins/lisa-devin/skills/lisa-notion-prd-intake/SKILL.md +360 -0
- package/plugins/lisa-devin/skills/lisa-notion-to-tracker/SKILL.md +467 -0
- package/plugins/lisa-devin/skills/lisa-notion-write-prd/SKILL.md +111 -0
- package/plugins/lisa-devin/skills/lisa-parity-code-review/SKILL.md +83 -0
- package/plugins/lisa-devin/skills/lisa-parity-code-simplifier/SKILL.md +76 -0
- package/plugins/lisa-devin/skills/lisa-parity-coderabbit/SKILL.md +87 -0
- package/plugins/lisa-devin/skills/lisa-parity-sentry-sdk-setup/SKILL.md +375 -0
- package/plugins/lisa-devin/skills/lisa-parity-sentry-seer/SKILL.md +168 -0
- package/plugins/lisa-devin/skills/lisa-parity-skill-creator/SKILL.md +155 -0
- package/plugins/lisa-devin/skills/lisa-performance-review/SKILL.md +94 -0
- package/plugins/lisa-devin/skills/lisa-persist-learning/SKILL.md +247 -0
- package/plugins/lisa-devin/skills/lisa-plan/SKILL.md +64 -0
- package/plugins/lisa-devin/skills/lisa-plugin-sync-explain/SKILL.md +53 -0
- package/plugins/lisa-devin/skills/lisa-posthog-access/SKILL.md +140 -0
- package/plugins/lisa-devin/skills/lisa-prd-backlink/SKILL.md +274 -0
- package/plugins/lisa-devin/skills/lisa-prd-source-write/SKILL.md +104 -0
- package/plugins/lisa-devin/skills/lisa-prd-ticket-coverage/SKILL.md +187 -0
- package/plugins/lisa-devin/skills/lisa-product-walkthrough/SKILL.md +106 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/SKILL.md +417 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/evidence-card-format.md +21 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/host-project-only.md +22 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/idempotency-verification-harness.md +57 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/public-external-inspiration.md +22 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/unavailable-data-rejection.md +22 -0
- package/plugins/lisa-devin/skills/lisa-pull-request-review/SKILL.md +132 -0
- package/plugins/lisa-devin/skills/lisa-quality-review/SKILL.md +63 -0
- package/plugins/lisa-devin/skills/lisa-queue-status/SKILL.md +215 -0
- package/plugins/lisa-devin/skills/lisa-remote-dispatch/SKILL.md +109 -0
- package/plugins/lisa-devin/skills/lisa-remote-dispatch/scripts/dispatch.mjs +535 -0
- package/plugins/lisa-devin/skills/lisa-repair-intake/SKILL.md +1621 -0
- package/plugins/lisa-devin/skills/lisa-reproduce-bug/SKILL.md +75 -0
- package/plugins/lisa-devin/skills/lisa-research/SKILL.md +92 -0
- package/plugins/lisa-devin/skills/lisa-review-implementation/SKILL.md +209 -0
- package/plugins/lisa-devin/skills/lisa-review-local/SKILL.md +141 -0
- package/plugins/lisa-devin/skills/lisa-rework-triage/SKILL.md +206 -0
- package/plugins/lisa-devin/skills/lisa-root-cause-analysis/SKILL.md +135 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/SKILL.md +430 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/aws-bootstrap.mjs +433 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +288 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +273 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/envfile.mjs +80 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/inspect-vault.mjs +142 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +1085 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/note-format.mjs +179 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/owner.mjs +96 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +342 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/prompt-secret.mjs +79 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/providers.mjs +805 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +680 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/read-secret-note.mjs +105 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/resolve-expo-token.mjs +90 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/resolve-secret.mjs +285 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/rotate-secret.mjs +297 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/routing-floor.mjs +153 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/surfaces.mjs +480 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/sync-secret-to-ci.mjs +510 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/tools-from-notes.mjs +63 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/validate-config.mjs +550 -0
- package/plugins/lisa-devin/skills/lisa-security-review/SKILL.md +124 -0
- package/plugins/lisa-devin/skills/lisa-security-zap-scan/SKILL.md +53 -0
- package/plugins/lisa-devin/skills/lisa-sentry-access/SKILL.md +140 -0
- package/plugins/lisa-devin/skills/lisa-setup-atlassian/SKILL.md +407 -0
- package/plugins/lisa-devin/skills/lisa-setup-automations/SKILL.md +430 -0
- package/plugins/lisa-devin/skills/lisa-setup-automations/scripts/generate-workflow.mjs +288 -0
- package/plugins/lisa-devin/skills/lisa-setup-confluence/SKILL.md +254 -0
- package/plugins/lisa-devin/skills/lisa-setup-github/SKILL.md +319 -0
- package/plugins/lisa-devin/skills/lisa-setup-github-repo/SKILL.md +128 -0
- package/plugins/lisa-devin/skills/lisa-setup-jira/SKILL.md +198 -0
- package/plugins/lisa-devin/skills/lisa-setup-kane/SKILL.md +96 -0
- package/plugins/lisa-devin/skills/lisa-setup-linear/SKILL.md +375 -0
- package/plugins/lisa-devin/skills/lisa-setup-local-env/SKILL.md +95 -0
- package/plugins/lisa-devin/skills/lisa-setup-local-env/scripts/local-env.mjs +196 -0
- package/plugins/lisa-devin/skills/lisa-setup-notion/SKILL.md +376 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-aws/SKILL.md +149 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/SKILL.md +382 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/materialized-env-authority.mjs +262 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/session-start.sh +87 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/setup.sh +216 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/environment.mjs +227 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +378 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +1632 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/tool-floor.mjs +94 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +448 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +250 -0
- package/plugins/lisa-devin/skills/lisa-setup-sonar/SKILL.md +153 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/SKILL.md +143 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/bounded-child.mjs +168 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/catalogue.mjs +293 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/cli.mjs +333 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/workstation.mjs +511 -0
- package/plugins/lisa-devin/skills/lisa-sonarcloud-access/SKILL.md +202 -0
- package/plugins/lisa-devin/skills/lisa-spec-conformance/SKILL.md +205 -0
- package/plugins/lisa-devin/skills/lisa-starter-sync/SKILL.md +35 -0
- package/plugins/lisa-devin/skills/lisa-sync-down/SKILL.md +209 -0
- package/plugins/lisa-devin/skills/lisa-task-decomposition/SKILL.md +195 -0
- package/plugins/lisa-devin/skills/lisa-task-triage/SKILL.md +23 -0
- package/plugins/lisa-devin/skills/lisa-tdd-implementation/SKILL.md +91 -0
- package/plugins/lisa-devin/skills/lisa-tear-down-automations/SKILL.md +85 -0
- package/plugins/lisa-devin/skills/lisa-test-strategy/SKILL.md +66 -0
- package/plugins/lisa-devin/skills/lisa-ticket-triage/SKILL.md +214 -0
- package/plugins/lisa-devin/skills/lisa-track/SKILL.md +129 -0
- package/plugins/lisa-devin/skills/lisa-tracker-add-journey/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-tracker-build-intake/SKILL.md +134 -0
- package/plugins/lisa-devin/skills/lisa-tracker-claim/SKILL.md +35 -0
- package/plugins/lisa-devin/skills/lisa-tracker-create/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-tracker-evidence/SKILL.md +63 -0
- package/plugins/lisa-devin/skills/lisa-tracker-journey/SKILL.md +27 -0
- package/plugins/lisa-devin/skills/lisa-tracker-read/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-tracker-source-artifacts/SKILL.md +111 -0
- package/plugins/lisa-devin/skills/lisa-tracker-sync/SKILL.md +150 -0
- package/plugins/lisa-devin/skills/lisa-tracker-validate/SKILL.md +37 -0
- package/plugins/lisa-devin/skills/lisa-tracker-verify/SKILL.md +29 -0
- package/plugins/lisa-devin/skills/lisa-tracker-write/SKILL.md +55 -0
- package/plugins/lisa-devin/skills/lisa-usage-accounting/SKILL.md +205 -0
- package/plugins/lisa-devin/skills/lisa-usage-accounting/references/effectiveness.md +63 -0
- package/plugins/lisa-devin/skills/lisa-use-the-product/SKILL.md +86 -0
- package/plugins/lisa-devin/skills/lisa-validate-tracker-mapping/SKILL.md +467 -0
- package/plugins/lisa-devin/skills/lisa-verification-lifecycle/SKILL.md +384 -0
- package/plugins/lisa-devin/skills/lisa-verify/SKILL.md +55 -0
- package/plugins/lisa-devin/skills/lisa-verify-prd/SKILL.md +394 -0
- package/plugins/lisa-devin/skills/lisa-verify-workflow-change/SKILL.md +154 -0
- package/plugins/lisa-devin/skills/lisa-wiki-install/SKILL.md +101 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-expo-devin/.mcp.json +8 -0
- package/plugins/lisa-expo-devin/THIRD-PARTY-NOTICES.md +84 -0
- package/plugins/lisa-expo-devin/agents/ops-specialist.md +124 -0
- package/plugins/lisa-expo-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-expo-devin/hooks/register-maestro-mcp.sh +84 -0
- package/plugins/lisa-expo-devin/hooks.json +14 -0
- package/plugins/lisa-expo-devin/skills/add-app-clip/SKILL.md +280 -0
- package/plugins/lisa-expo-devin/skills/add-app-clip/references/native-module.md +96 -0
- package/plugins/lisa-expo-devin/skills/apollo-client/SKILL.md +238 -0
- package/plugins/lisa-expo-devin/skills/apollo-client/references/mutation-patterns.md +360 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/SKILL.md +369 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/scripts/test_validate_atomic_structure.py +240 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +438 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/SKILL.md +321 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/animations.md +220 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/controls.md +272 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/form-sheet.md +253 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/gradients.md +106 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/icons.md +213 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/media.md +198 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/route-structure.md +229 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/search.md +248 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/storage.md +121 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/tabs.md +433 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/toolbar-and-headers.md +284 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/visual-effects.md +197 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/webgpu-three.md +605 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/zoom-transitions.md +158 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/SKILL.md +355 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/references/examples.md +749 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/references/patterns.md +318 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/scripts/create_component.py +200 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/scripts/validate_component.py +332 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/SKILL.md +268 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/common-issues.md +619 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/platform-api.md +276 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +416 -0
- package/plugins/lisa-expo-devin/skills/directory-structure/SKILL.md +214 -0
- package/plugins/lisa-expo-devin/skills/directory-structure/scripts/validate_structure.py +454 -0
- package/plugins/lisa-expo-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
- package/plugins/lisa-expo-devin/skills/eas-update-insights/SKILL.md +228 -0
- package/plugins/lisa-expo-devin/skills/eas-update-insights/references/channel-insights-schema.md +47 -0
- package/plugins/lisa-expo-devin/skills/eas-update-insights/references/update-insights-schema.md +69 -0
- package/plugins/lisa-expo-devin/skills/expo-api-routes/SKILL.md +369 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/SKILL.md +54 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/brownfield-integrated.md +526 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/brownfield-isolated.md +402 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/comparison.md +63 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/troubleshooting.md +88 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/SKILL.md +92 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/fetch.js +113 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/package.json +11 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/validate.js +85 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/SKILL.md +193 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/app-store-metadata.md +479 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/ios-app-store.md +355 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/play-store.md +264 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/testflight.md +58 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/workflows.md +200 -0
- package/plugins/lisa-expo-devin/skills/expo-dev-client/SKILL.md +164 -0
- package/plugins/lisa-expo-devin/skills/expo-env-config/SKILL.md +309 -0
- package/plugins/lisa-expo-devin/skills/expo-env-config/references/validation-patterns.md +417 -0
- package/plugins/lisa-expo-devin/skills/expo-module/SKILL.md +141 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/config-plugin.md +90 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/create-expo-module.md +206 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/lifecycle.md +127 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/module-config.md +48 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/native-module.md +286 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/native-view.md +171 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/SKILL.md +432 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/references/official-docs.md +290 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/references/static-export-verification.md +54 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/scripts/generate-route.py +171 -0
- package/plugins/lisa-expo-devin/skills/expo-tailwind-setup/SKILL.md +487 -0
- package/plugins/lisa-expo-devin/skills/expo-ui-jetpack-compose/SKILL.md +40 -0
- package/plugins/lisa-expo-devin/skills/expo-ui-swift-ui/SKILL.md +39 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/SKILL.md +419 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/color-tokens.md +343 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/component-mapping.md +307 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/scripts/test_validate_styling.py +240 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/scripts/validate_styling.py +424 -0
- package/plugins/lisa-expo-devin/skills/jira-add-journey/SKILL.md +140 -0
- package/plugins/lisa-expo-devin/skills/jira-create/SKILL.md +161 -0
- package/plugins/lisa-expo-devin/skills/jira-evidence/SKILL.md +84 -0
- package/plugins/lisa-expo-devin/skills/jira-evidence/scripts/post-evidence.sh +262 -0
- package/plugins/lisa-expo-devin/skills/jira-journey/SKILL.md +196 -0
- package/plugins/lisa-expo-devin/skills/jira-journey/scripts/generate-templates.py +281 -0
- package/plugins/lisa-expo-devin/skills/jira-journey/scripts/parse-plan.py +400 -0
- package/plugins/lisa-expo-devin/skills/jira-verify/SKILL.md +27 -0
- package/plugins/lisa-expo-devin/skills/local-state/SKILL.md +362 -0
- package/plugins/lisa-expo-devin/skills/local-state/references/async-storage.md +505 -0
- package/plugins/lisa-expo-devin/skills/local-state/references/persistence-patterns.md +711 -0
- package/plugins/lisa-expo-devin/skills/local-state/references/reactive-variables.md +446 -0
- package/plugins/lisa-expo-devin/skills/maestro-mcp-setup/SKILL.md +144 -0
- package/plugins/lisa-expo-devin/skills/native-data-fetching/SKILL.md +507 -0
- package/plugins/lisa-expo-devin/skills/native-data-fetching/references/expo-router-loaders.md +344 -0
- package/plugins/lisa-expo-devin/skills/ops-browser-uat/SKILL.md +124 -0
- package/plugins/lisa-expo-devin/skills/ops-check-logs/SKILL.md +217 -0
- package/plugins/lisa-expo-devin/skills/ops-db-ops/SKILL.md +214 -0
- package/plugins/lisa-expo-devin/skills/ops-deploy/SKILL.md +123 -0
- package/plugins/lisa-expo-devin/skills/ops-monitor-errors/SKILL.md +99 -0
- package/plugins/lisa-expo-devin/skills/ops-performance/SKILL.md +165 -0
- package/plugins/lisa-expo-devin/skills/ops-run-local/SKILL.md +169 -0
- package/plugins/lisa-expo-devin/skills/ops-verify-health/SKILL.md +101 -0
- package/plugins/lisa-expo-devin/skills/owasp-zap/SKILL.md +61 -0
- package/plugins/lisa-expo-devin/skills/play-store-access/SKILL.md +226 -0
- package/plugins/lisa-expo-devin/skills/playwright-ci-debugging/SKILL.md +140 -0
- package/plugins/lisa-expo-devin/skills/playwright-selectors/SKILL.md +496 -0
- package/plugins/lisa-expo-devin/skills/reduce-complexity/SKILL.md +251 -0
- package/plugins/lisa-expo-devin/skills/reduce-complexity/references/extraction-strategies.md +456 -0
- package/plugins/lisa-expo-devin/skills/reduce-complexity/references/refactoring-patterns.md +557 -0
- package/plugins/lisa-expo-devin/skills/testing-library/SKILL.md +314 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/async-patterns.md +420 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/expo-router-testing.md +556 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/mocking-patterns.md +612 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/query-priority.md +291 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/SKILL.md +134 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/expo-av-to-audio.md +132 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/expo-av-to-video.md +160 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/native-tabs.md +124 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/new-architecture.md +79 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-19.md +79 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-compiler.md +59 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-navigation-to-expo-router.md +61 -0
- package/plugins/lisa-expo-devin/skills/use-dom/SKILL.md +417 -0
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-harper-fabric-devin/generated-artifact-globs.txt +5 -0
- package/plugins/lisa-harper-fabric-devin/hooks/block-generated-artifact-edits.sh +141 -0
- package/plugins/lisa-harper-fabric-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-harper-fabric-devin/hooks/enforce-config-extensions.mjs +196 -0
- package/plugins/lisa-harper-fabric-devin/hooks/enforce-config-extensions.sh +46 -0
- package/plugins/lisa-harper-fabric-devin/hooks/inject-rules.sh +27 -0
- package/plugins/lisa-harper-fabric-devin/hooks.json +36 -0
- package/plugins/lisa-harper-fabric-devin/rules/eager/harper-fabric.md +31 -0
- package/plugins/lisa-harper-fabric-devin/rules/reference/harper-fabric.md +52 -0
- package/plugins/lisa-harper-fabric-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-auth/SKILL.md +368 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-build-and-deploy/SKILL.md +198 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-caching/SKILL.md +265 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-component-model/SKILL.md +87 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-config-yaml/SKILL.md +283 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-operations/SKILL.md +239 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-realtime/SKILL.md +187 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-resources/SKILL.md +142 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-rest-queries/SKILL.md +403 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-schema-graphql/SKILL.md +123 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-testing/SKILL.md +254 -0
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-nestjs-devin/hooks/block-migration-edits.sh +125 -0
- package/plugins/lisa-nestjs-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-nestjs-devin/hooks/lisa-edit-gate.sh +152 -0
- package/plugins/lisa-nestjs-devin/hooks.json +15 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/SKILL.md +176 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/advanced-features.md +527 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/project-patterns.md +531 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/quick-start.md +257 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/types-scalars.md +513 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-rules/SKILL.md +560 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/SKILL.md +275 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/entity-patterns.md +450 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/observability-patterns.md +536 -0
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +8 -0
- package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-repo-topic/SKILL.md +163 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-repo-topic/references/repo-topic-config.md +185 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/SKILL.md +175 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/references/platform-routing.md +123 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/references/prompts.md +78 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-setup/SKILL.md +141 -0
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-phaser-devin/agents/accessibility-advocate.md +55 -0
- package/plugins/lisa-phaser-devin/agents/art-director.md +90 -0
- package/plugins/lisa-phaser-devin/agents/audio-director.md +65 -0
- package/plugins/lisa-phaser-devin/agents/combat-designer.md +61 -0
- package/plugins/lisa-phaser-devin/agents/economy-designer.md +59 -0
- package/plugins/lisa-phaser-devin/agents/game-designer.md +62 -0
- package/plugins/lisa-phaser-devin/agents/game-feel-specialist.md +56 -0
- package/plugins/lisa-phaser-devin/agents/level-designer.md +59 -0
- package/plugins/lisa-phaser-devin/agents/localization-manager.md +61 -0
- package/plugins/lisa-phaser-devin/agents/marketing-strategist.md +59 -0
- package/plugins/lisa-phaser-devin/agents/monetization-designer.md +59 -0
- package/plugins/lisa-phaser-devin/agents/narrative-designer.md +56 -0
- package/plugins/lisa-phaser-devin/agents/onboarding-advocate.md +59 -0
- package/plugins/lisa-phaser-devin/agents/player-advocate.md +57 -0
- package/plugins/lisa-phaser-devin/agents/producer.md +61 -0
- package/plugins/lisa-phaser-devin/agents/product-analyst.md +62 -0
- package/plugins/lisa-phaser-devin/agents/publisher.md +59 -0
- package/plugins/lisa-phaser-devin/agents/qa-playtester.md +57 -0
- package/plugins/lisa-phaser-devin/agents/target-player.md +52 -0
- package/plugins/lisa-phaser-devin/agents/ux-ui-designer.md +60 -0
- package/plugins/lisa-phaser-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-phaser-devin/hooks/inject-rules.sh +27 -0
- package/plugins/lisa-phaser-devin/hooks.json +14 -0
- package/plugins/lisa-phaser-devin/rules/eager/phaser.md +44 -0
- package/plugins/lisa-phaser-devin/rules/reference/phaser.md +184 -0
- package/plugins/lisa-phaser-devin/skills/phaser-accessibility/SKILL.md +135 -0
- package/plugins/lisa-phaser-devin/skills/phaser-asset-pipeline/SKILL.md +222 -0
- package/plugins/lisa-phaser-devin/skills/phaser-asset-sourcing/SKILL.md +151 -0
- package/plugins/lisa-phaser-devin/skills/phaser-build-deploy/SKILL.md +153 -0
- package/plugins/lisa-phaser-devin/skills/phaser-i18n/SKILL.md +122 -0
- package/plugins/lisa-phaser-devin/skills/phaser-project-structure/SKILL.md +89 -0
- package/plugins/lisa-phaser-devin/skills/phaser-services/SKILL.md +208 -0
- package/plugins/lisa-phaser-devin/skills/phaser-testing/SKILL.md +192 -0
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-rails-devin/agents/ops-specialist.md +226 -0
- package/plugins/lisa-rails-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-rails-devin/hooks/inject-rules.sh +40 -0
- package/plugins/lisa-rails-devin/hooks/lisa-edit-gate.sh +152 -0
- package/plugins/lisa-rails-devin/hooks/rubocop-on-edit.sh +101 -0
- package/plugins/lisa-rails-devin/hooks/sg-scan-on-edit.sh +97 -0
- package/plugins/lisa-rails-devin/hooks.json +29 -0
- package/plugins/lisa-rails-devin/rules/eager/rails-conventions.md +23 -0
- package/plugins/lisa-rails-devin/rules/reference/rails-conventions.md +176 -0
- package/plugins/lisa-rails-devin/skills/action-controller-best-practices/SKILL.md +374 -0
- package/plugins/lisa-rails-devin/skills/action-view-best-practices/SKILL.md +335 -0
- package/plugins/lisa-rails-devin/skills/active-record-model-best-practices/SKILL.md +166 -0
- package/plugins/lisa-rails-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
- package/plugins/lisa-rails-devin/skills/fix-linter-error/SKILL.md +45 -0
- package/plugins/lisa-rails-devin/skills/improve-code-complexity/SKILL.md +51 -0
- package/plugins/lisa-rails-devin/skills/improve-max-lines/SKILL.md +46 -0
- package/plugins/lisa-rails-devin/skills/improve-max-lines-per-function/SKILL.md +46 -0
- package/plugins/lisa-rails-devin/skills/improve-test-coverage/SKILL.md +48 -0
- package/plugins/lisa-rails-devin/skills/jira-add-journey/SKILL.md +79 -0
- package/plugins/lisa-rails-devin/skills/jira-create/SKILL.md +100 -0
- package/plugins/lisa-rails-devin/skills/jira-evidence/SKILL.md +76 -0
- package/plugins/lisa-rails-devin/skills/jira-evidence/scripts/post-evidence.sh +262 -0
- package/plugins/lisa-rails-devin/skills/jira-journey/SKILL.md +65 -0
- package/plugins/lisa-rails-devin/skills/jira-verify/SKILL.md +26 -0
- package/plugins/lisa-rails-devin/skills/ops-check-logs/SKILL.md +191 -0
- package/plugins/lisa-rails-devin/skills/ops-deploy/SKILL.md +153 -0
- package/plugins/lisa-rails-devin/skills/ops-run-local/SKILL.md +169 -0
- package/plugins/lisa-rails-devin/skills/ops-verify-jobs/SKILL.md +157 -0
- package/plugins/lisa-rails-devin/skills/ops-verify-telemetry/SKILL.md +226 -0
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-typescript-devin/hooks/block-suppress-directives.sh +141 -0
- package/plugins/lisa-typescript-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-typescript-devin/hooks/format-on-edit.sh +102 -0
- package/plugins/lisa-typescript-devin/hooks/lint-on-edit.sh +117 -0
- package/plugins/lisa-typescript-devin/hooks/lisa-edit-gate.sh +152 -0
- package/plugins/lisa-typescript-devin/hooks/sg-scan-on-edit.sh +94 -0
- package/plugins/lisa-typescript-devin/hooks.json +34 -0
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +8 -0
- package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-wiki-devin/ci/lisa-wiki-validate.yml +33 -0
- package/plugins/lisa-wiki-devin/schema/lisa-wiki-config.schema.json +162 -0
- package/plugins/lisa-wiki-devin/schema/wiki-structure.schema.json +51 -0
- package/plugins/lisa-wiki-devin/scripts/_wiki-lib.mjs +204 -0
- package/plugins/lisa-wiki-devin/scripts/diff-guard.mjs +117 -0
- package/plugins/lisa-wiki-devin/scripts/ensure-gitignore.mjs +131 -0
- package/plugins/lisa-wiki-devin/scripts/ensure-wiki.mjs +268 -0
- package/plugins/lisa-wiki-devin/scripts/ingest-git.mjs +198 -0
- package/plugins/lisa-wiki-devin/scripts/ingest-memory.mjs +135 -0
- package/plugins/lisa-wiki-devin/scripts/ingest-roles.mjs +95 -0
- package/plugins/lisa-wiki-devin/scripts/ingest_slack_channel.py +361 -0
- package/plugins/lisa-wiki-devin/scripts/lib/bounded-child.mjs +159 -0
- package/plugins/lisa-wiki-devin/scripts/lint-wiki.mjs +513 -0
- package/plugins/lisa-wiki-devin/scripts/mcp-doctor.mjs +72 -0
- package/plugins/lisa-wiki-devin/scripts/render-contract.mjs +107 -0
- package/plugins/lisa-wiki-devin/scripts/rewrite-refs.mjs +144 -0
- package/plugins/lisa-wiki-devin/scripts/slack_oauth_user.py +179 -0
- package/plugins/lisa-wiki-devin/scripts/validate-config.mjs +321 -0
- package/plugins/lisa-wiki-devin/scripts/verify-migration.mjs +203 -0
- package/plugins/lisa-wiki-devin/scripts/verify-wiki-safety.mjs +126 -0
- package/plugins/lisa-wiki-devin/scripts/wiki-safety.mjs +378 -0
- package/plugins/lisa-wiki-devin/scripts/wiki-status.mjs +345 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-add-ingest/SKILL.md +34 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-add-role/SKILL.md +35 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-confluence/SKILL.md +25 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-docs/SKILL.md +30 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-git/SKILL.md +25 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-jira/SKILL.md +28 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-memory/SKILL.md +28 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-notion/SKILL.md +25 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-roles/SKILL.md +22 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-slack/SKILL.md +30 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-web/SKILL.md +23 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-doctor/SKILL.md +47 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-ingest/SKILL.md +106 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-lint/SKILL.md +32 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-migrate/SKILL.md +43 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-onboard-me/SKILL.md +33 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-query/SKILL.md +40 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-setup/SKILL.md +75 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-setup-automations/SKILL.md +83 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-status/SKILL.md +47 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-tear-down-automations/SKILL.md +34 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-usage/SKILL.md +50 -0
- package/plugins/lisa-wiki-devin/templates/agents/role-agent.claude.md +16 -0
- package/plugins/lisa-wiki-devin/templates/agents/role-agent.codex.toml +15 -0
- package/plugins/lisa-wiki-devin/templates/index.md +17 -0
- package/plugins/lisa-wiki-devin/templates/llm-wiki-contract.md +72 -0
- package/plugins/lisa-wiki-devin/templates/log.md +8 -0
- package/plugins/lisa-wiki-devin/templates/page-types/architecture.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/concept.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/decision.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/entity.md +19 -0
- package/plugins/lisa-wiki-devin/templates/page-types/open-question.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/playbook.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/project.md +19 -0
- package/plugins/lisa-wiki-devin/templates/page-types/requirement.md +19 -0
- package/plugins/lisa-wiki-devin/templates/page-types/staff.md +26 -0
- package/plugins/lisa-wiki-devin/templates/start-here.md +24 -0
- package/plugins/lisa-wiki-devin/templates/state-readme.md +20 -0
- package/plugins/lisa-wiki-devin/templates/wrapper-gitignore.txt +19 -0
- package/scripts/build-plugins.sh +2 -2
- package/scripts/deployed-guard-advice.mjs +1 -0
- package/scripts/generate-devin-plugin-artifacts.mjs +263 -0
- package/scripts/lib/hook-registration-audit.mjs +3 -1
- package/scripts/lib/per-agent-hook-filter.mjs +174 -5
- package/scripts/lib/plugin-rule-citations.mjs +2 -2
- package/scripts/two-channel-couplings.json +1 -1
|
@@ -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
|
+
}
|