@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,636 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* What the DEPLOYED guard copies say, not what the source says (#3998).
|
|
4
|
+
*
|
|
5
|
+
* ## The defect
|
|
6
|
+
*
|
|
7
|
+
* The repository forbids a piece of advice, pins a test against it, and
|
|
8
|
+
* deployed guards keep dispensing it anyway. Those are two different claims —
|
|
9
|
+
* *"the repo forbids this advice"* and *"no agent is receiving this advice"* —
|
|
10
|
+
* and until this file only the first was testable. Every conformance control
|
|
11
|
+
* here reads `plugins/**` and `all/**`. None of them can see what a copy that
|
|
12
|
+
* is actually serving an agent says.
|
|
13
|
+
*
|
|
14
|
+
* `parity-safety-net-no-stash-advice.test.ts` forbids four advisory spellings
|
|
15
|
+
* of "use the shared stash to preserve your work". It is green. Three sessions
|
|
16
|
+
* were nonetheless told to use the shared stash in one day, by guards resolving
|
|
17
|
+
* from an older copy — and on a machine running many agents against one `.git`,
|
|
18
|
+
* `stash@{0}` belongs to whoever pushed last, so a `pop` taken on that advice
|
|
19
|
+
* consumes a sibling lane's uncommitted work.
|
|
20
|
+
*
|
|
21
|
+
* ## Why a fix does not retire the advice
|
|
22
|
+
*
|
|
23
|
+
* A guard reaches an agent by more than one channel and the agent sees the
|
|
24
|
+
* UNION of their verdicts, so a TIGHTENING on either channel takes effect at
|
|
25
|
+
* once while a RELAXATION is inert until the slowest channel catches up.
|
|
26
|
+
* Deleting the advice from source is a relaxation. It propagates at the speed
|
|
27
|
+
* of the slowest copy on the disk, and a session resolves its copy ONCE at
|
|
28
|
+
* start and executes that copy for its whole life — which is why every cache
|
|
29
|
+
* entry below is a channel and not an archive.
|
|
30
|
+
*
|
|
31
|
+
* ## One list, cited by both
|
|
32
|
+
*
|
|
33
|
+
* `FORBIDDEN_ADVISORY_PATTERNS` is the single definition of what the advice IS.
|
|
34
|
+
* The in-repo source suite imports it from here rather than keeping its own
|
|
35
|
+
* table, because two lists drift and the drift is invisible: the source suite
|
|
36
|
+
* would stay green against patterns this sweep no longer looks for.
|
|
37
|
+
*
|
|
38
|
+
* ## Fail direction
|
|
39
|
+
*
|
|
40
|
+
* **Report-only on findings, fail-closed on its own blindness.** It gates no
|
|
41
|
+
* commit and no push. It must not: a developer machine legitimately holds
|
|
42
|
+
* hundreds of superseded cache entries, and a blocker that fires on every one
|
|
43
|
+
* of them is a permanent stop-work whose first consequence is being switched
|
|
44
|
+
* off. But the healthy state of this check is an EMPTY report, so a wrong path,
|
|
45
|
+
* an unreadable cache directory, or a channel the resolver does not know about
|
|
46
|
+
* all produce empty and read as clean. Any channel that could not be read makes
|
|
47
|
+
* the sweep `NOT_MEASURED`, never `CLEAN`. Exit codes carry the same split:
|
|
48
|
+
* 0 clean, 1 findings, 2 not measured.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
import { readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
52
|
+
import path from "node:path";
|
|
53
|
+
import process from "node:process";
|
|
54
|
+
import { fileURLToPath } from "node:url";
|
|
55
|
+
|
|
56
|
+
/** Directory segment holding plugin trees under a harness config directory. */
|
|
57
|
+
const PLUGINS = "plugins";
|
|
58
|
+
|
|
59
|
+
/** The plugin and marketplace both named `lisa`. */
|
|
60
|
+
const LISA = "lisa";
|
|
61
|
+
|
|
62
|
+
/** Name of the channel the plugin manifest serves. */
|
|
63
|
+
const PLUGIN_MANIFEST = "plugin-manifest";
|
|
64
|
+
|
|
65
|
+
/** Directory a plugin's own manifest lives in. */
|
|
66
|
+
const PLUGIN_MANIFEST_DIR = ".claude-plugin";
|
|
67
|
+
|
|
68
|
+
/** Filename of a plugin's own manifest. */
|
|
69
|
+
const PLUGIN_MANIFEST_FILE = "plugin.json";
|
|
70
|
+
|
|
71
|
+
/** The guard every caller means unless it says otherwise. */
|
|
72
|
+
const DEFAULT_GUARD = "parity-safety-net";
|
|
73
|
+
|
|
74
|
+
/** Whole-sweep verdicts. */
|
|
75
|
+
export const ADVICE_SWEEP_VERDICTS = Object.freeze([
|
|
76
|
+
"CLEAN",
|
|
77
|
+
"OUTLAWED_ADVICE",
|
|
78
|
+
"NOT_MEASURED",
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Spellings that RECOMMEND the shared stash rather than prohibiting it.
|
|
83
|
+
*
|
|
84
|
+
* **Every pattern here was verified against the real historical bytes, in both
|
|
85
|
+
* directions** — it matches the text that shipped before the fix and does not
|
|
86
|
+
* match the text that shipped after. That check is not ceremony: the first
|
|
87
|
+
* draft of this table required "stash" to appear AFTER "safe alternatives",
|
|
88
|
+
* and in guard 7's actual comment it appears before, so the pattern matched
|
|
89
|
+
* nothing and the suite passed against the very file it was written to catch.
|
|
90
|
+
* A prose pattern that has not been run against the prose it forbids is not
|
|
91
|
+
* evidence of anything. If you add one, take the bytes from the commit that
|
|
92
|
+
* carried the bad text and watch the case go red first.
|
|
93
|
+
*
|
|
94
|
+
* ## Why these key on ADVISORY forms, not the bare word
|
|
95
|
+
*
|
|
96
|
+
* Guard 7 legitimately BLOCKS `git stash drop` / `git stash clear`, and its
|
|
97
|
+
* message necessarily contains the word. That is a prohibition, not a
|
|
98
|
+
* recommendation, and it must stay. Keying on the bare token would report the
|
|
99
|
+
* one message doing the right thing. Do not "tighten" these to /stash/.
|
|
100
|
+
*
|
|
101
|
+
* The first three were the shipped refusal text of guards 4 and 3 until the
|
|
102
|
+
* fix; the fourth is guard 7's comment, which that fix left behind.
|
|
103
|
+
*/
|
|
104
|
+
export const FORBIDDEN_ADVISORY_PATTERNS = Object.freeze([
|
|
105
|
+
Object.freeze({
|
|
106
|
+
id: "guard-4-old-refusal",
|
|
107
|
+
label: "guard 4's old refusal",
|
|
108
|
+
pattern: /use\s+git\s+stash/i,
|
|
109
|
+
why: "offers the shared stash as the way to preserve work before a discard",
|
|
110
|
+
}),
|
|
111
|
+
Object.freeze({
|
|
112
|
+
id: "guard-3-old-refusal",
|
|
113
|
+
label: "guard 3's old refusal",
|
|
114
|
+
pattern: /\(\s*stash\s+or\s+commit/i,
|
|
115
|
+
why: "offers the shared stash as an alternative to committing",
|
|
116
|
+
}),
|
|
117
|
+
Object.freeze({
|
|
118
|
+
id: "stash-as-preservation",
|
|
119
|
+
label: "stash offered as the preservation step",
|
|
120
|
+
pattern: /stash\s+to\s+preserve/i,
|
|
121
|
+
why: "names the shared stash as the preservation mechanism",
|
|
122
|
+
}),
|
|
123
|
+
Object.freeze({
|
|
124
|
+
id: "push-pop-safe-alternative",
|
|
125
|
+
label: "push/pop called a safe alternative",
|
|
126
|
+
pattern: /push\/pop[^.]*safe\s+alternatives?/i,
|
|
127
|
+
why: "asserts the shared stash is safe, which it is not across worktrees",
|
|
128
|
+
}),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Every in-repo directory a guard is SOURCED from.
|
|
133
|
+
*
|
|
134
|
+
* These are the copies the existing source-only suite reads. They are listed
|
|
135
|
+
* here so the source scan and the deployed scan share one scanner and one
|
|
136
|
+
* pattern table — the whole point being that the two answers differ.
|
|
137
|
+
*/
|
|
138
|
+
export const SOURCE_GUARD_ROOTS = Object.freeze([
|
|
139
|
+
"plugins/src/base/hooks",
|
|
140
|
+
"plugins/lisa/hooks",
|
|
141
|
+
"plugins/lisa-agy/hooks",
|
|
142
|
+
"plugins/lisa-cursor/hooks",
|
|
143
|
+
"plugins/lisa-copilot/hooks",
|
|
144
|
+
"all/copy-overwrite/scripts/lisa-hooks",
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The version a JSON manifest states at its top level.
|
|
149
|
+
* @param {string} file - Absolute path to a JSON file.
|
|
150
|
+
* @param {string} key - Top-level key holding the version.
|
|
151
|
+
* @returns {string} The version, or "" when the file or key is unreadable.
|
|
152
|
+
*/
|
|
153
|
+
function readJsonVersion(file, key) {
|
|
154
|
+
try {
|
|
155
|
+
const value = JSON.parse(readFileSync(file, "utf8"))?.[key];
|
|
156
|
+
return typeof value === "string" ? value : "";
|
|
157
|
+
} catch {
|
|
158
|
+
return "";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A channel that resolved to a copy on disk.
|
|
164
|
+
* @param {string} channel - Channel name.
|
|
165
|
+
* @param {string} file - Absolute path to the guard copy.
|
|
166
|
+
* @param {string} version - The copy's vintage, or "" when undateable.
|
|
167
|
+
* @returns {{channel: string, path: string, version: string, reason: string}}
|
|
168
|
+
* The resolved channel.
|
|
169
|
+
*/
|
|
170
|
+
function resolved(channel, file, version) {
|
|
171
|
+
return { channel, path: file, version: version || "unknown", reason: "" };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* A channel that exists in the topology but could not be resolved to a copy.
|
|
176
|
+
* @param {string} channel - Channel name.
|
|
177
|
+
* @param {string} reason - Why it could not be resolved.
|
|
178
|
+
* @returns {{channel: string, path: string, version: string, reason: string}}
|
|
179
|
+
* The unresolved channel.
|
|
180
|
+
*/
|
|
181
|
+
function unresolved(channel, reason) {
|
|
182
|
+
return { channel, path: "", version: "", reason };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The guard tree the repository hook dispatcher resolves, and its vintage.
|
|
187
|
+
*
|
|
188
|
+
* Resolution is first-wins: `scripts/lisa-hooks/` shadows `plugins/lisa/hooks/`
|
|
189
|
+
* outright and the shadowed copy never runs, so the tree reported is whichever
|
|
190
|
+
* is FIRST rather than whichever is newest.
|
|
191
|
+
* @param {string} projectDir - Repository root.
|
|
192
|
+
* @param {string} guard - Guard basename without extension.
|
|
193
|
+
* @returns {{channel: string, path: string, version: string, reason: string}}
|
|
194
|
+
* The dispatcher channel.
|
|
195
|
+
*/
|
|
196
|
+
export function repositoryChannel(projectDir, guard) {
|
|
197
|
+
const host = path.join(projectDir, "scripts", "lisa-hooks", `${guard}.sh`);
|
|
198
|
+
if (existsFile(host)) {
|
|
199
|
+
const receipt = path.join(projectDir, ".lisa", "apply-receipt.json");
|
|
200
|
+
return resolved(
|
|
201
|
+
"repository",
|
|
202
|
+
host,
|
|
203
|
+
readJsonVersion(receipt, "lisa_version")
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const plugin = path.join(projectDir, PLUGINS, LISA, "hooks", `${guard}.sh`);
|
|
207
|
+
if (existsFile(plugin)) {
|
|
208
|
+
const manifest = path.join(
|
|
209
|
+
projectDir,
|
|
210
|
+
PLUGINS,
|
|
211
|
+
LISA,
|
|
212
|
+
PLUGIN_MANIFEST_DIR,
|
|
213
|
+
PLUGIN_MANIFEST_FILE
|
|
214
|
+
);
|
|
215
|
+
return resolved("repository", plugin, readJsonVersion(manifest, "version"));
|
|
216
|
+
}
|
|
217
|
+
return unresolved("repository", "no-dispatcher-tree-in-project");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Whether a path names a readable regular file.
|
|
222
|
+
* @param {string} file - Candidate path.
|
|
223
|
+
* @returns {boolean} True when it is a file this process can stat.
|
|
224
|
+
*/
|
|
225
|
+
function existsFile(file) {
|
|
226
|
+
try {
|
|
227
|
+
return statSync(file).isFile();
|
|
228
|
+
} catch {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* The channel the plugin manifest runs, per PROJECT DIRECTORY.
|
|
235
|
+
*
|
|
236
|
+
* The runtime keys installs on the project path, which is exactly why two
|
|
237
|
+
* checkouts of one repository on one disk really do run different vintages.
|
|
238
|
+
* A record whose install path does not sit under a `lisa/lisa/` cache is left
|
|
239
|
+
* unresolved on purpose: reporting a clean result about a copy that was never
|
|
240
|
+
* read is the failure this whole file exists to end.
|
|
241
|
+
* @param {string} configDir - Harness config directory.
|
|
242
|
+
* @param {string} projectDir - Repository root.
|
|
243
|
+
* @param {string} guard - Guard basename without extension.
|
|
244
|
+
* @returns {{channel: string, path: string, version: string, reason: string}}
|
|
245
|
+
* The plugin-manifest channel.
|
|
246
|
+
*/
|
|
247
|
+
export function pluginManifestChannel(configDir, projectDir, guard) {
|
|
248
|
+
const record = path.join(configDir, PLUGINS, "installed_plugins.json");
|
|
249
|
+
let parsed = null;
|
|
250
|
+
try {
|
|
251
|
+
parsed = JSON.parse(readFileSync(record, "utf8"));
|
|
252
|
+
} catch {
|
|
253
|
+
return unresolved(PLUGIN_MANIFEST, "install-record-unreadable");
|
|
254
|
+
}
|
|
255
|
+
const installs = Object.values(parsed?.plugins ?? {}).flat();
|
|
256
|
+
const match = installs.find(
|
|
257
|
+
entry =>
|
|
258
|
+
entry?.projectPath === projectDir &&
|
|
259
|
+
typeof entry?.installPath === "string" &&
|
|
260
|
+
entry.installPath.includes(`${path.sep}lisa${path.sep}lisa${path.sep}`)
|
|
261
|
+
);
|
|
262
|
+
if (!match) {
|
|
263
|
+
return unresolved(PLUGIN_MANIFEST, "no-install-record-for-this-project");
|
|
264
|
+
}
|
|
265
|
+
return resolved(
|
|
266
|
+
PLUGIN_MANIFEST,
|
|
267
|
+
path.join(match.installPath, "hooks", `${guard}.sh`),
|
|
268
|
+
String(match.version ?? "")
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The copy this session itself resolved, as the harness reports it.
|
|
274
|
+
*
|
|
275
|
+
* `CLAUDE_PLUGIN_ROOT` is a claim the harness makes rather than something read
|
|
276
|
+
* off disk, so an unset variable is an unanswered question and not an absence.
|
|
277
|
+
* @param {string | undefined} pluginRoot - The harness-declared plugin root.
|
|
278
|
+
* @param {string} guard - Guard basename without extension.
|
|
279
|
+
* @returns {{channel: string, path: string, version: string, reason: string}}
|
|
280
|
+
* The session channel.
|
|
281
|
+
*/
|
|
282
|
+
export function sessionChannel(pluginRoot, guard) {
|
|
283
|
+
if (!pluginRoot) return unresolved("session", "plugin-root-not-declared");
|
|
284
|
+
const manifest = path.join(
|
|
285
|
+
pluginRoot,
|
|
286
|
+
PLUGIN_MANIFEST_DIR,
|
|
287
|
+
PLUGIN_MANIFEST_FILE
|
|
288
|
+
);
|
|
289
|
+
return resolved(
|
|
290
|
+
"session",
|
|
291
|
+
path.join(pluginRoot, "hooks", `${guard}.sh`),
|
|
292
|
+
readJsonVersion(manifest, "version")
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* The marketplace clone, which is what the next install will copy from.
|
|
298
|
+
* @param {string} configDir - Harness config directory.
|
|
299
|
+
* @param {string} guard - Guard basename without extension.
|
|
300
|
+
* @returns {{channel: string, path: string, version: string, reason: string}}
|
|
301
|
+
* The marketplace channel.
|
|
302
|
+
*/
|
|
303
|
+
export function marketplaceChannel(configDir, guard) {
|
|
304
|
+
const root = path.join(
|
|
305
|
+
configDir,
|
|
306
|
+
PLUGINS,
|
|
307
|
+
"marketplaces",
|
|
308
|
+
LISA,
|
|
309
|
+
PLUGINS,
|
|
310
|
+
LISA
|
|
311
|
+
);
|
|
312
|
+
const manifest = path.join(root, PLUGIN_MANIFEST_DIR, PLUGIN_MANIFEST_FILE);
|
|
313
|
+
return resolved(
|
|
314
|
+
"marketplace",
|
|
315
|
+
path.join(root, "hooks", `${guard}.sh`),
|
|
316
|
+
readJsonVersion(manifest, "version")
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Every cache entry a live session may still be pinned to.
|
|
322
|
+
*
|
|
323
|
+
* These are channels, not an archive. A session resolves its copy once at start
|
|
324
|
+
* and executes it for its whole life, so an entry six months old is serving
|
|
325
|
+
* whichever lane started while it was in force. Enumerating them is the only
|
|
326
|
+
* way the sweep can answer "is any agent being told this right now".
|
|
327
|
+
* @param {string} configDir - Harness config directory.
|
|
328
|
+
* @param {string} guard - Guard basename without extension.
|
|
329
|
+
* @returns {readonly {channel: string, path: string, version: string, reason: string}[]}
|
|
330
|
+
* One channel per cached vintage, or one unresolved channel.
|
|
331
|
+
*/
|
|
332
|
+
export function cacheChannels(configDir, guard) {
|
|
333
|
+
const root = path.join(configDir, PLUGINS, "cache", LISA, LISA);
|
|
334
|
+
let entries = [];
|
|
335
|
+
try {
|
|
336
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
337
|
+
} catch {
|
|
338
|
+
return [unresolved("cache", "cache-directory-unreadable")];
|
|
339
|
+
}
|
|
340
|
+
return entries
|
|
341
|
+
.filter(entry => entry.isDirectory())
|
|
342
|
+
.map(entry =>
|
|
343
|
+
resolved(
|
|
344
|
+
`cache:${entry.name}`,
|
|
345
|
+
path.join(root, entry.name, "hooks", `${guard}.sh`),
|
|
346
|
+
entry.name
|
|
347
|
+
)
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Every channel a guard can be SERVED from on this machine.
|
|
353
|
+
* @param {{
|
|
354
|
+
* readonly projectDir: string
|
|
355
|
+
* readonly configDir: string
|
|
356
|
+
* readonly pluginRoot?: string | undefined
|
|
357
|
+
* readonly guard?: string | undefined
|
|
358
|
+
* }} where - Where to look.
|
|
359
|
+
* @returns {readonly {channel: string, path: string, version: string, reason: string}[]}
|
|
360
|
+
* The channel roster.
|
|
361
|
+
*/
|
|
362
|
+
export function resolveGuardChannels(where) {
|
|
363
|
+
const guard = where.guard ?? DEFAULT_GUARD;
|
|
364
|
+
return [
|
|
365
|
+
repositoryChannel(where.projectDir, guard),
|
|
366
|
+
pluginManifestChannel(where.configDir, where.projectDir, guard),
|
|
367
|
+
sessionChannel(where.pluginRoot, guard),
|
|
368
|
+
marketplaceChannel(where.configDir, guard),
|
|
369
|
+
...cacheChannels(where.configDir, guard),
|
|
370
|
+
];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Every channel a guard is SOURCED from in the checkout.
|
|
375
|
+
*
|
|
376
|
+
* The deliberate counterpart to `resolveGuardChannels`: same scanner, same
|
|
377
|
+
* patterns, different copies. A tree whose source scan is empty and whose
|
|
378
|
+
* deployed scan is not is the gap this file closes, stated as two numbers.
|
|
379
|
+
* @param {string} projectDir - Repository root.
|
|
380
|
+
* @param {string} [guard] - Guard basename without extension.
|
|
381
|
+
* @returns {readonly {channel: string, path: string, version: string, reason: string}[]}
|
|
382
|
+
* The source roster.
|
|
383
|
+
*/
|
|
384
|
+
export function resolveSourceChannels(projectDir, guard = DEFAULT_GUARD) {
|
|
385
|
+
return SOURCE_GUARD_ROOTS.map(root =>
|
|
386
|
+
resolved(
|
|
387
|
+
`source:${root}`,
|
|
388
|
+
path.join(projectDir, root, `${guard}.sh`),
|
|
389
|
+
"source"
|
|
390
|
+
)
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Every forbidden advisory pattern a piece of guard text carries.
|
|
396
|
+
* @param {string} text - Guard file contents.
|
|
397
|
+
* @returns {readonly {id: string, label: string, why: string}[]} Matches.
|
|
398
|
+
*/
|
|
399
|
+
export function matchAdvisory(text) {
|
|
400
|
+
return FORBIDDEN_ADVISORY_PATTERNS.filter(entry =>
|
|
401
|
+
entry.pattern.test(text)
|
|
402
|
+
).map(({ id, label, why }) => ({ id, label, why }));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Scan a channel roster and report every copy dispensing outlawed advice.
|
|
407
|
+
* @param {{
|
|
408
|
+
* readonly channels?: readonly {
|
|
409
|
+
* channel: string, path: string, version: string, reason: string
|
|
410
|
+
* }[]
|
|
411
|
+
* readonly read?: (file: string) => string
|
|
412
|
+
* }} input - The roster, and how to read a copy.
|
|
413
|
+
* @returns {{
|
|
414
|
+
* readonly verdict: string
|
|
415
|
+
* readonly examinedCount: number
|
|
416
|
+
* readonly findings: readonly {
|
|
417
|
+
* readonly channel: string
|
|
418
|
+
* readonly path: string
|
|
419
|
+
* readonly version: string
|
|
420
|
+
* readonly id: string
|
|
421
|
+
* readonly label: string
|
|
422
|
+
* readonly why: string
|
|
423
|
+
* }[]
|
|
424
|
+
* readonly notMeasured: readonly {
|
|
425
|
+
* readonly channel: string
|
|
426
|
+
* readonly reason: string
|
|
427
|
+
* }[]
|
|
428
|
+
* readonly reportOnly: true
|
|
429
|
+
* }} The sweep result.
|
|
430
|
+
*/
|
|
431
|
+
export function scanDeployedGuardAdvice(input = {}) {
|
|
432
|
+
const channels = Array.isArray(input.channels) ? input.channels : null;
|
|
433
|
+
const read = input.read ?? (file => readFileSync(file, "utf8"));
|
|
434
|
+
if (channels === null) {
|
|
435
|
+
return sweep(
|
|
436
|
+
[],
|
|
437
|
+
[{ channel: "<roster>", reason: "channel-list-unavailable" }],
|
|
438
|
+
0
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const findings = [];
|
|
443
|
+
const notMeasured = [];
|
|
444
|
+
let examinedCount = 0;
|
|
445
|
+
for (const channel of channels) {
|
|
446
|
+
if (!channel?.path) {
|
|
447
|
+
notMeasured.push({
|
|
448
|
+
channel: channel?.channel ?? "<unnamed>",
|
|
449
|
+
reason: channel?.reason || "unresolved",
|
|
450
|
+
});
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
let text = null;
|
|
454
|
+
try {
|
|
455
|
+
text = read(channel.path);
|
|
456
|
+
} catch {
|
|
457
|
+
notMeasured.push({ channel: channel.channel, reason: "copy-unreadable" });
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
examinedCount += 1;
|
|
461
|
+
for (const match of matchAdvisory(text)) {
|
|
462
|
+
findings.push({
|
|
463
|
+
channel: channel.channel,
|
|
464
|
+
path: channel.path,
|
|
465
|
+
version: channel.version,
|
|
466
|
+
...match,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return sweep(findings, notMeasured, examinedCount);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Assemble a sweep result, with blindness outranking findings.
|
|
475
|
+
*
|
|
476
|
+
* A sweep that could not read part of its roster cannot call the rest clean —
|
|
477
|
+
* but it still reports what it DID find, because a real finding does not stop
|
|
478
|
+
* mattering because a different channel was unreadable.
|
|
479
|
+
* @param {readonly object[]} findings - Copies dispensing outlawed advice.
|
|
480
|
+
* @param {readonly object[]} notMeasured - Channels that could not be read.
|
|
481
|
+
* @param {number} examinedCount - Copies actually read.
|
|
482
|
+
* @returns {ReturnType<typeof scanDeployedGuardAdvice>} The sweep.
|
|
483
|
+
*/
|
|
484
|
+
function sweep(findings, notMeasured, examinedCount) {
|
|
485
|
+
const verdict =
|
|
486
|
+
notMeasured.length > 0
|
|
487
|
+
? "NOT_MEASURED"
|
|
488
|
+
: findings.length > 0
|
|
489
|
+
? "OUTLAWED_ADVICE"
|
|
490
|
+
: "CLEAN";
|
|
491
|
+
return { verdict, examinedCount, findings, notMeasured, reportOnly: true };
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* The CLI exit status for a sweep.
|
|
496
|
+
*
|
|
497
|
+
* `2` is load-bearing: a sweep that examined nothing must not exit 0 beside an
|
|
498
|
+
* empty finding list, which is indistinguishable from a clean fleet.
|
|
499
|
+
* @param {{ readonly verdict?: string }} result - A sweep result.
|
|
500
|
+
* @returns {0 | 1 | 2} The exit status.
|
|
501
|
+
*/
|
|
502
|
+
export function deployedGuardAdviceExitCode(result) {
|
|
503
|
+
if (result?.verdict === "CLEAN") return 0;
|
|
504
|
+
if (result?.verdict === "OUTLAWED_ADVICE") return 1;
|
|
505
|
+
return 2;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** Channel-name prefix marking a superseded copy a session may still be pinned to. */
|
|
509
|
+
const CACHE_PREFIX = "cache:";
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Numeric release fields of a version string, ignoring prerelease suffixes.
|
|
513
|
+
*
|
|
514
|
+
* A field that is not a plain number becomes 0 rather than throwing, so a
|
|
515
|
+
* version string this does not understand can never sort ABOVE one it does.
|
|
516
|
+
* @param {string} version - A dotted version string.
|
|
517
|
+
* @returns {readonly number[]} Three release fields.
|
|
518
|
+
*/
|
|
519
|
+
function releaseFields(version) {
|
|
520
|
+
const parts = String(version).split("-")[0].split("+")[0].split(".");
|
|
521
|
+
return [0, 1, 2].map(index =>
|
|
522
|
+
/^\d+$/u.test(parts[index] ?? "") ? Number(parts[index]) : 0
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Order two versions newest-first. String order is not usable here: `2.9.0`
|
|
528
|
+
* sorts above `2.128.1` lexically and would name the wrong copy.
|
|
529
|
+
* @param {string} a - Left version.
|
|
530
|
+
* @param {string} b - Right version.
|
|
531
|
+
* @returns {number} Negative when `a` is newer.
|
|
532
|
+
*/
|
|
533
|
+
function newestFirst(a, b) {
|
|
534
|
+
const left = releaseFields(a);
|
|
535
|
+
const right = releaseFields(b);
|
|
536
|
+
for (let index = 0; index < 3; index += 1) {
|
|
537
|
+
if (left[index] !== right[index]) return right[index] - left[index];
|
|
538
|
+
}
|
|
539
|
+
return 0;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* One rendered finding: the copy, its vintage, and the pattern it matched.
|
|
544
|
+
* @param {{channel: string, path: string, version: string, id: string, label: string, why: string}} finding
|
|
545
|
+
* A finding.
|
|
546
|
+
* @returns {readonly string[]} Report lines.
|
|
547
|
+
*/
|
|
548
|
+
function renderFinding(finding) {
|
|
549
|
+
return [
|
|
550
|
+
` ! ${finding.channel} (lisa ${finding.version})`,
|
|
551
|
+
` ${finding.path}`,
|
|
552
|
+
` ${finding.id}: ${finding.label} — ${finding.why}`,
|
|
553
|
+
];
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Render the sweep for an operator.
|
|
558
|
+
*
|
|
559
|
+
* IN-FORCE findings are printed in full and CACHED ones are summarised, with
|
|
560
|
+
* the newest cached vintage per pattern shown. That split is the whole
|
|
561
|
+
* operator judgement: a developer machine legitimately holds hundreds of
|
|
562
|
+
* superseded copies, so printing all of them buries the one that is serving
|
|
563
|
+
* somebody. The newest cached copy carrying a pattern is the one most likely to
|
|
564
|
+
* still be pinned by a live session, so it is the one named.
|
|
565
|
+
* @param {ReturnType<typeof scanDeployedGuardAdvice>} result - A sweep result.
|
|
566
|
+
* @returns {string} A terminal-first report.
|
|
567
|
+
*/
|
|
568
|
+
export function formatDeployedGuardAdviceReport(result) {
|
|
569
|
+
const inForce = result.findings.filter(
|
|
570
|
+
f => !f.channel.startsWith(CACHE_PREFIX)
|
|
571
|
+
);
|
|
572
|
+
const cached = result.findings.filter(f =>
|
|
573
|
+
f.channel.startsWith(CACHE_PREFIX)
|
|
574
|
+
);
|
|
575
|
+
const lines = [
|
|
576
|
+
`Deployed guard advice: ${result.verdict}`,
|
|
577
|
+
`Read ${result.examinedCount} deployed copy(s); ${result.findings.length} carry advice this repository has outlawed.`,
|
|
578
|
+
];
|
|
579
|
+
|
|
580
|
+
lines.push(`In force now: ${inForce.length} finding(s).`);
|
|
581
|
+
for (const finding of inForce) lines.push(...renderFinding(finding));
|
|
582
|
+
|
|
583
|
+
lines.push(
|
|
584
|
+
`Superseded copies a live session may still be pinned to: ${cached.length} finding(s) across ${new Set(cached.map(f => f.channel)).size} vintage(s).`
|
|
585
|
+
);
|
|
586
|
+
for (const id of new Set(cached.map(f => f.id))) {
|
|
587
|
+
const newest = cached
|
|
588
|
+
.filter(f => f.id === id)
|
|
589
|
+
.sort((a, b) => newestFirst(a.version, b.version))[0];
|
|
590
|
+
lines.push(
|
|
591
|
+
...renderFinding(newest),
|
|
592
|
+
" (newest cached copy carrying it)"
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (result.notMeasured.length > 0) {
|
|
597
|
+
lines.push(
|
|
598
|
+
`${result.notMeasured.length} channel(s) could not be read. This is not a clean fleet — it is an unanswered question.`,
|
|
599
|
+
...result.notMeasured.map(
|
|
600
|
+
entry => ` ? ${entry.channel}: ${entry.reason}`
|
|
601
|
+
)
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
return `${lines.join("\n")}\n`;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* True when `moduleUrl` names the module node was asked to run.
|
|
609
|
+
* @param {string} moduleUrl - The caller's own `import.meta.url`.
|
|
610
|
+
* @param {string | undefined} [argv1] - Entry path.
|
|
611
|
+
* @returns {boolean} Whether to run the CLI body.
|
|
612
|
+
*/
|
|
613
|
+
export function invokedAsScript(moduleUrl, argv1 = process.argv[1]) {
|
|
614
|
+
if (!argv1) return false;
|
|
615
|
+
try {
|
|
616
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
|
|
617
|
+
} catch {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (invokedAsScript(import.meta.url)) {
|
|
623
|
+
const configDir =
|
|
624
|
+
process.env.CLAUDE_CONFIG_DIR ??
|
|
625
|
+
path.join(process.env.HOME ?? "", ".claude");
|
|
626
|
+
const result = scanDeployedGuardAdvice({
|
|
627
|
+
channels: resolveGuardChannels({
|
|
628
|
+
projectDir: process.cwd(),
|
|
629
|
+
configDir,
|
|
630
|
+
pluginRoot: process.env.CLAUDE_PLUGIN_ROOT,
|
|
631
|
+
guard: process.argv[2] ?? DEFAULT_GUARD,
|
|
632
|
+
}),
|
|
633
|
+
});
|
|
634
|
+
process.stdout.write(formatDeployedGuardAdviceReport(result));
|
|
635
|
+
process.exitCode = deployedGuardAdviceExitCode(result);
|
|
636
|
+
}
|