@danmoisan/drm-copilot-mcp 1.1.22 → 1.1.24
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/out/mcp-server.js +18758 -12030
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/MEMORY.md +1 -1
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_test_files_count_against_500_cap.md +4 -4
- package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +1 -1
- package/resources/claude-customizations/.claude/agents/discovery-capability-sentinel.md +34 -0
- package/resources/claude-customizations/.claude/agents/feature-review.md +8 -0
- package/resources/claude-customizations/.claude/agents/legacy-parity-analyst.md +101 -43
- package/resources/claude-customizations/.claude/agents/migration-coverage-reviewer.md +107 -43
- package/resources/claude-customizations/.claude/agents/orchestrator.md +1 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +60 -6
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +21 -1
- package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +1 -1
- package/resources/claude-customizations/.claude/agents/pr-author.md +48 -7
- package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +1 -1
- package/resources/claude-customizations/.claude/agents/requirements-reconciler.md +117 -42
- package/resources/claude-customizations/.claude/agents/runtime-characterization-analyst.md +103 -43
- package/resources/claude-customizations/.claude/hooks/discovery-capability-probe.ps1 +102 -0
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +69 -194
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-planning-gate.ps1 +87 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-green-ci.ps1 +325 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +29 -7
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +23 -10
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +29 -21
- package/resources/claude-customizations/.claude/hooks/enforce-file-size-limit.ps1 +244 -0
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +51 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +168 -48
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +1 -1
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +22 -11
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +59 -20
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +51 -4
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +72 -11
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.current-branch.ps1 +149 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +4 -2
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +59 -20
- package/resources/claude-customizations/.claude/hooks/record-discovery-agent-start.ps1 +132 -0
- package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +56 -212
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-feature-folder.ps1 +146 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-rejection.ps1 +172 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +34 -4
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +9 -3
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusAggregate.psm1 +154 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusCombination.psm1 +228 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +1 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +63 -16
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +56 -1
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +4 -1
- package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +33 -6
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +5 -1
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +25 -2
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +188 -341
- package/resources/claude-customizations/.claude/lib/file-size-policy/FileSizePolicy.psm1 +434 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateFeatureFolderRecord.psm1 +166 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +8 -3
- package/resources/claude-customizations/.claude/lib/worktree-resolution/HookTargetWorktree.psm1 +391 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunRecordPath.psm1 +185 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +11 -21
- package/resources/claude-customizations/.claude/rules/general-code-change.md +4 -1
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +29 -5
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +152 -20
- package/resources/claude-customizations/.claude/rules/powershell.md +1 -1
- package/resources/claude-customizations/.claude/rules/shell.md +1 -1
- package/resources/claude-customizations/.claude/settings.json +36 -3
- package/resources/claude-customizations/.claude/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/claude-customizations/.claude/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +2 -2
- package/resources/claude-customizations/.claude/skills/discovery-behavior-reconciliation/SKILL.md +28 -47
- package/resources/claude-customizations/.claude/skills/discovery-coverage-ledger/SKILL.md +32 -48
- package/resources/claude-customizations/.claude/skills/discovery-parity-matrix/SKILL.md +28 -45
- package/resources/claude-customizations/.claude/skills/discovery-repo-inventory/SKILL.md +28 -62
- package/resources/claude-customizations/.claude/skills/discovery-runtime-characterization/SKILL.md +28 -45
- package/resources/claude-customizations/.claude/skills/discovery-validate-artifacts/SKILL.md +28 -61
- package/resources/claude-customizations/.claude/skills/discovery-workflow/SKILL.md +55 -139
- package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +25 -10
- package/resources/claude-customizations/.claude/skills/extract-feature-contract/SKILL.md +133 -0
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +1 -0
- package/resources/claude-customizations/.claude/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/claude-customizations/.claude/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +15 -7
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +25 -2
- package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +11 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +124 -14
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +72 -7
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +15 -0
- package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +6 -0
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +56 -9
- package/resources/claude-customizations/.claude/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/claude-customizations/.claude/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +1 -1
- package/resources/claude-customizations/config/blast-radius.json +6 -5
- package/resources/claude-customizations/config/orchestration-routing.json +61 -1
- package/resources/claude-customizations/pack-manifests/core.json +40 -1
- package/resources/codex-and-agents-customizations/.agents/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +7 -0
- package/resources/codex-and-agents-customizations/.agents/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-coverage-ledger/SKILL.md +50 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-parity-matrix/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-repo-inventory/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-runtime-characterization/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-validate-artifacts/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-workflow/SKILL.md +62 -0
- package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +25 -10
- package/resources/codex-and-agents-customizations/.agents/skills/extract-feature-contract/SKILL.md +133 -0
- package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +4 -1
- package/resources/codex-and-agents-customizations/.agents/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/codex-and-agents-customizations/.agents/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/codex-and-agents-customizations/.agents/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/discovery-capability-sentinel.toml +51 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +8 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c1.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c2.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3-elevated.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c4.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c1.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c2.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3-elevated.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c4.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c1.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c2.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3-elevated.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c4.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c1.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c2.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3-elevated.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c4.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +39 -3
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +22 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/discovery-capability-probe.ps1 +97 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/discovery-start-attestation.ps1 +171 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +4 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-artifact-gate.ps1 +187 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-planning-gate.ps1 +149 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-file-size-limit.ps1 +282 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +239 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +54 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +54 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/file-size-policy.ps1 +421 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-target-worktree.ps1 +369 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +8 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +22 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-discovery-agent-output.ps1 +178 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +11 -1
- package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/AGENTS.md +4 -1
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +65 -1
- package/resources/config/orchestration-routing.json +61 -1
- package/resources/customizations/.github/agents/commentary-remediation.agent.md +1 -1
- package/resources/customizations/.github/agents/discovery-capability-sentinel.agent.md +34 -0
- package/resources/customizations/.github/agents/legacy-parity-analyst.agent.md +122 -0
- package/resources/customizations/.github/agents/migration-coverage-reviewer.agent.md +128 -0
- package/resources/customizations/.github/agents/python-execution-only-typed.agent.md +1 -1
- package/resources/customizations/.github/agents/python-typed-engineer.agent.md +1 -1
- package/resources/customizations/.github/agents/requirements-reconciler.agent.md +138 -0
- package/resources/customizations/.github/agents/runtime-characterization-analyst.agent.md +124 -0
- package/resources/customizations/.github/hooks/discovery-agent-start.ps1 +139 -0
- package/resources/customizations/.github/hooks/discovery-agent-stop.ps1 +123 -0
- package/resources/customizations/.github/hooks/discovery-capability-probe.ps1 +98 -0
- package/resources/customizations/.github/hooks/discovery-planning.ps1 +91 -0
- package/resources/customizations/.github/hooks/discovery-pre-tool.ps1 +123 -0
- package/resources/customizations/.github/hooks/discovery.json +35 -0
- package/resources/customizations/.github/instructions/general-code-change.instructions.md +5 -2
- package/resources/customizations/.github/instructions/mermaid.instructions.md +85 -0
- package/resources/customizations/.github/instructions/powershell-code-change.instructions.md +1 -1
- package/resources/customizations/.github/prompts/execute-hard-lock.prompt.md +105 -0
- package/resources/customizations/.github/prompts/remediate-comments.prompt.md +1 -1
- package/resources/customizations/.github/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/customizations/.github/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/customizations/.github/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-coverage-ledger/SKILL.md +50 -0
- package/resources/customizations/.github/skills/discovery-parity-matrix/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-repo-inventory/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-runtime-characterization/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-validate-artifacts/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-workflow/SKILL.md +62 -0
- package/resources/customizations/.github/skills/execution-environment/SKILL.md +25 -10
- package/resources/customizations/.github/skills/extract-feature-contract/SKILL.md +133 -0
- package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +8 -0
- package/resources/customizations/.github/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/customizations/.github/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/customizations/.github/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/customizations/.github/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/customizations/.github/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/customizations/.github/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/lib/codex-routing/CodexDeployment.psm1 +5 -1
- package/resources/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
- package/resources/lib/codex-routing/CodexTopology.psm1 +25 -2
- package/resources/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
- package/resources/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
- package/resources/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
- package/resources/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
- package/resources/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
- package/resources/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
- package/resources/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
- package/resources/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
- package/resources/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
- package/resources/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
- package/resources/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
- package/resources/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
- package/resources/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
- package/resources/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
- package/resources/powershell/PoshQC/PoshQC.Parallel.psm1 +452 -0
- package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +106 -8
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +12 -0
- package/resources/powershell/PoshQC/PoshQC.psd1 +6 -1
- package/resources/powershell/PoshQC/PoshQC.psm1 +7 -1
- package/resources/powershell/PoshQC/README.md +20 -1
- package/resources/templates/policy_audit/code-review.yyyy-MM-ddTHH-mm.md +4 -4
- package/resources/templates/policy_audit/policy-audit.yyyy-MM-ddTHH-mm.md +4 -4
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Green-CI standalone merge branch of the merge gate (issue #997).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Dot-sourced by enforce-epic-merge-gate.ps1. Invoke-EpicMergeGateDecision calls
|
|
7
|
+
Get-EpicMergeGateGreenCiDecision after the parallel allow-branch declines and before the
|
|
8
|
+
standalone authorization branch, so branches 1 to 3 keep their order and behavior. The
|
|
9
|
+
branch allows a standalone run (neither an epic child nor a parallel item) to merge its own
|
|
10
|
+
pull request once its per-feature checkpoint records a green S9 gate for that exact pull
|
|
11
|
+
request and the live pull-request head still equals the head that CI passed on.
|
|
12
|
+
|
|
13
|
+
Stages run cheapest first, and only stage D starts a process:
|
|
14
|
+
A. The item target resolved to one worktree and its per-feature checkpoint records a green
|
|
15
|
+
standalone S9 gate bound to the explicit PR number (Test-ChildCheckpointGreenCiRecorded).
|
|
16
|
+
B. No epic or parallel run checkpoint records the PR number (both run targets NoTarget),
|
|
17
|
+
and no parallel run checkpoint records the item worktree as a parallel item.
|
|
18
|
+
C. The command is one structural gh pr merge with no chain, wrapper, inline environment
|
|
19
|
+
prefix, -R or --repo, /pull/ URL selector, or --auto (Get-DocsOnlyMergeShapeViolation),
|
|
20
|
+
and carries no --admin flag (declined silently).
|
|
21
|
+
D. gh pr view <PR> --json headRefOid returns a well-formed head (one bounded call).
|
|
22
|
+
E. The live head equals ci_gate.head_sha, and --match-head-commit, when present, equals it.
|
|
23
|
+
Stages A to C decline silently, so every existing deny text is unchanged for a non-green
|
|
24
|
+
case. A stage D or E decline returns a suffix that the gate appends to its deny reason:
|
|
25
|
+
GREEN_CI_LIVE_HEAD_UNRESOLVED, GREEN_CI_HEAD_STALE, or GREEN_CI_MATCH_HEAD_MISMATCH.
|
|
26
|
+
|
|
27
|
+
Residual parallel-exclusion window: the Parallel mode: true marker lives in the Agent
|
|
28
|
+
delegation prompt and is not in a Bash PreToolUse payload, so this hook cannot see it. The
|
|
29
|
+
exclusion is layered and best-effort: a run target recording the PR number (layer 1a), a
|
|
30
|
+
parallel item recording the worktree path (layer 1b), and child-recorded parallel_mode or
|
|
31
|
+
parallel_slug keys (layer 2), with skill prose as layer 3. A parallel child whose parent has
|
|
32
|
+
recorded neither the PR number nor the worktree path, and which omits the layer 2 keys, is
|
|
33
|
+
not excluded mechanically. Like the standalone authorization record, this is a policy-level
|
|
34
|
+
control, not a cryptographic one. A push after the stage D lookup can also change the head
|
|
35
|
+
unless the command passes --match-head-commit; the hook does not require the flag.
|
|
36
|
+
|
|
37
|
+
.NOTES
|
|
38
|
+
Compatible with PowerShell 7+. This file dot-sources nothing and imports no module. It
|
|
39
|
+
calls, at run time only, Invoke-DocsOnlyGhExe and Get-DocsOnlyMergeShapeViolation
|
|
40
|
+
(hook-docs-only-diff.ps1), Test-CommandLineFlag and Get-CommandLineFlagValue (the shared
|
|
41
|
+
command parser), and Resolve-WorktreeRunTargetByRecord (WorktreeRunResolution.psm1), all of
|
|
42
|
+
which the gate loads before it calls this file's functions.
|
|
43
|
+
#>
|
|
44
|
+
|
|
45
|
+
function Test-EpicMergeGateGreenCiObjectId {
|
|
46
|
+
<#
|
|
47
|
+
.SYNOPSIS
|
|
48
|
+
True when a value is a full lowercase hexadecimal object id of 40 or 64 characters.
|
|
49
|
+
.PARAMETER Value
|
|
50
|
+
The candidate value; any non-string value returns $false.
|
|
51
|
+
.OUTPUTS
|
|
52
|
+
System.Boolean
|
|
53
|
+
#>
|
|
54
|
+
[CmdletBinding()]
|
|
55
|
+
[OutputType([bool])]
|
|
56
|
+
param([AllowNull()] $Value)
|
|
57
|
+
|
|
58
|
+
return ($Value -is [string]) -and ($Value -cmatch '^[0-9a-f]{40}([0-9a-f]{24})?$')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function Test-EpicMergeGateGreenCiNotParallel {
|
|
62
|
+
<#
|
|
63
|
+
.SYNOPSIS
|
|
64
|
+
True when a parsed per-feature checkpoint carries no epic or parallel marker (pure).
|
|
65
|
+
.DESCRIPTION
|
|
66
|
+
epic_mode must be absent, JSON null, or JSON false; epic_mode true belongs to branch 1,
|
|
67
|
+
and any other value (for example the string "false") fails closed. parallel_mode must
|
|
68
|
+
be absent, JSON null, or JSON false, and parallel_slug must be absent or blank.
|
|
69
|
+
.PARAMETER Checkpoint
|
|
70
|
+
The parsed checkpoint object.
|
|
71
|
+
.OUTPUTS
|
|
72
|
+
System.Boolean
|
|
73
|
+
#>
|
|
74
|
+
[CmdletBinding()]
|
|
75
|
+
[OutputType([bool])]
|
|
76
|
+
param([Parameter(Mandatory)] $Checkpoint)
|
|
77
|
+
|
|
78
|
+
$props = @($Checkpoint.PSObject.Properties.Name)
|
|
79
|
+
# Each marker key passes only when it is absent, JSON null, or the JSON boolean false.
|
|
80
|
+
foreach ($marker in @('epic_mode', 'parallel_mode')) {
|
|
81
|
+
if ($props -ccontains $marker -and $null -ne $Checkpoint.$marker -and
|
|
82
|
+
-not (($Checkpoint.$marker -is [bool]) -and -not $Checkpoint.$marker)) {
|
|
83
|
+
return $false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return -not ($props -ccontains 'parallel_slug' -and -not [string]::IsNullOrWhiteSpace([string]$Checkpoint.parallel_slug))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function Test-ChildCheckpointGreenCiRecorded {
|
|
90
|
+
<#
|
|
91
|
+
.SYNOPSIS
|
|
92
|
+
True when a per-feature checkpoint records a green standalone S9 gate for the PR (pure).
|
|
93
|
+
.DESCRIPTION
|
|
94
|
+
Every condition must hold: the command names a PR number above zero; the checkpoint
|
|
95
|
+
carries no epic or parallel marker (Test-EpicMergeGateGreenCiNotParallel);
|
|
96
|
+
step9_status is exactly "passed"; ci_gate is an object whose conclusion is exactly
|
|
97
|
+
"success"; pr_gate.pr_number is a JSON integer equal to the command number, with no
|
|
98
|
+
fallback to a standalone_merge_authorizations record; ci_gate.head_sha is a lowercase
|
|
99
|
+
40 or 64 character object id; and pr_gate.head_sha, when present and not null, equals
|
|
100
|
+
ci_gate.head_sha. Comparisons are ordinal, so a case variant fails closed.
|
|
101
|
+
.PARAMETER Checkpoint
|
|
102
|
+
Parsed per-feature checkpoint, or $null when absent or unreadable.
|
|
103
|
+
.PARAMETER CommandPrNumber
|
|
104
|
+
The explicit PR number the command names, or $null for a bare command.
|
|
105
|
+
.OUTPUTS
|
|
106
|
+
System.Boolean
|
|
107
|
+
#>
|
|
108
|
+
[CmdletBinding()]
|
|
109
|
+
[OutputType([bool])]
|
|
110
|
+
param(
|
|
111
|
+
[AllowNull()] $Checkpoint,
|
|
112
|
+
[AllowNull()][Nullable[int]] $CommandPrNumber
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if ($null -eq $CommandPrNumber -or $CommandPrNumber -le 0) { return $false }
|
|
116
|
+
if ($Checkpoint -isnot [System.Management.Automation.PSCustomObject]) { return $false }
|
|
117
|
+
if (-not (Test-EpicMergeGateGreenCiNotParallel -Checkpoint $Checkpoint)) { return $false }
|
|
118
|
+
if (-not (($Checkpoint.step9_status -is [string]) -and ($Checkpoint.step9_status -ceq 'passed'))) { return $false }
|
|
119
|
+
|
|
120
|
+
$ciGate = $Checkpoint.ci_gate
|
|
121
|
+
if ($ciGate -isnot [System.Management.Automation.PSCustomObject]) { return $false }
|
|
122
|
+
if (-not (($ciGate.conclusion -is [string]) -and ($ciGate.conclusion -ceq 'success'))) { return $false }
|
|
123
|
+
if (-not (Test-EpicMergeGateGreenCiObjectId -Value $ciGate.head_sha)) { return $false }
|
|
124
|
+
|
|
125
|
+
# The PR binding comes from pr_gate alone; an authorization record must not stand in for a CI result.
|
|
126
|
+
$prGate = $Checkpoint.pr_gate
|
|
127
|
+
if ($prGate -isnot [System.Management.Automation.PSCustomObject]) { return $false }
|
|
128
|
+
$recordedNumber = $prGate.pr_number
|
|
129
|
+
if (-not (($recordedNumber -is [int]) -or ($recordedNumber -is [long])) -or [long]$recordedNumber -ne $CommandPrNumber) { return $false }
|
|
130
|
+
|
|
131
|
+
# A recorded PR head must agree with the head CI passed on; null or absent means not recorded.
|
|
132
|
+
if (@($prGate.PSObject.Properties.Name) -ccontains 'head_sha' -and $null -ne $prGate.head_sha) {
|
|
133
|
+
return [string]::Equals([string]$prGate.head_sha, [string]$ciGate.head_sha, [System.StringComparison]::Ordinal)
|
|
134
|
+
}
|
|
135
|
+
return $true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function Get-EpicMergeGateLivePrHeadSha {
|
|
139
|
+
<#
|
|
140
|
+
.SYNOPSIS
|
|
141
|
+
Return the live head commit of a pull request, or $null on any failure (the live-head seam).
|
|
142
|
+
.DESCRIPTION
|
|
143
|
+
Runs gh pr view <PR> --json headRefOid through Invoke-DocsOnlyGhExe, which bounds the
|
|
144
|
+
call at 7 seconds and never throws. Returns the head only for exit code 0, output that
|
|
145
|
+
parses to a JSON object, and a well-formed lowercase object id; a non-zero exit
|
|
146
|
+
(including 124 for a timeout and -1 for a missing root), unparseable output, and a
|
|
147
|
+
malformed head all return $null. Tests mock this function by name.
|
|
148
|
+
.PARAMETER PrNumber
|
|
149
|
+
The pull request number.
|
|
150
|
+
.PARAMETER WorktreeRoot
|
|
151
|
+
The directory gh runs in.
|
|
152
|
+
.OUTPUTS
|
|
153
|
+
System.String or $null
|
|
154
|
+
#>
|
|
155
|
+
[CmdletBinding()]
|
|
156
|
+
[OutputType([string])]
|
|
157
|
+
param(
|
|
158
|
+
[Parameter(Mandatory)][int] $PrNumber,
|
|
159
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreeRoot
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
$lookup = Invoke-DocsOnlyGhExe -WorkingRoot $WorktreeRoot -GhArgs @('pr', 'view', [string]$PrNumber, '--json', 'headRefOid')
|
|
163
|
+
if ($null -eq $lookup -or $lookup.ExitCode -ne 0) { return $null }
|
|
164
|
+
try {
|
|
165
|
+
$pr = ConvertFrom-Json -InputObject ([string]$lookup.Output) -ErrorAction Stop
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
# Unreadable gh output is a lookup failure; the gate fails closed on $null.
|
|
169
|
+
return $null
|
|
170
|
+
}
|
|
171
|
+
if ($pr -isnot [System.Management.Automation.PSCustomObject]) { return $null }
|
|
172
|
+
if (-not (Test-EpicMergeGateGreenCiObjectId -Value $pr.headRefOid)) { return $null }
|
|
173
|
+
return [string]$pr.headRefOid
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function Test-EpicMergeGateGreenCiHeadAgreement {
|
|
177
|
+
<#
|
|
178
|
+
.SYNOPSIS
|
|
179
|
+
True when the live head equals ci_gate.head_sha and any pinned head equals the live head (pure).
|
|
180
|
+
.PARAMETER Checkpoint
|
|
181
|
+
Parsed per-feature checkpoint carrying ci_gate.head_sha.
|
|
182
|
+
.PARAMETER LiveHeadSha
|
|
183
|
+
The live pull-request head, or $null when unresolved.
|
|
184
|
+
.PARAMETER PinnedHeadSha
|
|
185
|
+
The --match-head-commit value, or $null when the flag is absent. An empty string means
|
|
186
|
+
the flag is present without a usable value and fails closed.
|
|
187
|
+
.OUTPUTS
|
|
188
|
+
System.Boolean
|
|
189
|
+
#>
|
|
190
|
+
[CmdletBinding()]
|
|
191
|
+
[OutputType([bool])]
|
|
192
|
+
param(
|
|
193
|
+
[AllowNull()] $Checkpoint,
|
|
194
|
+
[AllowNull()] $LiveHeadSha,
|
|
195
|
+
[AllowNull()] $PinnedHeadSha
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if ($null -eq $Checkpoint -or $LiveHeadSha -isnot [string] -or $null -eq $Checkpoint.ci_gate) { return $false }
|
|
199
|
+
if (-not [string]::Equals($LiveHeadSha, [string]$Checkpoint.ci_gate.head_sha, [System.StringComparison]::Ordinal)) { return $false }
|
|
200
|
+
if ($null -ne $PinnedHeadSha -and -not [string]::Equals([string]$PinnedHeadSha, $LiveHeadSha, [System.StringComparison]::Ordinal)) { return $false }
|
|
201
|
+
return $true
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function Resolve-EpicMergeGateParallelItemByWorktree {
|
|
205
|
+
<#
|
|
206
|
+
.SYNOPSIS
|
|
207
|
+
Resolve the parallel run whose items[] record a worktree path (layer 1b seam).
|
|
208
|
+
.DESCRIPTION
|
|
209
|
+
A one-call seam over Resolve-WorktreeRunTargetByRecord so tests can mock it. Status
|
|
210
|
+
NoTarget means no parallel checkpoint records the worktree as a parallel item.
|
|
211
|
+
.PARAMETER WorktreeRoot
|
|
212
|
+
The item worktree whose per-feature checkpoint was read.
|
|
213
|
+
.OUTPUTS
|
|
214
|
+
System.Management.Automation.PSCustomObject (the worktree-resolution target result).
|
|
215
|
+
#>
|
|
216
|
+
[CmdletBinding()]
|
|
217
|
+
[OutputType([pscustomobject])]
|
|
218
|
+
param([AllowNull()][AllowEmptyString()][string] $WorktreeRoot)
|
|
219
|
+
|
|
220
|
+
return Resolve-WorktreeRunTargetByRecord -Kind parallel -RecordField worktree_path -Value $WorktreeRoot -SessionRoot (Get-Location).Path
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function Test-EpicMergeGateGreenCiNoRunRecord {
|
|
224
|
+
<#
|
|
225
|
+
.SYNOPSIS
|
|
226
|
+
True when no epic or parallel run checkpoint records the PR or the item worktree (stage B).
|
|
227
|
+
.DESCRIPTION
|
|
228
|
+
Both run targets must have status NoTarget; a resolved or Ambiguous target declines.
|
|
229
|
+
The worktree lookup declines on any status other than NoTarget, on $null, and on a
|
|
230
|
+
thrown error, because an unreadable parallel record cannot prove the item is standalone.
|
|
231
|
+
.PARAMETER EpicTarget
|
|
232
|
+
The epic run target resolved for the PR number.
|
|
233
|
+
.PARAMETER ParallelTarget
|
|
234
|
+
The parallel run target resolved for the PR number.
|
|
235
|
+
.PARAMETER WorktreeRoot
|
|
236
|
+
The item worktree root.
|
|
237
|
+
.OUTPUTS
|
|
238
|
+
System.Boolean
|
|
239
|
+
#>
|
|
240
|
+
[CmdletBinding()]
|
|
241
|
+
[OutputType([bool])]
|
|
242
|
+
param(
|
|
243
|
+
[AllowNull()] $EpicTarget,
|
|
244
|
+
[AllowNull()] $ParallelTarget,
|
|
245
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreeRoot
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
foreach ($target in @($EpicTarget, $ParallelTarget)) {
|
|
249
|
+
if ($null -eq $target -or [string]$target.Status -cne 'NoTarget') { return $false }
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
$byWorktree = Resolve-EpicMergeGateParallelItemByWorktree -WorktreeRoot $WorktreeRoot
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
# A failed lookup cannot show the worktree is not a parallel item, so the stage declines.
|
|
256
|
+
return $false
|
|
257
|
+
}
|
|
258
|
+
return ($null -ne $byWorktree -and [string]$byWorktree.Status -ceq 'NoTarget')
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function Get-EpicMergeGateGreenCiDecision {
|
|
262
|
+
<#
|
|
263
|
+
.SYNOPSIS
|
|
264
|
+
Evaluate stages A to E of the green-CI branch and return Allowed and a deny suffix.
|
|
265
|
+
Stage C declines silently a command that carries --admin.
|
|
266
|
+
.PARAMETER CommandText
|
|
267
|
+
The Bash command text under evaluation.
|
|
268
|
+
.PARAMETER CommandPrNumber
|
|
269
|
+
The explicit PR number, or $null for a bare command.
|
|
270
|
+
.PARAMETER ChildCheckpoint
|
|
271
|
+
The parsed per-feature checkpoint the gate read, or $null.
|
|
272
|
+
.PARAMETER ItemTarget
|
|
273
|
+
The item target, or $null for a bare command.
|
|
274
|
+
.PARAMETER EpicTarget
|
|
275
|
+
The epic run target, or $null for a bare command.
|
|
276
|
+
.PARAMETER ParallelTarget
|
|
277
|
+
The parallel run target, or $null for a bare command.
|
|
278
|
+
.PARAMETER SessionRoot
|
|
279
|
+
The session worktree root, where gh runs.
|
|
280
|
+
.OUTPUTS
|
|
281
|
+
System.Collections.Specialized.OrderedDictionary with Allowed and Suffix.
|
|
282
|
+
#>
|
|
283
|
+
[CmdletBinding()]
|
|
284
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
285
|
+
param(
|
|
286
|
+
[AllowNull()][AllowEmptyString()][string] $CommandText,
|
|
287
|
+
[AllowNull()][Nullable[int]] $CommandPrNumber,
|
|
288
|
+
[AllowNull()] $ChildCheckpoint,
|
|
289
|
+
[AllowNull()] $ItemTarget,
|
|
290
|
+
[AllowNull()] $EpicTarget,
|
|
291
|
+
[AllowNull()] $ParallelTarget,
|
|
292
|
+
[AllowNull()][AllowEmptyString()][string] $SessionRoot
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
$silent = [ordered]@{ Allowed = $false; Suffix = '' }
|
|
296
|
+
# Stage A: one resolved item worktree whose checkpoint records a green S9 gate for this PR.
|
|
297
|
+
if ($null -eq $ItemTarget -or @('SessionRoot', 'OtherWorktree') -cnotcontains [string]$ItemTarget.Status) { return $silent }
|
|
298
|
+
if (-not (Test-ChildCheckpointGreenCiRecorded -Checkpoint $ChildCheckpoint -CommandPrNumber $CommandPrNumber)) { return $silent }
|
|
299
|
+
# Stage B: no epic or parallel run records the PR or the item worktree.
|
|
300
|
+
if (-not (Test-EpicMergeGateGreenCiNoRunRecord -EpicTarget $EpicTarget -ParallelTarget $ParallelTarget -WorktreeRoot ([string]$ItemTarget.WorktreeRoot))) { return $silent }
|
|
301
|
+
# Stage C: a single structural gh pr merge naming the PR, with no repository or auto override.
|
|
302
|
+
if ($null -ne (Get-DocsOnlyMergeShapeViolation -CommandText $CommandText -PrNumber $CommandPrNumber)) { return $silent }
|
|
303
|
+
# --admin bypasses branch protection, so a green checkpoint must not authorize it.
|
|
304
|
+
if (Test-CommandLineFlag -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('pr', 'merge') -FlagName '--admin') { return $silent }
|
|
305
|
+
|
|
306
|
+
# Stage D: the only process call, made after every pure stage passed.
|
|
307
|
+
$liveHead = Get-EpicMergeGateLivePrHeadSha -PrNumber $CommandPrNumber -WorktreeRoot $SessionRoot
|
|
308
|
+
if ($null -eq $liveHead) {
|
|
309
|
+
return [ordered]@{ Allowed = $false; Suffix = ' (green-CI merge not applied: GREEN_CI_LIVE_HEAD_UNRESOLVED)' }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
# Stage E: a stale checkpoint head is reported before a mismatched --match-head-commit pin.
|
|
313
|
+
$subcommandPath = @('pr', 'merge')
|
|
314
|
+
$pinned = $null
|
|
315
|
+
if (Test-CommandLineFlag -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath -FlagName '--match-head-commit') {
|
|
316
|
+
$pinned = [string](Get-CommandLineFlagValue -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath -FlagName '--match-head-commit')
|
|
317
|
+
}
|
|
318
|
+
if (Test-EpicMergeGateGreenCiHeadAgreement -Checkpoint $ChildCheckpoint -LiveHeadSha $liveHead -PinnedHeadSha $pinned) {
|
|
319
|
+
return [ordered]@{ Allowed = $true; Suffix = '' }
|
|
320
|
+
}
|
|
321
|
+
if (-not (Test-EpicMergeGateGreenCiHeadAgreement -Checkpoint $ChildCheckpoint -LiveHeadSha $liveHead -PinnedHeadSha $null)) {
|
|
322
|
+
return [ordered]@{ Allowed = $false; Suffix = ' (green-CI merge not applied: GREEN_CI_HEAD_STALE)' }
|
|
323
|
+
}
|
|
324
|
+
return [ordered]@{ Allowed = $false; Suffix = ' (green-CI merge not applied: GREEN_CI_MATCH_HEAD_MISMATCH)' }
|
|
325
|
+
}
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
1. Child-feature path: artifacts/orchestration/orchestrator-state.json exists,
|
|
13
13
|
epic_mode == true, and step9_status == "passed" (the per-feature orchestrator has
|
|
14
14
|
already run S9 step 6's CI-green gate before attempting its own merge-on-green).
|
|
15
|
+
Only the JSON boolean true counts as epic mode; a string or number such as "false"
|
|
16
|
+
does not (issue #1001).
|
|
15
17
|
2. Epic-integration path: artifacts/orchestration/epic-orchestrator-state.json exists,
|
|
16
18
|
epic_merge_pr.ci_gate.conclusion == "success", and, when the command names an
|
|
17
19
|
explicit PR number, that number matches epic_merge_pr.pr_number.
|
|
@@ -20,6 +22,11 @@
|
|
|
20
22
|
entry whose merge_status == "ci_green". A parallel run always names an explicit PR
|
|
21
23
|
number (each item merges from its own isolated worktree), so a bare command with no
|
|
22
24
|
PR number cannot satisfy this branch.
|
|
25
|
+
3b. Green-CI standalone path (issue #997): the command names an explicit PR number, no
|
|
26
|
+
epic or parallel checkpoint records that number or the item worktree, the per-feature
|
|
27
|
+
checkpoint records step9_status == "passed", ci_gate.conclusion == "success", and a
|
|
28
|
+
pr_gate.pr_number equal to it, and the live PR head equals ci_gate.head_sha. The
|
|
29
|
+
dot-sourced enforce-epic-merge-gate-green-ci.ps1 implements this branch.
|
|
23
30
|
4. Standalone path (issue #670), evaluated last: the command names an explicit PR
|
|
24
31
|
number and one of the three checkpoints above carries a
|
|
25
32
|
standalone_merge_authorizations record whose pr_number equals it, whose fields are
|
|
@@ -29,15 +36,20 @@
|
|
|
29
36
|
Otherwise the command is denied with reason EPIC_MERGE_GATE_BLOCKED; a denial from
|
|
30
37
|
condition 4 also carries a standalone reason code after that token. A missing or
|
|
31
38
|
unreadable checkpoint in any branch fails closed (denies). A standalone (non-epic,
|
|
32
|
-
non-parallel) run never satisfies conditions 1-3
|
|
33
|
-
explicit, PR-specific, session-bound record under condition 4
|
|
34
|
-
PR number
|
|
39
|
+
non-parallel) run never satisfies conditions 1-3; it merges through the green-CI branch
|
|
40
|
+
3b or through an explicit, PR-specific, session-bound record under condition 4. A bare
|
|
41
|
+
command with no PR number satisfies neither, and a blanket flag is rejected rather than
|
|
42
|
+
read. A 3b decline after its live-head lookup appends a GREEN_CI_* code to the deny.
|
|
35
43
|
Trigger scope (issue #952): every merge method (--merge/-m, --squash/-s, --rebase/-r), --auto,
|
|
36
44
|
--admin, and a bare gh pr merge are gated alike, because under a merge queue each spelling
|
|
37
45
|
enqueues the pull request; only a pure --disable-auto is out of scope. The predicate and the
|
|
38
46
|
PR selector reader live in enforce-epic-merge-gate-command.ps1.
|
|
39
47
|
|
|
40
|
-
Design decision: branches 1-4 trust the on-disk checkpoint and never call gh.
|
|
48
|
+
Design decision: branches 1-4 trust the on-disk checkpoint and never call gh. The green-CI
|
|
49
|
+
branch makes at most one bounded gh pr view call, only after its pure stages A to C pass.
|
|
50
|
+
Its parallel exclusion is best-effort: the Parallel mode: true marker is in the delegation
|
|
51
|
+
prompt, not in a Bash payload, so a parallel child that no checkpoint records yet is not
|
|
52
|
+
excluded mechanically (residual window; see enforce-epic-merge-gate-green-ci.ps1). Issue #927
|
|
41
53
|
adds a docs-only branch after branch 4 fails, only for an explicit PR number whose item,
|
|
42
54
|
epic, and parallel targets are all NoTarget (no checkpoint records the PR): it calls
|
|
43
55
|
Get-DocsOnlyMergeExemption (hook-docs-only-diff.ps1), which runs gh pr view and git in the
|
|
@@ -86,6 +98,8 @@ try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-resolution.ps1') } cat
|
|
|
86
98
|
try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-command.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-command.ps1' -ErrorRecord $_ }
|
|
87
99
|
# Shared docs-only diff classification and the merge-side docs-only exemption (issue #927).
|
|
88
100
|
try { . (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1') } catch { Add-HookDependencyFailure -Name 'hook-docs-only-diff.ps1' -ErrorRecord $_ }
|
|
101
|
+
# Green-CI standalone merge branch: pure checks, the live-head seam, and the stage orchestrator (issue #997).
|
|
102
|
+
try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-green-ci.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-green-ci.ps1' -ErrorRecord $_ }
|
|
89
103
|
|
|
90
104
|
function ConvertFrom-EpicMergeGateJson {
|
|
91
105
|
<#
|
|
@@ -143,6 +157,10 @@ function Test-ChildCheckpointAllowsEpicMerge {
|
|
|
143
157
|
<#
|
|
144
158
|
.SYNOPSIS
|
|
145
159
|
Decision logic for the child-feature checkpoint path (branch 1).
|
|
160
|
+
.DESCRIPTION
|
|
161
|
+
Only the JSON boolean true counts as epic mode: epic_mode must be a System.Boolean
|
|
162
|
+
$true after ConvertFrom-Json. A string or number such as "false", "true", or 1 is not
|
|
163
|
+
epic mode, so branch 1 declines and later branches decide (issue #1001).
|
|
146
164
|
.PARAMETER Checkpoint
|
|
147
165
|
Parsed child orchestrator checkpoint, or $null when absent/unreadable.
|
|
148
166
|
.OUTPUTS
|
|
@@ -159,7 +177,7 @@ function Test-ChildCheckpointAllowsEpicMerge {
|
|
|
159
177
|
return $false
|
|
160
178
|
}
|
|
161
179
|
$props = @($Checkpoint.PSObject.Properties.Name)
|
|
162
|
-
if ($props -notcontains 'epic_mode' -or -not [bool]$Checkpoint.epic_mode) {
|
|
180
|
+
if ($props -notcontains 'epic_mode' -or -not (($Checkpoint.epic_mode -is [bool]) -and $Checkpoint.epic_mode)) {
|
|
163
181
|
return $false
|
|
164
182
|
}
|
|
165
183
|
if ($props -notcontains 'step9_status') {
|
|
@@ -376,6 +394,10 @@ function Invoke-EpicMergeGateDecision {
|
|
|
376
394
|
return Get-EpicMergeGateAllowDecision
|
|
377
395
|
}
|
|
378
396
|
|
|
397
|
+
# Branch 3b (issue #997): a standalone run whose checkpoint records a green S9 gate merges its own PR.
|
|
398
|
+
$greenCi = Get-EpicMergeGateGreenCiDecision -CommandText $commandText -CommandPrNumber $commandPrNumber -ChildCheckpoint $childCheckpoint -ItemTarget $itemTarget -EpicTarget $epicTarget -ParallelTarget $parallelTarget -SessionRoot $sessionRoot
|
|
399
|
+
if ($greenCi.Allowed) { return Get-EpicMergeGateAllowDecision }
|
|
400
|
+
|
|
379
401
|
# Branch 4, evaluated last and only for an explicit PR number: a PR-specific standalone
|
|
380
402
|
# authorization record bound to the live session. Its deny keeps the gate token first.
|
|
381
403
|
if ($null -ne $commandPrNumber) {
|
|
@@ -397,10 +419,10 @@ function Invoke-EpicMergeGateDecision {
|
|
|
397
419
|
# three details precede the standalone reason (issue #850).
|
|
398
420
|
$unresolved = Get-EpicMergeGateUnresolvedReason -ItemTarget $itemTarget -EpicTarget $epicTarget -ParallelTarget $parallelTarget
|
|
399
421
|
$prefix = if ($unresolved) { $unresolved + '; ' } else { '' }
|
|
400
|
-
return Get-EpicMergeGateBlockDecision -Reason ('EPIC_MERGE_GATE_BLOCKED: ' + $prefix + $standalone.ReasonCode + ': ' + $standalone.Message + $docsOnlySuffix)
|
|
422
|
+
return Get-EpicMergeGateBlockDecision -Reason ('EPIC_MERGE_GATE_BLOCKED: ' + $prefix + $standalone.ReasonCode + ': ' + $standalone.Message + $docsOnlySuffix + $greenCi.Suffix)
|
|
401
423
|
}
|
|
402
424
|
|
|
403
|
-
return Get-EpicMergeGateBlockDecision -Reason 'EPIC_MERGE_GATE_BLOCKED: a pull-request merge or merge-queue enqueue (gh pr merge with any method, --auto, or --admin, or a gh api merge or enqueue mutation) requires either a per-feature checkpoint with epic_mode == true and step9_status == "passed", an epic checkpoint with epic_merge_pr.ci_gate.conclusion == "success" and a matching pr_number,
|
|
425
|
+
return Get-EpicMergeGateBlockDecision -Reason 'EPIC_MERGE_GATE_BLOCKED: a pull-request merge or merge-queue enqueue (gh pr merge with any method, --auto, or --admin, or a gh api merge or enqueue mutation) requires either a per-feature checkpoint with epic_mode == true and step9_status == "passed", an epic checkpoint with epic_merge_pr.ci_gate.conclusion == "success" and a matching pr_number, a parallel-orchestrator checkpoint with route_id == "parallel" whose target item (matched by pr_number) has merge_status == "ci_green", or a standalone per-feature checkpoint (epic_mode absent or false, no parallel marker) with step9_status == "passed", ci_gate.conclusion == "success", pr_gate.pr_number equal to the explicit PR number, and ci_gate.head_sha equal to the live pull-request head. No checkpoint satisfied this gate.'
|
|
404
426
|
}
|
|
405
427
|
|
|
406
428
|
function Invoke-EpicMergeGateEntryPoint {
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
for the removal target, and Get-EpicWorktreeGateDenyDiagnostics, which builds the
|
|
22
22
|
diagnostics clause of the final deny from two read results without reading any
|
|
23
23
|
file (issue #851).
|
|
24
|
+
- The import guard for WorktreeRunRecordPath.psm1, whose Get-WorktreeRunRecordBaseRoot
|
|
25
|
+
pairs the main checkout root with a read's own root so a relative recorded
|
|
26
|
+
worktree_path is compared through the shared matcher (issue #995).
|
|
24
27
|
|
|
25
28
|
.NOTES
|
|
26
29
|
PowerShell 7+. Depends on Get-EpicWorktreeGateBlockDecision,
|
|
@@ -29,6 +32,8 @@
|
|
|
29
32
|
extensions/drm-copilot/resources/claude-customizations/.
|
|
30
33
|
#>
|
|
31
34
|
|
|
35
|
+
# Shared recorded-path matcher and main-checkout-root seam (issue #995).
|
|
36
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunRecordPath.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunRecordPath.psm1' -ErrorRecord $_ }
|
|
32
37
|
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunResolution.psm1' -ErrorRecord $_ }
|
|
33
38
|
|
|
34
39
|
function Get-EpicWorktreeGateCheckpointContent {
|
|
@@ -132,13 +137,15 @@ function Find-EpicWorktreeGateParallelItemRecord {
|
|
|
132
137
|
.SYNOPSIS
|
|
133
138
|
Locate the parallel items[] record whose worktree_path matches the target path.
|
|
134
139
|
.DESCRIPTION
|
|
135
|
-
Pure.
|
|
136
|
-
does
|
|
137
|
-
|
|
140
|
+
Pure. Compares through Test-WorktreeRunRecordedPathMatchesTarget, exactly as
|
|
141
|
+
Test-ParallelCheckpointAllowsWorktreeRemoval does, and returns the first matching
|
|
142
|
+
entry (issues #851, #995).
|
|
138
143
|
.PARAMETER Checkpoint
|
|
139
144
|
Parsed parallel-orchestrator checkpoint, or $null when absent/unreadable.
|
|
140
145
|
.PARAMETER WorktreePath
|
|
141
146
|
The removal target the command names.
|
|
147
|
+
.PARAMETER BaseRoot
|
|
148
|
+
Optional. The roots a relative recorded worktree_path is joined to.
|
|
142
149
|
.OUTPUTS
|
|
143
150
|
System.Object or $null
|
|
144
151
|
#>
|
|
@@ -148,7 +155,9 @@ function Find-EpicWorktreeGateParallelItemRecord {
|
|
|
148
155
|
$Checkpoint,
|
|
149
156
|
|
|
150
157
|
[AllowNull()]
|
|
151
|
-
[string] $WorktreePath
|
|
158
|
+
[string] $WorktreePath,
|
|
159
|
+
|
|
160
|
+
[AllowNull()][AllowEmptyCollection()][string[]] $BaseRoot = @()
|
|
152
161
|
)
|
|
153
162
|
|
|
154
163
|
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
|
|
@@ -159,7 +168,7 @@ function Find-EpicWorktreeGateParallelItemRecord {
|
|
|
159
168
|
return $null
|
|
160
169
|
}
|
|
161
170
|
|
|
162
|
-
|
|
171
|
+
# Scan the items for the first record the shared matcher accepts for the target.
|
|
163
172
|
foreach ($item in @($Checkpoint.items)) {
|
|
164
173
|
if ($null -eq $item) {
|
|
165
174
|
continue
|
|
@@ -168,8 +177,7 @@ function Find-EpicWorktreeGateParallelItemRecord {
|
|
|
168
177
|
if ($itemProps -notcontains 'worktree_path') {
|
|
169
178
|
continue
|
|
170
179
|
}
|
|
171
|
-
|
|
172
|
-
if ($normalizedItemPath -eq $normalizedTarget) {
|
|
180
|
+
if (Test-WorktreeRunRecordedPathMatchesTarget -RecordedPath ([string]$item.worktree_path) -TargetPath $WorktreePath -BaseRoot $BaseRoot) {
|
|
173
181
|
return $item
|
|
174
182
|
}
|
|
175
183
|
}
|
|
@@ -191,6 +199,9 @@ function Get-EpicWorktreeGateDenyDiagnostics {
|
|
|
191
199
|
The parallel read result (Target, Checkpoint, Path).
|
|
192
200
|
.PARAMETER WorktreePath
|
|
193
201
|
The removal target the command names.
|
|
202
|
+
.PARAMETER MainRoot
|
|
203
|
+
Optional. The main checkout root the gate derived; with each read's own root it
|
|
204
|
+
forms the base roots a relative recorded worktree_path is joined to (issue #995).
|
|
194
205
|
.OUTPUTS
|
|
195
206
|
System.String
|
|
196
207
|
#>
|
|
@@ -200,7 +211,8 @@ function Get-EpicWorktreeGateDenyDiagnostics {
|
|
|
200
211
|
param(
|
|
201
212
|
[Parameter(Mandatory)] $EpicRead,
|
|
202
213
|
[Parameter(Mandatory)] $ParallelRead,
|
|
203
|
-
[AllowNull()][AllowEmptyString()][string] $WorktreePath
|
|
214
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreePath,
|
|
215
|
+
[AllowNull()][AllowEmptyString()][string] $MainRoot
|
|
204
216
|
)
|
|
205
217
|
|
|
206
218
|
$parts = foreach ($kind in @('epic', 'parallel')) {
|
|
@@ -213,11 +225,12 @@ function Get-EpicWorktreeGateDenyDiagnostics {
|
|
|
213
225
|
$recordText = 'checkpoint absent or unparseable'
|
|
214
226
|
}
|
|
215
227
|
else {
|
|
228
|
+
$baseRoot = Get-WorktreeRunRecordBaseRoot -MainRoot $MainRoot -Read $read
|
|
216
229
|
$record = if ($kind -eq 'epic') {
|
|
217
|
-
Find-EpicWorktreeFeatureRecord -Checkpoint $read.Checkpoint -WorktreePath $WorktreePath
|
|
230
|
+
Find-EpicWorktreeFeatureRecord -Checkpoint $read.Checkpoint -WorktreePath $WorktreePath -BaseRoot $baseRoot
|
|
218
231
|
}
|
|
219
232
|
else {
|
|
220
|
-
Find-EpicWorktreeGateParallelItemRecord -Checkpoint $read.Checkpoint -WorktreePath $WorktreePath
|
|
233
|
+
Find-EpicWorktreeGateParallelItemRecord -Checkpoint $read.Checkpoint -WorktreePath $WorktreePath -BaseRoot $baseRoot
|
|
221
234
|
}
|
|
222
235
|
$arrayName = if ($kind -eq 'epic') { 'features' } else { 'items' }
|
|
223
236
|
$recordText = if ($null -eq $record) {
|
package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1
CHANGED
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
before either checkpoint is read.
|
|
27
27
|
|
|
28
28
|
Both branches key on the worktree PATH because the command names a path, and both
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
compare through Test-WorktreeRunRecordedPathMatchesTarget (issue #995), so Windows-
|
|
30
|
+
and POSIX-style paths compare equal and a relative record is joined to the main
|
|
31
|
+
checkout root and the checkpoint's own root. Path is also why the two branches are
|
|
31
32
|
mutually exclusive in practice: an epic run and a parallel run allocate worktrees
|
|
32
33
|
under distinct per-run, per-item paths, so a path recorded in one checkpoint is not a
|
|
33
34
|
path recorded in the other, and adding branch 2 cannot widen what branch 1 authorizes.
|
|
@@ -121,6 +122,8 @@ function Find-EpicWorktreeFeatureRecord {
|
|
|
121
122
|
Parsed epic checkpoint, or $null when absent/unreadable.
|
|
122
123
|
.PARAMETER WorktreePath
|
|
123
124
|
The target worktree path extracted from the command text.
|
|
125
|
+
.PARAMETER BaseRoot
|
|
126
|
+
Optional. The roots a relative recorded worktree_path is joined to (issue #995).
|
|
124
127
|
.OUTPUTS
|
|
125
128
|
System.Object or $null
|
|
126
129
|
#>
|
|
@@ -130,7 +133,9 @@ function Find-EpicWorktreeFeatureRecord {
|
|
|
130
133
|
$Checkpoint,
|
|
131
134
|
|
|
132
135
|
[AllowNull()]
|
|
133
|
-
[string] $WorktreePath
|
|
136
|
+
[string] $WorktreePath,
|
|
137
|
+
|
|
138
|
+
[AllowNull()][AllowEmptyCollection()][string[]] $BaseRoot = @()
|
|
134
139
|
)
|
|
135
140
|
|
|
136
141
|
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
|
|
@@ -141,17 +146,14 @@ function Find-EpicWorktreeFeatureRecord {
|
|
|
141
146
|
return $null
|
|
142
147
|
}
|
|
143
148
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
# Scan every recorded feature for a worktree_path that matches the removal target;
|
|
147
|
-
# path separators are normalized so Windows- and POSIX-style paths compare equal.
|
|
149
|
+
# Scan every recorded feature for a worktree_path that names the removal target
|
|
150
|
+
# through the shared matcher (separator, trailing slash, case rule, relative join).
|
|
148
151
|
foreach ($feature in @($Checkpoint.features)) {
|
|
149
152
|
$featureProps = @($feature.PSObject.Properties.Name)
|
|
150
153
|
if ($featureProps -notcontains 'worktree_path') {
|
|
151
154
|
continue
|
|
152
155
|
}
|
|
153
|
-
|
|
154
|
-
if ($normalizedFeaturePath -eq $normalizedTarget) {
|
|
156
|
+
if (Test-WorktreeRunRecordedPathMatchesTarget -RecordedPath ([string]$feature.worktree_path) -TargetPath $WorktreePath -BaseRoot $BaseRoot) {
|
|
155
157
|
return $feature
|
|
156
158
|
}
|
|
157
159
|
}
|
|
@@ -198,6 +200,8 @@ function Test-ParallelCheckpointAllowsWorktreeRemoval {
|
|
|
198
200
|
Parsed parallel-orchestrator checkpoint, or $null when absent/unreadable.
|
|
199
201
|
.PARAMETER WorktreePath
|
|
200
202
|
The target worktree path extracted from the command text.
|
|
203
|
+
.PARAMETER BaseRoot
|
|
204
|
+
Optional. The roots a relative recorded worktree_path is joined to (issue #995).
|
|
201
205
|
.OUTPUTS
|
|
202
206
|
System.Boolean
|
|
203
207
|
#>
|
|
@@ -208,7 +212,9 @@ function Test-ParallelCheckpointAllowsWorktreeRemoval {
|
|
|
208
212
|
$Checkpoint,
|
|
209
213
|
|
|
210
214
|
[AllowNull()]
|
|
211
|
-
[string] $WorktreePath
|
|
215
|
+
[string] $WorktreePath,
|
|
216
|
+
|
|
217
|
+
[AllowNull()][AllowEmptyCollection()][string[]] $BaseRoot = @()
|
|
212
218
|
)
|
|
213
219
|
|
|
214
220
|
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
|
|
@@ -224,10 +230,8 @@ function Test-ParallelCheckpointAllowsWorktreeRemoval {
|
|
|
224
230
|
return $false
|
|
225
231
|
}
|
|
226
232
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
# Scan every recorded item for a worktree_path that matches the removal target;
|
|
230
|
-
# path separators are normalized exactly as the epic branch normalizes them.
|
|
233
|
+
# Scan every recorded item for a worktree_path that names the removal target through
|
|
234
|
+
# the same shared matcher the epic branch uses.
|
|
231
235
|
foreach ($item in @($Checkpoint.items)) {
|
|
232
236
|
if ($null -eq $item) {
|
|
233
237
|
continue
|
|
@@ -236,8 +240,7 @@ function Test-ParallelCheckpointAllowsWorktreeRemoval {
|
|
|
236
240
|
if ($itemProps -notcontains 'worktree_path') {
|
|
237
241
|
continue
|
|
238
242
|
}
|
|
239
|
-
|
|
240
|
-
if ($normalizedItemPath -ne $normalizedTarget) {
|
|
243
|
+
if (-not (Test-WorktreeRunRecordedPathMatchesTarget -RecordedPath ([string]$item.worktree_path) -TargetPath $WorktreePath -BaseRoot $BaseRoot)) {
|
|
241
244
|
continue
|
|
242
245
|
}
|
|
243
246
|
if ($itemProps -notcontains 'merge_status') {
|
|
@@ -366,14 +369,19 @@ function Get-EpicWorktreeRemovalTargetDenial {
|
|
|
366
369
|
}
|
|
367
370
|
}
|
|
368
371
|
$checkpoint = $epicRead.Checkpoint
|
|
372
|
+
# A relative record is joined to the main checkout root and to the root holding the
|
|
373
|
+
# checkpoint read (issue #995); the main root is derived once per target.
|
|
374
|
+
$mainRoot = Get-WorktreeRunMainCheckoutRoot -SessionRoot (Get-Location).Path
|
|
375
|
+
$epicBaseRoot = Get-WorktreeRunRecordBaseRoot -MainRoot $mainRoot -Read $epicRead
|
|
376
|
+
$parallelBaseRoot = Get-WorktreeRunRecordBaseRoot -MainRoot $mainRoot -Read $parallelRead
|
|
369
377
|
|
|
370
|
-
$featureRecord = Find-EpicWorktreeFeatureRecord -Checkpoint $checkpoint -WorktreePath $worktreePath
|
|
378
|
+
$featureRecord = Find-EpicWorktreeFeatureRecord -Checkpoint $checkpoint -WorktreePath $worktreePath -BaseRoot $epicBaseRoot
|
|
371
379
|
if (Test-EpicWorktreeRemovalAllowed -FeatureRecord $featureRecord) {
|
|
372
380
|
return $null
|
|
373
381
|
}
|
|
374
382
|
|
|
375
383
|
$parallelCheckpoint = $parallelRead.Checkpoint
|
|
376
|
-
if (Test-ParallelCheckpointAllowsWorktreeRemoval -Checkpoint $parallelCheckpoint -WorktreePath $worktreePath) {
|
|
384
|
+
if (Test-ParallelCheckpointAllowsWorktreeRemoval -Checkpoint $parallelCheckpoint -WorktreePath $worktreePath -BaseRoot $parallelBaseRoot) {
|
|
377
385
|
return $null
|
|
378
386
|
}
|
|
379
387
|
|
|
@@ -387,8 +395,8 @@ function Get-EpicWorktreeRemovalTargetDenial {
|
|
|
387
395
|
# the unchanged deny below. That exclusion is the only path by which the manifest
|
|
388
396
|
# could widen what the gates protect, which is why it is required rather than
|
|
389
397
|
# advisory, and why it is evaluated before the manifest predicate.
|
|
390
|
-
if (-not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $checkpoint -RecordArrayName 'features' -WorktreePath $worktreePath) -and
|
|
391
|
-
-not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $parallelCheckpoint -RecordArrayName 'items' -WorktreePath $worktreePath) -and
|
|
398
|
+
if (-not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $checkpoint -RecordArrayName 'features' -WorktreePath $worktreePath -BaseRoot $epicBaseRoot) -and
|
|
399
|
+
-not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $parallelCheckpoint -RecordArrayName 'items' -WorktreePath $worktreePath -BaseRoot $parallelBaseRoot) -and
|
|
392
400
|
(Test-CleanupWorktreeManifestAuthorizesRemoval -WorktreePath $worktreePath)) {
|
|
393
401
|
return $null
|
|
394
402
|
}
|
|
@@ -400,7 +408,7 @@ function Get-EpicWorktreeRemovalTargetDenial {
|
|
|
400
408
|
if ($epicRead.Target.Status -eq 'NoTarget' -and $parallelRead.Target.Status -eq 'NoTarget') {
|
|
401
409
|
$prefix = "$($epicRead.Target.ReasonCode): $($epicRead.Target.Detail). "
|
|
402
410
|
}
|
|
403
|
-
$diagnostics = Get-EpicWorktreeGateDenyDiagnostics -EpicRead $epicRead -ParallelRead $parallelRead -WorktreePath $worktreePath
|
|
411
|
+
$diagnostics = Get-EpicWorktreeGateDenyDiagnostics -EpicRead $epicRead -ParallelRead $parallelRead -WorktreePath $worktreePath -MainRoot $mainRoot
|
|
404
412
|
return Get-EpicWorktreeGateBlockDecision -Reason ('EPIC_WORKTREE_REMOVAL_BLOCKED: ' + $prefix + "git worktree remove for '$worktreePath' requires either an epic checkpoint features[] record with merge_status in {merged, worktree_removed}, or a parallel-orchestrator checkpoint with route_id == ""parallel"" whose matching items[] record (matched by worktree_path) has merge_status in {merged, worktree_removed}. No checkpoint authorized this removal. " + $diagnostics)
|
|
405
413
|
}
|
|
406
414
|
|