@codyswann/lisa 4.74.9 → 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/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 +730 -5
- 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
|
@@ -0,0 +1,1085 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Write the provider's current view to the two files a materializing surface
|
|
4
|
+
* reads.
|
|
5
|
+
*
|
|
6
|
+
* Only surfaces that cannot read through live use this. A remote agent
|
|
7
|
+
* container prepares itself during setup — before any task exists, and often
|
|
8
|
+
* before network policy would permit a provider call from the task itself — so
|
|
9
|
+
* files written at that moment are the only channel available. Everywhere else
|
|
10
|
+
* this is forbidden, because a value on disk is a copy that can drift and leak.
|
|
11
|
+
*
|
|
12
|
+
* Both files are written atomically from one provider response, so values and
|
|
13
|
+
* notes always describe the same revision. A rename within the destination
|
|
14
|
+
* directory is atomic on the same filesystem; a temporary file elsewhere would
|
|
15
|
+
* not be.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* materialize-secrets.mjs [--dry-run]
|
|
19
|
+
* @module materialize-secrets
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
chmodSync,
|
|
24
|
+
existsSync,
|
|
25
|
+
mkdirSync,
|
|
26
|
+
readFileSync,
|
|
27
|
+
realpathSync,
|
|
28
|
+
renameSync,
|
|
29
|
+
rmSync,
|
|
30
|
+
writeFileSync,
|
|
31
|
+
} from "node:fs";
|
|
32
|
+
import { homedir } from "node:os";
|
|
33
|
+
import { join } from "node:path";
|
|
34
|
+
import { fileURLToPath } from "node:url";
|
|
35
|
+
|
|
36
|
+
import {
|
|
37
|
+
BOOTSTRAP_KEY,
|
|
38
|
+
deriveAwsEnvironment,
|
|
39
|
+
RESOLVE_ONLY_LEGACY_SOURCE_PROFILE,
|
|
40
|
+
parseBootstrap,
|
|
41
|
+
renderAwsProfiles,
|
|
42
|
+
} from "./aws-bootstrap.mjs";
|
|
43
|
+
import { renderEnv, renderNotes } from "./envfile.mjs";
|
|
44
|
+
import { assertOwner, ownerOf, ownerTag } from "./owner.mjs";
|
|
45
|
+
import { fetchAll } from "./providers.mjs";
|
|
46
|
+
import { materializedPaths, readConfig } from "./surfaces.mjs";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Write one file to a temporary sibling, then move it into place.
|
|
50
|
+
*
|
|
51
|
+
* The mode is set before the rename so the file is never briefly readable by
|
|
52
|
+
* anyone else, and the temporary sibling is removed on any failure so a partial
|
|
53
|
+
* write cannot be mistaken for a complete one.
|
|
54
|
+
* @param {string} destination Final path.
|
|
55
|
+
* @param {string} contents File body.
|
|
56
|
+
*/
|
|
57
|
+
function writeAtomic(destination, contents) {
|
|
58
|
+
const temporary = `${destination}.tmp-${process.pid}`;
|
|
59
|
+
try {
|
|
60
|
+
writeFileSync(temporary, contents, { mode: 0o600 });
|
|
61
|
+
chmodSync(temporary, 0o600);
|
|
62
|
+
renameSync(temporary, destination);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
rmSync(temporary, { force: true });
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Materialize the configured provider view for the current surface.
|
|
71
|
+
* @param {object} [cfg] Resolved configuration.
|
|
72
|
+
* @returns {{count: number, dir: string}} What was written, and where.
|
|
73
|
+
*/
|
|
74
|
+
/**
|
|
75
|
+
* Marker version this build writes. Bumping it is SAFE, which is the point.
|
|
76
|
+
*
|
|
77
|
+
* The recognisers below match the FAMILY — the frozen identifier plus any
|
|
78
|
+
* version — rather than one literal string, so a block written by an older
|
|
79
|
+
* build is still found and replaced in place. Before that, the reader looked
|
|
80
|
+
* for the exact text it was about to write, so the first rename made it
|
|
81
|
+
* conclude there was no block here and append a second one.
|
|
82
|
+
*
|
|
83
|
+
* That failure is additive and silent, and this module is the worst place in
|
|
84
|
+
* Lisa for it: it writes into `~/.aws` files and a shell profile, outside any
|
|
85
|
+
* repository, where no apply, diff, or review ever revisits the result. An
|
|
86
|
+
* orphaned block in a shell profile is STILL SOURCED, and profiles apply
|
|
87
|
+
* assignments in order — so whichever block comes last wins, and an operator
|
|
88
|
+
* can end up running under credentials from a block Lisa believes it no longer
|
|
89
|
+
* manages. That is credential selection going wrong with no review step.
|
|
90
|
+
*
|
|
91
|
+
* Everywhere else in the codebase the mitigation is "widen the recogniser and
|
|
92
|
+
* accept the old shape until an apply normalises the file". There is no such
|
|
93
|
+
* operation here: the old population never drains and cannot be counted, so
|
|
94
|
+
* the recogniser must accept every past version permanently.
|
|
95
|
+
*
|
|
96
|
+
* Following `core/apply-receipt`, which treats a `schema_version` it does not
|
|
97
|
+
* recognise as NO RECEIPT rather than one it half-understands: on an
|
|
98
|
+
* unrecognised marker, fail toward redoing the work, not toward assuming it is
|
|
99
|
+
* done. Here that means replacing every family member found, so a file already
|
|
100
|
+
* carrying orphans from a past rename is repaired on the next run rather than
|
|
101
|
+
* accumulating one more.
|
|
102
|
+
*/
|
|
103
|
+
const MARKER_VERSION = "v3";
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The frozen half of each marker. Changing one of these DOES orphan blocks —
|
|
107
|
+
* that is the whole contract, and it is why the version lives beside it.
|
|
108
|
+
*
|
|
109
|
+
* This does not make renaming impossible, it makes it deliberate: an innocuous
|
|
110
|
+
* text edit no longer orphans a block, and the one edit that still would is the
|
|
111
|
+
* one these comments forbid.
|
|
112
|
+
*/
|
|
113
|
+
const PROFILE_FAMILY = "lisa secrets (managed";
|
|
114
|
+
|
|
115
|
+
/** The frozen family identifier for an `~/.aws` managed region. */
|
|
116
|
+
const MANAGED_FAMILY = "managed by lisa-secrets-access";
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Build the start/end recognisers for one marker family.
|
|
120
|
+
*
|
|
121
|
+
* Matches the family followed by anything up to the closing delimiter, so
|
|
122
|
+
* `(managed)` — every block in the field today — and `(managed v2)` are both
|
|
123
|
+
* found by the same reader.
|
|
124
|
+
* @param {string} family Frozen family identifier.
|
|
125
|
+
* @returns {{start: RegExp, end: RegExp}} Global recognisers for the family.
|
|
126
|
+
*/
|
|
127
|
+
function familyRecognisers(family) {
|
|
128
|
+
const quoted = family.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
129
|
+
return {
|
|
130
|
+
start: new RegExp(`# >>> ${quoted}[^\\n]*>>>`, "g"),
|
|
131
|
+
end: new RegExp(`# <<< ${quoted}[^\\n]*<<<`, "g"),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Index of the first family match at or after `from`, its length, and its text.
|
|
137
|
+
*
|
|
138
|
+
* The matched text is returned because the owner lives inside the marker, and
|
|
139
|
+
* re-slicing it at every call site is how a reader ends up owner-blind in one
|
|
140
|
+
* of them.
|
|
141
|
+
* @param {string} text Haystack.
|
|
142
|
+
* @param {RegExp} recogniser Global family recogniser.
|
|
143
|
+
* @param {number} from Index to search from.
|
|
144
|
+
* @returns {{index: number, length: number, text: string}} `index` is -1 when absent.
|
|
145
|
+
*/
|
|
146
|
+
function findFamily(text, recogniser, from = 0) {
|
|
147
|
+
recogniser.lastIndex = from;
|
|
148
|
+
const match = recogniser.exec(text);
|
|
149
|
+
return match === null
|
|
150
|
+
? { index: -1, length: 0, text: "" }
|
|
151
|
+
: { index: match.index, length: match[0].length, text: match[0] };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Every managed block of one family, in file order, with its owner.
|
|
156
|
+
*
|
|
157
|
+
* Every block, not just the first: a file may carry orphans from a rename that
|
|
158
|
+
* predates the family recogniser, AND — since a machine can serve more than one
|
|
159
|
+
* tenant — blocks belonging to other projects that must be left exactly alone.
|
|
160
|
+
* Those are different populations with different handling, so the reader
|
|
161
|
+
* enumerates rather than assuming.
|
|
162
|
+
* @param {string} text File contents.
|
|
163
|
+
* @param {string} family Frozen family identifier.
|
|
164
|
+
* @returns {Array<{start: number, end: number, owner: string|null, body: string}>} Blocks.
|
|
165
|
+
*/
|
|
166
|
+
function familyBlocks(text, family) {
|
|
167
|
+
const { start, end } = familyRecognisers(family);
|
|
168
|
+
const blocks = [];
|
|
169
|
+
let from = 0;
|
|
170
|
+
for (;;) {
|
|
171
|
+
const opened = findFamily(text, start, from);
|
|
172
|
+
if (opened.index === -1) return blocks;
|
|
173
|
+
const closed = findFamily(text, end, opened.index + opened.length);
|
|
174
|
+
// A truncated block (opened, never closed) is not safe to interpret. Any
|
|
175
|
+
// guessed boundary could delete operator-authored content after the marker,
|
|
176
|
+
// so refuse before the caller writes and preserve the original byte-for-byte.
|
|
177
|
+
if (closed.index === -1) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Refusing to rewrite a managed ${family} block with no closing marker. ` +
|
|
180
|
+
`Repair the marker pair first; no file was changed.`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const stop = closed.index + closed.length;
|
|
184
|
+
blocks.push({
|
|
185
|
+
start: opened.index,
|
|
186
|
+
end: stop,
|
|
187
|
+
owner: ownerOf(opened.text),
|
|
188
|
+
body: text.slice(opened.index, stop),
|
|
189
|
+
});
|
|
190
|
+
from = stop;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Remove the family blocks a predicate selects, leaving the rest of the file.
|
|
196
|
+
*
|
|
197
|
+
* Selective rather than total, and that is the fix. "Our own previous output is
|
|
198
|
+
* meant to be replaced" was true and unqualified by WHOSE: a second tenant's
|
|
199
|
+
* run matched the same unowned marker and stripped the first tenant's block.
|
|
200
|
+
* @param {string} text File contents.
|
|
201
|
+
* @param {string} family Frozen family identifier.
|
|
202
|
+
* @param {(block: {owner: string|null, body: string}) => boolean} selects Which blocks to remove.
|
|
203
|
+
* @returns {string} The file with the selected blocks removed.
|
|
204
|
+
*/
|
|
205
|
+
function stripFamilyBlocks(text, family, selects) {
|
|
206
|
+
// Back to front, so an earlier block's offsets are still valid after a later
|
|
207
|
+
// one is cut.
|
|
208
|
+
const doomed = familyBlocks(text, family).filter(block => selects(block));
|
|
209
|
+
let out = text;
|
|
210
|
+
for (const block of doomed.reverse()) {
|
|
211
|
+
const after = out.slice(block.end).replace(/^\n/, "");
|
|
212
|
+
out = `${out.slice(0, block.start)}${after}`;
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Marks the block one owner owns, so it is replaced rather than appended twice.
|
|
219
|
+
* @param {string} owner Validated owner.
|
|
220
|
+
* @returns {string} The opening marker.
|
|
221
|
+
*/
|
|
222
|
+
function profileMarker(owner) {
|
|
223
|
+
return `# >>> ${PROFILE_FAMILY} ${MARKER_VERSION} ${ownerTag(owner)}) >>>`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Closes one owner's managed shell block.
|
|
228
|
+
* @param {string} owner Validated owner.
|
|
229
|
+
* @returns {string} The closing marker.
|
|
230
|
+
*/
|
|
231
|
+
function profileEnd(owner) {
|
|
232
|
+
return `# <<< ${PROFILE_FAMILY} ${MARKER_VERSION} ${ownerTag(owner)}) <<<`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Identifies an `~/.aws` region as one this wrote FOR A GIVEN OWNER.
|
|
237
|
+
*
|
|
238
|
+
* `#` is a comment in the AWS shared-config format, so this is inert to every
|
|
239
|
+
* consumer while still being the thing that distinguishes "our file, refresh
|
|
240
|
+
* it" from "someone else's file, leave it alone" — and now also from "another
|
|
241
|
+
* Lisa project's, leave it alone too", which is the case that was missing.
|
|
242
|
+
* @param {string} owner Validated owner.
|
|
243
|
+
* @returns {string} The opening marker.
|
|
244
|
+
*/
|
|
245
|
+
function managedMarker(owner) {
|
|
246
|
+
return `# >>> ${MANAGED_FAMILY} ${MARKER_VERSION} ${ownerTag(owner)} >>>`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Closes one owner's managed region of an `~/.aws` file.
|
|
251
|
+
* @param {string} owner Validated owner.
|
|
252
|
+
* @returns {string} The closing marker.
|
|
253
|
+
*/
|
|
254
|
+
function managedEnd(owner) {
|
|
255
|
+
return `# <<< ${MANAGED_FAMILY} ${MARKER_VERSION} ${ownerTag(owner)} <<<`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Whether a block belongs to `owner`, for stripping.
|
|
260
|
+
*
|
|
261
|
+
* An unowned block is NEVER ours here. It predates ownership, so it may belong
|
|
262
|
+
* to any tenant on this machine, and adopting it is exactly the silent
|
|
263
|
+
* consumption this change exists to stop. It is reported instead, and removed
|
|
264
|
+
* only under an explicit prune.
|
|
265
|
+
* @param {string} owner Our owner.
|
|
266
|
+
* @returns {(candidate: string|null) => boolean} Predicate over block owners.
|
|
267
|
+
*/
|
|
268
|
+
function ownedBy(owner) {
|
|
269
|
+
return block => block.owner === owner;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Replace one owner's delimited region in a file, preserving everything else.
|
|
274
|
+
*
|
|
275
|
+
* Written as a merge rather than a whole-file write because both `~/.aws` files
|
|
276
|
+
* routinely hold sections nobody here knows about — an operator's own profiles,
|
|
277
|
+
* a container's bare `[default]`, or another Lisa project's block. Refusing on
|
|
278
|
+
* their account wrote nothing at all; overwriting would delete them. This does
|
|
279
|
+
* neither.
|
|
280
|
+
* @param {string} current Existing file contents, or "".
|
|
281
|
+
* @param {string} body The region this owner owns.
|
|
282
|
+
* @param {string} owner Validated owner.
|
|
283
|
+
* @param {boolean} [claimLegacy] Also replace pre-ownership blocks.
|
|
284
|
+
* @returns {string} The merged file.
|
|
285
|
+
*/
|
|
286
|
+
export function upsertManagedBlock(current, body, owner, claimLegacy = false) {
|
|
287
|
+
const scope = assertOwner(owner, "~/.aws");
|
|
288
|
+
const block = `${managedMarker(scope)}\n${body.trimEnd()}\n${managedEnd(scope)}`;
|
|
289
|
+
|
|
290
|
+
// Legacy first, and OUR position measured after it. Removing a pre-ownership
|
|
291
|
+
// block that sits ahead of ours moves ours, so a position taken before the
|
|
292
|
+
// removal would splice the replacement into the wrong place.
|
|
293
|
+
const withoutLegacy = claimLegacy
|
|
294
|
+
? stripFamilyBlocks(current, MANAGED_FAMILY, block => block.owner === null)
|
|
295
|
+
: current;
|
|
296
|
+
const mine = familyBlocks(withoutLegacy, MANAGED_FAMILY).find(
|
|
297
|
+
found => found.owner === scope
|
|
298
|
+
);
|
|
299
|
+
// Only ours. Everything else in the family belongs to another project.
|
|
300
|
+
const withoutOurs = stripFamilyBlocks(
|
|
301
|
+
withoutLegacy,
|
|
302
|
+
MANAGED_FAMILY,
|
|
303
|
+
ownedBy(scope)
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
if (mine === undefined) {
|
|
307
|
+
const prefix =
|
|
308
|
+
withoutOurs && !withoutOurs.endsWith("\n")
|
|
309
|
+
? `${withoutOurs}\n`
|
|
310
|
+
: withoutOurs;
|
|
311
|
+
return `${prefix}${prefix ? "\n" : ""}${block}\n`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Nothing before our first block is ours, so stripping ours cannot have moved
|
|
315
|
+
// anything ahead of it — the original offset still points where it did.
|
|
316
|
+
const head = withoutOurs.slice(0, mine.start);
|
|
317
|
+
const tail = withoutOurs.slice(mine.start);
|
|
318
|
+
return `${head}${block}\n${tail}`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Make every shell in this container load the materialized secrets.
|
|
323
|
+
*
|
|
324
|
+
* `set -a` so the values are exported rather than merely set as shell
|
|
325
|
+
* variables, and sourced LAST so they win over anything the host injected —
|
|
326
|
+
* environment variables outrank profile files in AWS's credential chain, which
|
|
327
|
+
* is the whole reason a valid credential was being ignored.
|
|
328
|
+
*
|
|
329
|
+
* Guarded on the file existing, so a shell still starts cleanly before the
|
|
330
|
+
* first materialization or if the file is removed. Written to both `.bashrc`
|
|
331
|
+
* and `.profile` because which one a given shell reads depends on whether it is
|
|
332
|
+
* interactive or a login shell, and an agent's tool calls are not reliably
|
|
333
|
+
* either.
|
|
334
|
+
* @param {string} valuesFile Absolute path to the materialized env file.
|
|
335
|
+
* @param {object} [options] Home directory and file seams, for tests.
|
|
336
|
+
* @returns {string[]} The profile files that now source it.
|
|
337
|
+
*/
|
|
338
|
+
export function installProfileSourcing(valuesFile, options = {}) {
|
|
339
|
+
const {
|
|
340
|
+
home = process.env.HOME || homedir(),
|
|
341
|
+
owner,
|
|
342
|
+
claimShell = process.env.LISA_SECRETS_CLAIM_SHELL_PROFILE === "1",
|
|
343
|
+
exists = existsSync,
|
|
344
|
+
read = readFileSync,
|
|
345
|
+
write = writeFileSync,
|
|
346
|
+
} = options;
|
|
347
|
+
|
|
348
|
+
const scope = assertOwner(owner, "the shell profile block");
|
|
349
|
+
|
|
350
|
+
const block = [
|
|
351
|
+
profileMarker(scope),
|
|
352
|
+
// Unset BEFORE sourcing. A cloud container injects its own AWS key pair,
|
|
353
|
+
// and environment credentials outrank both ~/.aws profiles and AWS_PROFILE
|
|
354
|
+
// — so leaving them set means the session ignores whichever environment it
|
|
355
|
+
// selected and runs as whatever the host injected. That is how a perfectly
|
|
356
|
+
// valid credential produced InvalidClientTokenId for a day. Removing the
|
|
357
|
+
// poison beats out-shouting it, and it is what lets `--profile agent-dev`
|
|
358
|
+
// behave here exactly as it does on a developer's machine.
|
|
359
|
+
`unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN`,
|
|
360
|
+
// The pinned toolchain installs into ~/.local/bin, which is not on PATH in
|
|
361
|
+
// every base image. Without this the tools are present and unfindable:
|
|
362
|
+
// `command -v bws` fails and anything spawning it gets ENOENT, which reads
|
|
363
|
+
// as "nothing was installed" when everything was.
|
|
364
|
+
`case ":$PATH:" in *":$HOME/.local/bin:"*) ;; ` +
|
|
365
|
+
`*) PATH="$HOME/.local/bin:$PATH"; export PATH;; esac`,
|
|
366
|
+
`if [ -f "${valuesFile}" ]; then`,
|
|
367
|
+
` set -a`,
|
|
368
|
+
` . "${valuesFile}"`,
|
|
369
|
+
` set +a`,
|
|
370
|
+
`fi`,
|
|
371
|
+
profileEnd(scope),
|
|
372
|
+
].join("\n");
|
|
373
|
+
|
|
374
|
+
const files = [".bashrc", ".profile"].map(name => join(home, name));
|
|
375
|
+
const contents = files.map(file =>
|
|
376
|
+
exists(file) ? String(read(file, "utf8")) : ""
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
// Every file is inspected BEFORE any is written. A shell profile is not a
|
|
380
|
+
// namespaced resource — it exports into every shell on the machine, so if one
|
|
381
|
+
// of the two belongs to another tenant the whole run must stop, not stop
|
|
382
|
+
// halfway with `.bashrc` rewritten and `.profile` not.
|
|
383
|
+
for (const [index, current] of contents.entries()) {
|
|
384
|
+
assertShellUnclaimed(files[index], current, scope, claimShell);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const updated = [];
|
|
388
|
+
for (const [index, file] of files.entries()) {
|
|
389
|
+
const current = contents[index];
|
|
390
|
+
// Ours, plus any legacy block this owner can prove is its own, plus — only
|
|
391
|
+
// under an explicit claim — another tenant's. Stripping every family block
|
|
392
|
+
// unconditionally is what let a second tenant delete the first's.
|
|
393
|
+
const selects = block =>
|
|
394
|
+
claimShell || [scope, null].includes(shellOwnerOf(block));
|
|
395
|
+
const mine = familyBlocks(current, PROFILE_FAMILY).find(selects);
|
|
396
|
+
const withoutOurs = stripFamilyBlocks(current, PROFILE_FAMILY, selects);
|
|
397
|
+
const next =
|
|
398
|
+
mine === undefined
|
|
399
|
+
? `${current}${current.endsWith("\n") || !current ? "" : "\n"}\n${block}\n`
|
|
400
|
+
: `${withoutOurs.slice(0, mine.start)}${block}\n${withoutOurs
|
|
401
|
+
.slice(mine.start)
|
|
402
|
+
.replace(/^\n/, "")}`;
|
|
403
|
+
|
|
404
|
+
if (next !== current) {
|
|
405
|
+
write(file, next, { mode: 0o600 });
|
|
406
|
+
updated.push(file);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return updated;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Who one shell block belongs to, marked or recoverable.
|
|
414
|
+
*
|
|
415
|
+
* A legacy `~/.aws` block carries no tell of who wrote it — bare stage names
|
|
416
|
+
* name a stage and no owner — which is why those are attributed by account id
|
|
417
|
+
* instead. A legacy SHELL block is different: its body sources
|
|
418
|
+
* `<config root>/<tenant>/secrets.env`, so it states its own owner in the one
|
|
419
|
+
* line that matters. Reading it back removes the ambiguity entirely, which is
|
|
420
|
+
* better than a flag, and it lets an existing single-tenant machine upgrade in
|
|
421
|
+
* one silent step rather than failing until an operator intervenes.
|
|
422
|
+
*
|
|
423
|
+
* `null` means unattributable, and that is treated as OURS to claim rather than
|
|
424
|
+
* as a stranger's. Every block this module has ever written names a tenant
|
|
425
|
+
* directory, so a block that names none is not a well-formed block of any
|
|
426
|
+
* tenant — while leaving it behind would break the rule that matters most here:
|
|
427
|
+
* an orphaned block in a shell profile is still sourced, and the last
|
|
428
|
+
* assignment wins.
|
|
429
|
+
* @param {{owner: string|null, body: string}} block One managed block.
|
|
430
|
+
* @returns {string|null} The owner, or null when it cannot be attributed.
|
|
431
|
+
*/
|
|
432
|
+
function shellOwnerOf(block) {
|
|
433
|
+
if (block.owner !== null) return block.owner;
|
|
434
|
+
// The tenant directory is the segment before `secrets.env`; that path is the
|
|
435
|
+
// only place a pre-ownership block records whose values it loads.
|
|
436
|
+
const found = /[/\\]([^/\\]+)[/\\]secrets\.env/.exec(block.body);
|
|
437
|
+
return found === null ? null : found[1];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Stop before redirecting a machine that already serves another tenant.
|
|
442
|
+
*
|
|
443
|
+
* `~/.aws` profiles can be namespaced, so two tenants coexist there and a wrong
|
|
444
|
+
* resolution becomes impossible. A shell profile cannot: it exports into every
|
|
445
|
+
* shell and there is no name for a consumer to select, so it is a single slot
|
|
446
|
+
* two projects contend for — the same problem `[default]` poses in
|
|
447
|
+
* `~/.aws/config`, and it gets the same answer. Claimed when unowned or already
|
|
448
|
+
* ours; otherwise the run stops and names the tenant that holds it.
|
|
449
|
+
*
|
|
450
|
+
* Taking it over silently is the whole defect in miniature: every shell on the
|
|
451
|
+
* machine would start exporting another project's credentials, and the sessions
|
|
452
|
+
* that lost their block would carry on believing they had their own.
|
|
453
|
+
* @param {string} file The shell profile path, for the message.
|
|
454
|
+
* @param {string} current File contents.
|
|
455
|
+
* @param {string} scope Our owner.
|
|
456
|
+
* @param {boolean} claimShell Whether the operator asked to take it over.
|
|
457
|
+
*/
|
|
458
|
+
function assertShellUnclaimed(file, current, scope, claimShell) {
|
|
459
|
+
if (claimShell) return;
|
|
460
|
+
const others = [
|
|
461
|
+
...new Set(
|
|
462
|
+
familyBlocks(current, PROFILE_FAMILY)
|
|
463
|
+
.map(shellOwnerOf)
|
|
464
|
+
.filter(found => found !== null)
|
|
465
|
+
),
|
|
466
|
+
].filter(found => found !== scope);
|
|
467
|
+
if (others.length === 0) return;
|
|
468
|
+
throw new Error(
|
|
469
|
+
`${file} already loads secrets for ${others.map(n => `"${n}"`).join(", ")}, ` +
|
|
470
|
+
`not "${scope}".\n` +
|
|
471
|
+
`A shell profile is read by every shell on this machine, so rewriting it ` +
|
|
472
|
+
`would hand ${scope}'s credentials to that project's sessions too — and ` +
|
|
473
|
+
`they would report ready, because the credentials work.\n` +
|
|
474
|
+
`Re-run with LISA_SECRETS_CLAIM_SHELL_PROFILE=1 to switch this machine ` +
|
|
475
|
+
`to "${scope}" deliberately. Nothing was changed.`
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Write `~/.aws/credentials` and `~/.aws/config` from the bootstrap bundle.
|
|
481
|
+
*
|
|
482
|
+
* Both at 0600 inside a 0700 directory, matching how the materialized secrets
|
|
483
|
+
* themselves are protected — the credentials file holds the source key pair.
|
|
484
|
+
* @param {object|null} bundle Parsed bootstrap bundle.
|
|
485
|
+
* @param {object} [options] Home directory and file seams, for tests.
|
|
486
|
+
* @returns {string[]} The profile names written.
|
|
487
|
+
*/
|
|
488
|
+
/**
|
|
489
|
+
* Profile names already defined outside the block this owner is about to write.
|
|
490
|
+
*
|
|
491
|
+
* Only what lies outside OUR block counts: our own previous output is meant to
|
|
492
|
+
* be replaced, and treating it as a collision would make the second run fail.
|
|
493
|
+
* What changed is that "ours" is now scoped to one owner, so another project's
|
|
494
|
+
* block reads as a collision instead of as our own output.
|
|
495
|
+
*
|
|
496
|
+
* Each hit reports who holds the name: `null` for a section an operator wrote
|
|
497
|
+
* outside any managed block, or the owning project. The two need different
|
|
498
|
+
* advice, and a message that guessed would send the reader to the wrong file.
|
|
499
|
+
* @param {string} dir The `.aws` directory.
|
|
500
|
+
* @param {string[]} names Profile names about to be written.
|
|
501
|
+
* @param {object} io `exists`, `read`, `owner`, and `includeLegacy` seams.
|
|
502
|
+
* @returns {Array<{name: string, owner: string|null}>} Collisions, in the order given.
|
|
503
|
+
*/
|
|
504
|
+
export function collidingProfiles(dir, names, io = {}) {
|
|
505
|
+
const {
|
|
506
|
+
exists = existsSync,
|
|
507
|
+
read = readFileSync,
|
|
508
|
+
owner = null,
|
|
509
|
+
includeLegacy = false,
|
|
510
|
+
} = io;
|
|
511
|
+
const file = join(dir, "config");
|
|
512
|
+
if (!exists(file)) return [];
|
|
513
|
+
|
|
514
|
+
const text = String(read(file, "utf8"));
|
|
515
|
+
// Our own previous output is meant to be replaced, so counting its profiles
|
|
516
|
+
// as a collision would refuse to write the very names we wrote last time.
|
|
517
|
+
// "Ours" now means this owner's — another project's block is exactly the case
|
|
518
|
+
// that was being consumed silently — plus, unless the caller says otherwise,
|
|
519
|
+
// pre-ownership blocks, which the caller is about to replace in place.
|
|
520
|
+
const ours = block =>
|
|
521
|
+
(block.owner === owner && owner !== null) ||
|
|
522
|
+
(block.owner === null && !includeLegacy);
|
|
523
|
+
const outside = stripFamilyBlocks(text, MANAGED_FAMILY, ours);
|
|
524
|
+
|
|
525
|
+
const claimed = [];
|
|
526
|
+
for (const block of familyBlocks(text, MANAGED_FAMILY)) {
|
|
527
|
+
if (ours(block)) continue;
|
|
528
|
+
for (const name of profileNamesIn(block.body)) {
|
|
529
|
+
claimed.push({ name, owner: block.owner });
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return names
|
|
534
|
+
.filter(name => sectionPresent(outside, name))
|
|
535
|
+
.map(name => ({
|
|
536
|
+
name,
|
|
537
|
+
owner: claimed.find(entry => entry.name === name)?.owner ?? null,
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Whether a `[profile name]` section header appears in some text.
|
|
543
|
+
* @param {string} text Haystack.
|
|
544
|
+
* @param {string} name Exact profile name.
|
|
545
|
+
* @returns {boolean} Whether the section is defined.
|
|
546
|
+
*/
|
|
547
|
+
function sectionPresent(text, name) {
|
|
548
|
+
return new RegExp(
|
|
549
|
+
`^\\s*\\[profile\\s+${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\]`,
|
|
550
|
+
"m"
|
|
551
|
+
).test(text);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* The `[profile …]` names defined in a chunk of shared-config text.
|
|
556
|
+
* @param {string} text Config text.
|
|
557
|
+
* @returns {string[]} Section names, in order.
|
|
558
|
+
*/
|
|
559
|
+
function profileNamesIn(text) {
|
|
560
|
+
return [...text.matchAll(/^\s*\[profile\s+([^\]\n]+)\]/gm)].map(found =>
|
|
561
|
+
found[1].trim()
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Profile names left in `~/.aws/config` by a build that predated ownership.
|
|
567
|
+
*
|
|
568
|
+
* Reported rather than removed, and that asymmetry with the shell block is the
|
|
569
|
+
* point: a legacy `~/.aws` block carries no record of who wrote it. Its stage
|
|
570
|
+
* names name a stage and no owner, and the account ids in its role ARNs are not
|
|
571
|
+
* tied to any tenant name this process knows. Deleting it could therefore
|
|
572
|
+
* remove another project's working profiles — the exact harm being fixed —
|
|
573
|
+
* so it is named on every run and removed only under an explicit prune.
|
|
574
|
+
* @param {string} dir The `.aws` directory.
|
|
575
|
+
* @param {object} [io] `exists` and `read` seams.
|
|
576
|
+
* @returns {string[]} Unowned profile names still present.
|
|
577
|
+
*/
|
|
578
|
+
export function legacyManagedProfiles(dir, io = {}) {
|
|
579
|
+
const { exists = existsSync, read = readFileSync } = io;
|
|
580
|
+
const file = join(dir, "config");
|
|
581
|
+
if (!exists(file)) return [];
|
|
582
|
+
|
|
583
|
+
const text = String(read(file, "utf8"));
|
|
584
|
+
return familyBlocks(text, MANAGED_FAMILY)
|
|
585
|
+
.filter(block => block.owner === null)
|
|
586
|
+
.flatMap(block => profileNamesIn(block.body));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export function installAwsProfiles(bundle, options = {}) {
|
|
590
|
+
const {
|
|
591
|
+
home = process.env.HOME || homedir(),
|
|
592
|
+
owner,
|
|
593
|
+
pruneLegacy = process.env.LISA_SECRETS_PRUNE_LEGACY_PROFILES === "1",
|
|
594
|
+
// The RESOLVE-ONLY window, on by default and closable from both ends. It
|
|
595
|
+
// never affects what is emitted as canonical output.
|
|
596
|
+
// `NO_LEGACY` is how a caller that has migrated takes the isolation before
|
|
597
|
+
// the window closes; `CLAIM_LEGACY` is how one takes the shared bare slot
|
|
598
|
+
// from another project deliberately.
|
|
599
|
+
noResolveOnly = process.env.LISA_SECRETS_NO_LEGACY_PROFILES === "1",
|
|
600
|
+
claimResolveOnly = process.env.LISA_SECRETS_CLAIM_LEGACY_PROFILES === "1",
|
|
601
|
+
mkdir = mkdirSync,
|
|
602
|
+
write = writeAtomic,
|
|
603
|
+
read = readFileSync,
|
|
604
|
+
exists = existsSync,
|
|
605
|
+
chmod = chmodSync,
|
|
606
|
+
} = options;
|
|
607
|
+
|
|
608
|
+
const rendered = renderAwsProfiles(bundle, owner);
|
|
609
|
+
if (!rendered) return [];
|
|
610
|
+
const scope = assertOwner(owner, "~/.aws");
|
|
611
|
+
|
|
612
|
+
const dir = join(home, ".aws");
|
|
613
|
+
|
|
614
|
+
// Whether a pre-ownership block in this file is THIS project's own past
|
|
615
|
+
// output, or another project's.
|
|
616
|
+
//
|
|
617
|
+
// Leaving one behind is not free. Everywhere else in Lisa the orphan doctrine
|
|
618
|
+
// is absolute — an orphaned block is still read, and the reader cannot tell it
|
|
619
|
+
// from the live one — so a change that started leaving orphans would trade
|
|
620
|
+
// this bug for the one the marker versioning already fixed.
|
|
621
|
+
//
|
|
622
|
+
// The block itself answers the question: the accounts in its role ARNs are
|
|
623
|
+
// this project's if they are the accounts we are about to write. A bundle
|
|
624
|
+
// names its own accounts, so a match is attribution rather than a guess. That
|
|
625
|
+
// makes the ordinary single-tenant upgrade replace its block silently and
|
|
626
|
+
// leave nothing behind, and confines the reported-not-deleted case to a block
|
|
627
|
+
// that provably belongs to a different project.
|
|
628
|
+
const configFile = join(dir, "config");
|
|
629
|
+
const existingConfig = exists(configFile)
|
|
630
|
+
? String(read(configFile, "utf8"))
|
|
631
|
+
: "";
|
|
632
|
+
const claimLegacy =
|
|
633
|
+
pruneLegacy || legacyIsOurs(existingConfig, rendered.config);
|
|
634
|
+
|
|
635
|
+
// Refuse to write a profile name someone else already uses.
|
|
636
|
+
//
|
|
637
|
+
// AWS does not error on a duplicate `[profile x]` — it resolves one and
|
|
638
|
+
// ignores the other. So writing a name next to an operator's existing SSO
|
|
639
|
+
// profile of the same name would silently run some calls as the wrong
|
|
640
|
+
// identity, which is worse than either winning outright. Merging protects
|
|
641
|
+
// their sections from being deleted; this protects them from being shadowed.
|
|
642
|
+
//
|
|
643
|
+
// "Someone else" now includes another Lisa project. Prefixing every name with
|
|
644
|
+
// its owner should make that impossible rather than merely detectable, so
|
|
645
|
+
// this arm is the backstop for the case prefixing cannot prevent — two owners
|
|
646
|
+
// whose prefix and stage happen to reduce to one final name.
|
|
647
|
+
//
|
|
648
|
+
// Deliberately not resolved by renaming theirs: this module writes its own
|
|
649
|
+
// block and nothing else. The owner of the existing entry renames it
|
|
650
|
+
// (conventionally to `-sso`) and re-runs.
|
|
651
|
+
const collisions = collidingProfiles(dir, rendered.profiles, {
|
|
652
|
+
exists,
|
|
653
|
+
read,
|
|
654
|
+
owner: scope,
|
|
655
|
+
// A legacy block this run is about to replace is not a collision; one it is
|
|
656
|
+
// leaving in place is, because both sections would then exist and AWS would
|
|
657
|
+
// resolve exactly one of them.
|
|
658
|
+
includeLegacy: !claimLegacy,
|
|
659
|
+
});
|
|
660
|
+
if (collisions.length > 0) {
|
|
661
|
+
const quoted = collisions.map(c => `"${c.name}"`).join(", ");
|
|
662
|
+
const holder = collisions[0].owner;
|
|
663
|
+
throw new Error(
|
|
664
|
+
holder === null
|
|
665
|
+
? `~/.aws/config already defines ${quoted} outside any lisa-managed ` +
|
|
666
|
+
`block.\n` +
|
|
667
|
+
`Writing them would create duplicate sections, and AWS resolves ` +
|
|
668
|
+
`only one — some calls would silently use the wrong identity.\n` +
|
|
669
|
+
`Rename the existing entries (for example to ` +
|
|
670
|
+
`"${collisions[0].name}-sso") and run this again.`
|
|
671
|
+
: `~/.aws/config already defines ${quoted} for the Lisa project ` +
|
|
672
|
+
`"${holder}", not "${scope}".\n` +
|
|
673
|
+
`AWS resolves only one section of a duplicated name, so writing ` +
|
|
674
|
+
`these would silently point "${holder}" at ${scope}'s account — ` +
|
|
675
|
+
`and it would authenticate, because the credentials are real.\n` +
|
|
676
|
+
`Rename one project's namespace so the two do not reduce to the ` +
|
|
677
|
+
`same profile name. Nothing was changed.`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Whether the deprecated bare-named twins go in beside the owned names.
|
|
682
|
+
//
|
|
683
|
+
// ALL OR NOTHING. A half-written legacy family is worse than none: a bare
|
|
684
|
+
// `[profile <stage>]` whose `source_profile` names a bare section belonging to
|
|
685
|
+
// someone else is a profile that resolves into another project's account, and
|
|
686
|
+
// it is exactly the failure the owned names exist to remove.
|
|
687
|
+
const legacyHolder = noResolveOnly
|
|
688
|
+
? null
|
|
689
|
+
: resolveOnlyHolder(dir, rendered.resolveOnly, {
|
|
690
|
+
exists,
|
|
691
|
+
read,
|
|
692
|
+
owner: scope,
|
|
693
|
+
includeLegacy: !claimLegacy,
|
|
694
|
+
});
|
|
695
|
+
// `undefined` is the only free state. `null` means a section outside every
|
|
696
|
+
// managed block already holds the name — an operator's, or an external
|
|
697
|
+
// generator's — and shadowing that is the duplicate-section failure this
|
|
698
|
+
// module refuses everywhere else.
|
|
699
|
+
const writeResolveOnly =
|
|
700
|
+
!noResolveOnly && (claimResolveOnly || legacyHolder === undefined);
|
|
701
|
+
|
|
702
|
+
mkdir(dir, { recursive: true, mode: 0o700 });
|
|
703
|
+
chmod(dir, 0o700);
|
|
704
|
+
|
|
705
|
+
// Merge into a delimited block; never replace the file.
|
|
706
|
+
//
|
|
707
|
+
// Refusing whole files whenever one already existed sounded safe and was
|
|
708
|
+
// worse than useless: a container ships `~/.aws/config` containing a bare
|
|
709
|
+
// `[default]`, so the guard fired every time, wrote nothing, and returned
|
|
710
|
+
// silently — while AWS_PROFILE was still derived from the bundle. The session
|
|
711
|
+
// got a pointer to a profile that did not exist:
|
|
712
|
+
//
|
|
713
|
+
// The config profile (agent-dev) could not be found
|
|
714
|
+
//
|
|
715
|
+
// Merging keeps the operator's own sections intact AND writes ours, which is
|
|
716
|
+
// what the guard was actually for. Same delimited-block approach as the shell
|
|
717
|
+
// profile, and `#` is a comment in the shared-config format so the markers are
|
|
718
|
+
// inert to every consumer.
|
|
719
|
+
for (const [name, body, resolveOnlyBody] of [
|
|
720
|
+
["credentials", rendered.credentials, rendered.resolveOnly.credentials],
|
|
721
|
+
["config", rendered.config, rendered.resolveOnly.config],
|
|
722
|
+
]) {
|
|
723
|
+
const file = join(dir, name);
|
|
724
|
+
const current = exists(file) ? String(read(file, "utf8")) : "";
|
|
725
|
+
// Both halves live INSIDE this owner's block. That is what keeps the
|
|
726
|
+
// resolve-only names owned rather than anonymous — another project's run
|
|
727
|
+
// leaves them alone instead of replacing them, and this project's next run
|
|
728
|
+
// regenerates the whole block, so nothing accumulates or drifts.
|
|
729
|
+
const merged = writeResolveOnly
|
|
730
|
+
? `${body.trimEnd()}\n\n${resolveOnlyBody}`
|
|
731
|
+
: body;
|
|
732
|
+
write(file, upsertManagedBlock(current, merged, scope, claimLegacy));
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return rendered.profiles;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Who already holds any part of the deprecated bare-named family.
|
|
740
|
+
*
|
|
741
|
+
* Both files are consulted, because the family only works whole: the bare
|
|
742
|
+
* `[profile <stage>]` sections in `config` are useless without the bare source
|
|
743
|
+
* profile in `credentials` that they assume from.
|
|
744
|
+
* @param {string} dir The `.aws` directory.
|
|
745
|
+
* @param {{profiles: string[], sourceProfile: string}} resolveOnly The bare half.
|
|
746
|
+
* @param {object} io `exists`, `read`, `owner`, `includeLegacy` seams.
|
|
747
|
+
* @returns {string|null|undefined} The holding project, `null` when held
|
|
748
|
+
* outside any managed block, or `undefined` when nobody holds it.
|
|
749
|
+
*/
|
|
750
|
+
function resolveOnlyHolder(dir, resolveOnly, io) {
|
|
751
|
+
const inConfig = collidingProfiles(dir, resolveOnly.profiles, io);
|
|
752
|
+
if (inConfig.length > 0) return inConfig[0].owner;
|
|
753
|
+
return sourceProfileHolder(dir, resolveOnly.sourceProfile, io);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Who holds a bare source-profile section in `~/.aws/credentials`.
|
|
758
|
+
*
|
|
759
|
+
* Separate from `collidingProfiles` because a credentials section is spelled
|
|
760
|
+
* `[name]`, not `[profile name]`, and reusing the config reader here would
|
|
761
|
+
* silently match nothing — a guard that always passes.
|
|
762
|
+
* @param {string} dir The `.aws` directory.
|
|
763
|
+
* @param {string} name The section name.
|
|
764
|
+
* @param {object} io `exists`, `read`, `owner`, `includeLegacy` seams.
|
|
765
|
+
* @returns {string|null|undefined} As {@link resolveOnlyHolder}.
|
|
766
|
+
*/
|
|
767
|
+
export function sourceProfileHolder(dir, name, io = {}) {
|
|
768
|
+
const {
|
|
769
|
+
exists = existsSync,
|
|
770
|
+
read = readFileSync,
|
|
771
|
+
owner = null,
|
|
772
|
+
includeLegacy = false,
|
|
773
|
+
} = io;
|
|
774
|
+
const file = join(dir, "credentials");
|
|
775
|
+
if (!exists(file)) return undefined;
|
|
776
|
+
|
|
777
|
+
const text = String(read(file, "utf8"));
|
|
778
|
+
const ours = block =>
|
|
779
|
+
(block.owner === owner && owner !== null) ||
|
|
780
|
+
(block.owner === null && !includeLegacy);
|
|
781
|
+
const header = new RegExp(
|
|
782
|
+
`^\\s*\\[${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\]`,
|
|
783
|
+
"m"
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
for (const block of familyBlocks(text, MANAGED_FAMILY)) {
|
|
787
|
+
if (ours(block)) continue;
|
|
788
|
+
if (header.test(block.body)) return block.owner;
|
|
789
|
+
}
|
|
790
|
+
// Outside every managed block: an operator's own section, or one written by a
|
|
791
|
+
// generator that does not go through this module at all.
|
|
792
|
+
return header.test(stripFamilyBlocks(text, MANAGED_FAMILY, () => true))
|
|
793
|
+
? null
|
|
794
|
+
: undefined;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* The 12-digit accounts named by the role ARNs in some shared-config text.
|
|
799
|
+
* @param {string} text Config text.
|
|
800
|
+
* @returns {Set<string>} Account ids.
|
|
801
|
+
*/
|
|
802
|
+
function accountsIn(text) {
|
|
803
|
+
return new Set(
|
|
804
|
+
[...text.matchAll(/^\s*role_arn\s*=\s*arn:[^:]*:iam::(\d+):/gm)].map(
|
|
805
|
+
found => found[1]
|
|
806
|
+
)
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Whether every pre-ownership block present is this project's own past output.
|
|
812
|
+
*
|
|
813
|
+
* Conservative on purpose. A legacy block with no role ARN at all cannot be
|
|
814
|
+
* attributed, so it is not claimed; and one account belonging to someone else is
|
|
815
|
+
* enough to leave the whole population alone, because a partial claim would
|
|
816
|
+
* delete a block this process could not read the ownership of.
|
|
817
|
+
* @param {string} current Existing config contents.
|
|
818
|
+
* @param {string} ours The config body about to be written.
|
|
819
|
+
* @returns {boolean} Whether the legacy blocks may be replaced.
|
|
820
|
+
*/
|
|
821
|
+
function legacyIsOurs(current, ours) {
|
|
822
|
+
const legacy = familyBlocks(current, MANAGED_FAMILY).filter(
|
|
823
|
+
block => block.owner === null
|
|
824
|
+
);
|
|
825
|
+
if (legacy.length === 0) return false;
|
|
826
|
+
|
|
827
|
+
const mine = accountsIn(ours);
|
|
828
|
+
return legacy.every(block => {
|
|
829
|
+
const theirs = accountsIn(block.body);
|
|
830
|
+
return theirs.size > 0 && [...theirs].every(account => mine.has(account));
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
export function materialize(cfg = readConfig(), options = {}) {
|
|
835
|
+
// `requested` distinguishes an OPERATOR asking from a flow deciding.
|
|
836
|
+
//
|
|
837
|
+
// The capability governs automated behaviour: a session-start hook on a
|
|
838
|
+
// laptop must not write credentials to disk, because the provider CLI is
|
|
839
|
+
// authenticated there and a copy would add drift and exposure without adding
|
|
840
|
+
// capability. That reasoning holds, and the guard below still enforces it.
|
|
841
|
+
//
|
|
842
|
+
// It does not hold for `lisa environment local`, where the operator typed the
|
|
843
|
+
// command whose entire purpose is to put credentials on this machine — the
|
|
844
|
+
// AWS `-static` profiles reference variables that exist only once the env
|
|
845
|
+
// file is materialized, so refusing there means the profiles never work.
|
|
846
|
+
//
|
|
847
|
+
// Two different questions, so two different answers, rather than flipping
|
|
848
|
+
// `local` to `materialized: true` and silently starting to write on every
|
|
849
|
+
// machine that upgrades.
|
|
850
|
+
if (!cfg.capabilities.mayWriteValues && !options.requested) {
|
|
851
|
+
throw new Error(
|
|
852
|
+
`surface "${cfg.surface}" may not write secret values to disk.\n` +
|
|
853
|
+
`It can read through to the provider, so a copy on disk would add drift ` +
|
|
854
|
+
`and exposure without adding capability.\n` +
|
|
855
|
+
`Run 'lisa environment local --tenant=<name>' to ask for it explicitly.`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const selected = fetchAll(cfg);
|
|
860
|
+
if (!selected.size) {
|
|
861
|
+
throw new Error(
|
|
862
|
+
"no exportable secrets matched the configured boundary.\n" +
|
|
863
|
+
"Check the machine account's project grants and any narrowing filters."
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// The AWS bundle is stored as one JSON blob under a name no SDK reads, so
|
|
868
|
+
// the variables it implies are derived here. Without them a host-injected
|
|
869
|
+
// AWS_ACCESS_KEY_ID wins — environment variables outrank profile files in the
|
|
870
|
+
// credential chain — and every AWS call fails with InvalidClientTokenId even
|
|
871
|
+
// though the real credential materialized correctly.
|
|
872
|
+
// The namespace IS the owner. It already scopes `~/.config/<namespace>`, so
|
|
873
|
+
// two repositories of one tenant share and two tenants do not — exactly the
|
|
874
|
+
// sharing model the shared write paths below were missing.
|
|
875
|
+
const owner = assertOwner(cfg.namespace, "the materialized credentials");
|
|
876
|
+
|
|
877
|
+
const derived = deriveAwsEnvironment(selected, owner);
|
|
878
|
+
for (const [name, entry] of derived) selected.set(name, entry);
|
|
879
|
+
|
|
880
|
+
const { dir, valuesFile, notesFile } = materializedPaths(cfg.namespace);
|
|
881
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
882
|
+
chmodSync(dir, 0o700);
|
|
883
|
+
writeAtomic(valuesFile, renderEnv(selected));
|
|
884
|
+
writeAtomic(notesFile, renderNotes(selected));
|
|
885
|
+
|
|
886
|
+
// Writing the file is not the same as the values being in effect.
|
|
887
|
+
//
|
|
888
|
+
// A materialized secrets.env that nothing sources changes nothing: the agent's
|
|
889
|
+
// shell starts from the container's own environment, so a host-injected
|
|
890
|
+
// AWS_ACCESS_KEY_ID keeps winning and every call fails with
|
|
891
|
+
// InvalidClientTokenId while the correct credential sits on disk, correct and
|
|
892
|
+
// unused. Deriving the variables (above) only fixes precedence WITHIN the
|
|
893
|
+
// file — something still has to load the file.
|
|
894
|
+
//
|
|
895
|
+
// So the shell profile sources it. That is what makes "the credential is
|
|
896
|
+
// materialized" and "the credential is usable" the same statement.
|
|
897
|
+
const sourced = installProfileSourcing(valuesFile, { owner });
|
|
898
|
+
|
|
899
|
+
// The environments live in separate AWS accounts, reached by assuming a role
|
|
900
|
+
// per environment. Without these files `--profile <owner>-agent-dev` fails
|
|
901
|
+
// with "profile not found" and everything silently falls back to the
|
|
902
|
+
// bootstrap identity, which can assume roles and do nothing else.
|
|
903
|
+
const profiles = installAwsProfiles(
|
|
904
|
+
parseBootstrap(selected.get(BOOTSTRAP_KEY)?.value),
|
|
905
|
+
{ owner }
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
return {
|
|
909
|
+
count: selected.size,
|
|
910
|
+
derived: derived.size,
|
|
911
|
+
dir,
|
|
912
|
+
sourced,
|
|
913
|
+
profiles,
|
|
914
|
+
legacy: legacyManagedProfiles(join(process.env.HOME || homedir(), ".aws")),
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function main() {
|
|
919
|
+
const cfg = readConfig();
|
|
920
|
+
|
|
921
|
+
// `--aws-profiles-only` writes ~/.aws and NOTHING else, on any surface.
|
|
922
|
+
//
|
|
923
|
+
// A laptop refuses to materialize secrets to disk, deliberately: read-through
|
|
924
|
+
// to the provider adds no drift and leaves no copy. That rule is about the
|
|
925
|
+
// thirteen values in secrets.env, and it stays exactly as it was here.
|
|
926
|
+
//
|
|
927
|
+
// The AWS profiles are a different bargain, requested explicitly. Agents
|
|
928
|
+
// working on a developer's machine should act as RemoteAgent rather than
|
|
929
|
+
// borrowing the human's SSO identity — separate attribution in CloudTrail,
|
|
930
|
+
// the role's blast radius instead of a person's, and the same `agent-*`
|
|
931
|
+
// profile names as a container so a script does not need two vocabularies.
|
|
932
|
+
//
|
|
933
|
+
// The cost is real and belongs in the open: `source_profile` needs a
|
|
934
|
+
// long-lived key pair, so this writes one to a machine that is not
|
|
935
|
+
// disposable, which is the thing the local surface otherwise prevents. It is
|
|
936
|
+
// therefore opt-in per run, never automatic, and never part of a normal
|
|
937
|
+
// materialize.
|
|
938
|
+
if (process.argv.includes("--aws-profiles-only")) {
|
|
939
|
+
const selected = fetchAll(cfg);
|
|
940
|
+
const bundle = parseBootstrap(selected.get(BOOTSTRAP_KEY)?.value);
|
|
941
|
+
const owner = assertOwner(cfg.namespace, "~/.aws");
|
|
942
|
+
const written = installAwsProfiles(bundle, { owner });
|
|
943
|
+
|
|
944
|
+
if (written.length === 0) {
|
|
945
|
+
console.log(
|
|
946
|
+
`no AWS profiles written — ${BOOTSTRAP_KEY} is absent, unparseable, ` +
|
|
947
|
+
`or declares no profile carrying a roleArn.`
|
|
948
|
+
);
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
console.log(
|
|
952
|
+
`wrote ${written.length} AWS profile(s): ${written.join(", ")}`
|
|
953
|
+
);
|
|
954
|
+
console.log(
|
|
955
|
+
` Sourced from a long-lived key pair in ~/.aws/credentials. Existing\n` +
|
|
956
|
+
` sections were preserved — only "${owner}"'s block was replaced.\n` +
|
|
957
|
+
` Use them explicitly: aws --profile ${written[0]} ...`
|
|
958
|
+
);
|
|
959
|
+
reportLegacyProfiles();
|
|
960
|
+
reportResolveOnlySlot(cfg.namespace);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (process.argv.includes("--dry-run")) {
|
|
965
|
+
const selected = fetchAll(cfg);
|
|
966
|
+
// Derive here too, or the preview lies in the one place it matters most:
|
|
967
|
+
// the derived names are exactly the ones that override ambient host
|
|
968
|
+
// credentials, so omitting them hides the most surprising effect of the run
|
|
969
|
+
// and reports a smaller count than the real write produces.
|
|
970
|
+
for (const [name, entry] of deriveAwsEnvironment(
|
|
971
|
+
selected,
|
|
972
|
+
assertOwner(cfg.namespace, "the materialized credentials")
|
|
973
|
+
)) {
|
|
974
|
+
selected.set(name, entry);
|
|
975
|
+
}
|
|
976
|
+
const { dir } = materializedPaths(cfg.namespace);
|
|
977
|
+
console.log(`would write ${selected.size} secret(s) to ${dir}`);
|
|
978
|
+
console.log([...selected.keys()].sort().join("\n"));
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
const { count, derived, dir } = materialize(cfg);
|
|
982
|
+
console.log(`materialized ${count} secret(s) and their notes into ${dir}`);
|
|
983
|
+
if (derived > 0) {
|
|
984
|
+
// Do not claim an override this does not perform.
|
|
985
|
+
//
|
|
986
|
+
// This line used to end "overriding any ambient value". That was true when
|
|
987
|
+
// the key pair was exported and stopped being true when the pair moved into
|
|
988
|
+
// ~/.aws and AWS_PROFILE took its place — AWS_PROFILE loses to ambient
|
|
989
|
+
// environment credentials, it does not beat them. The stale sentence sent
|
|
990
|
+
// two separate investigations after a credential problem that did not
|
|
991
|
+
// exist, while the real fault was that nothing sourced the file. A message
|
|
992
|
+
// that overclaims costs more than no message.
|
|
993
|
+
console.log(
|
|
994
|
+
` ${derived} AWS variable(s) derived from the bootstrap bundle. ` +
|
|
995
|
+
`These take\n effect only in a shell that sourced the materialized ` +
|
|
996
|
+
`env file; ambient\n credentials outrank AWS_PROFILE, so prefer an ` +
|
|
997
|
+
`explicit --profile.`
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
reportLegacyProfiles();
|
|
1001
|
+
reportResolveOnlySlot(cfg.namespace);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Say so when the deprecated bare names went to a different project.
|
|
1006
|
+
*
|
|
1007
|
+
* Silence here would be the worst of both worlds. A caller that has not
|
|
1008
|
+
* migrated still names the bare profiles; if another project on this machine
|
|
1009
|
+
* holds them, those names resolve into that project's account and the run would
|
|
1010
|
+
* otherwise report success. Loud-and-wrong is recoverable, silent-and-wrong is
|
|
1011
|
+
* the defect this whole change is about.
|
|
1012
|
+
* @param {string} owner The tenant this run materialized for.
|
|
1013
|
+
*/
|
|
1014
|
+
function reportResolveOnlySlot(owner) {
|
|
1015
|
+
const holder = sourceProfileHolder(
|
|
1016
|
+
join(process.env.HOME || homedir(), ".aws"),
|
|
1017
|
+
RESOLVE_ONLY_LEGACY_SOURCE_PROFILE,
|
|
1018
|
+
{ owner }
|
|
1019
|
+
);
|
|
1020
|
+
if (holder === undefined || holder === owner) return;
|
|
1021
|
+
console.log(
|
|
1022
|
+
` The deprecated bare profile names were NOT written for "${owner}".\n` +
|
|
1023
|
+
` ${holder === null ? "A section outside any lisa-managed block" : `The project "${holder}"`} ` +
|
|
1024
|
+
`already holds "${RESOLVE_ONLY_LEGACY_SOURCE_PROFILE}".\n` +
|
|
1025
|
+
` They are one shared slot, so only one project on a machine can have ` +
|
|
1026
|
+
`them.\n Use the owned names — aws --profile ${owner}-<stage> — or ` +
|
|
1027
|
+
`re-run with\n LISA_SECRETS_CLAIM_LEGACY_PROFILES=1 to take the bare ` +
|
|
1028
|
+
`names deliberately.`
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* Name any pre-ownership profiles still sitting in `~/.aws/config`.
|
|
1034
|
+
*
|
|
1035
|
+
* Reported on every run rather than once, because the population never drains
|
|
1036
|
+
* on its own and nothing else ever revisits this file — there is no apply, diff
|
|
1037
|
+
* or review that would surface it. Never deleted here: an unowned profile may
|
|
1038
|
+
* belong to another project on this machine, and removing it would be the same
|
|
1039
|
+
* silent consumption in the other direction.
|
|
1040
|
+
*/
|
|
1041
|
+
function reportLegacyProfiles() {
|
|
1042
|
+
const stale = legacyManagedProfiles(
|
|
1043
|
+
join(process.env.HOME || homedir(), ".aws")
|
|
1044
|
+
);
|
|
1045
|
+
if (stale.length === 0) return;
|
|
1046
|
+
console.log(
|
|
1047
|
+
` ~/.aws/config still holds ${stale.length} profile(s) written before ` +
|
|
1048
|
+
`Lisa\n recorded which project a profile belongs to: ` +
|
|
1049
|
+
`${stale.join(", ")}.\n` +
|
|
1050
|
+
` They name a stage but no owner, so on a machine serving more than one ` +
|
|
1051
|
+
`\n project they can resolve to another project's account. Review them, ` +
|
|
1052
|
+
`or\n re-run with LISA_SECRETS_PRUNE_LEGACY_PROFILES=1 to remove them.`
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* Whether this module is the one node was asked to run.
|
|
1058
|
+
*
|
|
1059
|
+
* Both sides are realpath'd: a raw URL comparison answers "no" through a
|
|
1060
|
+
* symlinked checkout, a git worktree, or a /tmp path on macOS, so the module
|
|
1061
|
+
* loads, runs nothing and exits 0 — a silent no-op that reads as success.
|
|
1062
|
+
*
|
|
1063
|
+
* A local copy rather than an import: plugin payload scripts ship standalone,
|
|
1064
|
+
* with no `lib/` sibling to import from once installed.
|
|
1065
|
+
* @param {string} moduleUrl - The caller's own `import.meta.url`.
|
|
1066
|
+
* @param {string | undefined} [argv1] - Entry path; defaults to `process.argv[1]`.
|
|
1067
|
+
* @returns {boolean} Whether the caller should run its CLI body.
|
|
1068
|
+
*/
|
|
1069
|
+
function invokedAsScript(moduleUrl, argv1 = process.argv[1]) {
|
|
1070
|
+
if (!argv1) return false;
|
|
1071
|
+
try {
|
|
1072
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
|
|
1073
|
+
} catch {
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
if (invokedAsScript(import.meta.url)) {
|
|
1079
|
+
try {
|
|
1080
|
+
main();
|
|
1081
|
+
} catch (err) {
|
|
1082
|
+
console.error(err.message);
|
|
1083
|
+
process.exit(1);
|
|
1084
|
+
}
|
|
1085
|
+
}
|