@codyswann/lisa 4.74.9 → 4.75.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +60 -0
- package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +73 -40
- package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +54 -0
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +3 -2
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +6 -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 +735 -8
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-cdk-devin/skills/setup-aws-accounts/SKILL.md +292 -0
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/.claude-plugin/plugin.json +8 -0
- package/plugins/lisa-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-devin/.mcp.json +8 -0
- package/plugins/lisa-devin/agents/architecture-specialist.md +32 -0
- package/plugins/lisa-devin/agents/bug-fixer.md +43 -0
- package/plugins/lisa-devin/agents/builder.md +44 -0
- package/plugins/lisa-devin/agents/confluence-prd-intake.md +65 -0
- package/plugins/lisa-devin/agents/debug-specialist.md +34 -0
- package/plugins/lisa-devin/agents/eval-specialist.md +33 -0
- package/plugins/lisa-devin/agents/git-history-analyzer.md +183 -0
- package/plugins/lisa-devin/agents/github-agent.md +155 -0
- package/plugins/lisa-devin/agents/github-build-intake.md +64 -0
- package/plugins/lisa-devin/agents/github-prd-intake.md +66 -0
- package/plugins/lisa-devin/agents/jira-agent.md +135 -0
- package/plugins/lisa-devin/agents/jira-build-intake.md +64 -0
- package/plugins/lisa-devin/agents/learner.md +85 -0
- package/plugins/lisa-devin/agents/learning-judge.md +119 -0
- package/plugins/lisa-devin/agents/learnings-synthesizer.md +155 -0
- package/plugins/lisa-devin/agents/linear-agent.md +138 -0
- package/plugins/lisa-devin/agents/linear-build-intake.md +64 -0
- package/plugins/lisa-devin/agents/linear-prd-intake.md +66 -0
- package/plugins/lisa-devin/agents/notion-prd-intake.md +63 -0
- package/plugins/lisa-devin/agents/performance-specialist.md +26 -0
- package/plugins/lisa-devin/agents/pr-mining-specialist.md +85 -0
- package/plugins/lisa-devin/agents/product-specialist.md +30 -0
- package/plugins/lisa-devin/agents/quality-specialist.md +30 -0
- package/plugins/lisa-devin/agents/security-specialist.md +43 -0
- package/plugins/lisa-devin/agents/skill-evaluator.md +205 -0
- package/plugins/lisa-devin/agents/spec-conformance-specialist.md +56 -0
- package/plugins/lisa-devin/agents/test-specialist.md +32 -0
- package/plugins/lisa-devin/agents/tracker-mining-specialist.md +85 -0
- package/plugins/lisa-devin/agents/verification-specialist.md +145 -0
- package/plugins/lisa-devin/hooks/auto-update.mjs +824 -0
- package/plugins/lisa-devin/hooks/auto-update.sh +31 -0
- package/plugins/lisa-devin/hooks/block-blind-automerge.sh +1242 -0
- package/plugins/lisa-devin/hooks/block-direct-issue-create.sh +2603 -0
- package/plugins/lisa-devin/hooks/block-host-name-leak.mjs +412 -0
- package/plugins/lisa-devin/hooks/block-host-name-leak.sh +61 -0
- package/plugins/lisa-devin/hooks/block-instruction-file-edits.sh +207 -0
- package/plugins/lisa-devin/hooks/block-managed-file-edits.sh +1175 -0
- package/plugins/lisa-devin/hooks/block-no-verify.sh +2288 -0
- package/plugins/lisa-devin/hooks/block-shell-json-parsing.sh +263 -0
- package/plugins/lisa-devin/hooks/cleanup-stale-worktrees.sh +122 -0
- package/plugins/lisa-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-devin/hooks/discharge-work-item-gates.sh +90 -0
- package/plugins/lisa-devin/hooks/enforce-verification-gate.sh +506 -0
- package/plugins/lisa-devin/hooks/enforcement-vintage-npm.mjs +320 -0
- package/plugins/lisa-devin/hooks/enforcement-vintage.mjs +472 -0
- package/plugins/lisa-devin/hooks/enforcement-vintage.sh +48 -0
- package/plugins/lisa-devin/hooks/failure-signature-index.mjs +717 -0
- package/plugins/lisa-devin/hooks/failure-signature-index.sh +26 -0
- package/plugins/lisa-devin/hooks/guard-dedupe.bash +374 -0
- package/plugins/lisa-devin/hooks/inject-resolved-config.mjs +903 -0
- package/plugins/lisa-devin/hooks/inject-resolved-config.sh +107 -0
- package/plugins/lisa-devin/hooks/inject-rules.sh +49 -0
- package/plugins/lisa-devin/hooks/install-pkgs.sh +84 -0
- package/plugins/lisa-devin/hooks/operational-hazards.mjs +736 -0
- package/plugins/lisa-devin/hooks/operational-hazards.sh +28 -0
- package/plugins/lisa-devin/hooks/parity-safety-net-heredoc.py +1004 -0
- package/plugins/lisa-devin/hooks/parity-safety-net.sh +3275 -0
- package/plugins/lisa-devin/hooks/secrets-preflight.sh +72 -0
- package/plugins/lisa-devin/hooks/setup-jira-cli.sh +205 -0
- package/plugins/lisa-devin/hooks/shell-write-nudge.sh +86 -0
- package/plugins/lisa-devin/hooks/sonar-secrets.sh +205 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet-compare.mjs +518 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet-families.mjs +543 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet.mjs +568 -0
- package/plugins/lisa-devin/hooks/threshold-ratchet.sh +22 -0
- package/plugins/lisa-devin/hooks/withdrawn-rulings.mjs +600 -0
- package/plugins/lisa-devin/hooks/withdrawn-rulings.sh +24 -0
- package/plugins/lisa-devin/hooks/worktree-binding-guard.mjs +1383 -0
- package/plugins/lisa-devin/hooks/worktree-binding-guard.sh +79 -0
- package/plugins/lisa-devin/hooks.json +247 -0
- package/plugins/lisa-devin/rules/eager/00-rule-index.md +82 -0
- package/plugins/lisa-devin/rules/eager/base-rules.md +85 -0
- package/plugins/lisa-devin/rules/eager/coding-philosophy.md +27 -0
- package/plugins/lisa-devin/rules/eager/config-resolution.md +37 -0
- package/plugins/lisa-devin/rules/eager/do-it-now.md +11 -0
- package/plugins/lisa-devin/rules/eager/empirical-inquiry.md +24 -0
- package/plugins/lisa-devin/rules/eager/falsifiable-checks.md +48 -0
- package/plugins/lisa-devin/rules/eager/history-audit.md +27 -0
- package/plugins/lisa-devin/rules/eager/integration-access-layer.md +19 -0
- package/plugins/lisa-devin/rules/eager/intent-routing.md +18 -0
- package/plugins/lisa-devin/rules/eager/learnings-ladder.md +18 -0
- package/plugins/lisa-devin/rules/eager/local-ci-first.md +15 -0
- package/plugins/lisa-devin/rules/eager/measurement-integrity.md +18 -0
- package/plugins/lisa-devin/rules/eager/never-name-a-downstream-project.md +9 -0
- package/plugins/lisa-devin/rules/eager/not-blocked-just-waiting.md +13 -0
- package/plugins/lisa-devin/rules/eager/operational-hazards.md +50 -0
- package/plugins/lisa-devin/rules/eager/report-actionability.md +26 -0
- package/plugins/lisa-devin/rules/eager/security-audit-handling.md +40 -0
- package/plugins/lisa-devin/rules/eager/session-status-updates.md +20 -0
- package/plugins/lisa-devin/rules/eager/settled-decisions.md +75 -0
- package/plugins/lisa-devin/rules/eager/stale-state-claims.md +26 -0
- package/plugins/lisa-devin/rules/eager/tool-access-gate.md +23 -0
- package/plugins/lisa-devin/rules/eager/tracked-work.md +9 -0
- package/plugins/lisa-devin/rules/eager/upstream-to-lisa.md +14 -0
- package/plugins/lisa-devin/rules/eager/verification.md +33 -0
- package/plugins/lisa-devin/rules/eager/wiki-knowledge-source.md +13 -0
- package/plugins/lisa-devin/rules/eager/withdrawn-rulings.md +102 -0
- package/plugins/lisa-devin/rules/reference/automation-runbook-contract.md +355 -0
- package/plugins/lisa-devin/rules/reference/base-rules.md +136 -0
- package/plugins/lisa-devin/rules/reference/bdd-e2e-coverage.md +301 -0
- package/plugins/lisa-devin/rules/reference/blocker-containment.md +165 -0
- package/plugins/lisa-devin/rules/reference/claim-archaeology.md +184 -0
- package/plugins/lisa-devin/rules/reference/claim-evidence-mapping.md +362 -0
- package/plugins/lisa-devin/rules/reference/claim-time-guards.md +135 -0
- package/plugins/lisa-devin/rules/reference/coding-philosophy.md +428 -0
- package/plugins/lisa-devin/rules/reference/config-resolution.md +1225 -0
- package/plugins/lisa-devin/rules/reference/control-reachability.md +166 -0
- package/plugins/lisa-devin/rules/reference/convergent-review.md +145 -0
- package/plugins/lisa-devin/rules/reference/credential-substrate-precedence.md +249 -0
- package/plugins/lisa-devin/rules/reference/dependency-decision-records.md +177 -0
- package/plugins/lisa-devin/rules/reference/dependency-internalization-kit.md +244 -0
- package/plugins/lisa-devin/rules/reference/dependency-trust-classes.md +291 -0
- package/plugins/lisa-devin/rules/reference/deployed-state-readback.md +142 -0
- package/plugins/lisa-devin/rules/reference/derived-branch-plan.md +214 -0
- package/plugins/lisa-devin/rules/reference/design-source-of-truth.md +260 -0
- package/plugins/lisa-devin/rules/reference/design-value-binding.md +311 -0
- package/plugins/lisa-devin/rules/reference/do-it-now.md +19 -0
- package/plugins/lisa-devin/rules/reference/documentation-source-paths.md +31 -0
- package/plugins/lisa-devin/rules/reference/empirical-inquiry.md +28 -0
- package/plugins/lisa-devin/rules/reference/factory-model.md +111 -0
- package/plugins/lisa-devin/rules/reference/falsifiable-checks.md +275 -0
- package/plugins/lisa-devin/rules/reference/history-audit.md +42 -0
- package/plugins/lisa-devin/rules/reference/integration-access-layer.md +89 -0
- package/plugins/lisa-devin/rules/reference/intent-routing.md +428 -0
- package/plugins/lisa-devin/rules/reference/leaf-only-lifecycle.md +227 -0
- package/plugins/lisa-devin/rules/reference/learnings-ladder.md +41 -0
- package/plugins/lisa-devin/rules/reference/local-ci-first.md +34 -0
- package/plugins/lisa-devin/rules/reference/measurement-integrity.md +119 -0
- package/plugins/lisa-devin/rules/reference/never-name-a-downstream-project.md +37 -0
- package/plugins/lisa-devin/rules/reference/not-blocked-just-waiting.md +41 -0
- package/plugins/lisa-devin/rules/reference/observability-audit.md +146 -0
- package/plugins/lisa-devin/rules/reference/operational-hazards.md +115 -0
- package/plugins/lisa-devin/rules/reference/prd-definition-of-ready.md +118 -0
- package/plugins/lisa-devin/rules/reference/prd-lifecycle-rollup.md +192 -0
- package/plugins/lisa-devin/rules/reference/pre-flight-autofill.md +250 -0
- package/plugins/lisa-devin/rules/reference/project-learnings.md +159 -0
- package/plugins/lisa-devin/rules/reference/promotion-contract.md +148 -0
- package/plugins/lisa-devin/rules/reference/readiness-rubric.md +242 -0
- package/plugins/lisa-devin/rules/reference/ready-role-filing.md +212 -0
- package/plugins/lisa-devin/rules/reference/rejection-detection.md +308 -0
- package/plugins/lisa-devin/rules/reference/repo-scope-split.md +119 -0
- package/plugins/lisa-devin/rules/reference/report-actionability.md +61 -0
- package/plugins/lisa-devin/rules/reference/reset-seed-coverage.md +286 -0
- package/plugins/lisa-devin/rules/reference/security-audit-handling.md +55 -0
- package/plugins/lisa-devin/rules/reference/session-status-updates.md +111 -0
- package/plugins/lisa-devin/rules/reference/settled-decisions.md +39 -0
- package/plugins/lisa-devin/rules/reference/stale-state-claims.md +88 -0
- package/plugins/lisa-devin/rules/reference/tool-access-gate.md +114 -0
- package/plugins/lisa-devin/rules/reference/tracked-work.md +49 -0
- package/plugins/lisa-devin/rules/reference/upstream-to-lisa.md +26 -0
- package/plugins/lisa-devin/rules/reference/usage-accounting.md +261 -0
- package/plugins/lisa-devin/rules/reference/verification.md +278 -0
- package/plugins/lisa-devin/rules/reference/wiki-knowledge-source.md +37 -0
- package/plugins/lisa-devin/rules/reference/withdrawn-rulings.md +148 -0
- package/plugins/lisa-devin/rules/reference/work-item-definition-of-ready.md +177 -0
- package/plugins/lisa-devin/rules/reference/work-item-trailer-definition.md +129 -0
- package/plugins/lisa-devin/scripts/automation-run-record.mjs +678 -0
- package/plugins/lisa-devin/scripts/automation-status-claude-adapter.mjs +804 -0
- package/plugins/lisa-devin/scripts/automation-status-codex-adapter.mjs +1081 -0
- package/plugins/lisa-devin/scripts/automation-status-contract-drift.mjs +392 -0
- package/plugins/lisa-devin/scripts/automation-status-expected-fleet.mjs +508 -0
- package/plugins/lisa-devin/scripts/automation-status-report.mjs +200 -0
- package/plugins/lisa-devin/scripts/automation-status-run-history.mjs +216 -0
- package/plugins/lisa-devin/scripts/automation-status-unrecorded-runs.mjs +127 -0
- package/plugins/lisa-devin/scripts/blocker-edge-resolution.mjs +150 -0
- package/plugins/lisa-devin/scripts/cross-pollinate.mjs +763 -0
- package/plugins/lisa-devin/scripts/design-bindings-probe.mjs +713 -0
- package/plugins/lisa-devin/scripts/design-intake-gate.mjs +470 -0
- package/plugins/lisa-devin/scripts/design-source-gate.mjs +610 -0
- package/plugins/lisa-devin/scripts/design-variable-ids.mjs +567 -0
- package/plugins/lisa-devin/scripts/doctor-report.mjs +635 -0
- package/plugins/lisa-devin/scripts/install-remote-agent-aws.mjs +443 -0
- package/plugins/lisa-devin/scripts/intake-blocker-reprobe.mjs +1065 -0
- package/plugins/lisa-devin/scripts/intake-prework-denominator.mjs +249 -0
- package/plugins/lisa-devin/scripts/lifecycle-label-trust.mjs +593 -0
- package/plugins/lisa-devin/scripts/linear-state-write-target.mjs +362 -0
- package/plugins/lisa-devin/scripts/plugin-sync-explain.mjs +654 -0
- package/plugins/lisa-devin/scripts/pr-arming-sweep.mjs +561 -0
- package/plugins/lisa-devin/scripts/project-ideation-idempotency-harness.mjs +338 -0
- package/plugins/lisa-devin/scripts/qa-signal-lifecycle.mjs +523 -0
- package/plugins/lisa-devin/scripts/queue-contract-resolution.mjs +583 -0
- package/plugins/lisa-devin/scripts/queue-health-classification.mjs +157 -0
- package/plugins/lisa-devin/scripts/queue-status-build-readers.mjs +561 -0
- package/plugins/lisa-devin/scripts/queue-status-prd-readers.mjs +452 -0
- package/plugins/lisa-devin/scripts/remote-agent-aws-setup.sh +677 -0
- package/plugins/lisa-devin/scripts/repo-scope-vocabulary-audit.mjs +458 -0
- package/plugins/lisa-devin/scripts/resolve-lifecycle-role.mjs +347 -0
- package/plugins/lisa-devin/scripts/rollup-blocker-classification.mjs +616 -0
- package/plugins/lisa-devin/scripts/scoping-label-audit.mjs +474 -0
- package/plugins/lisa-devin/skills/lisa-acceptance-criteria/SKILL.md +73 -0
- package/plugins/lisa-devin/skills/lisa-agent-design-best-practices/SKILL.md +242 -0
- package/plugins/lisa-devin/skills/lisa-agent-ready/SKILL.md +271 -0
- package/plugins/lisa-devin/skills/lisa-analyze-claude-remote/SKILL.md +434 -0
- package/plugins/lisa-devin/skills/lisa-atlassian-access/SKILL.md +469 -0
- package/plugins/lisa-devin/skills/lisa-atlassian-access/scripts/markdown-to-adf.mjs +236 -0
- package/plugins/lisa-devin/skills/lisa-attribute-failure/SKILL.md +142 -0
- package/plugins/lisa-devin/skills/lisa-automation-status/SKILL.md +149 -0
- package/plugins/lisa-devin/skills/lisa-bug-triage/SKILL.md +23 -0
- package/plugins/lisa-devin/skills/lisa-capability-drift/SKILL.md +71 -0
- package/plugins/lisa-devin/skills/lisa-codebase-research/SKILL.md +87 -0
- package/plugins/lisa-devin/skills/lisa-codify-verification/SKILL.md +213 -0
- package/plugins/lisa-devin/skills/lisa-confluence-prd-intake/SKILL.md +417 -0
- package/plugins/lisa-devin/skills/lisa-confluence-to-tracker/SKILL.md +469 -0
- package/plugins/lisa-devin/skills/lisa-confluence-write-prd/SKILL.md +111 -0
- package/plugins/lisa-devin/skills/lisa-cross-pollinate/SKILL.md +175 -0
- package/plugins/lisa-devin/skills/lisa-debrief/SKILL.md +96 -0
- package/plugins/lisa-devin/skills/lisa-debrief-apply/SKILL.md +106 -0
- package/plugins/lisa-devin/skills/lisa-delivery-effectiveness/SKILL.md +62 -0
- package/plugins/lisa-devin/skills/lisa-design-intake/SKILL.md +198 -0
- package/plugins/lisa-devin/skills/lisa-detect-tooling/SKILL.md +110 -0
- package/plugins/lisa-devin/skills/lisa-detect-tooling/scripts/commands.mjs +479 -0
- package/plugins/lisa-devin/skills/lisa-detect-tooling/scripts/detect-tooling.mjs +658 -0
- package/plugins/lisa-devin/skills/lisa-doctor/SKILL.md +743 -0
- package/plugins/lisa-devin/skills/lisa-drive-pr-to-merge/SKILL.md +1587 -0
- package/plugins/lisa-devin/skills/lisa-epic-triage/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-evaluation-suite/SKILL.md +63 -0
- package/plugins/lisa-devin/skills/lisa-exploratory-qa/SKILL.md +185 -0
- package/plugins/lisa-devin/skills/lisa-fix-linter-error/SKILL.md +45 -0
- package/plugins/lisa-devin/skills/lisa-generate-claude-remote-build-script/SKILL.md +235 -0
- package/plugins/lisa-devin/skills/lisa-git-commit/SKILL.md +53 -0
- package/plugins/lisa-devin/skills/lisa-git-prune/SKILL.md +35 -0
- package/plugins/lisa-devin/skills/lisa-git-submit-pr/SKILL.md +170 -0
- package/plugins/lisa-devin/skills/lisa-github-add-journey/SKILL.md +118 -0
- package/plugins/lisa-devin/skills/lisa-github-build-intake/SKILL.md +745 -0
- package/plugins/lisa-devin/skills/lisa-github-claim/SKILL.md +27 -0
- package/plugins/lisa-devin/skills/lisa-github-create/SKILL.md +105 -0
- package/plugins/lisa-devin/skills/lisa-github-evidence/SKILL.md +120 -0
- package/plugins/lisa-devin/skills/lisa-github-journey/SKILL.md +134 -0
- package/plugins/lisa-devin/skills/lisa-github-prd-intake/SKILL.md +424 -0
- package/plugins/lisa-devin/skills/lisa-github-project-v2/SKILL.md +227 -0
- package/plugins/lisa-devin/skills/lisa-github-read-issue/SKILL.md +305 -0
- package/plugins/lisa-devin/skills/lisa-github-sync/SKILL.md +201 -0
- package/plugins/lisa-devin/skills/lisa-github-to-tracker/SKILL.md +456 -0
- package/plugins/lisa-devin/skills/lisa-github-validate-issue/SKILL.md +598 -0
- package/plugins/lisa-devin/skills/lisa-github-verify/SKILL.md +60 -0
- package/plugins/lisa-devin/skills/lisa-github-write-issue/SKILL.md +531 -0
- package/plugins/lisa-devin/skills/lisa-github-write-prd/SKILL.md +156 -0
- package/plugins/lisa-devin/skills/lisa-health/SKILL.md +142 -0
- package/plugins/lisa-devin/skills/lisa-health-drift-cron/SKILL.md +80 -0
- package/plugins/lisa-devin/skills/lisa-implement/SKILL.md +372 -0
- package/plugins/lisa-devin/skills/lisa-improve-code-complexity/SKILL.md +44 -0
- package/plugins/lisa-devin/skills/lisa-improve-harness/SKILL.md +487 -0
- package/plugins/lisa-devin/skills/lisa-improve-max-lines/SKILL.md +45 -0
- package/plugins/lisa-devin/skills/lisa-improve-max-lines-per-function/SKILL.md +46 -0
- package/plugins/lisa-devin/skills/lisa-improve-test-coverage/SKILL.md +47 -0
- package/plugins/lisa-devin/skills/lisa-improve-tests/SKILL.md +50 -0
- package/plugins/lisa-devin/skills/lisa-intake/SKILL.md +208 -0
- package/plugins/lisa-devin/skills/lisa-intake-explain/SKILL.md +279 -0
- package/plugins/lisa-devin/skills/lisa-jam-access/SKILL.md +128 -0
- package/plugins/lisa-devin/skills/lisa-jira-add-journey/SKILL.md +139 -0
- package/plugins/lisa-devin/skills/lisa-jira-build-intake/SKILL.md +556 -0
- package/plugins/lisa-devin/skills/lisa-jira-claim/SKILL.md +27 -0
- package/plugins/lisa-devin/skills/lisa-jira-create/SKILL.md +157 -0
- package/plugins/lisa-devin/skills/lisa-jira-evidence/SKILL.md +106 -0
- package/plugins/lisa-devin/skills/lisa-jira-evidence/scripts/post-evidence.sh +262 -0
- package/plugins/lisa-devin/skills/lisa-jira-journey/SKILL.md +168 -0
- package/plugins/lisa-devin/skills/lisa-jira-journey/scripts/generate-templates.py +239 -0
- package/plugins/lisa-devin/skills/lisa-jira-journey/scripts/parse-plan.py +570 -0
- package/plugins/lisa-devin/skills/lisa-jira-read-ticket/SKILL.md +199 -0
- package/plugins/lisa-devin/skills/lisa-jira-read-ticket/scripts/download-attachment.sh +211 -0
- package/plugins/lisa-devin/skills/lisa-jira-sync/SKILL.md +117 -0
- package/plugins/lisa-devin/skills/lisa-jira-validate-ticket/SKILL.md +525 -0
- package/plugins/lisa-devin/skills/lisa-jira-verify/SKILL.md +61 -0
- package/plugins/lisa-devin/skills/lisa-jira-write-ticket/SKILL.md +440 -0
- package/plugins/lisa-devin/skills/lisa-jsdoc-best-practices/SKILL.md +491 -0
- package/plugins/lisa-devin/skills/lisa-kane-browser/SKILL.md +80 -0
- package/plugins/lisa-devin/skills/lisa-learnings-audit/SKILL.md +490 -0
- package/plugins/lisa-devin/skills/lisa-linear-access/SKILL.md +483 -0
- package/plugins/lisa-devin/skills/lisa-linear-add-journey/SKILL.md +109 -0
- package/plugins/lisa-devin/skills/lisa-linear-build-intake/SKILL.md +548 -0
- package/plugins/lisa-devin/skills/lisa-linear-claim/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-linear-create/SKILL.md +150 -0
- package/plugins/lisa-devin/skills/lisa-linear-evidence/SKILL.md +124 -0
- package/plugins/lisa-devin/skills/lisa-linear-journey/SKILL.md +147 -0
- package/plugins/lisa-devin/skills/lisa-linear-prd-intake/SKILL.md +411 -0
- package/plugins/lisa-devin/skills/lisa-linear-read-issue/SKILL.md +201 -0
- package/plugins/lisa-devin/skills/lisa-linear-sync/SKILL.md +164 -0
- package/plugins/lisa-devin/skills/lisa-linear-to-tracker/SKILL.md +454 -0
- package/plugins/lisa-devin/skills/lisa-linear-validate-issue/SKILL.md +504 -0
- package/plugins/lisa-devin/skills/lisa-linear-verify/SKILL.md +65 -0
- package/plugins/lisa-devin/skills/lisa-linear-write-issue/SKILL.md +448 -0
- package/plugins/lisa-devin/skills/lisa-linear-write-prd/SKILL.md +98 -0
- package/plugins/lisa-devin/skills/lisa-monitor/SKILL.md +224 -0
- package/plugins/lisa-devin/skills/lisa-nightly-add-test-coverage/SKILL.md +40 -0
- package/plugins/lisa-devin/skills/lisa-nightly-improve-tests/SKILL.md +29 -0
- package/plugins/lisa-devin/skills/lisa-nightly-lower-code-complexity/SKILL.md +42 -0
- package/plugins/lisa-devin/skills/lisa-notion-access/SKILL.md +301 -0
- package/plugins/lisa-devin/skills/lisa-notion-prd-intake/SKILL.md +360 -0
- package/plugins/lisa-devin/skills/lisa-notion-to-tracker/SKILL.md +467 -0
- package/plugins/lisa-devin/skills/lisa-notion-write-prd/SKILL.md +111 -0
- package/plugins/lisa-devin/skills/lisa-parity-code-review/SKILL.md +83 -0
- package/plugins/lisa-devin/skills/lisa-parity-code-simplifier/SKILL.md +76 -0
- package/plugins/lisa-devin/skills/lisa-parity-coderabbit/SKILL.md +87 -0
- package/plugins/lisa-devin/skills/lisa-parity-sentry-sdk-setup/SKILL.md +375 -0
- package/plugins/lisa-devin/skills/lisa-parity-sentry-seer/SKILL.md +168 -0
- package/plugins/lisa-devin/skills/lisa-parity-skill-creator/SKILL.md +155 -0
- package/plugins/lisa-devin/skills/lisa-performance-review/SKILL.md +94 -0
- package/plugins/lisa-devin/skills/lisa-persist-learning/SKILL.md +247 -0
- package/plugins/lisa-devin/skills/lisa-plan/SKILL.md +64 -0
- package/plugins/lisa-devin/skills/lisa-plugin-sync-explain/SKILL.md +53 -0
- package/plugins/lisa-devin/skills/lisa-posthog-access/SKILL.md +140 -0
- package/plugins/lisa-devin/skills/lisa-prd-backlink/SKILL.md +274 -0
- package/plugins/lisa-devin/skills/lisa-prd-source-write/SKILL.md +104 -0
- package/plugins/lisa-devin/skills/lisa-prd-ticket-coverage/SKILL.md +187 -0
- package/plugins/lisa-devin/skills/lisa-product-walkthrough/SKILL.md +106 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/SKILL.md +417 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/evidence-card-format.md +21 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/host-project-only.md +22 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/idempotency-verification-harness.md +57 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/public-external-inspiration.md +22 -0
- package/plugins/lisa-devin/skills/lisa-project-ideation/examples/unavailable-data-rejection.md +22 -0
- package/plugins/lisa-devin/skills/lisa-pull-request-review/SKILL.md +132 -0
- package/plugins/lisa-devin/skills/lisa-quality-review/SKILL.md +63 -0
- package/plugins/lisa-devin/skills/lisa-queue-status/SKILL.md +215 -0
- package/plugins/lisa-devin/skills/lisa-remote-dispatch/SKILL.md +109 -0
- package/plugins/lisa-devin/skills/lisa-remote-dispatch/scripts/dispatch.mjs +535 -0
- package/plugins/lisa-devin/skills/lisa-repair-intake/SKILL.md +1621 -0
- package/plugins/lisa-devin/skills/lisa-reproduce-bug/SKILL.md +75 -0
- package/plugins/lisa-devin/skills/lisa-research/SKILL.md +92 -0
- package/plugins/lisa-devin/skills/lisa-review-implementation/SKILL.md +209 -0
- package/plugins/lisa-devin/skills/lisa-review-local/SKILL.md +141 -0
- package/plugins/lisa-devin/skills/lisa-rework-triage/SKILL.md +206 -0
- package/plugins/lisa-devin/skills/lisa-root-cause-analysis/SKILL.md +135 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/SKILL.md +430 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/aws-bootstrap.mjs +433 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +288 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +273 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/envfile.mjs +80 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/inspect-vault.mjs +142 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +1085 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/note-format.mjs +179 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/owner.mjs +96 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +342 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/prompt-secret.mjs +79 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/providers.mjs +805 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +680 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/read-secret-note.mjs +105 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/resolve-expo-token.mjs +90 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/resolve-secret.mjs +285 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/rotate-secret.mjs +297 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/routing-floor.mjs +153 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/surfaces.mjs +480 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/sync-secret-to-ci.mjs +510 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/tools-from-notes.mjs +63 -0
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/validate-config.mjs +550 -0
- package/plugins/lisa-devin/skills/lisa-security-review/SKILL.md +124 -0
- package/plugins/lisa-devin/skills/lisa-security-zap-scan/SKILL.md +53 -0
- package/plugins/lisa-devin/skills/lisa-sentry-access/SKILL.md +140 -0
- package/plugins/lisa-devin/skills/lisa-setup-atlassian/SKILL.md +407 -0
- package/plugins/lisa-devin/skills/lisa-setup-automations/SKILL.md +430 -0
- package/plugins/lisa-devin/skills/lisa-setup-automations/scripts/generate-workflow.mjs +288 -0
- package/plugins/lisa-devin/skills/lisa-setup-confluence/SKILL.md +254 -0
- package/plugins/lisa-devin/skills/lisa-setup-github/SKILL.md +319 -0
- package/plugins/lisa-devin/skills/lisa-setup-github-repo/SKILL.md +128 -0
- package/plugins/lisa-devin/skills/lisa-setup-jira/SKILL.md +198 -0
- package/plugins/lisa-devin/skills/lisa-setup-kane/SKILL.md +96 -0
- package/plugins/lisa-devin/skills/lisa-setup-linear/SKILL.md +375 -0
- package/plugins/lisa-devin/skills/lisa-setup-local-env/SKILL.md +95 -0
- package/plugins/lisa-devin/skills/lisa-setup-local-env/scripts/local-env.mjs +196 -0
- package/plugins/lisa-devin/skills/lisa-setup-notion/SKILL.md +376 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-aws/SKILL.md +149 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/SKILL.md +382 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/materialized-env-authority.mjs +262 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/session-start.sh +87 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/assets/setup.sh +216 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/environment.mjs +227 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +378 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +1632 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/tool-floor.mjs +94 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +448 -0
- package/plugins/lisa-devin/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +250 -0
- package/plugins/lisa-devin/skills/lisa-setup-sonar/SKILL.md +153 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/SKILL.md +143 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/bounded-child.mjs +168 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/catalogue.mjs +293 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/cli.mjs +333 -0
- package/plugins/lisa-devin/skills/lisa-setup-workstation/scripts/workstation.mjs +511 -0
- package/plugins/lisa-devin/skills/lisa-sonarcloud-access/SKILL.md +202 -0
- package/plugins/lisa-devin/skills/lisa-spec-conformance/SKILL.md +205 -0
- package/plugins/lisa-devin/skills/lisa-starter-sync/SKILL.md +35 -0
- package/plugins/lisa-devin/skills/lisa-sync-down/SKILL.md +209 -0
- package/plugins/lisa-devin/skills/lisa-task-decomposition/SKILL.md +195 -0
- package/plugins/lisa-devin/skills/lisa-task-triage/SKILL.md +23 -0
- package/plugins/lisa-devin/skills/lisa-tdd-implementation/SKILL.md +91 -0
- package/plugins/lisa-devin/skills/lisa-tear-down-automations/SKILL.md +85 -0
- package/plugins/lisa-devin/skills/lisa-test-strategy/SKILL.md +66 -0
- package/plugins/lisa-devin/skills/lisa-ticket-triage/SKILL.md +214 -0
- package/plugins/lisa-devin/skills/lisa-track/SKILL.md +129 -0
- package/plugins/lisa-devin/skills/lisa-tracker-add-journey/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-tracker-build-intake/SKILL.md +134 -0
- package/plugins/lisa-devin/skills/lisa-tracker-claim/SKILL.md +35 -0
- package/plugins/lisa-devin/skills/lisa-tracker-create/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-tracker-evidence/SKILL.md +63 -0
- package/plugins/lisa-devin/skills/lisa-tracker-journey/SKILL.md +27 -0
- package/plugins/lisa-devin/skills/lisa-tracker-read/SKILL.md +28 -0
- package/plugins/lisa-devin/skills/lisa-tracker-source-artifacts/SKILL.md +111 -0
- package/plugins/lisa-devin/skills/lisa-tracker-sync/SKILL.md +150 -0
- package/plugins/lisa-devin/skills/lisa-tracker-validate/SKILL.md +37 -0
- package/plugins/lisa-devin/skills/lisa-tracker-verify/SKILL.md +29 -0
- package/plugins/lisa-devin/skills/lisa-tracker-write/SKILL.md +55 -0
- package/plugins/lisa-devin/skills/lisa-usage-accounting/SKILL.md +205 -0
- package/plugins/lisa-devin/skills/lisa-usage-accounting/references/effectiveness.md +63 -0
- package/plugins/lisa-devin/skills/lisa-use-the-product/SKILL.md +86 -0
- package/plugins/lisa-devin/skills/lisa-validate-tracker-mapping/SKILL.md +467 -0
- package/plugins/lisa-devin/skills/lisa-verification-lifecycle/SKILL.md +384 -0
- package/plugins/lisa-devin/skills/lisa-verify/SKILL.md +55 -0
- package/plugins/lisa-devin/skills/lisa-verify-prd/SKILL.md +394 -0
- package/plugins/lisa-devin/skills/lisa-verify-workflow-change/SKILL.md +154 -0
- package/plugins/lisa-devin/skills/lisa-wiki-install/SKILL.md +101 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-expo-devin/.mcp.json +8 -0
- package/plugins/lisa-expo-devin/THIRD-PARTY-NOTICES.md +84 -0
- package/plugins/lisa-expo-devin/agents/ops-specialist.md +124 -0
- package/plugins/lisa-expo-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-expo-devin/hooks/register-maestro-mcp.sh +84 -0
- package/plugins/lisa-expo-devin/hooks.json +14 -0
- package/plugins/lisa-expo-devin/skills/add-app-clip/SKILL.md +280 -0
- package/plugins/lisa-expo-devin/skills/add-app-clip/references/native-module.md +96 -0
- package/plugins/lisa-expo-devin/skills/apollo-client/SKILL.md +238 -0
- package/plugins/lisa-expo-devin/skills/apollo-client/references/mutation-patterns.md +360 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/SKILL.md +369 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/scripts/test_validate_atomic_structure.py +240 -0
- package/plugins/lisa-expo-devin/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +438 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/SKILL.md +321 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/animations.md +220 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/controls.md +272 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/form-sheet.md +253 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/gradients.md +106 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/icons.md +213 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/media.md +198 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/route-structure.md +229 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/search.md +248 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/storage.md +121 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/tabs.md +433 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/toolbar-and-headers.md +284 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/visual-effects.md +197 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/webgpu-three.md +605 -0
- package/plugins/lisa-expo-devin/skills/building-native-ui/references/zoom-transitions.md +158 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/SKILL.md +355 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/references/examples.md +749 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/references/patterns.md +318 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/scripts/create_component.py +200 -0
- package/plugins/lisa-expo-devin/skills/container-view-pattern/scripts/validate_component.py +332 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/SKILL.md +268 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/common-issues.md +619 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/references/platform-api.md +276 -0
- package/plugins/lisa-expo-devin/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +416 -0
- package/plugins/lisa-expo-devin/skills/directory-structure/SKILL.md +214 -0
- package/plugins/lisa-expo-devin/skills/directory-structure/scripts/validate_structure.py +454 -0
- package/plugins/lisa-expo-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
- package/plugins/lisa-expo-devin/skills/eas-update-insights/SKILL.md +228 -0
- package/plugins/lisa-expo-devin/skills/eas-update-insights/references/channel-insights-schema.md +47 -0
- package/plugins/lisa-expo-devin/skills/eas-update-insights/references/update-insights-schema.md +69 -0
- package/plugins/lisa-expo-devin/skills/expo-api-routes/SKILL.md +369 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/SKILL.md +54 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/brownfield-integrated.md +526 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/brownfield-isolated.md +402 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/comparison.md +63 -0
- package/plugins/lisa-expo-devin/skills/expo-brownfield/references/troubleshooting.md +88 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/SKILL.md +92 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/fetch.js +113 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/package.json +11 -0
- package/plugins/lisa-expo-devin/skills/expo-cicd-workflows/scripts/validate.js +85 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/SKILL.md +193 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/app-store-metadata.md +479 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/ios-app-store.md +355 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/play-store.md +264 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/testflight.md +58 -0
- package/plugins/lisa-expo-devin/skills/expo-deployment/references/workflows.md +200 -0
- package/plugins/lisa-expo-devin/skills/expo-dev-client/SKILL.md +164 -0
- package/plugins/lisa-expo-devin/skills/expo-env-config/SKILL.md +309 -0
- package/plugins/lisa-expo-devin/skills/expo-env-config/references/validation-patterns.md +417 -0
- package/plugins/lisa-expo-devin/skills/expo-module/SKILL.md +141 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/config-plugin.md +90 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/create-expo-module.md +206 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/lifecycle.md +127 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/module-config.md +48 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/native-module.md +286 -0
- package/plugins/lisa-expo-devin/skills/expo-module/references/native-view.md +171 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/SKILL.md +432 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/references/official-docs.md +290 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/references/static-export-verification.md +54 -0
- package/plugins/lisa-expo-devin/skills/expo-router-best-practices/scripts/generate-route.py +171 -0
- package/plugins/lisa-expo-devin/skills/expo-tailwind-setup/SKILL.md +487 -0
- package/plugins/lisa-expo-devin/skills/expo-ui-jetpack-compose/SKILL.md +40 -0
- package/plugins/lisa-expo-devin/skills/expo-ui-swift-ui/SKILL.md +39 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/SKILL.md +419 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/color-tokens.md +343 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/component-mapping.md +307 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/scripts/test_validate_styling.py +240 -0
- package/plugins/lisa-expo-devin/skills/gluestack-nativewind/scripts/validate_styling.py +424 -0
- package/plugins/lisa-expo-devin/skills/jira-add-journey/SKILL.md +140 -0
- package/plugins/lisa-expo-devin/skills/jira-create/SKILL.md +161 -0
- package/plugins/lisa-expo-devin/skills/jira-evidence/SKILL.md +84 -0
- package/plugins/lisa-expo-devin/skills/jira-evidence/scripts/post-evidence.sh +262 -0
- package/plugins/lisa-expo-devin/skills/jira-journey/SKILL.md +196 -0
- package/plugins/lisa-expo-devin/skills/jira-journey/scripts/generate-templates.py +281 -0
- package/plugins/lisa-expo-devin/skills/jira-journey/scripts/parse-plan.py +400 -0
- package/plugins/lisa-expo-devin/skills/jira-verify/SKILL.md +27 -0
- package/plugins/lisa-expo-devin/skills/local-state/SKILL.md +362 -0
- package/plugins/lisa-expo-devin/skills/local-state/references/async-storage.md +505 -0
- package/plugins/lisa-expo-devin/skills/local-state/references/persistence-patterns.md +711 -0
- package/plugins/lisa-expo-devin/skills/local-state/references/reactive-variables.md +446 -0
- package/plugins/lisa-expo-devin/skills/maestro-mcp-setup/SKILL.md +144 -0
- package/plugins/lisa-expo-devin/skills/native-data-fetching/SKILL.md +507 -0
- package/plugins/lisa-expo-devin/skills/native-data-fetching/references/expo-router-loaders.md +344 -0
- package/plugins/lisa-expo-devin/skills/ops-browser-uat/SKILL.md +124 -0
- package/plugins/lisa-expo-devin/skills/ops-check-logs/SKILL.md +217 -0
- package/plugins/lisa-expo-devin/skills/ops-db-ops/SKILL.md +214 -0
- package/plugins/lisa-expo-devin/skills/ops-deploy/SKILL.md +123 -0
- package/plugins/lisa-expo-devin/skills/ops-monitor-errors/SKILL.md +99 -0
- package/plugins/lisa-expo-devin/skills/ops-performance/SKILL.md +165 -0
- package/plugins/lisa-expo-devin/skills/ops-run-local/SKILL.md +169 -0
- package/plugins/lisa-expo-devin/skills/ops-verify-health/SKILL.md +101 -0
- package/plugins/lisa-expo-devin/skills/owasp-zap/SKILL.md +61 -0
- package/plugins/lisa-expo-devin/skills/play-store-access/SKILL.md +226 -0
- package/plugins/lisa-expo-devin/skills/playwright-ci-debugging/SKILL.md +140 -0
- package/plugins/lisa-expo-devin/skills/playwright-selectors/SKILL.md +496 -0
- package/plugins/lisa-expo-devin/skills/reduce-complexity/SKILL.md +251 -0
- package/plugins/lisa-expo-devin/skills/reduce-complexity/references/extraction-strategies.md +456 -0
- package/plugins/lisa-expo-devin/skills/reduce-complexity/references/refactoring-patterns.md +557 -0
- package/plugins/lisa-expo-devin/skills/testing-library/SKILL.md +314 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/async-patterns.md +420 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/expo-router-testing.md +556 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/mocking-patterns.md +612 -0
- package/plugins/lisa-expo-devin/skills/testing-library/references/query-priority.md +291 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/SKILL.md +134 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/expo-av-to-audio.md +132 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/expo-av-to-video.md +160 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/native-tabs.md +124 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/new-architecture.md +79 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-19.md +79 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-compiler.md +59 -0
- package/plugins/lisa-expo-devin/skills/upgrading-expo/references/react-navigation-to-expo-router.md +61 -0
- package/plugins/lisa-expo-devin/skills/use-dom/SKILL.md +417 -0
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-harper-fabric-devin/generated-artifact-globs.txt +5 -0
- package/plugins/lisa-harper-fabric-devin/hooks/block-generated-artifact-edits.sh +141 -0
- package/plugins/lisa-harper-fabric-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-harper-fabric-devin/hooks/enforce-config-extensions.mjs +196 -0
- package/plugins/lisa-harper-fabric-devin/hooks/enforce-config-extensions.sh +46 -0
- package/plugins/lisa-harper-fabric-devin/hooks/inject-rules.sh +27 -0
- package/plugins/lisa-harper-fabric-devin/hooks.json +36 -0
- package/plugins/lisa-harper-fabric-devin/rules/eager/harper-fabric.md +31 -0
- package/plugins/lisa-harper-fabric-devin/rules/reference/harper-fabric.md +52 -0
- package/plugins/lisa-harper-fabric-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-auth/SKILL.md +368 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-build-and-deploy/SKILL.md +198 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-caching/SKILL.md +265 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-component-model/SKILL.md +87 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-config-yaml/SKILL.md +283 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-operations/SKILL.md +239 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-realtime/SKILL.md +187 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-resources/SKILL.md +142 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-rest-queries/SKILL.md +403 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-schema-graphql/SKILL.md +123 -0
- package/plugins/lisa-harper-fabric-devin/skills/harper-testing/SKILL.md +254 -0
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-nestjs-devin/hooks/block-migration-edits.sh +125 -0
- package/plugins/lisa-nestjs-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-nestjs-devin/hooks/lisa-edit-gate.sh +152 -0
- package/plugins/lisa-nestjs-devin/hooks.json +15 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/SKILL.md +176 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/advanced-features.md +527 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/project-patterns.md +531 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/quick-start.md +257 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-graphql/references/types-scalars.md +513 -0
- package/plugins/lisa-nestjs-devin/skills/nestjs-rules/SKILL.md +560 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/SKILL.md +275 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/entity-patterns.md +450 -0
- package/plugins/lisa-nestjs-devin/skills/typeorm-patterns/references/observability-patterns.md +536 -0
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +8 -0
- package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-repo-topic/SKILL.md +163 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-repo-topic/references/repo-topic-config.md +185 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/SKILL.md +175 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/references/platform-routing.md +123 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-connect-staff/references/prompts.md +78 -0
- package/plugins/lisa-openclaw-devin/skills/lisa-openclaw-setup/SKILL.md +141 -0
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-phaser-devin/agents/accessibility-advocate.md +55 -0
- package/plugins/lisa-phaser-devin/agents/art-director.md +90 -0
- package/plugins/lisa-phaser-devin/agents/audio-director.md +65 -0
- package/plugins/lisa-phaser-devin/agents/combat-designer.md +61 -0
- package/plugins/lisa-phaser-devin/agents/economy-designer.md +59 -0
- package/plugins/lisa-phaser-devin/agents/game-designer.md +62 -0
- package/plugins/lisa-phaser-devin/agents/game-feel-specialist.md +56 -0
- package/plugins/lisa-phaser-devin/agents/level-designer.md +59 -0
- package/plugins/lisa-phaser-devin/agents/localization-manager.md +61 -0
- package/plugins/lisa-phaser-devin/agents/marketing-strategist.md +59 -0
- package/plugins/lisa-phaser-devin/agents/monetization-designer.md +59 -0
- package/plugins/lisa-phaser-devin/agents/narrative-designer.md +56 -0
- package/plugins/lisa-phaser-devin/agents/onboarding-advocate.md +59 -0
- package/plugins/lisa-phaser-devin/agents/player-advocate.md +57 -0
- package/plugins/lisa-phaser-devin/agents/producer.md +61 -0
- package/plugins/lisa-phaser-devin/agents/product-analyst.md +62 -0
- package/plugins/lisa-phaser-devin/agents/publisher.md +59 -0
- package/plugins/lisa-phaser-devin/agents/qa-playtester.md +57 -0
- package/plugins/lisa-phaser-devin/agents/target-player.md +52 -0
- package/plugins/lisa-phaser-devin/agents/ux-ui-designer.md +60 -0
- package/plugins/lisa-phaser-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-phaser-devin/hooks/inject-rules.sh +27 -0
- package/plugins/lisa-phaser-devin/hooks.json +14 -0
- package/plugins/lisa-phaser-devin/rules/eager/phaser.md +44 -0
- package/plugins/lisa-phaser-devin/rules/reference/phaser.md +184 -0
- package/plugins/lisa-phaser-devin/skills/phaser-accessibility/SKILL.md +135 -0
- package/plugins/lisa-phaser-devin/skills/phaser-asset-pipeline/SKILL.md +222 -0
- package/plugins/lisa-phaser-devin/skills/phaser-asset-sourcing/SKILL.md +151 -0
- package/plugins/lisa-phaser-devin/skills/phaser-build-deploy/SKILL.md +153 -0
- package/plugins/lisa-phaser-devin/skills/phaser-i18n/SKILL.md +122 -0
- package/plugins/lisa-phaser-devin/skills/phaser-project-structure/SKILL.md +89 -0
- package/plugins/lisa-phaser-devin/skills/phaser-services/SKILL.md +208 -0
- package/plugins/lisa-phaser-devin/skills/phaser-testing/SKILL.md +192 -0
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-rails-devin/agents/ops-specialist.md +226 -0
- package/plugins/lisa-rails-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-rails-devin/hooks/inject-rules.sh +40 -0
- package/plugins/lisa-rails-devin/hooks/lisa-edit-gate.sh +152 -0
- package/plugins/lisa-rails-devin/hooks/rubocop-on-edit.sh +101 -0
- package/plugins/lisa-rails-devin/hooks/sg-scan-on-edit.sh +97 -0
- package/plugins/lisa-rails-devin/hooks.json +29 -0
- package/plugins/lisa-rails-devin/rules/eager/rails-conventions.md +23 -0
- package/plugins/lisa-rails-devin/rules/reference/rails-conventions.md +176 -0
- package/plugins/lisa-rails-devin/skills/action-controller-best-practices/SKILL.md +374 -0
- package/plugins/lisa-rails-devin/skills/action-view-best-practices/SKILL.md +335 -0
- package/plugins/lisa-rails-devin/skills/active-record-model-best-practices/SKILL.md +166 -0
- package/plugins/lisa-rails-devin/skills/e2e-coverage-gaps/SKILL.md +117 -0
- package/plugins/lisa-rails-devin/skills/fix-linter-error/SKILL.md +45 -0
- package/plugins/lisa-rails-devin/skills/improve-code-complexity/SKILL.md +51 -0
- package/plugins/lisa-rails-devin/skills/improve-max-lines/SKILL.md +46 -0
- package/plugins/lisa-rails-devin/skills/improve-max-lines-per-function/SKILL.md +46 -0
- package/plugins/lisa-rails-devin/skills/improve-test-coverage/SKILL.md +48 -0
- package/plugins/lisa-rails-devin/skills/jira-add-journey/SKILL.md +79 -0
- package/plugins/lisa-rails-devin/skills/jira-create/SKILL.md +100 -0
- package/plugins/lisa-rails-devin/skills/jira-evidence/SKILL.md +76 -0
- package/plugins/lisa-rails-devin/skills/jira-evidence/scripts/post-evidence.sh +262 -0
- package/plugins/lisa-rails-devin/skills/jira-journey/SKILL.md +65 -0
- package/plugins/lisa-rails-devin/skills/jira-verify/SKILL.md +26 -0
- package/plugins/lisa-rails-devin/skills/ops-check-logs/SKILL.md +191 -0
- package/plugins/lisa-rails-devin/skills/ops-deploy/SKILL.md +153 -0
- package/plugins/lisa-rails-devin/skills/ops-run-local/SKILL.md +169 -0
- package/plugins/lisa-rails-devin/skills/ops-verify-jobs/SKILL.md +157 -0
- package/plugins/lisa-rails-devin/skills/ops-verify-telemetry/SKILL.md +226 -0
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +11 -0
- package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-typescript-devin/hooks/block-suppress-directives.sh +141 -0
- package/plugins/lisa-typescript-devin/hooks/devin-tool-name-shim.sh +30 -0
- package/plugins/lisa-typescript-devin/hooks/format-on-edit.sh +102 -0
- package/plugins/lisa-typescript-devin/hooks/lint-on-edit.sh +117 -0
- package/plugins/lisa-typescript-devin/hooks/lisa-edit-gate.sh +152 -0
- package/plugins/lisa-typescript-devin/hooks/sg-scan-on-edit.sh +94 -0
- package/plugins/lisa-typescript-devin/hooks.json +34 -0
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +8 -0
- package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +8 -0
- package/plugins/lisa-wiki-devin/ci/lisa-wiki-validate.yml +33 -0
- package/plugins/lisa-wiki-devin/schema/lisa-wiki-config.schema.json +162 -0
- package/plugins/lisa-wiki-devin/schema/wiki-structure.schema.json +51 -0
- package/plugins/lisa-wiki-devin/scripts/_wiki-lib.mjs +204 -0
- package/plugins/lisa-wiki-devin/scripts/diff-guard.mjs +117 -0
- package/plugins/lisa-wiki-devin/scripts/ensure-gitignore.mjs +131 -0
- package/plugins/lisa-wiki-devin/scripts/ensure-wiki.mjs +268 -0
- package/plugins/lisa-wiki-devin/scripts/ingest-git.mjs +198 -0
- package/plugins/lisa-wiki-devin/scripts/ingest-memory.mjs +135 -0
- package/plugins/lisa-wiki-devin/scripts/ingest-roles.mjs +95 -0
- package/plugins/lisa-wiki-devin/scripts/ingest_slack_channel.py +361 -0
- package/plugins/lisa-wiki-devin/scripts/lib/bounded-child.mjs +159 -0
- package/plugins/lisa-wiki-devin/scripts/lint-wiki.mjs +513 -0
- package/plugins/lisa-wiki-devin/scripts/mcp-doctor.mjs +72 -0
- package/plugins/lisa-wiki-devin/scripts/render-contract.mjs +107 -0
- package/plugins/lisa-wiki-devin/scripts/rewrite-refs.mjs +144 -0
- package/plugins/lisa-wiki-devin/scripts/slack_oauth_user.py +179 -0
- package/plugins/lisa-wiki-devin/scripts/validate-config.mjs +321 -0
- package/plugins/lisa-wiki-devin/scripts/verify-migration.mjs +203 -0
- package/plugins/lisa-wiki-devin/scripts/verify-wiki-safety.mjs +126 -0
- package/plugins/lisa-wiki-devin/scripts/wiki-safety.mjs +378 -0
- package/plugins/lisa-wiki-devin/scripts/wiki-status.mjs +345 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-add-ingest/SKILL.md +34 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-add-role/SKILL.md +35 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-confluence/SKILL.md +25 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-docs/SKILL.md +30 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-git/SKILL.md +25 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-jira/SKILL.md +28 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-memory/SKILL.md +28 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-notion/SKILL.md +25 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-roles/SKILL.md +22 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-slack/SKILL.md +30 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-connector-web/SKILL.md +23 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-doctor/SKILL.md +47 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-ingest/SKILL.md +106 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-lint/SKILL.md +32 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-migrate/SKILL.md +43 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-onboard-me/SKILL.md +33 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-query/SKILL.md +40 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-setup/SKILL.md +75 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-setup-automations/SKILL.md +83 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-status/SKILL.md +47 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-tear-down-automations/SKILL.md +34 -0
- package/plugins/lisa-wiki-devin/skills/lisa-wiki-usage/SKILL.md +50 -0
- package/plugins/lisa-wiki-devin/templates/agents/role-agent.claude.md +16 -0
- package/plugins/lisa-wiki-devin/templates/agents/role-agent.codex.toml +15 -0
- package/plugins/lisa-wiki-devin/templates/index.md +17 -0
- package/plugins/lisa-wiki-devin/templates/llm-wiki-contract.md +72 -0
- package/plugins/lisa-wiki-devin/templates/log.md +8 -0
- package/plugins/lisa-wiki-devin/templates/page-types/architecture.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/concept.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/decision.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/entity.md +19 -0
- package/plugins/lisa-wiki-devin/templates/page-types/open-question.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/playbook.md +18 -0
- package/plugins/lisa-wiki-devin/templates/page-types/project.md +19 -0
- package/plugins/lisa-wiki-devin/templates/page-types/requirement.md +19 -0
- package/plugins/lisa-wiki-devin/templates/page-types/staff.md +26 -0
- package/plugins/lisa-wiki-devin/templates/start-here.md +24 -0
- package/plugins/lisa-wiki-devin/templates/state-readme.md +20 -0
- package/plugins/lisa-wiki-devin/templates/wrapper-gitignore.txt +19 -0
- package/scripts/build-plugins.sh +2 -2
- package/scripts/deployed-guard-advice.mjs +1 -0
- package/scripts/generate-devin-plugin-artifacts.mjs +263 -0
- package/scripts/lib/hook-registration-audit.mjs +3 -1
- package/scripts/lib/per-agent-hook-filter.mjs +174 -5
- package/scripts/lib/plugin-rule-citations.mjs +2 -2
|
@@ -0,0 +1,1242 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
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
|
+
# PreToolUse hook for Bash: refuse to ARM auto-merge on a pull request that
|
|
6
|
+
# GitHub already says is blocked on review.
|
|
7
|
+
#
|
|
8
|
+
# WHY THIS IS A HOOK AND NOT A RULE
|
|
9
|
+
#
|
|
10
|
+
# Arming auto-merge is the moment an agent claims "this will merge", and the
|
|
11
|
+
# claim can be false at exactly that moment. Measured on this repository: three
|
|
12
|
+
# of five open PRs sat armed, fully green, with `reviewDecision =
|
|
13
|
+
# CHANGES_REQUESTED`. Nothing surfaced it. `reviewDecision` is not part of the
|
|
14
|
+
# check rollup, so the failing-check count read 0, the checks tab was entirely
|
|
15
|
+
# green, and three separate agents armed and moved on. One PR had been parked
|
|
16
|
+
# for hours before someone queried the field by hand.
|
|
17
|
+
#
|
|
18
|
+
# The stale case is the one actually observed, and it is the one prose cannot
|
|
19
|
+
# help with: the `base` ruleset sets `dismiss_stale_reviews_on_push: false`, so
|
|
20
|
+
# a CHANGES_REQUESTED review stays attached to the commit it was made on and
|
|
21
|
+
# pushing the fix never clears it, while `required_approving_review_count: 0`
|
|
22
|
+
# means no scheduled re-review will ever clear it either. The PR is blocked
|
|
23
|
+
# indefinitely and every check-based signal reads ready.
|
|
24
|
+
#
|
|
25
|
+
# WHAT IT CHECKS, AND WHY THAT AND NOT THE CHECK ROLLUP
|
|
26
|
+
#
|
|
27
|
+
# The defect class here is a probe that measures what it can see rather than
|
|
28
|
+
# what it means. A signal assembled from check-runs reports "ready to merge" on
|
|
29
|
+
# a PR that can never merge — that is precisely how this stayed invisible — and
|
|
30
|
+
# a code-review gate can arrive as a commit STATUS rather than a check-run, so
|
|
31
|
+
# a check-run-only query misses that too. So this guard does not look at checks
|
|
32
|
+
# at all. It asks GitHub the one question whose answer is the blocker:
|
|
33
|
+
# `reviewDecision`.
|
|
34
|
+
#
|
|
35
|
+
# `mergeStateStatus` is deliberately NOT read. It is computed rather than
|
|
36
|
+
# stored, reports UNKNOWN transiently while GitHub recomputes it, and was
|
|
37
|
+
# observed reporting CONFLICTING on two PRs minutes apart where one had a real
|
|
38
|
+
# conflict and the other merged cleanly with zero conflicted paths. Same field,
|
|
39
|
+
# opposite truths. A guard built on it would refuse honest commands at random,
|
|
40
|
+
# which is how a guard gets switched off.
|
|
41
|
+
#
|
|
42
|
+
# WHAT THIS GUARD DOES *NOT* CLAIM
|
|
43
|
+
#
|
|
44
|
+
# It answers "is there a standing review verdict blocking this merge?", not
|
|
45
|
+
# "can this PR merge?". The difference matters for one measured case that looks
|
|
46
|
+
# like this ticket and is not it: a PR opened against a base where reviews are
|
|
47
|
+
# disabled gets a review bot reporting `success` with the description "Review
|
|
48
|
+
# skipped: reviews are disabled for this base branch", and the repository's two
|
|
49
|
+
# review-evidence gates then FAIL. That PR cannot merge either — but it is not
|
|
50
|
+
# invisible: the failing-check count is 2, and any check-based signal reports it
|
|
51
|
+
# correctly. Only the individual bot's *conclusion* misleads, and nothing here
|
|
52
|
+
# reads conclusions.
|
|
53
|
+
#
|
|
54
|
+
# So that case is deliberately out of scope rather than overlooked, and the
|
|
55
|
+
# distinction is the whole reason this guard exists: `CHANGES_REQUESTED` shows
|
|
56
|
+
# ZERO failing checks. A guard that also tried to adjudicate check descriptions
|
|
57
|
+
# would be re-deriving gates that already run, and would start refusing commands
|
|
58
|
+
# on evidence CI already surfaces. The suite pins the boundary with a case, so a
|
|
59
|
+
# later reader can see it was decided rather than missed.
|
|
60
|
+
#
|
|
61
|
+
# THE SECOND VECTOR: A BASE REF NO RULESET COVERS
|
|
62
|
+
#
|
|
63
|
+
# The paragraphs above answer "is a review verdict blocking this merge?". They
|
|
64
|
+
# assume the merge has gates at all. On this repository that assumption is FALSE
|
|
65
|
+
# for every ref but four.
|
|
66
|
+
#
|
|
67
|
+
# Read live from the API, the `base` and `quality checks` rulesets both scope
|
|
68
|
+
# `conditions.ref_name.include` to exactly `~DEFAULT_BRANCH`, `refs/heads/dev`,
|
|
69
|
+
# `refs/heads/staging`, `refs/heads/main`, with no exclusions. A pull request
|
|
70
|
+
# based on ANY other ref — a `stack/**` batching branch, a feature branch —
|
|
71
|
+
# therefore has ZERO required checks. Every gate still runs; none of them can
|
|
72
|
+
# block. `gh api repos/<o>/<r>/rules/branches/main` returns fifteen required
|
|
73
|
+
# contexts; the same call for a stack ref returns `[]`.
|
|
74
|
+
#
|
|
75
|
+
# Two ordinary, permission-free acts turn that into a merged pull request that
|
|
76
|
+
# nobody approved and no gate passed. Both are intercepted here.
|
|
77
|
+
#
|
|
78
|
+
# 1. ARMING on an uncovered base. `gh pr merge <pr> --auto` on a stack base is
|
|
79
|
+
# a claim that CI will decide, made where CI decides nothing. The merge lands
|
|
80
|
+
# the moment the PR becomes mergeable, with no reader in the path.
|
|
81
|
+
# 2. RE-TARGETING a red pull request onto an uncovered base. `gh pr edit <pr>
|
|
82
|
+
# --base stack/x` is a metadata edit any contributor or agent can make. It
|
|
83
|
+
# requires no elevated permission, no `--force`, and no code change.
|
|
84
|
+
# CodySwannGT/lisa#3922 records it executed by accident, by an agent
|
|
85
|
+
# following an approved plan: a pull request BLOCKED by two genuinely failing
|
|
86
|
+
# required checks was re-targeted, both blockers ceased to apply because they
|
|
87
|
+
# were no longer required, the already-armed auto-merge fired, and the head
|
|
88
|
+
# branch was auto-deleted leaving the work reachable from one ref.
|
|
89
|
+
#
|
|
90
|
+
# WHY THE CONFIGURATION CANNOT ANSWER THIS AND THE API MUST
|
|
91
|
+
#
|
|
92
|
+
# The resolved Lisa config reads `policy.protect: force_push=true`, which any
|
|
93
|
+
# reader takes for "force-push is blocked here". It is not a global switch — it
|
|
94
|
+
# configures a ruleset whose ref scope is those four refs. **A policy flag names
|
|
95
|
+
# an intent; only the ruleset's ref scope says where it applies.** So coverage
|
|
96
|
+
# is read from `GET /repos/{owner}/{repo}/rules/branches/{ref}`, which answers
|
|
97
|
+
# for the ONE ref in question and folds in org rulesets, repository rulesets and
|
|
98
|
+
# their exclusions. Nothing here reads `.lisa.config.json`.
|
|
99
|
+
#
|
|
100
|
+
# WHY RE-TARGETING READS CHECKS WHEN ARMING DOES NOT
|
|
101
|
+
#
|
|
102
|
+
# The section above says this guard reads no check conclusions, and for the
|
|
103
|
+
# review question that is right — `CHANGES_REQUESTED` shows zero failing checks,
|
|
104
|
+
# so a check-derived signal is the defect rather than the detector. The
|
|
105
|
+
# re-target question is the opposite shape: "does this edit remove a live
|
|
106
|
+
# blocker?" and a blocker there IS a check by construction. Reading them is not
|
|
107
|
+
# re-deriving a gate; it is asking whether one is currently biting.
|
|
108
|
+
#
|
|
109
|
+
# The condition is deliberately narrow, because re-targeting onto a stack base
|
|
110
|
+
# is a SANCTIONED batching workflow and a guard that refused it outright would
|
|
111
|
+
# be switched off within a day. A red pull request is refused; a green one is
|
|
112
|
+
# announced and allowed. The property protected is "a pull request something is
|
|
113
|
+
# currently failing must not be moved onto a base where nothing can block it",
|
|
114
|
+
# which holds whatever base it came from — so the current base is not consulted.
|
|
115
|
+
#
|
|
116
|
+
# WHAT THE REFUSAL ASKS FOR INSTEAD
|
|
117
|
+
#
|
|
118
|
+
# There is no env-var escape hatch here either, and none is needed: the
|
|
119
|
+
# acknowledgement the arming refusal asks for is dropping `--auto`. That is not
|
|
120
|
+
# ceremony. `gh pr merge <pr> --merge` on a stack base performs the merge at a
|
|
121
|
+
# moment when the advisory results exist and something can read them, which is
|
|
122
|
+
# exactly the mitigation #3922 shows is unreachable while auto-merge is armed —
|
|
123
|
+
# "the merge lands before any reader exists".
|
|
124
|
+
#
|
|
125
|
+
# SCOPE: ARMING AND RE-TARGETING, NOT MERGING
|
|
126
|
+
#
|
|
127
|
+
# A direct `gh pr merge` against a blocked PR fails loudly and immediately, so
|
|
128
|
+
# it is not the invisible failure this exists to close — and refusing it would
|
|
129
|
+
# also refuse the deliberate `--admin` override a human reaches for. Only the
|
|
130
|
+
# ARMING form is intercepted, because only arming produces a false report of
|
|
131
|
+
# success that outlives the command.
|
|
132
|
+
#
|
|
133
|
+
# The refusal is not a dead end: dismissing a genuinely stale review is a
|
|
134
|
+
# supported step of `lisa-drive-pr-to-merge`, and that is what the message
|
|
135
|
+
# names. There is deliberately no env-var escape hatch — an override the
|
|
136
|
+
# governed agent can set is theatre, and the real remedy is one command away.
|
|
137
|
+
#
|
|
138
|
+
# SUBSTRATES
|
|
139
|
+
#
|
|
140
|
+
# Arming has TWO shell spellings, not one. `gh pr merge --auto` is the common
|
|
141
|
+
# one; `gh api graphql` running the `enablePullRequestAutoMerge` mutation is the
|
|
142
|
+
# same act through the API, and a guard that recognises only the first reports
|
|
143
|
+
# safety about both. That is the failure mode CodySwannGT/lisa#3753 records one
|
|
144
|
+
# guard over — a control that sees a single substrate and speaks for all of
|
|
145
|
+
# them — so both are matched here. The mutation names a node id rather than a
|
|
146
|
+
# number, so the probe resolves it through `node(id:)`; a mutation whose id
|
|
147
|
+
# cannot be read at all FAILS CLOSED, because silence about an arming this
|
|
148
|
+
# guard could not inspect is this ticket's own defect in a new place.
|
|
149
|
+
#
|
|
150
|
+
# Recorded residual: the guard is registered `matcher: "Bash"`, so a structured
|
|
151
|
+
# tool call that arms auto-merge would not reach it. No MCP server provisioned
|
|
152
|
+
# here exposes auto-merge arming — the GitHub MCP merges directly, which is out
|
|
153
|
+
# of scope by the paragraph above — so the gap is currently unreachable rather
|
|
154
|
+
# than open. If one appears, the fix is #3753's: move the registration AND the
|
|
155
|
+
# internal `tool_name` gate together, since either alone ships a change that
|
|
156
|
+
# nothing fails on. Whether it earns its own `matcher: ""` group is a cost
|
|
157
|
+
# question that PR measured and bounded (~11.5ms per non-Bash call, with the
|
|
158
|
+
# whole broad set to stay under ~100ms); re-measure rather than assume.
|
|
159
|
+
#
|
|
160
|
+
# The line below is what lets `lisa apply` tell a downstream copy of this guard
|
|
161
|
+
# that is BEHIND from one that is AHEAD. Add a name here in the same commit that
|
|
162
|
+
# closes a vector.
|
|
163
|
+
# lisa-guard-capabilities: automerge-arm-review-decision, automerge-arm-graphql, automerge-arm-uncovered-base, retarget-uncovered-base, retarget-graphql
|
|
164
|
+
set -euo pipefail
|
|
165
|
+
|
|
166
|
+
input="$(cat)"
|
|
167
|
+
|
|
168
|
+
# Evaluate once per tool call when this guard is registered on both channels.
|
|
169
|
+
#
|
|
170
|
+
# Lisa reaches an agent through the repository dispatcher AND the plugin
|
|
171
|
+
# manifest, and where both are live the harness runs this guard twice for one
|
|
172
|
+
# tool call. `guard-dedupe.bash` short-circuits the second run ONLY when a
|
|
173
|
+
# byte-identical copy already ALLOWED this exact payload on this exact tool
|
|
174
|
+
# call; a differing vintage, a refusal, and a host with one channel all
|
|
175
|
+
# evaluate exactly as before. Nothing is de-registered by it
|
|
176
|
+
# (CodySwannGT/lisa#3814).
|
|
177
|
+
#
|
|
178
|
+
# Absent library means no dedupe, which is the pre-existing behaviour, so an
|
|
179
|
+
# older channel copy that predates it is unaffected.
|
|
180
|
+
lisa_guard_hook_dir="${BASH_SOURCE[0]%/*}"
|
|
181
|
+
lisa_guard_dedupe_lib="$lisa_guard_hook_dir/guard-dedupe.bash"
|
|
182
|
+
if [ -r "$lisa_guard_dedupe_lib" ]; then
|
|
183
|
+
# shellcheck source=guard-dedupe.bash
|
|
184
|
+
. "$lisa_guard_dedupe_lib"
|
|
185
|
+
trap 'lisa_guard_dedupe_record $?' EXIT
|
|
186
|
+
lisa_guard_dedupe block-blind-automerge "$input"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Both interpreters are probed BEFORE use, and a missing one is ANNOUNCED
|
|
190
|
+
# rather than swallowed. Under `set -e` an absent jq would abort with 127, and
|
|
191
|
+
# Claude Code treats any non-2 exit as a non-blocking hook error — so the guard
|
|
192
|
+
# would silently permit the very thing it exists to stop. Degrading to "allow"
|
|
193
|
+
# is right (a hook that cannot parse its input cannot tell an arming command
|
|
194
|
+
# from an ordinary one); doing it quietly is not.
|
|
195
|
+
for required in jq python3; do
|
|
196
|
+
if ! command -v "$required" >/dev/null 2>&1; then
|
|
197
|
+
printf 'block-blind-automerge: %s not found; auto-merge review-gate protection is NOT active\n' \
|
|
198
|
+
"$required" >&2
|
|
199
|
+
exit 0
|
|
200
|
+
fi
|
|
201
|
+
done
|
|
202
|
+
|
|
203
|
+
tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null || true)"
|
|
204
|
+
if [ "$tool_name" != "Bash" ]; then
|
|
205
|
+
exit 0
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
command_str="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null || true)"
|
|
209
|
+
if [ -z "$command_str" ]; then
|
|
210
|
+
exit 0
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
if ! BLOCK_BLIND_AUTOMERGE_COMMAND="$command_str" python3 - <<'PY'
|
|
214
|
+
import io
|
|
215
|
+
import json
|
|
216
|
+
import os
|
|
217
|
+
import re
|
|
218
|
+
import shlex
|
|
219
|
+
import subprocess
|
|
220
|
+
import sys
|
|
221
|
+
from urllib.parse import quote
|
|
222
|
+
|
|
223
|
+
command = os.environ.get("BLOCK_BLIND_AUTOMERGE_COMMAND", "")
|
|
224
|
+
|
|
225
|
+
# The one verdict that means "GitHub is refusing this merge on review grounds".
|
|
226
|
+
BLOCKING_REVIEW_DECISION = "CHANGES_REQUESTED"
|
|
227
|
+
|
|
228
|
+
# A hook must not become the reason a command hangs. `timeout(1)` is not
|
|
229
|
+
# present on every machine this runs on, so the bound is expressed where it is
|
|
230
|
+
# always available — and a TimeoutExpired is a NAMED exception, so a slow
|
|
231
|
+
# network can never be mistaken for a PR that reads as clean.
|
|
232
|
+
GH_TIMEOUT_SECONDS = 20
|
|
233
|
+
|
|
234
|
+
# gh's persistent flags that take a SEPARATE value token, which must be skipped
|
|
235
|
+
# when looking for the subcommand: `gh --repo o/r pr merge` reaches `merge`.
|
|
236
|
+
GH_SEPARATE_VALUE = {"-R", "--repo", "--hostname"}
|
|
237
|
+
|
|
238
|
+
# `gh pr merge` options taking a separate value. Listed so a VALUE is never
|
|
239
|
+
# mistaken for the PR selector — `gh pr merge --subject "fix 12" --auto` must
|
|
240
|
+
# not read `fix 12` as the PR.
|
|
241
|
+
MERGE_SEPARATE_VALUE = {
|
|
242
|
+
"-b", "--body", "-F", "--body-file", "-t", "--subject",
|
|
243
|
+
"--match-head-commit", "--author-email",
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
COMMAND_SEPARATORS = {
|
|
247
|
+
";", "|", "||", "&", "&&", "(", ")", "<", ">", ">>", "<<", "&|",
|
|
248
|
+
# `|&` pipes stdout AND stderr into the next command, so it ends a command
|
|
249
|
+
# exactly as `|` does. shlex emits it as one token, and before it was listed
|
|
250
|
+
# here `echo hi |& git commit --no-verify` read as a single `echo` command
|
|
251
|
+
# whose arguments happened to include git. The case terminators end a
|
|
252
|
+
# command the same way.
|
|
253
|
+
"|&", ";;", ";&", ";;&",
|
|
254
|
+
}
|
|
255
|
+
# shlex also GLUES adjacent punctuation into one token across distinct
|
|
256
|
+
# operators: `(echo hi)|&git ...` arrives as `)|&`, which no set membership
|
|
257
|
+
# test can recognise. An operator-only token is therefore re-split into the
|
|
258
|
+
# operators it is made of, longest first. Redirections are kept whole and are
|
|
259
|
+
# deliberately NOT separators, so `git commit 2>&1 -n` keeps its `-n` inside the
|
|
260
|
+
# commit's argv scope.
|
|
261
|
+
OPERATOR_ONLY = re.compile(r"^[();<>|&]+$")
|
|
262
|
+
SHELL_OPERATOR = re.compile("|".join(
|
|
263
|
+
re.escape(operator) for operator in sorted(
|
|
264
|
+
COMMAND_SEPARATORS | {">|", "&>", "&>>", ">&", "<&", "<>", "<<<"},
|
|
265
|
+
key=lambda operator: (-len(operator), operator),
|
|
266
|
+
)
|
|
267
|
+
))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def split_operators(token):
|
|
271
|
+
"""Split a glued operator-only token into the shell operators it holds.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
token: One shlex token that was not read inside a quote or escape.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
The operators, longest first, or the token itself when it is not
|
|
278
|
+
made only of shell punctuation.
|
|
279
|
+
"""
|
|
280
|
+
if OPERATOR_ONLY.match(token):
|
|
281
|
+
return SHELL_OPERATOR.findall(token)
|
|
282
|
+
return [token]
|
|
283
|
+
|
|
284
|
+
# Shell wrappers whose `-c` argument is a command in a string rather than argv.
|
|
285
|
+
# The outer argv names bash, so the arming is invisible without recursing once.
|
|
286
|
+
SHELL_WRAPPERS = {"bash", "sh", "zsh", "dash"}
|
|
287
|
+
|
|
288
|
+
# The GraphQL mutation that arms auto-merge. Same act as `gh pr merge --auto`,
|
|
289
|
+
# reached through the API instead of the porcelain.
|
|
290
|
+
AUTO_MERGE_MUTATION = "enablePullRequestAutoMerge"
|
|
291
|
+
|
|
292
|
+
# gh's field flags. `-f`/`--raw-field` and friends carry `name=value` pairs, so
|
|
293
|
+
# both the mutation text and its variables arrive through them.
|
|
294
|
+
GH_FIELD_FLAGS = {"-f", "--field", "-F", "--raw-field"}
|
|
295
|
+
|
|
296
|
+
# How the mutation names its subject, whether inline or as a variable.
|
|
297
|
+
PULL_REQUEST_ID_PATTERN = re.compile(
|
|
298
|
+
r"pullRequestId\s*:\s*(?:\"([^\"]+)\"|'([^']+)'|\$([A-Za-z_][A-Za-z0-9_]*))"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# A PR node id as GitHub issues them. Matched rather than accepted verbatim so
|
|
302
|
+
# a variable reference is never sent to the API as if it were an id.
|
|
303
|
+
NODE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_=-]+$")
|
|
304
|
+
|
|
305
|
+
# The probe that resolves a node id to the same fields `gh pr view` returns.
|
|
306
|
+
#
|
|
307
|
+
# `statusCheckRollup` is in here because the RE-TARGET path needs it and this
|
|
308
|
+
# is the only probe a GraphQL `updatePullRequest` can use. Without it
|
|
309
|
+
# `failing_check_names` read an absent key, found no failing check, and the
|
|
310
|
+
# guard announced the re-target as the sanctioned green-PR batching case —
|
|
311
|
+
# for a pull request that might be failing every check it has. The porcelain
|
|
312
|
+
# path asked the right question and the API path could not, so the identical
|
|
313
|
+
# act was guarded through `gh pr edit --base` and waved through as GraphQL:
|
|
314
|
+
# a bypass that needs no permission, only a different spelling.
|
|
315
|
+
#
|
|
316
|
+
# The nodes are reshaped by `--jq` below into the flat list `gh pr view
|
|
317
|
+
# --json statusCheckRollup` returns, so one reader serves both probes.
|
|
318
|
+
NODE_QUERY = (
|
|
319
|
+
"query($id:ID!){node(id:$id){... on PullRequest"
|
|
320
|
+
"{number url reviewDecision state baseRefName "
|
|
321
|
+
"commits(last:1){nodes{commit{statusCheckRollup{contexts(first:100){"
|
|
322
|
+
"pageInfo{hasNextPage} nodes{"
|
|
323
|
+
"__typename ... on CheckRun{name conclusion} "
|
|
324
|
+
"... on StatusContext{context state}"
|
|
325
|
+
"}}}}}}}}}"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Reshape the envelope into the porcelain's shape: unwrap `.data.node`, hoist
|
|
329
|
+
# the last commit's rollup contexts to a top-level `statusCheckRollup` list,
|
|
330
|
+
# and drop the `commits` scaffolding that carried it.
|
|
331
|
+
# `has("commits")` rather than an unconditional default, and the distinction is
|
|
332
|
+
# the point: a node that CARRIES the scaffolding and no contexts is a PR with no
|
|
333
|
+
# checks, which is genuinely not failing and gets an empty list. A node without
|
|
334
|
+
# the scaffolding is a probe that never asked, and it must leave the key absent
|
|
335
|
+
# so the re-target path can tell the two apart instead of reading both as green.
|
|
336
|
+
# A truncated page is equally unreadable: a failing check may be on the next
|
|
337
|
+
# page, so a green first page cannot authorize an uncovered-base re-target.
|
|
338
|
+
NODE_JQ = (
|
|
339
|
+
"if .data.node == null then null else .data.node as $n "
|
|
340
|
+
"| if ($n.commits.nodes[0].commit.statusCheckRollup.contexts.pageInfo.hasNextPage == true) "
|
|
341
|
+
"then ($n | del(.commits)) "
|
|
342
|
+
"elif ($n | has(\"commits\")) then ($n | del(.commits)) "
|
|
343
|
+
"+ {statusCheckRollup: "
|
|
344
|
+
"((($n.commits.nodes[0].commit.statusCheckRollup.contexts.nodes)) // [])} "
|
|
345
|
+
"else $n end end"
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# What a probe spec asks for. `view` and `node` differ only in how the PR is
|
|
349
|
+
# named; `unreadable` is an arming whose subject could not be determined.
|
|
350
|
+
PROBE_VIEW = "view"
|
|
351
|
+
PROBE_NODE = "node"
|
|
352
|
+
PROBE_UNREADABLE = "unreadable"
|
|
353
|
+
|
|
354
|
+
# What a probe needs to know, per act. The review question and the base-coverage
|
|
355
|
+
# question want different fields, and the arming path asks for the second set
|
|
356
|
+
# only when the first has already been answered "not blocked" — so a refusal
|
|
357
|
+
# still costs exactly one call.
|
|
358
|
+
ARM_FIELDS = "number,reviewDecision,state,url,baseRefName"
|
|
359
|
+
RETARGET_FIELDS = "number,state,url,baseRefName,statusCheckRollup"
|
|
360
|
+
|
|
361
|
+
# The two acts this guard intercepts.
|
|
362
|
+
ACT_ARM = "arm"
|
|
363
|
+
ACT_RETARGET = "retarget"
|
|
364
|
+
|
|
365
|
+
# The rule type that makes a check able to block a merge. A ref with no rule of
|
|
366
|
+
# this type has zero required checks, whatever runs against it.
|
|
367
|
+
REQUIRED_STATUS_CHECKS_RULE = "required_status_checks"
|
|
368
|
+
|
|
369
|
+
# The GraphQL mutation that re-targets a pull request. Same act as
|
|
370
|
+
# `gh pr edit --base`, reached through the API instead of the porcelain.
|
|
371
|
+
RETARGET_MUTATION = "updatePullRequest"
|
|
372
|
+
|
|
373
|
+
# How a re-target names its new base, whether inline or as a variable.
|
|
374
|
+
BASE_REF_NAME_PATTERN = re.compile(
|
|
375
|
+
r"baseRefName\s*:\s*(?:\"([^\"]+)\"|'([^']+)'|\$([A-Za-z_][A-Za-z0-9_]*))"
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# `gh pr edit` options taking a SEPARATE value, so a value is never mistaken for
|
|
379
|
+
# the PR selector: `gh pr edit --title "fix 12" --base main` must not read
|
|
380
|
+
# `fix 12` as the PR.
|
|
381
|
+
EDIT_SEPARATE_VALUE = {
|
|
382
|
+
"--add-assignee", "--add-label", "--add-project", "--add-reviewer",
|
|
383
|
+
"-b", "--body", "-F", "--body-file", "-m", "--milestone",
|
|
384
|
+
"--remove-assignee", "--remove-label", "--remove-project",
|
|
385
|
+
"--remove-reviewer", "-t", "--title",
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
# Both spellings of the re-target flag, as `gh pr edit --help` lists them.
|
|
389
|
+
BASE_FLAGS = {"-B", "--base"}
|
|
390
|
+
|
|
391
|
+
# Check outcomes that mean something is currently failing. Read from either
|
|
392
|
+
# shape `statusCheckRollup` returns: a CheckRun carries `conclusion`, a
|
|
393
|
+
# commit StatusContext carries `state`.
|
|
394
|
+
FAILING_CHECK_OUTCOMES = {
|
|
395
|
+
"FAILURE", "TIMED_OUT", "CANCELLED", "ACTION_REQUIRED", "STARTUP_FAILURE",
|
|
396
|
+
"ERROR",
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def strip_heredocs(text):
|
|
401
|
+
"""Drop heredoc bodies, which are data rather than commands.
|
|
402
|
+
|
|
403
|
+
A PR body or commit message pasted into a heredoc can contain the literal
|
|
404
|
+
text of an arming command; running the guard over it would refuse a command
|
|
405
|
+
that arms nothing.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
text: The raw command line.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
The command with heredoc payloads removed.
|
|
412
|
+
"""
|
|
413
|
+
lines = text.splitlines()
|
|
414
|
+
output = []
|
|
415
|
+
pending = []
|
|
416
|
+
marker_pattern = re.compile(
|
|
417
|
+
r"<<-?\s*(?:'([^']+)'|\"([^\"]+)\"|([A-Za-z_][A-Za-z0-9_]*))"
|
|
418
|
+
)
|
|
419
|
+
index = 0
|
|
420
|
+
while index < len(lines):
|
|
421
|
+
line = lines[index]
|
|
422
|
+
output.append(line)
|
|
423
|
+
pending.extend(
|
|
424
|
+
next(group for group in match.groups() if group)
|
|
425
|
+
for match in marker_pattern.finditer(line)
|
|
426
|
+
)
|
|
427
|
+
index += 1
|
|
428
|
+
while pending and index < len(lines):
|
|
429
|
+
if lines[index].strip() == pending[0]:
|
|
430
|
+
output.append(lines[index])
|
|
431
|
+
pending.pop(0)
|
|
432
|
+
index += 1
|
|
433
|
+
break
|
|
434
|
+
index += 1
|
|
435
|
+
return "\n".join(output)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def line_boundaries_as_separators(text):
|
|
439
|
+
"""Turn newlines into command separators, keeping continuations intact.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
text: The command line, heredoc payloads already stripped.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
The same command with line breaks spelled as separators.
|
|
446
|
+
"""
|
|
447
|
+
joined = text.replace("\r\n", "\n").replace("\\\n", " ")
|
|
448
|
+
return joined.replace("\n", " ; ")
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def shell_tokens(text):
|
|
452
|
+
"""Tokenize a command with shell operators kept as their own tokens.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
text: The command line, heredoc payloads already stripped.
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
The token list, with `;`, `|`, `&&`, `(` and friends standing alone.
|
|
459
|
+
"""
|
|
460
|
+
stream = OperatorStream(line_boundaries_as_separators(text))
|
|
461
|
+
lexer = shlex.shlex(stream, posix=True, punctuation_chars=True)
|
|
462
|
+
lexer.whitespace_split = True
|
|
463
|
+
lexer.commenters = ""
|
|
464
|
+
stream.lexer = lexer
|
|
465
|
+
tokens = []
|
|
466
|
+
while True:
|
|
467
|
+
stream.literal_operator = False
|
|
468
|
+
token = lexer.get_token()
|
|
469
|
+
if token is None:
|
|
470
|
+
return tokens
|
|
471
|
+
# Quoted punctuation is an argument, never a boundary: a branch can be
|
|
472
|
+
# named `|&`, and `gh pr merge '|&' --auto` must keep `--auto` in the
|
|
473
|
+
# merge's own argv. Only real operator runs are split.
|
|
474
|
+
if stream.literal_operator:
|
|
475
|
+
tokens.append(
|
|
476
|
+
chr(0) + token if token in COMMAND_SEPARATORS else token
|
|
477
|
+
)
|
|
478
|
+
else:
|
|
479
|
+
tokens.extend(split_operators(token))
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
class OperatorStream(io.StringIO):
|
|
483
|
+
"""Remember punctuation read while shlex is inside a quote or escape."""
|
|
484
|
+
|
|
485
|
+
lexer = None
|
|
486
|
+
literal_operator = False
|
|
487
|
+
|
|
488
|
+
def read(self, size=-1):
|
|
489
|
+
value = super().read(size)
|
|
490
|
+
if (self.lexer is not None and self.lexer.state in {"'", '"', chr(92)}
|
|
491
|
+
and any(char in "();|&<>" for char in value)):
|
|
492
|
+
self.literal_operator = True
|
|
493
|
+
return value
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def is_gh(token):
|
|
497
|
+
"""Whether a token invokes the gh CLI.
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
token: A single shell token.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
True for a bare `gh` or any absolute/relative path ending in `/gh`.
|
|
504
|
+
"""
|
|
505
|
+
return token == "gh" or token.endswith("/gh")
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def subcommand_argv_after_gh(tokens, start, verb):
|
|
509
|
+
"""Find a `pr <verb>` invocation's argv within one gh command.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
tokens: The operator-aware token list.
|
|
513
|
+
start: Index just past the `gh` token.
|
|
514
|
+
verb: The `pr` subcommand to look for, `merge` or `edit`.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
The argv following `verb`, or None when this gh call is not that verb.
|
|
518
|
+
"""
|
|
519
|
+
words = []
|
|
520
|
+
index = start
|
|
521
|
+
while index < len(tokens):
|
|
522
|
+
token = tokens[index]
|
|
523
|
+
if token in COMMAND_SEPARATORS:
|
|
524
|
+
break
|
|
525
|
+
if token.startswith("-"):
|
|
526
|
+
# `--repo=o/r` carries its value; `--repo o/r` eats the next token.
|
|
527
|
+
index += 2 if token in GH_SEPARATE_VALUE else 1
|
|
528
|
+
continue
|
|
529
|
+
words.append((token, index))
|
|
530
|
+
index += 1
|
|
531
|
+
for position, entry in enumerate(words):
|
|
532
|
+
if entry[0] == verb and position > 0 and words[position - 1][0] == "pr":
|
|
533
|
+
return tokens[entry[1] + 1:]
|
|
534
|
+
return None
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def arms_auto_merge(argv):
|
|
538
|
+
"""Whether a `gh pr merge` argv carries `--auto`.
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
argv: Tokens following the `merge` subcommand.
|
|
542
|
+
|
|
543
|
+
Returns:
|
|
544
|
+
True if this invocation arms auto-merge rather than merging now.
|
|
545
|
+
"""
|
|
546
|
+
index = 0
|
|
547
|
+
while index < len(argv):
|
|
548
|
+
token = argv[index]
|
|
549
|
+
index += 1
|
|
550
|
+
if token in COMMAND_SEPARATORS:
|
|
551
|
+
return False
|
|
552
|
+
if token == "--auto" or token.startswith("--auto="):
|
|
553
|
+
return True
|
|
554
|
+
if token in MERGE_SEPARATE_VALUE:
|
|
555
|
+
index += 1
|
|
556
|
+
return False
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def literal_value(token):
|
|
560
|
+
"""The argument a token stands for, without the quoted-separator marker.
|
|
561
|
+
|
|
562
|
+
`shell_tokens` prefixes a QUOTED separator with NUL so no scan stops at it.
|
|
563
|
+
That marker is for boundary checks only: a value handed to a gh probe
|
|
564
|
+
(a selector, a base ref, a repo) must be the argument the shell passes.
|
|
565
|
+
|
|
566
|
+
Args:
|
|
567
|
+
token: One token from `shell_tokens`.
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
The token as the shell would pass it.
|
|
571
|
+
"""
|
|
572
|
+
return token[1:] if token.startswith(chr(0)) else token
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def pr_selector(argv, separate_value=MERGE_SEPARATE_VALUE):
|
|
576
|
+
"""The PR the command names, as gh itself would read it.
|
|
577
|
+
|
|
578
|
+
Returned verbatim so it can be handed straight back to `gh pr view`, which
|
|
579
|
+
accepts the same number, URL, or branch. When the command names none, gh
|
|
580
|
+
resolves the current branch and so does the probe.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
argv: Tokens following the `merge` or `edit` subcommand.
|
|
584
|
+
separate_value: Options of that subcommand taking a separate value,
|
|
585
|
+
whose VALUE must never be read as the selector.
|
|
586
|
+
|
|
587
|
+
Returns:
|
|
588
|
+
The selector token, or None when the command relies on the branch.
|
|
589
|
+
"""
|
|
590
|
+
index = 0
|
|
591
|
+
while index < len(argv):
|
|
592
|
+
token = argv[index]
|
|
593
|
+
index += 1
|
|
594
|
+
if token in COMMAND_SEPARATORS:
|
|
595
|
+
return None
|
|
596
|
+
if token in separate_value or token in BASE_FLAGS:
|
|
597
|
+
index += 1
|
|
598
|
+
continue
|
|
599
|
+
if not token.startswith("-"):
|
|
600
|
+
return literal_value(token)
|
|
601
|
+
return None
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def retarget_target(argv):
|
|
605
|
+
"""The base ref a `gh pr edit` invocation moves the pull request to.
|
|
606
|
+
|
|
607
|
+
Args:
|
|
608
|
+
argv: Tokens following the `edit` subcommand.
|
|
609
|
+
|
|
610
|
+
Returns:
|
|
611
|
+
The new base ref, or None when this edit changes no base.
|
|
612
|
+
"""
|
|
613
|
+
index = 0
|
|
614
|
+
while index < len(argv):
|
|
615
|
+
token = argv[index]
|
|
616
|
+
index += 1
|
|
617
|
+
if token in COMMAND_SEPARATORS:
|
|
618
|
+
return None
|
|
619
|
+
if token in BASE_FLAGS and index < len(argv):
|
|
620
|
+
return literal_value(argv[index])
|
|
621
|
+
if token.startswith("--base="):
|
|
622
|
+
return token.split("=", 1)[1]
|
|
623
|
+
if token in EDIT_SEPARATE_VALUE:
|
|
624
|
+
index += 1
|
|
625
|
+
return None
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def repo_flag(tokens, start):
|
|
629
|
+
"""The `--repo` value a gh invocation carries, in either spelling.
|
|
630
|
+
|
|
631
|
+
Args:
|
|
632
|
+
tokens: The operator-aware token list.
|
|
633
|
+
start: Index just past the `gh` token.
|
|
634
|
+
|
|
635
|
+
Returns:
|
|
636
|
+
A list of gh arguments re-expressing the repo, empty when none.
|
|
637
|
+
"""
|
|
638
|
+
index = start
|
|
639
|
+
while index < len(tokens):
|
|
640
|
+
token = tokens[index]
|
|
641
|
+
if token in COMMAND_SEPARATORS:
|
|
642
|
+
break
|
|
643
|
+
if token in {"-R", "--repo"} and index + 1 < len(tokens):
|
|
644
|
+
return ["--repo", literal_value(tokens[index + 1])]
|
|
645
|
+
if token.startswith("--repo="):
|
|
646
|
+
return ["--repo", token.split("=", 1)[1]]
|
|
647
|
+
index += 1
|
|
648
|
+
return []
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def gh_invocation_tokens(tokens, start):
|
|
652
|
+
"""The tokens belonging to one gh invocation, up to the next separator.
|
|
653
|
+
|
|
654
|
+
Args:
|
|
655
|
+
tokens: The operator-aware token list.
|
|
656
|
+
start: Index just past the `gh` token.
|
|
657
|
+
|
|
658
|
+
Returns:
|
|
659
|
+
The invocation's own tokens.
|
|
660
|
+
"""
|
|
661
|
+
end = start
|
|
662
|
+
while end < len(tokens) and tokens[end] not in COMMAND_SEPARATORS:
|
|
663
|
+
end += 1
|
|
664
|
+
return tokens[start:end]
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def field_values(argv):
|
|
668
|
+
"""The `name=value` payloads a gh invocation passes as fields.
|
|
669
|
+
|
|
670
|
+
Args:
|
|
671
|
+
argv: One gh invocation's tokens.
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
A dict of field name to value. Later fields win, as gh does.
|
|
675
|
+
"""
|
|
676
|
+
values = {}
|
|
677
|
+
index = 0
|
|
678
|
+
while index < len(argv):
|
|
679
|
+
token = argv[index]
|
|
680
|
+
index += 1
|
|
681
|
+
raw = None
|
|
682
|
+
if token in GH_FIELD_FLAGS and index < len(argv):
|
|
683
|
+
raw = argv[index]
|
|
684
|
+
index += 1
|
|
685
|
+
elif token.startswith("--field=") or token.startswith("--raw-field="):
|
|
686
|
+
raw = token.split("=", 1)[1]
|
|
687
|
+
if raw is not None and "=" in raw:
|
|
688
|
+
name, value = raw.split("=", 1)
|
|
689
|
+
values[name] = value
|
|
690
|
+
return values
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def graphql_mutation(argv, mutation):
|
|
694
|
+
"""The text and fields of one `gh api graphql` call naming a mutation.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
argv: One gh invocation's tokens, `api` included.
|
|
698
|
+
mutation: The mutation name to look for.
|
|
699
|
+
|
|
700
|
+
Returns:
|
|
701
|
+
A pair (query_text, fields). `query_text` is empty when this call is
|
|
702
|
+
not a graphql invocation running that mutation.
|
|
703
|
+
"""
|
|
704
|
+
words = [token for token in argv if not token.startswith("-")]
|
|
705
|
+
if len(words) < 2 or words[0] != "api" or words[1] != "graphql":
|
|
706
|
+
return ("", {})
|
|
707
|
+
fields = field_values(argv)
|
|
708
|
+
query = " ".join(
|
|
709
|
+
value for name, value in fields.items() if mutation in value
|
|
710
|
+
)
|
|
711
|
+
return (query, fields)
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def resolved_literal(match, fields, pattern):
|
|
715
|
+
"""A mutation argument's value, whether written inline or as a variable.
|
|
716
|
+
|
|
717
|
+
Args:
|
|
718
|
+
match: The regex match over the mutation text, or None.
|
|
719
|
+
fields: The gh field payloads the same invocation sets.
|
|
720
|
+
pattern: A compiled pattern the value must satisfy, or None for any.
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
The value, or None when it cannot be resolved — which is a refusal
|
|
724
|
+
rather than a pass, because an act whose subject nobody can read is
|
|
725
|
+
this guard's own defect in a new place.
|
|
726
|
+
"""
|
|
727
|
+
if match is None:
|
|
728
|
+
return None
|
|
729
|
+
inline = match.group(1) or match.group(2)
|
|
730
|
+
candidate = inline if inline else fields.get(match.group(3))
|
|
731
|
+
if not candidate:
|
|
732
|
+
return None
|
|
733
|
+
if pattern is not None and not pattern.match(candidate):
|
|
734
|
+
return None
|
|
735
|
+
return candidate
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def graphql_arming_id(argv):
|
|
739
|
+
"""The PR node id a `gh api graphql` auto-merge mutation names.
|
|
740
|
+
|
|
741
|
+
Args:
|
|
742
|
+
argv: One gh invocation's tokens, `api` included.
|
|
743
|
+
|
|
744
|
+
Returns:
|
|
745
|
+
A pair (is_arming, node_id). `node_id` is None when the mutation arms
|
|
746
|
+
auto-merge but names its subject in a way this cannot resolve — which
|
|
747
|
+
is a refusal, not a pass.
|
|
748
|
+
"""
|
|
749
|
+
query, fields = graphql_mutation(argv, AUTO_MERGE_MUTATION)
|
|
750
|
+
if not query:
|
|
751
|
+
return (False, None)
|
|
752
|
+
return (
|
|
753
|
+
True,
|
|
754
|
+
resolved_literal(
|
|
755
|
+
PULL_REQUEST_ID_PATTERN.search(query), fields, NODE_ID_PATTERN
|
|
756
|
+
),
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def graphql_retarget(argv):
|
|
761
|
+
"""The subject and new base a `gh api graphql` re-target mutation names.
|
|
762
|
+
|
|
763
|
+
`updatePullRequest` also edits titles, bodies and labels, none of which
|
|
764
|
+
move a pull request out from under its gates. Only a mutation that carries
|
|
765
|
+
`baseRefName` is a re-target, so the others are left alone entirely.
|
|
766
|
+
|
|
767
|
+
Args:
|
|
768
|
+
argv: One gh invocation's tokens, `api` included.
|
|
769
|
+
|
|
770
|
+
Returns:
|
|
771
|
+
A triple (is_retarget, node_id, base_ref). Either of the last two is
|
|
772
|
+
None when the mutation re-targets but names that part in a way this
|
|
773
|
+
cannot resolve.
|
|
774
|
+
"""
|
|
775
|
+
query, fields = graphql_mutation(argv, RETARGET_MUTATION)
|
|
776
|
+
if not query:
|
|
777
|
+
return (False, None, None)
|
|
778
|
+
base_match = BASE_REF_NAME_PATTERN.search(query)
|
|
779
|
+
if base_match is None:
|
|
780
|
+
return (False, None, None)
|
|
781
|
+
return (
|
|
782
|
+
True,
|
|
783
|
+
resolved_literal(
|
|
784
|
+
PULL_REQUEST_ID_PATTERN.search(query), fields, NODE_ID_PATTERN
|
|
785
|
+
),
|
|
786
|
+
resolved_literal(base_match, fields, None),
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def guarded_invocations(text, depth=0):
|
|
791
|
+
"""Every arming and every re-target this command line performs.
|
|
792
|
+
|
|
793
|
+
Args:
|
|
794
|
+
text: The command line, heredoc payloads already stripped.
|
|
795
|
+
depth: Recursion depth, bounding the `bash -c` unwrap to one level.
|
|
796
|
+
|
|
797
|
+
Returns:
|
|
798
|
+
A list of (act, kind, subject, repo_args, target_ref) probe specs, one
|
|
799
|
+
per intercepted act. `target_ref` is meaningful only for a re-target.
|
|
800
|
+
"""
|
|
801
|
+
try:
|
|
802
|
+
tokens = shell_tokens(text)
|
|
803
|
+
except ValueError:
|
|
804
|
+
return []
|
|
805
|
+
found = []
|
|
806
|
+
for index, token in enumerate(tokens):
|
|
807
|
+
if depth == 0 and token.split("/")[-1] in SHELL_WRAPPERS:
|
|
808
|
+
# `bash -c '<command>'` hides the whole invocation inside one token.
|
|
809
|
+
if index + 2 < len(tokens) and tokens[index + 1] == "-c":
|
|
810
|
+
found.extend(guarded_invocations(tokens[index + 2], depth + 1))
|
|
811
|
+
continue
|
|
812
|
+
if not is_gh(token):
|
|
813
|
+
continue
|
|
814
|
+
repo_args = repo_flag(tokens, index + 1)
|
|
815
|
+
merge_argv = subcommand_argv_after_gh(tokens, index + 1, "merge")
|
|
816
|
+
if merge_argv is not None and arms_auto_merge(merge_argv):
|
|
817
|
+
found.append(
|
|
818
|
+
(ACT_ARM, PROBE_VIEW, pr_selector(merge_argv), repo_args, None)
|
|
819
|
+
)
|
|
820
|
+
continue
|
|
821
|
+
edit_argv = subcommand_argv_after_gh(tokens, index + 1, "edit")
|
|
822
|
+
if edit_argv is not None:
|
|
823
|
+
target = retarget_target(edit_argv)
|
|
824
|
+
if target is not None:
|
|
825
|
+
found.append(
|
|
826
|
+
(
|
|
827
|
+
ACT_RETARGET,
|
|
828
|
+
PROBE_VIEW,
|
|
829
|
+
pr_selector(edit_argv, EDIT_SEPARATE_VALUE),
|
|
830
|
+
repo_args,
|
|
831
|
+
target,
|
|
832
|
+
)
|
|
833
|
+
)
|
|
834
|
+
continue
|
|
835
|
+
invocation = gh_invocation_tokens(tokens, index + 1)
|
|
836
|
+
is_arming, node_id = graphql_arming_id(invocation)
|
|
837
|
+
if is_arming:
|
|
838
|
+
kind = PROBE_NODE if node_id else PROBE_UNREADABLE
|
|
839
|
+
found.append((ACT_ARM, kind, node_id, repo_args, None))
|
|
840
|
+
continue
|
|
841
|
+
is_retarget, node_id, target = graphql_retarget(invocation)
|
|
842
|
+
if is_retarget:
|
|
843
|
+
kind = (
|
|
844
|
+
PROBE_NODE if node_id and target is not None else PROBE_UNREADABLE
|
|
845
|
+
)
|
|
846
|
+
found.append((ACT_RETARGET, kind, node_id, repo_args, target))
|
|
847
|
+
return found
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
def probe_args(kind, subject, repo_args, fields):
|
|
851
|
+
"""The gh command that answers this guard's questions about one PR.
|
|
852
|
+
|
|
853
|
+
Args:
|
|
854
|
+
kind: PROBE_VIEW or PROBE_NODE.
|
|
855
|
+
subject: A PR selector, a node id, or None for the current branch.
|
|
856
|
+
repo_args: gh arguments naming the repository, possibly empty.
|
|
857
|
+
fields: The `--json` field list the act needs.
|
|
858
|
+
|
|
859
|
+
Returns:
|
|
860
|
+
The full gh argv.
|
|
861
|
+
"""
|
|
862
|
+
if kind == PROBE_NODE:
|
|
863
|
+
# `--jq` unwraps the envelope so both probes return the same shape.
|
|
864
|
+
return [
|
|
865
|
+
"gh", "api", "graphql",
|
|
866
|
+
"-f", "query=" + NODE_QUERY,
|
|
867
|
+
"-f", "id=" + subject,
|
|
868
|
+
"--jq", NODE_JQ,
|
|
869
|
+
]
|
|
870
|
+
args = ["gh", "pr", "view"]
|
|
871
|
+
if subject is not None:
|
|
872
|
+
args.append(subject)
|
|
873
|
+
args.extend(repo_args)
|
|
874
|
+
args.extend(["--json", fields])
|
|
875
|
+
return args
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def gh_json(args):
|
|
879
|
+
"""Run a gh command and decode its JSON, without throwing.
|
|
880
|
+
|
|
881
|
+
Args:
|
|
882
|
+
args: The full gh argv.
|
|
883
|
+
|
|
884
|
+
Returns:
|
|
885
|
+
A pair (payload, failure). Exactly one is non-None; `failure` names WHY
|
|
886
|
+
the question could not be answered, because "the box was busy" and "gh
|
|
887
|
+
said no" have different remedies and a killed child returns empty
|
|
888
|
+
streams that read like a clean refusal.
|
|
889
|
+
"""
|
|
890
|
+
try:
|
|
891
|
+
result = subprocess.run(
|
|
892
|
+
args, capture_output=True, text=True, timeout=GH_TIMEOUT_SECONDS
|
|
893
|
+
)
|
|
894
|
+
except FileNotFoundError:
|
|
895
|
+
return (None, "gh not found")
|
|
896
|
+
except subprocess.TimeoutExpired:
|
|
897
|
+
return (None, "gh timed out after %ds" % GH_TIMEOUT_SECONDS)
|
|
898
|
+
except OSError as error:
|
|
899
|
+
return (None, "gh could not be run (%s)" % error)
|
|
900
|
+
if result.returncode != 0:
|
|
901
|
+
return (None, "gh exited %d" % result.returncode)
|
|
902
|
+
try:
|
|
903
|
+
return (json.loads(result.stdout or "{}"), None)
|
|
904
|
+
except ValueError:
|
|
905
|
+
return (None, "gh returned unreadable JSON")
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
def pull_request_state(kind, subject, repo_args, fields=ARM_FIELDS):
|
|
909
|
+
"""Ask GitHub for the fields that decide whether a merge can happen.
|
|
910
|
+
|
|
911
|
+
Args:
|
|
912
|
+
kind: PROBE_VIEW or PROBE_NODE.
|
|
913
|
+
subject: A PR selector, a node id, or None for the current branch.
|
|
914
|
+
repo_args: gh arguments naming the repository, possibly empty.
|
|
915
|
+
|
|
916
|
+
Returns:
|
|
917
|
+
A pair (payload, failure). Exactly one is non-None: `payload` is the
|
|
918
|
+
decoded pull request object, `failure` is a human-readable reason the
|
|
919
|
+
question could not be answered.
|
|
920
|
+
"""
|
|
921
|
+
payload, failure = gh_json(probe_args(kind, subject, repo_args, fields))
|
|
922
|
+
if failure is not None:
|
|
923
|
+
return (None, failure)
|
|
924
|
+
if not isinstance(payload, dict):
|
|
925
|
+
return (None, "gh returned unreadable JSON")
|
|
926
|
+
return (payload, None)
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def repo_slug(payload, repo_args):
|
|
930
|
+
"""The `owner/name` the pull request belongs to.
|
|
931
|
+
|
|
932
|
+
Read from the PR's own URL first, because that is the repository GitHub
|
|
933
|
+
says the PR is in — a `--repo` flag is what the caller CLAIMED, and the two
|
|
934
|
+
can differ. The flag is the fallback for a payload with no URL.
|
|
935
|
+
|
|
936
|
+
Args:
|
|
937
|
+
payload: The decoded pull request object.
|
|
938
|
+
repo_args: gh arguments naming the repository, possibly empty.
|
|
939
|
+
|
|
940
|
+
Returns:
|
|
941
|
+
The slug, or None when neither source names one.
|
|
942
|
+
"""
|
|
943
|
+
url = str(payload.get("url") or "")
|
|
944
|
+
parts = url.split("://", 1)[-1].split("/")
|
|
945
|
+
# host / owner / name / "pull" / number
|
|
946
|
+
if len(parts) >= 4 and parts[3] == "pull" and parts[1] and parts[2]:
|
|
947
|
+
return "%s/%s" % (parts[1], parts[2])
|
|
948
|
+
if len(repo_args) == 2 and repo_args[1].count("/") == 1:
|
|
949
|
+
return repo_args[1]
|
|
950
|
+
return None
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
def branch_rules(repo, ref):
|
|
954
|
+
"""Every branch rule GitHub says applies to one ref, read live.
|
|
955
|
+
|
|
956
|
+
This is the whole reason the guard asks the API rather than the checked-in
|
|
957
|
+
policy: a `policy.protect` flag names an INTENT, and only the ruleset's ref
|
|
958
|
+
scope says where it applies. This endpoint answers for the one ref in
|
|
959
|
+
question and folds in organisation rulesets, repository rulesets, and every
|
|
960
|
+
exclusion — none of which a config file states.
|
|
961
|
+
|
|
962
|
+
Args:
|
|
963
|
+
repo: `owner/name`.
|
|
964
|
+
ref: The branch name, unencoded.
|
|
965
|
+
|
|
966
|
+
Returns:
|
|
967
|
+
A pair (rules, failure). Exactly one is non-None.
|
|
968
|
+
"""
|
|
969
|
+
payload, failure = gh_json(
|
|
970
|
+
["gh", "api", "repos/%s/rules/branches/%s" % (repo, quote(ref, safe=""))]
|
|
971
|
+
)
|
|
972
|
+
if failure is not None:
|
|
973
|
+
return (None, failure)
|
|
974
|
+
if not isinstance(payload, list):
|
|
975
|
+
return (None, "gh returned unreadable JSON")
|
|
976
|
+
return (payload, None)
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
def required_context_count(rules):
|
|
980
|
+
"""How many status checks can actually block a merge on that ref.
|
|
981
|
+
|
|
982
|
+
A `required_status_checks` rule carrying an empty context list enforces
|
|
983
|
+
nothing, so rules are counted by their CONTEXTS rather than by their
|
|
984
|
+
presence. Counting rules would report a vacuous rule as protection.
|
|
985
|
+
|
|
986
|
+
Args:
|
|
987
|
+
rules: The rule objects the API returned for one ref.
|
|
988
|
+
|
|
989
|
+
Returns:
|
|
990
|
+
The number of distinct required contexts.
|
|
991
|
+
"""
|
|
992
|
+
contexts = set()
|
|
993
|
+
for rule in rules:
|
|
994
|
+
if not isinstance(rule, dict):
|
|
995
|
+
continue
|
|
996
|
+
if rule.get("type") != REQUIRED_STATUS_CHECKS_RULE:
|
|
997
|
+
continue
|
|
998
|
+
parameters = rule.get("parameters")
|
|
999
|
+
listed = (parameters or {}).get(REQUIRED_STATUS_CHECKS_RULE) or []
|
|
1000
|
+
for entry in listed:
|
|
1001
|
+
if isinstance(entry, dict) and entry.get("context"):
|
|
1002
|
+
contexts.add(entry["context"])
|
|
1003
|
+
return len(contexts)
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
def failing_check_names(payload):
|
|
1007
|
+
"""Every check on the pull request that is currently reporting failure.
|
|
1008
|
+
|
|
1009
|
+
Both shapes `statusCheckRollup` returns are read: a CheckRun carries
|
|
1010
|
+
`conclusion` and `name`, a commit status carries `state` and `context`. A
|
|
1011
|
+
guard that read one of them would report a clean pull request whenever the
|
|
1012
|
+
blocker arrived on the other substrate.
|
|
1013
|
+
|
|
1014
|
+
Args:
|
|
1015
|
+
payload: The decoded pull request object.
|
|
1016
|
+
|
|
1017
|
+
Returns:
|
|
1018
|
+
The names of the failing checks, in the order GitHub listed them.
|
|
1019
|
+
"""
|
|
1020
|
+
rollup = payload.get("statusCheckRollup")
|
|
1021
|
+
names = []
|
|
1022
|
+
for entry in rollup if isinstance(rollup, list) else []:
|
|
1023
|
+
if not isinstance(entry, dict):
|
|
1024
|
+
continue
|
|
1025
|
+
outcome = str(entry.get("conclusion") or entry.get("state") or "").upper()
|
|
1026
|
+
if outcome in FAILING_CHECK_OUTCOMES:
|
|
1027
|
+
names.append(str(entry.get("name") or entry.get("context") or "?"))
|
|
1028
|
+
return names
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
def pr_name(payload):
|
|
1032
|
+
"""The most identifying name available for the PR in a refusal message.
|
|
1033
|
+
|
|
1034
|
+
Args:
|
|
1035
|
+
payload: The decoded `gh pr view --json` object.
|
|
1036
|
+
|
|
1037
|
+
Returns:
|
|
1038
|
+
A URL, a `#<number>`, or a generic phrase.
|
|
1039
|
+
"""
|
|
1040
|
+
if payload.get("url"):
|
|
1041
|
+
return payload["url"]
|
|
1042
|
+
if payload.get("number"):
|
|
1043
|
+
return "#%s" % payload["number"]
|
|
1044
|
+
return "this PR"
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
REFUSAL = """Blocked: arming auto-merge on %s would report a merge that can never happen.
|
|
1048
|
+
|
|
1049
|
+
GitHub reports reviewDecision = CHANGES_REQUESTED. That is a merge blocker with
|
|
1050
|
+
NO check-run representation: the failing-check count reads 0 and the checks tab
|
|
1051
|
+
is entirely green while the PR is permanently unmergeable. Where the ruleset
|
|
1052
|
+
sets dismiss_stale_reviews_on_push: false, pushing the fix does not clear it and
|
|
1053
|
+
no re-review is scheduled to.
|
|
1054
|
+
|
|
1055
|
+
Clear the blocker, then arm:
|
|
1056
|
+
- resolve every review thread and address or reply to the feedback
|
|
1057
|
+
(`lisa-pull-request-review`);
|
|
1058
|
+
- if the review is stale — made on an older head, with the fix already pushed
|
|
1059
|
+
— dismiss it (`lisa-drive-pr-to-merge`, review-gate stall), then re-arm.
|
|
1060
|
+
|
|
1061
|
+
Do not report the PR as armed-and-merging until this reads anything other than
|
|
1062
|
+
CHANGES_REQUESTED.
|
|
1063
|
+
"""
|
|
1064
|
+
|
|
1065
|
+
UNREADABLE_REFUSAL = """Blocked: this command acts on a pull request through a GraphQL mutation whose
|
|
1066
|
+
pullRequestId cannot be read, so what it does is unknowable at the moment the
|
|
1067
|
+
claim "this will merge" is made.
|
|
1068
|
+
|
|
1069
|
+
Use `gh pr merge <pr> --auto` or `gh pr edit <pr> --base <ref>` instead, or pass
|
|
1070
|
+
the node id as a literal or as a field this command also sets. An act nobody can
|
|
1071
|
+
inspect is the failure this guard exists to close, one substrate over.
|
|
1072
|
+
"""
|
|
1073
|
+
|
|
1074
|
+
UNCOVERED_ARM_REFUSAL = """Blocked: arming auto-merge on %s would merge it with NO required checks.
|
|
1075
|
+
|
|
1076
|
+
Its base branch is "%s", and GitHub reports ZERO required status checks on that
|
|
1077
|
+
ref. Every gate still RUNS against this PR; none of them can block the merge.
|
|
1078
|
+
Auto-merge therefore lands the moment the PR is mergeable, before any advisory
|
|
1079
|
+
result has a reader — which is exactly how a PR blocked by two failing required
|
|
1080
|
+
checks reached a merged state without either being fixed or waived
|
|
1081
|
+
(CodySwannGT/lisa#3922).
|
|
1082
|
+
|
|
1083
|
+
This is not the checks being red. It is the checks being unable to say no.
|
|
1084
|
+
|
|
1085
|
+
Merge it deliberately instead, at a moment when the results exist:
|
|
1086
|
+
- read them — `gh pr checks %s` — and merge only if you accept every one;
|
|
1087
|
+
- `gh pr merge %s --merge` performs the merge now, with you as the reader
|
|
1088
|
+
auto-merge would have removed from the path;
|
|
1089
|
+
- or re-target onto a covered base — the default branch, or any ref the
|
|
1090
|
+
rulesets name — and arm there, where the ruleset enforces the gates for you.
|
|
1091
|
+
"""
|
|
1092
|
+
|
|
1093
|
+
UNCOVERED_RETARGET_REFUSAL = """Blocked: re-targeting %s onto "%s" would evaporate its failing checks.
|
|
1094
|
+
|
|
1095
|
+
GitHub reports ZERO required status checks on that ref, and this PR is
|
|
1096
|
+
currently failing:
|
|
1097
|
+
%s
|
|
1098
|
+
|
|
1099
|
+
Those failures would not be fixed and would not be waived — they would simply
|
|
1100
|
+
stop being required, and an armed auto-merge would then fire. That is a
|
|
1101
|
+
permission-free bypass of every gate: no --force, no --admin, no code change,
|
|
1102
|
+
one metadata edit (CodySwannGT/lisa#3922).
|
|
1103
|
+
|
|
1104
|
+
Do one of these instead:
|
|
1105
|
+
- fix the failing checks, then re-target; a green PR may be batched freely;
|
|
1106
|
+
- leave the PR on its covered base and merge it there.
|
|
1107
|
+
"""
|
|
1108
|
+
|
|
1109
|
+
RETARGET_CHECKS_UNREADABLE = """Blocked: re-targeting %s onto "%s" cannot be judged, because this guard could
|
|
1110
|
+
not read the pull request's checks.
|
|
1111
|
+
|
|
1112
|
+
That ref has ZERO required status checks, so the one thing standing between the
|
|
1113
|
+
move and a permission-free gate bypass is whether the PR is currently failing —
|
|
1114
|
+
and the probe returned no complete `statusCheckRollup`. An absent or truncated
|
|
1115
|
+
rollup is not an empty one. Reporting the move as the sanctioned green-PR
|
|
1116
|
+
batching case here would claim a result the probe did not establish.
|
|
1117
|
+
|
|
1118
|
+
Re-target through `gh pr edit --base` instead, which reads the checks, or fix
|
|
1119
|
+
the probe. Do not report the PR as batched until something has actually looked.
|
|
1120
|
+
"""
|
|
1121
|
+
|
|
1122
|
+
|
|
1123
|
+
UNCOVERED_RETARGET_NOTICE = (
|
|
1124
|
+
"block-blind-automerge: %s is being re-targeted onto \"%s\", a ref with ZERO "
|
|
1125
|
+
"required status checks; its gates will run but cannot block a merge\n"
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
def degraded(question, failure):
|
|
1130
|
+
"""Announce that the guard could not measure, rather than passing silently.
|
|
1131
|
+
|
|
1132
|
+
Args:
|
|
1133
|
+
question: What could not be read.
|
|
1134
|
+
failure: Why not.
|
|
1135
|
+
"""
|
|
1136
|
+
sys.stderr.write(
|
|
1137
|
+
"block-blind-automerge: could not read %s (%s); auto-merge protection "
|
|
1138
|
+
"is NOT active for this command\n" % (question, failure)
|
|
1139
|
+
)
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
def base_is_uncovered(payload, repo_args, base_ref):
|
|
1143
|
+
"""Whether a ref has zero required status checks, read live from GitHub.
|
|
1144
|
+
|
|
1145
|
+
Args:
|
|
1146
|
+
payload: The decoded pull request object.
|
|
1147
|
+
repo_args: gh arguments naming the repository, possibly empty.
|
|
1148
|
+
base_ref: The ref to ask about.
|
|
1149
|
+
|
|
1150
|
+
Returns:
|
|
1151
|
+
True only when GitHub answered and the answer was zero. Every
|
|
1152
|
+
unanswerable case returns False and announces itself, because a guard
|
|
1153
|
+
that is silently absent reads exactly like a guard that is passing.
|
|
1154
|
+
"""
|
|
1155
|
+
if not base_ref:
|
|
1156
|
+
degraded("the base branch", "the PR payload named none")
|
|
1157
|
+
return False
|
|
1158
|
+
repo = repo_slug(payload, repo_args)
|
|
1159
|
+
if repo is None:
|
|
1160
|
+
degraded("the repository", "neither the PR URL nor --repo named one")
|
|
1161
|
+
return False
|
|
1162
|
+
rules, failure = branch_rules(repo, base_ref)
|
|
1163
|
+
if failure is not None:
|
|
1164
|
+
degraded("the branch rules for %s" % base_ref, failure)
|
|
1165
|
+
return False
|
|
1166
|
+
return required_context_count(rules) == 0
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
acts = guarded_invocations(strip_heredocs(command))
|
|
1170
|
+
if not acts:
|
|
1171
|
+
sys.exit(0)
|
|
1172
|
+
|
|
1173
|
+
for act, kind, subject, repo_args, target_ref in acts:
|
|
1174
|
+
if kind == PROBE_UNREADABLE:
|
|
1175
|
+
# Fails CLOSED, unlike every other unanswerable case below. Those are
|
|
1176
|
+
# the guard being unable to run; this is the command refusing to say
|
|
1177
|
+
# what it acts on, which is a property of the command.
|
|
1178
|
+
sys.stderr.write(UNREADABLE_REFUSAL)
|
|
1179
|
+
sys.exit(1)
|
|
1180
|
+
fields = ARM_FIELDS if act == ACT_ARM else RETARGET_FIELDS
|
|
1181
|
+
payload, failure = pull_request_state(kind, subject, repo_args, fields)
|
|
1182
|
+
if failure is not None:
|
|
1183
|
+
degraded(
|
|
1184
|
+
"reviewDecision" if act == ACT_ARM else "the pull request", failure
|
|
1185
|
+
)
|
|
1186
|
+
continue
|
|
1187
|
+
if act == ACT_RETARGET:
|
|
1188
|
+
# The current base is deliberately NOT consulted. The property is "a PR
|
|
1189
|
+
# something is currently failing must not be moved onto a ref where
|
|
1190
|
+
# nothing can block it", which holds whatever base it came from.
|
|
1191
|
+
if not base_is_uncovered(payload, repo_args, target_ref):
|
|
1192
|
+
continue
|
|
1193
|
+
# Fails CLOSED, and only here. Everywhere else in this file an
|
|
1194
|
+
# unanswerable question degrades and continues, because the guard being
|
|
1195
|
+
# unable to run must not become the reason a command is refused. This
|
|
1196
|
+
# branch is different: the base has already been confirmed to have zero
|
|
1197
|
+
# required checks, so an empty `failing` list is the whole basis for
|
|
1198
|
+
# allowing the move, and an ABSENT rollup produces the identical empty
|
|
1199
|
+
# list. Passing on it would be a measurement that could not fail.
|
|
1200
|
+
if not isinstance(payload.get("statusCheckRollup"), list):
|
|
1201
|
+
sys.stderr.write(
|
|
1202
|
+
RETARGET_CHECKS_UNREADABLE % (pr_name(payload), target_ref)
|
|
1203
|
+
)
|
|
1204
|
+
sys.exit(1)
|
|
1205
|
+
failing = failing_check_names(payload)
|
|
1206
|
+
if not failing:
|
|
1207
|
+
# A green PR moving onto a stack base is the sanctioned batching
|
|
1208
|
+
# workflow. Announced, not refused — a guard that refused it would
|
|
1209
|
+
# be switched off within a day.
|
|
1210
|
+
sys.stderr.write(
|
|
1211
|
+
UNCOVERED_RETARGET_NOTICE % (pr_name(payload), target_ref)
|
|
1212
|
+
)
|
|
1213
|
+
continue
|
|
1214
|
+
sys.stderr.write(
|
|
1215
|
+
UNCOVERED_RETARGET_REFUSAL
|
|
1216
|
+
% (
|
|
1217
|
+
pr_name(payload),
|
|
1218
|
+
target_ref,
|
|
1219
|
+
"\n".join(" - %s" % name for name in failing),
|
|
1220
|
+
)
|
|
1221
|
+
)
|
|
1222
|
+
sys.exit(1)
|
|
1223
|
+
if (payload.get("reviewDecision") or "") == BLOCKING_REVIEW_DECISION:
|
|
1224
|
+
sys.stderr.write(REFUSAL % pr_name(payload))
|
|
1225
|
+
sys.exit(1)
|
|
1226
|
+
base_ref = payload.get("baseRefName")
|
|
1227
|
+
if not base_is_uncovered(payload, repo_args, base_ref):
|
|
1228
|
+
continue
|
|
1229
|
+
selector = subject if subject is not None else str(payload.get("number") or "")
|
|
1230
|
+
sys.stderr.write(
|
|
1231
|
+
UNCOVERED_ARM_REFUSAL
|
|
1232
|
+
% (pr_name(payload), base_ref, selector, selector)
|
|
1233
|
+
)
|
|
1234
|
+
sys.exit(1)
|
|
1235
|
+
|
|
1236
|
+
sys.exit(0)
|
|
1237
|
+
PY
|
|
1238
|
+
then
|
|
1239
|
+
exit 2
|
|
1240
|
+
fi
|
|
1241
|
+
|
|
1242
|
+
exit 0
|