@codyswann/lisa 4.49.0 → 4.50.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all/copy-overwrite/scripts/check-conflict-markers.mjs +4 -1
- package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +163 -0
- package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +178 -1
- package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +89 -10
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +44 -0
- package/all/copy-overwrite/scripts/lisa-lint-staged-preflight.mjs +1 -1
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +24 -24
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +490 -81
- package/cdk/package-lisa/package.lisa.json +45 -15
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +76 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +121 -227
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/lisa-block-direct-issue-create.ts +45 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +44 -0
- package/expo/package-lisa/package.lisa.json +110 -53
- package/harper-fabric/package-lisa/package.lisa.json +62 -32
- package/nestjs/package-lisa/package.lisa.json +57 -27
- package/npm-package/package-lisa/package.lisa.json +43 -7
- package/package.json +11 -7
- package/phaser/copy-overwrite/ast-grep/rules/no-day-truncating-file-age-predicate.yml +50 -0
- package/phaser/package-lisa/package.lisa.json +62 -32
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +4 -1
- package/plugins/lisa/hooks/block-direct-issue-create.sh +89 -10
- package/plugins/lisa/hooks/discharge-work-item-gates.sh +7 -1
- package/plugins/lisa/hooks/parity-safety-net.sh +44 -0
- package/plugins/lisa/hooks/threshold-ratchet-families.mjs +12 -0
- package/plugins/lisa/rules/eager/00-rule-index.md +79 -0
- package/plugins/lisa/rules/eager/base-rules.md +10 -0
- package/plugins/lisa/rules/eager/config-resolution.md +1 -88
- package/plugins/lisa/rules/reference/automation-runbook-contract.md +97 -0
- package/plugins/lisa/rules/reference/bdd-e2e-coverage.md +56 -0
- package/plugins/lisa/rules/reference/blocker-containment.md +58 -0
- package/plugins/lisa/rules/reference/claim-archaeology.md +42 -0
- package/plugins/lisa/rules/reference/claim-evidence-mapping.md +83 -0
- package/plugins/lisa/rules/reference/claim-time-guards.md +37 -0
- package/plugins/lisa/rules/reference/config-resolution.md +97 -0
- package/plugins/lisa/rules/reference/control-reachability.md +44 -0
- package/plugins/lisa/rules/reference/convergent-review.md +31 -0
- package/plugins/lisa/rules/reference/credential-substrate-precedence.md +57 -0
- package/plugins/lisa/rules/reference/dependency-decision-records.md +37 -0
- package/plugins/lisa/rules/reference/dependency-internalization-kit.md +47 -0
- package/plugins/lisa/rules/reference/dependency-trust-classes.md +48 -0
- package/plugins/lisa/rules/reference/deployed-state-readback.md +52 -0
- package/plugins/lisa/rules/reference/derived-branch-plan.md +52 -0
- package/plugins/lisa/rules/reference/design-source-of-truth.md +43 -0
- package/plugins/lisa/rules/reference/design-value-binding.md +103 -0
- package/plugins/lisa/rules/reference/documentation-source-paths.md +18 -0
- package/plugins/lisa/rules/reference/leaf-only-lifecycle.md +75 -0
- package/plugins/lisa/rules/reference/observability-audit.md +20 -0
- package/plugins/lisa/rules/reference/prd-definition-of-ready.md +37 -0
- package/plugins/lisa/rules/reference/prd-lifecycle-rollup.md +36 -0
- package/plugins/lisa/rules/reference/pre-flight-autofill.md +88 -0
- package/plugins/lisa/rules/reference/promotion-contract.md +21 -0
- package/plugins/lisa/rules/reference/readiness-rubric.md +63 -0
- package/plugins/lisa/rules/reference/ready-role-filing.md +34 -0
- package/plugins/lisa/rules/reference/rejection-detection.md +69 -0
- package/plugins/lisa/rules/reference/repo-scope-split.md +48 -0
- package/plugins/lisa/rules/reference/reset-seed-coverage.md +55 -0
- package/plugins/lisa/rules/reference/usage-accounting.md +38 -0
- package/plugins/lisa/rules/reference/work-item-definition-of-ready.md +32 -0
- package/plugins/lisa/scripts/intake-blocker-reprobe.mjs +158 -1
- package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +4 -1
- package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +89 -10
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +44 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/intake-blocker-reprobe.mjs +158 -1
- package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +4 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/block-direct-issue-create.sh +89 -10
- package/plugins/lisa-copilot/hooks/discharge-work-item-gates.sh +7 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +44 -0
- package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +12 -0
- package/plugins/lisa-copilot/rules/eager/00-rule-index.md +79 -0
- package/plugins/lisa-copilot/rules/eager/base-rules.md +10 -0
- package/plugins/lisa-copilot/rules/eager/config-resolution.md +1 -88
- package/plugins/lisa-copilot/rules/reference/automation-runbook-contract.md +97 -0
- package/plugins/lisa-copilot/rules/reference/bdd-e2e-coverage.md +56 -0
- package/plugins/lisa-copilot/rules/reference/blocker-containment.md +58 -0
- package/plugins/lisa-copilot/rules/reference/claim-archaeology.md +42 -0
- package/plugins/lisa-copilot/rules/reference/claim-evidence-mapping.md +83 -0
- package/plugins/lisa-copilot/rules/reference/claim-time-guards.md +37 -0
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +97 -0
- package/plugins/lisa-copilot/rules/reference/control-reachability.md +44 -0
- package/plugins/lisa-copilot/rules/reference/convergent-review.md +31 -0
- package/plugins/lisa-copilot/rules/reference/credential-substrate-precedence.md +57 -0
- package/plugins/lisa-copilot/rules/reference/dependency-decision-records.md +37 -0
- package/plugins/lisa-copilot/rules/reference/dependency-internalization-kit.md +47 -0
- package/plugins/lisa-copilot/rules/reference/dependency-trust-classes.md +48 -0
- package/plugins/lisa-copilot/rules/reference/deployed-state-readback.md +52 -0
- package/plugins/lisa-copilot/rules/reference/derived-branch-plan.md +52 -0
- package/plugins/lisa-copilot/rules/reference/design-source-of-truth.md +43 -0
- package/plugins/lisa-copilot/rules/reference/design-value-binding.md +103 -0
- package/plugins/lisa-copilot/rules/reference/documentation-source-paths.md +18 -0
- package/plugins/lisa-copilot/rules/reference/leaf-only-lifecycle.md +75 -0
- package/plugins/lisa-copilot/rules/reference/observability-audit.md +20 -0
- package/plugins/lisa-copilot/rules/reference/prd-definition-of-ready.md +37 -0
- package/plugins/lisa-copilot/rules/reference/prd-lifecycle-rollup.md +36 -0
- package/plugins/lisa-copilot/rules/reference/pre-flight-autofill.md +88 -0
- package/plugins/lisa-copilot/rules/reference/promotion-contract.md +21 -0
- package/plugins/lisa-copilot/rules/reference/readiness-rubric.md +63 -0
- package/plugins/lisa-copilot/rules/reference/ready-role-filing.md +34 -0
- package/plugins/lisa-copilot/rules/reference/rejection-detection.md +69 -0
- package/plugins/lisa-copilot/rules/reference/repo-scope-split.md +48 -0
- package/plugins/lisa-copilot/rules/reference/reset-seed-coverage.md +55 -0
- package/plugins/lisa-copilot/rules/reference/usage-accounting.md +38 -0
- package/plugins/lisa-copilot/rules/reference/work-item-definition-of-ready.md +32 -0
- package/plugins/lisa-copilot/scripts/intake-blocker-reprobe.mjs +158 -1
- package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +4 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +89 -10
- package/plugins/lisa-cursor/hooks/discharge-work-item-gates.sh +7 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +44 -0
- package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +12 -0
- package/plugins/lisa-cursor/rules/00-rule-index.mdc +84 -0
- package/plugins/lisa-cursor/rules/automation-runbook-contract-reference.mdc +97 -0
- package/plugins/lisa-cursor/rules/base-rules.mdc +10 -0
- package/plugins/lisa-cursor/rules/bdd-e2e-coverage-reference.mdc +56 -0
- package/plugins/lisa-cursor/rules/blocker-containment-reference.mdc +58 -0
- package/plugins/lisa-cursor/rules/claim-archaeology-reference.mdc +42 -0
- package/plugins/lisa-cursor/rules/claim-evidence-mapping-reference.mdc +83 -0
- package/plugins/lisa-cursor/rules/claim-time-guards-reference.mdc +37 -0
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +97 -0
- package/plugins/lisa-cursor/rules/config-resolution.mdc +1 -88
- package/plugins/lisa-cursor/rules/control-reachability-reference.mdc +44 -0
- package/plugins/lisa-cursor/rules/convergent-review-reference.mdc +31 -0
- package/plugins/lisa-cursor/rules/credential-substrate-precedence-reference.mdc +57 -0
- package/plugins/lisa-cursor/rules/dependency-decision-records-reference.mdc +37 -0
- package/plugins/lisa-cursor/rules/dependency-internalization-kit-reference.mdc +47 -0
- package/plugins/lisa-cursor/rules/dependency-trust-classes-reference.mdc +48 -0
- package/plugins/lisa-cursor/rules/deployed-state-readback-reference.mdc +52 -0
- package/plugins/lisa-cursor/rules/derived-branch-plan-reference.mdc +52 -0
- package/plugins/lisa-cursor/rules/design-source-of-truth-reference.mdc +43 -0
- package/plugins/lisa-cursor/rules/design-value-binding-reference.mdc +103 -0
- package/plugins/lisa-cursor/rules/documentation-source-paths-reference.mdc +18 -0
- package/plugins/lisa-cursor/rules/leaf-only-lifecycle-reference.mdc +75 -0
- package/plugins/lisa-cursor/rules/observability-audit-reference.mdc +20 -0
- package/plugins/lisa-cursor/rules/prd-definition-of-ready-reference.mdc +37 -0
- package/plugins/lisa-cursor/rules/prd-lifecycle-rollup-reference.mdc +36 -0
- package/plugins/lisa-cursor/rules/pre-flight-autofill-reference.mdc +88 -0
- package/plugins/lisa-cursor/rules/promotion-contract-reference.mdc +21 -0
- package/plugins/lisa-cursor/rules/readiness-rubric-reference.mdc +63 -0
- package/plugins/lisa-cursor/rules/ready-role-filing-reference.mdc +34 -0
- package/plugins/lisa-cursor/rules/rejection-detection-reference.mdc +69 -0
- package/plugins/lisa-cursor/rules/repo-scope-split-reference.mdc +48 -0
- package/plugins/lisa-cursor/rules/reset-seed-coverage-reference.mdc +55 -0
- package/plugins/lisa-cursor/rules/usage-accounting-reference.mdc +38 -0
- package/plugins/lisa-cursor/rules/work-item-definition-of-ready-reference.mdc +32 -0
- package/plugins/lisa-cursor/scripts/intake-blocker-reprobe.mjs +158 -1
- package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +4 -1
- 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-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-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-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-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/src/base/hooks/block-direct-issue-create.sh +89 -10
- package/plugins/src/base/hooks/discharge-work-item-gates.sh +7 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +44 -0
- package/plugins/src/base/hooks/threshold-ratchet-families.mjs +12 -0
- package/plugins/src/base/rules/eager/00-rule-index.md +79 -0
- package/plugins/src/base/rules/eager/base-rules.md +10 -0
- package/plugins/src/base/rules/eager/config-resolution.md +1 -88
- package/plugins/src/base/rules/reference/automation-runbook-contract.md +97 -0
- package/plugins/src/base/rules/reference/bdd-e2e-coverage.md +56 -0
- package/plugins/src/base/rules/reference/blocker-containment.md +58 -0
- package/plugins/src/base/rules/reference/claim-archaeology.md +42 -0
- package/plugins/src/base/rules/reference/claim-evidence-mapping.md +83 -0
- package/plugins/src/base/rules/reference/claim-time-guards.md +37 -0
- package/plugins/src/base/rules/reference/config-resolution.md +97 -0
- package/plugins/src/base/rules/reference/control-reachability.md +44 -0
- package/plugins/src/base/rules/reference/convergent-review.md +31 -0
- package/plugins/src/base/rules/reference/credential-substrate-precedence.md +57 -0
- package/plugins/src/base/rules/reference/dependency-decision-records.md +37 -0
- package/plugins/src/base/rules/reference/dependency-internalization-kit.md +47 -0
- package/plugins/src/base/rules/reference/dependency-trust-classes.md +48 -0
- package/plugins/src/base/rules/reference/deployed-state-readback.md +52 -0
- package/plugins/src/base/rules/reference/derived-branch-plan.md +52 -0
- package/plugins/src/base/rules/reference/design-source-of-truth.md +43 -0
- package/plugins/src/base/rules/reference/design-value-binding.md +103 -0
- package/plugins/src/base/rules/reference/documentation-source-paths.md +18 -0
- package/plugins/src/base/rules/reference/leaf-only-lifecycle.md +75 -0
- package/plugins/src/base/rules/reference/observability-audit.md +20 -0
- package/plugins/src/base/rules/reference/prd-definition-of-ready.md +37 -0
- package/plugins/src/base/rules/reference/prd-lifecycle-rollup.md +36 -0
- package/plugins/src/base/rules/reference/pre-flight-autofill.md +88 -0
- package/plugins/src/base/rules/reference/promotion-contract.md +21 -0
- package/plugins/src/base/rules/reference/readiness-rubric.md +63 -0
- package/plugins/src/base/rules/reference/ready-role-filing.md +34 -0
- package/plugins/src/base/rules/reference/rejection-detection.md +69 -0
- package/plugins/src/base/rules/reference/repo-scope-split.md +48 -0
- package/plugins/src/base/rules/reference/reset-seed-coverage.md +55 -0
- package/plugins/src/base/rules/reference/usage-accounting.md +38 -0
- package/plugins/src/base/rules/reference/work-item-definition-of-ready.md +32 -0
- package/plugins/src/base/scripts/intake-blocker-reprobe.mjs +158 -1
- package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +4 -1
- package/rails/copy-overwrite/ast-grep/rules/no-day-truncating-file-age-predicate.yml +50 -0
- package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +12 -0
- package/scripts/check-eager-rule-budget.mjs +176 -0
- package/scripts/check-generated-artifact-merge-coverage.mjs +7 -0
- package/scripts/check-rules-pairing.sh +45 -14
- package/scripts/check-shipped-surface-removals.mjs +851 -0
- package/scripts/generate-export-surface.mjs +318 -0
- package/scripts/lib/shipped-surface.mjs +212 -0
- package/scripts/lisa-enforcement-fallback.sh +178 -1
- package/scripts/remedy-conformance.mjs +435 -0
- package/scripts/two-channel-couplings.json +2 -2
- package/typescript/copy-contents/.husky/pre-push +147 -2
- package/typescript/copy-overwrite/ast-grep/rules/no-day-truncating-file-age-predicate.yml +50 -0
- package/typescript/copy-overwrite/audit.ignore.config.json +0 -10
- package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +180 -29
- package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +12 -0
- package/typescript/package-lisa/package.lisa.json +72 -42
- package/plugins/lisa/rules/eager/automation-runbook-contract.md +0 -92
- package/plugins/lisa/rules/eager/bdd-e2e-coverage.md +0 -51
- package/plugins/lisa/rules/eager/blocker-containment.md +0 -53
- package/plugins/lisa/rules/eager/claim-archaeology.md +0 -37
- package/plugins/lisa/rules/eager/claim-evidence-mapping.md +0 -78
- package/plugins/lisa/rules/eager/claim-time-guards.md +0 -32
- package/plugins/lisa/rules/eager/control-reachability.md +0 -39
- package/plugins/lisa/rules/eager/convergent-review.md +0 -24
- package/plugins/lisa/rules/eager/credential-substrate-precedence.md +0 -54
- package/plugins/lisa/rules/eager/dependency-decision-records.md +0 -32
- package/plugins/lisa/rules/eager/dependency-internalization-kit.md +0 -42
- package/plugins/lisa/rules/eager/dependency-trust-classes.md +0 -43
- package/plugins/lisa/rules/eager/deployed-state-readback.md +0 -47
- package/plugins/lisa/rules/eager/derived-branch-plan.md +0 -47
- package/plugins/lisa/rules/eager/design-source-of-truth.md +0 -38
- package/plugins/lisa/rules/eager/design-value-binding.md +0 -98
- package/plugins/lisa/rules/eager/documentation-source-paths.md +0 -13
- package/plugins/lisa/rules/eager/factory-model.md +0 -41
- package/plugins/lisa/rules/eager/leaf-only-lifecycle.md +0 -70
- package/plugins/lisa/rules/eager/observability-audit.md +0 -15
- package/plugins/lisa/rules/eager/prd-definition-of-ready.md +0 -32
- package/plugins/lisa/rules/eager/prd-lifecycle-rollup.md +0 -31
- package/plugins/lisa/rules/eager/pre-flight-autofill.md +0 -84
- package/plugins/lisa/rules/eager/project-learnings.md +0 -22
- package/plugins/lisa/rules/eager/promotion-contract.md +0 -18
- package/plugins/lisa/rules/eager/readiness-rubric.md +0 -58
- package/plugins/lisa/rules/eager/ready-role-filing.md +0 -29
- package/plugins/lisa/rules/eager/rejection-detection.md +0 -64
- package/plugins/lisa/rules/eager/repo-scope-split.md +0 -43
- package/plugins/lisa/rules/eager/reset-seed-coverage.md +0 -50
- package/plugins/lisa/rules/eager/usage-accounting.md +0 -33
- package/plugins/lisa/rules/eager/work-item-definition-of-ready.md +0 -27
- package/plugins/lisa-copilot/rules/eager/automation-runbook-contract.md +0 -92
- package/plugins/lisa-copilot/rules/eager/bdd-e2e-coverage.md +0 -51
- package/plugins/lisa-copilot/rules/eager/blocker-containment.md +0 -53
- package/plugins/lisa-copilot/rules/eager/claim-archaeology.md +0 -37
- package/plugins/lisa-copilot/rules/eager/claim-evidence-mapping.md +0 -78
- package/plugins/lisa-copilot/rules/eager/claim-time-guards.md +0 -32
- package/plugins/lisa-copilot/rules/eager/control-reachability.md +0 -39
- package/plugins/lisa-copilot/rules/eager/convergent-review.md +0 -24
- package/plugins/lisa-copilot/rules/eager/credential-substrate-precedence.md +0 -54
- package/plugins/lisa-copilot/rules/eager/dependency-decision-records.md +0 -32
- package/plugins/lisa-copilot/rules/eager/dependency-internalization-kit.md +0 -42
- package/plugins/lisa-copilot/rules/eager/dependency-trust-classes.md +0 -43
- package/plugins/lisa-copilot/rules/eager/deployed-state-readback.md +0 -47
- package/plugins/lisa-copilot/rules/eager/derived-branch-plan.md +0 -47
- package/plugins/lisa-copilot/rules/eager/design-source-of-truth.md +0 -38
- package/plugins/lisa-copilot/rules/eager/design-value-binding.md +0 -98
- package/plugins/lisa-copilot/rules/eager/documentation-source-paths.md +0 -13
- package/plugins/lisa-copilot/rules/eager/factory-model.md +0 -41
- package/plugins/lisa-copilot/rules/eager/leaf-only-lifecycle.md +0 -70
- package/plugins/lisa-copilot/rules/eager/observability-audit.md +0 -15
- package/plugins/lisa-copilot/rules/eager/prd-definition-of-ready.md +0 -32
- package/plugins/lisa-copilot/rules/eager/prd-lifecycle-rollup.md +0 -31
- package/plugins/lisa-copilot/rules/eager/pre-flight-autofill.md +0 -84
- package/plugins/lisa-copilot/rules/eager/project-learnings.md +0 -22
- package/plugins/lisa-copilot/rules/eager/promotion-contract.md +0 -18
- package/plugins/lisa-copilot/rules/eager/readiness-rubric.md +0 -58
- package/plugins/lisa-copilot/rules/eager/ready-role-filing.md +0 -29
- package/plugins/lisa-copilot/rules/eager/rejection-detection.md +0 -64
- package/plugins/lisa-copilot/rules/eager/repo-scope-split.md +0 -43
- package/plugins/lisa-copilot/rules/eager/reset-seed-coverage.md +0 -50
- package/plugins/lisa-copilot/rules/eager/usage-accounting.md +0 -33
- package/plugins/lisa-copilot/rules/eager/work-item-definition-of-ready.md +0 -27
- package/plugins/lisa-cursor/rules/automation-runbook-contract.mdc +0 -97
- package/plugins/lisa-cursor/rules/bdd-e2e-coverage.mdc +0 -56
- package/plugins/lisa-cursor/rules/blocker-containment.mdc +0 -58
- package/plugins/lisa-cursor/rules/claim-archaeology.mdc +0 -42
- package/plugins/lisa-cursor/rules/claim-evidence-mapping.mdc +0 -83
- package/plugins/lisa-cursor/rules/claim-time-guards.mdc +0 -37
- package/plugins/lisa-cursor/rules/control-reachability.mdc +0 -44
- package/plugins/lisa-cursor/rules/convergent-review.mdc +0 -29
- package/plugins/lisa-cursor/rules/credential-substrate-precedence.mdc +0 -59
- package/plugins/lisa-cursor/rules/dependency-decision-records.mdc +0 -37
- package/plugins/lisa-cursor/rules/dependency-internalization-kit.mdc +0 -47
- package/plugins/lisa-cursor/rules/dependency-trust-classes.mdc +0 -48
- package/plugins/lisa-cursor/rules/deployed-state-readback.mdc +0 -52
- package/plugins/lisa-cursor/rules/derived-branch-plan.mdc +0 -52
- package/plugins/lisa-cursor/rules/design-source-of-truth.mdc +0 -43
- package/plugins/lisa-cursor/rules/design-value-binding.mdc +0 -103
- package/plugins/lisa-cursor/rules/documentation-source-paths.mdc +0 -18
- package/plugins/lisa-cursor/rules/factory-model.mdc +0 -46
- package/plugins/lisa-cursor/rules/leaf-only-lifecycle.mdc +0 -75
- package/plugins/lisa-cursor/rules/observability-audit.mdc +0 -20
- package/plugins/lisa-cursor/rules/prd-definition-of-ready.mdc +0 -37
- package/plugins/lisa-cursor/rules/prd-lifecycle-rollup.mdc +0 -36
- package/plugins/lisa-cursor/rules/pre-flight-autofill.mdc +0 -89
- package/plugins/lisa-cursor/rules/project-learnings.mdc +0 -27
- package/plugins/lisa-cursor/rules/promotion-contract.mdc +0 -23
- package/plugins/lisa-cursor/rules/readiness-rubric.mdc +0 -63
- package/plugins/lisa-cursor/rules/ready-role-filing.mdc +0 -34
- package/plugins/lisa-cursor/rules/rejection-detection.mdc +0 -69
- package/plugins/lisa-cursor/rules/repo-scope-split.mdc +0 -48
- package/plugins/lisa-cursor/rules/reset-seed-coverage.mdc +0 -55
- package/plugins/lisa-cursor/rules/usage-accounting.mdc +0 -38
- package/plugins/lisa-cursor/rules/work-item-definition-of-ready.mdc +0 -32
- package/plugins/src/base/rules/eager/automation-runbook-contract.md +0 -92
- package/plugins/src/base/rules/eager/bdd-e2e-coverage.md +0 -51
- package/plugins/src/base/rules/eager/blocker-containment.md +0 -53
- package/plugins/src/base/rules/eager/claim-archaeology.md +0 -37
- package/plugins/src/base/rules/eager/claim-evidence-mapping.md +0 -78
- package/plugins/src/base/rules/eager/claim-time-guards.md +0 -32
- package/plugins/src/base/rules/eager/control-reachability.md +0 -39
- package/plugins/src/base/rules/eager/convergent-review.md +0 -24
- package/plugins/src/base/rules/eager/credential-substrate-precedence.md +0 -54
- package/plugins/src/base/rules/eager/dependency-decision-records.md +0 -32
- package/plugins/src/base/rules/eager/dependency-internalization-kit.md +0 -42
- package/plugins/src/base/rules/eager/dependency-trust-classes.md +0 -43
- package/plugins/src/base/rules/eager/deployed-state-readback.md +0 -47
- package/plugins/src/base/rules/eager/derived-branch-plan.md +0 -47
- package/plugins/src/base/rules/eager/design-source-of-truth.md +0 -38
- package/plugins/src/base/rules/eager/design-value-binding.md +0 -98
- package/plugins/src/base/rules/eager/documentation-source-paths.md +0 -13
- package/plugins/src/base/rules/eager/factory-model.md +0 -41
- package/plugins/src/base/rules/eager/leaf-only-lifecycle.md +0 -70
- package/plugins/src/base/rules/eager/observability-audit.md +0 -15
- package/plugins/src/base/rules/eager/prd-definition-of-ready.md +0 -32
- package/plugins/src/base/rules/eager/prd-lifecycle-rollup.md +0 -31
- package/plugins/src/base/rules/eager/pre-flight-autofill.md +0 -84
- package/plugins/src/base/rules/eager/project-learnings.md +0 -22
- package/plugins/src/base/rules/eager/promotion-contract.md +0 -18
- package/plugins/src/base/rules/eager/readiness-rubric.md +0 -58
- package/plugins/src/base/rules/eager/ready-role-filing.md +0 -29
- package/plugins/src/base/rules/eager/rejection-detection.md +0 -64
- package/plugins/src/base/rules/eager/repo-scope-split.md +0 -43
- package/plugins/src/base/rules/eager/reset-seed-coverage.md +0 -50
- package/plugins/src/base/rules/eager/usage-accounting.md +0 -33
- package/plugins/src/base/rules/eager/work-item-definition-of-ready.md +0 -27
|
@@ -273,6 +273,178 @@ resolve_vintages() {
|
|
|
273
273
|
plugin_tree_version="$json_version"
|
|
274
274
|
fi
|
|
275
275
|
note_version "$plugin_tree_version" "$plugin_tree"
|
|
276
|
+
|
|
277
|
+
# The channel this dispatcher races. Deliberately NOT folded into
|
|
278
|
+
# `note_version`: the newest-on-disk maximum answers "is this copy behind
|
|
279
|
+
# something local", and the plugin channel's age is a separate question
|
|
280
|
+
# about a copy that runs in parallel rather than instead.
|
|
281
|
+
resolve_plugin_channel || true
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# ---------------------------------------------------------------------------
|
|
285
|
+
# The other channel
|
|
286
|
+
#
|
|
287
|
+
# YOU CANNOT RETIRE A REFUSAL BY SHIPPING A FIX.
|
|
288
|
+
#
|
|
289
|
+
# These guards reach an agent by two independent channels: this dispatcher,
|
|
290
|
+
# registered in `.claude/settings.json`, and the plugin manifest, which
|
|
291
|
+
# registers the same guards individually. An agent sees the UNION of the two
|
|
292
|
+
# verdicts, so a TIGHTENING on either channel takes effect at once while a
|
|
293
|
+
# RELAXATION is inert until the slower channel catches up — the stale copy goes
|
|
294
|
+
# on refusing, and its refusal wins. What the operator sees is a guard blocking
|
|
295
|
+
# something `main` already permits, which reads as the guard being WRONG rather
|
|
296
|
+
# than OLD, and the move that reading suggests is to route around it.
|
|
297
|
+
#
|
|
298
|
+
# The two channels are two different files refreshed by two different
|
|
299
|
+
# mechanisms: `scripts/lisa-hooks/` when `lisa apply` runs, the plugin's own
|
|
300
|
+
# `hooks/` when the plugin updates. They drift. Measured on one machine on one
|
|
301
|
+
# day, two checkouts of the SAME repository resolved plugin 4.32.2 and 4.47.0.
|
|
302
|
+
#
|
|
303
|
+
# `plugin_tree` above cannot see any of that: it is repo-relative, so it exists
|
|
304
|
+
# only inside the Lisa monorepo, and in a host project the plugin actually in
|
|
305
|
+
# force is somewhere else entirely. The vintage machinery written for #3205
|
|
306
|
+
# was therefore blind to the channel it races. This resolves it properly, from
|
|
307
|
+
# the record the runtime itself keeps.
|
|
308
|
+
#
|
|
309
|
+
# WHAT THIS RECORD CAN AND CANNOT ANSWER. The header above explains why a
|
|
310
|
+
# previous stand-down keyed on `installed_plugins.json` was removed: the record
|
|
311
|
+
# is project-blind as it was read then, enablement-blind (`enabledPlugins` can
|
|
312
|
+
# switch a plugin off without removing its entry), and session-blind (hooks load
|
|
313
|
+
# at session start; the record is rewritten on any install). Those defeat a
|
|
314
|
+
# LIVENESS question — "are the plugin's guards running in this session?" — and
|
|
315
|
+
# nothing on disk answers that one.
|
|
316
|
+
#
|
|
317
|
+
# This asks a different question: "what VINTAGE is the plugin installed for this
|
|
318
|
+
# project?" The record answers that directly, and the project-blindness is
|
|
319
|
+
# resolved by keying the lookup on this repo root rather than on the plugin name.
|
|
320
|
+
# The remaining two blindnesses bound the claim rather than break it: a skew
|
|
321
|
+
# reported here is a real difference between two copies on disk, but a disabled
|
|
322
|
+
# or not-yet-loaded plugin may mean those copies are not both firing right now.
|
|
323
|
+
# So the wording below reports what is INSTALLED and what the union WOULD mean,
|
|
324
|
+
# and never asserts that both channels are live. Reporting is also why this may
|
|
325
|
+
# use the record at all where a stand-down may not: an over-reported skew costs
|
|
326
|
+
# a line of notice, while an over-confident stand-down costs enforcement.
|
|
327
|
+
plugin_channel_version=""
|
|
328
|
+
plugin_channel_path=""
|
|
329
|
+
|
|
330
|
+
# Resolve the vintage of the guard channel the PLUGIN MANIFEST runs.
|
|
331
|
+
#
|
|
332
|
+
# `plugins/installed_plugins.json` records installs PER PROJECT DIRECTORY —
|
|
333
|
+
# which is exactly why one checkout sits versions behind another on the same
|
|
334
|
+
# disk — so the lookup is keyed on this repo root, not on the machine.
|
|
335
|
+
#
|
|
336
|
+
# A record is claimed only when its `installPath` sits under a `lisa/lisa/`
|
|
337
|
+
# marketplace cache. A locally-installed plugin does not match and is left
|
|
338
|
+
# UNRESOLVED on purpose: reporting "agree" about a copy that was never read is
|
|
339
|
+
# the failure this whole section exists to end, and an honest "I could not
|
|
340
|
+
# tell" is the safe direction.
|
|
341
|
+
#
|
|
342
|
+
# `grep` does the scanning because the record runs to tens of thousands of
|
|
343
|
+
# lines; the bash loop then sees a handful. Called only from resolve_vintages,
|
|
344
|
+
# so it inherits that latch and never runs on the silent allow path.
|
|
345
|
+
resolve_plugin_channel() {
|
|
346
|
+
local config_dir="${CLAUDE_CONFIG_DIR-}"
|
|
347
|
+
[ -n "$config_dir" ] || config_dir="${HOME-}/.claude"
|
|
348
|
+
local record="$config_dir/plugins/installed_plugins.json"
|
|
349
|
+
[ -f "$record" ] || return 1
|
|
350
|
+
|
|
351
|
+
local install_pattern="^[[:space:]]*\"installPath\"[[:space:]]*:[[:space:]]*\"(.*/lisa/lisa/[^\"]+)\""
|
|
352
|
+
local version_pattern="^[[:space:]]*\"version\"[[:space:]]*:[[:space:]]*\"([^\"]+)\""
|
|
353
|
+
local line
|
|
354
|
+
local candidate=""
|
|
355
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
356
|
+
# `grep -A` separates non-adjacent groups with `--`. A candidate must not
|
|
357
|
+
# survive across that boundary or one plugin's installPath answers for
|
|
358
|
+
# another plugin's version.
|
|
359
|
+
if [ "$line" = "--" ]; then
|
|
360
|
+
candidate=""
|
|
361
|
+
continue
|
|
362
|
+
fi
|
|
363
|
+
if [[ "$line" =~ $install_pattern ]]; then
|
|
364
|
+
candidate="${BASH_REMATCH[1]}"
|
|
365
|
+
continue
|
|
366
|
+
fi
|
|
367
|
+
if [ -n "$candidate" ] && [[ "$line" =~ $version_pattern ]]; then
|
|
368
|
+
plugin_channel_path="$candidate"
|
|
369
|
+
plugin_channel_version="${BASH_REMATCH[1]}"
|
|
370
|
+
return 0
|
|
371
|
+
fi
|
|
372
|
+
done < <(grep -F -A4 "\"projectPath\": \"$repo_root\"," "$record" 2>/dev/null)
|
|
373
|
+
return 1
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
# Verdict of the last classify_channel_skew call: agree | skew | undetermined.
|
|
377
|
+
#
|
|
378
|
+
# Three answers, never two. "The channels agree" and "I could not read one of
|
|
379
|
+
# them" are DIFFERENT facts, and collapsing them is how a probe reports success
|
|
380
|
+
# while measuring nothing.
|
|
381
|
+
#
|
|
382
|
+
# THIS PROBE REPORTS. IT NEVER BLOCKS. A permitted command stays permitted
|
|
383
|
+
# whatever the verdict, and the finding goes into the once-per-session notice.
|
|
384
|
+
# That is a decision, not an oversight, so here is the reasoning for whoever
|
|
385
|
+
# comes to change it:
|
|
386
|
+
#
|
|
387
|
+
# - The two error directions have asymmetric costs. An over-reported skew
|
|
388
|
+
# costs a line of notice; an over-confident stand-down costs enforcement.
|
|
389
|
+
# When the costs are that lopsided the direction is settled without needing
|
|
390
|
+
# to argue about likelihood.
|
|
391
|
+
# - Failing closed on `undetermined` would refuse work on every host with no
|
|
392
|
+
# `installed_plugins.json` — containers, fresh clones, non-Claude runtimes.
|
|
393
|
+
# That is a large population with nothing wrong with it, and refusing them
|
|
394
|
+
# is a bigger fault than the one being detected.
|
|
395
|
+
# - What is detected is two copies at different AGES, not a compromised
|
|
396
|
+
# guard. Blocking on it turns a diagnostic into an outage.
|
|
397
|
+
# - And the practical argument, which beats the principled one: skew is the
|
|
398
|
+
# common case on a developer machine right now, so as a blocker this would
|
|
399
|
+
# be a permanent stop-work and the first response would be to switch it
|
|
400
|
+
# off. A blocker everyone turns off protects nothing.
|
|
401
|
+
#
|
|
402
|
+
# Pinned by the test named "lets a permitted command through whatever the
|
|
403
|
+
# verdict is", which asserts exit 0 on both the skew and undetermined arms.
|
|
404
|
+
channel_skew_verdict="undetermined"
|
|
405
|
+
channel_skew_line=""
|
|
406
|
+
|
|
407
|
+
# Compare the vintage of the channel this dispatcher runs against the vintage
|
|
408
|
+
# of the channel the plugin manifest runs.
|
|
409
|
+
classify_channel_skew() {
|
|
410
|
+
local mine=""
|
|
411
|
+
if [ "$host_tree_used" -eq 1 ]; then
|
|
412
|
+
mine="$host_tree_version"
|
|
413
|
+
elif [ "$plugin_tree_used" -eq 1 ]; then
|
|
414
|
+
mine="$plugin_tree_version"
|
|
415
|
+
fi
|
|
416
|
+
|
|
417
|
+
if [ -z "$mine" ] || [ -z "$plugin_channel_version" ]; then
|
|
418
|
+
channel_skew_verdict="undetermined"
|
|
419
|
+
channel_skew_line=" cross-channel vintage UNDETERMINED — this dispatcher's guards could not be
|
|
420
|
+
compared with the plugin manifest's copies. Not agreement: an unread copy
|
|
421
|
+
can be any age, and a relaxation shipped to one channel stays inert until
|
|
422
|
+
the other catches up.
|
|
423
|
+
"
|
|
424
|
+
return 0
|
|
425
|
+
fi
|
|
426
|
+
|
|
427
|
+
if [ "$mine" = "$plugin_channel_version" ]; then
|
|
428
|
+
channel_skew_verdict="agree"
|
|
429
|
+
channel_skew_line=""
|
|
430
|
+
return 0
|
|
431
|
+
fi
|
|
432
|
+
|
|
433
|
+
channel_skew_verdict="skew"
|
|
434
|
+
# The sentence is kept whole on one line on purpose: it is the finding, and
|
|
435
|
+
# an operator greps for it.
|
|
436
|
+
channel_skew_line=" cross-channel vintage SKEW — this dispatcher runs lisa $mine; the plugin
|
|
437
|
+
installed for this project is lisa $plugin_channel_version at $plugin_channel_path.
|
|
438
|
+
When both fire on one tool call the agent sees the UNION of their verdicts,
|
|
439
|
+
which means:
|
|
440
|
+
YOU CANNOT RETIRE A REFUSAL BY SHIPPING A FIX.
|
|
441
|
+
A relaxation on the newer channel does nothing until the older one is
|
|
442
|
+
refreshed, so a block you cannot explain from \`main\` is the older copy
|
|
443
|
+
still enforcing.
|
|
444
|
+
repair: refresh both — \`npx @codyswann/lisa apply\` for this checkout's
|
|
445
|
+
guards, and update the installed plugin for the manifest's copies.
|
|
446
|
+
"
|
|
447
|
+
return 0
|
|
276
448
|
}
|
|
277
449
|
|
|
278
450
|
# Description of the last describe_vintage call.
|
|
@@ -610,8 +782,10 @@ if [ "$notice_due" -eq 1 ]; then
|
|
|
610
782
|
if [ "$plugin_tree_used" -eq 1 ]; then
|
|
611
783
|
note_tree_staleness "$plugin_tree" "$plugin_tree_version" "$PLUGIN_REPAIR"
|
|
612
784
|
fi
|
|
785
|
+
classify_channel_skew
|
|
613
786
|
|
|
614
|
-
if [ -n "$stale_notice" ] || [ -n "$shadowed" ] || [ -n "$missing" ]
|
|
787
|
+
if [ -n "$stale_notice" ] || [ -n "$shadowed" ] || [ -n "$missing" ] || \
|
|
788
|
+
[ -n "$channel_skew_line" ]; then
|
|
615
789
|
# Claim the session BEFORE printing. A failed `mkdir` suppresses this copy
|
|
616
790
|
# only when another process left the expected real directory behind. Every
|
|
617
791
|
# other failure leaves the claim unproven and prints, so an unwritable state
|
|
@@ -647,6 +821,9 @@ if [ "$notice_due" -eq 1 ]; then
|
|
|
647
821
|
if [ -n "$missing" ]; then
|
|
648
822
|
printf ' unresolved guards: %s (no copy was dispatched)\n' "$missing"
|
|
649
823
|
fi
|
|
824
|
+
if [ -n "$channel_skew_line" ]; then
|
|
825
|
+
printf '%s' "$channel_skew_line"
|
|
826
|
+
fi
|
|
650
827
|
} >&2
|
|
651
828
|
fi
|
|
652
829
|
fi
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Every remedy a guard prints must be one this environment permits (#3825).
|
|
4
|
+
*
|
|
5
|
+
* ## The defect
|
|
6
|
+
*
|
|
7
|
+
* A guard refuses correctly and then hands the operator an instruction that
|
|
8
|
+
* cannot work. The guard is fine; its remedy is the defect — and the failure is
|
|
9
|
+
* misattributed by construction, because when an agent follows the printed
|
|
10
|
+
* advice and is refused, the refusal names **the guard it obeyed**, not the
|
|
11
|
+
* advice that sent it there. So the evidence points away from the actual bug.
|
|
12
|
+
*
|
|
13
|
+
* Nothing executed a remedy string, so a remedy rotted exactly like a comment,
|
|
14
|
+
* except an agent will *act* on it. Three guards, three unrelated authors,
|
|
15
|
+
* accumulated broken remedies before anyone named the class.
|
|
16
|
+
*
|
|
17
|
+
* ## Why permission alone is not the check — measured, not assumed
|
|
18
|
+
*
|
|
19
|
+
* The obvious mechanism is "run the remedy through the guard chain, assert
|
|
20
|
+
* permitted". **That mechanism would have passed the instance that motivated
|
|
21
|
+
* the ticket.** Measured on this repository: `git stash push` is PERMITTED by
|
|
22
|
+
* `parity-safety-net.sh` — guard 7 refuses only `stash drop`/`clear`. So when a
|
|
23
|
+
* guard advised "use git stash to preserve work first", a permission check
|
|
24
|
+
* would have gone green while the advice pointed every reader at one stash
|
|
25
|
+
* stack shared by every worktree of the clone, racing concurrent agents.
|
|
26
|
+
*
|
|
27
|
+
* A remedy is therefore conformant only when BOTH hold:
|
|
28
|
+
*
|
|
29
|
+
* 1. the guard chain PERMITS it — nothing refuses the operator who obeys; and
|
|
30
|
+
* 2. it names no FORBIDDEN operation — an operation this repository has ruled
|
|
31
|
+
* unsafe for reasons no guard enforces at the command level.
|
|
32
|
+
*
|
|
33
|
+
* Check 2 exists precisely because check 1 cannot see hazards that are policy
|
|
34
|
+
* rather than pattern.
|
|
35
|
+
*
|
|
36
|
+
* ## Fail direction
|
|
37
|
+
*
|
|
38
|
+
* **Report-only on its findings, fail-closed on its own blindness.** It gates
|
|
39
|
+
* no commit and no push; a broken remedy is a documentation defect, and a guard
|
|
40
|
+
* that refused a contributor's push because some message was worded badly would
|
|
41
|
+
* be worse than the defect. But a probe whose guard did not refuse produced NO
|
|
42
|
+
* remedy text, and counting that as "no unfollowable remedies" is the reassuring
|
|
43
|
+
* answer for a measurement that never happened. Those become `NOT_EXAMINED`,
|
|
44
|
+
* and any of them makes the sweep `NOT_MEASURED` rather than `CONFORMING`.
|
|
45
|
+
* Exit codes carry the same split: 0 conforming, 1 findings, 2 not measured.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import { spawnSync } from "node:child_process";
|
|
49
|
+
import { realpathSync } from "node:fs";
|
|
50
|
+
import { fileURLToPath } from "node:url";
|
|
51
|
+
|
|
52
|
+
/** Per-remedy verdicts. */
|
|
53
|
+
export const REMEDY_VERDICTS = Object.freeze([
|
|
54
|
+
"PERMITTED",
|
|
55
|
+
"REFUSED",
|
|
56
|
+
"FORBIDDEN",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
/** Whole-sweep verdicts. */
|
|
60
|
+
export const REMEDY_SWEEP_VERDICTS = Object.freeze([
|
|
61
|
+
"CONFORMING",
|
|
62
|
+
"UNFOLLOWABLE_REMEDIES",
|
|
63
|
+
"NOT_MEASURED",
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Operations no remedy may recommend, whatever the guard chain permits.
|
|
68
|
+
*
|
|
69
|
+
* Each entry is a ruling this repository has already made and written down
|
|
70
|
+
* somewhere other than a command pattern. `git stash` is the founding member:
|
|
71
|
+
* `PRESERVE_GUIDANCE` in `parity-safety-net.sh` says "Do NOT reach for `git
|
|
72
|
+
* stash`. One stash stack is shared by every worktree of a clone, so a
|
|
73
|
+
* concurrently running agent can consume the entry you just pushed" — and yet
|
|
74
|
+
* the guard permits the command, because refusing it outright would break the
|
|
75
|
+
* legitimate single-worktree uses.
|
|
76
|
+
*
|
|
77
|
+
* `advisory` is what separates a recommendation from a prohibition. Guard 7's
|
|
78
|
+
* own message must keep containing the word "stash" while REFUSING it; only
|
|
79
|
+
* text that offers the operation as the way forward is a finding. Keying on the
|
|
80
|
+
* bare token would fail the one message doing the right thing — the mistake
|
|
81
|
+
* `parity-safety-net-no-stash-advice.test.ts` records making.
|
|
82
|
+
*/
|
|
83
|
+
export const FORBIDDEN_REMEDY_OPERATIONS = Object.freeze([
|
|
84
|
+
Object.freeze({
|
|
85
|
+
id: "shared-stash",
|
|
86
|
+
advisory: /\bgit\s+stash\s+(push|pop|save|apply)\b/i,
|
|
87
|
+
why: "one stash stack is shared by every worktree of a clone, so a concurrent agent can consume the entry",
|
|
88
|
+
}),
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
/** Command verbs a remedy line may begin with to count as a command. */
|
|
92
|
+
const REMEDY_VERBS = /^(git|gh|node|bun|npm|npx|bash|sh|mktemp|jq)\b/;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Markers that make a line talk ABOUT a command rather than instruct it.
|
|
96
|
+
*
|
|
97
|
+
* **Measured, not anticipated.** The first live run of this sweep reported six
|
|
98
|
+
* findings, every one of them the same string pulled out of
|
|
99
|
+
* `PRESERVE_GUIDANCE`'s sentence "Untracked files survive, exactly as they
|
|
100
|
+
* survive ...". That is a comparison. The guard is not telling anyone to run
|
|
101
|
+
* that command — it refuses it two guards further up the same file.
|
|
102
|
+
*
|
|
103
|
+
* So the extractor was making exactly the mistake #3815 and this ticket's own
|
|
104
|
+
* second comment describe: treating text ABOUT a thing as the thing. A
|
|
105
|
+
* conformance control that makes it reports its loudest findings against
|
|
106
|
+
* correct prose, and a control whose findings are wrong on day one gets
|
|
107
|
+
* switched off. That outcome matters more here than a missed remedy, because
|
|
108
|
+
* the entire value of this control is that somebody reads its output.
|
|
109
|
+
*
|
|
110
|
+
* The marker is looked for in a WINDOW of text immediately preceding the
|
|
111
|
+
* command, not on its line and not in its paragraph. Both of those were tried
|
|
112
|
+
* and both were measured wrong on this repository's real guard text:
|
|
113
|
+
*
|
|
114
|
+
* - LINE scope missed the founding case outright. The heredoc wraps, so
|
|
115
|
+
* "exactly as they" ends one line and "survive `git reset --hard`." begins
|
|
116
|
+
* the next; the marker and the command it qualifies are never on one line.
|
|
117
|
+
* - PARAGRAPH scope demotes correct remedies wholesale. `DESTRUCTIVE_GUIDANCE`
|
|
118
|
+
* opens a bullet with "Do not try to reword it" and then names three real
|
|
119
|
+
* remedies in the same bullet, all of which would vanish from the sweep.
|
|
120
|
+
*
|
|
121
|
+
* The window approximates the clause the command sits in, which is the scope
|
|
122
|
+
* the qualifier actually applies to. Mentions are not classified, and they are
|
|
123
|
+
* COUNTED AND REPORTED rather than dropped, so the coverage this rule costs
|
|
124
|
+
* stays visible instead of being silently absorbed.
|
|
125
|
+
*/
|
|
126
|
+
const MENTION_CONTEXT =
|
|
127
|
+
/\b(do not|don't|never|no longer|used to|as they survive|instead of|rather than|cannot|would|refuses?|blocks?|prohibit)/i;
|
|
128
|
+
|
|
129
|
+
/** How much text before a command is read for a mention marker. */
|
|
130
|
+
const MENTION_WINDOW = 64;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Pull the runnable commands out of a guard's refusal text.
|
|
134
|
+
*
|
|
135
|
+
* Two shapes, because guards use both: a backticked span, and an indented line
|
|
136
|
+
* inside a heredoc. Prose is skipped by requiring a known verb at the start —
|
|
137
|
+
* a remedy sentence is advice, and only its commands can be classified.
|
|
138
|
+
*
|
|
139
|
+
* A placeholder like `<path>` is substituted with a plausible path so the
|
|
140
|
+
* command reaches the guard in the shape an operator would actually type;
|
|
141
|
+
* leaving the angle brackets in would feed the guard a redirection.
|
|
142
|
+
*
|
|
143
|
+
* @param {string} text - A guard's refusal output.
|
|
144
|
+
* @returns {{ commands: readonly string[], mentions: readonly string[] }}
|
|
145
|
+
* Instructions to classify, and commands the text only talks about.
|
|
146
|
+
*/
|
|
147
|
+
export function extractRemedyCommands(text) {
|
|
148
|
+
const source = typeof text === "string" ? text : "";
|
|
149
|
+
const commands = [];
|
|
150
|
+
const mentions = [];
|
|
151
|
+
|
|
152
|
+
// Backticked spans, judged against the clause that precedes each one. The
|
|
153
|
+
// source is flattened so a wrapped qualifier still sits inside the window.
|
|
154
|
+
const flat = source.replace(/\s+/g, " ");
|
|
155
|
+
for (const match of flat.matchAll(/`([^`]+)`/g)) {
|
|
156
|
+
const candidate = match[1].trim();
|
|
157
|
+
if (!REMEDY_VERBS.test(candidate)) continue;
|
|
158
|
+
const before = flat.slice(
|
|
159
|
+
Math.max(0, match.index - MENTION_WINDOW),
|
|
160
|
+
match.index
|
|
161
|
+
);
|
|
162
|
+
(MENTION_CONTEXT.test(before) ? mentions : commands).push(candidate);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Indented lines are code blocks, which are instructions by convention even
|
|
166
|
+
// where the surrounding prose is comparative.
|
|
167
|
+
for (const line of source.split("\n")) {
|
|
168
|
+
if (!/^\s{2,}\S/.test(line)) continue;
|
|
169
|
+
const trimmed = line.trim();
|
|
170
|
+
if (REMEDY_VERBS.test(trimmed) || /^[a-z_]+="\$\(/.test(trimmed)) {
|
|
171
|
+
commands.push(trimmed);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const instructions = new Set(commands.map(substitutePlaceholders));
|
|
176
|
+
return {
|
|
177
|
+
commands: [...instructions],
|
|
178
|
+
// A command that appears BOTH as an instruction and as a mention is an
|
|
179
|
+
// instruction: the code block is the operative text.
|
|
180
|
+
mentions: [...new Set(mentions)].filter(
|
|
181
|
+
mention => !instructions.has(substitutePlaceholders(mention))
|
|
182
|
+
),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Replace `<placeholder>` tokens with a plausible concrete path.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} command - A command that may carry placeholders.
|
|
190
|
+
* @returns {string} The command with placeholders made concrete.
|
|
191
|
+
*/
|
|
192
|
+
function substitutePlaceholders(command) {
|
|
193
|
+
return command.replace(/<[a-z][a-z -]*>/gi, "tmp/remedy-conformance-probe");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Classify one remedy command.
|
|
198
|
+
*
|
|
199
|
+
* Forbidden beats refused: a remedy naming the shared stash is a finding even
|
|
200
|
+
* where the guard chain waves it through, which is the whole reason check 2
|
|
201
|
+
* exists.
|
|
202
|
+
*
|
|
203
|
+
* @param {string} command - The remedy command as printed.
|
|
204
|
+
* @param {(command: string) => number | null} permits - Guard-chain classifier,
|
|
205
|
+
* returning the guard's exit status for the command.
|
|
206
|
+
* @returns {{ command: string, verdict: string, reason: string }} The verdict.
|
|
207
|
+
*/
|
|
208
|
+
export function classifyRemedy(command, permits) {
|
|
209
|
+
const forbidden = FORBIDDEN_REMEDY_OPERATIONS.find(operation =>
|
|
210
|
+
operation.advisory.test(command)
|
|
211
|
+
);
|
|
212
|
+
if (forbidden) {
|
|
213
|
+
return {
|
|
214
|
+
command,
|
|
215
|
+
verdict: "FORBIDDEN",
|
|
216
|
+
reason: `${forbidden.id}: ${forbidden.why}`,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const status = permits(command);
|
|
221
|
+
return status === 0
|
|
222
|
+
? { command, verdict: "PERMITTED", reason: "guard-chain-permits" }
|
|
223
|
+
: {
|
|
224
|
+
command,
|
|
225
|
+
verdict: "REFUSED",
|
|
226
|
+
reason: `guard-chain-refuses (exit ${status})`,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Sweep a set of probes and report every unfollowable remedy.
|
|
232
|
+
*
|
|
233
|
+
* @param {{
|
|
234
|
+
* readonly probes?: readonly { label: string, refusal: string | null }[]
|
|
235
|
+
* readonly permits?: (command: string) => number | null
|
|
236
|
+
* }} input - `refusal` is the guard's output, or `null` when the guard did not
|
|
237
|
+
* refuse and therefore produced no remedy to examine.
|
|
238
|
+
* @returns {{
|
|
239
|
+
* readonly verdict: string
|
|
240
|
+
* readonly reasons: readonly string[]
|
|
241
|
+
* readonly examinedCount: number
|
|
242
|
+
* readonly commandCount: number
|
|
243
|
+
* readonly mentionCount: number
|
|
244
|
+
* readonly findings: readonly {
|
|
245
|
+
* readonly command: string
|
|
246
|
+
* readonly verdict: string
|
|
247
|
+
* readonly reason: string
|
|
248
|
+
* readonly probe: string
|
|
249
|
+
* }[]
|
|
250
|
+
* readonly notExamined: readonly string[]
|
|
251
|
+
* readonly reportOnly: true
|
|
252
|
+
* }} The sweep result.
|
|
253
|
+
*/
|
|
254
|
+
export function sweepRemedyConformance(input = {}) {
|
|
255
|
+
const probes = Array.isArray(input.probes) ? input.probes : null;
|
|
256
|
+
const permits = input.permits ?? (() => 0);
|
|
257
|
+
|
|
258
|
+
if (probes === null) {
|
|
259
|
+
return emptySweep("NOT_MEASURED", ["probe-list-unavailable"]);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const notExamined = probes
|
|
263
|
+
.filter(probe => typeof probe?.refusal !== "string")
|
|
264
|
+
.map(probe => String(probe?.label ?? "<unlabelled>"));
|
|
265
|
+
|
|
266
|
+
const findings = [];
|
|
267
|
+
let commandCount = 0;
|
|
268
|
+
let mentionCount = 0;
|
|
269
|
+
for (const probe of probes) {
|
|
270
|
+
if (typeof probe?.refusal !== "string") continue;
|
|
271
|
+
const extracted = extractRemedyCommands(probe.refusal);
|
|
272
|
+
mentionCount += extracted.mentions.length;
|
|
273
|
+
for (const command of extracted.commands) {
|
|
274
|
+
commandCount += 1;
|
|
275
|
+
const verdict = classifyRemedy(command, permits);
|
|
276
|
+
if (verdict.verdict !== "PERMITTED") {
|
|
277
|
+
findings.push({ ...verdict, probe: probe.label });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const examinedCount = probes.length - notExamined.length;
|
|
283
|
+
|
|
284
|
+
// Blindness outranks findings: a sweep that read no remedy text from part of
|
|
285
|
+
// the guard set cannot call the rest of it conformant.
|
|
286
|
+
if (notExamined.length > 0) {
|
|
287
|
+
return {
|
|
288
|
+
verdict: "NOT_MEASURED",
|
|
289
|
+
reasons: ["guard-did-not-refuse"],
|
|
290
|
+
examinedCount,
|
|
291
|
+
commandCount,
|
|
292
|
+
mentionCount,
|
|
293
|
+
findings,
|
|
294
|
+
notExamined,
|
|
295
|
+
reportOnly: true,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
verdict: findings.length > 0 ? "UNFOLLOWABLE_REMEDIES" : "CONFORMING",
|
|
301
|
+
reasons:
|
|
302
|
+
findings.length > 0
|
|
303
|
+
? ["remedies-the-environment-refuses-or-forbids"]
|
|
304
|
+
: ["every-printed-remedy-permitted-and-allowed"],
|
|
305
|
+
examinedCount,
|
|
306
|
+
commandCount,
|
|
307
|
+
mentionCount,
|
|
308
|
+
findings,
|
|
309
|
+
notExamined,
|
|
310
|
+
reportOnly: true,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* A sweep result carrying nothing, for the paths that measured nothing.
|
|
316
|
+
*
|
|
317
|
+
* @param {string} verdict - The sweep verdict.
|
|
318
|
+
* @param {readonly string[]} reasons - Why.
|
|
319
|
+
* @returns {ReturnType<typeof sweepRemedyConformance>} The empty sweep.
|
|
320
|
+
*/
|
|
321
|
+
function emptySweep(verdict, reasons) {
|
|
322
|
+
return {
|
|
323
|
+
verdict,
|
|
324
|
+
reasons,
|
|
325
|
+
examinedCount: 0,
|
|
326
|
+
commandCount: 0,
|
|
327
|
+
mentionCount: 0,
|
|
328
|
+
findings: [],
|
|
329
|
+
notExamined: [],
|
|
330
|
+
reportOnly: true,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The CLI exit status for a sweep.
|
|
336
|
+
*
|
|
337
|
+
* `2` is load-bearing: a sweep that examined nothing must not exit 0 beside an
|
|
338
|
+
* empty finding list, which is indistinguishable from a conformant guard set.
|
|
339
|
+
*
|
|
340
|
+
* @param {{ readonly verdict?: string }} sweep - A sweep result.
|
|
341
|
+
* @returns {0 | 1 | 2} The exit status.
|
|
342
|
+
*/
|
|
343
|
+
export function remedyConformanceExitCode(sweep) {
|
|
344
|
+
if (sweep?.verdict === "CONFORMING") return 0;
|
|
345
|
+
if (sweep?.verdict === "UNFOLLOWABLE_REMEDIES") return 1;
|
|
346
|
+
return 2;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Render the sweep for an operator.
|
|
351
|
+
*
|
|
352
|
+
* @param {ReturnType<typeof sweepRemedyConformance>} sweep - A sweep result.
|
|
353
|
+
* @returns {string} A terminal-first report.
|
|
354
|
+
*/
|
|
355
|
+
export function formatRemedyConformanceReport(sweep) {
|
|
356
|
+
const lines = [`Remedy conformance: ${sweep.verdict}`];
|
|
357
|
+
|
|
358
|
+
if (sweep.verdict === "NOT_MEASURED") {
|
|
359
|
+
lines.push(
|
|
360
|
+
`No remedy text was read for ${sweep.notExamined.length} probe(s) (${sweep.reasons.join(", ")}).`,
|
|
361
|
+
"This is not a conformant guard set — it is an unanswered question.",
|
|
362
|
+
...sweep.notExamined.map(label => ` ? ${label}`)
|
|
363
|
+
);
|
|
364
|
+
return `${lines.join("\n")}\n`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
lines.push(
|
|
368
|
+
`Examined ${sweep.examinedCount} guard refusal(s) and classified ${sweep.commandCount} printed command(s).`,
|
|
369
|
+
`Not classified: ${sweep.mentionCount} command(s) the text only mentions (comparisons and prohibitions).`
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
if (sweep.verdict === "CONFORMING") {
|
|
373
|
+
lines.push(
|
|
374
|
+
"Every command any examined guard prints is permitted here and names no forbidden operation."
|
|
375
|
+
);
|
|
376
|
+
return `${lines.join("\n")}\n`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
lines.push("These printed remedies cannot be followed as written:");
|
|
380
|
+
for (const finding of sweep.findings) {
|
|
381
|
+
lines.push(` ! [${finding.probe}] ${finding.command}`);
|
|
382
|
+
lines.push(` ${finding.verdict}: ${finding.reason}`);
|
|
383
|
+
}
|
|
384
|
+
return `${lines.join("\n")}\n`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Build a guard-chain classifier that runs the real shipped guard.
|
|
389
|
+
*
|
|
390
|
+
* @param {string} guardPath - Path to the shipped guard script.
|
|
391
|
+
* @param {string} cwd - Working directory reported to the guard.
|
|
392
|
+
* @returns {(command: string) => number | null} The classifier.
|
|
393
|
+
*/
|
|
394
|
+
export function guardChainClassifier(guardPath, cwd) {
|
|
395
|
+
return command =>
|
|
396
|
+
spawnSync("/bin/bash", [guardPath], {
|
|
397
|
+
input: JSON.stringify({
|
|
398
|
+
tool_name: "Bash",
|
|
399
|
+
tool_input: { command },
|
|
400
|
+
cwd,
|
|
401
|
+
}),
|
|
402
|
+
encoding: "utf8",
|
|
403
|
+
}).status;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* True when `moduleUrl` names the module node was asked to run.
|
|
408
|
+
*
|
|
409
|
+
* @param {string} moduleUrl - The caller's own `import.meta.url`.
|
|
410
|
+
* @param {string | undefined} [argv1] - Entry path.
|
|
411
|
+
* @returns {boolean} Whether to run the CLI body.
|
|
412
|
+
*/
|
|
413
|
+
export function invokedAsScript(moduleUrl, argv1 = process.argv[1]) {
|
|
414
|
+
if (!argv1) return false;
|
|
415
|
+
try {
|
|
416
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
|
|
417
|
+
} catch {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (invokedAsScript(import.meta.url)) {
|
|
423
|
+
const chunks = [];
|
|
424
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
425
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
426
|
+
const sweep = sweepRemedyConformance({
|
|
427
|
+
probes: raw.length === 0 ? null : JSON.parse(raw).probes,
|
|
428
|
+
permits: guardChainClassifier(
|
|
429
|
+
process.argv[2] ?? "plugins/lisa/hooks/parity-safety-net.sh",
|
|
430
|
+
process.cwd()
|
|
431
|
+
),
|
|
432
|
+
});
|
|
433
|
+
process.stdout.write(formatRemedyConformanceReport(sweep));
|
|
434
|
+
process.exitCode = remedyConformanceExitCode(sweep);
|
|
435
|
+
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"quality.yml::scripts/generate-nightly-e2e-guard-certificate.mjs": "Lisa-source-only branch. Both reusable jobs require `github.repository == 'CodySwannGT/lisa'` before the step can start, and the run body repeats that exact repository guard before checking this artifact path. Ordinary and private host callers therefore skip the step without reading or fetching from their own tree. The scoped branch fetches only the immutable refs needed to behavior-test Lisa's own retained certificate inputs. Ratified because the caller-tree read is unreachable outside this repository, not because an undelivered host dependency is tolerated."
|
|
5
5
|
},
|
|
6
6
|
"inspected": {
|
|
7
|
-
"workflows":
|
|
8
|
-
"steps":
|
|
7
|
+
"workflows": 23,
|
|
8
|
+
"steps": 719,
|
|
9
9
|
"couplings": 34,
|
|
10
10
|
"inventory": 77
|
|
11
11
|
},
|