@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
|
@@ -28,6 +28,15 @@
|
|
|
28
28
|
* This script reads what the templates actually reference and confirms the
|
|
29
29
|
* target exists. Only that.
|
|
30
30
|
*
|
|
31
|
+
* Its sibling, `check-workflow-contract-assertions.mjs`, answers the question
|
|
32
|
+
* this one cannot: a reference that resolves today still says nothing about
|
|
33
|
+
* whether what it resolves TO has changed under the consumer. Under the `@main`
|
|
34
|
+
* ruling one push here alters what gates and what ships in every downstream
|
|
35
|
+
* repository at once, and an unchanged workflow and a rewritten one produce
|
|
36
|
+
* identical silence (CodySwannGT/lisa#3698). That gate requires each reusable to
|
|
37
|
+
* declare a contract major and each caller template to be seeded with it, and
|
|
38
|
+
* the two run in the same CI step so neither can be adopted without the other.
|
|
39
|
+
*
|
|
31
40
|
* Determinism guarantees (so the unit test is reproducible and CI is stable):
|
|
32
41
|
* - zero third-party dependencies (Node built-ins only),
|
|
33
42
|
* - no network access,
|
|
@@ -158,8 +158,24 @@ const NO_VERSION = "missing-version-comment";
|
|
|
158
158
|
* `uses: <owner>/<repo>[/<subpath>]@<ref>` with anything after it captured, so
|
|
159
159
|
* a trailing `# v1.2.3` can be read. Local (`./path`) and container
|
|
160
160
|
* (`docker://`) references have no owner and never match.
|
|
161
|
+
*
|
|
162
|
+
* The value may be QUOTED, and that is not a stylistic detail here. YAML
|
|
163
|
+
* accepts `uses: "owner/repo@ref"` and `uses: 'owner/repo@ref'` as the same
|
|
164
|
+
* scalar GitHub Actions resolves identically, but a value class that excluded
|
|
165
|
+
* quotes could not begin matching at one — so a quoted reference did not fail
|
|
166
|
+
* the scan, it left it entirely, and the gate reported clean over an entry it
|
|
167
|
+
* had never inspected. A pin checker that silently skips a spelling is a
|
|
168
|
+
* vacuous pass on every reference written that way, which is the failure this
|
|
169
|
+
* gate exists to close rather than to reproduce.
|
|
170
|
+
*
|
|
171
|
+
* The closing quote is matched by back-reference so `"a'` cannot read as a
|
|
172
|
+
* quoted value, and it is consumed by the match so the trailing `# v1.2.3` is
|
|
173
|
+
* still found by the slice in `findActionRefs`. An unquoted value binds the
|
|
174
|
+
* group to the empty string, where the back-reference matches empty and the
|
|
175
|
+
* pattern behaves exactly as it did before.
|
|
161
176
|
*/
|
|
162
|
-
const USES_RE =
|
|
177
|
+
const USES_RE =
|
|
178
|
+
/^[ \t]*(?:-[ \t]*)?uses:[ \t]*(?<quote>["']?)(?<value>[^\s"'#]+)\k<quote>/;
|
|
163
179
|
|
|
164
180
|
/** A well-formed action owner. Anchored, so matching is linear. */
|
|
165
181
|
const OWNER_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic gate that every Lisa reusable workflow carries a staleness
|
|
4
|
+
* assertion, and that every shipped caller template is seeded with it
|
|
5
|
+
* (CodySwannGT/lisa#3698).
|
|
6
|
+
*
|
|
7
|
+
* ## The defect
|
|
8
|
+
*
|
|
9
|
+
* Every consumer reference to a Lisa reusable workflow tracks `@main` by owner
|
|
10
|
+
* ruling. One push here changes what gates — and what ships — in every
|
|
11
|
+
* downstream repository simultaneously, with no pull request, no review and no
|
|
12
|
+
* record in those repositories. That is intended, and it removed pinning as a
|
|
13
|
+
* mitigation without putting anything in its place:
|
|
14
|
+
*
|
|
15
|
+
* **a consumer silently receives a behaviour change, or silently receives
|
|
16
|
+
* nothing, and has no signal either way.**
|
|
17
|
+
*
|
|
18
|
+
* An unchanged workflow and a rewritten one produce identical silence. It is
|
|
19
|
+
* also why an audit cannot reproduce itself: an investigation that fetches
|
|
20
|
+
* `@main` afterwards is reading a different artifact than the run it is
|
|
21
|
+
* investigating, and cannot tell.
|
|
22
|
+
*
|
|
23
|
+
* ## What is asserted, and what deliberately is not
|
|
24
|
+
*
|
|
25
|
+
* Not the ref. A staleness check that pins a tag, a SHA or a tree hash goes red
|
|
26
|
+
* on every legitimate upstream change, and a check that is red for good reasons
|
|
27
|
+
* is disabled within a week — the staleness check becomes the staleness
|
|
28
|
+
* problem. What a caller depends on is the CONTRACT: the inputs it passes and
|
|
29
|
+
* the behaviour it relies on. So each reusable declares a contract MAJOR, each
|
|
30
|
+
* caller is seeded with the major it was written against, and the workflow
|
|
31
|
+
* compares the two at run time. A compatible change stays green, because
|
|
32
|
+
* receiving compatible changes is the whole point of tracking `@main`; only a
|
|
33
|
+
* declared break speaks up, naming both numbers.
|
|
34
|
+
*
|
|
35
|
+
* ## Why this gate exists on top of the run-time assertion
|
|
36
|
+
*
|
|
37
|
+
* The assertion and the `uses:` line are one unit. A template that takes the
|
|
38
|
+
* reference and drops the assertion is back to silent staleness and looks
|
|
39
|
+
* perfectly healthy doing it, so the two must not be separable by copy-paste.
|
|
40
|
+
* This gate is the thing that refuses that separation, and it is the reason the
|
|
41
|
+
* run-time half can afford to be non-fatal when a caller declares nothing.
|
|
42
|
+
*
|
|
43
|
+
* Local `./.github/workflows/...` callers inside this repository are
|
|
44
|
+
* deliberately exempt: a local reference resolves at the same commit as its
|
|
45
|
+
* caller and so cannot be stale. Requiring the assertion there would be a check
|
|
46
|
+
* that could not fail.
|
|
47
|
+
*
|
|
48
|
+
* Determinism: no network, no clock, no randomness. The template list comes
|
|
49
|
+
* from `git ls-files`, so the gate sees exactly what a release would carry.
|
|
50
|
+
*
|
|
51
|
+
* CLI:
|
|
52
|
+
* node scripts/check-workflow-contract-assertions.mjs [--root <dir>] [--json]
|
|
53
|
+
*
|
|
54
|
+
* Exit codes:
|
|
55
|
+
* 0 — every reusable is registered and asserts, every caller template is
|
|
56
|
+
* seeded with the current major.
|
|
57
|
+
* 1 — ≥1 finding.
|
|
58
|
+
* 2 — NOT DETERMINED: the registry or the canonical assertion body could not
|
|
59
|
+
* be read, or the scan discovered zero reusable workflows or zero caller
|
|
60
|
+
* templates. A gate that could not look must not report that it looked.
|
|
61
|
+
*
|
|
62
|
+
* @module scripts/check-workflow-contract-assertions
|
|
63
|
+
*/
|
|
64
|
+
import yaml from "js-yaml";
|
|
65
|
+
import fs from "node:fs";
|
|
66
|
+
import path from "node:path";
|
|
67
|
+
import process from "node:process";
|
|
68
|
+
import { fileURLToPath } from "node:url";
|
|
69
|
+
|
|
70
|
+
import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
|
|
71
|
+
import { invokedAsScript } from "./lib/invoked-as-script.mjs";
|
|
72
|
+
|
|
73
|
+
const REPO_ROOT = path.resolve(
|
|
74
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
75
|
+
".."
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
/** Where the declared majors live. */
|
|
79
|
+
export const REGISTRY_REL = ".github/reusable-workflow-contracts.json";
|
|
80
|
+
|
|
81
|
+
/** The single source of truth for the inlined assertion body. */
|
|
82
|
+
export const CANONICAL_REL = "scripts/workflow-contract-assertion.sh";
|
|
83
|
+
|
|
84
|
+
/** The input a caller passes and the workflow compares. */
|
|
85
|
+
export const INPUT_NAME = "expected_workflow_contract_major";
|
|
86
|
+
|
|
87
|
+
/** The job every covered reusable carries. */
|
|
88
|
+
export const JOB_ID = "workflow_contract";
|
|
89
|
+
|
|
90
|
+
/** `<lane>/<mode>/.github/workflows/<name>.yml` — a SHIPPED caller template. */
|
|
91
|
+
const TEMPLATE_RE = /^[^/]+\/[^/]+\/\.github\/workflows\/[^/]+\.ya?ml$/;
|
|
92
|
+
|
|
93
|
+
/** `CodySwannGT/lisa/.github/workflows/<file>@<ref>`. */
|
|
94
|
+
const REMOTE_USES_RE =
|
|
95
|
+
/^CodySwannGT\/lisa\/\.github\/workflows\/([\w.-]+\.ya?ml)@\S+$/;
|
|
96
|
+
|
|
97
|
+
/** Max bytes of `git ls-files` output. */
|
|
98
|
+
const MAX_GIT_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
99
|
+
|
|
100
|
+
/** Raised for an invalid invocation or a state the gate cannot judge. */
|
|
101
|
+
export class UsageError extends Error {}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Parse a workflow file, returning `null` when it is not a mapping.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} file - absolute path.
|
|
107
|
+
* @returns {Record<string, unknown> | null} the parsed document.
|
|
108
|
+
*/
|
|
109
|
+
function loadWorkflow(file) {
|
|
110
|
+
const document = yaml.load(fs.readFileSync(file, "utf8"));
|
|
111
|
+
return document !== null && typeof document === "object" ? document : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The `workflow_call` block of a parsed workflow, or `null` when the workflow
|
|
116
|
+
* is not reusable.
|
|
117
|
+
*
|
|
118
|
+
* @param {Record<string, unknown> | null} document - a parsed workflow.
|
|
119
|
+
* @returns {Record<string, unknown> | null} the `workflow_call` mapping.
|
|
120
|
+
*/
|
|
121
|
+
export function workflowCallOf(document) {
|
|
122
|
+
const triggers = document?.on;
|
|
123
|
+
if (triggers === null || typeof triggers !== "object") return null;
|
|
124
|
+
const call = triggers.workflow_call;
|
|
125
|
+
if (call === undefined) return null;
|
|
126
|
+
return call !== null && typeof call === "object" ? call : {};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validate one registry entry's shape.
|
|
131
|
+
*
|
|
132
|
+
* @param {unknown} entry - the value stored for a workflow.
|
|
133
|
+
* @returns {string | null} a complaint, or `null` when the entry is well formed.
|
|
134
|
+
*/
|
|
135
|
+
export function describeEntryProblem(entry) {
|
|
136
|
+
if (entry === null || typeof entry !== "object") {
|
|
137
|
+
return "entry is not an object";
|
|
138
|
+
}
|
|
139
|
+
const hasMajor = Object.hasOwn(entry, "major");
|
|
140
|
+
const hasUncovered = Object.hasOwn(entry, "uncovered");
|
|
141
|
+
if (hasMajor === hasUncovered) {
|
|
142
|
+
return "entry must carry exactly one of `major` or `uncovered`";
|
|
143
|
+
}
|
|
144
|
+
if (hasUncovered) {
|
|
145
|
+
return typeof entry.uncovered === "string" && entry.uncovered.trim() !== ""
|
|
146
|
+
? null
|
|
147
|
+
: "`uncovered` must be a non-empty reason, so a gap is recorded rather than inherited";
|
|
148
|
+
}
|
|
149
|
+
return Number.isInteger(entry.major) && entry.major >= 1
|
|
150
|
+
? null
|
|
151
|
+
: "`major` must be an integer >= 1";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Findings about ONE covered reusable workflow: does it declare the input, and
|
|
156
|
+
* does it carry the assertion job with the canonical body?
|
|
157
|
+
*
|
|
158
|
+
* @param {{ file: string, major: number, document: Record<string, unknown>, canonical: string }} subject
|
|
159
|
+
* the workflow under inspection.
|
|
160
|
+
* @returns {string[]} complaints, empty when the workflow is conformant.
|
|
161
|
+
*/
|
|
162
|
+
export function auditReusable(subject) {
|
|
163
|
+
const { canonical, document, file, major } = subject;
|
|
164
|
+
const problems = [];
|
|
165
|
+
const declared = workflowCallOf(document)?.inputs?.[INPUT_NAME];
|
|
166
|
+
if (declared === undefined) {
|
|
167
|
+
problems.push(`does not declare the \`${INPUT_NAME}\` input`);
|
|
168
|
+
} else if (declared.type !== "string" || declared.default !== "") {
|
|
169
|
+
problems.push(
|
|
170
|
+
`\`${INPUT_NAME}\` must be \`type: string\` with \`default: ''\` so an absent value is distinguishable from a declared one`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const job = document?.jobs?.[JOB_ID];
|
|
174
|
+
if (job === undefined) {
|
|
175
|
+
problems.push(
|
|
176
|
+
`has no \`${JOB_ID}\` job, so a caller is never told anything`
|
|
177
|
+
);
|
|
178
|
+
return problems;
|
|
179
|
+
}
|
|
180
|
+
const step = (job.steps ?? []).find(
|
|
181
|
+
candidate => candidate?.env?.DECLARED_MAJOR !== undefined
|
|
182
|
+
);
|
|
183
|
+
if (step === undefined) {
|
|
184
|
+
problems.push(`\`${JOB_ID}\` has no step declaring \`DECLARED_MAJOR\``);
|
|
185
|
+
return problems;
|
|
186
|
+
}
|
|
187
|
+
if (String(step.env.DECLARED_MAJOR) !== String(major)) {
|
|
188
|
+
problems.push(
|
|
189
|
+
`declares major ${step.env.DECLARED_MAJOR} but ${REGISTRY_REL} records ${major} — bump both in one commit`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (step.env.WORKFLOW_FILE !== file) {
|
|
193
|
+
problems.push(
|
|
194
|
+
`names \`${step.env.WORKFLOW_FILE}\` in WORKFLOW_FILE; a mismatch sends a reader to the wrong file`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (step.env.EXPECTED_MAJOR !== `\${{ inputs.${INPUT_NAME} }}`) {
|
|
198
|
+
problems.push(
|
|
199
|
+
`EXPECTED_MAJOR must read \`\${{ inputs.${INPUT_NAME} }}\`, otherwise the comparison is against something the caller never passed`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (String(step.run ?? "").trimEnd() !== canonical.trimEnd()) {
|
|
203
|
+
problems.push(
|
|
204
|
+
`the inlined assertion has drifted from ${CANONICAL_REL}; copy it back verbatim`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
return problems;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Every remote Lisa reference in one caller template, with the value seeded
|
|
212
|
+
* beside it.
|
|
213
|
+
*
|
|
214
|
+
* @param {Record<string, unknown> | null} document - a parsed caller template.
|
|
215
|
+
* @returns {{ job: string, target: string, seeded: unknown }[]} one per reference.
|
|
216
|
+
*/
|
|
217
|
+
export function callerReferences(document) {
|
|
218
|
+
const jobs = document?.jobs;
|
|
219
|
+
if (jobs === null || typeof jobs !== "object") return [];
|
|
220
|
+
const references = [];
|
|
221
|
+
for (const [job, definition] of Object.entries(jobs)) {
|
|
222
|
+
const uses = definition?.uses;
|
|
223
|
+
if (typeof uses !== "string") continue;
|
|
224
|
+
const match = REMOTE_USES_RE.exec(uses.trim());
|
|
225
|
+
if (match === null) continue;
|
|
226
|
+
references.push({
|
|
227
|
+
job,
|
|
228
|
+
seeded: definition?.with?.[INPUT_NAME],
|
|
229
|
+
target: match[1],
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return references;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* List every tracked file in `root`, relative to it.
|
|
237
|
+
*
|
|
238
|
+
* @param {string} root - the repository root.
|
|
239
|
+
* @returns {string[]} tracked paths.
|
|
240
|
+
*/
|
|
241
|
+
function listTrackedFiles(root) {
|
|
242
|
+
let stdout;
|
|
243
|
+
try {
|
|
244
|
+
stdout = boundedExecFileSync("git", ["-C", root, "ls-files", "-z"], {
|
|
245
|
+
encoding: "utf8",
|
|
246
|
+
maxBuffer: MAX_GIT_OUTPUT_BYTES,
|
|
247
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
248
|
+
});
|
|
249
|
+
} catch (error) {
|
|
250
|
+
throw new UsageError(
|
|
251
|
+
`could not list tracked files in ${root}: ${error.message}`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
return stdout.split("\0").filter(entry => entry !== "");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Parse argv into resolved options.
|
|
259
|
+
*
|
|
260
|
+
* @param {readonly string[]} argv - arguments (without node/script prefix).
|
|
261
|
+
* @returns {{ root: string, json: boolean }} options.
|
|
262
|
+
*/
|
|
263
|
+
export function parseArgs(argv) {
|
|
264
|
+
let root = null;
|
|
265
|
+
let json = false;
|
|
266
|
+
for (let index = 0; index < argv.length; index++) {
|
|
267
|
+
const arg = argv[index];
|
|
268
|
+
if (arg === "--json") {
|
|
269
|
+
json = true;
|
|
270
|
+
} else if (arg === "--root") {
|
|
271
|
+
const next = argv[index + 1];
|
|
272
|
+
if (next === undefined || next.startsWith("--")) {
|
|
273
|
+
throw new UsageError("--root requires a value");
|
|
274
|
+
}
|
|
275
|
+
root = next;
|
|
276
|
+
index += 1;
|
|
277
|
+
} else {
|
|
278
|
+
throw new UsageError(`unknown argument: ${arg}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return { json, root: path.resolve(root ?? REPO_ROOT) };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Read the registry and the canonical assertion body, or refuse.
|
|
286
|
+
*
|
|
287
|
+
* @param {string} root - the repository root.
|
|
288
|
+
* @returns {{ registry: Record<string, unknown>, canonical: string }} the inputs.
|
|
289
|
+
*/
|
|
290
|
+
function readContract(root) {
|
|
291
|
+
let registry;
|
|
292
|
+
try {
|
|
293
|
+
registry = JSON.parse(
|
|
294
|
+
fs.readFileSync(path.join(root, REGISTRY_REL), "utf8")
|
|
295
|
+
);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
throw new UsageError(`could not read ${REGISTRY_REL}: ${error.message}`);
|
|
298
|
+
}
|
|
299
|
+
if (registry?.workflows === null || typeof registry?.workflows !== "object") {
|
|
300
|
+
throw new UsageError(`${REGISTRY_REL} has no \`workflows\` mapping`);
|
|
301
|
+
}
|
|
302
|
+
let canonical;
|
|
303
|
+
try {
|
|
304
|
+
canonical = fs.readFileSync(path.join(root, CANONICAL_REL), "utf8");
|
|
305
|
+
} catch (error) {
|
|
306
|
+
throw new UsageError(`could not read ${CANONICAL_REL}: ${error.message}`);
|
|
307
|
+
}
|
|
308
|
+
return { canonical, registry };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Compare the registry against the reusable workflows on disk.
|
|
313
|
+
*
|
|
314
|
+
* @param {{ root: string, registry: Record<string, unknown>, canonical: string }} context
|
|
315
|
+
* the resolved inputs.
|
|
316
|
+
* @returns {{ findings: { where: string, problem: string }[], reusables: string[] }}
|
|
317
|
+
* findings plus the discovered reusable filenames.
|
|
318
|
+
*/
|
|
319
|
+
function auditReusables(context) {
|
|
320
|
+
const { canonical, registry, root } = context;
|
|
321
|
+
const directory = path.join(root, ".github", "workflows");
|
|
322
|
+
const findings = [];
|
|
323
|
+
const reusables = [];
|
|
324
|
+
const entries = fs.existsSync(directory) ? fs.readdirSync(directory) : [];
|
|
325
|
+
for (const name of entries.filter(file => /\.ya?ml$/.test(file)).sort()) {
|
|
326
|
+
const document = loadWorkflow(path.join(directory, name));
|
|
327
|
+
if (workflowCallOf(document) === null) continue;
|
|
328
|
+
reusables.push(name);
|
|
329
|
+
const entry = registry.workflows[name];
|
|
330
|
+
if (entry === undefined) {
|
|
331
|
+
findings.push({
|
|
332
|
+
problem: `reusable workflow is absent from ${REGISTRY_REL}; record a \`major\` for it, or an \`uncovered\` reason it cannot carry one`,
|
|
333
|
+
where: name,
|
|
334
|
+
});
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const problem = describeEntryProblem(entry);
|
|
338
|
+
if (problem !== null) {
|
|
339
|
+
findings.push({ problem: `${REGISTRY_REL}: ${problem}`, where: name });
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (entry.uncovered !== undefined) continue;
|
|
343
|
+
for (const complaint of auditReusable({
|
|
344
|
+
canonical,
|
|
345
|
+
document,
|
|
346
|
+
file: name,
|
|
347
|
+
major: entry.major,
|
|
348
|
+
})) {
|
|
349
|
+
findings.push({ problem: complaint, where: name });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (const name of Object.keys(registry.workflows)) {
|
|
353
|
+
if (!reusables.includes(name)) {
|
|
354
|
+
findings.push({
|
|
355
|
+
problem: `${REGISTRY_REL} records this workflow, but no reusable workflow of that name exists — a registry entry for nothing hides the absence of a real one`,
|
|
356
|
+
where: name,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return { findings, reusables };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Check every shipped caller template is seeded beside its `uses:` line.
|
|
365
|
+
*
|
|
366
|
+
* @param {{ root: string, registry: Record<string, unknown>, templates: readonly string[] }} context
|
|
367
|
+
* the resolved inputs.
|
|
368
|
+
* @returns {{ findings: { where: string, problem: string }[], checked: number }}
|
|
369
|
+
* findings plus the number of references examined.
|
|
370
|
+
*/
|
|
371
|
+
function auditTemplates(context) {
|
|
372
|
+
const { registry, root, templates } = context;
|
|
373
|
+
const findings = [];
|
|
374
|
+
let checked = 0;
|
|
375
|
+
for (const file of templates) {
|
|
376
|
+
const document = loadWorkflow(path.join(root, file));
|
|
377
|
+
for (const reference of callerReferences(document)) {
|
|
378
|
+
const entry = registry.workflows[reference.target];
|
|
379
|
+
if (entry === undefined || entry.major === undefined) continue;
|
|
380
|
+
checked += 1;
|
|
381
|
+
const expected = String(entry.major);
|
|
382
|
+
if (reference.seeded === undefined) {
|
|
383
|
+
findings.push({
|
|
384
|
+
problem: `job \`${reference.job}\` calls ${reference.target} but seeds no \`${INPUT_NAME}\`. The reference and the assertion are one unit: without it the run cannot tell a behaviour change from silence. Add \`${INPUT_NAME}: '${expected}'\` to its \`with:\` block`,
|
|
385
|
+
where: file,
|
|
386
|
+
});
|
|
387
|
+
} else if (String(reference.seeded) !== expected) {
|
|
388
|
+
findings.push({
|
|
389
|
+
problem: `job \`${reference.job}\` seeds ${reference.target} with major ${reference.seeded}, but the workflow now declares ${expected}. A shipped template must hand a NEW consumer the current major`,
|
|
390
|
+
where: file,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return { checked, findings };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Render the human-readable report.
|
|
400
|
+
*
|
|
401
|
+
* @param {{ findings: readonly { where: string, problem: string }[], summary: Record<string, number> }} report
|
|
402
|
+
* the report object.
|
|
403
|
+
* @returns {string} the rendered report.
|
|
404
|
+
*/
|
|
405
|
+
function humanReport(report) {
|
|
406
|
+
const { summary } = report;
|
|
407
|
+
if (report.findings.length === 0) {
|
|
408
|
+
return `✓ ${summary.reusables} reusable workflow(s) declare a contract major; ${summary.checked} caller reference(s) across ${summary.templates} template(s) are seeded with it`;
|
|
409
|
+
}
|
|
410
|
+
return [
|
|
411
|
+
...report.findings.map(
|
|
412
|
+
finding => `✗ ${finding.where}\n ${finding.problem}`
|
|
413
|
+
),
|
|
414
|
+
"",
|
|
415
|
+
"A reusable workflow tracked at @main changes under every consumer with no",
|
|
416
|
+
"pull request in their repository. The contract major is the only thing",
|
|
417
|
+
"telling them a compatible change from a breaking one — and a caller that",
|
|
418
|
+
"carries the reference without the assertion is back to silence while",
|
|
419
|
+
"looking perfectly healthy.",
|
|
420
|
+
].join("\n");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Run the gate. Returns the process exit code (does not call `exit`).
|
|
425
|
+
*
|
|
426
|
+
* @param {readonly string[]} argv - arguments (without node/script prefix).
|
|
427
|
+
* @param {{ stdout?: { write(s: string): void }, stderr?: { write(s: string): void } }} [io]
|
|
428
|
+
* injectable streams.
|
|
429
|
+
* @returns {number} exit code (0 clean, 1 finding, 2 not determined).
|
|
430
|
+
*/
|
|
431
|
+
export function main(argv, io = {}) {
|
|
432
|
+
const out = io.stdout ?? process.stdout;
|
|
433
|
+
const err = io.stderr ?? process.stderr;
|
|
434
|
+
let report;
|
|
435
|
+
let json = false;
|
|
436
|
+
try {
|
|
437
|
+
const opts = parseArgs(argv);
|
|
438
|
+
json = opts.json;
|
|
439
|
+
if (!fs.existsSync(opts.root) || !fs.statSync(opts.root).isDirectory()) {
|
|
440
|
+
throw new UsageError(`--root is not a directory: ${opts.root}`);
|
|
441
|
+
}
|
|
442
|
+
const { canonical, registry } = readContract(opts.root);
|
|
443
|
+
const reusable = auditReusables({ canonical, registry, root: opts.root });
|
|
444
|
+
if (reusable.reusables.length === 0) {
|
|
445
|
+
throw new UsageError(
|
|
446
|
+
`no reusable workflows found under ${opts.root}/.github/workflows — refusing to report a clean run for a scan that examined nothing`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
const templates = listTrackedFiles(opts.root).filter(file =>
|
|
450
|
+
TEMPLATE_RE.test(file)
|
|
451
|
+
);
|
|
452
|
+
if (templates.length === 0) {
|
|
453
|
+
throw new UsageError(
|
|
454
|
+
`no caller templates found under ${opts.root} — expected paths like expo/create-only/.github/workflows/*.yml. Refusing to report a clean run for a scan that examined nothing`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
const caller = auditTemplates({ registry, root: opts.root, templates });
|
|
458
|
+
report = {
|
|
459
|
+
findings: [...reusable.findings, ...caller.findings],
|
|
460
|
+
schemaVersion: 1,
|
|
461
|
+
summary: {
|
|
462
|
+
checked: caller.checked,
|
|
463
|
+
findings: reusable.findings.length + caller.findings.length,
|
|
464
|
+
reusables: reusable.reusables.length,
|
|
465
|
+
templates: templates.length,
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
} catch (error) {
|
|
469
|
+
err.write(`NOT DETERMINED: ${error.message}\n`);
|
|
470
|
+
return 2;
|
|
471
|
+
}
|
|
472
|
+
out.write(
|
|
473
|
+
`${json ? JSON.stringify(report, null, 2) : humanReport(report)}\n`
|
|
474
|
+
);
|
|
475
|
+
return report.findings.length === 0 ? 0 : 1;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (invokedAsScript(import.meta.url)) {
|
|
479
|
+
process.exitCode = main(process.argv.slice(2));
|
|
480
|
+
}
|