@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
|
@@ -326,6 +326,12 @@ export function run(command, args, options = {}) {
|
|
|
326
326
|
encoding: "utf8",
|
|
327
327
|
env: options.env ?? process.env,
|
|
328
328
|
input: options.input,
|
|
329
|
+
// Node's default is 1 MiB, and it is silent in exactly the wrong way: an
|
|
330
|
+
// over-large stdout sets `error` while leaving `status` null, so a caller
|
|
331
|
+
// passing `allowFailure` and testing `status !== 0` reads "the command
|
|
332
|
+
// returned nothing" when what happened is "the command was cut off". A
|
|
333
|
+
// caller that needs more says so; the default stays.
|
|
334
|
+
maxBuffer: options.maxBuffer,
|
|
329
335
|
timeout: options.timeout ?? CHILD_TIMEOUT_MS,
|
|
330
336
|
// SIGKILL, not the default SIGTERM. `timeout` sends killSignal and then
|
|
331
337
|
// keeps waiting for the child to exit, so a child that traps or ignores
|
|
@@ -1837,11 +1843,74 @@ function carriesMarker(body) {
|
|
|
1837
1843
|
return JSON.stringify(body ?? "").includes(MARKER);
|
|
1838
1844
|
}
|
|
1839
1845
|
|
|
1846
|
+
/**
|
|
1847
|
+
* Lisa's managed backlink comments, split by whether one is THIS pull request's.
|
|
1848
|
+
*
|
|
1849
|
+
* The whole defect this exists to remove is in the word "the": the writers used
|
|
1850
|
+
* to look for *a* managed comment and rewrite it, so a work item with a second
|
|
1851
|
+
* pull request against it had the first one's backlink silently repointed. The
|
|
1852
|
+
* earlier pull request then failed a gate it had already passed, for a reason
|
|
1853
|
+
* that was not in front of anyone looking at it, and the tool said `updated` —
|
|
1854
|
+
* which reads as "added a link", not "took someone else's away".
|
|
1855
|
+
*
|
|
1856
|
+
* `[lisa-pr-link]` is a statement about a PAIR: *this pull request is linked to
|
|
1857
|
+
* this item*. Storing a pair in a single slot means every new pair evicts the
|
|
1858
|
+
* last, so the comment is keyed on the pull request it names and a work item
|
|
1859
|
+
* carries one per pull request. Nothing here needs a migration: an item holding
|
|
1860
|
+
* one legacy comment is simply an item where `mine` matches or it does not.
|
|
1861
|
+
*
|
|
1862
|
+
* This is deliberately NOT stack-awareness. A fix and its follow-up, a revert
|
|
1863
|
+
* and its replacement, a pull request reopened or recreated against a different
|
|
1864
|
+
* base, and work split across repositories all put two pull requests on one
|
|
1865
|
+
* item, and none of them involve a stack.
|
|
1866
|
+
* @param {readonly unknown[]} comments Comments as the provider returned them.
|
|
1867
|
+
* @param {string} prUrl The pull request being discharged.
|
|
1868
|
+
* @param {(comment: unknown) => unknown} bodyOf Reads a comment's body.
|
|
1869
|
+
* @returns {{mine: unknown, others: number}} This PR's comment, and how many
|
|
1870
|
+
* managed comments belong to OTHER pull requests.
|
|
1871
|
+
*/
|
|
1872
|
+
function partitionBacklinks(comments, prUrl, bodyOf) {
|
|
1873
|
+
const mine = [];
|
|
1874
|
+
let others = 0;
|
|
1875
|
+
for (const comment of comments) {
|
|
1876
|
+
const body = bodyOf(comment);
|
|
1877
|
+
if (!carriesMarker(body)) continue;
|
|
1878
|
+
if (textContainsBacklink(body, prUrl)) mine.push(comment);
|
|
1879
|
+
else others += 1;
|
|
1880
|
+
}
|
|
1881
|
+
return { mine: mine[0], others };
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
/**
|
|
1885
|
+
* Say what a backlink write did, including what it did NOT touch.
|
|
1886
|
+
*
|
|
1887
|
+
* The sibling count is reported rather than left implicit because a tool that
|
|
1888
|
+
* writes to a shared surface should say what else is on it. `updated` was the
|
|
1889
|
+
* only signal the old writer gave before it evicted another pull request's
|
|
1890
|
+
* link, and one word is not enough to distinguish "added" from "replaced".
|
|
1891
|
+
* Exported so the wording can be asserted without a tracker.
|
|
1892
|
+
* @param {{change: string, others: number}} outcome What the writer did.
|
|
1893
|
+
* @param {string} ref Canonical work-item reference.
|
|
1894
|
+
* @param {string} prUrl Pull request URL.
|
|
1895
|
+
* @returns {string} The operator-readable report.
|
|
1896
|
+
*/
|
|
1897
|
+
export function backlinkReport(outcome, ref, prUrl) {
|
|
1898
|
+
const line = `work-item backlink ${outcome.change} on ${ref}: ${MARKER} ${prUrl}`;
|
|
1899
|
+
if (!outcome.others) return line;
|
|
1900
|
+
const plural = outcome.others === 1 ? "" : "s";
|
|
1901
|
+
return (
|
|
1902
|
+
`${line}\n ${outcome.others} other pull request${plural} already linked ` +
|
|
1903
|
+
`to ${ref}, left untouched — an item carries one backlink per pull ` +
|
|
1904
|
+
`request, and discharging one never removes another.`
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1840
1908
|
/**
|
|
1841
1909
|
* Establish the backlink on a GitHub issue.
|
|
1842
1910
|
* @param {string} ref Canonical `owner/repo#number` reference.
|
|
1843
1911
|
* @param {string} prUrl Pull request URL.
|
|
1844
|
-
* @returns {string} What changed
|
|
1912
|
+
* @returns {{change: string, others: number}} What changed, and how many other
|
|
1913
|
+
* pull requests are linked to this item and were left alone.
|
|
1845
1914
|
*/
|
|
1846
1915
|
function githubBacklink(ref, prUrl) {
|
|
1847
1916
|
const [repository, number] = ref.split("#");
|
|
@@ -1856,18 +1925,26 @@ function githubBacklink(ref, prUrl) {
|
|
|
1856
1925
|
);
|
|
1857
1926
|
if (listing.status !== 0) throw githubFailure(listing, ref);
|
|
1858
1927
|
const comments = safeJson(listing.stdout, `GitHub comments on ${ref}`);
|
|
1859
|
-
|
|
1860
|
-
|
|
1928
|
+
// This listing is the read-before-write, and it is fetched here rather than
|
|
1929
|
+
// passed in for exactly that reason: the version you last wrote is not the
|
|
1930
|
+
// version that is live on a surface other agents also comment on.
|
|
1931
|
+
const { mine, others } = partitionBacklinks(
|
|
1932
|
+
Array.isArray(comments) ? comments : [],
|
|
1933
|
+
prUrl,
|
|
1934
|
+
comment => comment?.body
|
|
1861
1935
|
);
|
|
1862
1936
|
const body = backlinkBody(prUrl);
|
|
1863
|
-
if (
|
|
1864
|
-
|
|
1865
|
-
|
|
1937
|
+
if (mine && mine.body === body) return { change: "unchanged", others };
|
|
1938
|
+
// PATCH only ever targets THIS pull request's own comment. A managed comment
|
|
1939
|
+
// naming a different pull request is that pull request's proof of gate 5,
|
|
1940
|
+
// and rewriting it would fail a gate that had already passed.
|
|
1941
|
+
const [method, endpoint] = mine
|
|
1942
|
+
? ["PATCH", `repos/${repository}/issues/comments/${mine.id}`]
|
|
1866
1943
|
: ["POST", `repos/${repository}/issues/${number}/comments`];
|
|
1867
1944
|
run("gh", ["api", "--method", method, endpoint, "--field", `body=${body}`], {
|
|
1868
1945
|
error: `could not write the backlink comment on ${ref}`,
|
|
1869
1946
|
});
|
|
1870
|
-
return
|
|
1947
|
+
return { change: mine ? "updated" : "created", others };
|
|
1871
1948
|
}
|
|
1872
1949
|
|
|
1873
1950
|
/**
|
|
@@ -1906,7 +1983,8 @@ function linearGraphql(token, query, variables, context) {
|
|
|
1906
1983
|
* @param {string} ref Canonical `KEY-123` identifier.
|
|
1907
1984
|
* @param {string} prUrl Pull request URL.
|
|
1908
1985
|
* @param {object} contract Resolved tracker contract.
|
|
1909
|
-
* @returns {string} What changed
|
|
1986
|
+
* @returns {{change: string, others: number}} What changed, and how many other
|
|
1987
|
+
* pull requests are linked to this item and were left alone.
|
|
1910
1988
|
*/
|
|
1911
1989
|
function linearBacklink(ref, prUrl, contract) {
|
|
1912
1990
|
const token = readLinearKey(contract.workspace);
|
|
@@ -1930,19 +2008,21 @@ function linearBacklink(ref, prUrl, contract) {
|
|
|
1930
2008
|
throw new TrackingError(
|
|
1931
2009
|
`Linear issue ${ref} does not exist or is inaccessible`
|
|
1932
2010
|
);
|
|
1933
|
-
const
|
|
1934
|
-
|
|
2011
|
+
const { mine, others } = partitionBacklinks(
|
|
2012
|
+
issue.comments?.nodes ?? [],
|
|
2013
|
+
prUrl,
|
|
2014
|
+
comment => comment?.body
|
|
1935
2015
|
);
|
|
1936
2016
|
const body = backlinkBody(prUrl);
|
|
1937
|
-
if (
|
|
1938
|
-
if (
|
|
2017
|
+
if (mine && mine.body === body) return { change: "unchanged", others };
|
|
2018
|
+
if (mine) {
|
|
1939
2019
|
linearGraphql(
|
|
1940
2020
|
token,
|
|
1941
2021
|
"mutation($id:String!,$body:String!){commentUpdate(id:$id,input:{body:$body}){success}}",
|
|
1942
|
-
{ body, id:
|
|
2022
|
+
{ body, id: mine.id },
|
|
1943
2023
|
`Linear backlink update on ${ref}`
|
|
1944
2024
|
);
|
|
1945
|
-
return "updated";
|
|
2025
|
+
return { change: "updated", others };
|
|
1946
2026
|
}
|
|
1947
2027
|
linearGraphql(
|
|
1948
2028
|
token,
|
|
@@ -1950,7 +2030,7 @@ function linearBacklink(ref, prUrl, contract) {
|
|
|
1950
2030
|
{ body, id: issue.id },
|
|
1951
2031
|
`Linear backlink comment on ${ref}`
|
|
1952
2032
|
);
|
|
1953
|
-
return "created";
|
|
2033
|
+
return { change: "created", others };
|
|
1954
2034
|
}
|
|
1955
2035
|
|
|
1956
2036
|
/**
|
|
@@ -1985,7 +2065,8 @@ function jiraCommentDocument(prUrl) {
|
|
|
1985
2065
|
* @param {string} ref Canonical `KEY-123` reference.
|
|
1986
2066
|
* @param {string} prUrl Pull request URL.
|
|
1987
2067
|
* @param {object} contract Resolved tracker contract.
|
|
1988
|
-
* @returns {string} What changed
|
|
2068
|
+
* @returns {{change: string, others: number}} What changed, and how many other
|
|
2069
|
+
* pull requests are linked to this item and were left alone.
|
|
1989
2070
|
*/
|
|
1990
2071
|
function jiraBacklink(ref, prUrl, contract) {
|
|
1991
2072
|
const credentials = jiraCredentials(contract);
|
|
@@ -2004,24 +2085,26 @@ function jiraBacklink(ref, prUrl, contract) {
|
|
|
2004
2085
|
if (listing.status !== 0)
|
|
2005
2086
|
throw new TrackingError(`Jira ticket ${ref} comments are inaccessible`);
|
|
2006
2087
|
const existing = safeJson(listing.stdout, `Jira comments on ${ref}`);
|
|
2007
|
-
const
|
|
2008
|
-
|
|
2088
|
+
const { mine, others } = partitionBacklinks(
|
|
2089
|
+
existing.comments ?? [],
|
|
2090
|
+
prUrl,
|
|
2091
|
+
comment => comment?.body
|
|
2009
2092
|
);
|
|
2010
2093
|
const document = jiraCommentDocument(prUrl);
|
|
2011
2094
|
const payload = JSON.stringify({ body: document });
|
|
2012
|
-
if (
|
|
2013
|
-
return "unchanged";
|
|
2095
|
+
if (mine && JSON.stringify(mine.body) === JSON.stringify(document))
|
|
2096
|
+
return { change: "unchanged", others };
|
|
2014
2097
|
secureCurl(
|
|
2015
|
-
[
|
|
2098
|
+
[mine ? `${issueUrl}/${encodeURIComponent(mine.id)}` : issueUrl],
|
|
2016
2099
|
[
|
|
2017
2100
|
...auth,
|
|
2018
|
-
["request",
|
|
2101
|
+
["request", mine ? "PUT" : "POST"],
|
|
2019
2102
|
["header", CONTENT_TYPE_JSON],
|
|
2020
2103
|
[DATA_BINARY, payload],
|
|
2021
2104
|
],
|
|
2022
2105
|
{ error: `could not write the backlink comment on ${ref}` }
|
|
2023
2106
|
);
|
|
2024
|
-
return
|
|
2107
|
+
return { change: mine ? "updated" : "created", others };
|
|
2025
2108
|
}
|
|
2026
2109
|
|
|
2027
2110
|
/**
|
|
@@ -2039,7 +2122,8 @@ function jiraBacklink(ref, prUrl, contract) {
|
|
|
2039
2122
|
* @param {string} ref Canonical work-item reference.
|
|
2040
2123
|
* @param {string} prUrl Pull request URL.
|
|
2041
2124
|
* @param {object} contract Resolved tracker contract.
|
|
2042
|
-
* @returns {string} What changed
|
|
2125
|
+
* @returns {{change: string, others: number}} What changed, and how many other
|
|
2126
|
+
* pull requests are linked to this item and were left alone.
|
|
2043
2127
|
*/
|
|
2044
2128
|
function postBacklink(ref, prUrl, contract) {
|
|
2045
2129
|
if (contract.provider === "github") return githubBacklink(ref, prUrl);
|
|
@@ -2442,6 +2526,74 @@ function remoteDefaultRef(remote) {
|
|
|
2442
2526
|
return exists.status === 0 ? target : undefined;
|
|
2443
2527
|
}
|
|
2444
2528
|
|
|
2529
|
+
/**
|
|
2530
|
+
* The scope `validate-push` reports when it never saw the pushed refs at all.
|
|
2531
|
+
*
|
|
2532
|
+
* Named rather than left blank because this is the exact case #3874 measured:
|
|
2533
|
+
* with no refs to read, the range falls back to the PUSHER'S `HEAD` — which on
|
|
2534
|
+
* a cross-worktree push is a different branch than the one being pushed. The
|
|
2535
|
+
* answer computed there is accurate about the local checkout and says nothing
|
|
2536
|
+
* whatsoever about what is leaving the machine, so the verdict has to carry the
|
|
2537
|
+
* scope it was computed over or a reader cannot tell the two apart.
|
|
2538
|
+
*/
|
|
2539
|
+
const HEAD_FALLBACK_SCOPE =
|
|
2540
|
+
"local HEAD (the pushed refs were not readable, so this is NOT the pushed range)";
|
|
2541
|
+
|
|
2542
|
+
/**
|
|
2543
|
+
* An object id, short enough to read and long enough to be unambiguous.
|
|
2544
|
+
* @param {string} oid A 40-character object id.
|
|
2545
|
+
* @returns {string} The first twelve characters.
|
|
2546
|
+
*/
|
|
2547
|
+
function abbreviate(oid) {
|
|
2548
|
+
return oid.slice(0, 12);
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
/**
|
|
2552
|
+
* The pushed refs, from wherever this invocation can actually reach them.
|
|
2553
|
+
*
|
|
2554
|
+
* READING THE STREAM SPENDS IT, and the pre-push hook has several readers: the
|
|
2555
|
+
* deletion guard, the destination guard, every declared gate, and this. The
|
|
2556
|
+
* hook therefore captures git's stdin to a file once and names it in
|
|
2557
|
+
* `LISA_PUSHED_REFS_FILE`, which is the only source that survives a gate having
|
|
2558
|
+
* already drained the stream — and the gate runner is exactly where this runs
|
|
2559
|
+
* on a project that declares `gates.traceability`.
|
|
2560
|
+
*
|
|
2561
|
+
* Stdin stays the fallback so driving the subcommand by hand with a pipe still
|
|
2562
|
+
* works, and an unreadable file falls through to it rather than failing: a
|
|
2563
|
+
* scratch file that vanished must not turn a push into an error about
|
|
2564
|
+
* plumbing.
|
|
2565
|
+
* @param {string|undefined} refsFile Path named by `--refs` or the environment.
|
|
2566
|
+
* @returns {string} The raw stream, possibly empty.
|
|
2567
|
+
*/
|
|
2568
|
+
function readPushRefs(refsFile) {
|
|
2569
|
+
if (refsFile !== undefined) {
|
|
2570
|
+
try {
|
|
2571
|
+
return readFileSync(refsFile, "utf8");
|
|
2572
|
+
} catch {
|
|
2573
|
+
// Fall through to stdin below.
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
try {
|
|
2577
|
+
return readFileSync(0, "utf8");
|
|
2578
|
+
} catch {
|
|
2579
|
+
return "";
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
/**
|
|
2584
|
+
* The scope clause every push verdict carries, pass or fail.
|
|
2585
|
+
*
|
|
2586
|
+
* A green that does not say what it looked at is indistinguishable from a green
|
|
2587
|
+
* that looked at the wrong thing, which is the whole of #3874: `0 commit(s)`
|
|
2588
|
+
* printed for a branch carrying a traced commit, because the range came from
|
|
2589
|
+
* the pusher's checkout instead of the pushed ref.
|
|
2590
|
+
* @param {string} scope The range or tree examined.
|
|
2591
|
+
* @returns {string} The clause, ready to append to a verdict line.
|
|
2592
|
+
*/
|
|
2593
|
+
function examined(scope) {
|
|
2594
|
+
return ` [examined ${scope}]`;
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2445
2597
|
/**
|
|
2446
2598
|
* One pushed ref and the commits it introduces, per line git sent.
|
|
2447
2599
|
*
|
|
@@ -2460,7 +2612,8 @@ function remoteDefaultRef(remote) {
|
|
|
2460
2612
|
* the rule is unchanged and now applied to the unit it was always about.
|
|
2461
2613
|
* @param {string} input The pre-push stdin stream.
|
|
2462
2614
|
* @param {string} remote Remote being pushed to.
|
|
2463
|
-
* @returns {{localRef: string|undefined, commits: string[], scope: string[]
|
|
2615
|
+
* @returns {{localRef: string|undefined, commits: string[], scope: string[],
|
|
2616
|
+
* examinedScope: string}[]}
|
|
2464
2617
|
* One entry per ref update that introduces commits. `scope` is the rev-list
|
|
2465
2618
|
* argument vector the commit list came from, kept so a later question about
|
|
2466
2619
|
* the same range — see {@link ancestryUnreachable} — is asked of the range
|
|
@@ -2475,30 +2628,34 @@ function parsePushGroups(input, remote) {
|
|
|
2475
2628
|
for (const line of input.trim().split(/\r?\n/).filter(Boolean)) {
|
|
2476
2629
|
const [localRef, localOid, , remoteOid] = line.trim().split(/\s+/);
|
|
2477
2630
|
if (!localOid || ZERO_OID.test(localOid)) continue;
|
|
2478
|
-
const
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2631
|
+
const existing = Boolean(remoteOid) && !ZERO_OID.test(remoteOid);
|
|
2632
|
+
const args = existing
|
|
2633
|
+
? [
|
|
2634
|
+
"rev-list",
|
|
2635
|
+
`${remoteOid}..${localOid}`,
|
|
2636
|
+
...(defaultRef ? ["--not", defaultRef] : []),
|
|
2637
|
+
]
|
|
2638
|
+
: // New-branch lane: `--remotes=<remote>` is safe HERE because the branch
|
|
2639
|
+
// being pushed has no remote-tracking ref yet — the exclusion set can
|
|
2640
|
+
// only contain refs that already passed validation on earlier pushes.
|
|
2641
|
+
// The existing-branch lane above must NOT use it (its tracking ref is
|
|
2642
|
+
// pusher-controlled); it excludes only the remote default branch.
|
|
2643
|
+
["rev-list", localOid, "--not", `--remotes=${remote}`];
|
|
2491
2644
|
groups.push({
|
|
2492
|
-
localRef,
|
|
2493
2645
|
commits: git(args).split("\n").filter(Boolean),
|
|
2646
|
+
examinedScope: existing
|
|
2647
|
+
? `${localRef} ${abbreviate(remoteOid)}..${abbreviate(localOid)}`
|
|
2648
|
+
: `${localRef} (new branch) ${abbreviate(localOid)}`,
|
|
2649
|
+
localRef,
|
|
2494
2650
|
scope: args,
|
|
2495
2651
|
});
|
|
2496
2652
|
}
|
|
2497
2653
|
if (groups.length === 0 && input.trim() === "") {
|
|
2498
2654
|
const args = ["rev-list", "HEAD", "--not", `--remotes=${remote}`];
|
|
2499
2655
|
groups.push({
|
|
2500
|
-
localRef: undefined,
|
|
2501
2656
|
commits: git(args).split("\n").filter(Boolean),
|
|
2657
|
+
examinedScope: HEAD_FALLBACK_SCOPE,
|
|
2658
|
+
localRef: undefined,
|
|
2502
2659
|
scope: args,
|
|
2503
2660
|
});
|
|
2504
2661
|
}
|
|
@@ -2783,6 +2940,14 @@ function collect(findings, scope, gate, check) {
|
|
|
2783
2940
|
* ticket id in it can never acquire one — that requirement is unreachable from
|
|
2784
2941
|
* inside the pull request and the message has to say so, rather than leaving a
|
|
2785
2942
|
* reader to try edits that cannot work.
|
|
2943
|
+
*
|
|
2944
|
+
* The closing sentence used to read "creates that comment or updates the
|
|
2945
|
+
* existing one", and that was the whole problem: the existing one could belong
|
|
2946
|
+
* to a DIFFERENT pull request, so following this printed remedy broke a pull
|
|
2947
|
+
* request that had done nothing wrong (CodySwannGT/lisa#3916). The remedy is
|
|
2948
|
+
* now safe by construction rather than by the reader noticing, and it says so —
|
|
2949
|
+
* a reader who has just been told a gate is unmet needs to know that running
|
|
2950
|
+
* the fix cannot un-fix somebody else.
|
|
2786
2951
|
* @param {string} ref Canonical work-item reference.
|
|
2787
2952
|
* @param {string} prUrl Pull request URL.
|
|
2788
2953
|
* @param {object} contract Resolved tracker contract.
|
|
@@ -2797,7 +2962,10 @@ function backlinkAdvice(ref, prUrl, contract) {
|
|
|
2797
2962
|
`${branchDerived}The one remedy that needs no new branch is the managed comment ` +
|
|
2798
2963
|
`\`${MARKER} ${prUrl}\` on ${ref}. Do not post it by hand — run:\n\n` +
|
|
2799
2964
|
` ${BACKLINK_COMMAND} --ref ${ref} --pr-url ${prUrl}\n\n` +
|
|
2800
|
-
`which creates that comment or updates the
|
|
2965
|
+
`which creates that comment, or updates the one already naming THIS pull ` +
|
|
2966
|
+
`request. A backlink belonging to another pull request on ${ref} is left ` +
|
|
2967
|
+
`alone, so running this twice — or for a second pull request against the ` +
|
|
2968
|
+
`same item — is safe`
|
|
2801
2969
|
);
|
|
2802
2970
|
}
|
|
2803
2971
|
|
|
@@ -3446,8 +3614,72 @@ function backlink(args) {
|
|
|
3446
3614
|
const prUrl = pullRequestUrlOption(args);
|
|
3447
3615
|
if (!prUrl)
|
|
3448
3616
|
throw new TrackingError(`${BACKLINK_COMMAND} requires --pr-url <url>`);
|
|
3449
|
-
|
|
3450
|
-
|
|
3617
|
+
console.log(backlinkReport(postBacklink(ref, prUrl, contract), ref, prUrl));
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
/**
|
|
3621
|
+
* Every work item a commit body DECLARES, by full-body scan.
|
|
3622
|
+
*
|
|
3623
|
+
* A declaration is the commit's own `Work-Item:` line. It is written by the
|
|
3624
|
+
* author of the work, enforced on every non-merge commit by the commit-msg
|
|
3625
|
+
* gate, and says "this commit is that item". A cross-reference says only that
|
|
3626
|
+
* somebody's pull request NAMED the item somewhere, which is a different and
|
|
3627
|
+
* much weaker claim — see `sweepDeclarations` for what that difference cost.
|
|
3628
|
+
*
|
|
3629
|
+
* Scanned with the `m` flag over the WHOLE body rather than read through git's
|
|
3630
|
+
* `%(trailers:key=Work-Item)`, and that is the load-bearing part. Git's trailer
|
|
3631
|
+
* parser only considers the LAST paragraph of a message, and this project's
|
|
3632
|
+
* commits put `Work-Item:` above the trailing co-author block, so the parser
|
|
3633
|
+
* does not see it as a trailer at all. Measured on `origin/main`: the parser
|
|
3634
|
+
* finds 602 distinct issues where a full-body scan finds 835 — it misses 233,
|
|
3635
|
+
* and the undercount is clean, plausible and well-formed, which is why it
|
|
3636
|
+
* survived. Anything in this fleet that counts or verifies trailers has to
|
|
3637
|
+
* scan the body (CodySwannGT/lisa#3859).
|
|
3638
|
+
* @param {string} body A commit message body.
|
|
3639
|
+
* @param {string} repository `owner/name` the item must belong to.
|
|
3640
|
+
* @returns {number[]} Declared issue numbers, de-duplicated, in first-seen order.
|
|
3641
|
+
*/
|
|
3642
|
+
export function declaredWorkItemNumbers(body, repository) {
|
|
3643
|
+
const escaped = String(repository ?? "").replace(
|
|
3644
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
3645
|
+
"\\$&"
|
|
3646
|
+
);
|
|
3647
|
+
if (!escaped) return [];
|
|
3648
|
+
const pattern = new RegExp(
|
|
3649
|
+
`^[ \\t]*Work-Item:[ \\t]*${escaped}#([1-9]\\d*)[ \\t]*$`,
|
|
3650
|
+
"gim"
|
|
3651
|
+
);
|
|
3652
|
+
const numbers = [];
|
|
3653
|
+
let match;
|
|
3654
|
+
while ((match = pattern.exec(String(body ?? ""))) !== null)
|
|
3655
|
+
numbers.push(Number(match[1]));
|
|
3656
|
+
return [...new Set(numbers)];
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
/**
|
|
3660
|
+
* Work items declared by commits in a `git log` transcript.
|
|
3661
|
+
*
|
|
3662
|
+
* Pure over the transcript text so the whole attribution can be asserted
|
|
3663
|
+
* without a repository. Records are NUL-separated (`git log -z`) because a
|
|
3664
|
+
* commit body contains newlines and blank lines by construction, and any
|
|
3665
|
+
* newline-based framing would split one commit into several.
|
|
3666
|
+
* @param {string} logText Output of `git log -z --format=%H%n%B`.
|
|
3667
|
+
* @param {string} repository `owner/name` the item must belong to.
|
|
3668
|
+
* @returns {Map<number, string[]>} Issue number to the commits declaring it.
|
|
3669
|
+
*/
|
|
3670
|
+
export function shippedDeclarations(logText, repository) {
|
|
3671
|
+
const declarations = new Map();
|
|
3672
|
+
for (const record of String(logText ?? "").split("\0")) {
|
|
3673
|
+
if (record.trim() === "") continue;
|
|
3674
|
+
const newline = record.indexOf("\n");
|
|
3675
|
+
const sha = (newline === -1 ? record : record.slice(0, newline)).trim();
|
|
3676
|
+
const body = newline === -1 ? "" : record.slice(newline + 1);
|
|
3677
|
+
for (const number of declaredWorkItemNumbers(body, repository)) {
|
|
3678
|
+
if (!declarations.has(number)) declarations.set(number, []);
|
|
3679
|
+
declarations.get(number).push(sha);
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
return declarations;
|
|
3451
3683
|
}
|
|
3452
3684
|
|
|
3453
3685
|
/**
|
|
@@ -4210,17 +4442,42 @@ function complete(args) {
|
|
|
4210
4442
|
* Reports by default and only acts under `--apply`, because a sweep that closes
|
|
4211
4443
|
* things as a side effect of being run is not something anyone will run twice.
|
|
4212
4444
|
*/
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4445
|
+
/**
|
|
4446
|
+
* Buffer bound for a deploy branch's commit log.
|
|
4447
|
+
*
|
|
4448
|
+
* Sized against measurement rather than taste: `origin/main` here is 4.6 MiB
|
|
4449
|
+
* of `%H%n%B` across ~1,600 commits, so 64 MiB leaves an order of magnitude of
|
|
4450
|
+
* headroom and still bounds a runaway. It is a ceiling, not a target — and
|
|
4451
|
+
* exceeding it now refuses loudly instead of reading as "no drift".
|
|
4452
|
+
*/
|
|
4453
|
+
const DECLARATION_LOG_MAX_BYTES = 64 * 1024 * 1024;
|
|
4454
|
+
|
|
4455
|
+
/**
|
|
4456
|
+
* The lifecycle roles the sweep examines, in report order.
|
|
4457
|
+
*
|
|
4458
|
+
* Both open lanes, not just the claimed one. An item whose work shipped while
|
|
4459
|
+
* it still carried the ready role is structurally invisible to a claimed-only
|
|
4460
|
+
* subject list — the flow never claimed it, or the claim was reverted, or the
|
|
4461
|
+
* pull request came from a session that never transitioned it. Measured on
|
|
4462
|
+
* this repository: of 122 open ready items, 11 had commits on `origin/main`
|
|
4463
|
+
* carrying their `Work-Item:` trailer, and the sweep reported none of them in
|
|
4464
|
+
* either mode (CodySwannGT/lisa#3907).
|
|
4465
|
+
* @param {object} lifecycle Resolved lifecycle contract.
|
|
4466
|
+
* @returns {string[]} Role names to query, de-duplicated.
|
|
4467
|
+
*/
|
|
4468
|
+
function sweptRoles(lifecycle) {
|
|
4469
|
+
return [...new Set([lifecycle.ready, lifecycle.claimed])].filter(
|
|
4470
|
+
role => typeof role === "string" && role !== ""
|
|
4471
|
+
);
|
|
4472
|
+
}
|
|
4473
|
+
|
|
4474
|
+
/**
|
|
4475
|
+
* Open issues carrying one lifecycle role.
|
|
4476
|
+
* @param {string} repository `owner/name` to list from.
|
|
4477
|
+
* @param {string} role The label to filter by.
|
|
4478
|
+
* @returns {object[]} Issues with `number` and `title`.
|
|
4479
|
+
*/
|
|
4480
|
+
function openIssuesWithRole(repository, role) {
|
|
4224
4481
|
const listing = run(
|
|
4225
4482
|
"gh",
|
|
4226
4483
|
[
|
|
@@ -4231,7 +4488,7 @@ function sweep(args) {
|
|
|
4231
4488
|
"--state",
|
|
4232
4489
|
"open",
|
|
4233
4490
|
"--label",
|
|
4234
|
-
|
|
4491
|
+
role,
|
|
4235
4492
|
"--limit",
|
|
4236
4493
|
"200",
|
|
4237
4494
|
"--json",
|
|
@@ -4241,33 +4498,157 @@ function sweep(args) {
|
|
|
4241
4498
|
);
|
|
4242
4499
|
if (listing.status !== 0) throw githubFailure(listing, repository);
|
|
4243
4500
|
const issues = safeJson(listing.stdout, "GitHub issue listing");
|
|
4501
|
+
return Array.isArray(issues) ? issues : [];
|
|
4502
|
+
}
|
|
4503
|
+
|
|
4504
|
+
/**
|
|
4505
|
+
* The first of `origin/<branch>` and `<branch>` that resolves to a commit.
|
|
4506
|
+
* @param {string} branch Configured deploy branch name.
|
|
4507
|
+
* @returns {string|undefined} A resolvable rev, or undefined.
|
|
4508
|
+
*/
|
|
4509
|
+
function resolvedBranchRev(branch) {
|
|
4510
|
+
for (const candidate of [`origin/${branch}`, branch]) {
|
|
4511
|
+
const result = run(
|
|
4512
|
+
"git",
|
|
4513
|
+
["rev-parse", "--verify", "--quiet", `${candidate}^{commit}`],
|
|
4514
|
+
{ allowFailure: true }
|
|
4515
|
+
);
|
|
4516
|
+
if (result.status === 0 && result.stdout.trim() !== "") return candidate;
|
|
4517
|
+
}
|
|
4518
|
+
return undefined;
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4521
|
+
/**
|
|
4522
|
+
* Work items DECLARED by commits reachable from a deploy branch.
|
|
4523
|
+
*
|
|
4524
|
+
* This is the sweep's evidence, and it replaced a timeline cross-reference
|
|
4525
|
+
* scan. The difference is not academic. A cross-reference is created by any
|
|
4526
|
+
* pull request that so much as NAMES `#N` — so one merged land-stack pull
|
|
4527
|
+
* request made every sibling in its batch look shipped, and a single pull
|
|
4528
|
+
* request was counted as evidence for several unrelated items at once.
|
|
4529
|
+
* Measured before this change: of 53 items the sweep reported as drifted, 8
|
|
4530
|
+
* had no commit on `main` declaring them at all — 15%, and every one of the 8
|
|
4531
|
+
* had a live worktree branch open against it. Applying the sweep would have
|
|
4532
|
+
* closed eight in-flight work items, and by this file's own words the closure
|
|
4533
|
+
* "would be indistinguishable from a real one afterwards".
|
|
4534
|
+
*
|
|
4535
|
+
* Reachability is also what confirms the work actually landed, rather than
|
|
4536
|
+
* trusting a pull request's `baseRefName` to say where it went.
|
|
4537
|
+
* @param {string} repository `owner/name` the items belong to.
|
|
4538
|
+
* @param {object} contract Resolved tracker contract.
|
|
4539
|
+
* @returns {Map<number, string[]>} Issue number to declaring commits.
|
|
4540
|
+
*/
|
|
4541
|
+
function deployedDeclarations(repository, contract) {
|
|
4542
|
+
const declarations = new Map();
|
|
4543
|
+
for (const branch of contract.deployBranches.keys()) {
|
|
4544
|
+
const rev = resolvedBranchRev(branch);
|
|
4545
|
+
if (!rev) continue;
|
|
4546
|
+
const result = run("git", ["log", rev, "-z", "--format=%H%n%B"], {
|
|
4547
|
+
allowFailure: true,
|
|
4548
|
+
maxBuffer: DECLARATION_LOG_MAX_BYTES,
|
|
4549
|
+
});
|
|
4550
|
+
// Loudly, not `continue`. This sweep's whole output is an absence claim,
|
|
4551
|
+
// and a read that failed is not an absence — under the default 1 MiB
|
|
4552
|
+
// buffer the real `origin/main` log (4.6 MiB) was truncated into an
|
|
4553
|
+
// `error` with a null status, and the run reported "No drift" over 194
|
|
4554
|
+
// items while having read nothing. Refusing is the only safe answer,
|
|
4555
|
+
// because the alternative is the tool's most reassuring output being its
|
|
4556
|
+
// least trustworthy one.
|
|
4557
|
+
if (result.error || result.status !== 0) {
|
|
4558
|
+
throw new TrackingError(
|
|
4559
|
+
`could not read the commit log of ${rev}, so no absence of drift can be reported: ` +
|
|
4560
|
+
`${result.error ? result.error.message : result.stderr.trim() || "git log failed"}.\n` +
|
|
4561
|
+
`The sweep reports what a deploy branch declares; a log it could not read is not an empty one.`
|
|
4562
|
+
);
|
|
4563
|
+
}
|
|
4564
|
+
for (const [number, shas] of shippedDeclarations(
|
|
4565
|
+
result.stdout,
|
|
4566
|
+
repository
|
|
4567
|
+
)) {
|
|
4568
|
+
declarations.set(number, [
|
|
4569
|
+
...new Set([...(declarations.get(number) ?? []), ...shas]),
|
|
4570
|
+
]);
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
return declarations;
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4576
|
+
/**
|
|
4577
|
+
* Render the commits that declare an item, without printing a wall of hashes.
|
|
4578
|
+
* @param {string[]} shas Declaring commits.
|
|
4579
|
+
* @returns {string} A short, readable citation.
|
|
4580
|
+
*/
|
|
4581
|
+
function describeDeclarations(shas) {
|
|
4582
|
+
const shown = shas.slice(0, 3).map(sha => sha.slice(0, 9));
|
|
4583
|
+
const rest = shas.length - shown.length;
|
|
4584
|
+
return rest > 0 ? `${shown.join(", ")}, +${rest} more` : shown.join(", ");
|
|
4585
|
+
}
|
|
4586
|
+
|
|
4587
|
+
function sweep(args) {
|
|
4588
|
+
const contract = trackerContract();
|
|
4589
|
+
if (contract.provider !== "github") {
|
|
4590
|
+
throw new TrackingError(
|
|
4591
|
+
`no sweep for tracker '${contract.provider}'; only github is supported so far.`
|
|
4592
|
+
);
|
|
4593
|
+
}
|
|
4594
|
+
const repository = currentRepository();
|
|
4595
|
+
if (!repository)
|
|
4596
|
+
throw new TrackingError("could not resolve the current GitHub repository");
|
|
4597
|
+
const roles = sweptRoles(contract.lifecycle);
|
|
4598
|
+
const subjects = new Map();
|
|
4599
|
+
for (const role of roles) {
|
|
4600
|
+
for (const issue of openIssuesWithRole(repository, role)) {
|
|
4601
|
+
const existing = subjects.get(issue.number);
|
|
4602
|
+
if (existing) existing.roles.push(role);
|
|
4603
|
+
else subjects.set(issue.number, { ...issue, roles: [role] });
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
const declarations = deployedDeclarations(repository, contract);
|
|
4244
4607
|
const apply = args.includes("--apply");
|
|
4608
|
+
// Named in every report, clean or not. The old clean-result sentence spoke
|
|
4609
|
+
// only of the claimed role while the subject list excluded the ready lane
|
|
4610
|
+
// entirely — a true sentence that answered a narrower question than the one
|
|
4611
|
+
// its reader was asking. An instrument that reports an absence has to say
|
|
4612
|
+
// what it looked at.
|
|
4613
|
+
const examined = roles.map(role => `"${role}"`).join(" and ");
|
|
4245
4614
|
let drifted = 0;
|
|
4246
|
-
for (const issue of
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4615
|
+
for (const issue of [...subjects.values()].sort(
|
|
4616
|
+
(left, right) => right.number - left.number
|
|
4617
|
+
)) {
|
|
4618
|
+
const shas = declarations.get(issue.number) ?? [];
|
|
4619
|
+
if (shas.length === 0) continue;
|
|
4250
4620
|
drifted += 1;
|
|
4251
|
-
const
|
|
4621
|
+
const ref = `${repository}#${issue.number}`;
|
|
4252
4622
|
if (!apply) {
|
|
4253
|
-
console.log(
|
|
4623
|
+
console.log(
|
|
4624
|
+
`DRIFT ${ref} declared by: ${describeDeclarations(shas)} [${issue.roles.join(", ")}] ${issue.title}`
|
|
4625
|
+
);
|
|
4254
4626
|
continue;
|
|
4255
4627
|
}
|
|
4256
|
-
|
|
4257
|
-
//
|
|
4258
|
-
//
|
|
4259
|
-
// the backstop safe to run over a whole queue.
|
|
4260
|
-
|
|
4628
|
+
// A sweep must not abort on an item the applying arm refuses — whether it
|
|
4629
|
+
// refuses because the merge reached no deploy branch, or because the
|
|
4630
|
+
// tracker rejected the write. It reports that item and carries on, which
|
|
4631
|
+
// is what makes the backstop safe to run over a whole queue.
|
|
4632
|
+
try {
|
|
4633
|
+
console.log(completeWorkItem(ref, contract).report);
|
|
4634
|
+
} catch (error) {
|
|
4635
|
+
console.log(
|
|
4636
|
+
`work-item NOT completed: ${ref}\n${error instanceof Error ? error.message : String(error)}`
|
|
4637
|
+
);
|
|
4638
|
+
}
|
|
4261
4639
|
}
|
|
4262
4640
|
if (drifted === 0) {
|
|
4263
4641
|
console.log(
|
|
4264
|
-
`No drift: every item carrying
|
|
4642
|
+
`No drift: every open item carrying ${examined} is genuinely in flight.\n` +
|
|
4643
|
+
`Examined ${subjects.size} item(s) across ${roles.length} lifecycle role(s); ` +
|
|
4644
|
+
`no role outside ${examined} was queried.`
|
|
4265
4645
|
);
|
|
4266
4646
|
return;
|
|
4267
4647
|
}
|
|
4268
4648
|
if (!apply) {
|
|
4269
4649
|
console.log(
|
|
4270
|
-
`\n${drifted}
|
|
4650
|
+
`\n${drifted} open item(s) carrying ${examined} are declared by a commit on a deploy branch. ` +
|
|
4651
|
+
`Re-run with --apply to complete them.`
|
|
4271
4652
|
);
|
|
4272
4653
|
}
|
|
4273
4654
|
}
|
|
@@ -4327,7 +4708,8 @@ function validateCommit(args) {
|
|
|
4327
4708
|
* that answer into silence.
|
|
4328
4709
|
* @param {string} input The pre-push stdin stream.
|
|
4329
4710
|
* @param {string} remote Remote being pushed to.
|
|
4330
|
-
* @returns {{localRef: string|undefined, commits: string[], scope: string[]
|
|
4711
|
+
* @returns {{localRef: string|undefined, commits: string[], scope: string[],
|
|
4712
|
+
* examinedScope: string}[]}
|
|
4331
4713
|
* Groups to check.
|
|
4332
4714
|
*/
|
|
4333
4715
|
function pushGroups(input, remote) {
|
|
@@ -4335,7 +4717,17 @@ function pushGroups(input, remote) {
|
|
|
4335
4717
|
const carrying = parsed.filter(group => group.commits.length > 0);
|
|
4336
4718
|
return carrying.length > 0
|
|
4337
4719
|
? carrying
|
|
4338
|
-
: [
|
|
4720
|
+
: [
|
|
4721
|
+
{
|
|
4722
|
+
commits: [],
|
|
4723
|
+
examinedScope: parsed[0]?.examinedScope ?? HEAD_FALLBACK_SCOPE,
|
|
4724
|
+
localRef: parsed[0]?.localRef,
|
|
4725
|
+
// Empty on purpose: `ancestryUnreachable` asks a question OF A RANGE,
|
|
4726
|
+
// and there is no range here. `examinedScope` above is the separate,
|
|
4727
|
+
// human-readable answer to "what did this verdict look at".
|
|
4728
|
+
scope: [],
|
|
4729
|
+
},
|
|
4730
|
+
];
|
|
4339
4731
|
}
|
|
4340
4732
|
|
|
4341
4733
|
/**
|
|
@@ -4495,9 +4887,14 @@ function pushDeferralNote(result) {
|
|
|
4495
4887
|
* the WIRING and the direct cases prove the WORDS.
|
|
4496
4888
|
* @param {object} result Commit-side result.
|
|
4497
4889
|
* @param {string} label Prefix naming the ref, empty for a single-ref push.
|
|
4890
|
+
* @param {string} [scope] The range or tree this verdict was computed over.
|
|
4498
4891
|
* @returns {string} The whole report, replacing the success line.
|
|
4499
4892
|
*/
|
|
4500
|
-
export function unresolvedPushReport(
|
|
4893
|
+
export function unresolvedPushReport(
|
|
4894
|
+
result,
|
|
4895
|
+
label,
|
|
4896
|
+
scope = HEAD_FALLBACK_SCOPE
|
|
4897
|
+
) {
|
|
4501
4898
|
const contract = result.contract;
|
|
4502
4899
|
const refs = result.refs ?? [];
|
|
4503
4900
|
const declaration = refs.map(ref => `Work-Item: ${ref}`).join("\n ");
|
|
@@ -4506,7 +4903,7 @@ export function unresolvedPushReport(result, label) {
|
|
|
4506
4903
|
? ` UNRESOLVED gate 5 — each item needs a managed \`${MARKER}\` backlink comment pointing at the pull request. \`discharge-pr-gates\` posts it; nothing else has to remember to.`
|
|
4507
4904
|
: ` n/a gate 5 — the tracker backlink is not required here: workItem.verify is "trailer".`;
|
|
4508
4905
|
return [
|
|
4509
|
-
`WORK_ITEM_TRACKING_INCOMPLETE ${label}${result.relevant} commit(s)${alreadyTraced(result)}: gates 1-3 proved here, 2 of 5 gates NOT CHECKED.`,
|
|
4906
|
+
`WORK_ITEM_TRACKING_INCOMPLETE ${label}${result.relevant} commit(s)${alreadyTraced(result)}${examined(scope)}: gates 1-3 proved here, 2 of 5 gates NOT CHECKED.`,
|
|
4510
4907
|
` This push is not a clean bill of health for ${refs.join(", ")}. ${pushDeferralNote(result)}`,
|
|
4511
4908
|
` UNRESOLVED gate 4 — the pull-request BODY must carry, on its own line${refs.length > 1 ? "s" : ""}:`,
|
|
4512
4909
|
` ${declaration}`,
|
|
@@ -4546,15 +4943,20 @@ export function pushSuccessLine(result, label) {
|
|
|
4546
4943
|
* @param {{result?: object, error?: Error}} outcome Commit-side outcome.
|
|
4547
4944
|
* @param {object|undefined} pr The pull request this ref is about, if any.
|
|
4548
4945
|
* @param {string} label Prefix naming the ref, empty for a single-ref push.
|
|
4946
|
+
* @param {string} scope The range or tree this verdict was computed over.
|
|
4549
4947
|
*/
|
|
4550
|
-
function reportPushGroup(outcome, pr, label) {
|
|
4948
|
+
function reportPushGroup(outcome, pr, label, scope) {
|
|
4551
4949
|
if (pr) {
|
|
4552
4950
|
// `true`: a push range is ALWAYS a subset of the pull request's — it drops
|
|
4553
4951
|
// commits already on the remote branch and everything reachable from the
|
|
4554
4952
|
// remote default branch. So a commit side with nothing in it means "no
|
|
4555
4953
|
// subject in THIS push", never "no subject in the pull request".
|
|
4556
4954
|
validatePrData(outcome, pr.url, pr.body, true);
|
|
4557
|
-
|
|
4955
|
+
// The scope clause is appended HERE rather than inside `pushSuccessLine`
|
|
4956
|
+
// so that function keeps the two-argument shape #3886's inequality test
|
|
4957
|
+
// calls it with. What it returns is the verdict; what this adds is the
|
|
4958
|
+
// range the verdict was computed over.
|
|
4959
|
+
console.log(`${pushSuccessLine(outcome.result, label)}${examined(scope)}`);
|
|
4558
4960
|
return;
|
|
4559
4961
|
}
|
|
4560
4962
|
// No pull request means gates 4 and 5 cannot be CHECKED here. They are
|
|
@@ -4571,16 +4973,22 @@ function reportPushGroup(outcome, pr, label) {
|
|
|
4571
4973
|
// checklist teaches the reader to skip it.
|
|
4572
4974
|
if ((outcome.result.refs ?? []).length === 0) {
|
|
4573
4975
|
console.log(
|
|
4574
|
-
`WORK_ITEM_TRACKING_OK ${label}${outcome.result.relevant} commit(s)${alreadyTraced(outcome.result)}; this range names no work item, so gates 4 and 5 have nothing to check here
|
|
4976
|
+
`WORK_ITEM_TRACKING_OK ${label}${outcome.result.relevant} commit(s)${alreadyTraced(outcome.result)}; this range names no work item, so gates 4 and 5 have nothing to check here.${examined(scope)}`
|
|
4575
4977
|
);
|
|
4576
4978
|
return;
|
|
4577
4979
|
}
|
|
4578
|
-
console.log(unresolvedPushReport(outcome.result, label));
|
|
4980
|
+
console.log(unresolvedPushReport(outcome.result, label, scope));
|
|
4579
4981
|
}
|
|
4580
4982
|
|
|
4581
4983
|
function validatePush(args) {
|
|
4582
|
-
const remote = args[0]
|
|
4583
|
-
|
|
4984
|
+
const remote = args[0] && !args[0].startsWith("-") ? args[0] : "origin";
|
|
4985
|
+
// `--refs <file>` / `LISA_PUSHED_REFS_FILE` names the captured pre-push
|
|
4986
|
+
// stream, exactly as `validate-push-destination` reads it. Without it this
|
|
4987
|
+
// command is reachable — through the gate runner — only after some earlier
|
|
4988
|
+
// reader has already spent git's stdin, and the empty-stream fallback below
|
|
4989
|
+
// then answers about the PUSHER'S `HEAD` rather than the pushed ref
|
|
4990
|
+
// (CodySwannGT/lisa#3874).
|
|
4991
|
+
const input = readPushRefs(option(args, "--refs", "LISA_PUSHED_REFS_FILE"));
|
|
4584
4992
|
// The deploy chain is read from the remote default branch, the same ref this
|
|
4585
4993
|
// path already trusts to bound the range. A local working tree could declare
|
|
4586
4994
|
// anything, but so could a `--no-verify`; CI is the enforcing copy.
|
|
@@ -4599,7 +5007,8 @@ function validatePush(args) {
|
|
|
4599
5007
|
reportPushGroup(
|
|
4600
5008
|
outcome,
|
|
4601
5009
|
group === target ? pr : undefined,
|
|
4602
|
-
groups.length > 1 ? `${group.localRef ?? "(stdin)"}: ` : ""
|
|
5010
|
+
groups.length > 1 ? `${group.localRef ?? "(stdin)"}: ` : "",
|
|
5011
|
+
group.examinedScope
|
|
4603
5012
|
);
|
|
4604
5013
|
}
|
|
4605
5014
|
}
|
|
@@ -4708,8 +5117,8 @@ export function postDischargeBacklinks(
|
|
|
4708
5117
|
let changed = false;
|
|
4709
5118
|
for (const ref of refs) {
|
|
4710
5119
|
const outcome = post(ref, prUrl, contract);
|
|
4711
|
-
changed ||= outcome !== "unchanged";
|
|
4712
|
-
console.log(
|
|
5120
|
+
changed ||= outcome.change !== "unchanged";
|
|
5121
|
+
console.log(backlinkReport(outcome, ref, prUrl));
|
|
4713
5122
|
}
|
|
4714
5123
|
return changed;
|
|
4715
5124
|
}
|