@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
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# This file is managed by Lisa and IS replaced on each `lisa` run.
|
|
2
|
+
# Do not edit directly — durable changes belong upstream in Lisa.
|
|
3
|
+
|
|
4
|
+
# shellcheck shell=bash
|
|
5
|
+
# Evaluate a dual-channel guard once per tool call, without de-registering it.
|
|
6
|
+
#
|
|
7
|
+
# Sourced — never executed — by every guard that Lisa registers on BOTH of its
|
|
8
|
+
# enforcement channels: the repository dispatcher
|
|
9
|
+
# (`scripts/lisa-enforcement-fallback.sh`, which sweeps the guard roster in one
|
|
10
|
+
# process) and the plugin manifest, which registers the same guards
|
|
11
|
+
# individually. On a machine where both are live the harness runs each guard
|
|
12
|
+
# twice for one tool call. The verdicts agree — the guards are idempotent — so
|
|
13
|
+
# the defect is cost, not correctness (CodySwannGT/lisa#3814).
|
|
14
|
+
#
|
|
15
|
+
# WHY THIS IS NOT "DROP ONE REGISTRATION".
|
|
16
|
+
#
|
|
17
|
+
# The obvious fix is to de-register the fallback where the plugin covers the
|
|
18
|
+
# same ground. That fix cannot be made safely from here, and the reason is
|
|
19
|
+
# written into the dispatcher itself: plugin-hook LIVENESS is not observable
|
|
20
|
+
# from disk. `installed_plugins.json` answers "was this plugin ever installed",
|
|
21
|
+
# which is project-blind, enablement-blind and session-blind — hooks load at
|
|
22
|
+
# session start, the record is rewritten on any install. A stand-down keyed on
|
|
23
|
+
# that record switched enforcement off in sessions where the plugin hooks were
|
|
24
|
+
# not loaded, and did it silently. So the dispatcher runs unconditionally and
|
|
25
|
+
# always will.
|
|
26
|
+
#
|
|
27
|
+
# What IS observable is EXECUTION. A guard that has already run in this session
|
|
28
|
+
# from a different directory is proof — not inference — that a second channel is
|
|
29
|
+
# live right now. This file turns that observation into a per-call memo, so the
|
|
30
|
+
# second copy of an identical guard replays a verdict the first one produced
|
|
31
|
+
# instead of computing it again. Nothing is de-registered, no matcher narrows,
|
|
32
|
+
# and a host with the plugin absent takes the single-channel path where the memo
|
|
33
|
+
# can never hit.
|
|
34
|
+
#
|
|
35
|
+
# WHAT THE MEMO IS ALLOWED TO SHORT-CIRCUIT, and the three conditions that make
|
|
36
|
+
# it sound. Every one of them is a REFUSAL to dedupe when it cannot be proven:
|
|
37
|
+
#
|
|
38
|
+
# 1. BYTE-IDENTICAL CODE. The digest covers the guard script and this library
|
|
39
|
+
# verbatim. Two channels at different vintages therefore compute different
|
|
40
|
+
# keys and both run — which is required, because the agent is meant to see
|
|
41
|
+
# the UNION of two vintages' verdicts, and a tightening on either channel
|
|
42
|
+
# must take effect at once. Deduplicating across vintages would let the
|
|
43
|
+
# older, weaker copy answer for the newer one.
|
|
44
|
+
# 2. IDENTICAL PAYLOAD, SAME TOOL CALL. The digest covers the payload and a
|
|
45
|
+
# per-call discriminator — the transcript's size, which grows with every
|
|
46
|
+
# turn. Without the discriminator a command repeated later in the session
|
|
47
|
+
# would hit a memo written before the repository changed underneath it, and
|
|
48
|
+
# a guard that classifies against the working tree would be answering with
|
|
49
|
+
# a stale verdict. No discriminator available means no memo.
|
|
50
|
+
# 3. ALLOW VERDICTS ONLY. A refusal is never memoised and never replayed. That
|
|
51
|
+
# is deliberate: refusals are the rare path, so deduplicating them saves
|
|
52
|
+
# nothing measurable, and it removes the whole class of hazard the ticket
|
|
53
|
+
# names — a replayed refusal whose producing channel died mid-run, or a
|
|
54
|
+
# refusal reaching the agent without the message that explains it. On a
|
|
55
|
+
# refusal every channel evaluates and speaks exactly as it does today.
|
|
56
|
+
#
|
|
57
|
+
# COST ON THE PATH THAT GETS NO BENEFIT. A host with one channel must not pay
|
|
58
|
+
# for a mechanism it cannot use. The channel check is builtins only — one small
|
|
59
|
+
# file read, no subshell, no fork — and returns before the digest is computed
|
|
60
|
+
# unless a second channel has actually been seen. The digest, and the one `stat`
|
|
61
|
+
# that feeds it the per-call discriminator, are reached only where a second
|
|
62
|
+
# channel is live, which is exactly where they are repaid.
|
|
63
|
+
|
|
64
|
+
# Key this guard would memoise under, once computed. Empty means "record
|
|
65
|
+
# nothing on exit".
|
|
66
|
+
lisa_guard_memo_key=""
|
|
67
|
+
|
|
68
|
+
# File that memo is written to.
|
|
69
|
+
lisa_guard_memo_file=""
|
|
70
|
+
|
|
71
|
+
# Base directory for memo state, resolved at most once per process.
|
|
72
|
+
lisa_guard_memo_base=""
|
|
73
|
+
|
|
74
|
+
# Whether a key was computed and a memo may be recorded on exit.
|
|
75
|
+
lisa_guard_memo_ready=0
|
|
76
|
+
|
|
77
|
+
# Digest tool for the memo key, resolved once. Empty disables the memo.
|
|
78
|
+
lisa_guard_memo_digest_tool=""
|
|
79
|
+
|
|
80
|
+
# Whether a directory is a real directory this user owns, and not a symlink —
|
|
81
|
+
# builtins only, so this costs no process.
|
|
82
|
+
#
|
|
83
|
+
# `-O` is the load-bearing test: a memo planted by another user would suppress a
|
|
84
|
+
# refusal, so a directory this user does not own is never used. The dispatcher
|
|
85
|
+
# validates the whole parent chain before exporting `LISA_GUARD_MEMO_DIR`; this
|
|
86
|
+
# re-checks the leaf rather than trusting the environment variable, because an
|
|
87
|
+
# exported path is an input like any other.
|
|
88
|
+
#
|
|
89
|
+
# $1 - candidate directory
|
|
90
|
+
lisa_guard_memo_dir_usable() {
|
|
91
|
+
[ -n "${1:-}" ] || return 1
|
|
92
|
+
[ -d "$1" ] || return 1
|
|
93
|
+
[ ! -L "$1" ] || return 1
|
|
94
|
+
[ -O "$1" ] || return 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Establish the memo base directory.
|
|
98
|
+
#
|
|
99
|
+
# Preferred source is `LISA_GUARD_MEMO_DIR`, exported by the dispatcher, which
|
|
100
|
+
# has already resolved `TMPDIR` physically and walked every parent for ownership
|
|
101
|
+
# and mode. Guards it spawns therefore pay nothing for that walk. A guard
|
|
102
|
+
# reached by the plugin channel has no such parent, so it creates the directory
|
|
103
|
+
# itself under `TMPDIR` — private by umask, and used only when this user owns
|
|
104
|
+
# it. If neither works the memo is unavailable and every guard evaluates, which
|
|
105
|
+
# is today's behaviour.
|
|
106
|
+
lisa_guard_memo_resolve_base() {
|
|
107
|
+
[ -z "$lisa_guard_memo_base" ] || return 0
|
|
108
|
+
|
|
109
|
+
local candidate="${LISA_GUARD_MEMO_DIR:-}"
|
|
110
|
+
if lisa_guard_memo_dir_usable "$candidate"; then
|
|
111
|
+
lisa_guard_memo_base="$candidate"
|
|
112
|
+
return 0
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
local uid="${LISA_GUARD_MEMO_UID:-}"
|
|
116
|
+
if [ -z "$uid" ]; then
|
|
117
|
+
uid="$(id -u 2>/dev/null || printf 'unknown')"
|
|
118
|
+
fi
|
|
119
|
+
candidate="${TMPDIR:-/tmp}"
|
|
120
|
+
candidate="${candidate%/}/lisa-guard-memo-$uid"
|
|
121
|
+
if [ ! -e "$candidate" ] && [ ! -L "$candidate" ]; then
|
|
122
|
+
(umask 077 && mkdir -p "$candidate") 2>/dev/null || true
|
|
123
|
+
fi
|
|
124
|
+
if lisa_guard_memo_dir_usable "$candidate"; then
|
|
125
|
+
lisa_guard_memo_base="$candidate"
|
|
126
|
+
return 0
|
|
127
|
+
fi
|
|
128
|
+
return 1
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Resolve a SHA-256 tool, or report that there is none.
|
|
132
|
+
#
|
|
133
|
+
# Two spellings because the guards run on macOS, on Linux CI images, and in
|
|
134
|
+
# containers that carry neither. A missing digest tool disables dedupe rather
|
|
135
|
+
# than degrading it to something weaker: a cheap checksum could collide, and a
|
|
136
|
+
# collision here means a DIFFERENT payload reads as already-allowed, which is
|
|
137
|
+
# lost enforcement rather than a slow guard.
|
|
138
|
+
lisa_guard_memo_resolve_digest() {
|
|
139
|
+
[ -z "$lisa_guard_memo_digest_tool" ] || return 0
|
|
140
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
141
|
+
lisa_guard_memo_digest_tool="shasum"
|
|
142
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
143
|
+
lisa_guard_memo_digest_tool="sha256sum"
|
|
144
|
+
else
|
|
145
|
+
return 1
|
|
146
|
+
fi
|
|
147
|
+
return 0
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
# Remove memo state older than a day.
|
|
151
|
+
#
|
|
152
|
+
# Files first, then the emptied session directories, both with `find -delete`.
|
|
153
|
+
# Never a recursive forced delete: this runs unattended inside every guard, and
|
|
154
|
+
# the blast radius of a mis-resolved base directory has to stay bounded to
|
|
155
|
+
# entries this scheme itself created.
|
|
156
|
+
lisa_guard_memo_sweep() {
|
|
157
|
+
find "$lisa_guard_memo_base" -mindepth 2 -maxdepth 2 -type f -mmin +1440 \
|
|
158
|
+
-delete 2>/dev/null || true
|
|
159
|
+
find "$lisa_guard_memo_base" -mindepth 1 -maxdepth 1 -type d -empty \
|
|
160
|
+
-mmin +1440 -delete 2>/dev/null || true
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# Decide whether this guard has already been evaluated for this tool call, and
|
|
164
|
+
# exit 0 in place of re-evaluating it when it has.
|
|
165
|
+
#
|
|
166
|
+
# Returns 0 for "carry on and evaluate". Never returns non-zero, so a caller
|
|
167
|
+
# running under `set -e` is unaffected by it.
|
|
168
|
+
#
|
|
169
|
+
# $1 - guard name, used only to name the memo file
|
|
170
|
+
# $2 - the raw tool payload this guard was handed on stdin
|
|
171
|
+
lisa_guard_dedupe() {
|
|
172
|
+
local guard_name="${1:-}"
|
|
173
|
+
local payload="${2:-}"
|
|
174
|
+
[ -n "$guard_name" ] || return 0
|
|
175
|
+
[ -n "$payload" ] || return 0
|
|
176
|
+
[ -z "${LISA_GUARD_DEDUPE_DISABLE:-}" ] || return 0
|
|
177
|
+
|
|
178
|
+
# The session scopes every piece of state below. No session id means the
|
|
179
|
+
# payload came from a surface this cannot reason about — an adapter's
|
|
180
|
+
# synthesised envelope, a test harness — and the answer there is to evaluate.
|
|
181
|
+
local session_id=""
|
|
182
|
+
local session_pattern="\"session_id\"[[:space:]]*:[[:space:]]*\"([^\"]+)\""
|
|
183
|
+
if [[ "$payload" =~ $session_pattern ]]; then
|
|
184
|
+
session_id="${BASH_REMATCH[1]}"
|
|
185
|
+
fi
|
|
186
|
+
[ -n "$session_id" ] || return 0
|
|
187
|
+
# It is used as a path component, so anything that could escape the state
|
|
188
|
+
# directory disqualifies it outright.
|
|
189
|
+
case "$session_id" in *[!A-Za-z0-9._-]* | .*) return 0 ;; esac
|
|
190
|
+
|
|
191
|
+
lisa_guard_memo_resolve_base || return 0
|
|
192
|
+
|
|
193
|
+
local session_dir="$lisa_guard_memo_base/$session_id"
|
|
194
|
+
local fresh_session=0
|
|
195
|
+
if [ ! -d "$session_dir" ]; then
|
|
196
|
+
if (umask 077 && mkdir "$session_dir") 2>/dev/null; then
|
|
197
|
+
fresh_session=1
|
|
198
|
+
fi
|
|
199
|
+
fi
|
|
200
|
+
[ -d "$session_dir" ] || return 0
|
|
201
|
+
[ ! -L "$session_dir" ] || return 0
|
|
202
|
+
|
|
203
|
+
# Swept once, by the process that created the session directory, so a
|
|
204
|
+
# long-lived machine does not accumulate memo state. Off the hot path by
|
|
205
|
+
# construction: every later call in the session finds the directory present.
|
|
206
|
+
if [ "$fresh_session" -eq 1 ]; then
|
|
207
|
+
lisa_guard_memo_sweep
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# The directory this copy of the guard was served from. Two registrations of
|
|
211
|
+
# one guard are always two different directories — a repository tree or an
|
|
212
|
+
# applied `scripts/lisa-hooks/` on one side, a plugin cache on the other — so
|
|
213
|
+
# the set of directories seen this session IS the set of live channels.
|
|
214
|
+
local self_script="${BASH_SOURCE[1]:-$0}"
|
|
215
|
+
local self_dir="."
|
|
216
|
+
case "$self_script" in */*) self_dir="${self_script%/*}" ;; esac
|
|
217
|
+
|
|
218
|
+
local channels_file="$session_dir/channels"
|
|
219
|
+
local seen_self=0
|
|
220
|
+
local other_channel=0
|
|
221
|
+
local line=""
|
|
222
|
+
if [ -f "$channels_file" ]; then
|
|
223
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
224
|
+
[ -n "$line" ] || continue
|
|
225
|
+
if [ "$line" = "$self_dir" ]; then
|
|
226
|
+
seen_self=1
|
|
227
|
+
else
|
|
228
|
+
other_channel=1
|
|
229
|
+
fi
|
|
230
|
+
done <"$channels_file"
|
|
231
|
+
fi
|
|
232
|
+
if [ "$seen_self" -eq 0 ]; then
|
|
233
|
+
printf '%s\n' "$self_dir" >>"$channels_file" 2>/dev/null || true
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
# Single channel: the memo can never hit, so nothing below is worth its cost.
|
|
237
|
+
# This is the branch a host with no plugin takes on every call, and it is the
|
|
238
|
+
# reason the fork lives below this line rather than above it.
|
|
239
|
+
[ "$other_channel" -eq 1 ] || return 0
|
|
240
|
+
|
|
241
|
+
# The per-call discriminator. The transcript grows with every turn, so its
|
|
242
|
+
# size separates two identical commands issued at different points in one
|
|
243
|
+
# session — which matters because a guard classifies against a working tree
|
|
244
|
+
# that may have changed between them. Both channels read the same transcript
|
|
245
|
+
# within one tool call, and anything appended between them costs a memo miss,
|
|
246
|
+
# never a stale hit.
|
|
247
|
+
local transcript=""
|
|
248
|
+
local transcript_pattern="\"transcript_path\"[[:space:]]*:[[:space:]]*\"([^\"]+)\""
|
|
249
|
+
if [[ "$payload" =~ $transcript_pattern ]]; then
|
|
250
|
+
transcript="${BASH_REMATCH[1]}"
|
|
251
|
+
fi
|
|
252
|
+
[ -n "$transcript" ] || return 0
|
|
253
|
+
[ -f "$transcript" ] || return 0
|
|
254
|
+
|
|
255
|
+
lisa_guard_memo_resolve_digest || return 0
|
|
256
|
+
|
|
257
|
+
# The transcript's size, captured and VALIDATED before it can reach the
|
|
258
|
+
# digest, because a `stat` that rejects the other platform's spelling can
|
|
259
|
+
# still write to stdout on its way out. GNU `stat -f '%z' FILE` reads `-f` as
|
|
260
|
+
# `--file-system` and the format string as a FILE that does not exist: it
|
|
261
|
+
# exits non-zero AND prints a filesystem status block for the real operand,
|
|
262
|
+
# whose `Free` and `Available` counts move under any disk activity. Appended
|
|
263
|
+
# straight into the digest, that made the two channels of ONE tool call
|
|
264
|
+
# compute different keys, so the memo could only ever miss — the whole
|
|
265
|
+
# mechanism switched off on every Linux host, while the three "must not
|
|
266
|
+
# dedupe" controls stayed green because they assert a miss.
|
|
267
|
+
#
|
|
268
|
+
# Only an all-digits answer is accepted, and no size means NO MEMO rather
|
|
269
|
+
# than a memo with nothing separating one call from the next: that is
|
|
270
|
+
# condition 2 above, which the `A || B` spelling had left unenforced on a
|
|
271
|
+
# host carrying neither `stat`.
|
|
272
|
+
local transcript_size=""
|
|
273
|
+
transcript_size="$(stat -c '%s' "$transcript" 2>/dev/null)" ||
|
|
274
|
+
transcript_size=""
|
|
275
|
+
case "$transcript_size" in
|
|
276
|
+
"" | *[!0-9]*)
|
|
277
|
+
transcript_size="$(stat -f '%z' "$transcript" 2>/dev/null)" ||
|
|
278
|
+
transcript_size=""
|
|
279
|
+
;;
|
|
280
|
+
esac
|
|
281
|
+
case "$transcript_size" in "" | *[!0-9]*) return 0 ;; esac
|
|
282
|
+
|
|
283
|
+
# One command substitution, covering everything the verdict depends on: the
|
|
284
|
+
# guard's own bytes, this library's bytes, the payload, and which call it is.
|
|
285
|
+
local key=""
|
|
286
|
+
key="$(
|
|
287
|
+
{
|
|
288
|
+
cat "$self_script" "${BASH_SOURCE[0]}" 2>/dev/null
|
|
289
|
+
printf '\n%s\n' "$payload"
|
|
290
|
+
printf '%s\n' "$transcript_size"
|
|
291
|
+
} | if [ "$lisa_guard_memo_digest_tool" = "shasum" ]; then
|
|
292
|
+
shasum -a 256 2>/dev/null
|
|
293
|
+
else
|
|
294
|
+
sha256sum 2>/dev/null
|
|
295
|
+
fi
|
|
296
|
+
)" || key=""
|
|
297
|
+
key="${key%% *}"
|
|
298
|
+
case "$key" in "" | *[!0-9a-f]*) return 0 ;; esac
|
|
299
|
+
|
|
300
|
+
# One memo file per guard per session, rewritten as the call moves on, so the
|
|
301
|
+
# state directory stays a fixed size however long a session runs. A saturated
|
|
302
|
+
# temp directory is its own outage on this fleet, and a memo scheme that grew
|
|
303
|
+
# a file per tool call would be one.
|
|
304
|
+
local memo_file="$session_dir/$guard_name.allow"
|
|
305
|
+
local recorded=""
|
|
306
|
+
if [ -f "$memo_file" ] && [ ! -L "$memo_file" ]; then
|
|
307
|
+
IFS= read -r recorded <"$memo_file" 2>/dev/null || recorded=""
|
|
308
|
+
if [ "$recorded" = "$key" ]; then
|
|
309
|
+
# A byte-identical copy of this guard already allowed this exact payload
|
|
310
|
+
# on this exact tool call. The evaluation being skipped could only have
|
|
311
|
+
# produced the verdict that was already delivered.
|
|
312
|
+
exit 0
|
|
313
|
+
fi
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
lisa_guard_memo_key="$key"
|
|
317
|
+
lisa_guard_memo_file="$memo_file"
|
|
318
|
+
lisa_guard_memo_ready=1
|
|
319
|
+
return 0
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
# Record an ALLOW verdict for the other channel to replay. Installed as the
|
|
323
|
+
# guard's EXIT trap, so it covers every path the guard can leave by.
|
|
324
|
+
#
|
|
325
|
+
# Any status other than 0 writes nothing and, when a memo from an earlier call
|
|
326
|
+
# is still sitting there, removes it: a guard that refused must never leave
|
|
327
|
+
# behind something that reads as permission.
|
|
328
|
+
#
|
|
329
|
+
# $1 - the exiting status
|
|
330
|
+
lisa_guard_dedupe_record() {
|
|
331
|
+
[ "${lisa_guard_memo_ready:-0}" -eq 1 ] || return 0
|
|
332
|
+
[ -n "$lisa_guard_memo_file" ] || return 0
|
|
333
|
+
if [ "${1:-1}" -eq 0 ]; then
|
|
334
|
+
printf '%s\n' "$lisa_guard_memo_key" >"$lisa_guard_memo_file" 2>/dev/null ||
|
|
335
|
+
true
|
|
336
|
+
else
|
|
337
|
+
rm -f "$lisa_guard_memo_file" 2>/dev/null || true
|
|
338
|
+
fi
|
|
339
|
+
return 0
|
|
340
|
+
}
|
|
@@ -121,6 +121,27 @@ trap 'printf "%s\n" "Blocked by safety-net: hook failed while parsing its input;
|
|
|
121
121
|
|
|
122
122
|
input="$(cat)"
|
|
123
123
|
|
|
124
|
+
# Evaluate once per tool call when this guard is registered on both channels.
|
|
125
|
+
#
|
|
126
|
+
# Lisa reaches an agent through the repository dispatcher AND the plugin
|
|
127
|
+
# manifest, and where both are live the harness runs this guard twice for one
|
|
128
|
+
# tool call. `guard-dedupe.bash` short-circuits the second run ONLY when a
|
|
129
|
+
# byte-identical copy already ALLOWED this exact payload on this exact tool
|
|
130
|
+
# call; a differing vintage, a refusal, and a host with one channel all
|
|
131
|
+
# evaluate exactly as before. Nothing is de-registered by it
|
|
132
|
+
# (CodySwannGT/lisa#3814).
|
|
133
|
+
#
|
|
134
|
+
# Absent library means no dedupe, which is the pre-existing behaviour, so an
|
|
135
|
+
# older channel copy that predates it is unaffected.
|
|
136
|
+
lisa_guard_hook_dir="${BASH_SOURCE[0]%/*}"
|
|
137
|
+
lisa_guard_dedupe_lib="$lisa_guard_hook_dir/guard-dedupe.bash"
|
|
138
|
+
if [ -r "$lisa_guard_dedupe_lib" ]; then
|
|
139
|
+
# shellcheck source=guard-dedupe.bash
|
|
140
|
+
. "$lisa_guard_dedupe_lib"
|
|
141
|
+
trap 'lisa_guard_dedupe_record $?' EXIT
|
|
142
|
+
lisa_guard_dedupe parity-safety-net "$input"
|
|
143
|
+
fi
|
|
144
|
+
|
|
124
145
|
tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty')"
|
|
125
146
|
if [ "$tool_name" != "Bash" ]; then
|
|
126
147
|
exit 0
|
|
@@ -247,12 +268,19 @@ later with `git apply "$patch"`.
|
|
|
247
268
|
|
|
248
269
|
If the work is NOT yours -- a concurrent agent dirtied this worktree and your
|
|
249
270
|
judgement is "none of this is mine" -- that same capture is how you return the
|
|
250
|
-
tree to HEAD:
|
|
271
|
+
tree to HEAD. This block is self-contained; paste it as it stands:
|
|
251
272
|
|
|
273
|
+
patch="$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX")"
|
|
252
274
|
git reset
|
|
253
275
|
git diff --binary HEAD > "$patch"
|
|
254
276
|
git apply -R "$patch"
|
|
255
277
|
|
|
278
|
+
It names the file itself rather than inheriting one from the block above,
|
|
279
|
+
because the two blocks are alternatives and only one of them gets run. A remedy
|
|
280
|
+
printed at the moment an agent is blocked is followed literally; one that
|
|
281
|
+
depends on a variable the reader never set fails on its first line, and the
|
|
282
|
+
guard that printed it stops being believed.
|
|
283
|
+
|
|
256
284
|
`git reset` clears staged and unmerged entries first, so the capture sees the
|
|
257
285
|
whole tree as one diff; reversing it leaves the tree at HEAD with "$patch" as
|
|
258
286
|
the record of what you discarded. Untracked files survive, exactly as they
|
|
@@ -405,6 +405,11 @@ function applyCd(current, target) {
|
|
|
405
405
|
* disagreeing. The scripted form is the one neither covers, and it is the only
|
|
406
406
|
* cell this arm fills.
|
|
407
407
|
*
|
|
408
|
+
* That reason is a dependency on a runtime Lisa does not ship, and it can
|
|
409
|
+
* expire without anything here failing. It is pinned at
|
|
410
|
+
* {@link VERIFIED_RUNTIME_VERSION} and reported when the runtime moves off it
|
|
411
|
+
* — read that constant before widening this arm.
|
|
412
|
+
*
|
|
408
413
|
* ## A relative script path is resolved against the cwd the command will have
|
|
409
414
|
*
|
|
410
415
|
* A leading `cd` establishes the directory this guard resolves relative tokens
|
|
@@ -514,11 +519,28 @@ function readState(sessionId) {
|
|
|
514
519
|
}
|
|
515
520
|
}
|
|
516
521
|
|
|
522
|
+
/**
|
|
523
|
+
* Write the session's binding state, MERGING over whatever is already there.
|
|
524
|
+
*
|
|
525
|
+
* Every caller below hands in all three lifecycle fields explicitly, so the
|
|
526
|
+
* merge changes nothing for them — `claimedRoot: null` still clears a claim.
|
|
527
|
+
* What it buys is that a field one writer owns is not silently dropped by
|
|
528
|
+
* another writer that had no opinion about it: `runtimeNoticed` is written by
|
|
529
|
+
* {@link noticeRuntimeDrift} and read by nobody else, and a clobbering write
|
|
530
|
+
* would turn its "reported once" into "reported again after the next
|
|
531
|
+
* acknowledgement", which is the noise the notice is designed not to be.
|
|
532
|
+
* @param sessionId - The session whose state file this is
|
|
533
|
+
* @param state - Fields to write over the recorded state
|
|
534
|
+
* @returns Whether the write succeeded
|
|
535
|
+
*/
|
|
517
536
|
function writeState(sessionId, state) {
|
|
518
537
|
const file = stateFile(sessionId);
|
|
519
538
|
try {
|
|
520
539
|
mkdirSync(dirname(file), { recursive: true });
|
|
521
|
-
writeFileSync(
|
|
540
|
+
writeFileSync(
|
|
541
|
+
file,
|
|
542
|
+
`${JSON.stringify({ ...readState(sessionId), ...state }, null, 2)}\n`
|
|
543
|
+
);
|
|
522
544
|
return true;
|
|
523
545
|
} catch (error) {
|
|
524
546
|
say(`could not record the binding (${error.message}); NOT enforcing`);
|
|
@@ -698,6 +720,162 @@ function displaced(bound, observed) {
|
|
|
698
720
|
]);
|
|
699
721
|
}
|
|
700
722
|
|
|
723
|
+
/**
|
|
724
|
+
* The runtime version the inline-redirect gap was last verified at.
|
|
725
|
+
*
|
|
726
|
+
* ## What this constant is a pin ON
|
|
727
|
+
*
|
|
728
|
+
* This guard deliberately does NOT refuse an INLINE redirect into another
|
|
729
|
+
* worktree — `cd <other> && git …`, `git -C <other> …`. The reason is good and
|
|
730
|
+
* is written out at {@link scriptReachingForeignTree}: the runtime's own
|
|
731
|
+
* worktree isolation already refuses them, and duplicating a control is how two
|
|
732
|
+
* controls drift into disagreeing about the same command.
|
|
733
|
+
*
|
|
734
|
+
* That reason is a DEPENDENCY on behaviour Lisa does not ship. It was verified
|
|
735
|
+
* refusing at this version, with the refusal wording "this command names git in
|
|
736
|
+
* a form too complex to verify that it stays inside the worktree" and
|
|
737
|
+
* "redirects git to the shared checkout via -C".
|
|
738
|
+
*
|
|
739
|
+
* ## Why a pin rather than a test
|
|
740
|
+
*
|
|
741
|
+
* The runtime's isolation is not reachable from a unit test: it adjudicates
|
|
742
|
+
* inside a live worktree-isolated session, and there is no entry point that
|
|
743
|
+
* takes one hook envelope and returns a verdict the way this guard does. So the
|
|
744
|
+
* dependency cannot be turned red by writing a test, and a claim that cannot go
|
|
745
|
+
* red goes stale silently instead (CodySwannGT/lisa#3944).
|
|
746
|
+
*
|
|
747
|
+
* What CAN be measured is whether the runtime is still the one the claim was
|
|
748
|
+
* checked against. That does not prove the behaviour changed — only that nobody
|
|
749
|
+
* has checked since, which is the honest thing to report and the whole of what
|
|
750
|
+
* this pin does.
|
|
751
|
+
*
|
|
752
|
+
* ## Moving it
|
|
753
|
+
*
|
|
754
|
+
* Re-run both inline forms in a worktree-isolated session. Still refused: move
|
|
755
|
+
* this pin, and say where. Permitted: those two cells are UNCOVERED, and #3944
|
|
756
|
+
* owns the decision about which of them Lisa takes on.
|
|
757
|
+
*/
|
|
758
|
+
const VERIFIED_RUNTIME_VERSION = "2.1.261";
|
|
759
|
+
|
|
760
|
+
/** The ticket that owns the inline-redirect dependency and its expiry. */
|
|
761
|
+
const RUNTIME_ASSUMPTION_TICKET = "CodySwannGT/lisa#3944";
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* This session's runtime version, or null when this is not that runtime.
|
|
765
|
+
*
|
|
766
|
+
* Three answers, and the difference between the last two is the point:
|
|
767
|
+
* - `null` — not Claude Code. Codex, Cursor and Copilot ship this guard too,
|
|
768
|
+
* and the assumption above is about a control none of them has. A notice that
|
|
769
|
+
* fires on every session of every harness is noise, and noise gets switched
|
|
770
|
+
* off.
|
|
771
|
+
* - `""` — Claude Code, which will not say which version. Read as a DIVERGENCE
|
|
772
|
+
* rather than as a match: the assumption is unconfirmed either way, and
|
|
773
|
+
* treating an unreadable version as "still 2.1.261" is exactly the silent
|
|
774
|
+
* expiry #3944 was filed about.
|
|
775
|
+
* - a version — take it at its word.
|
|
776
|
+
*
|
|
777
|
+
* Both env vars are read because either can be the one a future release keeps.
|
|
778
|
+
* `AI_AGENT` carries `claude-code_2-1-261_agent`; `CLAUDE_CODE_EXECPATH` ends
|
|
779
|
+
* in the version directory the running build was launched from.
|
|
780
|
+
* @param env - Environment to read
|
|
781
|
+
* @returns The version, `""` when unreadable, or null for another runtime
|
|
782
|
+
*/
|
|
783
|
+
const isDigit = ch => ch >= "0" && ch <= "9";
|
|
784
|
+
|
|
785
|
+
/** @param value - Segment to test @returns Whether it is all digits */
|
|
786
|
+
const allDigits = value => value.length > 0 && [...value].every(isDigit);
|
|
787
|
+
|
|
788
|
+
/** @param value - Segment to read @returns Its trailing digit run, else `""` */
|
|
789
|
+
const trailingDigits = value => {
|
|
790
|
+
let start = value.length;
|
|
791
|
+
while (start > 0 && isDigit(value[start - 1])) start -= 1;
|
|
792
|
+
return value.slice(start);
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
function runtimeVersion(env) {
|
|
796
|
+
if (!env.CLAUDECODE) return null;
|
|
797
|
+
const tagged = /claude-code[_-](\d+)[-.](\d+)[-.](\d+)/u.exec(
|
|
798
|
+
env.AI_AGENT ?? ""
|
|
799
|
+
);
|
|
800
|
+
if (tagged) return `${tagged[1]}.${tagged[2]}.${tagged[3]}`;
|
|
801
|
+
// Read the version off the final path segment by scanning characters rather
|
|
802
|
+
// than by regex: an unanchored `\d+\.\d+\.\d+` before `$` backtracks
|
|
803
|
+
// super-linearly on a long execpath, and the version Claude Code launches
|
|
804
|
+
// from is always the trailing directory name.
|
|
805
|
+
const segments = (env.CLAUDE_CODE_EXECPATH ?? "").split("/").filter(Boolean);
|
|
806
|
+
const trailing = segments.length ? segments[segments.length - 1] : "";
|
|
807
|
+
const parts = trailing.split(".");
|
|
808
|
+
if (parts.length < 3) return "";
|
|
809
|
+
const [major, minor, patch] = parts.slice(-3);
|
|
810
|
+
const majorDigits = trailingDigits(major);
|
|
811
|
+
if (!majorDigits || !allDigits(minor) || !allDigits(patch)) return "";
|
|
812
|
+
return `${majorDigits}.${minor}.${patch}`;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* The notice text for a runtime the inline-redirect claim was not checked at.
|
|
817
|
+
* @param version - The observed runtime version, `""` when unreadable
|
|
818
|
+
* @returns The lines to report
|
|
819
|
+
*/
|
|
820
|
+
function driftNotice(version) {
|
|
821
|
+
return [
|
|
822
|
+
"worktree-binding-guard: the inline-redirect gap is UNVERIFIED here.",
|
|
823
|
+
"",
|
|
824
|
+
"This guard does not refuse `cd <other-worktree> && git …` or",
|
|
825
|
+
"`git -C <other-worktree> …`. Those two cells are covered by the runtime's",
|
|
826
|
+
`own worktree isolation, verified refusing at Claude Code ${VERIFIED_RUNTIME_VERSION}.`,
|
|
827
|
+
version
|
|
828
|
+
? `This session is running ${version}, so that check does not cover it.`
|
|
829
|
+
: "This session's runtime version could not be read, so nothing covers it.",
|
|
830
|
+
"",
|
|
831
|
+
"This does NOT say the runtime stopped refusing them — only that nobody has",
|
|
832
|
+
"checked since. Run both forms in a worktree-isolated session: still",
|
|
833
|
+
"refused, move VERIFIED_RUNTIME_VERSION in worktree-binding-guard.mjs;",
|
|
834
|
+
`permitted, the two cells are uncovered and ${RUNTIME_ASSUMPTION_TICKET}`,
|
|
835
|
+
"owns what Lisa does about it.",
|
|
836
|
+
];
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Report, once per session, that the runtime moved off the verified version.
|
|
841
|
+
*
|
|
842
|
+
* ## Why SessionStart and not the guarded call
|
|
843
|
+
*
|
|
844
|
+
* SessionStart is the guard's first evaluation of a session, it fires exactly
|
|
845
|
+
* once per start, and its `additionalContext` is the one channel here that the
|
|
846
|
+
* agent actually reads — the `say()` lines elsewhere in this file are for a
|
|
847
|
+
* human reading a transcript. A notice attached to a guarded tool call would
|
|
848
|
+
* either repeat on every Bash command or need a suppression rule of its own.
|
|
849
|
+
*
|
|
850
|
+
* ## Why it is recorded rather than counted
|
|
851
|
+
*
|
|
852
|
+
* A resume fires SessionStart again for the same session id. The recorded
|
|
853
|
+
* version is what makes the second one silent, and recording the VERSION rather
|
|
854
|
+
* than a flag means a runtime that moves twice inside one resumed session is
|
|
855
|
+
* still reported the second time.
|
|
856
|
+
*
|
|
857
|
+
* Never refuses. The dependency is a reporting gap, not a live displacement,
|
|
858
|
+
* and blocking a session over a version number would be a wall where an
|
|
859
|
+
* observation belongs.
|
|
860
|
+
* @param sessionId - The session being started
|
|
861
|
+
* @param env - Environment to read the runtime version from
|
|
862
|
+
* @returns Nothing; every path through this allows
|
|
863
|
+
*/
|
|
864
|
+
function noticeRuntimeDrift(sessionId, env) {
|
|
865
|
+
const version = runtimeVersion(env);
|
|
866
|
+
if (version === null || version === VERIFIED_RUNTIME_VERSION) return;
|
|
867
|
+
if (readState(sessionId)?.runtimeNoticed === version) return;
|
|
868
|
+
writeState(sessionId, { runtimeNoticed: version });
|
|
869
|
+
process.stdout.write(
|
|
870
|
+
`${JSON.stringify({
|
|
871
|
+
hookSpecificOutput: {
|
|
872
|
+
hookEventName: "SessionStart",
|
|
873
|
+
additionalContext: driftNotice(version).join("\n"),
|
|
874
|
+
},
|
|
875
|
+
})}\n`
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
701
879
|
function evaluate(payload) {
|
|
702
880
|
const sessionId = payload.session_id;
|
|
703
881
|
const cwd = payload.cwd;
|
|
@@ -710,6 +888,10 @@ function evaluate(payload) {
|
|
|
710
888
|
|
|
711
889
|
if (payload.hook_event_name === "SessionStart") {
|
|
712
890
|
recordBaseline(sessionId, observed);
|
|
891
|
+
// After the baseline, never before: `recordBaseline` writes a fresh state
|
|
892
|
+
// for a session it has not seen, and the notice's record has to go on top
|
|
893
|
+
// of that write rather than under it.
|
|
894
|
+
noticeRuntimeDrift(sessionId, process.env);
|
|
713
895
|
return 0;
|
|
714
896
|
}
|
|
715
897
|
if (payload.tool_name === "EnterWorktree") {
|
|
@@ -39,6 +39,27 @@ set -uo pipefail
|
|
|
39
39
|
|
|
40
40
|
input="$(cat)"
|
|
41
41
|
|
|
42
|
+
# Evaluate once per tool call when this guard is registered on both channels.
|
|
43
|
+
#
|
|
44
|
+
# Lisa reaches an agent through the repository dispatcher AND the plugin
|
|
45
|
+
# manifest, and where both are live the harness runs this guard twice for one
|
|
46
|
+
# tool call. `guard-dedupe.bash` short-circuits the second run ONLY when a
|
|
47
|
+
# byte-identical copy already ALLOWED this exact payload on this exact tool
|
|
48
|
+
# call; a differing vintage, a refusal, and a host with one channel all
|
|
49
|
+
# evaluate exactly as before. Nothing is de-registered by it
|
|
50
|
+
# (CodySwannGT/lisa#3814).
|
|
51
|
+
#
|
|
52
|
+
# Absent library means no dedupe, which is the pre-existing behaviour, so an
|
|
53
|
+
# older channel copy that predates it is unaffected.
|
|
54
|
+
lisa_guard_hook_dir="${BASH_SOURCE[0]%/*}"
|
|
55
|
+
lisa_guard_dedupe_lib="$lisa_guard_hook_dir/guard-dedupe.bash"
|
|
56
|
+
if [ -r "$lisa_guard_dedupe_lib" ]; then
|
|
57
|
+
# shellcheck source=guard-dedupe.bash
|
|
58
|
+
. "$lisa_guard_dedupe_lib"
|
|
59
|
+
trap 'lisa_guard_dedupe_record $?' EXIT
|
|
60
|
+
lisa_guard_dedupe worktree-binding-guard "$input"
|
|
61
|
+
fi
|
|
62
|
+
|
|
42
63
|
if ! command -v node >/dev/null 2>&1; then
|
|
43
64
|
printf 'worktree-binding-guard: node not found; worktree-binding enforcement is NOT active\n' >&2
|
|
44
65
|
exit 0
|
|
@@ -173,7 +173,27 @@ export function runPostinstall(cwd = process.cwd(), env = process.env) {
|
|
|
173
173
|
// package manager finish. Unknown throws still escape because converting a
|
|
174
174
|
// broken postinstall module into an apparent apply failure would hide the
|
|
175
175
|
// defect that needs fixing.
|
|
176
|
-
|
|
176
|
+
//
|
|
177
|
+
// The MARKER is written for an unknown throw too, before it escapes, and
|
|
178
|
+
// that is not the same decision as the exit code. The invocation this
|
|
179
|
+
// module is chained into ends in `|| true`, deliberately — a non-zero
|
|
180
|
+
// postinstall aborts `bun install` outright and strands the project with
|
|
181
|
+
// no way to install the fix. So the escaping throw reaches nothing that
|
|
182
|
+
// records it: the stack trace scrolls past once and every later instrument
|
|
183
|
+
// reads a project whose templates simply were not applied. `lisa doctor`
|
|
184
|
+
// asks the marker, and the marker was the only durable half. Writing it
|
|
185
|
+
// here keeps the loud-and-durable contract this module's header states,
|
|
186
|
+
// without trading it for an install that cannot complete.
|
|
187
|
+
if (!isChildTimeout(error)) {
|
|
188
|
+
recordFailure(cwd, {
|
|
189
|
+
status: null,
|
|
190
|
+
output:
|
|
191
|
+
error instanceof Error
|
|
192
|
+
? (error.stack ?? error.message)
|
|
193
|
+
: String(error),
|
|
194
|
+
});
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
177
197
|
child = {
|
|
178
198
|
error: Object.assign(
|
|
179
199
|
new Error(
|