@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,709 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* check-guard-parity-notes — refuse a guard's parity note that claims a port is
|
|
4
|
+
* missing when that port is present in the tree (CodySwannGT/lisa#3908).
|
|
5
|
+
*
|
|
6
|
+
* ## The defect
|
|
7
|
+
*
|
|
8
|
+
* `block-managed-file-edits.sh` carried a note headed "Parity gap, recorded
|
|
9
|
+
* rather than silently dropped" saying the guard had "no Antigravity, Codex or
|
|
10
|
+
* OpenCode port". Two of those three shipped afterwards; the note did not
|
|
11
|
+
* change, because nothing read it. Its neighbour on the very next line — the
|
|
12
|
+
* machine-checked `lisa-guard-capabilities:` declaration — stayed honest over
|
|
13
|
+
* the same period, and the only difference between them is that something
|
|
14
|
+
* reads one of them.
|
|
15
|
+
*
|
|
16
|
+
* A stale gap note is worse than no note, and the reason is the note's own
|
|
17
|
+
* stated purpose: it exists so a reader treats the gap as KNOWN and stops
|
|
18
|
+
* investigating. An overstated one sends that reader looking for three missing
|
|
19
|
+
* ports, they find two present, and they have no way to tell which third is
|
|
20
|
+
* real without redoing the measurement the note was written to spare them.
|
|
21
|
+
*
|
|
22
|
+
* ## What this refuses, and what it deliberately does not
|
|
23
|
+
*
|
|
24
|
+
* A CLAIM is refused when the surface it names has a port. Nothing else about
|
|
25
|
+
* a note is this check's business:
|
|
26
|
+
*
|
|
27
|
+
* refused the note says a surface has no port and the port exists. The
|
|
28
|
+
* note overstates the gap; the tree is the authority.
|
|
29
|
+
* accepted the note says a surface has no port and none exists. A genuine
|
|
30
|
+
* gap stays recordable — that is the whole point of the note, and
|
|
31
|
+
* a check that demanded its deletion would delete the record
|
|
32
|
+
* along with the drift.
|
|
33
|
+
* unread a guard with no claim at all. Absence of a note is not a
|
|
34
|
+
* finding: most guards have complete parity and say nothing.
|
|
35
|
+
*
|
|
36
|
+
* ## Surface presence is DERIVED, never declared
|
|
37
|
+
*
|
|
38
|
+
* The one thing that must not be hand-maintained here is the answer, because a
|
|
39
|
+
* second hand-written enumeration of which ports exist would go stale exactly
|
|
40
|
+
* the way the first one did. So presence is resolved from the files: a guard
|
|
41
|
+
* `<id>` has an Antigravity port when `plugins/src/base/hooks/<id>.agy.sh`
|
|
42
|
+
* exists, an OpenCode port when `src/opencode/plugin-templates/lisa-<id>.ts`
|
|
43
|
+
* does, and so on per `SURFACES`. Add a surface to Lisa and the entry added
|
|
44
|
+
* here is a path shape, not a list of which guards have it.
|
|
45
|
+
*
|
|
46
|
+
* ## A path shape alone measures the wrong thing on Codex
|
|
47
|
+
*
|
|
48
|
+
* A file named for the guard is sufficient evidence that a surface carries it;
|
|
49
|
+
* it is not NECESSARY. Codex is the surface where the difference is real, and
|
|
50
|
+
* the first version of this file got Codex wrong for exactly that reason
|
|
51
|
+
* (CodySwannGT/lisa#3750).
|
|
52
|
+
*
|
|
53
|
+
* `src/codex/scripts/<id>.sh` is the retired channel. `src/codex/hooks-installer.ts`
|
|
54
|
+
* says so in its own opening remark — the project overlay moved to
|
|
55
|
+
* plugin-bundled hooks plus one repository-owned dispatcher, and that module
|
|
56
|
+
* "remains for migration coverage of the retired linked script/rule layout".
|
|
57
|
+
* Two channels deliver a guard to Codex today, and neither puts a file in that
|
|
58
|
+
* directory:
|
|
59
|
+
*
|
|
60
|
+
* - `scripts/lisa-enforcement-fallback.sh`, whose roster names the guards it
|
|
61
|
+
* dispatches. `src/codex/enforcement-fallback-installer.ts` registers it on
|
|
62
|
+
* `PreToolUse` for `Bash|Edit|Write|apply_patch`.
|
|
63
|
+
* - `plugins/lisa/.codex-plugin/hooks.json`, the Codex plugin manifest, whose
|
|
64
|
+
* hook commands name the guards Codex runs directly.
|
|
65
|
+
*
|
|
66
|
+
* So a `registries` entry is read the same way a `ports` entry is — from the
|
|
67
|
+
* file, never from a declaration — but it answers "does this file REGISTER the
|
|
68
|
+
* guard" rather than "is there a file named for it". Both readers are
|
|
69
|
+
* STRUCTURAL: the roster reader parses the `for guard in ... ; do` list and the
|
|
70
|
+
* manifest reader parses JSON. Neither scans for the guard's name in prose,
|
|
71
|
+
* because the dispatcher's own header lists guard names in a sentence and a
|
|
72
|
+
* substring sweep would count that sentence as registration.
|
|
73
|
+
*
|
|
74
|
+
* ## The claim grammar
|
|
75
|
+
*
|
|
76
|
+
* Bounded on purpose. Inside one comment block, between a negation (`no`,
|
|
77
|
+
* `lacks`, `missing`, `without`) and the word `port`/`ports`, every word must
|
|
78
|
+
* be a surface name or an ordinary connective. Anything else ends the
|
|
79
|
+
* candidate. So this is a claim:
|
|
80
|
+
*
|
|
81
|
+
* This guard has no Antigravity, Codex or OpenCode port
|
|
82
|
+
*
|
|
83
|
+
* and a sentence that merely mentions a surface and, separately, the word
|
|
84
|
+
* "port" is not. Widening the grammar to catch every phrasing would make the
|
|
85
|
+
* check's own reach unreviewable; narrowing it is why a finding it does report
|
|
86
|
+
* can be trusted without reading the source. What the grammar cannot see is
|
|
87
|
+
* declared in `BLIND_SPOTS` and printed on every run.
|
|
88
|
+
*
|
|
89
|
+
* Determinism: Node built-ins only, no network, no clock, no `Math.random`.
|
|
90
|
+
* The scanned root is a parameter so the suite can point it at a fixture tree
|
|
91
|
+
* holding a known offender.
|
|
92
|
+
*
|
|
93
|
+
* CLI:
|
|
94
|
+
* node scripts/check-guard-parity-notes.mjs [--json] [root]
|
|
95
|
+
*
|
|
96
|
+
* Exit codes (mirroring the sibling check-* scripts):
|
|
97
|
+
* 0 — guard sources were read and no claim contradicts the tree.
|
|
98
|
+
* 1 — >=1 finding.
|
|
99
|
+
* 2 — operational error: unknown flag, or ZERO guard sources read.
|
|
100
|
+
*
|
|
101
|
+
* @module scripts/check-guard-parity-notes
|
|
102
|
+
*/
|
|
103
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
104
|
+
import path from "node:path";
|
|
105
|
+
import process from "node:process";
|
|
106
|
+
|
|
107
|
+
import { invokedAsScript } from "./lib/invoked-as-script.mjs";
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* One refused claim.
|
|
111
|
+
* @typedef {object} ParityFinding
|
|
112
|
+
* @property {string} file - Repo-relative path of the note.
|
|
113
|
+
* @property {number} line - One-based line the claim starts on.
|
|
114
|
+
* @property {string} guard - Guard id the note belongs to.
|
|
115
|
+
* @property {string} surface - Surface the note says has no port.
|
|
116
|
+
* @property {string} evidence - Repo-relative path that contradicts the claim.
|
|
117
|
+
* @property {string} claim - The claim as written.
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A completed sweep.
|
|
122
|
+
* @typedef {object} ParityReport
|
|
123
|
+
* @property {number} files - Guard sources read.
|
|
124
|
+
* @property {number} claims - Parity claims recognised.
|
|
125
|
+
* @property {ParityFinding[]} findings - Claims the tree contradicts.
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The coding-agent surfaces Lisa supports, and where a port for each lives.
|
|
130
|
+
*
|
|
131
|
+
* `aliases` are the single words a note may use for the surface; `ports` are
|
|
132
|
+
* the candidate paths, any one of which existing means the surface carries the
|
|
133
|
+
* guard. Both are matched case-insensitively on the alias side and exactly on
|
|
134
|
+
* the path side.
|
|
135
|
+
*
|
|
136
|
+
* `registries` are files that REGISTER a guard without being named for it —
|
|
137
|
+
* see the module remark above. Each names a `reader` from
|
|
138
|
+
* {@link REGISTRY_READERS}, which returns the guard ids that file registers.
|
|
139
|
+
*/
|
|
140
|
+
export const SURFACES = Object.freeze([
|
|
141
|
+
Object.freeze({
|
|
142
|
+
id: "claude",
|
|
143
|
+
aliases: Object.freeze(["claude"]),
|
|
144
|
+
ports: Object.freeze([
|
|
145
|
+
"plugins/src/base/hooks/{id}.sh",
|
|
146
|
+
"plugins/src/base/hooks/{id}.mjs",
|
|
147
|
+
]),
|
|
148
|
+
}),
|
|
149
|
+
Object.freeze({
|
|
150
|
+
id: "codex",
|
|
151
|
+
aliases: Object.freeze(["codex"]),
|
|
152
|
+
ports: Object.freeze([
|
|
153
|
+
"src/codex/scripts/{id}.sh",
|
|
154
|
+
"src/codex/scripts/{id}.mjs",
|
|
155
|
+
]),
|
|
156
|
+
registries: Object.freeze([
|
|
157
|
+
Object.freeze({
|
|
158
|
+
path: "scripts/lisa-enforcement-fallback.sh",
|
|
159
|
+
reader: "dispatcher-roster",
|
|
160
|
+
}),
|
|
161
|
+
Object.freeze({
|
|
162
|
+
path: "plugins/lisa/.codex-plugin/hooks.json",
|
|
163
|
+
reader: "codex-hooks-manifest",
|
|
164
|
+
}),
|
|
165
|
+
]),
|
|
166
|
+
}),
|
|
167
|
+
Object.freeze({
|
|
168
|
+
id: "cursor",
|
|
169
|
+
aliases: Object.freeze(["cursor"]),
|
|
170
|
+
ports: Object.freeze([
|
|
171
|
+
"plugins/lisa-cursor/hooks/{id}.sh",
|
|
172
|
+
"plugins/lisa-cursor/hooks/{id}.mjs",
|
|
173
|
+
]),
|
|
174
|
+
}),
|
|
175
|
+
Object.freeze({
|
|
176
|
+
id: "opencode",
|
|
177
|
+
aliases: Object.freeze(["opencode"]),
|
|
178
|
+
ports: Object.freeze(["src/opencode/plugin-templates/lisa-{id}.ts"]),
|
|
179
|
+
}),
|
|
180
|
+
Object.freeze({
|
|
181
|
+
id: "antigravity",
|
|
182
|
+
aliases: Object.freeze(["antigravity", "agy"]),
|
|
183
|
+
ports: Object.freeze(["plugins/src/base/hooks/{id}.agy.sh"]),
|
|
184
|
+
}),
|
|
185
|
+
Object.freeze({
|
|
186
|
+
id: "copilot",
|
|
187
|
+
aliases: Object.freeze(["copilot"]),
|
|
188
|
+
ports: Object.freeze([
|
|
189
|
+
"plugins/lisa-copilot/hooks/{id}.sh",
|
|
190
|
+
"plugins/lisa-copilot/hooks/{id}.mjs",
|
|
191
|
+
]),
|
|
192
|
+
}),
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Directories holding guard SOURCES.
|
|
197
|
+
*
|
|
198
|
+
* Generated copies — the per-plugin `hooks/` roots and the shipped host guard
|
|
199
|
+
* directory — are excluded on purpose. They inherit their note verbatim from a
|
|
200
|
+
* source read here, so scanning them would report the same drift five more
|
|
201
|
+
* times and make the finding count meaningless.
|
|
202
|
+
*/
|
|
203
|
+
export const SCANNED_DIRS = Object.freeze([
|
|
204
|
+
"plugins/src/base/hooks",
|
|
205
|
+
"src/codex/scripts",
|
|
206
|
+
"src/opencode/plugin-templates",
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
/** What this sweep does not look at, printed on every run. */
|
|
210
|
+
export const BLIND_SPOTS = Object.freeze([
|
|
211
|
+
"generated copies of a guard — they inherit the source note scanned here",
|
|
212
|
+
"gaps stated outside the claim grammar (see the module remarks)",
|
|
213
|
+
"understated gaps: a note that omits a genuinely missing surface passes",
|
|
214
|
+
"whether a present port file is actually registered for its agent",
|
|
215
|
+
"a delivery channel absent from SURFACES — presence is only as complete as the ports and registries named there",
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
/** File extensions a guard source may use. */
|
|
219
|
+
const GUARD_EXTENSIONS = Object.freeze([".sh", ".mjs", ".ts", ".py"]);
|
|
220
|
+
|
|
221
|
+
/** Runs of whitespace, for splitting a roster or a command line. */
|
|
222
|
+
const WHITESPACE = /\s+/u;
|
|
223
|
+
|
|
224
|
+
/** The shape every guard id has; anything else is not one. */
|
|
225
|
+
const GUARD_ID_SHAPE = /^[a-z0-9][a-z0-9-]*$/u;
|
|
226
|
+
|
|
227
|
+
/** Shell words in the dispatcher roster that are syntax, not guard names. */
|
|
228
|
+
const ROSTER_KEYWORDS = new Set(["", "\\", "do", "for", "guard", "in"]);
|
|
229
|
+
|
|
230
|
+
/** Words that open a claim. */
|
|
231
|
+
const NEGATIONS = new Set(["no", "lacks", "lack", "missing", "without"]);
|
|
232
|
+
|
|
233
|
+
/** Words that close a claim. */
|
|
234
|
+
const CLAIM_TARGETS = new Set(["port", "ports"]);
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Words permitted between the negation and the target.
|
|
238
|
+
*
|
|
239
|
+
* Connectives and the nouns a parity sentence uses about itself. Anything
|
|
240
|
+
* outside this set ends the candidate, which is what keeps the grammar from
|
|
241
|
+
* spanning an unrelated clause.
|
|
242
|
+
*/
|
|
243
|
+
const FILLER = new Set([
|
|
244
|
+
"a",
|
|
245
|
+
"an",
|
|
246
|
+
"and",
|
|
247
|
+
"any",
|
|
248
|
+
"code",
|
|
249
|
+
"dedicated",
|
|
250
|
+
"guard",
|
|
251
|
+
"has",
|
|
252
|
+
"have",
|
|
253
|
+
"hook",
|
|
254
|
+
"hooks",
|
|
255
|
+
"its",
|
|
256
|
+
"native",
|
|
257
|
+
"neither",
|
|
258
|
+
"no",
|
|
259
|
+
"nor",
|
|
260
|
+
"of",
|
|
261
|
+
"or",
|
|
262
|
+
"own",
|
|
263
|
+
"separate",
|
|
264
|
+
"still",
|
|
265
|
+
"the",
|
|
266
|
+
"this",
|
|
267
|
+
"yet",
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
/** Characters that end a candidate claim outright. */
|
|
271
|
+
const TERMINATORS = new Set([".", ";", ":", "—", "–", "(", ")", "`", '"']);
|
|
272
|
+
|
|
273
|
+
/** Words and terminators, in order, with their offsets. */
|
|
274
|
+
const TOKEN_PATTERN = /[A-Za-z][A-Za-z-]*|[.;:—–()`"]/gu;
|
|
275
|
+
|
|
276
|
+
/** Surface id for each alias a note may write. */
|
|
277
|
+
const SURFACE_BY_ALIAS = new Map(
|
|
278
|
+
SURFACES.flatMap(surface => surface.aliases.map(alias => [alias, surface.id]))
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* The guard a source file belongs to.
|
|
283
|
+
*
|
|
284
|
+
* Every surface spells the same guard differently — an Antigravity sibling
|
|
285
|
+
* carries `.agy` before its extension, an OpenCode template carries a `lisa-`
|
|
286
|
+
* prefix — so the id is what lets one note be checked against every surface.
|
|
287
|
+
* @param {string} relative - Repo-relative path, forward-slashed.
|
|
288
|
+
* @returns {string | undefined} The guard id, or nothing for a non-guard file.
|
|
289
|
+
*/
|
|
290
|
+
export function guardIdFor(relative) {
|
|
291
|
+
const base = path.posix.basename(relative);
|
|
292
|
+
const extension = GUARD_EXTENSIONS.find(candidate =>
|
|
293
|
+
base.endsWith(candidate)
|
|
294
|
+
);
|
|
295
|
+
if (!extension) return undefined;
|
|
296
|
+
const stem = base.slice(0, -extension.length);
|
|
297
|
+
const undotted = stem.endsWith(".agy") ? stem.slice(0, -".agy".length) : stem;
|
|
298
|
+
if (path.posix.dirname(relative) !== "src/opencode/plugin-templates") {
|
|
299
|
+
return undotted;
|
|
300
|
+
}
|
|
301
|
+
return undotted.startsWith("lisa-")
|
|
302
|
+
? undotted.slice("lisa-".length)
|
|
303
|
+
: undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* The port file that proves a surface carries a guard.
|
|
308
|
+
* @param {string} repoRoot - Absolute path of the tree being scanned.
|
|
309
|
+
* @param {string} guardId - Guard id from {@link guardIdFor}.
|
|
310
|
+
* @param {string} surfaceId - Surface id from {@link SURFACES}.
|
|
311
|
+
* @returns {string | undefined} Repo-relative path, or nothing if absent.
|
|
312
|
+
*/
|
|
313
|
+
export function portFor(repoRoot, guardId, surfaceId) {
|
|
314
|
+
const surface = SURFACES.find(candidate => candidate.id === surfaceId);
|
|
315
|
+
if (!surface) return undefined;
|
|
316
|
+
return surface.ports
|
|
317
|
+
.map(template => template.replace("{id}", guardId))
|
|
318
|
+
.find(relative => existsSync(path.join(repoRoot, relative)));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Guard ids the enforcement dispatcher runs.
|
|
323
|
+
*
|
|
324
|
+
* Read from the `for guard in ... ; do` roster and from nowhere else. The
|
|
325
|
+
* dispatcher's header also lists guard names in an English sentence, so a
|
|
326
|
+
* substring sweep of this file would report every guard it MENTIONS as one it
|
|
327
|
+
* RUNS — the sentinel-versus-talk-about-the-sentinel mistake, and the reason
|
|
328
|
+
* this reader is a parse rather than a search.
|
|
329
|
+
* @param {string} source - Full contents of the dispatcher.
|
|
330
|
+
* @returns {Set<string>} Guard ids in the roster; empty when there is none.
|
|
331
|
+
*/
|
|
332
|
+
export function dispatcherRoster(source) {
|
|
333
|
+
const names = new Set();
|
|
334
|
+
const lines = source.split("\n");
|
|
335
|
+
let index = lines.findIndex(line =>
|
|
336
|
+
line.trimStart().startsWith("for guard in")
|
|
337
|
+
);
|
|
338
|
+
if (index === -1) return names;
|
|
339
|
+
let roster = "";
|
|
340
|
+
let open = true;
|
|
341
|
+
while (open && index < lines.length) {
|
|
342
|
+
const line = lines[index];
|
|
343
|
+
roster += ` ${line}`;
|
|
344
|
+
open = !line.includes("; do");
|
|
345
|
+
index += 1;
|
|
346
|
+
}
|
|
347
|
+
// Still open at end of file means the roster was never terminated, which is
|
|
348
|
+
// a malformed dispatcher rather than an empty one. Report nothing: a partial
|
|
349
|
+
// parse that answered anyway would claim registration it had not read.
|
|
350
|
+
if (open) return new Set();
|
|
351
|
+
for (const raw of roster.split(WHITESPACE)) {
|
|
352
|
+
const word = raw.endsWith(";") ? raw.slice(0, -1) : raw;
|
|
353
|
+
if (ROSTER_KEYWORDS.has(word) || !GUARD_ID_SHAPE.test(word)) continue;
|
|
354
|
+
names.add(word);
|
|
355
|
+
}
|
|
356
|
+
return names;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Guard ids the Codex plugin manifest registers.
|
|
361
|
+
*
|
|
362
|
+
* Structural: the JSON is parsed and every hook `command` is reduced to the
|
|
363
|
+
* basename of the file it runs. A command carrying arguments (`… --hook`)
|
|
364
|
+
* still resolves to its script, and anything unparseable registers nothing.
|
|
365
|
+
* @param {string} source - Full contents of the manifest.
|
|
366
|
+
* @returns {Set<string>} Guard ids the manifest registers.
|
|
367
|
+
*/
|
|
368
|
+
export function codexHooksManifest(source) {
|
|
369
|
+
const names = new Set();
|
|
370
|
+
let parsed;
|
|
371
|
+
try {
|
|
372
|
+
parsed = JSON.parse(source);
|
|
373
|
+
} catch {
|
|
374
|
+
// probe-direction: fail-closed — an unreadable manifest registers nothing,
|
|
375
|
+
// so a genuine gap note keeps passing rather than being refused on the
|
|
376
|
+
// strength of a file this reader could not read.
|
|
377
|
+
return names;
|
|
378
|
+
}
|
|
379
|
+
const events = parsed?.hooks;
|
|
380
|
+
if (events === null || typeof events !== "object") return names;
|
|
381
|
+
for (const entries of Object.values(events)) {
|
|
382
|
+
if (!Array.isArray(entries)) continue;
|
|
383
|
+
for (const entry of entries) {
|
|
384
|
+
for (const hook of Array.isArray(entry?.hooks) ? entry.hooks : []) {
|
|
385
|
+
const id = guardIdOfCommand(hook?.command);
|
|
386
|
+
if (id !== undefined) names.add(id);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return names;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* The guard a hook command runs.
|
|
395
|
+
* @param {unknown} command - A manifest hook's `command` field.
|
|
396
|
+
* @returns {string | undefined} The guard id, or nothing.
|
|
397
|
+
*/
|
|
398
|
+
function guardIdOfCommand(command) {
|
|
399
|
+
if (typeof command !== "string") return undefined;
|
|
400
|
+
const [word] = command.split(WHITESPACE);
|
|
401
|
+
const base = word.slice(word.lastIndexOf("/") + 1);
|
|
402
|
+
const extension = GUARD_EXTENSIONS.find(candidate =>
|
|
403
|
+
base.endsWith(candidate)
|
|
404
|
+
);
|
|
405
|
+
if (extension === undefined) return undefined;
|
|
406
|
+
const stem = base.slice(0, -extension.length);
|
|
407
|
+
return GUARD_ID_SHAPE.test(stem) ? stem : undefined;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** How each `registries` entry's `reader` is resolved. */
|
|
411
|
+
export const REGISTRY_READERS = Object.freeze({
|
|
412
|
+
"codex-hooks-manifest": codexHooksManifest,
|
|
413
|
+
"dispatcher-roster": dispatcherRoster,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* The registration file that proves a surface RUNS a guard.
|
|
418
|
+
* @param {string} repoRoot - Absolute path of the tree being scanned.
|
|
419
|
+
* @param {string} guardId - Guard id from {@link guardIdFor}.
|
|
420
|
+
* @param {string} surfaceId - Surface id from {@link SURFACES}.
|
|
421
|
+
* @returns {string | undefined} Repo-relative path, or nothing if none does.
|
|
422
|
+
*/
|
|
423
|
+
export function registryFor(repoRoot, guardId, surfaceId) {
|
|
424
|
+
const surface = SURFACES.find(candidate => candidate.id === surfaceId);
|
|
425
|
+
for (const registry of surface?.registries ?? []) {
|
|
426
|
+
const absolute = path.join(repoRoot, registry.path);
|
|
427
|
+
if (!existsSync(absolute)) continue;
|
|
428
|
+
const read = REGISTRY_READERS[registry.reader];
|
|
429
|
+
if (read !== undefined && read(readFileSync(absolute, "utf8")).has(guardId))
|
|
430
|
+
return registry.path;
|
|
431
|
+
}
|
|
432
|
+
return undefined;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* The file that contradicts a claim of no port on one surface.
|
|
437
|
+
*
|
|
438
|
+
* A port file OR a registration; either is proof the surface carries the
|
|
439
|
+
* guard, and a note claiming otherwise is refused on whichever is found.
|
|
440
|
+
* @param {string} repoRoot - Absolute path of the tree being scanned.
|
|
441
|
+
* @param {string} guardId - Guard id from {@link guardIdFor}.
|
|
442
|
+
* @param {string} surfaceId - Surface id from {@link SURFACES}.
|
|
443
|
+
* @returns {string | undefined} Repo-relative path, or nothing.
|
|
444
|
+
*/
|
|
445
|
+
export function evidenceFor(repoRoot, guardId, surfaceId) {
|
|
446
|
+
return (
|
|
447
|
+
portFor(repoRoot, guardId, surfaceId) ??
|
|
448
|
+
registryFor(repoRoot, guardId, surfaceId)
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Consecutive comment lines, joined into blocks.
|
|
454
|
+
*
|
|
455
|
+
* Only comments are read. A guard's body is code, and a string literal in it
|
|
456
|
+
* that happens to read like a parity note is not one.
|
|
457
|
+
* Each block records where every contributing line begins inside the joined
|
|
458
|
+
* text, so a claim can be reported at the line it is written on rather than at
|
|
459
|
+
* the top of a header that may be seventy lines above it.
|
|
460
|
+
* @param {string} source - Full file contents.
|
|
461
|
+
* @returns {{ line: number, text: string, spans: { at: number, line: number }[] }[]}
|
|
462
|
+
* Blocks, in file order.
|
|
463
|
+
*/
|
|
464
|
+
export function commentBlocks(source) {
|
|
465
|
+
const blocks = [];
|
|
466
|
+
source.split("\n").forEach((raw, index) => {
|
|
467
|
+
const stripped = commentTextOf(raw);
|
|
468
|
+
if (stripped === undefined) {
|
|
469
|
+
blocks.push(undefined);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const previous = blocks.at(-1);
|
|
473
|
+
if (previous === undefined) {
|
|
474
|
+
blocks.push({
|
|
475
|
+
line: index + 1,
|
|
476
|
+
text: stripped,
|
|
477
|
+
spans: [{ at: 0, line: index + 1 }],
|
|
478
|
+
});
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
blocks[blocks.length - 1] = {
|
|
482
|
+
line: previous.line,
|
|
483
|
+
text: `${previous.text} ${stripped}`,
|
|
484
|
+
spans: [
|
|
485
|
+
...previous.spans,
|
|
486
|
+
{ at: previous.text.length + 1, line: index + 1 },
|
|
487
|
+
],
|
|
488
|
+
};
|
|
489
|
+
});
|
|
490
|
+
return blocks.filter(block => block !== undefined);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* The source line an offset inside a block's joined text came from.
|
|
495
|
+
* @param {{ spans: { at: number, line: number }[], line: number }} block - Block.
|
|
496
|
+
* @param {number} offset - Character offset within the block's joined text.
|
|
497
|
+
* @returns {number} One-based source line.
|
|
498
|
+
*/
|
|
499
|
+
export function lineAt(block, offset) {
|
|
500
|
+
return (
|
|
501
|
+
block.spans.filter(span => span.at <= offset).at(-1)?.line ?? block.line
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* A comment line's text, with its marker removed.
|
|
507
|
+
* @param {string} raw - One line of source.
|
|
508
|
+
* @returns {string | undefined} The text, or nothing when it is not a comment.
|
|
509
|
+
*/
|
|
510
|
+
function commentTextOf(raw) {
|
|
511
|
+
const trimmed = raw.trim();
|
|
512
|
+
if (trimmed.startsWith("#!")) return undefined;
|
|
513
|
+
const marker = ["#", "//", "*/", "/**", "/*", "*"].find(candidate =>
|
|
514
|
+
trimmed.startsWith(candidate)
|
|
515
|
+
);
|
|
516
|
+
return marker === undefined ? undefined : trimmed.slice(marker.length).trim();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* How a claim candidate advances over one token.
|
|
521
|
+
* @param {{ open: boolean, offset: number, named: string[] }} state - Progress.
|
|
522
|
+
* @param {{ text: string, index: number }} token - Word or terminator.
|
|
523
|
+
* @returns {{ state: object, surfaces: string[] | undefined, offset: number }}
|
|
524
|
+
* The next state, plus the surfaces of a claim that just closed.
|
|
525
|
+
*/
|
|
526
|
+
function advance(state, token) {
|
|
527
|
+
const closed = { open: false, offset: 0, named: [] };
|
|
528
|
+
const word = token.text.toLowerCase();
|
|
529
|
+
if (TERMINATORS.has(token.text)) return { state: closed };
|
|
530
|
+
if (!state.open) {
|
|
531
|
+
return NEGATIONS.has(word)
|
|
532
|
+
? { state: { open: true, offset: token.index, named: [] } }
|
|
533
|
+
: { state: closed };
|
|
534
|
+
}
|
|
535
|
+
if (CLAIM_TARGETS.has(word)) {
|
|
536
|
+
const surfaces =
|
|
537
|
+
state.named.length > 0 ? [...new Set(state.named)] : undefined;
|
|
538
|
+
return { state: closed, surfaces, offset: state.offset };
|
|
539
|
+
}
|
|
540
|
+
const surface = SURFACE_BY_ALIAS.get(word);
|
|
541
|
+
if (surface) {
|
|
542
|
+
return { state: { ...state, named: [...state.named, surface] } };
|
|
543
|
+
}
|
|
544
|
+
return FILLER.has(word) ? { state } : { state: closed };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* The parity claims one comment block makes.
|
|
549
|
+
* @param {string} text - The block's joined text.
|
|
550
|
+
* @returns {{ offset: number, surfaces: string[], text: string }[]} Claims.
|
|
551
|
+
*/
|
|
552
|
+
export function claimsIn(text) {
|
|
553
|
+
const claims = [];
|
|
554
|
+
let state = { open: false, offset: 0, named: [] };
|
|
555
|
+
for (const match of text.matchAll(TOKEN_PATTERN)) {
|
|
556
|
+
const step = advance(state, { text: match[0], index: match.index });
|
|
557
|
+
state = step.state;
|
|
558
|
+
if (!step.surfaces) continue;
|
|
559
|
+
claims.push({
|
|
560
|
+
offset: step.offset,
|
|
561
|
+
surfaces: step.surfaces,
|
|
562
|
+
text: text.slice(step.offset, match.index + match[0].length),
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
return claims;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Guard source files under one directory of a tree.
|
|
570
|
+
* @param {string} repoRoot - Absolute path of the tree being scanned.
|
|
571
|
+
* @param {string} directory - Repo-relative directory to read.
|
|
572
|
+
* @returns {{ relative: string, guardId: string }[]} Guard sources found.
|
|
573
|
+
*/
|
|
574
|
+
function guardsUnder(repoRoot, directory) {
|
|
575
|
+
const absolute = path.join(repoRoot, directory);
|
|
576
|
+
try {
|
|
577
|
+
if (!statSync(absolute).isDirectory()) return [];
|
|
578
|
+
} catch {
|
|
579
|
+
// probe-direction: fail-closed — an unreadable directory contributes no
|
|
580
|
+
// files, so the zero-sources exit-2 rule fires rather than a clean tick.
|
|
581
|
+
return [];
|
|
582
|
+
}
|
|
583
|
+
return readdirSync(absolute, { withFileTypes: true })
|
|
584
|
+
.filter(entry => entry.isFile())
|
|
585
|
+
.map(entry => ({
|
|
586
|
+
relative: `${directory}/${entry.name}`,
|
|
587
|
+
guardId: guardIdFor(`${directory}/${entry.name}`),
|
|
588
|
+
}))
|
|
589
|
+
.filter(guard => guard.guardId !== undefined);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Read every guard source and refuse the claims the tree contradicts.
|
|
594
|
+
* @param {string} repoRoot - Absolute path of the tree to scan.
|
|
595
|
+
* @param {readonly string[]} [directories] - Repo-relative dirs to read.
|
|
596
|
+
* @returns {ParityReport} The completed sweep.
|
|
597
|
+
*/
|
|
598
|
+
export function sweep(repoRoot, directories = SCANNED_DIRS) {
|
|
599
|
+
const report = { files: 0, claims: 0, findings: [] };
|
|
600
|
+
const guards = directories.flatMap(directory =>
|
|
601
|
+
guardsUnder(repoRoot, directory)
|
|
602
|
+
);
|
|
603
|
+
for (const guard of guards) {
|
|
604
|
+
report.files += 1;
|
|
605
|
+
const source = readFileSync(path.join(repoRoot, guard.relative), "utf8");
|
|
606
|
+
for (const block of commentBlocks(source)) {
|
|
607
|
+
inspectBlock(repoRoot, guard, block, report);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
report.findings.sort(
|
|
611
|
+
(left, right) =>
|
|
612
|
+
left.file.localeCompare(right.file) ||
|
|
613
|
+
left.line - right.line ||
|
|
614
|
+
left.surface.localeCompare(right.surface)
|
|
615
|
+
);
|
|
616
|
+
return report;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Refuse the claims one comment block makes that the tree contradicts.
|
|
621
|
+
* @param {string} repoRoot - Absolute path of the tree being scanned.
|
|
622
|
+
* @param {{ relative: string, guardId: string }} guard - The guard source.
|
|
623
|
+
* @param {{ line: number, text: string }} block - One comment block.
|
|
624
|
+
* @param {ParityReport} report - Accumulating report, mutated in place.
|
|
625
|
+
* @returns {void}
|
|
626
|
+
*/
|
|
627
|
+
function inspectBlock(repoRoot, guard, block, report) {
|
|
628
|
+
for (const claim of claimsIn(block.text)) {
|
|
629
|
+
report.claims += 1;
|
|
630
|
+
for (const surface of claim.surfaces) {
|
|
631
|
+
const evidence = evidenceFor(repoRoot, guard.guardId, surface);
|
|
632
|
+
if (!evidence) continue;
|
|
633
|
+
report.findings.push({
|
|
634
|
+
file: guard.relative,
|
|
635
|
+
line: lineAt(block, claim.offset),
|
|
636
|
+
guard: guard.guardId,
|
|
637
|
+
surface,
|
|
638
|
+
evidence,
|
|
639
|
+
claim: claim.text,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Render the human-readable report.
|
|
647
|
+
* @param {ParityReport} report - Result.
|
|
648
|
+
* @returns {string} The report text.
|
|
649
|
+
*/
|
|
650
|
+
export function formatReport(report) {
|
|
651
|
+
const lines = [
|
|
652
|
+
`check:guard-parity-notes — read ${report.files} guard source(s) and recognised ${report.claims} parity claim(s).`,
|
|
653
|
+
" Not audited by this sweep:",
|
|
654
|
+
...BLIND_SPOTS.map(spot => ` · ${spot}`),
|
|
655
|
+
];
|
|
656
|
+
if (report.files === 0) {
|
|
657
|
+
lines.push(
|
|
658
|
+
" ✖ ZERO guard sources read. A sweep that read nothing cannot report a clean tree; treating this as a failure, not an all-clear."
|
|
659
|
+
);
|
|
660
|
+
return lines.join("\n");
|
|
661
|
+
}
|
|
662
|
+
for (const finding of report.findings) {
|
|
663
|
+
lines.push(
|
|
664
|
+
` ✖ ${finding.file}:${finding.line} — ${finding.guard} claims no ${finding.surface} port`,
|
|
665
|
+
` "${finding.claim}"`,
|
|
666
|
+
` contradicted by ${finding.evidence}`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
if (report.findings.length === 0) {
|
|
670
|
+
lines.push(
|
|
671
|
+
" ✔ Every parity note that records a missing port names a surface the tree agrees is missing."
|
|
672
|
+
);
|
|
673
|
+
return lines.join("\n");
|
|
674
|
+
}
|
|
675
|
+
lines.push(
|
|
676
|
+
"",
|
|
677
|
+
"Fix: correct the note to name only the surfaces that are genuinely absent, and delete it outright once none are.",
|
|
678
|
+
"The tree is the authority — a note is a reader's shortcut, and a shortcut that points the wrong way costs more than no shortcut at all."
|
|
679
|
+
);
|
|
680
|
+
return lines.join("\n");
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* CLI entry point.
|
|
685
|
+
* @returns {void}
|
|
686
|
+
*/
|
|
687
|
+
export function main() {
|
|
688
|
+
const args = process.argv.slice(2);
|
|
689
|
+
const unknown = args.find(arg => arg.startsWith("--") && arg !== "--json");
|
|
690
|
+
if (unknown) {
|
|
691
|
+
console.error(`check:guard-parity-notes: unknown flag ${unknown}`);
|
|
692
|
+
process.exitCode = 2;
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const json = args.includes("--json");
|
|
696
|
+
const repoRoot = path.resolve(args.find(arg => !arg.startsWith("--")) ?? ".");
|
|
697
|
+
const report = sweep(repoRoot);
|
|
698
|
+
console.log(
|
|
699
|
+
json
|
|
700
|
+
? JSON.stringify({ ...report, blindSpots: BLIND_SPOTS }, null, 2)
|
|
701
|
+
: formatReport(report)
|
|
702
|
+
);
|
|
703
|
+
if (report.files === 0) process.exitCode = 2;
|
|
704
|
+
else if (report.findings.length > 0) process.exitCode = 1;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (invokedAsScript(import.meta.url)) {
|
|
708
|
+
main();
|
|
709
|
+
}
|