@codyswann/lisa 4.54.13 → 4.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all/copy-contents/.gitattributes +8 -0
- package/all/copy-overwrite/scripts/check-orphaned-branches.mjs +78 -9
- package/all/copy-overwrite/scripts/check-third-party-action-pins.mjs +17 -1
- package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +194 -7
- package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +31 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-blind-automerge.sh +81 -2
- package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +293 -8
- package/all/copy-overwrite/scripts/lisa-hooks/block-instruction-file-edits.sh +21 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-managed-file-edits.sh +78 -15
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +21 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-shell-json-parsing.sh +21 -0
- package/all/copy-overwrite/scripts/lisa-hooks/guard-dedupe.bash +340 -0
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +29 -1
- package/all/copy-overwrite/scripts/lisa-hooks/worktree-binding-guard.mjs +183 -1
- package/all/copy-overwrite/scripts/lisa-hooks/worktree-binding-guard.sh +21 -0
- package/all/copy-overwrite/scripts/lisa-postinstall.mjs +21 -1
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +100 -5
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +1085 -80
- package/all/create-only/.github/workflows/continuous-gates.yml +7 -0
- package/cdk/create-only/.github/workflows/ci.yml +7 -0
- package/cdk/create-only/.github/workflows/deploy.yml +21 -0
- package/dist/cli/apply.d.ts.map +1 -1
- package/dist/cli/apply.js +9 -1
- package/dist/cli/apply.js.map +1 -1
- package/dist/cli/doctor-apply-deletions.d.ts +61 -0
- package/dist/cli/doctor-apply-deletions.d.ts.map +1 -0
- package/dist/cli/doctor-apply-deletions.js +137 -0
- package/dist/cli/doctor-apply-deletions.js.map +1 -0
- package/dist/cli/doctor-cdk-preset-adoption.d.ts +15 -0
- package/dist/cli/doctor-cdk-preset-adoption.d.ts.map +1 -0
- package/dist/cli/doctor-cdk-preset-adoption.js +316 -0
- package/dist/cli/doctor-cdk-preset-adoption.js.map +1 -0
- package/dist/cli/doctor-config-shadowing.d.ts +22 -0
- package/dist/cli/doctor-config-shadowing.d.ts.map +1 -0
- package/dist/cli/doctor-config-shadowing.js +123 -0
- package/dist/cli/doctor-config-shadowing.js.map +1 -0
- package/dist/cli/doctor-rails-deploy-intent.d.ts +15 -0
- package/dist/cli/doctor-rails-deploy-intent.d.ts.map +1 -0
- package/dist/cli/doctor-rails-deploy-intent.js +197 -0
- package/dist/cli/doctor-rails-deploy-intent.js.map +1 -0
- package/dist/cli/doctor-seeded-artifacts.d.ts +12 -0
- package/dist/cli/doctor-seeded-artifacts.d.ts.map +1 -0
- package/dist/cli/doctor-seeded-artifacts.js +54 -0
- package/dist/cli/doctor-seeded-artifacts.js.map +1 -0
- package/dist/cli/doctor-stale-banner-scan.d.ts +44 -0
- package/dist/cli/doctor-stale-banner-scan.d.ts.map +1 -0
- package/dist/cli/doctor-stale-banner-scan.js +152 -0
- package/dist/cli/doctor-stale-banner-scan.js.map +1 -0
- package/dist/cli/doctor-stale-managed-banner.d.ts +19 -0
- package/dist/cli/doctor-stale-managed-banner.d.ts.map +1 -0
- package/dist/cli/doctor-stale-managed-banner.js +198 -0
- package/dist/cli/doctor-stale-managed-banner.js.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +16 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/ui-detected-stacks.d.ts.map +1 -1
- package/dist/cli/ui-detected-stacks.js +3 -2
- package/dist/cli/ui-detected-stacks.js.map +1 -1
- package/dist/cli/update-check.d.ts.map +1 -1
- package/dist/cli/update-check.js +14 -0
- package/dist/cli/update-check.js.map +1 -1
- package/dist/cli/worktree-liveness.d.ts +5 -0
- package/dist/cli/worktree-liveness.d.ts.map +1 -1
- package/dist/cli/worktree-liveness.js +7 -6
- package/dist/cli/worktree-liveness.js.map +1 -1
- package/dist/cli/worktree-ownership.d.ts +13 -0
- package/dist/cli/worktree-ownership.d.ts.map +1 -1
- package/dist/cli/worktree-ownership.js +14 -0
- package/dist/cli/worktree-ownership.js.map +1 -1
- package/dist/configs/eslint/base.d.ts +34 -0
- package/dist/configs/eslint/base.d.ts.map +1 -1
- package/dist/configs/eslint/base.js +34 -0
- package/dist/configs/eslint/base.js.map +1 -1
- package/dist/core/apply-receipt.d.ts +65 -0
- package/dist/core/apply-receipt.d.ts.map +1 -1
- package/dist/core/apply-receipt.js +62 -2
- package/dist/core/apply-receipt.js.map +1 -1
- package/dist/core/cdk-preset-adoption.d.ts +136 -0
- package/dist/core/cdk-preset-adoption.d.ts.map +1 -0
- package/dist/core/cdk-preset-adoption.js +135 -0
- package/dist/core/cdk-preset-adoption.js.map +1 -0
- package/dist/core/config-shadowing.d.ts +36 -0
- package/dist/core/config-shadowing.d.ts.map +1 -1
- package/dist/core/config-shadowing.js +38 -0
- package/dist/core/config-shadowing.js.map +1 -1
- package/dist/core/config.d.ts +11 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/learnings-merge-driver.d.ts +10 -0
- package/dist/core/learnings-merge-driver.d.ts.map +1 -1
- package/dist/core/learnings-merge-driver.js +18 -0
- package/dist/core/learnings-merge-driver.js.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +136 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +9 -1
- package/dist/core/lisa.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts +5 -5
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +7 -7
- package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
- package/dist/core/ownership-header.d.ts +74 -0
- package/dist/core/ownership-header.d.ts.map +1 -0
- package/dist/core/ownership-header.js +76 -0
- package/dist/core/ownership-header.js.map +1 -0
- package/dist/core/rails-deploy-production-intent.d.ts +36 -0
- package/dist/core/rails-deploy-production-intent.d.ts.map +1 -0
- package/dist/core/rails-deploy-production-intent.js +79 -0
- package/dist/core/rails-deploy-production-intent.js.map +1 -0
- package/dist/core/stale-managed-banner.d.ts +78 -0
- package/dist/core/stale-managed-banner.d.ts.map +1 -0
- package/dist/core/stale-managed-banner.js +118 -0
- package/dist/core/stale-managed-banner.js.map +1 -0
- package/dist/core/two-channel-delivery-scan.d.ts +33 -1
- package/dist/core/two-channel-delivery-scan.d.ts.map +1 -1
- package/dist/core/two-channel-delivery-scan.js +133 -6
- package/dist/core/two-channel-delivery-scan.js.map +1 -1
- package/dist/core/two-channel-delivery.d.ts +101 -4
- package/dist/core/two-channel-delivery.d.ts.map +1 -1
- package/dist/core/two-channel-delivery.js +121 -13
- package/dist/core/two-channel-delivery.js.map +1 -1
- package/dist/core/two-channel-staleness.d.ts +137 -0
- package/dist/core/two-channel-staleness.d.ts.map +1 -0
- package/dist/core/two-channel-staleness.js +268 -0
- package/dist/core/two-channel-staleness.js.map +1 -0
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +265 -121
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/core/workflow-deletion-ownership.d.ts +6 -24
- package/dist/core/workflow-deletion-ownership.d.ts.map +1 -1
- package/dist/core/workflow-deletion-ownership.js +22 -42
- package/dist/core/workflow-deletion-ownership.js.map +1 -1
- package/dist/detection/detectors/cdk.d.ts.map +1 -1
- package/dist/detection/detectors/cdk.js +2 -1
- package/dist/detection/detectors/cdk.js.map +1 -1
- package/dist/health/template-inspection.d.ts.map +1 -1
- package/dist/health/template-inspection.js +2 -1
- package/dist/health/template-inspection.js.map +1 -1
- package/dist/migrations/ensure-lisa-postinstall.d.ts +16 -0
- package/dist/migrations/ensure-lisa-postinstall.d.ts.map +1 -1
- package/dist/migrations/ensure-lisa-postinstall.js +16 -0
- package/dist/migrations/ensure-lisa-postinstall.js.map +1 -1
- package/dist/migrations/ensure-pinned-reusable-workflow-refs.d.ts +2 -2
- package/dist/migrations/ensure-pinned-reusable-workflow-refs.d.ts.map +1 -1
- package/dist/migrations/ensure-pinned-reusable-workflow-refs.js +98 -34
- package/dist/migrations/ensure-pinned-reusable-workflow-refs.js.map +1 -1
- package/dist/migrations/ensure-playwright-dedicated-caller.d.ts.map +1 -1
- package/dist/migrations/ensure-playwright-dedicated-caller.js +1 -0
- package/dist/migrations/ensure-playwright-dedicated-caller.js.map +1 -1
- package/dist/opencode/hooks-installer.d.ts.map +1 -1
- package/dist/opencode/hooks-installer.js +1 -0
- package/dist/opencode/hooks-installer.js.map +1 -1
- package/dist/opencode/plugin-templates/block-managed-file-edits.sh +78 -15
- package/dist/opencode/plugin-templates/block-no-verify.sh +21 -0
- package/dist/opencode/plugin-templates/guard-dedupe.bash +337 -0
- package/dist/opencode/plugin-templates/lisa-block-direct-issue-create.ts +242 -55
- package/dist/opencode/plugin-templates/parity-safety-net.sh +29 -1
- package/dist/strategies/package-lisa.d.ts.map +1 -1
- package/dist/strategies/package-lisa.js +2 -1
- package/dist/strategies/package-lisa.js.map +1 -1
- package/dist/utils/path-utils.d.ts +29 -0
- package/dist/utils/path-utils.d.ts.map +1 -1
- package/dist/utils/path-utils.js +41 -0
- package/dist/utils/path-utils.js.map +1 -1
- package/expo/create-only/.github/workflows/ci.yml +21 -0
- package/expo/create-only/.github/workflows/deploy.yml +28 -0
- package/expo/create-only/.github/workflows/maestro-e2e.yml +7 -0
- package/expo/create-only/.github/workflows/nightly-e2e-bypass-reaper.yml +124 -13
- package/expo/create-only/.github/workflows/nightly-e2e-health.yml +7 -0
- package/expo/create-only/.github/workflows/nightly-e2e-report.yml +7 -0
- package/expo/create-only/.github/workflows/nightly-e2e-tracking.yml +8 -0
- package/expo/create-only/.github/workflows/playwright-e2e.yml +7 -0
- package/harper-fabric/copy-overwrite/.github/workflows/ci.yml +7 -0
- package/harper-fabric/create-only/.github/workflows/deploy.yml +14 -0
- package/nestjs/create-only/.github/workflows/ci.yml +14 -0
- package/nestjs/create-only/.github/workflows/deploy.yml +28 -0
- package/package.json +10 -5
- package/phaser/copy-overwrite/.github/workflows/ci.yml +7 -0
- package/plugins/lisa/.claude-plugin/plugin.json +28 -1
- package/plugins/lisa/.codex-plugin/hooks.json +27 -0
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
- package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +27 -4
- package/plugins/lisa/.codex-plugin/skills/lisa-github-prd-intake/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-github-validate-issue/SKILL.md +22 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-verify/SKILL.md +14 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-write-issue/SKILL.md +51 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-intake/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +27 -4
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-verify/SKILL.md +14 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-write-ticket/SKILL.md +51 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +55 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +27 -4
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-prd-intake/SKILL.md +47 -30
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-to-tracker/SKILL.md +3 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-validate-issue/SKILL.md +22 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-verify/SKILL.md +14 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-write-issue/SKILL.md +54 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-qa-clear/SKILL.md +19 -7
- package/plugins/lisa/.codex-plugin/skills/lisa-qa-fail/SKILL.md +28 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-qa-queue/SKILL.md +28 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +94 -24
- package/plugins/lisa/.codex-plugin/skills/lisa-rework-triage/SKILL.md +32 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-linear/SKILL.md +4 -4
- package/plugins/lisa/.codex-plugin/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-build-intake/SKILL.md +9 -0
- package/plugins/lisa/agents/linear-prd-intake.md +8 -8
- package/plugins/lisa/hooks/block-blind-automerge.sh +81 -2
- package/plugins/lisa/hooks/block-direct-issue-create.agy.sh +64 -16
- package/plugins/lisa/hooks/block-direct-issue-create.sh +293 -8
- package/plugins/lisa/hooks/block-instruction-file-edits.sh +21 -0
- package/plugins/lisa/hooks/block-managed-file-edits.sh +78 -15
- package/plugins/lisa/hooks/block-no-verify.sh +21 -0
- package/plugins/lisa/hooks/block-shell-json-parsing.sh +21 -0
- package/plugins/lisa/hooks/failure-signature-index.mjs +240 -5
- package/plugins/lisa/hooks/guard-dedupe.bash +337 -0
- package/plugins/lisa/hooks/inject-rules.sh +13 -5
- package/plugins/lisa/hooks/operational-hazards.mjs +736 -0
- package/plugins/lisa/hooks/operational-hazards.sh +28 -0
- package/plugins/lisa/hooks/parity-safety-net.sh +29 -1
- package/plugins/lisa/hooks/threshold-ratchet-compare.mjs +102 -21
- package/plugins/lisa/hooks/threshold-ratchet-families.mjs +130 -2
- package/plugins/lisa/hooks/threshold-ratchet.mjs +63 -13
- package/plugins/lisa/hooks/worktree-binding-guard.mjs +183 -1
- package/plugins/lisa/hooks/worktree-binding-guard.sh +21 -0
- package/plugins/lisa/rules/eager/00-rule-index.md +1 -0
- package/plugins/lisa/rules/eager/operational-hazards.md +50 -0
- package/plugins/lisa/rules/eager/tracked-work.md +1 -1
- package/plugins/lisa/rules/reference/config-resolution.md +1 -1
- package/plugins/lisa/rules/reference/leaf-only-lifecycle.md +1 -1
- package/plugins/lisa/rules/reference/operational-hazards.md +115 -0
- package/plugins/lisa/rules/reference/work-item-trailer-definition.md +129 -0
- package/plugins/lisa/scripts/intake-blocker-reprobe.mjs +407 -26
- package/plugins/lisa/scripts/qa-signal-lifecycle.mjs +507 -0
- package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +27 -4
- package/plugins/lisa/skills/lisa-github-prd-intake/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-github-validate-issue/SKILL.md +22 -0
- package/plugins/lisa/skills/lisa-github-verify/SKILL.md +14 -0
- package/plugins/lisa/skills/lisa-github-write-issue/SKILL.md +51 -0
- package/plugins/lisa/skills/lisa-intake/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +27 -4
- package/plugins/lisa/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
- package/plugins/lisa/skills/lisa-jira-verify/SKILL.md +14 -0
- package/plugins/lisa/skills/lisa-jira-write-ticket/SKILL.md +51 -0
- package/plugins/lisa/skills/lisa-linear-access/SKILL.md +55 -2
- package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +27 -4
- package/plugins/lisa/skills/lisa-linear-prd-intake/SKILL.md +48 -31
- package/plugins/lisa/skills/lisa-linear-to-tracker/SKILL.md +3 -3
- package/plugins/lisa/skills/lisa-linear-validate-issue/SKILL.md +22 -0
- package/plugins/lisa/skills/lisa-linear-verify/SKILL.md +14 -0
- package/plugins/lisa/skills/lisa-linear-write-issue/SKILL.md +54 -3
- package/plugins/lisa/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
- package/plugins/lisa/skills/lisa-qa-clear/SKILL.md +19 -7
- package/plugins/lisa/skills/lisa-qa-fail/SKILL.md +28 -3
- package/plugins/lisa/skills/lisa-qa-queue/SKILL.md +28 -3
- package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +94 -24
- package/plugins/lisa/skills/lisa-rework-triage/SKILL.md +32 -2
- package/plugins/lisa/skills/lisa-setup-linear/SKILL.md +5 -5
- package/plugins/lisa/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-tracker-build-intake/SKILL.md +9 -0
- package/plugins/lisa-agy/agents/linear-prd-intake.md +8 -8
- package/plugins/lisa-agy/hooks/block-blind-automerge.sh +81 -2
- package/plugins/lisa-agy/hooks/block-direct-issue-create.agy.sh +64 -16
- package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +293 -8
- package/plugins/lisa-agy/hooks/block-instruction-file-edits.sh +21 -0
- package/plugins/lisa-agy/hooks/block-managed-file-edits.sh +78 -15
- package/plugins/lisa-agy/hooks/block-shell-json-parsing.sh +21 -0
- package/plugins/lisa-agy/hooks/guard-dedupe.bash +337 -0
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +29 -1
- package/plugins/lisa-agy/hooks.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/intake-blocker-reprobe.mjs +407 -26
- package/plugins/lisa-agy/scripts/qa-signal-lifecycle.mjs +507 -0
- package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +27 -4
- package/plugins/lisa-agy/skills/lisa-github-prd-intake/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-github-validate-issue/SKILL.md +22 -0
- package/plugins/lisa-agy/skills/lisa-github-verify/SKILL.md +14 -0
- package/plugins/lisa-agy/skills/lisa-github-write-issue/SKILL.md +51 -0
- package/plugins/lisa-agy/skills/lisa-intake/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +27 -4
- package/plugins/lisa-agy/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
- package/plugins/lisa-agy/skills/lisa-jira-verify/SKILL.md +14 -0
- package/plugins/lisa-agy/skills/lisa-jira-write-ticket/SKILL.md +51 -0
- package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +55 -2
- package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +27 -4
- package/plugins/lisa-agy/skills/lisa-linear-prd-intake/SKILL.md +48 -31
- package/plugins/lisa-agy/skills/lisa-linear-to-tracker/SKILL.md +3 -3
- package/plugins/lisa-agy/skills/lisa-linear-validate-issue/SKILL.md +22 -0
- package/plugins/lisa-agy/skills/lisa-linear-verify/SKILL.md +14 -0
- package/plugins/lisa-agy/skills/lisa-linear-write-issue/SKILL.md +54 -3
- package/plugins/lisa-agy/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
- package/plugins/lisa-agy/skills/lisa-qa-clear/SKILL.md +19 -7
- package/plugins/lisa-agy/skills/lisa-qa-fail/SKILL.md +28 -3
- package/plugins/lisa-agy/skills/lisa-qa-queue/SKILL.md +28 -3
- package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +94 -24
- package/plugins/lisa-agy/skills/lisa-rework-triage/SKILL.md +32 -2
- package/plugins/lisa-agy/skills/lisa-setup-linear/SKILL.md +5 -5
- package/plugins/lisa-agy/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-tracker-build-intake/SKILL.md +9 -0
- 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-copilot/.claude-plugin/plugin.json +19 -1
- package/plugins/lisa-copilot/agents/linear-prd-intake.agent.md +8 -8
- package/plugins/lisa-copilot/hooks/block-blind-automerge.sh +81 -2
- package/plugins/lisa-copilot/hooks/block-direct-issue-create.sh +293 -8
- package/plugins/lisa-copilot/hooks/block-instruction-file-edits.sh +21 -0
- package/plugins/lisa-copilot/hooks/block-managed-file-edits.sh +78 -15
- package/plugins/lisa-copilot/hooks/block-no-verify.sh +21 -0
- package/plugins/lisa-copilot/hooks/block-shell-json-parsing.sh +21 -0
- package/plugins/lisa-copilot/hooks/failure-signature-index.mjs +240 -5
- package/plugins/lisa-copilot/hooks/guard-dedupe.bash +337 -0
- package/plugins/lisa-copilot/hooks/inject-rules.sh +13 -5
- package/plugins/lisa-copilot/hooks/operational-hazards.mjs +736 -0
- package/plugins/lisa-copilot/hooks/operational-hazards.sh +28 -0
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +29 -1
- package/plugins/lisa-copilot/hooks/threshold-ratchet-compare.mjs +102 -21
- package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +130 -2
- package/plugins/lisa-copilot/hooks/threshold-ratchet.mjs +63 -13
- package/plugins/lisa-copilot/hooks/worktree-binding-guard.mjs +183 -1
- package/plugins/lisa-copilot/hooks/worktree-binding-guard.sh +21 -0
- package/plugins/lisa-copilot/rules/eager/00-rule-index.md +1 -0
- package/plugins/lisa-copilot/rules/eager/operational-hazards.md +50 -0
- package/plugins/lisa-copilot/rules/eager/tracked-work.md +1 -1
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +1 -1
- package/plugins/lisa-copilot/rules/reference/leaf-only-lifecycle.md +1 -1
- package/plugins/lisa-copilot/rules/reference/operational-hazards.md +115 -0
- package/plugins/lisa-copilot/rules/reference/work-item-trailer-definition.md +129 -0
- package/plugins/lisa-copilot/scripts/intake-blocker-reprobe.mjs +407 -26
- package/plugins/lisa-copilot/scripts/qa-signal-lifecycle.mjs +507 -0
- package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +27 -4
- package/plugins/lisa-copilot/skills/lisa-github-prd-intake/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-github-validate-issue/SKILL.md +22 -0
- package/plugins/lisa-copilot/skills/lisa-github-verify/SKILL.md +14 -0
- package/plugins/lisa-copilot/skills/lisa-github-write-issue/SKILL.md +51 -0
- package/plugins/lisa-copilot/skills/lisa-intake/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +27 -4
- package/plugins/lisa-copilot/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
- package/plugins/lisa-copilot/skills/lisa-jira-verify/SKILL.md +14 -0
- package/plugins/lisa-copilot/skills/lisa-jira-write-ticket/SKILL.md +51 -0
- package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +55 -2
- package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +27 -4
- package/plugins/lisa-copilot/skills/lisa-linear-prd-intake/SKILL.md +48 -31
- package/plugins/lisa-copilot/skills/lisa-linear-to-tracker/SKILL.md +3 -3
- package/plugins/lisa-copilot/skills/lisa-linear-validate-issue/SKILL.md +22 -0
- package/plugins/lisa-copilot/skills/lisa-linear-verify/SKILL.md +14 -0
- package/plugins/lisa-copilot/skills/lisa-linear-write-issue/SKILL.md +54 -3
- package/plugins/lisa-copilot/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
- package/plugins/lisa-copilot/skills/lisa-qa-clear/SKILL.md +19 -7
- package/plugins/lisa-copilot/skills/lisa-qa-fail/SKILL.md +28 -3
- package/plugins/lisa-copilot/skills/lisa-qa-queue/SKILL.md +28 -3
- package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +94 -24
- package/plugins/lisa-copilot/skills/lisa-rework-triage/SKILL.md +32 -2
- package/plugins/lisa-copilot/skills/lisa-setup-linear/SKILL.md +5 -5
- package/plugins/lisa-copilot/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-tracker-build-intake/SKILL.md +9 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/agents/linear-prd-intake.md +8 -8
- package/plugins/lisa-cursor/hooks/block-blind-automerge.sh +81 -2
- package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +293 -8
- package/plugins/lisa-cursor/hooks/block-instruction-file-edits.sh +21 -0
- package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +78 -15
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +21 -0
- package/plugins/lisa-cursor/hooks/block-shell-json-parsing.sh +21 -0
- package/plugins/lisa-cursor/hooks/failure-signature-index.mjs +240 -5
- package/plugins/lisa-cursor/hooks/guard-dedupe.bash +337 -0
- package/plugins/lisa-cursor/hooks/hooks.json +10 -0
- package/plugins/lisa-cursor/hooks/operational-hazards.mjs +736 -0
- package/plugins/lisa-cursor/hooks/operational-hazards.sh +28 -0
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +29 -1
- package/plugins/lisa-cursor/hooks/threshold-ratchet-compare.mjs +102 -21
- package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +130 -2
- package/plugins/lisa-cursor/hooks/threshold-ratchet.mjs +63 -13
- package/plugins/lisa-cursor/hooks/worktree-binding-guard.mjs +183 -1
- package/plugins/lisa-cursor/hooks/worktree-binding-guard.sh +21 -0
- package/plugins/lisa-cursor/rules/00-rule-index.mdc +1 -0
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +1 -1
- package/plugins/lisa-cursor/rules/leaf-only-lifecycle-reference.mdc +1 -1
- package/plugins/lisa-cursor/rules/operational-hazards-reference.mdc +120 -0
- package/plugins/lisa-cursor/rules/operational-hazards.mdc +55 -0
- package/plugins/lisa-cursor/rules/tracked-work.mdc +1 -1
- package/plugins/lisa-cursor/rules/work-item-trailer-definition-reference.mdc +134 -0
- package/plugins/lisa-cursor/scripts/intake-blocker-reprobe.mjs +407 -26
- package/plugins/lisa-cursor/scripts/qa-signal-lifecycle.mjs +507 -0
- package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +27 -4
- package/plugins/lisa-cursor/skills/lisa-github-prd-intake/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-github-validate-issue/SKILL.md +22 -0
- package/plugins/lisa-cursor/skills/lisa-github-verify/SKILL.md +14 -0
- package/plugins/lisa-cursor/skills/lisa-github-write-issue/SKILL.md +51 -0
- package/plugins/lisa-cursor/skills/lisa-intake/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +27 -4
- package/plugins/lisa-cursor/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
- package/plugins/lisa-cursor/skills/lisa-jira-verify/SKILL.md +14 -0
- package/plugins/lisa-cursor/skills/lisa-jira-write-ticket/SKILL.md +51 -0
- package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +55 -2
- package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +27 -4
- package/plugins/lisa-cursor/skills/lisa-linear-prd-intake/SKILL.md +48 -31
- package/plugins/lisa-cursor/skills/lisa-linear-to-tracker/SKILL.md +3 -3
- package/plugins/lisa-cursor/skills/lisa-linear-validate-issue/SKILL.md +22 -0
- package/plugins/lisa-cursor/skills/lisa-linear-verify/SKILL.md +14 -0
- package/plugins/lisa-cursor/skills/lisa-linear-write-issue/SKILL.md +54 -3
- package/plugins/lisa-cursor/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
- package/plugins/lisa-cursor/skills/lisa-qa-clear/SKILL.md +19 -7
- package/plugins/lisa-cursor/skills/lisa-qa-fail/SKILL.md +28 -3
- package/plugins/lisa-cursor/skills/lisa-qa-queue/SKILL.md +28 -3
- package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +94 -24
- package/plugins/lisa-cursor/skills/lisa-rework-triage/SKILL.md +32 -2
- package/plugins/lisa-cursor/skills/lisa-setup-linear/SKILL.md +5 -5
- package/plugins/lisa-cursor/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-tracker-build-intake/SKILL.md +9 -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-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/hooks/inject-rules.sh +14 -3
- package/plugins/lisa-harper-fabric/rules/eager/harper-fabric.md +31 -0
- 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-copilot/hooks/inject-rules.sh +14 -3
- package/plugins/lisa-harper-fabric-copilot/rules/eager/harper-fabric.md +31 -0
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/rules/harper-fabric-reference.mdc +57 -0
- package/plugins/lisa-harper-fabric-cursor/rules/harper-fabric.mdc +32 -53
- 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-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-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/hooks/inject-rules.sh +14 -3
- package/plugins/lisa-phaser/rules/eager/phaser.md +44 -0
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/hooks/inject-rules.sh +14 -3
- package/plugins/lisa-phaser-copilot/rules/eager/phaser.md +44 -0
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/rules/phaser-reference.mdc +189 -0
- package/plugins/lisa-phaser-cursor/rules/phaser.mdc +45 -185
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/hooks/inject-rules.sh +14 -4
- package/plugins/lisa-rails/rules/eager/rails-conventions.md +23 -0
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/hooks/inject-rules.sh +14 -4
- package/plugins/lisa-rails-copilot/rules/eager/rails-conventions.md +23 -0
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/rules/rails-conventions-reference.mdc +181 -0
- package/plugins/lisa-rails-cursor/rules/rails-conventions.mdc +24 -177
- 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-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/materialized-artifacts.json +1 -0
- package/plugins/src/base/.claude-plugin/plugin.json +27 -0
- package/plugins/src/base/agents/linear-prd-intake.md +8 -8
- package/plugins/src/base/hooks/block-blind-automerge.sh +81 -2
- package/plugins/src/base/hooks/block-direct-issue-create.agy.sh +64 -16
- package/plugins/src/base/hooks/block-direct-issue-create.sh +293 -8
- package/plugins/src/base/hooks/block-instruction-file-edits.sh +21 -0
- package/plugins/src/base/hooks/block-managed-file-edits.sh +78 -15
- package/plugins/src/base/hooks/block-no-verify.sh +21 -0
- package/plugins/src/base/hooks/block-shell-json-parsing.sh +21 -0
- package/plugins/src/base/hooks/failure-signature-index.mjs +240 -5
- package/plugins/src/base/hooks/guard-dedupe.bash +337 -0
- package/plugins/src/base/hooks/inject-rules.sh +13 -5
- package/plugins/src/base/hooks/operational-hazards.mjs +736 -0
- package/plugins/src/base/hooks/operational-hazards.sh +28 -0
- package/plugins/src/base/hooks/parity-safety-net.sh +29 -1
- package/plugins/src/base/hooks/threshold-ratchet-compare.mjs +102 -21
- package/plugins/src/base/hooks/threshold-ratchet-families.mjs +130 -2
- package/plugins/src/base/hooks/threshold-ratchet.mjs +63 -13
- package/plugins/src/base/hooks/worktree-binding-guard.mjs +183 -1
- package/plugins/src/base/hooks/worktree-binding-guard.sh +21 -0
- package/plugins/src/base/rules/eager/00-rule-index.md +1 -0
- package/plugins/src/base/rules/eager/operational-hazards.md +50 -0
- package/plugins/src/base/rules/eager/tracked-work.md +1 -1
- package/plugins/src/base/rules/reference/config-resolution.md +1 -1
- package/plugins/src/base/rules/reference/leaf-only-lifecycle.md +1 -1
- package/plugins/src/base/rules/reference/operational-hazards.md +115 -0
- package/plugins/src/base/rules/reference/work-item-trailer-definition.md +129 -0
- package/plugins/src/base/scripts/intake-blocker-reprobe.mjs +407 -26
- package/plugins/src/base/scripts/qa-signal-lifecycle.mjs +507 -0
- package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +27 -4
- package/plugins/src/base/skills/lisa-github-prd-intake/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-github-validate-issue/SKILL.md +22 -0
- package/plugins/src/base/skills/lisa-github-verify/SKILL.md +14 -0
- package/plugins/src/base/skills/lisa-github-write-issue/SKILL.md +51 -0
- package/plugins/src/base/skills/lisa-intake/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +27 -4
- package/plugins/src/base/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
- package/plugins/src/base/skills/lisa-jira-verify/SKILL.md +14 -0
- package/plugins/src/base/skills/lisa-jira-write-ticket/SKILL.md +51 -0
- package/plugins/src/base/skills/lisa-linear-access/SKILL.md +55 -2
- package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +27 -4
- package/plugins/src/base/skills/lisa-linear-prd-intake/SKILL.md +48 -31
- package/plugins/src/base/skills/lisa-linear-to-tracker/SKILL.md +3 -3
- package/plugins/src/base/skills/lisa-linear-validate-issue/SKILL.md +22 -0
- package/plugins/src/base/skills/lisa-linear-verify/SKILL.md +14 -0
- package/plugins/src/base/skills/lisa-linear-write-issue/SKILL.md +54 -3
- package/plugins/src/base/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
- package/plugins/src/base/skills/lisa-qa-clear/SKILL.md +19 -7
- package/plugins/src/base/skills/lisa-qa-fail/SKILL.md +28 -3
- package/plugins/src/base/skills/lisa-qa-queue/SKILL.md +28 -3
- package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +94 -24
- package/plugins/src/base/skills/lisa-rework-triage/SKILL.md +32 -2
- package/plugins/src/base/skills/lisa-setup-linear/SKILL.md +5 -5
- package/plugins/src/base/skills/lisa-track/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-tracker-build-intake/SKILL.md +9 -0
- package/plugins/src/harper-fabric/hooks/inject-rules.sh +14 -3
- package/plugins/src/harper-fabric/rules/eager/harper-fabric.md +31 -0
- package/plugins/src/phaser/hooks/inject-rules.sh +14 -3
- package/plugins/src/phaser/rules/eager/phaser.md +44 -0
- package/plugins/src/rails/hooks/inject-rules.sh +14 -4
- package/plugins/src/rails/rules/eager/rails-conventions.md +23 -0
- package/rails/copy-overwrite/scripts/check-threshold-ratchet.mjs +63 -13
- package/rails/copy-overwrite/scripts/threshold-ratchet-compare.mjs +102 -21
- package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +130 -2
- package/rails/create-only/.github/workflows/ci.yml +8 -0
- package/rails/create-only/.github/workflows/deploy.yml +22 -0
- package/scripts/build-plugins.sh +2 -1
- package/scripts/check-engine-floor.mjs +361 -0
- package/scripts/check-guard-parity-notes.mjs +709 -0
- package/scripts/check-template-workflow-refs.mjs +9 -0
- package/scripts/check-third-party-action-pins.mjs +17 -1
- package/scripts/check-workflow-contract-assertions.mjs +480 -0
- package/scripts/copy-opencode-plugin-templates.mjs +1 -0
- package/scripts/deployed-guard-advice.mjs +636 -0
- package/scripts/generate-agy-plugin-artifacts.mjs +35 -10
- package/scripts/generate-export-surface.mjs +105 -19
- package/scripts/generate-two-channel-couplings.ts +143 -212
- package/scripts/lisa-enforcement-fallback.sh +31 -0
- package/scripts/measure-scripts-profile-gap.mts +361 -0
- package/scripts/mutation-performance-measure.mjs +21 -5
- package/scripts/reconcile-release-tags.mjs +65 -84
- package/scripts/two-channel-couplings.json +302 -70
- package/scripts/workflow-contract-assertion.sh +47 -0
- package/typescript/copy-overwrite/.lintstagedrc.json +1 -0
- package/typescript/copy-overwrite/eslint.ignore.config.json +1 -0
- package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +527 -53
- package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +301 -13
- package/typescript/copy-overwrite/scripts/check-threshold-ratchet.mjs +63 -13
- package/typescript/copy-overwrite/scripts/threshold-ratchet-compare.mjs +102 -21
- package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +130 -2
- package/typescript/create-only/.github/workflows/ci.yml +7 -0
- /package/plugins/lisa-harper-fabric/rules/{harper-fabric.md → reference/harper-fabric.md} +0 -0
- /package/plugins/lisa-harper-fabric-copilot/rules/{harper-fabric.md → reference/harper-fabric.md} +0 -0
- /package/plugins/lisa-phaser/rules/{phaser.md → reference/phaser.md} +0 -0
- /package/plugins/lisa-phaser-copilot/rules/{phaser.md → reference/phaser.md} +0 -0
- /package/plugins/lisa-rails/rules/{rails-conventions.md → reference/rails-conventions.md} +0 -0
- /package/plugins/lisa-rails-copilot/rules/{rails-conventions.md → reference/rails-conventions.md} +0 -0
- /package/plugins/src/harper-fabric/rules/{harper-fabric.md → reference/harper-fabric.md} +0 -0
- /package/plugins/src/phaser/rules/{phaser.md → reference/phaser.md} +0 -0
- /package/plugins/src/rails/rules/{rails-conventions.md → reference/rails-conventions.md} +0 -0
|
@@ -1529,6 +1529,37 @@ export function resolveSuiteGrace(suite, maxDays, now) {
|
|
|
1529
1529
|
// 4. Bypass — maintainers only, reason required, auto-expiring, self-service
|
|
1530
1530
|
// ---------------------------------------------------------------------------
|
|
1531
1531
|
|
|
1532
|
+
/**
|
|
1533
|
+
* Reads the `Nightly-E2E-Bypass: <TICKET> <reason>` trailer out of a body.
|
|
1534
|
+
*
|
|
1535
|
+
* Split out because the trailer is read by three consumers that MUST agree:
|
|
1536
|
+
* `evaluateBypass` (does this waiver qualify?), the merge-time re-derivation
|
|
1537
|
+
* (does it still qualify NOW?), and the durable record (what did it say?). The
|
|
1538
|
+
* third one is why this returns the trailer independently of validity — an
|
|
1539
|
+
* EXPIRED waiver still has a ticket and a reason, and a record that omitted
|
|
1540
|
+
* them because the waiver had lapsed would answer "what shipped on a waiver,
|
|
1541
|
+
* and why" with silence for exactly the merges most worth reading about.
|
|
1542
|
+
*
|
|
1543
|
+
* The body is read with fenced code blanked (`stripMarkdownCode`): a waiver is
|
|
1544
|
+
* prose that ASSERTS something, and a code sample never is. The strip lives
|
|
1545
|
+
* HERE rather than at one call site precisely because there are three of them
|
|
1546
|
+
* — a pull request that merely DOCUMENTS the trailer format would otherwise
|
|
1547
|
+
* waive its own gate through whichever consumer had not been fixed.
|
|
1548
|
+
*
|
|
1549
|
+
* @param {string|null|undefined} prBody - The pull request body
|
|
1550
|
+
* @returns {{ticket: string, reason: string|null}|null} The trailer, or `null` when absent
|
|
1551
|
+
*/
|
|
1552
|
+
export function parseWaiverTrailer(prBody) {
|
|
1553
|
+
const found = new RegExp(REQUIRED_BYPASS_REASON_PATTERN, "m").exec(
|
|
1554
|
+
stripMarkdownCode(prBody ?? "")
|
|
1555
|
+
);
|
|
1556
|
+
if (!found) return null;
|
|
1557
|
+
return Object.freeze({
|
|
1558
|
+
ticket: found.groups?.ticket ?? null,
|
|
1559
|
+
reason: (found.groups?.reason ?? "").trim() || null,
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1532
1563
|
/**
|
|
1533
1564
|
* Decides whether a bypass request is valid, as a pure function of the facts
|
|
1534
1565
|
* the caller gathered.
|
|
@@ -1614,15 +1645,15 @@ export function evaluateBypass(request) {
|
|
|
1614
1645
|
}
|
|
1615
1646
|
|
|
1616
1647
|
// The built-in rule ALWAYS applies. It is checked first and on its own, so no
|
|
1617
|
-
// caller-supplied pattern can stand in for it.
|
|
1618
|
-
//
|
|
1619
|
-
//
|
|
1620
|
-
//
|
|
1648
|
+
// caller-supplied pattern can stand in for it. Parsed through the ONE shared
|
|
1649
|
+
// reader (`parseWaiverTrailer`), so the merge-time re-derivation and the
|
|
1650
|
+
// durable record cannot come to read the trailer differently from the gate —
|
|
1651
|
+
// and that reader blanks fenced code before matching, because a waiver is
|
|
1652
|
+
// prose that ASSERTS something and a code sample never is. `assertedBody` is
|
|
1653
|
+
// the same view, kept so the optional project pattern below sees it too.
|
|
1621
1654
|
const assertedBody = stripMarkdownCode(prBody ?? "");
|
|
1622
|
-
const
|
|
1623
|
-
|
|
1624
|
-
);
|
|
1625
|
-
if (!found) {
|
|
1655
|
+
const trailer = parseWaiverTrailer(prBody);
|
|
1656
|
+
if (!trailer) {
|
|
1626
1657
|
return reject("no_reason_or_ticket", {
|
|
1627
1658
|
expiresAt: new Date(expiresMs).toISOString(),
|
|
1628
1659
|
});
|
|
@@ -1655,8 +1686,8 @@ export function evaluateBypass(request) {
|
|
|
1655
1686
|
actor: labelEvent.actor,
|
|
1656
1687
|
appliedAt: new Date(appliedMs).toISOString(),
|
|
1657
1688
|
expiresAt: new Date(expiresMs).toISOString(),
|
|
1658
|
-
ticket:
|
|
1659
|
-
detail:
|
|
1689
|
+
ticket: trailer.ticket,
|
|
1690
|
+
detail: trailer.reason,
|
|
1660
1691
|
});
|
|
1661
1692
|
}
|
|
1662
1693
|
|
|
@@ -1674,6 +1705,174 @@ const BYPASS_REJECTIONS = Object.freeze({
|
|
|
1674
1705
|
"this pull request's live labels and body could not be read, so whether a waiver was requested is UNKNOWN. The gate stays closed: a bypass that fires when it could not read the request is worse than no bypass. On a private repository the caller job needs `pull-requests: read`; otherwise this is a transient API failure and a re-run resolves it.",
|
|
1675
1706
|
});
|
|
1676
1707
|
|
|
1708
|
+
// ---------------------------------------------------------------------------
|
|
1709
|
+
// 4.1 The waiver record — durable, and RE-DERIVED at the moment of asking
|
|
1710
|
+
// ---------------------------------------------------------------------------
|
|
1711
|
+
//
|
|
1712
|
+
// Two defects live here, and they are the same defect seen from two ends.
|
|
1713
|
+
//
|
|
1714
|
+
// THE VERDICT WAS REPLAYED. The merge consumes a STORED check run. Between the
|
|
1715
|
+
// last evaluation and the merge, `bypassed` is a record of the past, and a
|
|
1716
|
+
// waiver is mutable pull-request state a human can edit. Removal at least fires
|
|
1717
|
+
// `unlabeled` and re-runs the gate; EXPIRY fires nothing at all, so the one
|
|
1718
|
+
// transition that produces no event is the one a replayed verdict can never
|
|
1719
|
+
// see. `runWaiverVerdict` re-derives the waiver from the LIVE pull request at
|
|
1720
|
+
// the moment the merge asks, through the SAME `observeWaiver` the gate itself
|
|
1721
|
+
// uses — not a second implementation that can drift into disagreeing with it.
|
|
1722
|
+
//
|
|
1723
|
+
// THE WAIVER LEFT NO RECORD. The label is the only repository-wide index of
|
|
1724
|
+
// "which merges went past this gate on a waiver", and the reaper strips it on
|
|
1725
|
+
// close, which withdraws the REQUEST and erases the INDEX with it. The record
|
|
1726
|
+
// below is written into a marker-delimited comment BEFORE the strip, so the
|
|
1727
|
+
// index outlives the label. `formatWaiverRecord` is deliberately buildable from
|
|
1728
|
+
// a REFUSED waiver too: an expired waiver still shipped something.
|
|
1729
|
+
//
|
|
1730
|
+
// Both halves refuse to guess. An unreadable pull request is `not_determined`
|
|
1731
|
+
// and never `waived` — "we could not check" must not render as "it is fine",
|
|
1732
|
+
// which is the rule the rest of this file is built on.
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* The four answers to "is a waiver in force on this pull request right now?".
|
|
1736
|
+
*
|
|
1737
|
+
* `none` is not a synonym for `waived`: it means nobody asked for a waiver, so
|
|
1738
|
+
* whatever the gate reported rests on suite evidence alone. `not_determined` is
|
|
1739
|
+
* not a synonym for `refused` either — one is a fact about the waiver, the
|
|
1740
|
+
* other is the absence of any fact — and only `refused` names a remedy.
|
|
1741
|
+
*/
|
|
1742
|
+
export const WAIVER_STATES = Object.freeze({
|
|
1743
|
+
none: "none",
|
|
1744
|
+
waived: "waived",
|
|
1745
|
+
refused: "refused",
|
|
1746
|
+
notDetermined: "not_determined",
|
|
1747
|
+
});
|
|
1748
|
+
|
|
1749
|
+
/**
|
|
1750
|
+
* The token every durable waiver record carries, and the key it is found by.
|
|
1751
|
+
*
|
|
1752
|
+
* The record has to be enumerable AFTER the reaper has stripped the label, so
|
|
1753
|
+
* the search key must be something the reaper does not touch. This string is
|
|
1754
|
+
* that key; the markers below wrap it.
|
|
1755
|
+
*/
|
|
1756
|
+
export const WAIVER_RECORD_MARKER = "NIGHTLY-E2E-BYPASS-RECORD";
|
|
1757
|
+
|
|
1758
|
+
/** Opening marker of the durable waiver record. */
|
|
1759
|
+
export const WAIVER_RECORD_BEGIN = `<!-- ${WAIVER_RECORD_MARKER}-BEGIN -->`;
|
|
1760
|
+
|
|
1761
|
+
/** Closing marker of the durable waiver record. */
|
|
1762
|
+
export const WAIVER_RECORD_END = `<!-- ${WAIVER_RECORD_MARKER}-END -->`;
|
|
1763
|
+
|
|
1764
|
+
/**
|
|
1765
|
+
* Turns a bypass decision into the merge-time waiver state.
|
|
1766
|
+
*
|
|
1767
|
+
* @param {object|null|undefined} bypass - A decision from `evaluateBypass`, or `null` when no waiver was requested
|
|
1768
|
+
* @returns {string} One of `WAIVER_STATES`
|
|
1769
|
+
*/
|
|
1770
|
+
export function classifyWaiver(bypass) {
|
|
1771
|
+
if (bypass === null || bypass === undefined) return WAIVER_STATES.none;
|
|
1772
|
+
if (bypass.reason === "pr_state_unreadable") {
|
|
1773
|
+
return WAIVER_STATES.notDetermined;
|
|
1774
|
+
}
|
|
1775
|
+
return bypass.valid === true ? WAIVER_STATES.waived : WAIVER_STATES.refused;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
/** One row of the record table, or nothing when the value is unknown. */
|
|
1779
|
+
const recordRow = (field, value) =>
|
|
1780
|
+
value === null || value === undefined || value === ""
|
|
1781
|
+
? []
|
|
1782
|
+
: [`| ${field} | ${value} |`];
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* Renders the durable waiver record as a marker-delimited comment body.
|
|
1786
|
+
*
|
|
1787
|
+
* The markers are what make it survivable and enumerable: a rewriter that
|
|
1788
|
+
* preserves marked regions cannot drop it by accident, and one that does not at
|
|
1789
|
+
* least leaves the deletion legible. They are also the search key — the record
|
|
1790
|
+
* outlives the label, so enumeration has to key on something the reaper does
|
|
1791
|
+
* not touch.
|
|
1792
|
+
*
|
|
1793
|
+
* Every unknown field is OMITTED rather than rendered as a confident empty
|
|
1794
|
+
* string. A record that prints `ticket:` with nothing after it reads as "there
|
|
1795
|
+
* was no ticket" when what happened is "the trailer could not be read".
|
|
1796
|
+
*
|
|
1797
|
+
* @param {{state: string, label: string, prNumber: number|null, gateContext: string, derivedAt: string, waiver: object|null, trailer: object|null, repo: string|null}} result - A `runWaiverVerdict` result
|
|
1798
|
+
* @returns {string} The comment body
|
|
1799
|
+
*/
|
|
1800
|
+
export function formatWaiverRecord(result) {
|
|
1801
|
+
const waiver = result.waiver ?? {};
|
|
1802
|
+
return [
|
|
1803
|
+
WAIVER_RECORD_BEGIN,
|
|
1804
|
+
"### 🌙 Nightly E2E waiver record",
|
|
1805
|
+
"",
|
|
1806
|
+
`This pull request carried an audited waiver against the \`${result.gateContext}\` merge gate. The \`${result.label}\` label is stripped when the pull request closes, so THIS comment — not the label — is what makes the waiver enumerable afterwards.`,
|
|
1807
|
+
"",
|
|
1808
|
+
"| field | value |",
|
|
1809
|
+
"| --- | --- |",
|
|
1810
|
+
...recordRow("label", `\`${result.label}\``),
|
|
1811
|
+
...recordRow(
|
|
1812
|
+
"ticket",
|
|
1813
|
+
result.trailer?.ticket ? `\`${result.trailer.ticket}\`` : null
|
|
1814
|
+
),
|
|
1815
|
+
...recordRow("reason", result.trailer?.reason),
|
|
1816
|
+
...recordRow("applied by", waiver.actor ? `\`${waiver.actor}\`` : null),
|
|
1817
|
+
...recordRow("applied at", waiver.appliedAt),
|
|
1818
|
+
...recordRow("expires at", waiver.expiresAt),
|
|
1819
|
+
...recordRow("state when recorded", `\`${result.state}\``),
|
|
1820
|
+
...recordRow(
|
|
1821
|
+
"refusal",
|
|
1822
|
+
result.state === WAIVER_STATES.refused
|
|
1823
|
+
? (BYPASS_REJECTIONS[waiver.reason] ?? waiver.reason)
|
|
1824
|
+
: null
|
|
1825
|
+
),
|
|
1826
|
+
...recordRow("re-derived at", result.derivedAt),
|
|
1827
|
+
"",
|
|
1828
|
+
`**Search key:** \`${WAIVER_RECORD_MARKER}\`. Every waived merge in \`${result.repo ?? "this repository"}\` is the set of pull-request comments carrying it, paginated from the REST comment listing for the repository's issues. The reaper never touches this comment, which is the whole point: the label answers "is a waiver being requested", and only this answers "what shipped on a waiver, and why".`,
|
|
1829
|
+
WAIVER_RECORD_END,
|
|
1830
|
+
"",
|
|
1831
|
+
].join("\n");
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
/** The headline each waiver state reports. */
|
|
1835
|
+
const WAIVER_HEADLINES = Object.freeze({
|
|
1836
|
+
none: "✅ **No waiver is in force.** Nobody asked this gate to waive anything, so whatever it reported rests on suite evidence alone.",
|
|
1837
|
+
waived:
|
|
1838
|
+
"⚠️ **A valid waiver IS in force right now.** This is not the stored verdict being trusted — it was re-derived from the live pull request at the moment of asking.",
|
|
1839
|
+
refused:
|
|
1840
|
+
"⛔ **A waiver was requested and is NOT valid right now.** A stored `bypassed` verdict must not carry this merge: it is a record of the past, and the waiver behind it no longer holds.",
|
|
1841
|
+
not_determined:
|
|
1842
|
+
"⚠️ **NOT DETERMINED.** The live pull request could not be read, so whether a waiver is in force is UNKNOWN. This is not a pass — an answer that could not be measured must never render as one that was.",
|
|
1843
|
+
});
|
|
1844
|
+
|
|
1845
|
+
/**
|
|
1846
|
+
* Renders the merge-time waiver verdict for a human.
|
|
1847
|
+
*
|
|
1848
|
+
* @param {object} result - A `runWaiverVerdict` result
|
|
1849
|
+
* @returns {string} Markdown
|
|
1850
|
+
*/
|
|
1851
|
+
export function formatWaiverVerdict(result) {
|
|
1852
|
+
const lines = [
|
|
1853
|
+
"## 🌙 Nightly E2E waiver — re-derived",
|
|
1854
|
+
"",
|
|
1855
|
+
WAIVER_HEADLINES[result.state],
|
|
1856
|
+
"",
|
|
1857
|
+
`Pull request: #${result.prNumber ?? "—"} · label \`${result.label}\` · re-derived at ${result.derivedAt}.`,
|
|
1858
|
+
];
|
|
1859
|
+
if (result.state === WAIVER_STATES.refused) {
|
|
1860
|
+
lines.push(
|
|
1861
|
+
"",
|
|
1862
|
+
`**What the waiver covered:** the \`${result.gateContext}\` merge gate${result.trailer?.ticket ? `, under ticket \`${result.trailer.ticket}\`` : ""}${result.trailer?.reason ? ` — ${result.trailer.reason}` : ""}.`,
|
|
1863
|
+
"",
|
|
1864
|
+
`**Why it is refused now:** ${BYPASS_REJECTIONS[result.waiver?.reason] ?? result.waiver?.reason}`,
|
|
1865
|
+
"",
|
|
1866
|
+
`**Remedy:** fix the red suite, or re-apply a fresh waiver — remove the \`${result.label}\` label, confirm the \`Nightly-E2E-Bypass: <TICKET> <reason>\` line still says what you mean, and re-apply the label so a maintainer's grant is dated from now. Do not merge on the earlier green.`
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
if (result.state === WAIVER_STATES.notDetermined) {
|
|
1870
|
+
lines.push("", `**Remedy:** ${BYPASS_REJECTIONS.pr_state_unreadable}`);
|
|
1871
|
+
}
|
|
1872
|
+
if (result.record) lines.push("", result.record);
|
|
1873
|
+
return `${lines.join("\n")}\n`;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1677
1876
|
// ---------------------------------------------------------------------------
|
|
1678
1877
|
// 5. Verdict
|
|
1679
1878
|
// ---------------------------------------------------------------------------
|
|
@@ -2686,6 +2885,9 @@ const GITHUB_ACCEPT = "application/vnd.github+json";
|
|
|
2686
2885
|
*/
|
|
2687
2886
|
const HEALTH_USER_AGENT = "lisa-nightly-e2e-health";
|
|
2688
2887
|
|
|
2888
|
+
/** GitHub's public API origin, used when the runner exports no override. */
|
|
2889
|
+
const DEFAULT_GITHUB_API_URL = "https://api.github.com";
|
|
2890
|
+
|
|
2689
2891
|
/**
|
|
2690
2892
|
* How long to wait before retrying a throttled response, bounded.
|
|
2691
2893
|
*
|
|
@@ -3697,6 +3899,226 @@ export async function fetchActorPermission(api, login, wait) {
|
|
|
3697
3899
|
return result?.body?.role_name ?? result?.body?.permission ?? null;
|
|
3698
3900
|
}
|
|
3699
3901
|
|
|
3902
|
+
/**
|
|
3903
|
+
* The waiver question, extracted from resolved settings.
|
|
3904
|
+
*
|
|
3905
|
+
* @param {object} settings - Resolved settings carrying `pr`, `bypassLabel`, `bypassMaxHours`
|
|
3906
|
+
* @param {Date} now - The instant to evaluate at
|
|
3907
|
+
* @returns {object} The request `observeWaiver` consumes
|
|
3908
|
+
*/
|
|
3909
|
+
function waiverRequest(settings, now) {
|
|
3910
|
+
return {
|
|
3911
|
+
prNumber: settings.pr.number,
|
|
3912
|
+
label: settings.bypassLabel,
|
|
3913
|
+
payloadAuthor: settings.pr.payloadAuthor,
|
|
3914
|
+
maxHours: settings.bypassMaxHours,
|
|
3915
|
+
extraReasonPattern: settings.extraBypassReasonPattern,
|
|
3916
|
+
now,
|
|
3917
|
+
};
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3920
|
+
/**
|
|
3921
|
+
* Reads the pull request LIVE and derives the waiver from what it says NOW.
|
|
3922
|
+
*
|
|
3923
|
+
* The ONE place the waiver is derived. The gate calls it while producing the
|
|
3924
|
+
* check, and the merge-time re-derivation calls it again later; that they are
|
|
3925
|
+
* the same call is the property that makes "re-derived" mean anything, because
|
|
3926
|
+
* a second implementation would eventually answer a different question.
|
|
3927
|
+
*
|
|
3928
|
+
* Four outcomes, and the two that are easy to collapse are kept apart:
|
|
3929
|
+
*
|
|
3930
|
+
* no pull request `bypass: null`, `labelPresent: false` — nothing to waive
|
|
3931
|
+
* label absent `bypass: null`, `labelPresent: false` — nobody asked
|
|
3932
|
+
* unreadable a REJECTED bypass, `labelPresent: null` — UNKNOWN, never a grant
|
|
3933
|
+
* label present whatever `evaluateBypass` decides
|
|
3934
|
+
*
|
|
3935
|
+
* `trailer` is returned alongside, parsed from the live body regardless of
|
|
3936
|
+
* whether the waiver qualifies, because the durable record has to be able to
|
|
3937
|
+
* say what an EXPIRED waiver claimed.
|
|
3938
|
+
*
|
|
3939
|
+
* @param {object} api - API coordinates
|
|
3940
|
+
* @param {{prNumber: number|null, label: string, payloadAuthor: string|null, maxHours: number, extraReasonPattern: string, now: Date}} request - The waiver question
|
|
3941
|
+
* @param {(ms: number) => Promise<void>} [wait] - Injectable sleep
|
|
3942
|
+
* @returns {Promise<{bypass: object|null, trailer: object|null, labelPresent: boolean|null}>} The observation
|
|
3943
|
+
*/
|
|
3944
|
+
export async function observeWaiver(api, request, wait) {
|
|
3945
|
+
const absent = Object.freeze({
|
|
3946
|
+
bypass: null,
|
|
3947
|
+
trailer: null,
|
|
3948
|
+
labelPresent: false,
|
|
3949
|
+
});
|
|
3950
|
+
if (!request.prNumber) return absent;
|
|
3951
|
+
|
|
3952
|
+
const live = await fetchPullRequestState(api, request.prNumber, wait);
|
|
3953
|
+
const subject = {
|
|
3954
|
+
prNumber: request.prNumber,
|
|
3955
|
+
label: request.label,
|
|
3956
|
+
prAuthor: request.payloadAuthor,
|
|
3957
|
+
};
|
|
3958
|
+
if (live === null) {
|
|
3959
|
+
// Unreadable is UNKNOWN, and `labelPresent: null` says so in the same
|
|
3960
|
+
// vocabulary the bypass uses. Falling back to the event payload here is
|
|
3961
|
+
// exactly the defect row 40 exists to close.
|
|
3962
|
+
return Object.freeze({
|
|
3963
|
+
bypass: unreadablePullRequestBypass(subject),
|
|
3964
|
+
trailer: null,
|
|
3965
|
+
labelPresent: null,
|
|
3966
|
+
});
|
|
3967
|
+
}
|
|
3968
|
+
const trailer = parseWaiverTrailer(live.body);
|
|
3969
|
+
if (!live.labels.includes(request.label)) {
|
|
3970
|
+
// Read successfully, label not present. Nobody asked for a waiver, and a
|
|
3971
|
+
// label REMOVED since the run was triggered lands here rather than waiving
|
|
3972
|
+
// anything. The trailer is still carried: prose in a body is a request
|
|
3973
|
+
// nobody granted, not a waiver.
|
|
3974
|
+
return Object.freeze({ bypass: null, trailer, labelPresent: false });
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
const labelEvent = await fetchLabelEvent(
|
|
3978
|
+
api,
|
|
3979
|
+
request.prNumber,
|
|
3980
|
+
request.label,
|
|
3981
|
+
wait
|
|
3982
|
+
);
|
|
3983
|
+
const actorPermission = labelEvent
|
|
3984
|
+
? await fetchActorPermission(api, labelEvent.actor, wait)
|
|
3985
|
+
: null;
|
|
3986
|
+
return Object.freeze({
|
|
3987
|
+
bypass: evaluateBypass({
|
|
3988
|
+
labelEvent,
|
|
3989
|
+
prAuthor: live.author ?? request.payloadAuthor,
|
|
3990
|
+
prBody: live.body,
|
|
3991
|
+
actorPermission,
|
|
3992
|
+
prNumber: request.prNumber,
|
|
3993
|
+
label: request.label,
|
|
3994
|
+
maxHours: request.maxHours,
|
|
3995
|
+
extraReasonPattern: request.extraReasonPattern,
|
|
3996
|
+
now: request.now,
|
|
3997
|
+
}),
|
|
3998
|
+
trailer,
|
|
3999
|
+
labelPresent: true,
|
|
4000
|
+
});
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
/**
|
|
4004
|
+
* Resolves everything the WAIVER half needs, and nothing else.
|
|
4005
|
+
*
|
|
4006
|
+
* Deliberately does not require `branch` or `suites`: the merge-time question
|
|
4007
|
+
* is "is the waiver still good?", not "is the nightly green?". Demanding the
|
|
4008
|
+
* suite table here would make the re-derivation impossible to run from the one
|
|
4009
|
+
* place that most needs it — a merge driver that has a pull request number and
|
|
4010
|
+
* a token, and no business restating the gate's configuration.
|
|
4011
|
+
*
|
|
4012
|
+
* The security ceilings are resolved through the SAME `resolveSecurityLimits`
|
|
4013
|
+
* the gate uses, so a caller cannot buy a longer waiver by asking a different
|
|
4014
|
+
* entry point.
|
|
4015
|
+
*
|
|
4016
|
+
* @param {NodeJS.ProcessEnv} env - The environment
|
|
4017
|
+
* @returns {object} Resolved waiver settings
|
|
4018
|
+
* @throws {GateConfigError} When required settings are absent or unusable
|
|
4019
|
+
*/
|
|
4020
|
+
export function resolveWaiverSettings(env) {
|
|
4021
|
+
const token = env.GITHUB_TOKEN || env.GH_TOKEN;
|
|
4022
|
+
if (!token) {
|
|
4023
|
+
throw new GateConfigError(
|
|
4024
|
+
"No GITHUB_TOKEN / GH_TOKEN in the environment. Re-deriving a waiver means reading the live pull request, and that needs a token."
|
|
4025
|
+
);
|
|
4026
|
+
}
|
|
4027
|
+
if (!env.GITHUB_REPOSITORY) {
|
|
4028
|
+
throw new GateConfigError(
|
|
4029
|
+
"No GITHUB_REPOSITORY in the environment, so there is no pull request to re-derive a waiver from."
|
|
4030
|
+
);
|
|
4031
|
+
}
|
|
4032
|
+
const prNumber = Number(env.NIGHTLY_PR_NUMBER) || null;
|
|
4033
|
+
if (!prNumber) {
|
|
4034
|
+
throw new GateConfigError(
|
|
4035
|
+
"No NIGHTLY_PR_NUMBER in the environment. A waiver is a property of one pull request; without one there is nothing to re-derive, and answering `none` here would be a guess wearing a verdict's clothes."
|
|
4036
|
+
);
|
|
4037
|
+
}
|
|
4038
|
+
const raw = env.NIGHTLY_BYPASS_MAX_HOURS;
|
|
4039
|
+
const requestedHours =
|
|
4040
|
+
raw === undefined || String(raw).trim() === "" ? 24 : Number(raw);
|
|
4041
|
+
if (!Number.isFinite(requestedHours) || requestedHours <= 0) {
|
|
4042
|
+
throw new GateConfigError(
|
|
4043
|
+
`\`NIGHTLY_BYPASS_MAX_HOURS\` must be a positive number (got ${raw}).`
|
|
4044
|
+
);
|
|
4045
|
+
}
|
|
4046
|
+
const { limits } = resolveSecurityLimits({
|
|
4047
|
+
bypassMaxHours: requestedHours,
|
|
4048
|
+
bootstrapMaxDays: BOOTSTRAP_ABSOLUTE_MAX_DAYS,
|
|
4049
|
+
freshnessHours: ABSOLUTE_MAX_FRESHNESS_HOURS,
|
|
4050
|
+
apiMaxAttempts: ABSOLUTE_MAX_API_ATTEMPTS,
|
|
4051
|
+
apiMaxPages: ABSOLUTE_MAX_API_PAGES,
|
|
4052
|
+
apiRetryMaxSeconds: ABSOLUTE_MAX_RETRY_SECONDS,
|
|
4053
|
+
});
|
|
4054
|
+
return {
|
|
4055
|
+
api: {
|
|
4056
|
+
apiUrl: env.GITHUB_API_URL || DEFAULT_GITHUB_API_URL,
|
|
4057
|
+
repo: env.GITHUB_REPOSITORY,
|
|
4058
|
+
token,
|
|
4059
|
+
maxAttempts: limits.apiMaxAttempts,
|
|
4060
|
+
maxPages: limits.apiMaxPages,
|
|
4061
|
+
retryMaxSeconds: limits.apiRetryMaxSeconds,
|
|
4062
|
+
},
|
|
4063
|
+
gateContext:
|
|
4064
|
+
(env.NIGHTLY_GATE_CONTEXT ?? "").trim() || DEFAULT_GATE_CONTEXT,
|
|
4065
|
+
bypassLabel:
|
|
4066
|
+
String(env.NIGHTLY_BYPASS_LABEL ?? "").trim() || DEFAULT_BYPASS_LABEL,
|
|
4067
|
+
bypassMaxHours: limits.bypassMaxHours,
|
|
4068
|
+
extraBypassReasonPattern: env.NIGHTLY_BYPASS_REASON_PATTERN || "",
|
|
4069
|
+
pr: { number: prNumber, payloadAuthor: env.NIGHTLY_PR_AUTHOR || null },
|
|
4070
|
+
};
|
|
4071
|
+
}
|
|
4072
|
+
|
|
4073
|
+
/**
|
|
4074
|
+
* Re-derives the waiver from the live pull request, at the moment of asking.
|
|
4075
|
+
*
|
|
4076
|
+
* This is the answer to "the verdict is replayed, not re-derived". A stored
|
|
4077
|
+
* check run says what was true when it ran; this says what is true now. The
|
|
4078
|
+
* difference matters for exactly one transition — EXPIRY — because every other
|
|
4079
|
+
* change to a waiver (applying the label, removing it, editing the body) fires
|
|
4080
|
+
* a pull-request event the gate already subscribes to, and expiry fires
|
|
4081
|
+
* nothing. A waiver that lapses between the last evaluation and the merge is
|
|
4082
|
+
* invisible to any amount of re-running; it is only visible to re-deriving.
|
|
4083
|
+
*
|
|
4084
|
+
* @param {NodeJS.ProcessEnv} env - The environment
|
|
4085
|
+
* @param {(ms: number) => Promise<void>} [wait] - Injectable sleep
|
|
4086
|
+
* @returns {Promise<object>} The waiver verdict, with its durable record
|
|
4087
|
+
*/
|
|
4088
|
+
export async function runWaiverVerdict(env, wait) {
|
|
4089
|
+
const settings = resolveWaiverSettings(env);
|
|
4090
|
+
const now = new Date();
|
|
4091
|
+
const observed = await observeWaiver(
|
|
4092
|
+
settings.api,
|
|
4093
|
+
waiverRequest(settings, now),
|
|
4094
|
+
wait
|
|
4095
|
+
);
|
|
4096
|
+
const state = classifyWaiver(observed.bypass);
|
|
4097
|
+
const result = {
|
|
4098
|
+
state,
|
|
4099
|
+
prNumber: settings.pr.number,
|
|
4100
|
+
repo: settings.api.repo,
|
|
4101
|
+
label: settings.bypassLabel,
|
|
4102
|
+
gateContext: settings.gateContext,
|
|
4103
|
+
derivedAt: now.toISOString(),
|
|
4104
|
+
waiver: observed.bypass,
|
|
4105
|
+
trailer: observed.trailer,
|
|
4106
|
+
labelPresent: observed.labelPresent,
|
|
4107
|
+
record: null,
|
|
4108
|
+
};
|
|
4109
|
+
// A record is written for every state that involved a waiver at all —
|
|
4110
|
+
// including a REFUSED one. "What shipped on a waiver, and why" is exactly the
|
|
4111
|
+
// question an expired waiver makes hard to answer, so it is the one the
|
|
4112
|
+
// record must still answer.
|
|
4113
|
+
return Object.freeze({
|
|
4114
|
+
...result,
|
|
4115
|
+
record:
|
|
4116
|
+
state === WAIVER_STATES.none || state === WAIVER_STATES.notDetermined
|
|
4117
|
+
? null
|
|
4118
|
+
: formatWaiverRecord(result),
|
|
4119
|
+
});
|
|
4120
|
+
}
|
|
4121
|
+
|
|
3700
4122
|
// ---------------------------------------------------------------------------
|
|
3701
4123
|
// 8. Entry point
|
|
3702
4124
|
// ---------------------------------------------------------------------------
|
|
@@ -3754,7 +4176,7 @@ export function resolveSettings(env) {
|
|
|
3754
4176
|
|
|
3755
4177
|
return {
|
|
3756
4178
|
api: {
|
|
3757
|
-
apiUrl: env.GITHUB_API_URL ||
|
|
4179
|
+
apiUrl: env.GITHUB_API_URL || DEFAULT_GITHUB_API_URL,
|
|
3758
4180
|
repo: env.GITHUB_REPOSITORY,
|
|
3759
4181
|
token,
|
|
3760
4182
|
maxAttempts: limits.apiMaxAttempts,
|
|
@@ -3767,7 +4189,7 @@ export function resolveSettings(env) {
|
|
|
3767
4189
|
// the runner already exports the right value.
|
|
3768
4190
|
graphqlUrl:
|
|
3769
4191
|
env.GITHUB_GRAPHQL_URL ||
|
|
3770
|
-
`${env.GITHUB_API_URL ||
|
|
4192
|
+
`${env.GITHUB_API_URL || DEFAULT_GITHUB_API_URL}/graphql`,
|
|
3771
4193
|
},
|
|
3772
4194
|
issueLabel: env.NIGHTLY_ISSUE_LABEL || TRACKING_ISSUE_LABEL,
|
|
3773
4195
|
// Reporting-only settings. The gate reads none of them, which is why they
|
|
@@ -3865,47 +4287,16 @@ export async function runGate(env, wait) {
|
|
|
3865
4287
|
// The bypass reads the pull request LIVE and never consults `github.event`.
|
|
3866
4288
|
// Three outcomes, and the middle one is the vacuity guard: unreadable is a
|
|
3867
4289
|
// REJECTED bypass, never an absent one and never a granted one.
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
prAuthor: settings.pr.payloadAuthor,
|
|
3879
|
-
};
|
|
3880
|
-
if (live === null) {
|
|
3881
|
-
bypass = unreadablePullRequestBypass(subject);
|
|
3882
|
-
} else if (live.labels.includes(settings.bypassLabel)) {
|
|
3883
|
-
const labelEvent = await fetchLabelEvent(
|
|
3884
|
-
settings.api,
|
|
3885
|
-
settings.pr.number,
|
|
3886
|
-
settings.bypassLabel,
|
|
3887
|
-
wait
|
|
3888
|
-
);
|
|
3889
|
-
const actorPermission = labelEvent
|
|
3890
|
-
? await fetchActorPermission(settings.api, labelEvent.actor, wait)
|
|
3891
|
-
: null;
|
|
3892
|
-
bypass = evaluateBypass({
|
|
3893
|
-
labelEvent,
|
|
3894
|
-
prAuthor: live.author ?? settings.pr.payloadAuthor,
|
|
3895
|
-
prBody: live.body,
|
|
3896
|
-
actorPermission,
|
|
3897
|
-
prNumber: settings.pr.number,
|
|
3898
|
-
label: settings.bypassLabel,
|
|
3899
|
-
maxHours: settings.bypassMaxHours,
|
|
3900
|
-
extraReasonPattern: settings.extraBypassReasonPattern,
|
|
3901
|
-
now,
|
|
3902
|
-
});
|
|
3903
|
-
}
|
|
3904
|
-
// The remaining case — read successfully, label not present — leaves
|
|
3905
|
-
// `bypass` null. Nobody asked for a waiver, so there is nothing to report,
|
|
3906
|
-
// and a label REMOVED since the run was triggered lands here rather than
|
|
3907
|
-
// waiving anything.
|
|
3908
|
-
}
|
|
4290
|
+
//
|
|
4291
|
+
// Derived through `observeWaiver`, which is the SAME call the merge-time
|
|
4292
|
+
// re-derivation makes. Two implementations of "is this waiver good?" would
|
|
4293
|
+
// drift, and the whole value of re-deriving at merge time is that it answers
|
|
4294
|
+
// the question the gate answered — later, not differently.
|
|
4295
|
+
const { bypass } = await observeWaiver(
|
|
4296
|
+
settings.api,
|
|
4297
|
+
waiverRequest(settings, now),
|
|
4298
|
+
wait
|
|
4299
|
+
);
|
|
3909
4300
|
|
|
3910
4301
|
return {
|
|
3911
4302
|
...decide(findings, { bootstrap, bypass }),
|
|
@@ -4213,6 +4604,10 @@ async function main(argv) {
|
|
|
4213
4604
|
// The reporting half is opt-in at the call site, which is what keeps the
|
|
4214
4605
|
// default invocation — the required status check — provably read-only.
|
|
4215
4606
|
if (argv.includes("--report-issues")) return await reportIssues(asJson);
|
|
4607
|
+
// The merge-time re-derivation. Also read-only: it emits the durable record
|
|
4608
|
+
// rather than posting it, so the one caller that needs `pull-requests: write`
|
|
4609
|
+
// (the reaper) is the only place a write can happen.
|
|
4610
|
+
if (argv.includes("--waiver-verdict")) return await waiverVerdict(asJson);
|
|
4216
4611
|
|
|
4217
4612
|
/** @type {object} */
|
|
4218
4613
|
let verdict;
|
|
@@ -4277,6 +4672,85 @@ async function main(argv) {
|
|
|
4277
4672
|
}
|
|
4278
4673
|
}
|
|
4279
4674
|
|
|
4675
|
+
/**
|
|
4676
|
+
* The `--waiver-verdict` CLI: re-derive the waiver and say what it found.
|
|
4677
|
+
*
|
|
4678
|
+
* The exit code is the merge-time contract, and it has three arms rather than
|
|
4679
|
+
* two. `none` and `waived` exit 0 — the first because nobody asked for a
|
|
4680
|
+
* waiver, the second because a genuine, still-valid waiver is the escape hatch
|
|
4681
|
+
* working as designed and must keep merging. `refused` and `not_determined`
|
|
4682
|
+
* both exit 1, and they say DIFFERENT things: one names a lapsed waiver and its
|
|
4683
|
+
* remedy, the other says the question could not be answered. Collapsing the
|
|
4684
|
+
* second into a pass is the failure this whole mode exists to prevent.
|
|
4685
|
+
*
|
|
4686
|
+
* Exported for the same reason `reportIssues` is: the exit code IS the
|
|
4687
|
+
* contract here — it is what stops a merge — and a contract only a spawned
|
|
4688
|
+
* process can observe is one the suite tests indirectly or not at all.
|
|
4689
|
+
*
|
|
4690
|
+
* @param {boolean} asJson - Whether to emit the machine record instead
|
|
4691
|
+
* @returns {Promise<void>} Resolves once the verdict is written
|
|
4692
|
+
*/
|
|
4693
|
+
export async function waiverVerdict(asJson) {
|
|
4694
|
+
/** @type {object} */
|
|
4695
|
+
let result;
|
|
4696
|
+
try {
|
|
4697
|
+
result = await runWaiverVerdict(process.env);
|
|
4698
|
+
} catch (error) {
|
|
4699
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4700
|
+
const failure = {
|
|
4701
|
+
state: WAIVER_STATES.notDetermined,
|
|
4702
|
+
error: {
|
|
4703
|
+
kind: error instanceof GateConfigError ? "configuration" : "api",
|
|
4704
|
+
message,
|
|
4705
|
+
},
|
|
4706
|
+
record: null,
|
|
4707
|
+
};
|
|
4708
|
+
if (asJson) {
|
|
4709
|
+
process.stdout.write(`${JSON.stringify(failure, null, 2)}\n`);
|
|
4710
|
+
} else {
|
|
4711
|
+
process.stdout.write(
|
|
4712
|
+
`## 🌙 Nightly E2E waiver — re-derived\n\n⚠️ **NOT DETERMINED.** ${message}\n\nThis is not a pass. A waiver whose state could not be re-derived must not carry a merge.\n`
|
|
4713
|
+
);
|
|
4714
|
+
}
|
|
4715
|
+
await writeWaiverOutputs(failure);
|
|
4716
|
+
process.exitCode = 1;
|
|
4717
|
+
return;
|
|
4718
|
+
}
|
|
4719
|
+
|
|
4720
|
+
if (asJson) {
|
|
4721
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
4722
|
+
} else {
|
|
4723
|
+
const report = formatWaiverVerdict(result);
|
|
4724
|
+
process.stdout.write(report);
|
|
4725
|
+
await appendSummary(report);
|
|
4726
|
+
}
|
|
4727
|
+
await writeWaiverOutputs(result);
|
|
4728
|
+
if (
|
|
4729
|
+
result.state === WAIVER_STATES.refused ||
|
|
4730
|
+
result.state === WAIVER_STATES.notDetermined
|
|
4731
|
+
) {
|
|
4732
|
+
process.exitCode = 1;
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
|
|
4736
|
+
/**
|
|
4737
|
+
* Publishes the waiver verdict as step outputs.
|
|
4738
|
+
*
|
|
4739
|
+
* `waiver_record` is emitted as a heredoc so the reaper can post it verbatim
|
|
4740
|
+
* without the record's own markdown having to survive a shell quote.
|
|
4741
|
+
*
|
|
4742
|
+
* @param {object} result - The waiver verdict
|
|
4743
|
+
* @returns {Promise<void>} Resolves when written
|
|
4744
|
+
*/
|
|
4745
|
+
async function writeWaiverOutputs(result) {
|
|
4746
|
+
if (!process.env.GITHUB_OUTPUT) return;
|
|
4747
|
+
const { appendFileSync } = await import("node:fs");
|
|
4748
|
+
appendFileSync(
|
|
4749
|
+
process.env.GITHUB_OUTPUT,
|
|
4750
|
+
`waiver_state=${result.state}\nwaiver_record<<LISA_WAIVER_EOF\n${result.record ?? ""}\nLISA_WAIVER_EOF\n`
|
|
4751
|
+
);
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4280
4754
|
/**
|
|
4281
4755
|
* Appends a report to the job summary when one exists.
|
|
4282
4756
|
*
|