@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,736 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* A project-owned surface for a SHORT-LIVED operational hazard, read at every
|
|
4
|
+
* session and subagent start, whose entries expire on their own.
|
|
5
|
+
*
|
|
6
|
+
* ## The defect this exists against
|
|
7
|
+
*
|
|
8
|
+
* A broadcast cannot reach a session that starts after it, and the sessions
|
|
9
|
+
* that missed it never knew there was something to miss. That is a detector
|
|
10
|
+
* whose only failure mode is staying quiet, and the population a fan-out
|
|
11
|
+
* structurally cannot reach is the same population most exposed: the lanes
|
|
12
|
+
* that just started are the ones standing up fresh environments and colliding
|
|
13
|
+
* on ports, saturating workers, and re-hitting a defect somebody diagnosed an
|
|
14
|
+
* hour ago (CodySwannGT/lisa#3681).
|
|
15
|
+
*
|
|
16
|
+
* Measured on one machine in one hour: one session started 18 minutes after a
|
|
17
|
+
* warning it needed, another 8 minutes after a different one, four more
|
|
18
|
+
* appeared and vanished entirely. Worse, the hole is unmeasurable rather than
|
|
19
|
+
* merely growing — two sessions enumerating peers at the same moment returned
|
|
20
|
+
* DIFFERENT rosters, so "I checked the list" carries an unknown gap and a
|
|
21
|
+
* fan-out audited against one session's view cannot be audited at all.
|
|
22
|
+
*
|
|
23
|
+
* The existing rule injector cannot take this content. It reads
|
|
24
|
+
* `CLAUDE_PLUGIN_ROOT/rules/eager` — the plugin root, not the project — so a
|
|
25
|
+
* project cannot register anything there without editing an installed plugin
|
|
26
|
+
* that the next apply overwrites; and it `cat`s every file unconditionally, so
|
|
27
|
+
* anything written there becomes permanent scripture. A hazard surface without
|
|
28
|
+
* an expiry IS the permanent-scripture problem, and projects correctly refuse
|
|
29
|
+
* to use one.
|
|
30
|
+
*
|
|
31
|
+
* ## Why the expiry is the whole mechanism, not a convenience
|
|
32
|
+
*
|
|
33
|
+
* The expiry is what separates a transient hazard from doctrine, and it is the
|
|
34
|
+
* only reason this surface can be trusted with transient content: nobody has
|
|
35
|
+
* to remember to remove anything. So `until` is REQUIRED by the writer, and a
|
|
36
|
+
* past `until` stops the entry being injected with no human action.
|
|
37
|
+
*
|
|
38
|
+
* The shape is deliberately the threshold-ratchet exemption's
|
|
39
|
+
* (CodySwannGT/lisa#3856) rather than a second scheme: record the condition in
|
|
40
|
+
* a form something can evaluate, and never let an unevaluable condition lapse
|
|
41
|
+
* quietly into the permissive answer. An entry naming no evaluable expiry —
|
|
42
|
+
* hand-written, or written before `until` existed — still applies, and is
|
|
43
|
+
* reported as `unchecked` every time it is read. An expiry that lapses into
|
|
44
|
+
* "no longer shown" is worse than no expiry.
|
|
45
|
+
*
|
|
46
|
+
* ## The inverse, because a state change needs one
|
|
47
|
+
*
|
|
48
|
+
* A hazard also ends before its expiry, and a declaration nothing can void is
|
|
49
|
+
* the defect CodySwannGT/lisa#3855 and #3852 closed twice. `--lift` appends a
|
|
50
|
+
* void record naming what resolved it. Matching is time-ordered: a lift voids
|
|
51
|
+
* only a declaration that PRE-DATES it, so a hazard re-declared after an
|
|
52
|
+
* earlier lift is not born discharged — the unsafe direction that must not be
|
|
53
|
+
* introduced.
|
|
54
|
+
*
|
|
55
|
+
* ## Two tiers, because they reach different audiences
|
|
56
|
+
*
|
|
57
|
+
* - the repository ledger, `.lisa/HAZARDS.jsonl`, committed — reaches every
|
|
58
|
+
* future reader of this project and merges by union;
|
|
59
|
+
* - the machine ledger, `${LISA_STATE_HOME:-~/.lisa}/operational-hazards.jsonl`
|
|
60
|
+
* — reaches sibling sessions in OTHER worktrees on this machine NOW, which
|
|
61
|
+
* a committed file cannot do until it is merged and pulled. That is the
|
|
62
|
+
* tier the fleet case above actually needs.
|
|
63
|
+
*
|
|
64
|
+
* A declaration is written to both. Reads are the union.
|
|
65
|
+
*
|
|
66
|
+
* ## Which half of the reporting gap this closes
|
|
67
|
+
*
|
|
68
|
+
* `--session-start` reaches every session and subagent that starts AFTER the
|
|
69
|
+
* declaration — the half a fan-out structurally cannot serve. `--hook` runs as
|
|
70
|
+
* a PostToolUse hook and announces, on the next tool result, a hazard declared
|
|
71
|
+
* or lifted SINCE this session started — which is the only thing that reaches
|
|
72
|
+
* a session already running. Surfaces with no PostToolUse runtime get the
|
|
73
|
+
* first half and `--list`.
|
|
74
|
+
* @module operational-hazards
|
|
75
|
+
*/
|
|
76
|
+
import * as fs from "node:fs";
|
|
77
|
+
import * as os from "node:os";
|
|
78
|
+
import * as path from "node:path";
|
|
79
|
+
import { execFileSync } from "node:child_process";
|
|
80
|
+
import { fileURLToPath } from "node:url";
|
|
81
|
+
|
|
82
|
+
/** Repository-relative path of the committed, durable ledger. */
|
|
83
|
+
export const REPO_LEDGER = path.join(".lisa", "HAZARDS.jsonl");
|
|
84
|
+
/** Filename of the machine-local ledger inside the Lisa state home. */
|
|
85
|
+
export const MACHINE_LEDGER = "operational-hazards.jsonl";
|
|
86
|
+
|
|
87
|
+
/** An entry with an expiry still in the future. */
|
|
88
|
+
export const LIVE = "live";
|
|
89
|
+
/** An entry naming no evaluable expiry: it still applies, and says so. */
|
|
90
|
+
export const UNCHECKED = "unchecked";
|
|
91
|
+
/** An entry whose expiry has passed. Inert, with no human action. */
|
|
92
|
+
export const EXPIRED = "expired";
|
|
93
|
+
/** An entry voided early by a `--lift` recorded after it was declared. */
|
|
94
|
+
export const LIFTED = "lifted";
|
|
95
|
+
|
|
96
|
+
/** How a day-only `until` is completed into the instant it stops applying. */
|
|
97
|
+
const END_OF_DAY = "T23:59:59.999Z";
|
|
98
|
+
/** Matches a bare calendar day. Both quantifiers are bounded. */
|
|
99
|
+
const DAY_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The machine-local state directory. Overridable so tests never touch `$HOME`.
|
|
103
|
+
* @returns {string} An absolute directory path.
|
|
104
|
+
*/
|
|
105
|
+
export function stateHome() {
|
|
106
|
+
return process.env.LISA_STATE_HOME || path.join(os.homedir(), ".lisa");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The repository root, or the working directory when there is no repository.
|
|
111
|
+
* @returns {string} An absolute directory path.
|
|
112
|
+
*/
|
|
113
|
+
export function repoRoot() {
|
|
114
|
+
try {
|
|
115
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
116
|
+
encoding: "utf-8",
|
|
117
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
118
|
+
}).trim();
|
|
119
|
+
} catch {
|
|
120
|
+
return process.cwd();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Both ledger paths, repository tier first.
|
|
126
|
+
* @param {string} root Repository root.
|
|
127
|
+
* @returns {readonly string[]} Absolute ledger paths.
|
|
128
|
+
*/
|
|
129
|
+
export function ledgerPaths(root) {
|
|
130
|
+
return [path.join(root, REPO_LEDGER), path.join(stateHome(), MACHINE_LEDGER)];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A cheap identity for the ledger pair: size and mtime, no parsing.
|
|
135
|
+
*
|
|
136
|
+
* Two `stat` calls is the entire cost of the common case, and `--hook` fires
|
|
137
|
+
* on every tool call in every session, so anything expensive multiplies.
|
|
138
|
+
* @param {readonly string[]} paths Ledger paths.
|
|
139
|
+
* @returns {string} A fingerprint that changes whenever either file changes.
|
|
140
|
+
*/
|
|
141
|
+
export function fingerprint(paths) {
|
|
142
|
+
return paths
|
|
143
|
+
.map(file => {
|
|
144
|
+
try {
|
|
145
|
+
const stat = fs.statSync(file);
|
|
146
|
+
return `${stat.size}:${stat.mtimeMs}`;
|
|
147
|
+
} catch {
|
|
148
|
+
return "-";
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
.join("|");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The instant an `until` value stops applying, or null when it names none.
|
|
156
|
+
*
|
|
157
|
+
* A bare `YYYY-MM-DD` is live THROUGH that day, which is what a person means
|
|
158
|
+
* when they write one. Anything unparseable returns null and is therefore
|
|
159
|
+
* treated as naming no condition — reported, never silently permissive.
|
|
160
|
+
* @param {unknown} until The recorded expiry.
|
|
161
|
+
* @returns {number | null} Epoch milliseconds, or null.
|
|
162
|
+
*/
|
|
163
|
+
export function expiryMs(until) {
|
|
164
|
+
if (typeof until !== "string") return null;
|
|
165
|
+
const trimmed = until.trim();
|
|
166
|
+
if (trimmed === "") return null;
|
|
167
|
+
const completed = DAY_ONLY.test(trimmed)
|
|
168
|
+
? `${trimmed}${END_OF_DAY}`
|
|
169
|
+
: trimmed;
|
|
170
|
+
const parsed = Date.parse(completed);
|
|
171
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Parse one ledger file into declarations and lifts, reporting bad lines.
|
|
176
|
+
*
|
|
177
|
+
* A record carrying `lift: true` is a void record rather than a hazard, so the
|
|
178
|
+
* two travel in one append-only file and merge by the same union driver.
|
|
179
|
+
* @param {string} file Absolute ledger path.
|
|
180
|
+
* @returns {{ entries: object[], lifts: object[], malformed: string[] }} Parsed lines.
|
|
181
|
+
*/
|
|
182
|
+
export function parseLedger(file) {
|
|
183
|
+
const entries = [];
|
|
184
|
+
const lifts = [];
|
|
185
|
+
const malformed = [];
|
|
186
|
+
let raw = "";
|
|
187
|
+
try {
|
|
188
|
+
raw = fs.readFileSync(file, "utf-8");
|
|
189
|
+
} catch {
|
|
190
|
+
return { entries, lifts, malformed };
|
|
191
|
+
}
|
|
192
|
+
for (const line of raw.split("\n")) {
|
|
193
|
+
if (line.trim() === "") continue;
|
|
194
|
+
const record = tryParse(line);
|
|
195
|
+
if (record === null) malformed.push(line);
|
|
196
|
+
else if (record.lift === true) lifts.push(record);
|
|
197
|
+
else entries.push(record);
|
|
198
|
+
}
|
|
199
|
+
return { entries, lifts, malformed };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Parse one ledger line, requiring the identity every reader keys on.
|
|
204
|
+
* @param {string} line A single ledger line.
|
|
205
|
+
* @returns {object | null} The record, or null when it is unusable.
|
|
206
|
+
*/
|
|
207
|
+
function tryParse(line) {
|
|
208
|
+
try {
|
|
209
|
+
const record = JSON.parse(line);
|
|
210
|
+
const usable = typeof record?.id === "string" && record.id !== "";
|
|
211
|
+
return usable ? record : null;
|
|
212
|
+
} catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* When a record was written, or zero when it says nothing evaluable.
|
|
219
|
+
* @param {object} record A declaration or a lift.
|
|
220
|
+
* @param {string} field The timestamp field to read.
|
|
221
|
+
* @returns {number} Epoch milliseconds, or zero.
|
|
222
|
+
*/
|
|
223
|
+
export function stampMs(record, field) {
|
|
224
|
+
const parsed = Date.parse(String(record?.[field] ?? ""));
|
|
225
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The union of both ledger tiers, de-duplicated by id.
|
|
230
|
+
*
|
|
231
|
+
* The NEWEST declaration for an id wins, not the first one read. Both tiers
|
|
232
|
+
* are append-only and a re-declaration is an append, so first-wins would pin
|
|
233
|
+
* an id to the oldest thing ever said about it — and a hazard re-declared
|
|
234
|
+
* after an earlier lift would be born discharged, which is exactly the unsafe
|
|
235
|
+
* direction CodySwannGT/lisa#3852 refuses.
|
|
236
|
+
* @param {string} root Repository root.
|
|
237
|
+
* @returns {{ entries: object[], lifts: object[], malformed: string[] }} Every record.
|
|
238
|
+
*/
|
|
239
|
+
export function readLedgers(root) {
|
|
240
|
+
const byId = new Map();
|
|
241
|
+
const lifts = [];
|
|
242
|
+
const malformed = [];
|
|
243
|
+
for (const file of ledgerPaths(root)) {
|
|
244
|
+
const parsed = parseLedger(file);
|
|
245
|
+
for (const entry of parsed.entries) {
|
|
246
|
+
const held = byId.get(entry.id);
|
|
247
|
+
const newer =
|
|
248
|
+
held === undefined ||
|
|
249
|
+
stampMs(entry, "declaredAt") >= stampMs(held, "declaredAt");
|
|
250
|
+
if (newer) byId.set(entry.id, entry);
|
|
251
|
+
}
|
|
252
|
+
lifts.push(...parsed.lifts);
|
|
253
|
+
malformed.push(...parsed.malformed);
|
|
254
|
+
}
|
|
255
|
+
return { entries: [...byId.values()], lifts, malformed };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Whether a lift voids this declaration.
|
|
260
|
+
*
|
|
261
|
+
* Time-ordered on purpose: a lift discharges only a declaration that pre-dates
|
|
262
|
+
* it. Re-declaring a hazard after an earlier lift must not be born discharged
|
|
263
|
+
* — the sibling refusal CodySwannGT/lisa#3852 records for the human gate.
|
|
264
|
+
* @param {object} entry A hazard declaration.
|
|
265
|
+
* @param {readonly object[]} lifts Every void record.
|
|
266
|
+
* @returns {boolean} Whether the entry has been lifted.
|
|
267
|
+
*/
|
|
268
|
+
export function isLifted(entry, lifts) {
|
|
269
|
+
const declared = stampMs(entry, "declaredAt");
|
|
270
|
+
return lifts.some(
|
|
271
|
+
lift => lift.id === entry.id && stampMs(lift, "liftedAt") >= declared
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Classify one declaration at a moment in time.
|
|
277
|
+
* @param {object} entry A hazard declaration.
|
|
278
|
+
* @param {readonly object[]} lifts Every void record.
|
|
279
|
+
* @param {number} now Epoch milliseconds.
|
|
280
|
+
* @returns {string} One of LIVE, UNCHECKED, EXPIRED or LIFTED.
|
|
281
|
+
*/
|
|
282
|
+
export function classify(entry, lifts, now) {
|
|
283
|
+
if (isLifted(entry, lifts)) return LIFTED;
|
|
284
|
+
const expiry = expiryMs(entry.until);
|
|
285
|
+
if (expiry === null) return UNCHECKED;
|
|
286
|
+
return expiry < now ? EXPIRED : LIVE;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Split the ledger union into the four states a reader cares about.
|
|
291
|
+
* @param {string} root Repository root.
|
|
292
|
+
* @param {number} now Epoch milliseconds.
|
|
293
|
+
* @returns {{ live: object[], unchecked: object[], expired: object[], lifted: object[], malformed: string[] }} Partitioned records.
|
|
294
|
+
*/
|
|
295
|
+
export function partition(root, now) {
|
|
296
|
+
const { entries, lifts, malformed } = readLedgers(root);
|
|
297
|
+
const buckets = { live: [], unchecked: [], expired: [], lifted: [] };
|
|
298
|
+
for (const entry of entries) buckets[classify(entry, lifts, now)].push(entry);
|
|
299
|
+
return { ...buckets, malformed };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Render one declaration as the lines a reader can act on.
|
|
304
|
+
* @param {object} entry A hazard declaration.
|
|
305
|
+
* @param {string} state The classification from `classify`.
|
|
306
|
+
* @returns {string} A bullet block.
|
|
307
|
+
*/
|
|
308
|
+
export function formatEntry(entry, state) {
|
|
309
|
+
const head =
|
|
310
|
+
state === UNCHECKED
|
|
311
|
+
? `- ${entry.id} — NO EVALUABLE EXPIRY, so it still applies (${UNCHECKED})`
|
|
312
|
+
: `- ${entry.id} — live through ${entry.until}`;
|
|
313
|
+
return [
|
|
314
|
+
head,
|
|
315
|
+
` hazard: ${entry.hazard ?? "(not recorded)"}`,
|
|
316
|
+
` instead: ${entry.avoid ?? "(not recorded)"}`,
|
|
317
|
+
` declared: ${entry.declaredAt ?? "(undated)"}`,
|
|
318
|
+
].join("\n");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Render live and unchecked hazards as an injectable notice.
|
|
323
|
+
*
|
|
324
|
+
* Expired and lifted entries are absent by construction — that is the point of
|
|
325
|
+
* the surface, and the property the expiry test asserts.
|
|
326
|
+
* @param {readonly object[]} live Entries with a future expiry.
|
|
327
|
+
* @param {readonly object[]} unchecked Entries naming no evaluable expiry.
|
|
328
|
+
* @param {string} headline The opening line.
|
|
329
|
+
* @returns {string} Notice text, empty when there is nothing to say.
|
|
330
|
+
*/
|
|
331
|
+
export function formatNotice(live, unchecked, headline) {
|
|
332
|
+
if (live.length + unchecked.length === 0) return "";
|
|
333
|
+
return [
|
|
334
|
+
headline,
|
|
335
|
+
"",
|
|
336
|
+
...live.map(entry => formatEntry(entry, LIVE)),
|
|
337
|
+
...unchecked.map(entry => formatEntry(entry, UNCHECKED)),
|
|
338
|
+
"",
|
|
339
|
+
"These are transient conditions on this machine or branch, not doctrine. Each one",
|
|
340
|
+
"stops being shown when its expiry passes, with nobody remembering to remove it.",
|
|
341
|
+
"If you resolve one, record that so every other session stops paying for it:",
|
|
342
|
+
` node "$CLAUDE_PLUGIN_ROOT/hooks/operational-hazards.mjs" --lift <id> --because "<what resolved it>"`,
|
|
343
|
+
].join("\n");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Where this session's delivery mark lives.
|
|
348
|
+
* @param {string} sessionId The harness-supplied session id.
|
|
349
|
+
* @returns {string} An absolute file path.
|
|
350
|
+
*/
|
|
351
|
+
export function markPath(sessionId) {
|
|
352
|
+
return path.join(
|
|
353
|
+
stateHome(),
|
|
354
|
+
"hazard-marks",
|
|
355
|
+
`${sessionId.replace(/[^\w.-]/g, "_")}.json`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Write a JSON file, creating parents.
|
|
361
|
+
* @param {string} file Absolute path.
|
|
362
|
+
* @param {object} value Serialisable value.
|
|
363
|
+
* @returns {void}
|
|
364
|
+
*/
|
|
365
|
+
export function writeJson(file, value) {
|
|
366
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
367
|
+
fs.writeFileSync(file, `${JSON.stringify(value)}\n`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Emit a hook result on stdout, and the same text on stderr for humans.
|
|
372
|
+
* @param {string} event The hook event name.
|
|
373
|
+
* @param {string} notice Notice text.
|
|
374
|
+
* @returns {number} An exit code.
|
|
375
|
+
*/
|
|
376
|
+
export function emit(event, notice) {
|
|
377
|
+
if (notice === "") return 0;
|
|
378
|
+
process.stderr.write(`${notice}\n`);
|
|
379
|
+
process.stdout.write(
|
|
380
|
+
`${JSON.stringify({
|
|
381
|
+
hookSpecificOutput: { hookEventName: event, additionalContext: notice },
|
|
382
|
+
})}\n`
|
|
383
|
+
);
|
|
384
|
+
return 0;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Read the hook payload's session id.
|
|
389
|
+
* @param {string} raw Raw stdin.
|
|
390
|
+
* @returns {string} The session id, or the empty string.
|
|
391
|
+
*/
|
|
392
|
+
export function sessionIdOf(raw) {
|
|
393
|
+
try {
|
|
394
|
+
const payload = JSON.parse(raw);
|
|
395
|
+
return typeof payload?.session_id === "string" ? payload.session_id : "";
|
|
396
|
+
} catch {
|
|
397
|
+
return "";
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* SessionStart / SubagentStart: deliver every applying hazard and record what
|
|
403
|
+
* this session was born knowing.
|
|
404
|
+
*
|
|
405
|
+
* This is the half a fan-out structurally cannot serve — a session that starts
|
|
406
|
+
* 18 minutes after the warning reads it here.
|
|
407
|
+
* @param {string} raw Raw stdin.
|
|
408
|
+
* @param {string} root Repository root.
|
|
409
|
+
* @param {number} now Epoch milliseconds.
|
|
410
|
+
* @returns {number} An exit code.
|
|
411
|
+
*/
|
|
412
|
+
export function runSessionStart(raw, root, now) {
|
|
413
|
+
const sessionId = sessionIdOf(raw);
|
|
414
|
+
if (sessionId === "") return 0;
|
|
415
|
+
const { live, unchecked } = partition(root, now);
|
|
416
|
+
const applying = [...live, ...unchecked];
|
|
417
|
+
writeJson(markPath(sessionId), {
|
|
418
|
+
delivered: applying.map(entry => entry.id),
|
|
419
|
+
fingerprint: fingerprint(ledgerPaths(root)),
|
|
420
|
+
});
|
|
421
|
+
return emit(
|
|
422
|
+
"SessionStart",
|
|
423
|
+
formatNotice(
|
|
424
|
+
live,
|
|
425
|
+
unchecked,
|
|
426
|
+
`Lisa: ${applying.length} short-lived operational hazard(s) apply to this project right now.`
|
|
427
|
+
)
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* PostToolUse: announce what changed AFTER this session started.
|
|
433
|
+
*
|
|
434
|
+
* Two directions, because both were measured. A hazard declared while this
|
|
435
|
+
* session runs is the collision it is about to cause; a LIFT of a hazard this
|
|
436
|
+
* session was told about is the hold it is still paying for after everyone
|
|
437
|
+
* else stopped — the provenance of CodySwannGT/lisa#3681 is a session sent a
|
|
438
|
+
* lift for a hold it had never received.
|
|
439
|
+
* @param {string} raw Raw stdin.
|
|
440
|
+
* @param {string} root Repository root.
|
|
441
|
+
* @param {number} now Epoch milliseconds.
|
|
442
|
+
* @returns {number} An exit code.
|
|
443
|
+
*/
|
|
444
|
+
export function runHook(raw, root, now) {
|
|
445
|
+
const sessionId = sessionIdOf(raw);
|
|
446
|
+
if (sessionId === "") return 0;
|
|
447
|
+
const mark = readMark(markPath(sessionId));
|
|
448
|
+
if (mark === null) return 0;
|
|
449
|
+
const current = fingerprint(ledgerPaths(root));
|
|
450
|
+
if (current === mark.fingerprint) return 0;
|
|
451
|
+
const { live, unchecked } = partition(root, now);
|
|
452
|
+
const applying = [...live, ...unchecked];
|
|
453
|
+
const known = new Set(mark.delivered);
|
|
454
|
+
const applyingIds = new Set(applying.map(entry => entry.id));
|
|
455
|
+
const dropped = [...known].filter(id => !applyingIds.has(id));
|
|
456
|
+
writeJson(markPath(sessionId), {
|
|
457
|
+
delivered: applying.map(entry => entry.id),
|
|
458
|
+
fingerprint: current,
|
|
459
|
+
});
|
|
460
|
+
const fresh = applying.filter(entry => !known.has(entry.id));
|
|
461
|
+
return emit("PostToolUse", changeNotice(fresh, dropped));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Read a session mark, treating any unreadable mark as absent.
|
|
466
|
+
*
|
|
467
|
+
* A missing mark is deliberately silent: a session whose starting knowledge is
|
|
468
|
+
* unknown would otherwise be told every historical hazard, which degrades the
|
|
469
|
+
* mechanism into distrusting everything.
|
|
470
|
+
* @param {string} file Absolute mark path.
|
|
471
|
+
* @returns {{ delivered: string[], fingerprint: string } | null} The mark.
|
|
472
|
+
*/
|
|
473
|
+
export function readMark(file) {
|
|
474
|
+
try {
|
|
475
|
+
const mark = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
476
|
+
return {
|
|
477
|
+
delivered: Array.isArray(mark?.delivered) ? mark.delivered : [],
|
|
478
|
+
fingerprint: String(mark?.fingerprint ?? ""),
|
|
479
|
+
};
|
|
480
|
+
} catch {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Render the mid-session change notice for a running session.
|
|
487
|
+
* @param {readonly object[]} fresh Hazards that began applying since the mark.
|
|
488
|
+
* @param {readonly string[]} dropped Ids that stopped applying since the mark.
|
|
489
|
+
* @returns {string} Notice text, empty when nothing changed.
|
|
490
|
+
*/
|
|
491
|
+
export function changeNotice(fresh, dropped) {
|
|
492
|
+
const parts = [];
|
|
493
|
+
if (fresh.length > 0)
|
|
494
|
+
parts.push(
|
|
495
|
+
formatNotice(
|
|
496
|
+
fresh.filter(entry => expiryMs(entry.until) !== null),
|
|
497
|
+
fresh.filter(entry => expiryMs(entry.until) === null),
|
|
498
|
+
"Lisa: an operational hazard was declared AFTER this session started."
|
|
499
|
+
)
|
|
500
|
+
);
|
|
501
|
+
if (dropped.length > 0)
|
|
502
|
+
parts.push(
|
|
503
|
+
[
|
|
504
|
+
"Lisa: an operational hazard you were told about no longer applies —",
|
|
505
|
+
"it was lifted or its expiry passed. Stop working around it.",
|
|
506
|
+
"",
|
|
507
|
+
...dropped.map(id => `- ${id}`),
|
|
508
|
+
].join("\n")
|
|
509
|
+
);
|
|
510
|
+
return parts.join("\n\n");
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Read a `--flag value` pair out of argv.
|
|
515
|
+
* @param {readonly string[]} argv Arguments.
|
|
516
|
+
* @param {string} flag The flag, including dashes.
|
|
517
|
+
* @returns {string} The value, or the empty string.
|
|
518
|
+
*/
|
|
519
|
+
export function flagValue(argv, flag) {
|
|
520
|
+
const index = argv.indexOf(flag);
|
|
521
|
+
return index >= 0 && index + 1 < argv.length ? argv[index + 1] : "";
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Build the record `--declare` appends, refusing anything a reader cannot use.
|
|
526
|
+
*
|
|
527
|
+
* `--until` is required and there is no default. A hazard surface whose entries
|
|
528
|
+
* do not expire becomes permanent scripture, which is the failure this exists
|
|
529
|
+
* against — so the writer cannot produce one, and the reader still tolerates a
|
|
530
|
+
* hand-written entry that names no expiry by reporting it rather than dropping
|
|
531
|
+
* it.
|
|
532
|
+
* @param {readonly string[]} argv Arguments.
|
|
533
|
+
* @param {string} now ISO-8601 timestamp.
|
|
534
|
+
* @returns {{ record?: object, error?: string }} The record, or why not.
|
|
535
|
+
*/
|
|
536
|
+
export function buildDeclaration(argv, now) {
|
|
537
|
+
const id = flagValue(argv, "--declare");
|
|
538
|
+
const hazard = flagValue(argv, "--hazard");
|
|
539
|
+
const avoid = flagValue(argv, "--avoid");
|
|
540
|
+
const until = flagValue(argv, "--until");
|
|
541
|
+
if (id === "") return { error: "--declare needs an id" };
|
|
542
|
+
if (hazard === "")
|
|
543
|
+
return { error: "--hazard is required: say what will go wrong" };
|
|
544
|
+
if (avoid === "")
|
|
545
|
+
return {
|
|
546
|
+
error:
|
|
547
|
+
"--avoid is required: a hazard nobody can act on reaches them without reaching them",
|
|
548
|
+
};
|
|
549
|
+
if (until === "")
|
|
550
|
+
return {
|
|
551
|
+
error:
|
|
552
|
+
"--until is required: an entry that never expires is doctrine, and doctrine belongs in a rule",
|
|
553
|
+
};
|
|
554
|
+
if (expiryMs(until) === null)
|
|
555
|
+
return { error: `--until ${until} is not a date or ISO-8601 timestamp` };
|
|
556
|
+
return { record: { id, declaredAt: now, until, hazard, avoid } };
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Append one record to both ledger tiers.
|
|
561
|
+
* @param {object} record The record to append.
|
|
562
|
+
* @param {string} root Repository root.
|
|
563
|
+
* @returns {void}
|
|
564
|
+
*/
|
|
565
|
+
export function appendBothTiers(record, root) {
|
|
566
|
+
const line = `${JSON.stringify(record)}\n`;
|
|
567
|
+
for (const file of ledgerPaths(root)) {
|
|
568
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
569
|
+
fs.appendFileSync(file, line);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* `--declare`: record a hazard in both tiers. One command, by design.
|
|
575
|
+
* @param {readonly string[]} argv Arguments.
|
|
576
|
+
* @param {string} root Repository root.
|
|
577
|
+
* @returns {number} An exit code.
|
|
578
|
+
*/
|
|
579
|
+
export function runDeclare(argv, root) {
|
|
580
|
+
const built = buildDeclaration(argv, new Date().toISOString());
|
|
581
|
+
if (built.error !== undefined) {
|
|
582
|
+
process.stderr.write(`operational-hazards: ${built.error}\n`);
|
|
583
|
+
return 2;
|
|
584
|
+
}
|
|
585
|
+
appendBothTiers(built.record, root);
|
|
586
|
+
process.stdout.write(
|
|
587
|
+
`operational-hazards: ${built.record.id} applies through ${built.record.until}\n`
|
|
588
|
+
);
|
|
589
|
+
return 0;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* `--lift`: void a hazard before its expiry. The executable inverse.
|
|
594
|
+
* @param {readonly string[]} argv Arguments.
|
|
595
|
+
* @param {string} root Repository root.
|
|
596
|
+
* @returns {number} An exit code.
|
|
597
|
+
*/
|
|
598
|
+
export function runLift(argv, root) {
|
|
599
|
+
const id = flagValue(argv, "--lift");
|
|
600
|
+
const because = flagValue(argv, "--because");
|
|
601
|
+
if (because === "") {
|
|
602
|
+
process.stderr.write(
|
|
603
|
+
"operational-hazards: --because is required: say what resolved it\n"
|
|
604
|
+
);
|
|
605
|
+
return 2;
|
|
606
|
+
}
|
|
607
|
+
appendBothTiers(
|
|
608
|
+
{ id, lift: true, liftedAt: new Date().toISOString(), because },
|
|
609
|
+
root
|
|
610
|
+
);
|
|
611
|
+
process.stdout.write(`operational-hazards: lifted ${id}\n`);
|
|
612
|
+
return 0;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* `--list`: the surface for harnesses with no hook runtime, and for anyone
|
|
617
|
+
* asking what is currently in force.
|
|
618
|
+
* @param {string} root Repository root.
|
|
619
|
+
* @param {number} now Epoch milliseconds.
|
|
620
|
+
* @returns {number} An exit code.
|
|
621
|
+
*/
|
|
622
|
+
export function runList(root, now) {
|
|
623
|
+
const { live, unchecked, expired, lifted } = partition(root, now);
|
|
624
|
+
const notice = formatNotice(
|
|
625
|
+
live,
|
|
626
|
+
unchecked,
|
|
627
|
+
`operational-hazards: ${live.length + unchecked.length} applying`
|
|
628
|
+
);
|
|
629
|
+
const tail = `operational-hazards: ${expired.length} expired, ${lifted.length} lifted (both inert)`;
|
|
630
|
+
process.stdout.write(notice === "" ? `${tail}\n` : `${notice}\n${tail}\n`);
|
|
631
|
+
return 0;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Every reason a ledger entry is unusable by a reader.
|
|
636
|
+
* @param {object} entry A hazard declaration.
|
|
637
|
+
* @returns {string[]} Problem descriptions.
|
|
638
|
+
*/
|
|
639
|
+
export function entryProblems(entry) {
|
|
640
|
+
const problems = [];
|
|
641
|
+
if (typeof entry.hazard !== "string" || entry.hazard === "")
|
|
642
|
+
problems.push(`${entry.id}: no hazard recorded`);
|
|
643
|
+
if (Number.isNaN(Date.parse(String(entry.declaredAt ?? ""))))
|
|
644
|
+
problems.push(`${entry.id}: declaredAt is not an ISO-8601 timestamp`);
|
|
645
|
+
if (entry.until !== undefined && expiryMs(entry.until) === null)
|
|
646
|
+
problems.push(
|
|
647
|
+
`${entry.id}: until "${entry.until}" names no evaluable expiry, so this entry can never stop applying on its own`
|
|
648
|
+
);
|
|
649
|
+
return problems;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* `--check`: refuse a ledger that would swallow a hazard, or hold one forever.
|
|
654
|
+
*
|
|
655
|
+
* The failure that matters is an `until` that LOOKS like an expiry and is not:
|
|
656
|
+
* every signal reads healthy while the entry becomes permanent. Expired and
|
|
657
|
+
* lifted entries are not failures — they are the mechanism working, and they
|
|
658
|
+
* are only reported.
|
|
659
|
+
* @param {string} root Repository root.
|
|
660
|
+
* @param {number} now Epoch milliseconds.
|
|
661
|
+
* @returns {number} An exit code.
|
|
662
|
+
*/
|
|
663
|
+
export function runCheck(root, now) {
|
|
664
|
+
const { live, unchecked, expired, lifted, malformed } = partition(root, now);
|
|
665
|
+
const problems = malformed.map(
|
|
666
|
+
line => `unparseable ledger line: ${line.slice(0, 120)}`
|
|
667
|
+
);
|
|
668
|
+
for (const entry of [...live, ...unchecked, ...expired, ...lifted])
|
|
669
|
+
problems.push(...entryProblems(entry));
|
|
670
|
+
if (problems.length > 0) {
|
|
671
|
+
process.stderr.write(`operational-hazards: ${problems.join("\n ")}\n`);
|
|
672
|
+
return 1;
|
|
673
|
+
}
|
|
674
|
+
process.stdout.write(
|
|
675
|
+
`operational-hazards: ${live.length} live, ${unchecked.length} ${UNCHECKED}, ${expired.length} ${EXPIRED}, ${lifted.length} ${LIFTED}; ledger well-formed\n`
|
|
676
|
+
);
|
|
677
|
+
return 0;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Dispatch on the requested mode.
|
|
682
|
+
* @param {readonly string[]} argv Command-line arguments.
|
|
683
|
+
* @param {string} raw Piped stdin, when the mode needs it.
|
|
684
|
+
* @param {string} root Repository root.
|
|
685
|
+
* @param {number} now Epoch milliseconds.
|
|
686
|
+
* @returns {number} An exit code.
|
|
687
|
+
*/
|
|
688
|
+
export function main(argv, raw, root, now = Date.now()) {
|
|
689
|
+
if (argv.includes("--session-start")) return runSessionStart(raw, root, now);
|
|
690
|
+
if (argv.includes("--hook")) return runHook(raw, root, now);
|
|
691
|
+
if (argv.includes("--declare")) return runDeclare(argv, root);
|
|
692
|
+
if (argv.includes("--lift")) return runLift(argv, root);
|
|
693
|
+
if (argv.includes("--list")) return runList(root, now);
|
|
694
|
+
return runCheck(root, now);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Whether this module is the process entry point.
|
|
699
|
+
*
|
|
700
|
+
* Both sides are realpath'd, because `import.meta.url` is the REAL path while
|
|
701
|
+
* `argv[1]` is whatever the caller typed — they disagree through a git worktree
|
|
702
|
+
* or any `/tmp` path on macOS, and for a CHECK that disagreement is a
|
|
703
|
+
* fail-OPEN. The one implementation lives at `scripts/lib/invoked-as-script.mjs`;
|
|
704
|
+
* this file cannot import it, being materialized into plugin payloads that have
|
|
705
|
+
* no `./lib/`, the same accommodation `withdrawn-rulings.mjs` makes.
|
|
706
|
+
* @param {string} moduleUrl The caller's own `import.meta.url`.
|
|
707
|
+
* @param {string | undefined} [argv1] Entry path; defaults to `process.argv[1]`.
|
|
708
|
+
* @returns {boolean} Whether the caller should run its CLI body.
|
|
709
|
+
*/
|
|
710
|
+
export function invokedAsScript(moduleUrl, argv1 = process.argv[1]) {
|
|
711
|
+
if (!argv1) return false;
|
|
712
|
+
try {
|
|
713
|
+
return fs.realpathSync(argv1) === fs.realpathSync(fileURLToPath(moduleUrl));
|
|
714
|
+
} catch {
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Read all of stdin.
|
|
721
|
+
* @returns {Promise<string>} The piped text.
|
|
722
|
+
*/
|
|
723
|
+
export async function readStdin() {
|
|
724
|
+
if (process.stdin.isTTY) return "";
|
|
725
|
+
const chunks = [];
|
|
726
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
727
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (invokedAsScript(import.meta.url)) {
|
|
731
|
+
const argv = process.argv.slice(2);
|
|
732
|
+
const needsStdin =
|
|
733
|
+
argv.includes("--hook") || argv.includes("--session-start");
|
|
734
|
+
const raw = needsStdin ? await readStdin() : "";
|
|
735
|
+
process.exitCode = main(argv, raw, repoRoot());
|
|
736
|
+
}
|