@codyswann/lisa 4.74.8 → 4.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +60 -0
- package/all/copy-overwrite/scripts/lib/npm-update-cancel-origin.mjs +4 -2
- package/all/copy-overwrite/scripts/lib/npm-update-checkpoint-codec.mjs +141 -0
- package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +162 -116
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +1 -0
- package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +9 -60
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +18 -6
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +20 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/lisa-skill-sources.d.ts +3 -3
- package/dist/core/lisa-skill-sources.d.ts.map +1 -1
- package/dist/core/lisa-skill-sources.js +3 -2
- package/dist/core/lisa-skill-sources.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +739 -11
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-cdk-devin/skills/setup-aws-accounts/SKILL.md +292 -0
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/.claude-plugin/plugin.json +8 -0
- package/plugins/lisa-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-devin/.mcp.json +8 -0
- package/plugins/lisa-devin/agents/architecture-specialist.md +32 -0
- package/plugins/lisa-devin/agents/bug-fixer.md +43 -0
- package/plugins/lisa-devin/agents/builder.md +44 -0
- package/plugins/lisa-devin/agents/confluence-prd-intake.md +65 -0
- package/plugins/lisa-devin/agents/debug-specialist.md +34 -0
- package/plugins/lisa-devin/agents/eval-specialist.md +33 -0
- package/plugins/lisa-devin/agents/git-history-analyzer.md +183 -0
- package/plugins/lisa-devin/agents/github-agent.md +155 -0
- package/plugins/lisa-devin/agents/github-build-intake.md +64 -0
- package/plugins/lisa-devin/agents/github-prd-intake.md +66 -0
- package/plugins/lisa-devin/agents/jira-agent.md +135 -0
- package/plugins/lisa-devin/agents/jira-build-intake.md +64 -0
- package/plugins/lisa-devin/agents/learner.md +85 -0
- package/plugins/lisa-devin/agents/learning-judge.md +119 -0
- package/plugins/lisa-devin/agents/learnings-synthesizer.md +155 -0
- package/plugins/lisa-devin/agents/linear-agent.md +138 -0
- package/plugins/lisa-devin/agents/linear-build-intake.md +64 -0
- package/plugins/lisa-devin/agents/linear-prd-intake.md +66 -0
- package/plugins/lisa-devin/agents/notion-prd-intake.md +63 -0
- package/plugins/lisa-devin/agents/performance-specialist.md +26 -0
- package/plugins/lisa-devin/agents/pr-mining-specialist.md +85 -0
- package/plugins/lisa-devin/agents/product-specialist.md +30 -0
- package/plugins/lisa-devin/agents/quality-specialist.md +30 -0
- package/plugins/lisa-devin/agents/security-specialist.md +43 -0
- package/plugins/lisa-devin/agents/skill-evaluator.md +205 -0
- package/plugins/lisa-devin/agents/spec-conformance-specialist.md +56 -0
- package/plugins/lisa-devin/agents/test-specialist.md +32 -0
- package/plugins/lisa-devin/agents/tracker-mining-specialist.md +85 -0
- package/plugins/lisa-devin/agents/verification-specialist.md +145 -0
- package/plugins/lisa-devin/hooks/auto-update.mjs +824 -0
- package/plugins/lisa-devin/hooks/auto-update.sh +31 -0
- package/plugins/lisa-devin/hooks/block-blind-automerge.sh +1242 -0
- package/plugins/lisa-devin/hooks/block-direct-issue-create.sh +2603 -0
- package/plugins/lisa-devin/hooks/block-host-name-leak.mjs +412 -0
- package/plugins/lisa-devin/hooks/block-host-name-leak.sh +61 -0
- package/plugins/lisa-devin/hooks/block-instruction-file-edits.sh +207 -0
- package/plugins/lisa-devin/hooks/block-managed-file-edits.sh +1175 -0
- package/plugins/lisa-devin/hooks/block-no-verify.sh +2288 -0
- package/plugins/lisa-devin/hooks/block-shell-json-parsing.sh +263 -0
- package/plugins/lisa-devin/hooks/cleanup-stale-worktrees.sh +122 -0
- package/plugins/lisa-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-devin/hooks/discharge-work-item-gates.sh +90 -0
- package/plugins/lisa-devin/hooks/enforce-verification-gate.sh +506 -0
- package/plugins/lisa-devin/hooks/enforcement-vintage-npm.mjs +320 -0
- package/plugins/lisa-devin/hooks/enforcement-vintage.mjs +472 -0
- package/plugins/lisa-devin/hooks/enforcement-vintage.sh +48 -0
- package/plugins/lisa-devin/hooks/failure-signature-index.mjs +717 -0
- package/plugins/lisa-devin/hooks/failure-signature-index.sh +26 -0
- package/plugins/lisa-devin/hooks/guard-dedupe.bash +374 -0
- package/plugins/lisa-devin/hooks/inject-resolved-config.mjs +903 -0
- package/plugins/lisa-devin/hooks/inject-resolved-config.sh +107 -0
- package/plugins/lisa-devin/hooks/inject-rules.sh +49 -0
- package/plugins/lisa-devin/hooks/install-pkgs.sh +84 -0
- package/plugins/lisa-devin/hooks/operational-hazards.mjs +736 -0
- package/plugins/lisa-devin/hooks/operational-hazards.sh +28 -0
- package/plugins/lisa-devin/hooks/parity-safety-net-heredoc.py +1004 -0
- package/plugins/lisa-devin/hooks/parity-safety-net.sh +3275 -0
- package/plugins/lisa-devin/hooks/secrets-preflight.sh +72 -0
- package/plugins/lisa-devin/hooks/setup-jira-cli.sh +205 -0
- package/plugins/lisa-devin/hooks/shell-write-nudge.sh +86 -0
- package/plugins/lisa-devin/hooks/sonar-secrets.sh +205 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet-compare.mjs +518 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet-families.mjs +543 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet.mjs +568 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet.sh +22 -0
- package/plugins/lisa-devin/hooks/withdrawn-rulings.mjs +600 -0
- package/plugins/lisa-devin/hooks/withdrawn-rulings.sh +24 -0
- package/plugins/lisa-devin/hooks/worktree-binding-guard.mjs +1383 -0
- package/plugins/lisa-devin/hooks/worktree-binding-guard.sh +79 -0
- package/plugins/lisa-devin/hooks.json +247 -0
- package/plugins/lisa-devin/rules/eager/00-rule-index.md +82 -0
- package/plugins/lisa-devin/rules/eager/base-rules.md +85 -0
- package/plugins/lisa-devin/rules/eager/coding-philosophy.md +27 -0
- package/plugins/lisa-devin/rules/eager/config-resolution.md +37 -0
- package/plugins/lisa-devin/rules/eager/do-it-now.md +11 -0
- package/plugins/lisa-devin/rules/eager/empirical-inquiry.md +24 -0
- package/plugins/lisa-devin/rules/eager/falsifiable-checks.md +48 -0
- package/plugins/lisa-devin/rules/eager/history-audit.md +27 -0
- package/plugins/lisa-devin/rules/eager/integration-access-layer.md +19 -0
- package/plugins/lisa-devin/rules/eager/intent-routing.md +18 -0
- package/plugins/lisa-devin/rules/eager/learnings-ladder.md +18 -0
- package/plugins/lisa-devin/rules/eager/local-ci-first.md +15 -0
- package/plugins/lisa-devin/rules/eager/measurement-integrity.md +18 -0
- package/plugins/lisa-devin/rules/eager/never-name-a-downstream-project.md +9 -0
- package/plugins/lisa-devin/rules/eager/not-blocked-just-waiting.md +13 -0
- package/plugins/lisa-devin/rules/eager/operational-hazards.md +50 -0
- package/plugins/lisa-devin/rules/eager/report-actionability.md +26 -0
- package/plugins/lisa-devin/rules/eager/security-audit-handling.md +40 -0
- package/plugins/lisa-devin/rules/eager/session-status-updates.md +20 -0
- package/plugins/lisa-devin/rules/eager/settled-decisions.md +75 -0
- package/plugins/lisa-devin/rules/eager/stale-state-claims.md +26 -0
- package/plugins/lisa-devin/rules/eager/tool-access-gate.md +23 -0
- package/plugins/lisa-devin/rules/eager/tracked-work.md +9 -0
- package/plugins/lisa-devin/rules/eager/upstream-to-lisa.md +14 -0
- package/plugins/lisa-devin/rules/eager/verification.md +33 -0
- package/plugins/lisa-devin/rules/eager/wiki-knowledge-source.md +13 -0
- package/plugins/lisa-devin/rules/eager/withdrawn-rulings.md +102 -0
- package/plugins/lisa-devin/rules/reference/automation-runbook-contract.md +355 -0
- package/plugins/lisa-devin/rules/reference/base-rules.md +136 -0
- package/plugins/lisa-devin/rules/reference/bdd-e2e-coverage.md +301 -0
- package/plugins/lisa-devin/rules/reference/blocker-containment.md +165 -0
- package/plugins/lisa-devin/rules/reference/claim-archaeology.md +184 -0
- package/plugins/lisa-devin/rules/reference/claim-evidence-mapping.md +362 -0
- package/plugins/lisa-devin/rules/reference/claim-time-guards.md +135 -0
- package/plugins/lisa-devin/rules/reference/coding-philosophy.md +428 -0
- package/plugins/lisa-devin/rules/reference/config-resolution.md +1225 -0
- package/plugins/lisa-devin/rules/reference/control-reachability.md +166 -0
- package/plugins/lisa-devin/rules/reference/convergent-review.md +145 -0
- package/plugins/lisa-devin/rules/reference/credential-substrate-precedence.md +249 -0
- package/plugins/lisa-devin/rules/reference/dependency-decision-records.md +177 -0
- package/plugins/lisa-devin/rules/reference/dependency-internalization-kit.md +244 -0
- package/plugins/lisa-devin/rules/reference/dependency-trust-classes.md +291 -0
- package/plugins/lisa-devin/rules/reference/deployed-state-readback.md +142 -0
- package/plugins/lisa-devin/rules/reference/derived-branch-plan.md +214 -0
- package/plugins/lisa-devin/rules/reference/design-source-of-truth.md +260 -0
- package/plugins/lisa-devin/rules/reference/design-value-binding.md +311 -0
- package/plugins/lisa-devin/rules/reference/do-it-now.md +19 -0
- package/plugins/lisa-devin/rules/reference/documentation-source-paths.md +31 -0
- package/plugins/lisa-devin/rules/reference/empirical-inquiry.md +28 -0
- package/plugins/lisa-devin/rules/reference/factory-model.md +111 -0
- package/plugins/lisa-devin/rules/reference/falsifiable-checks.md +275 -0
- package/plugins/lisa-devin/rules/reference/history-audit.md +42 -0
- package/plugins/lisa-devin/rules/reference/integration-access-layer.md +89 -0
- package/plugins/lisa-devin/rules/reference/intent-routing.md +428 -0
- package/plugins/lisa-devin/rules/reference/leaf-only-lifecycle.md +227 -0
- package/plugins/lisa-devin/rules/reference/learnings-ladder.md +41 -0
- package/plugins/lisa-devin/rules/reference/local-ci-first.md +34 -0
- package/plugins/lisa-devin/rules/reference/measurement-integrity.md +119 -0
- package/plugins/lisa-devin/rules/reference/never-name-a-downstream-project.md +37 -0
- package/plugins/lisa-devin/rules/reference/not-blocked-just-waiting.md +41 -0
- package/plugins/lisa-devin/rules/reference/observability-audit.md +146 -0
- package/plugins/lisa-devin/rules/reference/operational-hazards.md +115 -0
- package/plugins/lisa-devin/rules/reference/prd-definition-of-ready.md +118 -0
- package/plugins/lisa-devin/rules/reference/prd-lifecycle-rollup.md +192 -0
- package/plugins/lisa-devin/rules/reference/pre-flight-autofill.md +250 -0
- package/plugins/lisa-devin/rules/reference/project-learnings.md +159 -0
- package/plugins/lisa-devin/rules/reference/promotion-contract.md +148 -0
- package/plugins/lisa-devin/rules/reference/readiness-rubric.md +242 -0
- package/plugins/lisa-devin/rules/reference/ready-role-filing.md +212 -0
- package/plugins/lisa-devin/rules/reference/rejection-detection.md +308 -0
- package/plugins/lisa-devin/rules/reference/repo-scope-split.md +119 -0
- package/plugins/lisa-devin/rules/reference/report-actionability.md +61 -0
- package/plugins/lisa-devin/rules/reference/reset-seed-coverage.md +286 -0
- package/plugins/lisa-devin/rules/reference/security-audit-handling.md +55 -0
- package/plugins/lisa-devin/rules/reference/session-status-updates.md +111 -0
- package/plugins/lisa-devin/rules/reference/settled-decisions.md +39 -0
- package/plugins/lisa-devin/rules/reference/stale-state-claims.md +88 -0
- package/plugins/lisa-devin/rules/reference/tool-access-gate.md +114 -0
- package/plugins/lisa-devin/rules/reference/tracked-work.md +49 -0
- package/plugins/lisa-devin/rules/reference/upstream-to-lisa.md +26 -0
- package/plugins/lisa-devin/rules/reference/usage-accounting.md +261 -0
- package/plugins/lisa-devin/rules/reference/verification.md +278 -0
- package/plugins/lisa-devin/rules/reference/wiki-knowledge-source.md +37 -0
- package/plugins/lisa-devin/rules/reference/withdrawn-rulings.md +148 -0
- package/plugins/lisa-devin/rules/reference/work-item-definition-of-ready.md +177 -0
- package/plugins/lisa-devin/rules/reference/work-item-trailer-definition.md +129 -0
- package/plugins/lisa-devin/scripts/automation-run-record.mjs +678 -0
- package/plugins/lisa-devin/scripts/automation-status-claude-adapter.mjs +804 -0
- package/plugins/lisa-devin/scripts/automation-status-codex-adapter.mjs +1081 -0
- package/plugins/lisa-devin/scripts/automation-status-contract-drift.mjs +392 -0
- package/plugins/lisa-devin/scripts/automation-status-expected-fleet.mjs +508 -0
- package/plugins/lisa-devin/scripts/automation-status-report.mjs +200 -0
- package/plugins/lisa-devin/scripts/automation-status-run-history.mjs +216 -0
- package/plugins/lisa-devin/scripts/automation-status-unrecorded-runs.mjs +127 -0
- package/plugins/lisa-devin/scripts/blocker-edge-resolution.mjs +150 -0
- package/plugins/lisa-devin/scripts/cross-pollinate.mjs +763 -0
- package/plugins/lisa-devin/scripts/design-bindings-probe.mjs +713 -0
- package/plugins/lisa-devin/scripts/design-intake-gate.mjs +470 -0
- package/plugins/lisa-devin/scripts/design-source-gate.mjs +610 -0
- package/plugins/lisa-devin/scripts/design-variable-ids.mjs +567 -0
- package/plugins/lisa-devin/scripts/doctor-report.mjs +635 -0
- package/plugins/lisa-devin/scripts/install-remote-agent-aws.mjs +443 -0
- package/plugins/lisa-devin/scripts/intake-blocker-reprobe.mjs +1065 -0
- package/plugins/lisa-devin/scripts/intake-prework-denominator.mjs +249 -0
- package/plugins/lisa-devin/scripts/lifecycle-label-trust.mjs +593 -0
- package/plugins/lisa-devin/scripts/linear-state-write-target.mjs +362 -0
- package/plugins/lisa-devin/scripts/plugin-sync-explain.mjs +654 -0
- package/plugins/lisa-devin/scripts/pr-arming-sweep.mjs +561 -0
- package/plugins/lisa-devin/scripts/project-ideation-idempotency-harness.mjs +338 -0
- package/plugins/lisa-devin/scripts/qa-signal-lifecycle.mjs +523 -0
- package/plugins/lisa-devin/scripts/queue-contract-resolution.mjs +583 -0
- package/plugins/lisa-devin/scripts/queue-health-classification.mjs +157 -0
- package/plugins/lisa-devin/scripts/queue-status-build-readers.mjs +561 -0
- package/plugins/lisa-devin/scripts/queue-status-prd-readers.mjs +452 -0
- package/plugins/lisa-devin/scripts/remote-agent-aws-setup.sh +677 -0
- package/plugins/lisa-devin/scripts/repo-scope-vocabulary-audit.mjs +458 -0
- package/plugins/lisa-devin/scripts/resolve-lifecycle-role.mjs +347 -0
- package/plugins/lisa-devin/scripts/rollup-blocker-classification.mjs +616 -0
- package/plugins/lisa-devin/scripts/scoping-label-audit.mjs +474 -0
- package/plugins/lisa-devin/skills/lisa-acceptance-criteria/SKILL.md +73 -0
- package/plugins/lisa-devin/skills/lisa-agent-design-best-practices/SKILL.md +242 -0
- package/plugins/lisa-devin/skills/lisa-agent-ready/SKILL.md +271 -0
- package/plugins/lisa-devin/skills/lisa-analyze-claude-remote/SKILL.md +434 -0
- package/plugins/lisa-devin/skills/lisa-atlassian-access/SKILL.md +469 -0
- package/plugins/lisa-devin/skills/lisa-atlassian-access/scripts/markdown-to-adf.mjs +236 -0
- package/plugins/lisa-devin/skills/lisa-attribute-failure/SKILL.md +142 -0
- package/plugins/lisa-devin/skills/lisa-automation-status/SKILL.md +149 -0
- package/plugins/lisa-devin/skills/lisa-bug-triage/SKILL.md +23 -0
- package/plugins/lisa-devin/skills/lisa-capability-drift/SKILL.md +71 -0
- package/plugins/lisa-devin/skills/lisa-codebase-research/SKILL.md +87 -0
- package/plugins/lisa-devin/skills/lisa-codify-verification/SKILL.md +213 -0
- package/plugins/lisa-devin/skills/lisa-confluence-prd-intake/SKILL.md +417 -0
- package/plugins/lisa-devin/skills/lisa-confluence-to-tracker/SKILL.md +469 -0
- package/plugins/lisa-devin/skills/lisa-confluence-write-prd/SKILL.md +111 -0
- package/plugins/lisa-devin/skills/lisa-cross-pollinate/SKILL.md +175 -0
- package/plugins/lisa-devin/skills/lisa-debrief/SKILL.md +96 -0
- package/plugins/lisa-devin/skills/lisa-debrief-apply/SKILL.md +106 -0
- package/plugins/lisa-devin/skills/lisa-delivery-effectiveness/SKILL.md +62 -0
- package/plugins/lisa-devin/skills/lisa-design-intake/SKILL.md +198 -0
- package/plugins/lisa-devin/skills/lisa-detect-tooling/SKILL.md +110 -0
- package/plugins/lisa-devin/skills/lisa-detect-tooling/scripts/commands.mjs +479 -0
- package/plugins/lisa-devin/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +658 -0
- package/plugins/lisa-devin/skills/lisa-doctor/SKILL.md +743 -0
- package/plugins/lisa-devin/skills/lisa-drive-pr-to-merge/SKILL.md +1587 -0
- package/plugins/lisa-devin/skills/lisa-epic-triage/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-evaluation-suite/SKILL.md +63 -0
- package/plugins/lisa-devin/skills/lisa-exploratory-qa/SKILL.md +185 -0
- package/plugins/lisa-devin/skills/lisa-fix-linter-error/SKILL.md +45 -0
- package/plugins/lisa-devin/skills/lisa-generate-claude-remote-build-script/SKILL.md +235 -0
- package/plugins/lisa-devin/skills/lisa-git-commit/SKILL.md +53 -0
- package/plugins/lisa-devin/skills/lisa-git-prune/SKILL.md +35 -0
- package/plugins/lisa-devin/skills/lisa-git-submit-pr/SKILL.md +170 -0
- package/plugins/lisa-devin/skills/lisa-github-add-journey/SKILL.md +118 -0
- package/plugins/lisa-devin/skills/lisa-github-build-intake/SKILL.md +745 -0
- package/plugins/lisa-devin/skills/lisa-github-claim/SKILL.md +27 -0
- package/plugins/lisa-devin/skills/lisa-github-create/SKILL.md +105 -0
- package/plugins/lisa-devin/skills/lisa-github-evidence/SKILL.md +120 -0
- package/plugins/lisa-devin/skills/lisa-github-journey/SKILL.md +134 -0
- package/plugins/lisa-devin/skills/lisa-github-prd-intake/SKILL.md +424 -0
- package/plugins/lisa-devin/skills/lisa-github-project-v2/SKILL.md +227 -0
- package/plugins/lisa-devin/skills/lisa-github-read-issue/SKILL.md +305 -0
- package/plugins/lisa-devin/skills/lisa-github-sync/SKILL.md +201 -0
- package/plugins/lisa-devin/skills/lisa-github-to-tracker/SKILL.md +456 -0
- package/plugins/lisa-devin/skills/lisa-github-validate-issue/SKILL.md +598 -0
- package/plugins/lisa-devin/skills/lisa-github-verify/SKILL.md +60 -0
- package/plugins/lisa-devin/skills/lisa-github-write-issue/SKILL.md +531 -0
- package/plugins/lisa-devin/skills/lisa-github-write-prd/SKILL.md +156 -0
- package/plugins/lisa-devin/skills/lisa-health/SKILL.md +142 -0
- package/plugins/lisa-devin/skills/lisa-health-drift-cron/SKILL.md +80 -0
- package/plugins/lisa-devin/skills/lisa-implement/SKILL.md +372 -0
- package/plugins/lisa-devin/skills/lisa-improve-code-complexity/SKILL.md +44 -0
- package/plugins/lisa-devin/skills/lisa-improve-harness/SKILL.md +487 -0
- package/plugins/lisa-devin/skills/lisa-improve-max-lines/SKILL.md +45 -0
- package/plugins/lisa-devin/skills/lisa-improve-max-lines-per-function/SKILL.md +46 -0
- package/plugins/lisa-devin/skills/lisa-improve-test-coverage/SKILL.md +47 -0
- package/plugins/lisa-devin/skills/lisa-improve-tests/SKILL.md +50 -0
- package/plugins/lisa-devin/skills/lisa-intake/SKILL.md +208 -0
- package/plugins/lisa-devin/skills/lisa-intake-explain/SKILL.md +279 -0
- package/plugins/lisa-devin/skills/lisa-jam-access/SKILL.md +128 -0
- package/plugins/lisa-devin/skills/lisa-jira-add-journey/SKILL.md +139 -0
- package/plugins/lisa-devin/skills/lisa-jira-build-intake/SKILL.md +556 -0
- package/plugins/lisa-devin/skills/lisa-jira-claim/SKILL.md +27 -0
- package/plugins/lisa-devin/skills/lisa-jira-create/SKILL.md +157 -0
- package/plugins/lisa-devin/skills/lisa-jira-evidence/SKILL.md +106 -0
- package/plugins/lisa-devin/skills/lisa-jira-evidence/scripts/post-evidence.sh +262 -0
- package/plugins/lisa-devin/skills/lisa-jira-journey/SKILL.md +168 -0
- package/plugins/lisa-devin/skills/lisa-jira-journey/scripts/generate-templates.py +239 -0
- package/plugins/lisa-devin/skills/lisa-jira-journey/scripts/parse-plan.py +570 -0
- package/plugins/lisa-devin/skills/lisa-jira-read-ticket/SKILL.md +199 -0
- package/plugins/lisa-devin/skills/lisa-jira-read-ticket/scripts/download-attachment.sh +211 -0
- package/plugins/lisa-devin/skills/lisa-jira-sync/SKILL.md +117 -0
- package/plugins/lisa-devin/skills/lisa-jira-validate-ticket/SKILL.md +525 -0
- package/plugins/lisa-devin/skills/lisa-jira-verify/SKILL.md +61 -0
- package/plugins/lisa-devin/skills/lisa-jira-write-ticket/SKILL.md +440 -0
- package/plugins/lisa-devin/skills/lisa-jsdoc-best-practices/SKILL.md +491 -0
- package/plugins/lisa-devin/skills/lisa-kane-browser/SKILL.md +80 -0
- package/plugins/lisa-devin/skills/lisa-learnings-audit/SKILL.md +490 -0
- package/plugins/lisa-devin/skills/lisa-linear-access/SKILL.md +483 -0
- package/plugins/lisa-devin/skills/lisa-linear-add-journey/SKILL.md +109 -0
- package/plugins/lisa-devin/skills/lisa-linear-build-intake/SKILL.md +548 -0
- package/plugins/lisa-devin/skills/lisa-linear-claim/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-linear-create/SKILL.md +150 -0
- package/plugins/lisa-devin/skills/lisa-linear-evidence/SKILL.md +124 -0
- package/plugins/lisa-devin/skills/lisa-linear-journey/SKILL.md +147 -0
- package/plugins/lisa-devin/skills/lisa-linear-prd-intake/SKILL.md +411 -0
- package/plugins/lisa-devin/skills/lisa-linear-read-issue/SKILL.md +201 -0
- package/plugins/lisa-devin/skills/lisa-linear-sync/SKILL.md +164 -0
- package/plugins/lisa-devin/skills/lisa-linear-to-tracker/SKILL.md +454 -0
- package/plugins/lisa-devin/skills/lisa-linear-validate-issue/SKILL.md +504 -0
- package/plugins/lisa-devin/skills/lisa-linear-verify/SKILL.md +65 -0
- package/plugins/lisa-devin/skills/lisa-linear-write-issue/SKILL.md +448 -0
- package/plugins/lisa-devin/skills/lisa-linear-write-prd/SKILL.md +98 -0
- package/plugins/lisa-devin/skills/lisa-monitor/SKILL.md +224 -0
- package/plugins/lisa-devin/skills/lisa-nightly-add-test-coverage/SKILL.md +40 -0
- package/plugins/lisa-devin/skills/lisa-nightly-improve-tests/SKILL.md +29 -0
- package/plugins/lisa-devin/skills/lisa-nightly-lower-code-complexity/SKILL.md +42 -0
- package/plugins/lisa-devin/skills/lisa-notion-access/SKILL.md +301 -0
- package/plugins/lisa-devin/skills/lisa-notion-prd-intake/SKILL.md +360 -0
- package/plugins/lisa-devin/skills/lisa-notion-to-tracker/SKILL.md +467 -0
- package/plugins/lisa-devin/skills/lisa-notion-write-prd/SKILL.md +111 -0
- package/plugins/lisa-devin/skills/lisa-parity-code-review/SKILL.md +83 -0
- package/plugins/lisa-devin/skills/lisa-parity-code-simplifier/SKILL.md +76 -0
- package/plugins/lisa-devin/skills/lisa-parity-coderabbit/SKILL.md +87 -0
- package/plugins/lisa-devin/skills/lisa-parity-sentry-sdk-setup/SKILL.md +375 -0
- package/plugins/lisa-devin/skills/lisa-parity-sentry-seer/SKILL.md +168 -0
- package/plugins/lisa-devin/skills/lisa-parity-skill-creator/SKILL.md +155 -0
- package/plugins/lisa-devin/skills/lisa-performance-review/SKILL.md +94 -0
- package/plugins/lisa-devin/skills/lisa-persist-learning/SKILL.md +247 -0
- package/plugins/lisa-devin/skills/lisa-plan/SKILL.md +64 -0
- package/plugins/lisa-devin/skills/lisa-plugin-sync-explain/SKILL.md +53 -0
- package/plugins/lisa-devin/skills/lisa-posthog-access/SKILL.md +140 -0
- package/plugins/lisa-devin/skills/lisa-prd-backlink/SKILL.md +274 -0
- package/plugins/lisa-devin/skills/lisa-prd-source-write/SKILL.md +104 -0
- package/plugins/lisa-devin/skills/lisa-prd-ticket-coverage/SKILL.md +187 -0
- package/plugins/lisa-devin/skills/lisa-product-walkthrough/SKILL.md +106 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/SKILL.md +417 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/evidence-card-format.md +21 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/host-project-only.md +22 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/idempotency-verification-harness.md +57 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/public-external-inspiration.md +22 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/unavailable-data-rejection.md +22 -0
- package/plugins/lisa-devin/skills/lisa-pull-request-review/SKILL.md +132 -0
- package/plugins/lisa-devin/skills/lisa-quality-review/SKILL.md +63 -0
- package/plugins/lisa-devin/skills/lisa-queue-status/SKILL.md +215 -0
- package/plugins/lisa-devin/skills/lisa-remote-dispatch/SKILL.md +109 -0
- package/plugins/lisa-devin/skills/lisa-remote-dispatch/scripts/dispatch.mjs +535 -0
- package/plugins/lisa-devin/skills/lisa-repair-intake/SKILL.md +1621 -0
- package/plugins/lisa-devin/skills/lisa-reproduce-bug/SKILL.md +75 -0
- package/plugins/lisa-devin/skills/lisa-research/SKILL.md +92 -0
- package/plugins/lisa-devin/skills/lisa-review-implementation/SKILL.md +209 -0
- package/plugins/lisa-devin/skills/lisa-review-local/SKILL.md +141 -0
- package/plugins/lisa-devin/skills/lisa-rework-triage/SKILL.md +206 -0
- package/plugins/lisa-devin/skills/lisa-root-cause-analysis/SKILL.md +135 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/SKILL.md +430 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/aws-bootstrap.mjs +433 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +288 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +273 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/envfile.mjs +80 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/inspect-vault.mjs +142 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +1085 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/note-format.mjs +179 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/owner.mjs +96 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +342 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/prompt-secret.mjs +79 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/providers.mjs +805 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +680 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/read-secret-note.mjs +105 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/resolve-expo-token.mjs +90 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/resolve-secret.mjs +285 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/rotate-secret.mjs +297 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/routing-floor.mjs +153 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/surfaces.mjs +480 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/sync-secret-to-ci.mjs +510 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/tools-from-notes.mjs +63 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/validate-config.mjs +550 -0
- package/plugins/lisa-devin/skills/lisa-security-review/SKILL.md +124 -0
- package/plugins/lisa-devin/skills/lisa-security-zap-scan/SKILL.md +53 -0
- package/plugins/lisa-devin/skills/lisa-sentry-access/SKILL.md +140 -0
- package/plugins/lisa-devin/skills/lisa-setup-atlassian/SKILL.md +407 -0
- package/plugins/lisa-devin/skills/lisa-setup-automations/SKILL.md +430 -0
- package/plugins/lisa-devin/skills/lisa-setup-automations/scripts/generate-workflow.mjs +288 -0
- package/plugins/lisa-devin/skills/lisa-setup-confluence/SKILL.md +254 -0
- package/plugins/lisa-devin/skills/lisa-setup-github/SKILL.md +319 -0
- package/plugins/lisa-devin/skills/lisa-setup-github-repo/SKILL.md +128 -0
- package/plugins/lisa-devin/skills/lisa-setup-jira/SKILL.md +198 -0
- package/plugins/lisa-devin/skills/lisa-setup-kane/SKILL.md +96 -0
- package/plugins/lisa-devin/skills/lisa-setup-linear/SKILL.md +375 -0
- package/plugins/lisa-devin/skills/lisa-setup-local-env/SKILL.md +95 -0
- package/plugins/lisa-devin/skills/lisa-setup-local-env/scripts/local-env.mjs +196 -0
- package/plugins/lisa-devin/skills/lisa-setup-notion/SKILL.md +376 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-aws/SKILL.md +149 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/SKILL.md +382 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/materialized-env-authority.mjs +262 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/session-start.sh +87 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/setup.sh +216 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/environment.mjs +227 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +378 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +1632 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/tool-floor.mjs +94 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +448 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +250 -0
- package/plugins/lisa-devin/skills/lisa-setup-sonar/SKILL.md +153 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/SKILL.md +143 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/bounded-child.mjs +168 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/catalogue.mjs +293 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/cli.mjs +333 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/workstation.mjs +511 -0
- package/plugins/lisa-devin/skills/lisa-sonarcloud-access/SKILL.md +202 -0
- package/plugins/lisa-devin/skills/lisa-spec-conformance/SKILL.md +205 -0
- package/plugins/lisa-devin/skills/lisa-starter-sync/SKILL.md +35 -0
- package/plugins/lisa-devin/skills/lisa-sync-down/SKILL.md +209 -0
- package/plugins/lisa-devin/skills/lisa-task-decomposition/SKILL.md +195 -0
- package/plugins/lisa-devin/skills/lisa-task-triage/SKILL.md +23 -0
- package/plugins/lisa-devin/skills/lisa-tdd-implementation/SKILL.md +91 -0
- package/plugins/lisa-devin/skills/lisa-tear-down-automations/SKILL.md +85 -0
- package/plugins/lisa-devin/skills/lisa-test-strategy/SKILL.md +66 -0
- package/plugins/lisa-devin/skills/lisa-ticket-triage/SKILL.md +214 -0
- package/plugins/lisa-devin/skills/lisa-track/SKILL.md +129 -0
- package/plugins/lisa-devin/skills/lisa-tracker-add-journey/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-tracker-build-intake/SKILL.md +134 -0
- package/plugins/lisa-devin/skills/lisa-tracker-claim/SKILL.md +35 -0
- package/plugins/lisa-devin/skills/lisa-tracker-create/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-tracker-evidence/SKILL.md +63 -0
- package/plugins/lisa-devin/skills/lisa-tracker-journey/SKILL.md +27 -0
- package/plugins/lisa-devin/skills/lisa-tracker-read/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-tracker-source-artifacts/SKILL.md +111 -0
- package/plugins/lisa-devin/skills/lisa-tracker-sync/SKILL.md +150 -0
- package/plugins/lisa-devin/skills/lisa-tracker-validate/SKILL.md +37 -0
- package/plugins/lisa-devin/skills/lisa-tracker-verify/SKILL.md +29 -0
- package/plugins/lisa-devin/skills/lisa-tracker-write/SKILL.md +55 -0
- package/plugins/lisa-devin/skills/lisa-usage-accounting/SKILL.md +205 -0
- package/plugins/lisa-devin/skills/lisa-usage-accounting/references/effectiveness.md +63 -0
- package/plugins/lisa-devin/skills/lisa-use-the-product/SKILL.md +86 -0
- package/plugins/lisa-devin/skills/lisa-validate-tracker-mapping/SKILL.md +467 -0
- package/plugins/lisa-devin/skills/lisa-verification-lifecycle/SKILL.md +384 -0
- package/plugins/lisa-devin/skills/lisa-verify/SKILL.md +55 -0
- package/plugins/lisa-devin/skills/lisa-verify-prd/SKILL.md +394 -0
- package/plugins/lisa-devin/skills/lisa-verify-workflow-change/SKILL.md +154 -0
- package/plugins/lisa-devin/skills/lisa-wiki-install/SKILL.md +101 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-expo-devin/.mcp.json +8 -0
- package/plugins/lisa-expo-devin/THIRD-PARTY-NOTICES.md +84 -0
- package/plugins/lisa-expo-devin/agents/ops-specialist.md +124 -0
- package/plugins/lisa-expo-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-expo-devin/hooks/register-maestro-mcp.sh +84 -0
- package/plugins/lisa-expo-devin/hooks.json +14 -0
- package/plugins/lisa-expo-devin/skills/add-app-clip/SKILL.md +280 -0
- package/plugins/lisa-expo-devin/skills/add-app-clip/references/native-module.md +96 -0
- package/plugins/lisa-expo-devin/skills/apollo-client/SKILL.md +238 -0
- package/plugins/lisa-expo-devin/skills/apollo-client/references/mutation-patterns.md +360 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/SKILL.md +369 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/scripts/test_validate_atomic_structure.py +240 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +438 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/SKILL.md +321 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/animations.md +220 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/controls.md +272 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/form-sheet.md +253 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/gradients.md +106 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/icons.md +213 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/media.md +198 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/route-structure.md +229 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/search.md +248 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/storage.md +121 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/tabs.md +433 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/toolbar-and-headers.md +284 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/visual-effects.md +197 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/webgpu-three.md +605 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/zoom-transitions.md +158 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/SKILL.md +355 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/references/examples.md +749 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/references/patterns.md +318 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/scripts/create_component.py +200 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/scripts/validate_component.py +332 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/SKILL.md +268 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/common-issues.md +619 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/platform-api.md +276 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +416 -0
- package/plugins/lisa-expo-devin/skills/directory-structure/SKILL.md +214 -0
- package/plugins/lisa-expo-devin/skills/directory-structure/scripts/validate_structure.py +454 -0
- package/plugins/lisa-expo-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
- package/plugins/lisa-expo-devin/skills/eas-update-insights/SKILL.md +228 -0
- package/plugins/lisa-expo-devin/skills/eas-update-insights/references/channel-insights-schema.md +47 -0
- package/plugins/lisa-expo-devin/skills/eas-update-insights/references/update-insights-schema.md +69 -0
- package/plugins/lisa-expo-devin/skills/expo-api-routes/SKILL.md +369 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/SKILL.md +54 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/brownfield-integrated.md +526 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/brownfield-isolated.md +402 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/comparison.md +63 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/troubleshooting.md +88 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/SKILL.md +92 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/fetch.js +113 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/package.json +11 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/validate.js +85 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/SKILL.md +193 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/app-store-metadata.md +479 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/ios-app-store.md +355 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/play-store.md +264 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/testflight.md +58 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/workflows.md +200 -0
- package/plugins/lisa-expo-devin/skills/expo-dev-client/SKILL.md +164 -0
- package/plugins/lisa-expo-devin/skills/expo-env-config/SKILL.md +309 -0
- package/plugins/lisa-expo-devin/skills/expo-env-config/references/validation-patterns.md +417 -0
- package/plugins/lisa-expo-devin/skills/expo-module/SKILL.md +141 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/config-plugin.md +90 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/create-expo-module.md +206 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/lifecycle.md +127 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/module-config.md +48 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/native-module.md +286 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/native-view.md +171 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/SKILL.md +432 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/references/official-docs.md +290 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/references/static-export-verification.md +54 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/scripts/generate-route.py +171 -0
- package/plugins/lisa-expo-devin/skills/expo-tailwind-setup/SKILL.md +487 -0
- package/plugins/lisa-expo-devin/skills/expo-ui-jetpack-compose/SKILL.md +40 -0
- package/plugins/lisa-expo-devin/skills/expo-ui-swift-ui/SKILL.md +39 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/SKILL.md +419 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/color-tokens.md +343 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/component-mapping.md +307 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/scripts/test_validate_styling.py +240 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/scripts/validate_styling.py +424 -0
- package/plugins/lisa-expo-devin/skills/jira-add-journey/SKILL.md +140 -0
- package/plugins/lisa-expo-devin/skills/jira-create/SKILL.md +161 -0
- package/plugins/lisa-expo-devin/skills/jira-evidence/SKILL.md +84 -0
- package/plugins/lisa-expo-devin/skills/jira-evidence/scripts/post-evidence.sh +262 -0
- package/plugins/lisa-expo-devin/skills/jira-journey/SKILL.md +196 -0
- package/plugins/lisa-expo-devin/skills/jira-journey/scripts/generate-templates.py +281 -0
- package/plugins/lisa-expo-devin/skills/jira-journey/scripts/parse-plan.py +400 -0
- package/plugins/lisa-expo-devin/skills/jira-verify/SKILL.md +27 -0
- package/plugins/lisa-expo-devin/skills/local-state/SKILL.md +362 -0
- package/plugins/lisa-expo-devin/skills/local-state/references/async-storage.md +505 -0
- package/plugins/lisa-expo-devin/skills/local-state/references/persistence-patterns.md +711 -0
- package/plugins/lisa-expo-devin/skills/local-state/references/reactive-variables.md +446 -0
- package/plugins/lisa-expo-devin/skills/maestro-mcp-setup/SKILL.md +144 -0
- package/plugins/lisa-expo-devin/skills/native-data-fetching/SKILL.md +507 -0
- package/plugins/lisa-expo-devin/skills/native-data-fetching/references/expo-router-loaders.md +344 -0
- package/plugins/lisa-expo-devin/skills/ops-browser-uat/SKILL.md +124 -0
- package/plugins/lisa-expo-devin/skills/ops-check-logs/SKILL.md +217 -0
- package/plugins/lisa-expo-devin/skills/ops-db-ops/SKILL.md +214 -0
- package/plugins/lisa-expo-devin/skills/ops-deploy/SKILL.md +123 -0
- package/plugins/lisa-expo-devin/skills/ops-monitor-errors/SKILL.md +99 -0
- package/plugins/lisa-expo-devin/skills/ops-performance/SKILL.md +165 -0
- package/plugins/lisa-expo-devin/skills/ops-run-local/SKILL.md +169 -0
- package/plugins/lisa-expo-devin/skills/ops-verify-health/SKILL.md +101 -0
- package/plugins/lisa-expo-devin/skills/owasp-zap/SKILL.md +61 -0
- package/plugins/lisa-expo-devin/skills/play-store-access/SKILL.md +226 -0
- package/plugins/lisa-expo-devin/skills/playwright-ci-debugging/SKILL.md +140 -0
- package/plugins/lisa-expo-devin/skills/playwright-selectors/SKILL.md +496 -0
- package/plugins/lisa-expo-devin/skills/reduce-complexity/SKILL.md +251 -0
- package/plugins/lisa-expo-devin/skills/reduce-complexity/references/extraction-strategies.md +456 -0
- package/plugins/lisa-expo-devin/skills/reduce-complexity/references/refactoring-patterns.md +557 -0
- package/plugins/lisa-expo-devin/skills/testing-library/SKILL.md +314 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/async-patterns.md +420 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/expo-router-testing.md +556 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/mocking-patterns.md +612 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/query-priority.md +291 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/SKILL.md +134 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/expo-av-to-audio.md +132 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/expo-av-to-video.md +160 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/native-tabs.md +124 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/new-architecture.md +79 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-19.md +79 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-compiler.md +59 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-navigation-to-expo-router.md +61 -0
- package/plugins/lisa-expo-devin/skills/use-dom/SKILL.md +417 -0
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-harper-fabric-devin/generated-artifact-globs.txt +5 -0
- package/plugins/lisa-harper-fabric-devin/hooks/block-generated-artifact-edits.sh +141 -0
- package/plugins/lisa-harper-fabric-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-harper-fabric-devin/hooks/enforce-config-extensions.mjs +196 -0
- package/plugins/lisa-harper-fabric-devin/hooks/enforce-config-extensions.sh +46 -0
- package/plugins/lisa-harper-fabric-devin/hooks/inject-rules.sh +27 -0
- package/plugins/lisa-harper-fabric-devin/hooks.json +36 -0
- package/plugins/lisa-harper-fabric-devin/rules/eager/harper-fabric.md +31 -0
- package/plugins/lisa-harper-fabric-devin/rules/reference/harper-fabric.md +52 -0
- package/plugins/lisa-harper-fabric-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-auth/SKILL.md +368 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-build-and-deploy/SKILL.md +198 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-caching/SKILL.md +265 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-component-model/SKILL.md +87 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-config-yaml/SKILL.md +283 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-operations/SKILL.md +239 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-realtime/SKILL.md +187 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-resources/SKILL.md +142 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-rest-queries/SKILL.md +403 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-schema-graphql/SKILL.md +123 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-testing/SKILL.md +254 -0
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-nestjs-devin/hooks/block-migration-edits.sh +125 -0
- package/plugins/lisa-nestjs-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-nestjs-devin/hooks/lisa-edit-gate.sh +152 -0
- package/plugins/lisa-nestjs-devin/hooks.json +15 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/SKILL.md +176 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/advanced-features.md +527 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/project-patterns.md +531 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/quick-start.md +257 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/types-scalars.md +513 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-rules/SKILL.md +560 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/SKILL.md +275 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/entity-patterns.md +450 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/observability-patterns.md +536 -0
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +8 -0
- package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-repo-topic/SKILL.md +163 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-repo-topic/references/repo-topic-config.md +185 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/SKILL.md +175 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/references/platform-routing.md +123 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/references/prompts.md +78 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-setup/SKILL.md +141 -0
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-phaser-devin/agents/accessibility-advocate.md +55 -0
- package/plugins/lisa-phaser-devin/agents/art-director.md +90 -0
- package/plugins/lisa-phaser-devin/agents/audio-director.md +65 -0
- package/plugins/lisa-phaser-devin/agents/combat-designer.md +61 -0
- package/plugins/lisa-phaser-devin/agents/economy-designer.md +59 -0
- package/plugins/lisa-phaser-devin/agents/game-designer.md +62 -0
- package/plugins/lisa-phaser-devin/agents/game-feel-specialist.md +56 -0
- package/plugins/lisa-phaser-devin/agents/level-designer.md +59 -0
- package/plugins/lisa-phaser-devin/agents/localization-manager.md +61 -0
- package/plugins/lisa-phaser-devin/agents/marketing-strategist.md +59 -0
- package/plugins/lisa-phaser-devin/agents/monetization-designer.md +59 -0
- package/plugins/lisa-phaser-devin/agents/narrative-designer.md +56 -0
- package/plugins/lisa-phaser-devin/agents/onboarding-advocate.md +59 -0
- package/plugins/lisa-phaser-devin/agents/player-advocate.md +57 -0
- package/plugins/lisa-phaser-devin/agents/producer.md +61 -0
- package/plugins/lisa-phaser-devin/agents/product-analyst.md +62 -0
- package/plugins/lisa-phaser-devin/agents/publisher.md +59 -0
- package/plugins/lisa-phaser-devin/agents/qa-playtester.md +57 -0
- package/plugins/lisa-phaser-devin/agents/target-player.md +52 -0
- package/plugins/lisa-phaser-devin/agents/ux-ui-designer.md +60 -0
- package/plugins/lisa-phaser-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-phaser-devin/hooks/inject-rules.sh +27 -0
- package/plugins/lisa-phaser-devin/hooks.json +14 -0
- package/plugins/lisa-phaser-devin/rules/eager/phaser.md +44 -0
- package/plugins/lisa-phaser-devin/rules/reference/phaser.md +184 -0
- package/plugins/lisa-phaser-devin/skills/phaser-accessibility/SKILL.md +135 -0
- package/plugins/lisa-phaser-devin/skills/phaser-asset-pipeline/SKILL.md +222 -0
- package/plugins/lisa-phaser-devin/skills/phaser-asset-sourcing/SKILL.md +151 -0
- package/plugins/lisa-phaser-devin/skills/phaser-build-deploy/SKILL.md +153 -0
- package/plugins/lisa-phaser-devin/skills/phaser-i18n/SKILL.md +122 -0
- package/plugins/lisa-phaser-devin/skills/phaser-project-structure/SKILL.md +89 -0
- package/plugins/lisa-phaser-devin/skills/phaser-services/SKILL.md +208 -0
- package/plugins/lisa-phaser-devin/skills/phaser-testing/SKILL.md +192 -0
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-rails-devin/agents/ops-specialist.md +226 -0
- package/plugins/lisa-rails-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-rails-devin/hooks/inject-rules.sh +40 -0
- package/plugins/lisa-rails-devin/hooks/lisa-edit-gate.sh +152 -0
- package/plugins/lisa-rails-devin/hooks/rubocop-on-edit.sh +101 -0
- package/plugins/lisa-rails-devin/hooks/sg-scan-on-edit.sh +97 -0
- package/plugins/lisa-rails-devin/hooks.json +29 -0
- package/plugins/lisa-rails-devin/rules/eager/rails-conventions.md +23 -0
- package/plugins/lisa-rails-devin/rules/reference/rails-conventions.md +176 -0
- package/plugins/lisa-rails-devin/skills/action-controller-best-practices/SKILL.md +374 -0
- package/plugins/lisa-rails-devin/skills/action-view-best-practices/SKILL.md +335 -0
- package/plugins/lisa-rails-devin/skills/active-record-model-best-practices/SKILL.md +166 -0
- package/plugins/lisa-rails-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
- package/plugins/lisa-rails-devin/skills/fix-linter-error/SKILL.md +45 -0
- package/plugins/lisa-rails-devin/skills/improve-code-complexity/SKILL.md +51 -0
- package/plugins/lisa-rails-devin/skills/improve-max-lines/SKILL.md +46 -0
- package/plugins/lisa-rails-devin/skills/improve-max-lines-per-function/SKILL.md +46 -0
- package/plugins/lisa-rails-devin/skills/improve-test-coverage/SKILL.md +48 -0
- package/plugins/lisa-rails-devin/skills/jira-add-journey/SKILL.md +79 -0
- package/plugins/lisa-rails-devin/skills/jira-create/SKILL.md +100 -0
- package/plugins/lisa-rails-devin/skills/jira-evidence/SKILL.md +76 -0
- package/plugins/lisa-rails-devin/skills/jira-evidence/scripts/post-evidence.sh +262 -0
- package/plugins/lisa-rails-devin/skills/jira-journey/SKILL.md +65 -0
- package/plugins/lisa-rails-devin/skills/jira-verify/SKILL.md +26 -0
- package/plugins/lisa-rails-devin/skills/ops-check-logs/SKILL.md +191 -0
- package/plugins/lisa-rails-devin/skills/ops-deploy/SKILL.md +153 -0
- package/plugins/lisa-rails-devin/skills/ops-run-local/SKILL.md +169 -0
- package/plugins/lisa-rails-devin/skills/ops-verify-jobs/SKILL.md +157 -0
- package/plugins/lisa-rails-devin/skills/ops-verify-telemetry/SKILL.md +226 -0
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-typescript-devin/hooks/block-suppress-directives.sh +141 -0
- package/plugins/lisa-typescript-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-typescript-devin/hooks/format-on-edit.sh +102 -0
- package/plugins/lisa-typescript-devin/hooks/lint-on-edit.sh +117 -0
- package/plugins/lisa-typescript-devin/hooks/lisa-edit-gate.sh +152 -0
- package/plugins/lisa-typescript-devin/hooks/sg-scan-on-edit.sh +94 -0
- package/plugins/lisa-typescript-devin/hooks.json +34 -0
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +8 -0
- package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-wiki-devin/ci/lisa-wiki-validate.yml +33 -0
- package/plugins/lisa-wiki-devin/schema/lisa-wiki-config.schema.json +162 -0
- package/plugins/lisa-wiki-devin/schema/wiki-structure.schema.json +51 -0
- package/plugins/lisa-wiki-devin/scripts/_wiki-lib.mjs +204 -0
- package/plugins/lisa-wiki-devin/scripts/diff-guard.mjs +117 -0
- package/plugins/lisa-wiki-devin/scripts/ensure-gitignore.mjs +131 -0
- package/plugins/lisa-wiki-devin/scripts/ensure-wiki.mjs +268 -0
- package/plugins/lisa-wiki-devin/scripts/ingest-git.mjs +198 -0
- package/plugins/lisa-wiki-devin/scripts/ingest-memory.mjs +135 -0
- package/plugins/lisa-wiki-devin/scripts/ingest-roles.mjs +95 -0
- package/plugins/lisa-wiki-devin/scripts/ingest_slack_channel.py +361 -0
- package/plugins/lisa-wiki-devin/scripts/lib/bounded-child.mjs +159 -0
- package/plugins/lisa-wiki-devin/scripts/lint-wiki.mjs +513 -0
- package/plugins/lisa-wiki-devin/scripts/mcp-doctor.mjs +72 -0
- package/plugins/lisa-wiki-devin/scripts/render-contract.mjs +107 -0
- package/plugins/lisa-wiki-devin/scripts/rewrite-refs.mjs +144 -0
- package/plugins/lisa-wiki-devin/scripts/slack_oauth_user.py +179 -0
- package/plugins/lisa-wiki-devin/scripts/validate-config.mjs +321 -0
- package/plugins/lisa-wiki-devin/scripts/verify-migration.mjs +203 -0
- package/plugins/lisa-wiki-devin/scripts/verify-wiki-safety.mjs +126 -0
- package/plugins/lisa-wiki-devin/scripts/wiki-safety.mjs +378 -0
- package/plugins/lisa-wiki-devin/scripts/wiki-status.mjs +345 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-add-ingest/SKILL.md +34 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-add-role/SKILL.md +35 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-confluence/SKILL.md +25 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-docs/SKILL.md +30 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-git/SKILL.md +25 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-jira/SKILL.md +28 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-memory/SKILL.md +28 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-notion/SKILL.md +25 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-roles/SKILL.md +22 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-slack/SKILL.md +30 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-web/SKILL.md +23 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-doctor/SKILL.md +47 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-ingest/SKILL.md +106 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-lint/SKILL.md +32 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-migrate/SKILL.md +43 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-onboard-me/SKILL.md +33 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-query/SKILL.md +40 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-setup/SKILL.md +75 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-setup-automations/SKILL.md +83 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-status/SKILL.md +47 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-tear-down-automations/SKILL.md +34 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-usage/SKILL.md +50 -0
- package/plugins/lisa-wiki-devin/templates/agents/role-agent.claude.md +16 -0
- package/plugins/lisa-wiki-devin/templates/agents/role-agent.codex.toml +15 -0
- package/plugins/lisa-wiki-devin/templates/index.md +17 -0
- package/plugins/lisa-wiki-devin/templates/llm-wiki-contract.md +72 -0
- package/plugins/lisa-wiki-devin/templates/log.md +8 -0
- package/plugins/lisa-wiki-devin/templates/page-types/architecture.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/concept.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/decision.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/entity.md +19 -0
- package/plugins/lisa-wiki-devin/templates/page-types/open-question.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/playbook.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/project.md +19 -0
- package/plugins/lisa-wiki-devin/templates/page-types/requirement.md +19 -0
- package/plugins/lisa-wiki-devin/templates/page-types/staff.md +26 -0
- package/plugins/lisa-wiki-devin/templates/start-here.md +24 -0
- package/plugins/lisa-wiki-devin/templates/state-readme.md +20 -0
- package/plugins/lisa-wiki-devin/templates/wrapper-gitignore.txt +19 -0
- package/scripts/build-plugins.sh +2 -2
- package/scripts/deployed-guard-advice.mjs +1 -0
- package/scripts/generate-devin-plugin-artifacts.mjs +263 -0
- package/scripts/lib/hook-registration-audit.mjs +3 -1
- package/scripts/lib/per-agent-hook-filter.mjs +174 -5
- package/scripts/lib/plugin-rule-citations.mjs +2 -2
- package/scripts/two-channel-couplings.json +1 -1
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# This file is managed by Lisa and IS replaced on each `lisa` run.
|
|
3
|
+
# Do not edit directly — durable changes belong upstream in Lisa.
|
|
4
|
+
|
|
5
|
+
"""Classify the only heredoc forms whose payload is non-executable text.
|
|
6
|
+
|
|
7
|
+
SCOPE — read this before hardening anything here (issue #1993).
|
|
8
|
+
|
|
9
|
+
This module is a HEURISTIC RE-IMPLEMENTATION of bash's here-doc lexing, not an
|
|
10
|
+
adversary-resistant control. Every divergence between this Python model and real
|
|
11
|
+
bash is, by construction, a potential mis-classification, and five rounds of
|
|
12
|
+
fix-then-re-attack on issue #1958 plus three more here (#1993) established that
|
|
13
|
+
enumerating those divergences is an open-ended arms race. Treat the class as
|
|
14
|
+
CLOSED: a newly found member is documented, not chased, unless someone first
|
|
15
|
+
demonstrates it defeats the CONTENT GUARDS, which are the layer that matters.
|
|
16
|
+
|
|
17
|
+
The one hard guarantee, and the reason a mis-classification is survivable:
|
|
18
|
+
|
|
19
|
+
Exit SAFE(0) is the ONLY path that strips payload text, and it requires the
|
|
20
|
+
narrow, fully-quoted `gh issue|pr create/edit/comment` + `<<'EOF'` grammar in
|
|
21
|
+
``classify_safe``. EVERY other path — UNSUPPORTED(10) and MALFORMED(20) —
|
|
22
|
+
hands the RAW, unmodified command to the content guards. So a here-doc the
|
|
23
|
+
classifier mis-reads degrades to the guards; it does not bypass them. Any
|
|
24
|
+
change here must preserve that property: widen ``classify_safe`` only with
|
|
25
|
+
proof, and keep every other exit raw-pass-through.
|
|
26
|
+
|
|
27
|
+
Known accepted bypass class (measured, deliberately not chased further):
|
|
28
|
+
R5a — a trailing backslash on the last body line is a bash continuation that
|
|
29
|
+
swallows the terminator; R5b — an odd apostrophe in an unquoted body poisons a
|
|
30
|
+
flat quote scanner past the terminator; R5c — `<<\\DELIM` is a quoted delimiter
|
|
31
|
+
this parser did not model. All three are FIXED below, but they are members of a
|
|
32
|
+
family, not the family itself. Each requires deliberately-crafted input; none is
|
|
33
|
+
a shape an agent emits by accident; and post-#1982 every destructive class the
|
|
34
|
+
safety net exists to stop is independently blocked by the content guards on the
|
|
35
|
+
raw text.
|
|
36
|
+
|
|
37
|
+
The two bash invariants this file relies on — both measured directly against
|
|
38
|
+
/bin/bash, not inferred — so a future round starts from a spec:
|
|
39
|
+
|
|
40
|
+
1. UNQUOTED delimiter (`<<EOF`): the body performs NO quote or comment
|
|
41
|
+
processing (`'`, `"`, `#` are literal data bytes) but STILL expands `$(...)`,
|
|
42
|
+
backticks and parameters, and bash removes `\\<newline>` continuations BEFORE
|
|
43
|
+
matching the delimiter. Measured: `cat <<EOF` / `foo\\` / `EOF` / `bar` emits
|
|
44
|
+
`fooEOF\\nbar` — the here-doc never terminated.
|
|
45
|
+
2. QUOTED delimiter in any spelling (`<<'EOF'`, `<<"EOF"`, `<<\\EOF`): the body is
|
|
46
|
+
fully literal, no expansion of any kind, and backslash is DATA rather than an
|
|
47
|
+
escape — so the delimiter IS matched per physical line there. Measured: the
|
|
48
|
+
same trailing-backslash shape under `<<'EOF'` emits `foo\\` and then fails on
|
|
49
|
+
`bar: command not found`.
|
|
50
|
+
|
|
51
|
+
That asymmetry is why the continuation handling in ``terminator_line_index`` is
|
|
52
|
+
conditioned on ``Marker.quoted`` rather than applied uniformly.
|
|
53
|
+
|
|
54
|
+
``Marker.quoted`` means "bash makes this BODY literal" and nothing more. The
|
|
55
|
+
three spellings above are interchangeable for body semantics ONLY — they are NOT
|
|
56
|
+
interchangeable on the writer path. ``classify_safe`` still demands the literal
|
|
57
|
+
``<<'DELIM'`` spelling, so `<<"EOF"` and `<<\\EOF` can never reach SAFE(0); since
|
|
58
|
+
#1993 they additionally record a Marker, which arms the writer-owns-a-real-marker
|
|
59
|
+
and multi-marker rules and lands a `gh … <<\\EOF` writer on MALFORMED(20) where it
|
|
60
|
+
used to be UNSUPPORTED(10). That is a deliberate, test-pinned over-block: the
|
|
61
|
+
documented payload-strip workaround is spelled `<<'EOF'`. Widening
|
|
62
|
+
``classify_safe`` to admit other spellings is a separate change needing its own
|
|
63
|
+
proof — do not do it as a side effect of body-semantics work.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
from __future__ import annotations
|
|
67
|
+
|
|
68
|
+
import re
|
|
69
|
+
import shlex
|
|
70
|
+
import sys
|
|
71
|
+
from dataclasses import dataclass
|
|
72
|
+
|
|
73
|
+
SAFE = 0
|
|
74
|
+
UNSUPPORTED = 10
|
|
75
|
+
MALFORMED = 20
|
|
76
|
+
|
|
77
|
+
DELIMITER = r"(?P<delimiter>[A-Za-z_][A-Za-z0-9_]*)"
|
|
78
|
+
BODY_CAT_MARKER = re.compile(
|
|
79
|
+
r"(?P<prefix>.*(?:^|\s)--body\s+)\"\$\(cat\s+<<'"
|
|
80
|
+
+ DELIMITER
|
|
81
|
+
+ r"'\s*$"
|
|
82
|
+
)
|
|
83
|
+
ALLOWED_GROUPS = {"issue", "pr"}
|
|
84
|
+
ALLOWED_ACTIONS = {"create", "edit", "comment"}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True)
|
|
88
|
+
class Marker:
|
|
89
|
+
start: int
|
|
90
|
+
end: int
|
|
91
|
+
delimiter: str
|
|
92
|
+
strip_tabs: bool
|
|
93
|
+
# True ONLY when the whole delimiter token was wrapped in one full single-
|
|
94
|
+
# or double-quote pair (<<'EOF' / <<"EOF") with nothing word-like attached
|
|
95
|
+
# after the closing quote. That is the only form whose body this parser can
|
|
96
|
+
# PROVE bash treats as non-expanding literal data (issue #1958).
|
|
97
|
+
quoted: bool
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def is_bash_blank(char: str) -> bool:
|
|
101
|
+
"""True only for a byte bash treats as a word separator here: space, tab,
|
|
102
|
+
newline.
|
|
103
|
+
|
|
104
|
+
Python ``str.isspace()`` is a strict SUPERSET of bash's word-separator set —
|
|
105
|
+
it is also True for NBSP (``\\xa0``), the C0 separators FS/GS/RS/US
|
|
106
|
+
(``\\x1c``–``\\x1f``), NEL (``\\x85``), VT/FF (``\\x0b``/``\\x0c``), CR
|
|
107
|
+
(``\\r``), and the Unicode spaces (ideographic space ``\\u3000``, the
|
|
108
|
+
``\\u2000``–``\\u200a`` run, ``\\u202f``, ``\\u205f``, ``\\u2028``/``\\u2029``).
|
|
109
|
+
Bash (C locale) treats NONE of these as a blank or a metacharacter, so a
|
|
110
|
+
``#`` preceded by one stays INSIDE the current word — it does NOT start a
|
|
111
|
+
comment — and any ``$(...)`` / `` `...` `` in that word is still expanded and
|
|
112
|
+
EXECUTED. Every comment-boundary walker here must therefore match bash's
|
|
113
|
+
actual blank set, not Python's: using ``str.isspace()`` let the scanner call
|
|
114
|
+
``#`` a comment where bash does not, skip to the newline, and go blind to a
|
|
115
|
+
live substitution — smuggling arbitrary command execution past the wall
|
|
116
|
+
(issue #1958 Finding R2). This is the single shared home of that predicate so
|
|
117
|
+
every walker stays in lockstep with bash's word-boundary rule; fail-closed on
|
|
118
|
+
ambiguity means narrowing (fewer bytes counted as blank), never widening.
|
|
119
|
+
"""
|
|
120
|
+
return char in " \t\n"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def bash_lines(text: str) -> list[str]:
|
|
124
|
+
"""Split into bash's notion of lines: on ``\\n`` ONLY.
|
|
125
|
+
|
|
126
|
+
Python ``str.splitlines()`` is the same over-broad classifier as
|
|
127
|
+
``str.isspace()`` in disguise — it ALSO breaks on ``\\r``, VT/FF
|
|
128
|
+
(``\\x0b``/``\\x0c``), the C0 separators FS/GS/RS (``\\x1c``–``\\x1e``), NEL
|
|
129
|
+
(``\\x85``), and the Unicode line separators (``\\u2028``/``\\u2029``). Bash
|
|
130
|
+
ends a line only at an unquoted ``\\n``, so ``splitlines()`` invents line
|
|
131
|
+
breaks bash never sees. The concrete hole: ``strip_provably_literal_body``
|
|
132
|
+
splits with ``splitlines()`` then rejoins with ``\\n``, so a ``echo X\\x1c#$(…)``
|
|
133
|
+
argument gets normalised into ``echo X`` / ``#$(…)`` on separate lines — now
|
|
134
|
+
the ``#`` sits at a real line start, is treated as a comment, and the live
|
|
135
|
+
``$(…)`` is smuggled past the wall exactly as the ``str.isspace()`` desync
|
|
136
|
+
did (issue #1958 Finding R2, FS/ideographic-space variant). Splitting on
|
|
137
|
+
``\\n`` alone keeps this parser's lines in lockstep with bash and with the
|
|
138
|
+
``command.count("\\n", …)`` offset arithmetic the markers already rely on.
|
|
139
|
+
"""
|
|
140
|
+
return text.split("\n")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def ansi_c_quote_end(text: str, dollar_index: int) -> int | None:
|
|
144
|
+
"""Index one past a bash ANSI-C ``$'...'`` token, or ``None``.
|
|
145
|
+
|
|
146
|
+
When an *unquoted* ``$`` is immediately followed by ``'`` bash opens an
|
|
147
|
+
ANSI-C-quoted string that ends at the first *unescaped* ``'``: a backslash
|
|
148
|
+
escapes the next single character, so ``\\'`` is a literal quote (does NOT
|
|
149
|
+
close the string) and ``\\\\`` is a literal backslash. Only literal
|
|
150
|
+
single quotes act as delimiters — value-only escapes like ``\\x27`` /
|
|
151
|
+
``\\047`` / ``\\uHHHH`` decode to a quote *character* but never terminate
|
|
152
|
+
the token, so scanning for the first unescaped ``'`` yields exactly bash's
|
|
153
|
+
token boundary for every spelling.
|
|
154
|
+
|
|
155
|
+
A naive single-quote scanner that ignores ``$'...'`` desyncs from bash on an
|
|
156
|
+
odd number of ``\\'`` escapes: it reads three bare quotes as
|
|
157
|
+
single→plain→single and ends in a phantom open single-quote, going blind to
|
|
158
|
+
everything after it — including a live ``$(...)`` the following ``"`` really
|
|
159
|
+
exposes (issue #1958 Finding R1). Consuming the whole inert token here keeps
|
|
160
|
+
every shared walker in lockstep with bash.
|
|
161
|
+
|
|
162
|
+
Returns the index just past the closing quote, or ``None`` for a
|
|
163
|
+
non-``$'`` position or an unterminated token so callers fail closed. This is
|
|
164
|
+
the single shared home of ANSI-C token boundaries; every quote-state walker
|
|
165
|
+
consults it rather than re-deriving the rule.
|
|
166
|
+
"""
|
|
167
|
+
if not text.startswith("$'", dollar_index):
|
|
168
|
+
return None
|
|
169
|
+
index = dollar_index + 2
|
|
170
|
+
length = len(text)
|
|
171
|
+
while index < length:
|
|
172
|
+
char = text[index]
|
|
173
|
+
if char == "\\":
|
|
174
|
+
index += 2
|
|
175
|
+
continue
|
|
176
|
+
if char == "'":
|
|
177
|
+
return index + 1
|
|
178
|
+
index += 1
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def shell_tokens(prefix: str) -> list[str] | None:
|
|
183
|
+
"""Return literal simple-command tokens, rejecting executable shell syntax."""
|
|
184
|
+
state = "plain"
|
|
185
|
+
escaped = False
|
|
186
|
+
index = 0
|
|
187
|
+
while index < len(prefix):
|
|
188
|
+
char = prefix[index]
|
|
189
|
+
if escaped:
|
|
190
|
+
escaped = False
|
|
191
|
+
elif state == "single":
|
|
192
|
+
if char == "'":
|
|
193
|
+
state = "plain"
|
|
194
|
+
elif state == "double":
|
|
195
|
+
if char == '"':
|
|
196
|
+
state = "plain"
|
|
197
|
+
elif char == "\\":
|
|
198
|
+
escaped = True
|
|
199
|
+
elif char == "`" or (
|
|
200
|
+
char == "$"
|
|
201
|
+
and index + 1 < len(prefix)
|
|
202
|
+
and prefix[index + 1] in "([{"):
|
|
203
|
+
return None
|
|
204
|
+
elif prefix.startswith("$'", index):
|
|
205
|
+
end = ansi_c_quote_end(prefix, index)
|
|
206
|
+
if end is None:
|
|
207
|
+
return None
|
|
208
|
+
index = end
|
|
209
|
+
continue
|
|
210
|
+
elif char == "'":
|
|
211
|
+
state = "single"
|
|
212
|
+
elif char == '"':
|
|
213
|
+
state = "double"
|
|
214
|
+
elif char == "\\":
|
|
215
|
+
escaped = True
|
|
216
|
+
elif char == "#" and (index == 0 or is_bash_blank(prefix[index - 1])):
|
|
217
|
+
return None
|
|
218
|
+
elif char in ";|&()<>`\n\r":
|
|
219
|
+
return None
|
|
220
|
+
elif char == "$" and index + 1 < len(prefix) and prefix[index + 1] in "([{":
|
|
221
|
+
return None
|
|
222
|
+
index += 1
|
|
223
|
+
|
|
224
|
+
if state != "plain" or escaped:
|
|
225
|
+
return None
|
|
226
|
+
try:
|
|
227
|
+
return shlex.split(prefix, comments=False, posix=True)
|
|
228
|
+
except ValueError:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def literal_display_projection(command: str) -> str:
|
|
233
|
+
"""Remove data from one literal printer, retaining its real output target.
|
|
234
|
+
|
|
235
|
+
Reuse the literal simple-command grammar: pipes, chains, substitutions,
|
|
236
|
+
comments and ambiguous syntax keep the original scan. This is not a shell
|
|
237
|
+
parser or an execution exemption. ANSI-C quoting and computed targets also
|
|
238
|
+
keep their original scan rather than guessing their expanded spelling.
|
|
239
|
+
"""
|
|
240
|
+
if "$'" in command:
|
|
241
|
+
return command
|
|
242
|
+
|
|
243
|
+
def printer(text: str) -> bool:
|
|
244
|
+
tokens = shell_tokens(text)
|
|
245
|
+
if not tokens or len(tokens) < 2:
|
|
246
|
+
return False
|
|
247
|
+
if tokens[0] == "echo":
|
|
248
|
+
return True
|
|
249
|
+
if tokens[0] != "printf":
|
|
250
|
+
return False
|
|
251
|
+
arguments = tokens[2:] if tokens[1] == "--" else tokens[1:]
|
|
252
|
+
if not arguments or arguments[0].startswith("-"):
|
|
253
|
+
return False
|
|
254
|
+
# %n writes a variable (including an evaluated array subscript), and a
|
|
255
|
+
# dynamic format could select it. Only known display conversions qualify.
|
|
256
|
+
remaining = re.sub(r"%%|%[sbqQ]", "", arguments[0])
|
|
257
|
+
return not any(char in remaining for char in "%$`*?[]~")
|
|
258
|
+
|
|
259
|
+
if printer(command):
|
|
260
|
+
return "printf '%s' literal-display-content"
|
|
261
|
+
for redirect in re.finditer(r">>|>\||>", command):
|
|
262
|
+
if not printer(command[:redirect.start()]):
|
|
263
|
+
continue
|
|
264
|
+
target = shell_tokens(command[redirect.end():])
|
|
265
|
+
if target and len(target) == 1 and not any(
|
|
266
|
+
char in target[0] for char in "$`\\\n\r*?[]~"
|
|
267
|
+
):
|
|
268
|
+
return "printf '%s' literal-display-content " + redirect[0] + " " + shlex.quote(target[0])
|
|
269
|
+
return command
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def is_allowed_gh(tokens: list[str]) -> bool:
|
|
273
|
+
return (
|
|
274
|
+
len(tokens) >= 3
|
|
275
|
+
and tokens[0] == "gh"
|
|
276
|
+
and tokens[1] in ALLOWED_GROUPS
|
|
277
|
+
and tokens[2] in ALLOWED_ACTIONS
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def line_has_allowed_writer(line: str) -> bool:
|
|
282
|
+
"""Find a writer after shell quote-concatenation and control operators."""
|
|
283
|
+
try:
|
|
284
|
+
lexer = shlex.shlex(line, posix=True, punctuation_chars=";&|()")
|
|
285
|
+
lexer.whitespace_split = True
|
|
286
|
+
lexer.commenters = "#"
|
|
287
|
+
tokens = list(lexer)
|
|
288
|
+
except ValueError:
|
|
289
|
+
return False
|
|
290
|
+
return any(
|
|
291
|
+
is_allowed_gh(tokens[index : index + 3])
|
|
292
|
+
for index in range(max(0, len(tokens) - 2))
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def has_body_file_stdin(tokens: list[str]) -> bool:
|
|
297
|
+
return "--body-file=-" in tokens or any(
|
|
298
|
+
tokens[index : index + 2] == ["--body-file", "-"]
|
|
299
|
+
for index in range(len(tokens) - 1)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def exact_terminator(lines: list[str], delimiter: str, start: int) -> int | None:
|
|
304
|
+
for index in range(start, len(lines)):
|
|
305
|
+
if lines[index] == delimiter:
|
|
306
|
+
return index
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def only_whitespace(lines: list[str], start: int) -> bool:
|
|
311
|
+
return all(not line.strip() for line in lines[start:])
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def classify_safe(command: str) -> str | None:
|
|
315
|
+
lines = bash_lines(command)
|
|
316
|
+
if len(lines) < 2 or "\x00" in command or "\r" in command:
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
header = lines[0]
|
|
320
|
+
body_cat = BODY_CAT_MARKER.search(header)
|
|
321
|
+
if body_cat is not None:
|
|
322
|
+
tokens = shell_tokens(body_cat.group("prefix"))
|
|
323
|
+
if tokens is None or not is_allowed_gh(tokens):
|
|
324
|
+
return None
|
|
325
|
+
terminator = exact_terminator(lines, body_cat.group("delimiter"), 1)
|
|
326
|
+
if terminator is None:
|
|
327
|
+
raise ValueError("unclosed body cat heredoc")
|
|
328
|
+
if terminator + 1 >= len(lines) or lines[terminator + 1].strip() != ')"':
|
|
329
|
+
raise ValueError("body cat substitution is not closed exactly")
|
|
330
|
+
if not only_whitespace(lines, terminator + 2):
|
|
331
|
+
raise ValueError("command follows body cat heredoc")
|
|
332
|
+
return body_cat.group("prefix") + '"<heredoc-text>"'
|
|
333
|
+
|
|
334
|
+
header_markers = top_level_markers(header)
|
|
335
|
+
if len(header_markers) != 1:
|
|
336
|
+
return None
|
|
337
|
+
direct = header_markers[0]
|
|
338
|
+
raw_marker = header[direct.start : direct.end]
|
|
339
|
+
if raw_marker != f"<<'{direct.delimiter}'" or header[direct.end :].strip():
|
|
340
|
+
return None
|
|
341
|
+
prefix = header[: direct.start]
|
|
342
|
+
tokens = shell_tokens(prefix)
|
|
343
|
+
if tokens is None or not is_allowed_gh(tokens) or not has_body_file_stdin(tokens):
|
|
344
|
+
return None
|
|
345
|
+
terminator = exact_terminator(lines, direct.delimiter, 1)
|
|
346
|
+
if terminator is None:
|
|
347
|
+
raise ValueError("unclosed direct heredoc")
|
|
348
|
+
if not only_whitespace(lines, terminator + 1):
|
|
349
|
+
raise ValueError("command follows direct heredoc")
|
|
350
|
+
return prefix.rstrip()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def top_level_markers(command: str) -> list[Marker]:
|
|
354
|
+
"""Find conservative top-level markers for malformed/duplicate detection."""
|
|
355
|
+
markers: list[Marker] = []
|
|
356
|
+
lines = bash_lines(command)
|
|
357
|
+
offset = 0
|
|
358
|
+
for line in lines:
|
|
359
|
+
state = "plain"
|
|
360
|
+
escaped = False
|
|
361
|
+
index = 0
|
|
362
|
+
while index < len(line):
|
|
363
|
+
char = line[index]
|
|
364
|
+
if escaped:
|
|
365
|
+
escaped = False
|
|
366
|
+
elif state == "single":
|
|
367
|
+
if char == "'":
|
|
368
|
+
state = "plain"
|
|
369
|
+
elif state == "double":
|
|
370
|
+
if char == '"':
|
|
371
|
+
state = "plain"
|
|
372
|
+
elif char == "\\":
|
|
373
|
+
escaped = True
|
|
374
|
+
elif line.startswith("$'", index):
|
|
375
|
+
end = ansi_c_quote_end(line, index)
|
|
376
|
+
if end is None:
|
|
377
|
+
break
|
|
378
|
+
index = end
|
|
379
|
+
continue
|
|
380
|
+
elif char == "'":
|
|
381
|
+
state = "single"
|
|
382
|
+
elif char == '"':
|
|
383
|
+
state = "double"
|
|
384
|
+
elif char == "\\":
|
|
385
|
+
escaped = True
|
|
386
|
+
elif char == "#" and (index == 0 or is_bash_blank(line[index - 1])):
|
|
387
|
+
break
|
|
388
|
+
elif line.startswith("<<", index) and not line.startswith("<<<", index):
|
|
389
|
+
marker = parse_marker(line, index, offset)
|
|
390
|
+
if marker is not None:
|
|
391
|
+
markers.append(marker)
|
|
392
|
+
index = marker.end - offset - 1
|
|
393
|
+
index += 1
|
|
394
|
+
offset += len(line) + 1
|
|
395
|
+
return markers
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def unquoted_code_and_comment(line: str) -> tuple[str, str]:
|
|
399
|
+
"""Return executable text and any shell comment, hiding quoted prose."""
|
|
400
|
+
code: list[str] = []
|
|
401
|
+
state = "plain"
|
|
402
|
+
escaped = False
|
|
403
|
+
index = 0
|
|
404
|
+
while index < len(line):
|
|
405
|
+
char = line[index]
|
|
406
|
+
if escaped:
|
|
407
|
+
code.append(" ")
|
|
408
|
+
escaped = False
|
|
409
|
+
elif state == "single":
|
|
410
|
+
code.append(" ")
|
|
411
|
+
if char == "'":
|
|
412
|
+
state = "plain"
|
|
413
|
+
elif state == "double":
|
|
414
|
+
code.append(" ")
|
|
415
|
+
if char == '"':
|
|
416
|
+
state = "plain"
|
|
417
|
+
elif char == "\\":
|
|
418
|
+
escaped = True
|
|
419
|
+
elif line.startswith("$'", index):
|
|
420
|
+
end = ansi_c_quote_end(line, index)
|
|
421
|
+
if end is None:
|
|
422
|
+
code.append(" ")
|
|
423
|
+
index += 1
|
|
424
|
+
continue
|
|
425
|
+
code.append(" " * (end - index))
|
|
426
|
+
index = end
|
|
427
|
+
continue
|
|
428
|
+
elif char == "'":
|
|
429
|
+
code.append(" ")
|
|
430
|
+
state = "single"
|
|
431
|
+
elif char == '"':
|
|
432
|
+
code.append(" ")
|
|
433
|
+
state = "double"
|
|
434
|
+
elif char == "\\":
|
|
435
|
+
code.append(" ")
|
|
436
|
+
escaped = True
|
|
437
|
+
elif char == "#" and (index == 0 or is_bash_blank(line[index - 1])):
|
|
438
|
+
return "".join(code), line[index + 1 :]
|
|
439
|
+
else:
|
|
440
|
+
code.append(char)
|
|
441
|
+
index += 1
|
|
442
|
+
return "".join(code), ""
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def collapse_line_continuations(command: str) -> str:
|
|
446
|
+
"""Remove Bash backslash-newline pairs outside single-quoted text."""
|
|
447
|
+
result: list[str] = []
|
|
448
|
+
state = "plain"
|
|
449
|
+
escaped = False
|
|
450
|
+
index = 0
|
|
451
|
+
while index < len(command):
|
|
452
|
+
char = command[index]
|
|
453
|
+
if escaped:
|
|
454
|
+
result.append(char)
|
|
455
|
+
escaped = False
|
|
456
|
+
elif state == "single":
|
|
457
|
+
result.append(char)
|
|
458
|
+
if char == "'":
|
|
459
|
+
state = "plain"
|
|
460
|
+
elif char == "\\" and command.startswith("\n", index + 1):
|
|
461
|
+
index += 2
|
|
462
|
+
continue
|
|
463
|
+
elif char == "\\":
|
|
464
|
+
result.append(char)
|
|
465
|
+
escaped = True
|
|
466
|
+
elif state == "plain" and command.startswith("$'", index):
|
|
467
|
+
end = ansi_c_quote_end(command, index)
|
|
468
|
+
if end is None:
|
|
469
|
+
result.append(char)
|
|
470
|
+
else:
|
|
471
|
+
# Preserve the whole inert ANSI-C token verbatim so its bytes
|
|
472
|
+
# stay a single quoted unit to every downstream walker; a
|
|
473
|
+
# backslash-newline inside it is part of the token, not a line
|
|
474
|
+
# continuation to strip.
|
|
475
|
+
result.append(command[index:end])
|
|
476
|
+
index = end
|
|
477
|
+
continue
|
|
478
|
+
elif char == "'" and state == "plain":
|
|
479
|
+
result.append(char)
|
|
480
|
+
state = "single"
|
|
481
|
+
elif char == '"':
|
|
482
|
+
result.append(char)
|
|
483
|
+
state = "plain" if state == "double" else "double"
|
|
484
|
+
else:
|
|
485
|
+
result.append(char)
|
|
486
|
+
index += 1
|
|
487
|
+
return "".join(result)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def writer_owns_real_marker(command: str, markers: list[Marker]) -> bool:
|
|
491
|
+
"""Detect a supported writer on a line containing a real heredoc marker."""
|
|
492
|
+
logical_command = collapse_line_continuations(command)
|
|
493
|
+
logical_markers = (
|
|
494
|
+
markers if logical_command == command else top_level_markers(logical_command)
|
|
495
|
+
)
|
|
496
|
+
lines = bash_lines(logical_command)
|
|
497
|
+
for marker in logical_markers:
|
|
498
|
+
line_index = logical_command.count("\n", 0, marker.start)
|
|
499
|
+
if line_has_allowed_writer(lines[line_index]):
|
|
500
|
+
return True
|
|
501
|
+
return False
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def writer_has_commented_marker_and_following_code(command: str) -> bool:
|
|
505
|
+
"""Reject fake writer markers whose following lines would execute."""
|
|
506
|
+
lines = bash_lines(command)
|
|
507
|
+
for index, line in enumerate(lines):
|
|
508
|
+
_code, comment = unquoted_code_and_comment(line)
|
|
509
|
+
if (
|
|
510
|
+
line_has_allowed_writer(line)
|
|
511
|
+
and "<<" in comment
|
|
512
|
+
and any(candidate.strip() for candidate in lines[index + 1 :])
|
|
513
|
+
):
|
|
514
|
+
return True
|
|
515
|
+
return False
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def has_active_command_substitution(command: str) -> bool:
|
|
519
|
+
"""Detect substitutions outside single-quoted text and shell comments."""
|
|
520
|
+
state = "plain"
|
|
521
|
+
escaped = False
|
|
522
|
+
index = 0
|
|
523
|
+
while index < len(command):
|
|
524
|
+
char = command[index]
|
|
525
|
+
if escaped:
|
|
526
|
+
escaped = False
|
|
527
|
+
elif state == "single":
|
|
528
|
+
if char == "'":
|
|
529
|
+
state = "plain"
|
|
530
|
+
elif state == "double":
|
|
531
|
+
if char == '"':
|
|
532
|
+
state = "plain"
|
|
533
|
+
elif char == "\\":
|
|
534
|
+
escaped = True
|
|
535
|
+
elif char == "`" or command.startswith("$(", index):
|
|
536
|
+
return True
|
|
537
|
+
elif command.startswith("$'", index):
|
|
538
|
+
end = ansi_c_quote_end(command, index)
|
|
539
|
+
if end is None:
|
|
540
|
+
return True
|
|
541
|
+
index = end
|
|
542
|
+
continue
|
|
543
|
+
elif char == "'":
|
|
544
|
+
state = "single"
|
|
545
|
+
elif char == '"':
|
|
546
|
+
state = "double"
|
|
547
|
+
elif char == "\\":
|
|
548
|
+
escaped = True
|
|
549
|
+
elif char == "#" and (
|
|
550
|
+
index == 0 or is_bash_blank(command[index - 1])
|
|
551
|
+
):
|
|
552
|
+
newline = command.find("\n", index)
|
|
553
|
+
if newline < 0:
|
|
554
|
+
return False
|
|
555
|
+
index = newline
|
|
556
|
+
elif char == "`" or command.startswith("$(", index):
|
|
557
|
+
return True
|
|
558
|
+
index += 1
|
|
559
|
+
return False
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def collapse_body_continuations(body: str) -> str:
|
|
563
|
+
"""Remove bash ``\\<newline>`` line continuations under heredoc-body rules.
|
|
564
|
+
|
|
565
|
+
An UNQUOTED here-doc body performs NO quote processing (``'``/``"``/``#`` are
|
|
566
|
+
literal bytes), but bash STILL removes an unescaped ``\\<newline>`` — the
|
|
567
|
+
bash manual: for an unquoted here-doc "the character sequence ``\\newline`` is
|
|
568
|
+
ignored". The whole-command ``collapse_line_continuations`` applies a FLAT
|
|
569
|
+
shell quote state, so a single body apostrophe flips it into a phantom
|
|
570
|
+
single-quoted string and it REFUSES to join a ``$\\<newline>(`` continuation,
|
|
571
|
+
leaving ``$`` and ``(`` on separate lines where the per-line substitution
|
|
572
|
+
scan sees neither — smuggling a live ``$(...)`` past the wall (issue #1958
|
|
573
|
+
Finding R4). The body window has no quote state to get wrong, so this joins
|
|
574
|
+
continuations with backslash-only semantics: a backslash escapes the next
|
|
575
|
+
single byte (so ``\\\\`` is a literal backslash whose following newline is
|
|
576
|
+
real, and ``\\$`` cannot start a substitution), and a backslash directly
|
|
577
|
+
before a newline is dropped together with that newline. Feed the joined body
|
|
578
|
+
to ``body_line_has_substitution`` so a split ``$(`` is scanned as the one
|
|
579
|
+
contiguous token bash will execute. Fail-closed: a trailing lone backslash is
|
|
580
|
+
kept verbatim (it can start no substitution).
|
|
581
|
+
"""
|
|
582
|
+
result: list[str] = []
|
|
583
|
+
index = 0
|
|
584
|
+
length = len(body)
|
|
585
|
+
while index < length:
|
|
586
|
+
char = body[index]
|
|
587
|
+
if char == "\\":
|
|
588
|
+
if index + 1 < length and body[index + 1] == "\n":
|
|
589
|
+
index += 2
|
|
590
|
+
continue
|
|
591
|
+
result.append(body[index : index + 2])
|
|
592
|
+
index += 2
|
|
593
|
+
continue
|
|
594
|
+
result.append(char)
|
|
595
|
+
index += 1
|
|
596
|
+
return "".join(result)
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def trailing_continuation(line: str) -> str | None:
|
|
600
|
+
"""Strip a bash ``\\<newline>`` continuation backslash, or ``None``.
|
|
601
|
+
|
|
602
|
+
A here-doc body line ends in a continuation exactly when it ends in an ODD
|
|
603
|
+
run of backslashes: each pair is one escaped literal backslash, so a leftover
|
|
604
|
+
single backslash is the one that escapes the following newline. Parity of the
|
|
605
|
+
TRAILING run is sufficient — any backslash earlier in the line is separated
|
|
606
|
+
from the run by a non-backslash byte, which ends that escape.
|
|
607
|
+
"""
|
|
608
|
+
stripped = line.rstrip("\\")
|
|
609
|
+
if (len(line) - len(stripped)) % 2 == 0:
|
|
610
|
+
return None
|
|
611
|
+
return line[:-1]
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def terminator_line_index(command: str, marker: Marker) -> int | None:
|
|
615
|
+
"""Index of the physical line that closes ``marker``, or ``None``.
|
|
616
|
+
|
|
617
|
+
THE single home of "where does this here-doc body end", consulted by the
|
|
618
|
+
closure check, the body scanner, the literal-body strip and the body
|
|
619
|
+
neutraliser so no two of them can ever disagree about the window (issue
|
|
620
|
+
#1993 R5a).
|
|
621
|
+
|
|
622
|
+
Bash removes ``\\<newline>`` continuations from an UNQUOTED here-doc body
|
|
623
|
+
BEFORE it matches the delimiter, so a trailing backslash on the last body
|
|
624
|
+
line JOINS that line to the delimiter line and the here-doc is NOT closed
|
|
625
|
+
there — the real body swallows the apparent terminator and keeps consuming
|
|
626
|
+
following lines, where a ``$(...)`` the classifier believed was ordinary
|
|
627
|
+
post-here-doc text is expanded and EXECUTED. Matching the delimiter per
|
|
628
|
+
PHYSICAL line therefore under-reads the body window for unquoted delimiters.
|
|
629
|
+
Measured ground truth: ``cat <<EOF`` / ``foo\\`` / ``EOF`` / ``bar`` prints
|
|
630
|
+
``fooEOF\\nbar`` (never terminated), while the same shape with ``<<'EOF'``
|
|
631
|
+
prints ``foo\\`` and then fails on ``bar: command not found``.
|
|
632
|
+
|
|
633
|
+
A QUOTED (or backslash-quoted) delimiter makes the body fully literal —
|
|
634
|
+
backslash is DATA there, not an escape — so those markers keep the exact
|
|
635
|
+
per-physical-line match, which the same measurement proves correct.
|
|
636
|
+
"""
|
|
637
|
+
lines = bash_lines(command)
|
|
638
|
+
marker_line = command.count("\n", 0, marker.start)
|
|
639
|
+
pending: list[str] = []
|
|
640
|
+
for index in range(marker_line + 1, len(lines)):
|
|
641
|
+
line = lines[index]
|
|
642
|
+
if not marker.quoted:
|
|
643
|
+
continued = trailing_continuation(line)
|
|
644
|
+
if continued is not None:
|
|
645
|
+
pending.append(continued)
|
|
646
|
+
continue
|
|
647
|
+
candidate = "".join(pending) + line
|
|
648
|
+
pending = []
|
|
649
|
+
if marker.strip_tabs:
|
|
650
|
+
candidate = candidate.lstrip("\t")
|
|
651
|
+
if candidate == marker.delimiter:
|
|
652
|
+
return index
|
|
653
|
+
return None
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def neutralize_heredoc_bodies(command: str, markers: list[Marker]) -> str:
|
|
657
|
+
"""Blank every real here-doc body window, preserving offsets.
|
|
658
|
+
|
|
659
|
+
``has_active_command_substitution`` runs ONE flat single/double-quote state
|
|
660
|
+
machine across the whole command — including here-doc bodies, where bash
|
|
661
|
+
applies NO quote processing at all. An odd apostrophe in an unquoted body
|
|
662
|
+
(``it's fine``) therefore opens a phantom single-quoted string in the flat
|
|
663
|
+
scanner that persists PAST the terminator, hiding a live ``$(...)`` on a
|
|
664
|
+
FOLLOWING command line that bash happily executes (issue #1993 R5b). The
|
|
665
|
+
trigger is precisely an odd ``'`` count: an even ``''``, an odd ``"``, a
|
|
666
|
+
``#``, an ANSI-C ``$'a`` and a lone trailing backslash all leave the flat
|
|
667
|
+
state correct, which is what pins the defect to this one branch.
|
|
668
|
+
|
|
669
|
+
``strip_provably_literal_body`` cannot fix this: it bails whenever the marker
|
|
670
|
+
is unquoted, so the poisoning body is exactly the one it never touches. So
|
|
671
|
+
blank the body window of EVERY marker bash really treats as a here-doc
|
|
672
|
+
redirection before the flat scan runs. Bytes are replaced with spaces and
|
|
673
|
+
newlines are preserved, so marker offsets and line numbering stay valid for
|
|
674
|
+
every later walker.
|
|
675
|
+
|
|
676
|
+
Blanking loses nothing: a substitution INSIDE an unquoted body is still
|
|
677
|
+
caught by ``unquoted_heredoc_body_has_substitution`` (scanned with correct
|
|
678
|
+
here-doc-body semantics), and a quoted body is genuinely inert to bash. A
|
|
679
|
+
marker that is not closed blanks to end of command — that command is
|
|
680
|
+
MALFORMED on the closure check regardless, and its trailing region is still
|
|
681
|
+
body-scanned. As everywhere else, a marker nested inside an open quoted
|
|
682
|
+
string is NOT a here-doc to bash and is skipped, so its "body" stays fully
|
|
683
|
+
visible to the flat scan (issue #1958 Finding 1).
|
|
684
|
+
"""
|
|
685
|
+
lines = bash_lines(command)
|
|
686
|
+
blanked = list(lines)
|
|
687
|
+
for marker in markers:
|
|
688
|
+
if cross_line_quote_state(command, marker.start) != "plain":
|
|
689
|
+
continue
|
|
690
|
+
marker_line = command.count("\n", 0, marker.start)
|
|
691
|
+
end = terminator_line_index(command, marker)
|
|
692
|
+
stop = len(lines) if end is None else end
|
|
693
|
+
for index in range(marker_line + 1, stop):
|
|
694
|
+
blanked[index] = " " * len(lines[index])
|
|
695
|
+
return "\n".join(blanked)
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def body_line_has_substitution(line: str) -> bool:
|
|
699
|
+
"""True if an unquoted-heredoc-body line contains an active substitution.
|
|
700
|
+
|
|
701
|
+
In an unquoted-delimiter heredoc body bash suppresses ALL quote and comment
|
|
702
|
+
processing — ``'``, ``"`` and ``#`` are literal bytes — but keeps
|
|
703
|
+
parameter/command/arithmetic expansion active, so ``$(...)`` and `` `...` ``
|
|
704
|
+
still EXECUTE. The only in-body metacharacter is the backslash, which escapes
|
|
705
|
+
the following ``$``, `` ` ``, ``\\`` or newline; every other byte (including
|
|
706
|
+
the quotes and ``#`` that blind the flat scanner) is inert text. So scan the
|
|
707
|
+
body raw, honouring only that escaping, and flag a bare ``$(`` or backtick —
|
|
708
|
+
the same command-substitution tokens ``has_active_command_substitution``
|
|
709
|
+
detects elsewhere, just without the quote/comment state machine that is wrong
|
|
710
|
+
for this region (issue #1958 Finding R3).
|
|
711
|
+
"""
|
|
712
|
+
index = 0
|
|
713
|
+
length = len(line)
|
|
714
|
+
while index < length:
|
|
715
|
+
char = line[index]
|
|
716
|
+
if char == "\\":
|
|
717
|
+
index += 2
|
|
718
|
+
continue
|
|
719
|
+
if char == "`" or line.startswith("$(", index):
|
|
720
|
+
return True
|
|
721
|
+
index += 1
|
|
722
|
+
return False
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def unquoted_heredoc_body_has_substitution(
|
|
726
|
+
command: str, markers: list[Marker]
|
|
727
|
+
) -> bool:
|
|
728
|
+
"""Detect a live substitution inside an unquoted top-level heredoc body.
|
|
729
|
+
|
|
730
|
+
``has_active_command_substitution`` applies flat shell quote/comment
|
|
731
|
+
semantics to the whole command, but an UNQUOTED ``<<EOF`` body follows
|
|
732
|
+
different rules (see ``body_line_has_substitution``): a lone apostrophe or
|
|
733
|
+
``#`` in the body flips the flat scanner's state and hides a live
|
|
734
|
+
``$(...)``/backtick that bash nonetheless expands, smuggling execution past
|
|
735
|
+
the wall (issue #1958 Finding R3). This complements the flat scan by walking
|
|
736
|
+
only the body window of each unquoted, top-level, closed heredoc with
|
|
737
|
+
heredoc-body semantics.
|
|
738
|
+
|
|
739
|
+
A quoted-delimiter body (``Marker.quoted``) is genuinely inert to bash and is
|
|
740
|
+
skipped — that is the whole point of the Finding-1 literal-payload win.
|
|
741
|
+
Before scanning, the marker is re-verified to sit at bash top level under
|
|
742
|
+
CROSS-LINE quote tracking (mirroring ``strip_provably_literal_body``): a
|
|
743
|
+
heredoc-shaped ``<<EOF`` nested inside an open single-quoted string is not a
|
|
744
|
+
real heredoc — its body is literal string data bash never expands — so
|
|
745
|
+
scanning it would over-block ordinary quoted prose; and one nested inside an
|
|
746
|
+
open double-quoted string has its ``$(...)`` already caught by the flat scan.
|
|
747
|
+
Either way, only markers bash actually treats as top-level heredoc
|
|
748
|
+
redirections get the body scan.
|
|
749
|
+
"""
|
|
750
|
+
lines = bash_lines(command)
|
|
751
|
+
for marker in markers:
|
|
752
|
+
if marker.quoted:
|
|
753
|
+
continue
|
|
754
|
+
if cross_line_quote_state(command, marker.start) != "plain":
|
|
755
|
+
continue
|
|
756
|
+
marker_line = command.count("\n", 0, marker.start)
|
|
757
|
+
end = terminator_line_index(command, marker)
|
|
758
|
+
stop = len(lines) if end is None else end
|
|
759
|
+
body_lines = lines[marker_line + 1 : stop]
|
|
760
|
+
# Join the raw body window under bash's unquoted-here-doc ``\<newline>``
|
|
761
|
+
# removal BEFORE scanning, so a ``$(`` a caller split across a line
|
|
762
|
+
# continuation is seen as the one contiguous token bash executes. The
|
|
763
|
+
# window itself comes from ``terminator_line_index``, which applies that
|
|
764
|
+
# SAME removal before matching the delimiter — because bash does (issue
|
|
765
|
+
# #1993 R5a). An earlier revision matched the terminator per physical
|
|
766
|
+
# line here on the belief that bash matches the delimiter before
|
|
767
|
+
# continuation processing; that is false for an unquoted delimiter, and
|
|
768
|
+
# it let a trailing backslash on the last body line swallow the
|
|
769
|
+
# terminator so this scan stopped short of the region bash still treats
|
|
770
|
+
# as body. This does not rely on ``collapse_line_continuations`` — whose
|
|
771
|
+
# flat quote state a body apostrophe corrupts — which is the whole point
|
|
772
|
+
# of Finding R4.
|
|
773
|
+
body = collapse_body_continuations("\n".join(body_lines))
|
|
774
|
+
if body_line_has_substitution(body):
|
|
775
|
+
return True
|
|
776
|
+
return False
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def parse_marker(line: str, start: int, offset: int) -> Marker | None:
|
|
780
|
+
index = start + 2
|
|
781
|
+
strip_tabs = False
|
|
782
|
+
if index < len(line) and line[index] == "-":
|
|
783
|
+
strip_tabs = True
|
|
784
|
+
index += 1
|
|
785
|
+
while index < len(line) and line[index] in " \t":
|
|
786
|
+
index += 1
|
|
787
|
+
if index >= len(line):
|
|
788
|
+
return None
|
|
789
|
+
quote = line[index] if line[index] in "'\"" else None
|
|
790
|
+
quoted = False
|
|
791
|
+
if quote:
|
|
792
|
+
index += 1
|
|
793
|
+
end = line.find(quote, index)
|
|
794
|
+
if end < 0:
|
|
795
|
+
return None
|
|
796
|
+
delimiter = line[index:end]
|
|
797
|
+
final = end + 1
|
|
798
|
+
# Deliberate POSIX divergence, fail-safe: POSIX makes the body
|
|
799
|
+
# non-expanding when ANY part of the delimiter is quoted. Partial forms
|
|
800
|
+
# like `<<EO'F'` stay unmodelled (mis-tokenized as delimiter EO, so the
|
|
801
|
+
# terminator never matches and the command fails closed as MALFORMED).
|
|
802
|
+
# Only a delimiter this parser can PROVE was one full quote pair earns
|
|
803
|
+
# quoted=True, and trailing word characters after the closing quote
|
|
804
|
+
# (`<<'EOF'X` — real bash delimiter EOFX) keep it conservative too: the
|
|
805
|
+
# next character must end the token.
|
|
806
|
+
quoted = final >= len(line) or line[final] in " \t;&|)<>#"
|
|
807
|
+
elif line[index] == "\\":
|
|
808
|
+
# `<<\DELIM` is a BACKSLASH-quoted delimiter: bash and POSIX make its
|
|
809
|
+
# body fully literal, exactly like `<<'DELIM'` (measured: `cat <<\EOF`
|
|
810
|
+
# with body `it's $(echo RAN)` prints that text verbatim, unexpanded).
|
|
811
|
+
# This spelling used to record NO Marker at all, on the rationale that an
|
|
812
|
+
# unmodelled body stays raw-visible to every content guard. That covers
|
|
813
|
+
# the body's own contents but NOT the body's ability to corrupt the flat
|
|
814
|
+
# scanner's quote state for text AFTER the terminator: an apostrophe in
|
|
815
|
+
# an invisible body still opened a phantom string that hid a live
|
|
816
|
+
# `$(...)` on a following line (issue #1993 R5c). Recording it as the
|
|
817
|
+
# quoted marker bash actually treats it as is both more faithful and
|
|
818
|
+
# what brings it under `neutralize_heredoc_bodies`. The same
|
|
819
|
+
# token-must-end discipline as the quote-pair branch applies, so exotic
|
|
820
|
+
# spellings (`<<\EOF'x'` — real bash delimiter EOFx) stay conservative.
|
|
821
|
+
# quoted=True here is a statement about BODY semantics only. Recording a
|
|
822
|
+
# Marker where there was none also arms the writer-owns-a-real-marker and
|
|
823
|
+
# multi-marker rules, so `gh issue create --body-file - <<\EOF` and
|
|
824
|
+
# `cat <<\A <<B` now fail closed at MALFORMED instead of passing through
|
|
825
|
+
# at UNSUPPORTED. Both are valid, harmless bash, so that is a real
|
|
826
|
+
# over-block — accepted and test-pinned, because `classify_safe` requires
|
|
827
|
+
# the literal `<<'DELIM'` spelling and `<<\EOF` can never reach SAFE
|
|
828
|
+
# anyway. The documented payload-strip workaround is `<<'EOF'`.
|
|
829
|
+
index += 1
|
|
830
|
+
match = re.match(r"[A-Za-z_][A-Za-z0-9_]*", line[index:])
|
|
831
|
+
if match is None:
|
|
832
|
+
return None
|
|
833
|
+
delimiter = match.group(0)
|
|
834
|
+
final = index + len(delimiter)
|
|
835
|
+
quoted = final >= len(line) or line[final] in " \t;&|)<>#"
|
|
836
|
+
else:
|
|
837
|
+
match = re.match(r"[A-Za-z_][A-Za-z0-9_]*", line[index:])
|
|
838
|
+
if match is None:
|
|
839
|
+
return None
|
|
840
|
+
delimiter = match.group(0)
|
|
841
|
+
final = index + len(delimiter)
|
|
842
|
+
if not delimiter:
|
|
843
|
+
return None
|
|
844
|
+
return Marker(offset + start, offset + final, delimiter, strip_tabs, quoted)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def cross_line_quote_state(command: str, offset: int) -> str:
|
|
848
|
+
"""Return the single/double/plain quote state bash sees at ``offset``.
|
|
849
|
+
|
|
850
|
+
Unlike ``top_level_markers`` (which re-initialises quote state at the start
|
|
851
|
+
of every line), this walks the whole command with a SINGLE cross-line state
|
|
852
|
+
machine — the same quote/escape/comment model ``has_active_command_substitution``
|
|
853
|
+
uses. It answers "is this position inside an open single- or double-quoted
|
|
854
|
+
string?" so a heredoc-shaped token can be checked against bash's real parse.
|
|
855
|
+
"""
|
|
856
|
+
state = "plain"
|
|
857
|
+
escaped = False
|
|
858
|
+
index = 0
|
|
859
|
+
limit = min(offset, len(command))
|
|
860
|
+
while index < limit:
|
|
861
|
+
char = command[index]
|
|
862
|
+
if escaped:
|
|
863
|
+
escaped = False
|
|
864
|
+
elif state == "single":
|
|
865
|
+
if char == "'":
|
|
866
|
+
state = "plain"
|
|
867
|
+
elif state == "double":
|
|
868
|
+
if char == '"':
|
|
869
|
+
state = "plain"
|
|
870
|
+
elif char == "\\":
|
|
871
|
+
escaped = True
|
|
872
|
+
elif command.startswith("$'", index):
|
|
873
|
+
end = ansi_c_quote_end(command, index)
|
|
874
|
+
if end is None or end > limit:
|
|
875
|
+
# The offset sits inside (or an unterminated) ANSI-C token: bash
|
|
876
|
+
# is not at plain top level there, so any heredoc-shaped marker
|
|
877
|
+
# is inert. Report a non-plain state so the strip gate refuses.
|
|
878
|
+
return "single"
|
|
879
|
+
index = end
|
|
880
|
+
continue
|
|
881
|
+
elif char == "'":
|
|
882
|
+
state = "single"
|
|
883
|
+
elif char == '"':
|
|
884
|
+
state = "double"
|
|
885
|
+
elif char == "\\":
|
|
886
|
+
escaped = True
|
|
887
|
+
elif char == "#" and (index == 0 or is_bash_blank(command[index - 1])):
|
|
888
|
+
newline = command.find("\n", index)
|
|
889
|
+
if newline < 0 or newline >= limit:
|
|
890
|
+
return state
|
|
891
|
+
index = newline
|
|
892
|
+
continue
|
|
893
|
+
index += 1
|
|
894
|
+
return state
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
def strip_provably_literal_body(command: str, markers: list[Marker]) -> str:
|
|
898
|
+
"""Drop the body window of a single fully-quoted, closed heredoc.
|
|
899
|
+
|
|
900
|
+
A fully-quoted delimiter (Marker.quoted) makes the body literal data to
|
|
901
|
+
bash, so substitution tokens inside it must not flip the classification to
|
|
902
|
+
MALFORMED (issue #1958). The exclusion is deliberately narrow: EXACTLY one
|
|
903
|
+
marker, provably quoted, provably closed, and — critically — a marker bash
|
|
904
|
+
actually treats as a top-level heredoc redirection. ``top_level_markers``
|
|
905
|
+
resets quote state per line, so a ``<<'DELIM'`` line nested inside an open
|
|
906
|
+
multi-line single/double-quoted string is mis-recorded as a top-level
|
|
907
|
+
quoted heredoc. To bash there is NO heredoc there — the whole thing is one
|
|
908
|
+
string, and a ``$(...)`` inside a double-quoted string is EXECUTED. Excluding
|
|
909
|
+
that fake "body" window would delete the live substitution before the scan
|
|
910
|
+
runs, smuggling arbitrary command execution past the wall (issue #1958
|
|
911
|
+
Finding 1). So before excluding, re-verify the marker sits at bash top level
|
|
912
|
+
(quote state ``plain``) under CROSS-LINE quote tracking; if it is inside an
|
|
913
|
+
open quote, strip nothing and let ``has_active_command_substitution`` see the
|
|
914
|
+
real substitution. Anything else returns the command unchanged so the scan
|
|
915
|
+
stays fail-closed. Only the body lines are removed; the header line,
|
|
916
|
+
terminator line, and everything after remain, so a substitution on the
|
|
917
|
+
command line proper is still detected. The raw payload itself still reaches
|
|
918
|
+
every content guard because this classifier returns UNSUPPORTED (raw
|
|
919
|
+
pass-through) for the target class, never SAFE.
|
|
920
|
+
"""
|
|
921
|
+
if len(markers) != 1 or not markers[0].quoted:
|
|
922
|
+
return command
|
|
923
|
+
marker = markers[0]
|
|
924
|
+
if cross_line_quote_state(command, marker.start) != "plain":
|
|
925
|
+
return command
|
|
926
|
+
lines = bash_lines(command)
|
|
927
|
+
marker_line = command.count("\n", 0, marker.start)
|
|
928
|
+
end = terminator_line_index(command, marker)
|
|
929
|
+
if end is None:
|
|
930
|
+
return command
|
|
931
|
+
return "\n".join(lines[: marker_line + 1] + lines[end:])
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
def marker_is_closed(command: str, marker: Marker) -> bool:
|
|
935
|
+
"""True when bash really terminates this here-doc inside the command.
|
|
936
|
+
|
|
937
|
+
Delegates to ``terminator_line_index`` so the closure verdict and the body
|
|
938
|
+
window are decided by one rule. For an unquoted delimiter that rule removes
|
|
939
|
+
``\\<newline>`` continuations first, so a trailing backslash on the last body
|
|
940
|
+
line correctly reads as NOT closed — which is what bash does, and what makes
|
|
941
|
+
the swallowed-terminator shape fail closed as MALFORMED (issue #1993 R5a).
|
|
942
|
+
"""
|
|
943
|
+
return terminator_line_index(command, marker) is not None
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def main() -> int:
|
|
947
|
+
command = sys.stdin.read()
|
|
948
|
+
if sys.argv[1:] == ["--literal-display"]:
|
|
949
|
+
print(literal_display_projection(command), end="")
|
|
950
|
+
return SAFE
|
|
951
|
+
if "<<" not in command:
|
|
952
|
+
print(command, end="")
|
|
953
|
+
return SAFE
|
|
954
|
+
try:
|
|
955
|
+
sanitized = classify_safe(command)
|
|
956
|
+
except ValueError:
|
|
957
|
+
return MALFORMED
|
|
958
|
+
if sanitized is not None:
|
|
959
|
+
print(sanitized, end="")
|
|
960
|
+
return SAFE
|
|
961
|
+
|
|
962
|
+
logical_command = collapse_line_continuations(command)
|
|
963
|
+
markers = top_level_markers(logical_command)
|
|
964
|
+
if writer_has_commented_marker_and_following_code(logical_command):
|
|
965
|
+
return MALFORMED
|
|
966
|
+
if writer_owns_real_marker(logical_command, markers):
|
|
967
|
+
return MALFORMED
|
|
968
|
+
# Textual operators inside quotes or comments are not heredocs. Leave the
|
|
969
|
+
# raw command unchanged so ordinary guard matching proceeds normally.
|
|
970
|
+
if not markers and not has_active_command_substitution(logical_command):
|
|
971
|
+
return UNSUPPORTED
|
|
972
|
+
|
|
973
|
+
# A supported writer that failed the exact safe grammar is ambiguous: do
|
|
974
|
+
# not let chaining, alternate redirects, or an expanding delimiter turn a
|
|
975
|
+
# would-be payload exemption into a bypass.
|
|
976
|
+
if bash_lines(logical_command) and line_has_allowed_writer(
|
|
977
|
+
bash_lines(logical_command)[0]
|
|
978
|
+
):
|
|
979
|
+
return MALFORMED
|
|
980
|
+
|
|
981
|
+
# Nested substitution plus a heredoc is executable shell syntax unless it
|
|
982
|
+
# matched the one exact quoted `--body "$(cat ...)"` form above. Here-doc
|
|
983
|
+
# bodies are neutralised before the flat scan: bash applies NO quote
|
|
984
|
+
# processing inside a body, so leaving those bytes in a flat quote state
|
|
985
|
+
# machine let an odd body apostrophe poison the scanner PAST the terminator
|
|
986
|
+
# and hide a live substitution on a following command line (issue #1993
|
|
987
|
+
# R5b). Neutralising preserves offsets, so the narrower literal-body strip
|
|
988
|
+
# still applies on top; the text outside every body window is still scanned,
|
|
989
|
+
# and in-body substitutions are covered by the body-semantics scan below.
|
|
990
|
+
if has_active_command_substitution(
|
|
991
|
+
strip_provably_literal_body(
|
|
992
|
+
neutralize_heredoc_bodies(logical_command, markers), markers
|
|
993
|
+
)
|
|
994
|
+
) or unquoted_heredoc_body_has_substitution(logical_command, markers):
|
|
995
|
+
return MALFORMED
|
|
996
|
+
if len(markers) > 1:
|
|
997
|
+
return MALFORMED
|
|
998
|
+
if markers and not marker_is_closed(logical_command, markers[0]):
|
|
999
|
+
return MALFORMED
|
|
1000
|
+
return UNSUPPORTED
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
if __name__ == "__main__":
|
|
1004
|
+
raise SystemExit(main())
|