@codyswann/lisa 4.48.0 → 4.50.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-overwrite/scripts/check-conflict-markers.mjs +4 -1
- package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +163 -0
- package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +178 -1
- package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +89 -10
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +44 -0
- package/all/copy-overwrite/scripts/lisa-lint-staged-preflight.mjs +1 -1
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +24 -24
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +490 -81
- package/cdk/package-lisa/package.lisa.json +45 -15
- package/dist/configs/vitest/base.d.ts +71 -1
- package/dist/configs/vitest/base.d.ts.map +1 -1
- package/dist/configs/vitest/base.js +141 -23
- package/dist/configs/vitest/base.js.map +1 -1
- package/dist/configs/vitest/fleet-admission-global-setup.d.ts +34 -0
- package/dist/configs/vitest/fleet-admission-global-setup.d.ts.map +1 -0
- package/dist/configs/vitest/fleet-admission-global-setup.js +123 -0
- package/dist/configs/vitest/fleet-admission-global-setup.js.map +1 -0
- package/dist/configs/vitest/fleet-admission.d.ts +129 -0
- package/dist/configs/vitest/fleet-admission.d.ts.map +1 -0
- package/dist/configs/vitest/fleet-admission.js +239 -0
- package/dist/configs/vitest/fleet-admission.js.map +1 -0
- package/dist/configs/vitest/synthesis-scratch-reclaim.d.ts +86 -0
- package/dist/configs/vitest/synthesis-scratch-reclaim.d.ts.map +1 -0
- package/dist/configs/vitest/synthesis-scratch-reclaim.js +226 -0
- package/dist/configs/vitest/synthesis-scratch-reclaim.js.map +1 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +76 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +129 -228
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/lisa-block-direct-issue-create.ts +45 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +44 -0
- package/expo/package-lisa/package.lisa.json +83 -53
- package/harper-fabric/package-lisa/package.lisa.json +62 -32
- package/nestjs/package-lisa/package.lisa.json +57 -27
- package/npm-package/package-lisa/package.lisa.json +43 -7
- package/package.json +11 -7
- package/phaser/copy-overwrite/ast-grep/rules/no-day-truncating-file-age-predicate.yml +50 -0
- package/phaser/package-lisa/package.lisa.json +62 -32
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +4 -1
- package/plugins/lisa/hooks/block-direct-issue-create.sh +89 -10
- package/plugins/lisa/hooks/discharge-work-item-gates.sh +7 -1
- package/plugins/lisa/hooks/parity-safety-net.sh +44 -0
- package/plugins/lisa/hooks/threshold-ratchet-families.mjs +12 -0
- package/plugins/lisa/rules/eager/00-rule-index.md +79 -0
- package/plugins/lisa/rules/eager/base-rules.md +10 -0
- package/plugins/lisa/rules/eager/config-resolution.md +1 -88
- package/plugins/lisa/rules/reference/automation-runbook-contract.md +97 -0
- package/plugins/lisa/rules/reference/bdd-e2e-coverage.md +56 -0
- package/plugins/lisa/rules/reference/blocker-containment.md +58 -0
- package/plugins/lisa/rules/reference/claim-archaeology.md +42 -0
- package/plugins/lisa/rules/reference/claim-evidence-mapping.md +83 -0
- package/plugins/lisa/rules/reference/claim-time-guards.md +37 -0
- package/plugins/lisa/rules/reference/config-resolution.md +97 -0
- package/plugins/lisa/rules/reference/control-reachability.md +44 -0
- package/plugins/lisa/rules/reference/convergent-review.md +31 -0
- package/plugins/lisa/rules/reference/credential-substrate-precedence.md +57 -0
- package/plugins/lisa/rules/reference/dependency-decision-records.md +37 -0
- package/plugins/lisa/rules/reference/dependency-internalization-kit.md +47 -0
- package/plugins/lisa/rules/reference/dependency-trust-classes.md +48 -0
- package/plugins/lisa/rules/reference/deployed-state-readback.md +52 -0
- package/plugins/lisa/rules/reference/derived-branch-plan.md +52 -0
- package/plugins/lisa/rules/reference/design-source-of-truth.md +43 -0
- package/plugins/lisa/rules/reference/design-value-binding.md +103 -0
- package/plugins/lisa/rules/reference/documentation-source-paths.md +18 -0
- package/plugins/lisa/rules/reference/leaf-only-lifecycle.md +75 -0
- package/plugins/lisa/rules/reference/observability-audit.md +20 -0
- package/plugins/lisa/rules/reference/prd-definition-of-ready.md +37 -0
- package/plugins/lisa/rules/reference/prd-lifecycle-rollup.md +36 -0
- package/plugins/lisa/rules/reference/pre-flight-autofill.md +88 -0
- package/plugins/lisa/rules/reference/promotion-contract.md +21 -0
- package/plugins/lisa/rules/reference/readiness-rubric.md +63 -0
- package/plugins/lisa/rules/reference/ready-role-filing.md +34 -0
- package/plugins/lisa/rules/reference/rejection-detection.md +69 -0
- package/plugins/lisa/rules/reference/repo-scope-split.md +48 -0
- package/plugins/lisa/rules/reference/reset-seed-coverage.md +55 -0
- package/plugins/lisa/rules/reference/usage-accounting.md +38 -0
- package/plugins/lisa/rules/reference/work-item-definition-of-ready.md +32 -0
- package/plugins/lisa/scripts/intake-blocker-reprobe.mjs +158 -1
- package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +4 -1
- package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +89 -10
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +44 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/intake-blocker-reprobe.mjs +158 -1
- package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +4 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/block-direct-issue-create.sh +89 -10
- package/plugins/lisa-copilot/hooks/discharge-work-item-gates.sh +7 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +44 -0
- package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +12 -0
- package/plugins/lisa-copilot/rules/eager/00-rule-index.md +79 -0
- package/plugins/lisa-copilot/rules/eager/base-rules.md +10 -0
- package/plugins/lisa-copilot/rules/eager/config-resolution.md +1 -88
- package/plugins/lisa-copilot/rules/reference/automation-runbook-contract.md +97 -0
- package/plugins/lisa-copilot/rules/reference/bdd-e2e-coverage.md +56 -0
- package/plugins/lisa-copilot/rules/reference/blocker-containment.md +58 -0
- package/plugins/lisa-copilot/rules/reference/claim-archaeology.md +42 -0
- package/plugins/lisa-copilot/rules/reference/claim-evidence-mapping.md +83 -0
- package/plugins/lisa-copilot/rules/reference/claim-time-guards.md +37 -0
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +97 -0
- package/plugins/lisa-copilot/rules/reference/control-reachability.md +44 -0
- package/plugins/lisa-copilot/rules/reference/convergent-review.md +31 -0
- package/plugins/lisa-copilot/rules/reference/credential-substrate-precedence.md +57 -0
- package/plugins/lisa-copilot/rules/reference/dependency-decision-records.md +37 -0
- package/plugins/lisa-copilot/rules/reference/dependency-internalization-kit.md +47 -0
- package/plugins/lisa-copilot/rules/reference/dependency-trust-classes.md +48 -0
- package/plugins/lisa-copilot/rules/reference/deployed-state-readback.md +52 -0
- package/plugins/lisa-copilot/rules/reference/derived-branch-plan.md +52 -0
- package/plugins/lisa-copilot/rules/reference/design-source-of-truth.md +43 -0
- package/plugins/lisa-copilot/rules/reference/design-value-binding.md +103 -0
- package/plugins/lisa-copilot/rules/reference/documentation-source-paths.md +18 -0
- package/plugins/lisa-copilot/rules/reference/leaf-only-lifecycle.md +75 -0
- package/plugins/lisa-copilot/rules/reference/observability-audit.md +20 -0
- package/plugins/lisa-copilot/rules/reference/prd-definition-of-ready.md +37 -0
- package/plugins/lisa-copilot/rules/reference/prd-lifecycle-rollup.md +36 -0
- package/plugins/lisa-copilot/rules/reference/pre-flight-autofill.md +88 -0
- package/plugins/lisa-copilot/rules/reference/promotion-contract.md +21 -0
- package/plugins/lisa-copilot/rules/reference/readiness-rubric.md +63 -0
- package/plugins/lisa-copilot/rules/reference/ready-role-filing.md +34 -0
- package/plugins/lisa-copilot/rules/reference/rejection-detection.md +69 -0
- package/plugins/lisa-copilot/rules/reference/repo-scope-split.md +48 -0
- package/plugins/lisa-copilot/rules/reference/reset-seed-coverage.md +55 -0
- package/plugins/lisa-copilot/rules/reference/usage-accounting.md +38 -0
- package/plugins/lisa-copilot/rules/reference/work-item-definition-of-ready.md +32 -0
- package/plugins/lisa-copilot/scripts/intake-blocker-reprobe.mjs +158 -1
- package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +4 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +89 -10
- package/plugins/lisa-cursor/hooks/discharge-work-item-gates.sh +7 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +44 -0
- package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +12 -0
- package/plugins/lisa-cursor/rules/00-rule-index.mdc +84 -0
- package/plugins/lisa-cursor/rules/automation-runbook-contract-reference.mdc +97 -0
- package/plugins/lisa-cursor/rules/base-rules.mdc +10 -0
- package/plugins/lisa-cursor/rules/bdd-e2e-coverage-reference.mdc +56 -0
- package/plugins/lisa-cursor/rules/blocker-containment-reference.mdc +58 -0
- package/plugins/lisa-cursor/rules/claim-archaeology-reference.mdc +42 -0
- package/plugins/lisa-cursor/rules/claim-evidence-mapping-reference.mdc +83 -0
- package/plugins/lisa-cursor/rules/claim-time-guards-reference.mdc +37 -0
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +97 -0
- package/plugins/lisa-cursor/rules/config-resolution.mdc +1 -88
- package/plugins/lisa-cursor/rules/control-reachability-reference.mdc +44 -0
- package/plugins/lisa-cursor/rules/convergent-review-reference.mdc +31 -0
- package/plugins/lisa-cursor/rules/credential-substrate-precedence-reference.mdc +57 -0
- package/plugins/lisa-cursor/rules/dependency-decision-records-reference.mdc +37 -0
- package/plugins/lisa-cursor/rules/dependency-internalization-kit-reference.mdc +47 -0
- package/plugins/lisa-cursor/rules/dependency-trust-classes-reference.mdc +48 -0
- package/plugins/lisa-cursor/rules/deployed-state-readback-reference.mdc +52 -0
- package/plugins/lisa-cursor/rules/derived-branch-plan-reference.mdc +52 -0
- package/plugins/lisa-cursor/rules/design-source-of-truth-reference.mdc +43 -0
- package/plugins/lisa-cursor/rules/design-value-binding-reference.mdc +103 -0
- package/plugins/lisa-cursor/rules/documentation-source-paths-reference.mdc +18 -0
- package/plugins/lisa-cursor/rules/leaf-only-lifecycle-reference.mdc +75 -0
- package/plugins/lisa-cursor/rules/observability-audit-reference.mdc +20 -0
- package/plugins/lisa-cursor/rules/prd-definition-of-ready-reference.mdc +37 -0
- package/plugins/lisa-cursor/rules/prd-lifecycle-rollup-reference.mdc +36 -0
- package/plugins/lisa-cursor/rules/pre-flight-autofill-reference.mdc +88 -0
- package/plugins/lisa-cursor/rules/promotion-contract-reference.mdc +21 -0
- package/plugins/lisa-cursor/rules/readiness-rubric-reference.mdc +63 -0
- package/plugins/lisa-cursor/rules/ready-role-filing-reference.mdc +34 -0
- package/plugins/lisa-cursor/rules/rejection-detection-reference.mdc +69 -0
- package/plugins/lisa-cursor/rules/repo-scope-split-reference.mdc +48 -0
- package/plugins/lisa-cursor/rules/reset-seed-coverage-reference.mdc +55 -0
- package/plugins/lisa-cursor/rules/usage-accounting-reference.mdc +38 -0
- package/plugins/lisa-cursor/rules/work-item-definition-of-ready-reference.mdc +32 -0
- package/plugins/lisa-cursor/scripts/intake-blocker-reprobe.mjs +158 -1
- package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +4 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/block-direct-issue-create.sh +89 -10
- package/plugins/src/base/hooks/discharge-work-item-gates.sh +7 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +44 -0
- package/plugins/src/base/hooks/threshold-ratchet-families.mjs +12 -0
- package/plugins/src/base/rules/eager/00-rule-index.md +79 -0
- package/plugins/src/base/rules/eager/base-rules.md +10 -0
- package/plugins/src/base/rules/eager/config-resolution.md +1 -88
- package/plugins/src/base/rules/reference/automation-runbook-contract.md +97 -0
- package/plugins/src/base/rules/reference/bdd-e2e-coverage.md +56 -0
- package/plugins/src/base/rules/reference/blocker-containment.md +58 -0
- package/plugins/src/base/rules/reference/claim-archaeology.md +42 -0
- package/plugins/src/base/rules/reference/claim-evidence-mapping.md +83 -0
- package/plugins/src/base/rules/reference/claim-time-guards.md +37 -0
- package/plugins/src/base/rules/reference/config-resolution.md +97 -0
- package/plugins/src/base/rules/reference/control-reachability.md +44 -0
- package/plugins/src/base/rules/reference/convergent-review.md +31 -0
- package/plugins/src/base/rules/reference/credential-substrate-precedence.md +57 -0
- package/plugins/src/base/rules/reference/dependency-decision-records.md +37 -0
- package/plugins/src/base/rules/reference/dependency-internalization-kit.md +47 -0
- package/plugins/src/base/rules/reference/dependency-trust-classes.md +48 -0
- package/plugins/src/base/rules/reference/deployed-state-readback.md +52 -0
- package/plugins/src/base/rules/reference/derived-branch-plan.md +52 -0
- package/plugins/src/base/rules/reference/design-source-of-truth.md +43 -0
- package/plugins/src/base/rules/reference/design-value-binding.md +103 -0
- package/plugins/src/base/rules/reference/documentation-source-paths.md +18 -0
- package/plugins/src/base/rules/reference/leaf-only-lifecycle.md +75 -0
- package/plugins/src/base/rules/reference/observability-audit.md +20 -0
- package/plugins/src/base/rules/reference/prd-definition-of-ready.md +37 -0
- package/plugins/src/base/rules/reference/prd-lifecycle-rollup.md +36 -0
- package/plugins/src/base/rules/reference/pre-flight-autofill.md +88 -0
- package/plugins/src/base/rules/reference/promotion-contract.md +21 -0
- package/plugins/src/base/rules/reference/readiness-rubric.md +63 -0
- package/plugins/src/base/rules/reference/ready-role-filing.md +34 -0
- package/plugins/src/base/rules/reference/rejection-detection.md +69 -0
- package/plugins/src/base/rules/reference/repo-scope-split.md +48 -0
- package/plugins/src/base/rules/reference/reset-seed-coverage.md +55 -0
- package/plugins/src/base/rules/reference/usage-accounting.md +38 -0
- package/plugins/src/base/rules/reference/work-item-definition-of-ready.md +32 -0
- package/plugins/src/base/scripts/intake-blocker-reprobe.mjs +158 -1
- package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +17 -1
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +4 -0
- package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +4 -0
- package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +4 -0
- package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +4 -1
- package/rails/copy-overwrite/ast-grep/rules/no-day-truncating-file-age-predicate.yml +50 -0
- package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +12 -0
- package/scripts/check-eager-rule-budget.mjs +176 -0
- package/scripts/check-generated-artifact-merge-coverage.mjs +7 -0
- package/scripts/check-rules-pairing.sh +45 -14
- package/scripts/check-shipped-surface-removals.mjs +851 -0
- package/scripts/cleanup-worktrees.sh +69 -16
- package/scripts/generate-export-surface.mjs +318 -0
- package/scripts/lib/shipped-surface.mjs +212 -0
- package/scripts/lisa-enforcement-fallback.sh +178 -1
- package/scripts/remedy-conformance.mjs +435 -0
- package/scripts/two-channel-couplings.json +2 -2
- package/typescript/copy-contents/.husky/pre-push +147 -2
- package/typescript/copy-overwrite/ast-grep/rules/no-day-truncating-file-age-predicate.yml +50 -0
- package/typescript/copy-overwrite/audit.ignore.config.json +0 -10
- package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +180 -29
- package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +12 -0
- package/typescript/package-lisa/package.lisa.json +72 -42
- package/plugins/lisa/rules/eager/automation-runbook-contract.md +0 -92
- package/plugins/lisa/rules/eager/bdd-e2e-coverage.md +0 -51
- package/plugins/lisa/rules/eager/blocker-containment.md +0 -53
- package/plugins/lisa/rules/eager/claim-archaeology.md +0 -37
- package/plugins/lisa/rules/eager/claim-evidence-mapping.md +0 -78
- package/plugins/lisa/rules/eager/claim-time-guards.md +0 -32
- package/plugins/lisa/rules/eager/control-reachability.md +0 -39
- package/plugins/lisa/rules/eager/convergent-review.md +0 -24
- package/plugins/lisa/rules/eager/credential-substrate-precedence.md +0 -54
- package/plugins/lisa/rules/eager/dependency-decision-records.md +0 -32
- package/plugins/lisa/rules/eager/dependency-internalization-kit.md +0 -42
- package/plugins/lisa/rules/eager/dependency-trust-classes.md +0 -43
- package/plugins/lisa/rules/eager/deployed-state-readback.md +0 -47
- package/plugins/lisa/rules/eager/derived-branch-plan.md +0 -47
- package/plugins/lisa/rules/eager/design-source-of-truth.md +0 -38
- package/plugins/lisa/rules/eager/design-value-binding.md +0 -98
- package/plugins/lisa/rules/eager/documentation-source-paths.md +0 -13
- package/plugins/lisa/rules/eager/factory-model.md +0 -41
- package/plugins/lisa/rules/eager/leaf-only-lifecycle.md +0 -70
- package/plugins/lisa/rules/eager/observability-audit.md +0 -15
- package/plugins/lisa/rules/eager/prd-definition-of-ready.md +0 -32
- package/plugins/lisa/rules/eager/prd-lifecycle-rollup.md +0 -31
- package/plugins/lisa/rules/eager/pre-flight-autofill.md +0 -84
- package/plugins/lisa/rules/eager/project-learnings.md +0 -22
- package/plugins/lisa/rules/eager/promotion-contract.md +0 -18
- package/plugins/lisa/rules/eager/readiness-rubric.md +0 -58
- package/plugins/lisa/rules/eager/ready-role-filing.md +0 -29
- package/plugins/lisa/rules/eager/rejection-detection.md +0 -64
- package/plugins/lisa/rules/eager/repo-scope-split.md +0 -43
- package/plugins/lisa/rules/eager/reset-seed-coverage.md +0 -50
- package/plugins/lisa/rules/eager/usage-accounting.md +0 -33
- package/plugins/lisa/rules/eager/work-item-definition-of-ready.md +0 -27
- package/plugins/lisa-copilot/rules/eager/automation-runbook-contract.md +0 -92
- package/plugins/lisa-copilot/rules/eager/bdd-e2e-coverage.md +0 -51
- package/plugins/lisa-copilot/rules/eager/blocker-containment.md +0 -53
- package/plugins/lisa-copilot/rules/eager/claim-archaeology.md +0 -37
- package/plugins/lisa-copilot/rules/eager/claim-evidence-mapping.md +0 -78
- package/plugins/lisa-copilot/rules/eager/claim-time-guards.md +0 -32
- package/plugins/lisa-copilot/rules/eager/control-reachability.md +0 -39
- package/plugins/lisa-copilot/rules/eager/convergent-review.md +0 -24
- package/plugins/lisa-copilot/rules/eager/credential-substrate-precedence.md +0 -54
- package/plugins/lisa-copilot/rules/eager/dependency-decision-records.md +0 -32
- package/plugins/lisa-copilot/rules/eager/dependency-internalization-kit.md +0 -42
- package/plugins/lisa-copilot/rules/eager/dependency-trust-classes.md +0 -43
- package/plugins/lisa-copilot/rules/eager/deployed-state-readback.md +0 -47
- package/plugins/lisa-copilot/rules/eager/derived-branch-plan.md +0 -47
- package/plugins/lisa-copilot/rules/eager/design-source-of-truth.md +0 -38
- package/plugins/lisa-copilot/rules/eager/design-value-binding.md +0 -98
- package/plugins/lisa-copilot/rules/eager/documentation-source-paths.md +0 -13
- package/plugins/lisa-copilot/rules/eager/factory-model.md +0 -41
- package/plugins/lisa-copilot/rules/eager/leaf-only-lifecycle.md +0 -70
- package/plugins/lisa-copilot/rules/eager/observability-audit.md +0 -15
- package/plugins/lisa-copilot/rules/eager/prd-definition-of-ready.md +0 -32
- package/plugins/lisa-copilot/rules/eager/prd-lifecycle-rollup.md +0 -31
- package/plugins/lisa-copilot/rules/eager/pre-flight-autofill.md +0 -84
- package/plugins/lisa-copilot/rules/eager/project-learnings.md +0 -22
- package/plugins/lisa-copilot/rules/eager/promotion-contract.md +0 -18
- package/plugins/lisa-copilot/rules/eager/readiness-rubric.md +0 -58
- package/plugins/lisa-copilot/rules/eager/ready-role-filing.md +0 -29
- package/plugins/lisa-copilot/rules/eager/rejection-detection.md +0 -64
- package/plugins/lisa-copilot/rules/eager/repo-scope-split.md +0 -43
- package/plugins/lisa-copilot/rules/eager/reset-seed-coverage.md +0 -50
- package/plugins/lisa-copilot/rules/eager/usage-accounting.md +0 -33
- package/plugins/lisa-copilot/rules/eager/work-item-definition-of-ready.md +0 -27
- package/plugins/lisa-cursor/rules/automation-runbook-contract.mdc +0 -97
- package/plugins/lisa-cursor/rules/bdd-e2e-coverage.mdc +0 -56
- package/plugins/lisa-cursor/rules/blocker-containment.mdc +0 -58
- package/plugins/lisa-cursor/rules/claim-archaeology.mdc +0 -42
- package/plugins/lisa-cursor/rules/claim-evidence-mapping.mdc +0 -83
- package/plugins/lisa-cursor/rules/claim-time-guards.mdc +0 -37
- package/plugins/lisa-cursor/rules/control-reachability.mdc +0 -44
- package/plugins/lisa-cursor/rules/convergent-review.mdc +0 -29
- package/plugins/lisa-cursor/rules/credential-substrate-precedence.mdc +0 -59
- package/plugins/lisa-cursor/rules/dependency-decision-records.mdc +0 -37
- package/plugins/lisa-cursor/rules/dependency-internalization-kit.mdc +0 -47
- package/plugins/lisa-cursor/rules/dependency-trust-classes.mdc +0 -48
- package/plugins/lisa-cursor/rules/deployed-state-readback.mdc +0 -52
- package/plugins/lisa-cursor/rules/derived-branch-plan.mdc +0 -52
- package/plugins/lisa-cursor/rules/design-source-of-truth.mdc +0 -43
- package/plugins/lisa-cursor/rules/design-value-binding.mdc +0 -103
- package/plugins/lisa-cursor/rules/documentation-source-paths.mdc +0 -18
- package/plugins/lisa-cursor/rules/factory-model.mdc +0 -46
- package/plugins/lisa-cursor/rules/leaf-only-lifecycle.mdc +0 -75
- package/plugins/lisa-cursor/rules/observability-audit.mdc +0 -20
- package/plugins/lisa-cursor/rules/prd-definition-of-ready.mdc +0 -37
- package/plugins/lisa-cursor/rules/prd-lifecycle-rollup.mdc +0 -36
- package/plugins/lisa-cursor/rules/pre-flight-autofill.mdc +0 -89
- package/plugins/lisa-cursor/rules/project-learnings.mdc +0 -27
- package/plugins/lisa-cursor/rules/promotion-contract.mdc +0 -23
- package/plugins/lisa-cursor/rules/readiness-rubric.mdc +0 -63
- package/plugins/lisa-cursor/rules/ready-role-filing.mdc +0 -34
- package/plugins/lisa-cursor/rules/rejection-detection.mdc +0 -69
- package/plugins/lisa-cursor/rules/repo-scope-split.mdc +0 -48
- package/plugins/lisa-cursor/rules/reset-seed-coverage.mdc +0 -55
- package/plugins/lisa-cursor/rules/usage-accounting.mdc +0 -38
- package/plugins/lisa-cursor/rules/work-item-definition-of-ready.mdc +0 -32
- package/plugins/src/base/rules/eager/automation-runbook-contract.md +0 -92
- package/plugins/src/base/rules/eager/bdd-e2e-coverage.md +0 -51
- package/plugins/src/base/rules/eager/blocker-containment.md +0 -53
- package/plugins/src/base/rules/eager/claim-archaeology.md +0 -37
- package/plugins/src/base/rules/eager/claim-evidence-mapping.md +0 -78
- package/plugins/src/base/rules/eager/claim-time-guards.md +0 -32
- package/plugins/src/base/rules/eager/control-reachability.md +0 -39
- package/plugins/src/base/rules/eager/convergent-review.md +0 -24
- package/plugins/src/base/rules/eager/credential-substrate-precedence.md +0 -54
- package/plugins/src/base/rules/eager/dependency-decision-records.md +0 -32
- package/plugins/src/base/rules/eager/dependency-internalization-kit.md +0 -42
- package/plugins/src/base/rules/eager/dependency-trust-classes.md +0 -43
- package/plugins/src/base/rules/eager/deployed-state-readback.md +0 -47
- package/plugins/src/base/rules/eager/derived-branch-plan.md +0 -47
- package/plugins/src/base/rules/eager/design-source-of-truth.md +0 -38
- package/plugins/src/base/rules/eager/design-value-binding.md +0 -98
- package/plugins/src/base/rules/eager/documentation-source-paths.md +0 -13
- package/plugins/src/base/rules/eager/factory-model.md +0 -41
- package/plugins/src/base/rules/eager/leaf-only-lifecycle.md +0 -70
- package/plugins/src/base/rules/eager/observability-audit.md +0 -15
- package/plugins/src/base/rules/eager/prd-definition-of-ready.md +0 -32
- package/plugins/src/base/rules/eager/prd-lifecycle-rollup.md +0 -31
- package/plugins/src/base/rules/eager/pre-flight-autofill.md +0 -84
- package/plugins/src/base/rules/eager/project-learnings.md +0 -22
- package/plugins/src/base/rules/eager/promotion-contract.md +0 -18
- package/plugins/src/base/rules/eager/readiness-rubric.md +0 -58
- package/plugins/src/base/rules/eager/ready-role-filing.md +0 -29
- package/plugins/src/base/rules/eager/rejection-detection.md +0 -64
- package/plugins/src/base/rules/eager/repo-scope-split.md +0 -43
- package/plugins/src/base/rules/eager/reset-seed-coverage.md +0 -50
- package/plugins/src/base/rules/eager/usage-accounting.md +0 -33
- package/plugins/src/base/rules/eager/work-item-definition-of-ready.md +0 -27
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
# ~/.codex/worktrees, .lisa-worktrees, /tmp) OR its branch matches an
|
|
11
11
|
# agent naming pattern (claude/*, worktree-*, codex/*) OR its branch is
|
|
12
12
|
# fully merged into an environment branch (main/dev/staging).
|
|
13
|
-
# 3.
|
|
13
|
+
# 3. Git could actually REPORT on it, and reported no modified/staged TRACKED
|
|
14
|
+
# files ("real work" is never deleted). "I looked and it was clean" and "I
|
|
15
|
+
# could not look" are different answers: a `git status` that FAILS is kept
|
|
16
|
+
# in its own bucket, never treated as clean. This mirrors the TypeScript
|
|
17
|
+
# entitlement rule (src/cli/worktree-prune-policy.ts), which blocks on
|
|
18
|
+
# `unreadable` rather than defaulting an unproven worktree to eligible.
|
|
14
19
|
# 4. Its HEAD commit is reachable from some remote ref (nothing unpushed).
|
|
15
20
|
# 5. It is older than --min-age-days (default 7) by directory mtime.
|
|
16
21
|
# 6. Its uncommitted content is reachable from some commit — asked of the
|
|
@@ -70,7 +75,7 @@ fi
|
|
|
70
75
|
|
|
71
76
|
NOW=$(date +%s)
|
|
72
77
|
MIN_AGE_SECS=$((MIN_AGE_DAYS * 86400))
|
|
73
|
-
REMOVED=0 KEPT_DIRTY=0 KEPT_UNPUSHED=0 KEPT_YOUNG=0 KEPT_UNTRACKED=0 ORPHANS=0 ERRORS=0
|
|
78
|
+
REMOVED=0 KEPT_DIRTY=0 KEPT_UNPUSHED=0 KEPT_YOUNG=0 KEPT_UNTRACKED=0 KEPT_UNREADABLE=0 ORPHANS=0 ERRORS=0
|
|
74
79
|
KEPT_UNREACHABLE=0
|
|
75
80
|
|
|
76
81
|
# Content-reachability guard. Absent node or an unresolvable script leaves the
|
|
@@ -162,7 +167,23 @@ process_worktree() {
|
|
|
162
167
|
return
|
|
163
168
|
fi
|
|
164
169
|
|
|
165
|
-
|
|
170
|
+
# Capture the OUTPUT and the EXIT STATUS separately. Written as
|
|
171
|
+
# `[ -n "$(git ... 2>/dev/null)" ]` — the way this and the untracked check
|
|
172
|
+
# below both used to be — a FAILED git collapses to the empty string, `-n ""`
|
|
173
|
+
# is false, and an unreadable worktree takes the same branch as a spotless
|
|
174
|
+
# one: it falls through every KEEP predicate to removal. A short/corrupt index
|
|
175
|
+
# (what a SIGKILLed git leaves behind, and the ordinary outcome of the
|
|
176
|
+
# concurrency pressure this script exists to relieve) reaches exactly that
|
|
177
|
+
# state: `git status` exits 128 while the ref queries below still answer.
|
|
178
|
+
local tracked_status tracked_rc
|
|
179
|
+
tracked_status=$(git -C "$wt" status --porcelain --untracked-files=no 2>/dev/null)
|
|
180
|
+
tracked_rc=$?
|
|
181
|
+
if [ "$tracked_rc" -ne 0 ]; then
|
|
182
|
+
KEPT_UNREADABLE=$((KEPT_UNREADABLE + 1))
|
|
183
|
+
log "KEEP (git could not report on it — unreadable, so nothing was proven) $wt"
|
|
184
|
+
return
|
|
185
|
+
fi
|
|
186
|
+
if [ -n "$tracked_status" ]; then
|
|
166
187
|
KEPT_DIRTY=$((KEPT_DIRTY + 1))
|
|
167
188
|
log "KEEP (modified tracked files) $wt"
|
|
168
189
|
return
|
|
@@ -183,7 +204,15 @@ process_worktree() {
|
|
|
183
204
|
fi
|
|
184
205
|
|
|
185
206
|
local force_flag=()
|
|
186
|
-
|
|
207
|
+
local full_status full_rc
|
|
208
|
+
full_status=$(git -C "$wt" status --porcelain 2>/dev/null)
|
|
209
|
+
full_rc=$?
|
|
210
|
+
if [ "$full_rc" -ne 0 ]; then
|
|
211
|
+
KEPT_UNREADABLE=$((KEPT_UNREADABLE + 1))
|
|
212
|
+
log "KEEP (git could not report on it — unreadable, so nothing was proven) $wt"
|
|
213
|
+
return
|
|
214
|
+
fi
|
|
215
|
+
if [ -n "$full_status" ]; then
|
|
187
216
|
if [ "$FORCE_UNTRACKED" = 1 ]; then
|
|
188
217
|
force_flag=(--force)
|
|
189
218
|
else
|
|
@@ -195,7 +224,12 @@ process_worktree() {
|
|
|
195
224
|
|
|
196
225
|
act "$wt [branch: ${branch:-detached}]"
|
|
197
226
|
if [ "$APPLY" = 1 ]; then
|
|
198
|
-
|
|
227
|
+
# `${arr[@]+"${arr[@]}"}` rather than a plain `"${force_flag[@]}"`. Under
|
|
228
|
+
# `set -u`, bash before 4.4 — which includes the 3.2.57 that ships as
|
|
229
|
+
# /bin/bash on macOS, where this script mostly runs — treats an EMPTY array
|
|
230
|
+
# expansion as an unbound variable and aborts. The abort landed on the first
|
|
231
|
+
# removal candidate, so `--apply` exited 1 with no summary at all.
|
|
232
|
+
if git -C "$REPO" worktree remove ${force_flag[@]+"${force_flag[@]}"} "$wt" 2>/dev/null; then
|
|
199
233
|
REMOVED=$((REMOVED + 1))
|
|
200
234
|
if [ "$DELETE_BRANCHES" = 1 ] && [ -n "$branch" ] && ! is_env_branch "$branch"; then
|
|
201
235
|
git -C "$REPO" branch -D "$branch" >/dev/null 2>&1 || true
|
|
@@ -232,24 +266,43 @@ while IFS= read -r line; do
|
|
|
232
266
|
done < <(git -C "$REPO" worktree list --porcelain; echo)
|
|
233
267
|
|
|
234
268
|
# Orphan directories under .claude/worktrees that git no longer tracks.
|
|
269
|
+
#
|
|
270
|
+
# This is the same "a failed read looks like a benign answer" hazard the status
|
|
271
|
+
# guards above fix, with a worse ending: the removal here is `rm -rf`, which
|
|
272
|
+
# bypasses `git worktree remove`'s own refusal to delete a dirty worktree. So
|
|
273
|
+
# the enumeration is done ONCE, up front, with its exit status checked. An empty
|
|
274
|
+
# result from a FAILED `git worktree list` would make `grep` match nothing, `!`
|
|
275
|
+
# invert to true, and a live registered worktree be deleted outright — and empty
|
|
276
|
+
# output from a killed git is the ordinary outcome of the very concurrency
|
|
277
|
+
# pressure this script exists to relieve. Nothing proven means nothing swept.
|
|
235
278
|
wtroot="$REPO/.claude/worktrees"
|
|
236
279
|
if [ -d "$wtroot" ]; then
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
280
|
+
registered=$(git -C "$REPO" worktree list --porcelain 2>/dev/null)
|
|
281
|
+
registered_rc=$?
|
|
282
|
+
if [ "$registered_rc" -ne 0 ]; then
|
|
283
|
+
log "SKIP orphan sweep (git could not enumerate worktrees — nothing was proven)"
|
|
284
|
+
else
|
|
285
|
+
for d in "$wtroot"/*/; do
|
|
286
|
+
d=${d%/}
|
|
287
|
+
[ -d "$d" ] || continue
|
|
288
|
+
# `-x` anchors the whole line. A substring match let a registered
|
|
289
|
+
# `…/wt-280` mark a genuine orphan `…/wt-28` as registered forever; that
|
|
290
|
+
# direction only ever keeps too much, but the prefix collision is real and
|
|
291
|
+
# this repository has that collision class on record.
|
|
292
|
+
if ! printf '%s\n' "$registered" | grep -qxF "worktree $d"; then
|
|
293
|
+
if dir_age_ok "$d"; then
|
|
294
|
+
ORPHANS=$((ORPHANS + 1))
|
|
295
|
+
act "(orphan dir, not a registered worktree) $d"
|
|
296
|
+
[ "$APPLY" = 1 ] && rm -rf "$d"
|
|
297
|
+
fi
|
|
245
298
|
fi
|
|
246
|
-
|
|
247
|
-
|
|
299
|
+
done
|
|
300
|
+
fi
|
|
248
301
|
fi
|
|
249
302
|
|
|
250
303
|
log ""
|
|
251
304
|
log "=== summary ==="
|
|
252
305
|
log "removed (or would remove): $REMOVED orphan dirs: $ORPHANS"
|
|
253
|
-
log "kept: $KEPT_DIRTY modified-tracked, $KEPT_UNPUSHED unpushed, $KEPT_UNTRACKED untracked-only, $KEPT_UNREACHABLE uncommitted-content, $KEPT_YOUNG too-young errors: $ERRORS"
|
|
306
|
+
log "kept: $KEPT_DIRTY modified-tracked, $KEPT_UNPUSHED unpushed, $KEPT_UNTRACKED untracked-only, $KEPT_UNREACHABLE uncommitted-content, $KEPT_YOUNG too-young, $KEPT_UNREADABLE unreadable errors: $ERRORS"
|
|
254
307
|
[ "$APPLY" = 0 ] && log "(dry run — rerun with --apply to delete)"
|
|
255
308
|
exit 0
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Record the public export surface this package ships, so a removal is an
|
|
4
|
+
* observation rather than an inference.
|
|
5
|
+
*
|
|
6
|
+
* ## The defect this exists against
|
|
7
|
+
*
|
|
8
|
+
* A release shipped a changelog declaring ZERO breaking changes across a range
|
|
9
|
+
* whose diff had removed a public export. The changelog was accurate about its
|
|
10
|
+
* own contents and useless as a safety signal: a downstream reader did the
|
|
11
|
+
* right check, carefully, with a positive control, and got a confident wrong
|
|
12
|
+
* answer. Two consumer repositories were about to upgrade on the strength of it
|
|
13
|
+
* (CodySwannGT/lisa#3718).
|
|
14
|
+
*
|
|
15
|
+
* The mechanism is that the attestation is **derived from something adjacent**.
|
|
16
|
+
* "No breaking changes" is computed from commit-message conventions — whether
|
|
17
|
+
* anyone wrote `!` or a `BREAKING CHANGE:` footer — and a template rewrite that
|
|
18
|
+
* drops an exported symbol carries neither. So the claim is about what authors
|
|
19
|
+
* typed, and it is read as a claim about what happened to the exported surface.
|
|
20
|
+
*
|
|
21
|
+
* A claim and an observation are different things. `"no breaking changes"` is a
|
|
22
|
+
* claim. `"these names were exported before and are exported now"` is an
|
|
23
|
+
* observation, and it is the one a consumer actually needs.
|
|
24
|
+
*
|
|
25
|
+
* ## Why the surface is recorded rather than computed at release time
|
|
26
|
+
*
|
|
27
|
+
* A committed artifact makes the removal **visible in the pull request that
|
|
28
|
+
* causes it**, not only in the release notes afterwards. The diff is the signal.
|
|
29
|
+
* It also means the comparison is a pure git operation with no network — which
|
|
30
|
+
* matters, because the release path already depends on registry availability
|
|
31
|
+
* that has failed under us.
|
|
32
|
+
*
|
|
33
|
+
* ## What it covers, and what it does not
|
|
34
|
+
*
|
|
35
|
+
* The shipped `.mjs` scripts under the `files` allowlist. That is deliberately
|
|
36
|
+
* where the incident happened: a `copy-overwrite` template is installed into a
|
|
37
|
+
* consumer's own `scripts/` directory, and the consumer's repo-owned code
|
|
38
|
+
* imports from it. Those files are invisible to an import graph on either side
|
|
39
|
+
* — Lisa cannot see the consumer's importer, and the consumer has no dependency
|
|
40
|
+
* to declare — so the exported names are the only shared contract there is.
|
|
41
|
+
*
|
|
42
|
+
* It reads exports **statically**, by their declaration form. It does not
|
|
43
|
+
* execute the modules, and it therefore cannot see a name attached at runtime.
|
|
44
|
+
* That limit is deliberate: importing 130 shipped scripts to enumerate them
|
|
45
|
+
* would run their top-level code during a build.
|
|
46
|
+
* @module scripts/generate-export-surface
|
|
47
|
+
*/
|
|
48
|
+
import { execFileSync } from "node:child_process";
|
|
49
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
50
|
+
import path from "node:path";
|
|
51
|
+
|
|
52
|
+
/** Directories inside the `files` allowlist that ship executable `.mjs`. */
|
|
53
|
+
const SHIPPED_ROOTS = [
|
|
54
|
+
"all",
|
|
55
|
+
"typescript",
|
|
56
|
+
"rails",
|
|
57
|
+
"expo",
|
|
58
|
+
"nestjs",
|
|
59
|
+
"cdk",
|
|
60
|
+
"phaser",
|
|
61
|
+
"harper-fabric",
|
|
62
|
+
"scripts",
|
|
63
|
+
"npm-package",
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Where the recorded surface lives.
|
|
68
|
+
*
|
|
69
|
+
* JSON rather than a TypeScript module, and the reason is the diff. One
|
|
70
|
+
* exported name per line is what makes a removal show up as a DELETED line in
|
|
71
|
+
* the pull request that causes it — which is the entire point of recording the
|
|
72
|
+
* surface. At 974 names that is well past the 300-line ceiling `max-lines`
|
|
73
|
+
* applies to source, and the sibling generated artifacts buy their way past it
|
|
74
|
+
* with a file-wide `eslint-disable`. Data does not need to be source: as JSON
|
|
75
|
+
* the artifact keeps the per-name diff, needs no waiver, and its reader becomes
|
|
76
|
+
* `JSON.parse` instead of a hand-rolled line parser that could silently
|
|
77
|
+
* disagree with the renderer.
|
|
78
|
+
*/
|
|
79
|
+
const ARTIFACT = "src/core/export-surface.json";
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Declaration forms that introduce a named export.
|
|
83
|
+
*
|
|
84
|
+
* Anchored at line start, because an `export` inside a string or a comment is
|
|
85
|
+
* not a declaration and counting it would make the artifact disagree with the
|
|
86
|
+
* module. The corpus was surveyed before this list was written: 613 `function`,
|
|
87
|
+
* 306 `const`, 38 `async function`, 14 `class`, 4 brace-lists.
|
|
88
|
+
*/
|
|
89
|
+
const DECLARATION =
|
|
90
|
+
/^export\s+(?:async\s+)?(?:function|const|let|var|class)\s+([A-Za-z_$][\w$]*)/;
|
|
91
|
+
|
|
92
|
+
/** `export { a, b as c }` — the re-export form. */
|
|
93
|
+
const BRACE_LIST = /^export\s*\{([^}]*)\}/;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Every tracked, shipped `.mjs` path.
|
|
97
|
+
* @param {string} cwd Repository root.
|
|
98
|
+
* @returns {string[]} Repo-relative paths, sorted.
|
|
99
|
+
*/
|
|
100
|
+
export function shippedScripts(cwd = process.cwd()) {
|
|
101
|
+
const listed = execFileSync("git", ["ls-files"], {
|
|
102
|
+
cwd,
|
|
103
|
+
encoding: "utf8",
|
|
104
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
105
|
+
}).split("\n");
|
|
106
|
+
return listed
|
|
107
|
+
.filter(file => file.endsWith(".mjs"))
|
|
108
|
+
.filter(file => SHIPPED_ROOTS.includes(file.split("/")[0]))
|
|
109
|
+
.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The names one module exports.
|
|
114
|
+
*
|
|
115
|
+
* `export default` is deliberately excluded: it has no name to compare, so
|
|
116
|
+
* recording it would add a row that can never change meaningfully.
|
|
117
|
+
* @param {string} source Module source.
|
|
118
|
+
* @returns {string[]} Exported names, sorted and deduplicated.
|
|
119
|
+
*/
|
|
120
|
+
export function exportedNames(source) {
|
|
121
|
+
const names = new Set();
|
|
122
|
+
for (const line of source.split("\n")) {
|
|
123
|
+
const declared = DECLARATION.exec(line);
|
|
124
|
+
if (declared) {
|
|
125
|
+
names.add(declared[1]);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const braced = BRACE_LIST.exec(line);
|
|
129
|
+
if (!braced) continue;
|
|
130
|
+
for (const clause of braced[1].split(",")) {
|
|
131
|
+
// `a as b` exports b; the local name is not part of the surface.
|
|
132
|
+
const parts = clause.trim().split(/\s+as\s+/);
|
|
133
|
+
const exported = (parts.at(-1) ?? "").trim();
|
|
134
|
+
if (exported && exported !== "default") names.add(exported);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return [...names].sort((left, right) =>
|
|
138
|
+
left < right ? -1 : left > right ? 1 : 0
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The surface as it stands in the working tree.
|
|
144
|
+
* @param {string} cwd Repository root.
|
|
145
|
+
* @returns {Record<string, string[]>} Path to exported names.
|
|
146
|
+
*/
|
|
147
|
+
export function currentSurface(cwd = process.cwd()) {
|
|
148
|
+
const surface = {};
|
|
149
|
+
for (const file of shippedScripts(cwd)) {
|
|
150
|
+
const names = exportedNames(readFileSync(path.join(cwd, file), "utf8"));
|
|
151
|
+
if (names.length > 0) surface[file] = names;
|
|
152
|
+
}
|
|
153
|
+
return surface;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Render the artifact.
|
|
158
|
+
* @param {Record<string, string[]>} surface The recorded surface.
|
|
159
|
+
* @returns {string} Artifact JSON, newline-terminated.
|
|
160
|
+
*/
|
|
161
|
+
export function renderArtifact(surface) {
|
|
162
|
+
const total = Object.values(surface).reduce(
|
|
163
|
+
(sum, names) => sum + names.length,
|
|
164
|
+
0
|
|
165
|
+
);
|
|
166
|
+
return `${JSON.stringify(
|
|
167
|
+
{
|
|
168
|
+
$generatedBy: "scripts/generate-export-surface.mjs",
|
|
169
|
+
$doNotEdit:
|
|
170
|
+
"The public export surface this package ships, recorded so a removal is an OBSERVATION rather than an inference from commit-message conventions. This file's diff is what makes a removed export visible in the pull request that causes it (CodySwannGT/lisa#3718).",
|
|
171
|
+
$modules: Object.keys(surface).length,
|
|
172
|
+
$names: total,
|
|
173
|
+
surface,
|
|
174
|
+
},
|
|
175
|
+
null,
|
|
176
|
+
2
|
|
177
|
+
)}\n`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Read the recorded surface out of an artifact's source.
|
|
182
|
+
*
|
|
183
|
+
* Parsed from text rather than imported, so the same reader works on a
|
|
184
|
+
* `git show` of an older revision — which is what comparing against a previous
|
|
185
|
+
* release requires.
|
|
186
|
+
* @param {string} source Artifact source.
|
|
187
|
+
* @returns {Record<string, string[]>} Path to exported names.
|
|
188
|
+
*/
|
|
189
|
+
export function parseArtifact(source) {
|
|
190
|
+
const parsed = JSON.parse(source);
|
|
191
|
+
return parsed.surface ?? {};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Names present in `before` and absent from `after`.
|
|
196
|
+
* @param {Record<string, string[]>} before Earlier surface.
|
|
197
|
+
* @param {Record<string, string[]>} after Later surface.
|
|
198
|
+
* @returns {Array<{file: string, name: string}>} Removals, sorted.
|
|
199
|
+
*/
|
|
200
|
+
export function removedExports(before, after) {
|
|
201
|
+
const gone = [];
|
|
202
|
+
for (const [file, names] of Object.entries(before)) {
|
|
203
|
+
const kept = new Set(after[file] ?? []);
|
|
204
|
+
for (const name of names) if (!kept.has(name)) gone.push({ file, name });
|
|
205
|
+
}
|
|
206
|
+
return gone.sort((left, right) =>
|
|
207
|
+
left.file === right.file
|
|
208
|
+
? left.name < right.name
|
|
209
|
+
? -1
|
|
210
|
+
: 1
|
|
211
|
+
: left.file < right.file
|
|
212
|
+
? -1
|
|
213
|
+
: 1
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The changelog section a release carries when it removes exports.
|
|
219
|
+
*
|
|
220
|
+
* Emits nothing when nothing was removed — a release that removed no export
|
|
221
|
+
* should not carry a paragraph saying so, or the section becomes noise that
|
|
222
|
+
* readers learn to skip, which is the failure this whole file exists against.
|
|
223
|
+
* @param {Array<{file: string, name: string}>} removals From {@link removedExports}.
|
|
224
|
+
* @returns {string} Markdown, or an empty string.
|
|
225
|
+
*/
|
|
226
|
+
export function changelogSection(removals) {
|
|
227
|
+
if (removals.length === 0) return "";
|
|
228
|
+
const rows = removals
|
|
229
|
+
.map(({ file, name }) => `- \`${name}\` — was exported by \`${file}\``)
|
|
230
|
+
.join("\n");
|
|
231
|
+
return `### ⚠ Removed public exports\n\n${String(removals.length)} exported name(s) present in the previous release are absent from this one. A consumer importing any of them will break on upgrade.\n\n${rows}\n`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Read the artifact as it stood at a git revision.
|
|
236
|
+
* @param {string} ref Git revision.
|
|
237
|
+
* @param {string} cwd Repository root.
|
|
238
|
+
* @returns {Record<string, string[]>|null} The surface, or null when absent.
|
|
239
|
+
*/
|
|
240
|
+
function surfaceAt(ref, cwd) {
|
|
241
|
+
try {
|
|
242
|
+
const source = execFileSync("git", ["show", `${ref}:${ARTIFACT}`], {
|
|
243
|
+
cwd,
|
|
244
|
+
encoding: "utf8",
|
|
245
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
246
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
247
|
+
});
|
|
248
|
+
return parseArtifact(source);
|
|
249
|
+
} catch {
|
|
250
|
+
// The artifact did not exist at that revision. That is not "no exports
|
|
251
|
+
// were removed" — it is "no comparison is possible", and the two must not
|
|
252
|
+
// render the same, so the caller is told rather than shown an empty list.
|
|
253
|
+
//
|
|
254
|
+
// probe-direction: fail-closed — `main` renders this null as exit 2 and
|
|
255
|
+
// says so in words, which any caller treating non-zero as a block reads
|
|
256
|
+
// as a refusal. An unreadable revision costs a false block, never a
|
|
257
|
+
// release that reports zero removals because it could not look.
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* CLI.
|
|
264
|
+
* @returns {number} Exit code.
|
|
265
|
+
*/
|
|
266
|
+
function main() {
|
|
267
|
+
const cwd = process.cwd();
|
|
268
|
+
const argv = process.argv.slice(2);
|
|
269
|
+
const since = argv.includes("--removed-since")
|
|
270
|
+
? argv[argv.indexOf("--removed-since") + 1]
|
|
271
|
+
: null;
|
|
272
|
+
|
|
273
|
+
if (since) {
|
|
274
|
+
const before = surfaceAt(since, cwd);
|
|
275
|
+
if (before === null) {
|
|
276
|
+
// Exit 2, not 0. "I could not compare" and "nothing was removed" are
|
|
277
|
+
// opposite facts, and this whole file exists because a release reported
|
|
278
|
+
// the second while meaning something closer to the first. A caller that
|
|
279
|
+
// treats any non-zero as a block gets the safe reading by default; one
|
|
280
|
+
// that wants to allow the very first release — before any surface has
|
|
281
|
+
// been recorded — can distinguish 2 from 1 deliberately.
|
|
282
|
+
process.stdout.write(
|
|
283
|
+
`No export surface recorded at ${since}; nothing to compare. This is NOT a clean result — it is an unanswered question.\n`
|
|
284
|
+
);
|
|
285
|
+
return 2;
|
|
286
|
+
}
|
|
287
|
+
const removals = removedExports(before, currentSurface(cwd));
|
|
288
|
+
const section = changelogSection(removals);
|
|
289
|
+
process.stdout.write(
|
|
290
|
+
section === ""
|
|
291
|
+
? `No public exports removed since ${since}. ${String(Object.values(before).reduce((sum, names) => sum + names.length, 0))} names compared.\n`
|
|
292
|
+
: section
|
|
293
|
+
);
|
|
294
|
+
return removals.length > 0 ? 1 : 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const rendered = renderArtifact(currentSurface(cwd));
|
|
298
|
+
const target = path.join(cwd, ARTIFACT);
|
|
299
|
+
if (argv.includes("--check")) {
|
|
300
|
+
let existing = "";
|
|
301
|
+
try {
|
|
302
|
+
existing = readFileSync(target, "utf8");
|
|
303
|
+
} catch {
|
|
304
|
+
existing = "";
|
|
305
|
+
}
|
|
306
|
+
if (existing === rendered) return 0;
|
|
307
|
+
process.stderr.write(
|
|
308
|
+
`${ARTIFACT} is stale. Regenerate it in this commit:\n bun run build:export-surface\n`
|
|
309
|
+
);
|
|
310
|
+
return 1;
|
|
311
|
+
}
|
|
312
|
+
writeFileSync(target, rendered);
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
317
|
+
process.exit(main());
|
|
318
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure parsing and classification helpers for the shipped-surface removal gate
|
|
3
|
+
* (`scripts/check-shipped-surface-removals.mjs`, CodySwannGT/lisa#3849).
|
|
4
|
+
*
|
|
5
|
+
* Everything here is a total function over strings and plain objects: no git,
|
|
6
|
+
* no filesystem, no clock. The gate keeps its I/O in one file so these can be
|
|
7
|
+
* mutation-tested by hand without a repository fixture, which matters because
|
|
8
|
+
* `stryker.conf.json`'s `mutate` is a ten-file allowlist that a new `scripts/`
|
|
9
|
+
* module does not join - an automated run over this file reports
|
|
10
|
+
* `nothing-to-mutate` and says nothing at all.
|
|
11
|
+
*
|
|
12
|
+
* @module scripts/lib/shipped-surface
|
|
13
|
+
*/
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extensions a consumer can put behind a `package.json` script entry.
|
|
18
|
+
*
|
|
19
|
+
* The list is deliberately broader than "things Lisa runs itself": the
|
|
20
|
+
* question this gate asks is not what upstream invokes, it is what a HOST
|
|
21
|
+
* could have wired. See `isConsumerBindable`.
|
|
22
|
+
*/
|
|
23
|
+
export const EXECUTABLE_EXTENSIONS = Object.freeze([
|
|
24
|
+
".cjs",
|
|
25
|
+
".js",
|
|
26
|
+
".mjs",
|
|
27
|
+
".py",
|
|
28
|
+
".sh",
|
|
29
|
+
".ts",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/** Destination directory whose contents a host routinely names in scripts. */
|
|
33
|
+
export const BINDABLE_ROOT = "scripts/";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether a destination path is one a consumer may have bound into their own
|
|
37
|
+
* `package.json` - and therefore one that must NOT be propagated as a deletion
|
|
38
|
+
* by default (#3849, revised scenario 3).
|
|
39
|
+
*
|
|
40
|
+
* The rule is the ticket's, stated as a question about the host rather than
|
|
41
|
+
* about upstream: not "does upstream still need it" but "could a consumer have
|
|
42
|
+
* wired this into their own scripts?" For an executable under `scripts/` the
|
|
43
|
+
* answer is usually yes, and the reported instance is exactly that shape - an
|
|
44
|
+
* importer with three npm script entries, a workflow, and a test in the host.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} destination - path in the consumer's tree.
|
|
47
|
+
* @returns {boolean} true when a host may have bound it.
|
|
48
|
+
*/
|
|
49
|
+
export function isConsumerBindable(destination) {
|
|
50
|
+
if (!destination.startsWith(BINDABLE_ROOT)) return false;
|
|
51
|
+
return EXECUTABLE_EXTENSIONS.includes(path.posix.extname(destination));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Declaration forms that name exactly one exported binding. */
|
|
55
|
+
const DECLARED_EXPORT_PATTERNS = Object.freeze([
|
|
56
|
+
/^\s*export\s+(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/gm,
|
|
57
|
+
/^\s*export\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)/gm,
|
|
58
|
+
/^\s*export\s+class\s+([A-Za-z_$][\w$]*)/gm,
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
/** `export { a, b as c }`, with or without a `from` clause. */
|
|
62
|
+
const EXPORT_CLAUSE = /^\s*export\s*\{([^}]*)\}/gm;
|
|
63
|
+
|
|
64
|
+
/** `export * from "./x.mjs"` - a re-export whose names live in the target. */
|
|
65
|
+
const EXPORT_STAR = /^\s*export\s*\*\s*from\s*["']([^"']+)["']/gm;
|
|
66
|
+
|
|
67
|
+
/** `import { a, b as c } from "./x.mjs"` - named imports only. */
|
|
68
|
+
const IMPORT_CLAUSE = /^\s*import\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/gm;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The exported name a `{ ... }` clause member introduces: the alias when one
|
|
72
|
+
* is written, the original otherwise. `default` members are dropped - a
|
|
73
|
+
* default export is not a named one and the gate never asks about it.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} member - one comma-separated clause member.
|
|
76
|
+
* @returns {string | null} the exported name, or null when there is none.
|
|
77
|
+
*/
|
|
78
|
+
export function clauseExportedName(member) {
|
|
79
|
+
const trimmed = member.trim().replace(/^type\s+/, "");
|
|
80
|
+
if (trimmed === "") return null;
|
|
81
|
+
const parts = trimmed.split(/\s+as\s+/);
|
|
82
|
+
const name = (parts.length > 1 ? parts[1] : parts[0]).trim();
|
|
83
|
+
return name === "" || name === "default" ? null : name;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The imported local binding a `{ ... }` clause member requests from the
|
|
88
|
+
* target module: always the name BEFORE any `as`, because that is the symbol
|
|
89
|
+
* the target must actually export.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} member - one comma-separated clause member.
|
|
92
|
+
* @returns {string | null} the requested export name, or null when there is none.
|
|
93
|
+
*/
|
|
94
|
+
export function clauseImportedName(member) {
|
|
95
|
+
const trimmed = member.trim().replace(/^type\s+/, "");
|
|
96
|
+
if (trimmed === "") return null;
|
|
97
|
+
const name = trimmed.split(/\s+as\s+/)[0].trim();
|
|
98
|
+
return name === "" || name === "default" ? null : name;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Every named export a module source declares directly, plus the relative
|
|
103
|
+
* specifiers it re-exports wholesale.
|
|
104
|
+
*
|
|
105
|
+
* `export *` is returned rather than resolved: resolution needs the delivery
|
|
106
|
+
* view, which lives in the gate. Doing it here would make this module read the
|
|
107
|
+
* filesystem.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} source - module source text.
|
|
110
|
+
* @returns {{ names: Set<string>, starFrom: string[] }} declared names and
|
|
111
|
+
* the relative specifiers whose names are inherited.
|
|
112
|
+
*/
|
|
113
|
+
export function parseNamedExports(source) {
|
|
114
|
+
const names = new Set();
|
|
115
|
+
for (const pattern of DECLARED_EXPORT_PATTERNS) {
|
|
116
|
+
for (const match of source.matchAll(pattern)) names.add(match[1]);
|
|
117
|
+
}
|
|
118
|
+
for (const match of source.matchAll(EXPORT_CLAUSE)) {
|
|
119
|
+
for (const member of match[1].split(",")) {
|
|
120
|
+
const name = clauseExportedName(member);
|
|
121
|
+
if (name !== null) names.add(name);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const starFrom = [...source.matchAll(EXPORT_STAR)]
|
|
125
|
+
.map(match => match[1])
|
|
126
|
+
.filter(specifier => specifier.startsWith("."));
|
|
127
|
+
return { names, starFrom };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Every named import a module source requests from a RELATIVE specifier.
|
|
132
|
+
*
|
|
133
|
+
* Bare specifiers are skipped: they resolve through `node_modules` in the
|
|
134
|
+
* consumer's tree, which this repository cannot see and does not govern.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} source - module source text.
|
|
137
|
+
* @returns {Array<{ specifier: string, names: string[] }>} one entry per
|
|
138
|
+
* relative named-import statement, in source order.
|
|
139
|
+
*/
|
|
140
|
+
export function parseRelativeNamedImports(source) {
|
|
141
|
+
const requests = [];
|
|
142
|
+
for (const match of source.matchAll(IMPORT_CLAUSE)) {
|
|
143
|
+
const specifier = match[2];
|
|
144
|
+
if (!specifier.startsWith(".")) continue;
|
|
145
|
+
const names = match[1]
|
|
146
|
+
.split(",")
|
|
147
|
+
.map(member => clauseImportedName(member))
|
|
148
|
+
.filter(name => name !== null);
|
|
149
|
+
if (names.length > 0) requests.push({ names, specifier });
|
|
150
|
+
}
|
|
151
|
+
return requests;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The destination a relative specifier resolves to, given the destination
|
|
156
|
+
* path of the importing module.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} importerDestination - the importer's path in the consumer tree.
|
|
159
|
+
* @param {string} specifier - a relative specifier from that importer.
|
|
160
|
+
* @returns {string} the resolved destination path.
|
|
161
|
+
*/
|
|
162
|
+
export function resolveRelative(importerDestination, specifier) {
|
|
163
|
+
return path.posix.normalize(
|
|
164
|
+
path.posix.join(path.posix.dirname(importerDestination), specifier)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The ledger key for one governed surface.
|
|
170
|
+
*
|
|
171
|
+
* A whole-file removal is keyed by path alone; an export removal is keyed
|
|
172
|
+
* `path symbol`. A shipped path never contains a space, so the two key spaces
|
|
173
|
+
* cannot collide.
|
|
174
|
+
*
|
|
175
|
+
* @param {string} shippedPath - repo-relative path inside a delivery lane.
|
|
176
|
+
* @param {string} [symbol] - the exported name, when a symbol rather than a file.
|
|
177
|
+
* @returns {string} the key.
|
|
178
|
+
*/
|
|
179
|
+
export function removalKey(shippedPath, symbol) {
|
|
180
|
+
return symbol === undefined ? shippedPath : `${shippedPath} ${symbol}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Index a removal ledger by the surface each entry governs.
|
|
185
|
+
*
|
|
186
|
+
* @param {ReadonlyArray<{ path: string, export?: string, note?: string }>} removals
|
|
187
|
+
* ledger entries.
|
|
188
|
+
* @returns {Map<string, { path: string, export?: string, note?: string }>} the index.
|
|
189
|
+
*/
|
|
190
|
+
export function indexRemovals(removals) {
|
|
191
|
+
return new Map(
|
|
192
|
+
removals.map(entry => [removalKey(entry.path, entry.export), entry])
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Whether a ledger entry actually records something an operator can act on.
|
|
198
|
+
*
|
|
199
|
+
* An entry with a blank note is treated as absent. A note that says nothing is
|
|
200
|
+
* the same silence the gate exists to break, and accepting it would let the
|
|
201
|
+
* ledger be satisfied by a placeholder.
|
|
202
|
+
*
|
|
203
|
+
* @param {{ note?: unknown } | undefined} entry - the indexed ledger entry.
|
|
204
|
+
* @returns {boolean} true when the entry carries a usable note.
|
|
205
|
+
*/
|
|
206
|
+
export function hasUsableNote(entry) {
|
|
207
|
+
return (
|
|
208
|
+
entry !== undefined &&
|
|
209
|
+
typeof entry.note === "string" &&
|
|
210
|
+
entry.note.trim() !== ""
|
|
211
|
+
);
|
|
212
|
+
}
|