@danmoisan/drm-copilot-mcp 1.1.18 → 1.1.20
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 +1149 -661
- package/package.json +3 -2
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +28 -6
- package/resources/claude-customizations/.claude/agents/atomic-planner.md +20 -2
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +17 -4
- package/resources/claude-customizations/.claude/agents/epic-planner.md +5 -4
- package/resources/claude-customizations/.claude/agents/feature-review.md +6 -4
- package/resources/claude-customizations/.claude/agents/orchestrator.md +9 -5
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +35 -13
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +9 -6
- package/resources/claude-customizations/.claude/agents/pr-author.md +15 -2
- package/resources/claude-customizations/.claude/agents/prd-feature.md +1 -1
- package/resources/claude-customizations/.claude/agents/task-researcher.md +8 -0
- package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +111 -46
- package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +105 -0
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +8 -1
- package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-command.ps1 +190 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +116 -59
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +78 -77
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +74 -75
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +127 -35
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +61 -57
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +129 -39
- package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +39 -51
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +17 -12
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +65 -50
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +49 -42
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +54 -72
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +8 -2
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +279 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +129 -75
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.artifact-root.ps1 +233 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.docs-only.ps1 +224 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +69 -24
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +29 -18
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +10 -4
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +14 -5
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +8 -2
- package/resources/claude-customizations/.claude/hooks/feature-folder-resolution.ps1 +373 -0
- package/resources/claude-customizations/.claude/hooks/feature-review-coverage-thresholds.ps1 +85 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-heredoc.ps1 +119 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-invocation-operands.ps1 +198 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +254 -255
- package/resources/claude-customizations/.claude/hooks/hook-command-payload-powershell.ps1 +201 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-payload.ps1 +484 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +27 -110
- package/resources/claude-customizations/.claude/hooks/hook-dependency-guard.ps1 +114 -0
- package/resources/claude-customizations/.claude/hooks/hook-docs-only-diff.ps1 +497 -0
- package/resources/claude-customizations/.claude/hooks/premortem-section.ps1 +149 -0
- package/resources/claude-customizations/.claude/hooks/subagentstop-envelope.ps1 +192 -0
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +40 -42
- package/resources/claude-customizations/.claude/hooks/validate-executor-output.ps1 +267 -113
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-metrics.ps1 +180 -0
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-rules.ps1 +195 -0
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage.ps1 +141 -395
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-resolution.ps1 +311 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-routing.ps1 +170 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +115 -190
- package/resources/claude-customizations/.claude/hooks/validate-planner-output.ps1 +77 -33
- package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +39 -44
- package/resources/claude-customizations/.claude/hooks/validate-prd-feature-output.ps1 +43 -14
- package/resources/claude-customizations/.claude/hooks/validate-required-artifact-output.ps1 +66 -36
- package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +52 -36
- package/resources/claude-customizations/.claude/lib/bash/mutate-parallel-run.sh +191 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-cohorts.sh +13 -3
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-cli.sh +152 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-ops.sh +193 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation.sh +322 -0
- package/resources/claude-customizations/.claude/lib/bash/remove-parallel-item.sh +146 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +23 -33
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +86 -6
- package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-CiGateParser.ps1 +91 -19
- package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-MergeConfirmationParser.ps1 +203 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateEpicWaveBarrier.psm1 +346 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +24 -3
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +79 -2
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +3 -3
- package/resources/claude-customizations/.claude/rules/architecture-boundaries.md +5 -5
- package/resources/claude-customizations/.claude/rules/csharp.md +2 -2
- package/resources/claude-customizations/.claude/rules/general-code-change.md +1 -1
- package/resources/claude-customizations/.claude/rules/general-unit-test.md +3 -2
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +37 -4
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +50 -5
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +85 -10
- package/resources/claude-customizations/.claude/rules/quality-tiers.md +11 -7
- package/resources/claude-customizations/.claude/rules/shell.md +10 -8
- package/resources/claude-customizations/.claude/rules/typescript.md +1 -1
- package/resources/claude-customizations/.claude/settings.json +3 -4
- package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +52 -16
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +7 -3
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_detached_lib.sh +1 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +2 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +2 -0
- package/resources/claude-customizations/.claude/skills/csharp-change-budget-router/SKILL.md +2 -2
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +45 -9
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +5 -3
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +134 -0
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +4 -3
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +36 -15
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +52 -14
- package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +22 -9
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +108 -21
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +63 -7
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +35 -12
- package/resources/claude-customizations/.claude/skills/powershell-change-budget-router/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +16 -8
- package/resources/claude-customizations/.claude/skills/research-issue/SKILL.md +6 -0
- package/resources/claude-customizations/pack-manifests/core.json +27 -0
- package/resources/codex-and-agents-customizations/.agents/skills/README.md +1 -0
- package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +5 -5
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-executor/SKILL.md +19 -7
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +43 -4
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +18 -6
- package/resources/codex-and-agents-customizations/.agents/skills/csharp/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/csharp-change-budget-router/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +16 -1
- package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +17 -1
- package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +2 -1
- package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +134 -0
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +13 -5
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +3 -2
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +27 -8
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/powershell-change-budget-router/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +11 -7
- package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +7 -3
- package/resources/codex-and-agents-customizations/.agents/skills/research-issue/SKILL.md +6 -0
- package/resources/codex-and-agents-customizations/.agents/skills/translate-claude-to-codex/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +4 -1
- package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c1.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c2.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3-elevated.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c4.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/codex-web-setup.sh +318 -273
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +97 -44
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-helpers.ps1 +105 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate-command.ps1 +190 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +54 -43
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +6 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +54 -41
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +35 -14
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +24 -11
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +13 -4
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/feature-folder-resolution.ps1 +373 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/feature-review-coverage-thresholds.ps1 +85 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-heredoc.ps1 +119 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation-operands.ps1 +198 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +254 -255
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload-powershell.ps1 +201 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload.ps1 +484 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +27 -110
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-dependency-guard.ps1 +114 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-docs-only-diff.ps1 +497 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +24 -8
- package/resources/codex-and-agents-customizations/.codex/prompts/feature-review-remediate.md +6 -4
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +11 -0
- package/resources/customizations/.github/agents/atomic_executor.agent.md +21 -11
- package/resources/customizations/.github/agents/atomic_planning.agent.md +8 -3
- package/resources/customizations/.github/agents/csharp-atomic-executor.agent.md +13 -9
- package/resources/customizations/.github/agents/csharp-atomic-planning.agent.md +2 -2
- package/resources/customizations/.github/agents/csharp-orchestrator.agent.md +2 -2
- package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +5 -5
- package/resources/customizations/.github/agents/orchestrator.agent.md +8 -5
- package/resources/customizations/.github/agents/powershell-atomic-executor.agent.md +13 -9
- package/resources/customizations/.github/agents/powershell-atomic-planning.agent.md +2 -2
- package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +2 -2
- package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +3 -3
- package/resources/customizations/.github/agents/python-atomic-executor.agent.md +13 -9
- package/resources/customizations/.github/agents/python-atomic-planning.agent.md +2 -2
- package/resources/customizations/.github/agents/python-orchestrator.agent.md +2 -2
- package/resources/customizations/.github/agents/task-researcher.agent.md +6 -0
- package/resources/customizations/.github/instructions/csharp-code-change.instructions.md +4 -4
- package/resources/customizations/.github/instructions/csharp-unit-test.instructions.md +2 -2
- package/resources/customizations/.github/prompts/generate-atomic-plan.prompt.md +16 -10
- package/resources/customizations/.github/prompts/orchestrate-csharp-work.prompt.md +2 -2
- package/resources/customizations/.github/prompts/orchestrate-powershell-work.prompt.md +1 -1
- package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +1 -1
- package/resources/customizations/.github/prompts/orchestrate-work.prompt.md +2 -2
- package/resources/customizations/.github/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +43 -4
- package/resources/customizations/.github/skills/csharp-change-budget-router/SKILL.md +1 -1
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/customizations/.github/skills/execution-environment/SKILL.md +134 -0
- package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +1 -1
- package/resources/customizations/.github/skills/powershell-change-budget-router/SKILL.md +1 -1
- package/resources/customizations/.github/skills/remediation-handoff-atomic-planner/SKILL.md +7 -3
- package/resources/feature-templates/bug/plan.yyyy-MM-ddTHH-mm.md +10 -0
- package/resources/feature-templates/feature/plan.yyyy-MM-ddTHH-mm.md +11 -0
- package/resources/feature-templates/refactor/plan.yyyy-MM-ddTHH-mm.md +11 -0
- package/resources/powershell/PoshQC/PoshQC.Coverage.psm1 +314 -0
- package/resources/powershell/PoshQC/PoshQC.CoverageMerge.psm1 +163 -0
- package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +296 -0
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +48 -32
- package/resources/powershell/PoshQC/PoshQC.psd1 +7 -1
- package/resources/powershell/PoshQC/PoshQC.psm1 +18 -5
- package/resources/powershell/PoshQC/README.md +21 -3
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +5 -319
|
@@ -8,45 +8,59 @@
|
|
|
8
8
|
and the serialized prompt contains the epic-mode kickoff marker "Epic mode: true".
|
|
9
9
|
|
|
10
10
|
Resolution and decision procedure:
|
|
11
|
-
1. Resolve the
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
4.
|
|
11
|
+
1. Resolve the worktree whose epic checkpoint records the prompt's
|
|
12
|
+
integration_branch: value (issue #690) and deny an unresolved or ambiguous target.
|
|
13
|
+
2. Collect the cited feature-folder candidates with the shared resolver
|
|
14
|
+
(feature-folder-resolution.ps1, issue #565): every docs/features/active/<token>
|
|
15
|
+
path is truncated to its feature-folder segment, so a nested research/ or
|
|
16
|
+
evidence/ citation names its own folder. No candidate denies.
|
|
17
|
+
3. Read artifacts/orchestration/epic-orchestrator-state.json beneath that worktree
|
|
18
|
+
and select the target among the candidates with Select-FeatureFolderTarget
|
|
19
|
+
-DependencyAware: a cited upstream dependency of another cited candidate is
|
|
20
|
+
pruned, and two or more remaining candidates deny as ambiguous. Neither string
|
|
21
|
+
length nor prompt position takes part in the selection.
|
|
22
|
+
4. Locate the target's features[] record and, for every depends_on entry, its own
|
|
23
|
+
record. An entry resolves through the union index, so an issue number matches
|
|
24
|
+
issue_num and a folder value matches the normalized feature_folder.
|
|
25
|
+
5. Deny with reason EPIC_WAVE_BARRIER_BLOCKED unless every dependency's merge_status
|
|
23
26
|
is merged or worktree_removed. A missing/unreadable checkpoint, an unresolved
|
|
24
27
|
target feature_folder, or a missing dependency record also denies (fail-closed).
|
|
25
28
|
|
|
26
|
-
This is the per-call deterrent (Layer 1) of the two-layer wave-barrier design
|
|
27
|
-
retrospective backstop (Layer 2) is the wave-barrier ordering invariant
|
|
28
|
-
validate_epic_orchestrator_state_text,
|
|
29
|
-
|
|
29
|
+
This is the per-call deterrent (Layer 1) of the two-layer wave-barrier design. The
|
|
30
|
+
retrospective backstop (Layer 2) is the wave-barrier ordering invariant of
|
|
31
|
+
validate_epic_orchestrator_state_text, run at epic-orchestrator SubagentStop time by
|
|
32
|
+
validate-orchestrator-output.ps1 through its PowerShell port
|
|
33
|
+
OrchestratorStateEpicWaveBarrier.psm1, with parity pinned by tests/fixtures/epic_wave_barrier/.
|
|
34
|
+
The SubagentStop transport and exit-code defect that hook depended on was resolved by epic #904
|
|
35
|
+
and issue #928: a violation blocks the stop with exit 2 and a stderr reason.
|
|
36
|
+
The two layers share no code.
|
|
30
37
|
|
|
31
38
|
.NOTES
|
|
32
|
-
Compatible with PowerShell 7+. Depends on WorktreeRunResolution.psm1 (issue #690)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
mock the boundary without
|
|
39
|
+
Compatible with PowerShell 7+. Depends on WorktreeRunResolution.psm1 (issue #690) and
|
|
40
|
+
the pure sibling feature-folder-resolution.ps1 (issue #565), each loaded inside a
|
|
41
|
+
guard: a failure is recorded and the decision denies naming the dependency. Filesystem
|
|
42
|
+
reads go through an injectable wrapper function so tests can mock the boundary without
|
|
43
|
+
writing temporary files.
|
|
36
44
|
#>
|
|
37
45
|
[CmdletBinding()]
|
|
38
46
|
param()
|
|
47
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
39
48
|
|
|
40
49
|
|
|
41
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
50
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunResolution.psm1' -ErrorRecord $_ }
|
|
44
53
|
$script:EpicWaveBarrierResolutionImportFailure = $null
|
|
54
|
+
|
|
55
|
+
# Shared feature-folder resolution (issue #565). Guarded so a failed dot-source denies
|
|
56
|
+
# rather than failing open; an earlier recorded failure is kept.
|
|
45
57
|
try {
|
|
46
|
-
|
|
58
|
+
. (Join-Path $PSScriptRoot 'feature-folder-resolution.ps1')
|
|
47
59
|
}
|
|
48
60
|
catch {
|
|
49
|
-
$script:EpicWaveBarrierResolutionImportFailure
|
|
61
|
+
if (-not $script:EpicWaveBarrierResolutionImportFailure) {
|
|
62
|
+
$script:EpicWaveBarrierResolutionImportFailure = 'feature-folder-resolution.ps1'
|
|
63
|
+
}
|
|
50
64
|
}
|
|
51
65
|
|
|
52
66
|
$script:AllowedMergeStatuses = @('merged', 'worktree_removed')
|
|
@@ -95,56 +109,35 @@ function Resolve-EpicWaveBarrierTarget {
|
|
|
95
109
|
function Find-EpicWaveBarrierFeatureFolderFromPrompt {
|
|
96
110
|
<#
|
|
97
111
|
.SYNOPSIS
|
|
98
|
-
|
|
99
|
-
|
|
112
|
+
Returns the distinct feature-folder basenames cited in a prompt, in first-occurrence
|
|
113
|
+
order. Returns no output when no folder is cited.
|
|
100
114
|
.DESCRIPTION
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
Delegates to Find-FeatureFolderCandidate in the shared resolver (issue #565): each
|
|
116
|
+
docs/features/active/<token> path, in either separator style, is truncated to its
|
|
117
|
+
feature-folder segment, so a nested research/ or evidence/ citation names its own
|
|
118
|
+
folder. Selection among several candidates is made by Select-FeatureFolderTarget.
|
|
105
119
|
.PARAMETER Prompt
|
|
106
120
|
The delegation prompt text under evaluation.
|
|
107
121
|
.OUTPUTS
|
|
108
|
-
System.String
|
|
122
|
+
System.String[]
|
|
109
123
|
#>
|
|
110
124
|
[CmdletBinding()]
|
|
111
|
-
[OutputType([string])]
|
|
125
|
+
[OutputType([string[]])]
|
|
112
126
|
param(
|
|
113
127
|
[Parameter(Mandatory)]
|
|
114
128
|
[AllowEmptyString()]
|
|
115
129
|
[string] $Prompt
|
|
116
130
|
)
|
|
117
131
|
|
|
118
|
-
|
|
119
|
-
return $null
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
$pattern = 'docs[\\/]+features[\\/]+active[\\/]+[^\s"''`]+'
|
|
123
|
-
$matchList = [regex]::Matches($Prompt, $pattern)
|
|
124
|
-
if ($matchList.Count -eq 0) {
|
|
125
|
-
return $null
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
$unique = @{}
|
|
129
|
-
foreach ($m in $matchList) {
|
|
130
|
-
$normalized = ($m.Value -replace '\\', '/').TrimEnd('/')
|
|
131
|
-
$unique[$normalized] = $true
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
$candidates = @(@($unique.Keys) | Sort-Object -Property Length -Descending)
|
|
135
|
-
$best = $candidates[0]
|
|
136
|
-
|
|
137
|
-
if ($best -match '\.md$') {
|
|
138
|
-
$best = $best -replace '/[^/]+\.md$', ''
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return ($best -split '/')[-1]
|
|
132
|
+
return [string[]]@(Find-FeatureFolderCandidate -Text $Prompt)
|
|
142
133
|
}
|
|
143
134
|
|
|
144
135
|
function Find-EpicWaveBarrierFeatureRecord {
|
|
145
136
|
<#
|
|
146
137
|
.SYNOPSIS
|
|
147
|
-
Locate the features[] record
|
|
138
|
+
Locate the features[] record the target basename identifies, through the shared
|
|
139
|
+
union index (Find-FeatureFolderRecord). A lifecycle-prefixed feature_folder value
|
|
140
|
+
such as active/<folder> matches its basename; zero or several matches return $null.
|
|
148
141
|
.PARAMETER Checkpoint
|
|
149
142
|
Parsed epic checkpoint, or $null when absent/unreadable.
|
|
150
143
|
.PARAMETER FeatureFolder
|
|
@@ -169,17 +162,7 @@ function Find-EpicWaveBarrierFeatureRecord {
|
|
|
169
162
|
return $null
|
|
170
163
|
}
|
|
171
164
|
|
|
172
|
-
|
|
173
|
-
foreach ($feature in @($Checkpoint.features)) {
|
|
174
|
-
$featureProps = @($feature.PSObject.Properties.Name)
|
|
175
|
-
if ($featureProps -notcontains 'feature_folder') {
|
|
176
|
-
continue
|
|
177
|
-
}
|
|
178
|
-
if (([string]$feature.feature_folder) -eq $FeatureFolder) {
|
|
179
|
-
return $feature
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return $null
|
|
165
|
+
return Find-FeatureFolderRecord -Records @($Checkpoint.features) -Reference $FeatureFolder
|
|
183
166
|
}
|
|
184
167
|
|
|
185
168
|
function Test-EpicWaveBarrierDependenciesMerged {
|
|
@@ -219,9 +202,11 @@ function Test-EpicWaveBarrierDependenciesMerged {
|
|
|
219
202
|
}
|
|
220
203
|
|
|
221
204
|
# Every dependency edge must be durably confirmed merged or worktree_removed before
|
|
222
|
-
# this wave's feature is allowed to start.
|
|
223
|
-
|
|
224
|
-
|
|
205
|
+
# this wave's feature is allowed to start. Each raw entry resolves through the union
|
|
206
|
+
# index, so an issue-number edge matches issue_num (issue #565).
|
|
207
|
+
$features = if (@($Checkpoint.PSObject.Properties.Name) -contains 'features') { @($Checkpoint.features) } else { @() }
|
|
208
|
+
foreach ($dependency in $dependsOn) {
|
|
209
|
+
$dependencyRecord = Find-FeatureFolderRecord -Records $features -Reference $dependency
|
|
225
210
|
if ($null -eq $dependencyRecord) {
|
|
226
211
|
return $false
|
|
227
212
|
}
|
|
@@ -280,11 +265,13 @@ function Invoke-EpicWaveBarrierDecision {
|
|
|
280
265
|
param(
|
|
281
266
|
[string] $ToolInputRaw
|
|
282
267
|
)
|
|
268
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EPIC_WAVE_BARRIER_BLOCKED:'
|
|
269
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
283
270
|
|
|
284
|
-
# A failed
|
|
271
|
+
# A failed dependency import denies before any other logic (issue #565).
|
|
285
272
|
if ($script:EpicWaveBarrierResolutionImportFailure) {
|
|
286
273
|
return Get-EpicWaveBarrierBlockDecision -Reason (
|
|
287
|
-
"EPIC_WAVE_BARRIER_BLOCKED: the
|
|
274
|
+
"EPIC_WAVE_BARRIER_BLOCKED: the dependency '$($script:EpicWaveBarrierResolutionImportFailure)' " +
|
|
288
275
|
'failed to import, so the epic checkpoint that governs this delegation cannot be located; the gate fails closed.')
|
|
289
276
|
}
|
|
290
277
|
|
|
@@ -312,8 +299,8 @@ function Invoke-EpicWaveBarrierDecision {
|
|
|
312
299
|
return Get-EpicWaveBarrierBlockDecision -Reason "EPIC_WAVE_BARRIER_BLOCKED: $($target.ReasonCode): $($target.Detail)"
|
|
313
300
|
}
|
|
314
301
|
|
|
315
|
-
$
|
|
316
|
-
if (-
|
|
302
|
+
$candidates = @(Find-EpicWaveBarrierFeatureFolderFromPrompt -Prompt $prompt)
|
|
303
|
+
if ($candidates.Count -eq 0) {
|
|
317
304
|
return Get-EpicWaveBarrierBlockDecision -Reason 'EPIC_WAVE_BARRIER_BLOCKED: an epic-mode orchestrator delegation must reference the target feature folder in the prompt so its dependency edges can be verified.'
|
|
318
305
|
}
|
|
319
306
|
|
|
@@ -327,6 +314,15 @@ function Invoke-EpicWaveBarrierDecision {
|
|
|
327
314
|
}
|
|
328
315
|
}
|
|
329
316
|
|
|
317
|
+
# The target is selected among the cited candidates (issue #565): a cited upstream
|
|
318
|
+
# dependency of another candidate is pruned, and an unresolved tie denies.
|
|
319
|
+
$records = if ($null -ne $checkpoint -and @($checkpoint.PSObject.Properties.Name) -contains 'features') { @($checkpoint.features) } else { @() }
|
|
320
|
+
$selection = Select-FeatureFolderTarget -Records $records -Candidate $candidates -DependencyAware
|
|
321
|
+
if ($selection.Status -eq 'Ambiguous') {
|
|
322
|
+
return Get-EpicWaveBarrierBlockDecision -Reason "EPIC_WAVE_BARRIER_BLOCKED: $($selection.Detail)."
|
|
323
|
+
}
|
|
324
|
+
$featureFolder = $selection.Basename
|
|
325
|
+
|
|
330
326
|
$featureRecord = Find-EpicWaveBarrierFeatureRecord -Checkpoint $checkpoint -FeatureFolder $featureFolder
|
|
331
327
|
if (Test-EpicWaveBarrierDependenciesMerged -Checkpoint $checkpoint -FeatureRecord $featureRecord) {
|
|
332
328
|
return Get-EpicWaveBarrierAllowDecision
|
|
@@ -377,5 +373,8 @@ function Invoke-EpicWaveBarrierEntryPoint {
|
|
|
377
373
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
378
374
|
return
|
|
379
375
|
}
|
|
376
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('EPIC_WAVE_BARRIER_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
377
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EPIC_WAVE_BARRIER_BLOCKED:'
|
|
378
|
+
if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
|
|
380
379
|
|
|
381
380
|
exit (Invoke-EpicWaveBarrierEntryPoint)
|
|
@@ -7,49 +7,29 @@
|
|
|
7
7
|
dot-sources. Holds:
|
|
8
8
|
|
|
9
9
|
- The import guard for WorktreeRunResolution.psm1. A failed import is recorded in
|
|
10
|
-
$script:
|
|
11
|
-
Get-
|
|
10
|
+
$script:HookDependencyFailures, and the gate's call to
|
|
11
|
+
Get-HookDependencyFailureDecision turns it into a deny, so a missing module
|
|
12
12
|
cannot make the gate exit non-zero and fail open.
|
|
13
13
|
- The two checkpoint read seams, relocated from the gate file with their names kept.
|
|
14
14
|
Each takes a mandatory absolute path composed beneath a resolved worktree root.
|
|
15
15
|
- The resolution seam Resolve-EpicWorktreeGateRunTarget, which locates the epic or
|
|
16
16
|
parallel checkpoint that records the removal target under worktree_path.
|
|
17
|
-
- Read-EpicWorktreeGateRunCheckpoint, which returns the target
|
|
18
|
-
|
|
17
|
+
- Read-EpicWorktreeGateRunCheckpoint, which returns the target, the parsed checkpoint
|
|
18
|
+
beneath it ($null when the target is unresolved), and the Path it read ($null when
|
|
19
|
+
the target is unresolved; issue #851).
|
|
20
|
+
- Find-EpicWorktreeGateParallelItemRecord, which locates the parallel items[] record
|
|
21
|
+
for the removal target, and Get-EpicWorktreeGateDenyDiagnostics, which builds the
|
|
22
|
+
diagnostics clause of the final deny from two read results without reading any
|
|
23
|
+
file (issue #851).
|
|
19
24
|
|
|
20
25
|
.NOTES
|
|
21
|
-
PowerShell 7+. Depends on Get-EpicWorktreeGateBlockDecision
|
|
22
|
-
ConvertFrom-EpicWorktreeGateJson from the gate
|
|
23
|
-
byte-identically under
|
|
26
|
+
PowerShell 7+. Depends on Get-EpicWorktreeGateBlockDecision,
|
|
27
|
+
ConvertFrom-EpicWorktreeGateJson, and Find-EpicWorktreeFeatureRecord from the gate
|
|
28
|
+
file, resolved at call time. Mirrored byte-identically under
|
|
29
|
+
extensions/drm-copilot/resources/claude-customizations/.
|
|
24
30
|
#>
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
$script:EpicWorktreeGateResolutionImportFailure = $null
|
|
28
|
-
try {
|
|
29
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
$script:EpicWorktreeGateResolutionImportFailure = 'WorktreeRunResolution.psm1'
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function Get-EpicWorktreeGateImportFailureDecision {
|
|
36
|
-
<#
|
|
37
|
-
.SYNOPSIS
|
|
38
|
-
Returns the deny for a failed worktree-resolution import, or $null.
|
|
39
|
-
.OUTPUTS
|
|
40
|
-
System.Collections.Specialized.OrderedDictionary, or $null when the import succeeded.
|
|
41
|
-
#>
|
|
42
|
-
[CmdletBinding()]
|
|
43
|
-
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
44
|
-
param()
|
|
45
|
-
|
|
46
|
-
if (-not $script:EpicWorktreeGateResolutionImportFailure) {
|
|
47
|
-
return $null
|
|
48
|
-
}
|
|
49
|
-
return Get-EpicWorktreeGateBlockDecision -Reason (
|
|
50
|
-
"EPIC_WORKTREE_REMOVAL_BLOCKED: the worktree-resolution module '$($script:EpicWorktreeGateResolutionImportFailure)' " +
|
|
51
|
-
'failed to import, so the run checkpoint that governs this removal cannot be located; the gate fails closed.')
|
|
52
|
-
}
|
|
32
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunResolution.psm1' -ErrorRecord $_ }
|
|
53
33
|
|
|
54
34
|
function Get-EpicWorktreeGateCheckpointContent {
|
|
55
35
|
<#
|
|
@@ -121,7 +101,9 @@ function Read-EpicWorktreeGateRunCheckpoint {
|
|
|
121
101
|
.PARAMETER WorktreePath
|
|
122
102
|
The removal target the command names.
|
|
123
103
|
.OUTPUTS
|
|
124
|
-
System.Management.Automation.PSCustomObject with Target
|
|
104
|
+
System.Management.Automation.PSCustomObject with Target, Checkpoint ($null when
|
|
105
|
+
unresolved, absent, or unparseable), and Path (the composed absolute checkpoint
|
|
106
|
+
path, or $null when the target is unresolved).
|
|
125
107
|
#>
|
|
126
108
|
[CmdletBinding()]
|
|
127
109
|
[OutputType([pscustomobject])]
|
|
@@ -132,6 +114,7 @@ function Read-EpicWorktreeGateRunCheckpoint {
|
|
|
132
114
|
|
|
133
115
|
$target = Resolve-EpicWorktreeGateRunTarget -Kind $Kind -WorktreePath $WorktreePath
|
|
134
116
|
$checkpoint = $null
|
|
117
|
+
$path = $null
|
|
135
118
|
if ($target.Status -eq 'SessionRoot' -or $target.Status -eq 'OtherWorktree') {
|
|
136
119
|
$path = Get-WorktreeRunCheckpointPath -Kind $Kind -WorktreeRoot $target.WorktreeRoot
|
|
137
120
|
$raw = if ($Kind -eq 'epic') { Get-EpicWorktreeGateCheckpointContent -Path $path } else { Get-EpicWorktreeGateParallelCheckpointContent -Path $path }
|
|
@@ -140,5 +123,114 @@ function Read-EpicWorktreeGateRunCheckpoint {
|
|
|
140
123
|
return [pscustomobject]@{
|
|
141
124
|
Target = $target
|
|
142
125
|
Checkpoint = $checkpoint
|
|
126
|
+
Path = $path
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function Find-EpicWorktreeGateParallelItemRecord {
|
|
131
|
+
<#
|
|
132
|
+
.SYNOPSIS
|
|
133
|
+
Locate the parallel items[] record whose worktree_path matches the target path.
|
|
134
|
+
.DESCRIPTION
|
|
135
|
+
Pure. Normalizes both paths exactly as Test-ParallelCheckpointAllowsWorktreeRemoval
|
|
136
|
+
does (backslash to slash, trailing slash trimmed, PowerShell -eq) and returns the
|
|
137
|
+
first matching entry (issue #851).
|
|
138
|
+
.PARAMETER Checkpoint
|
|
139
|
+
Parsed parallel-orchestrator checkpoint, or $null when absent/unreadable.
|
|
140
|
+
.PARAMETER WorktreePath
|
|
141
|
+
The removal target the command names.
|
|
142
|
+
.OUTPUTS
|
|
143
|
+
System.Object or $null
|
|
144
|
+
#>
|
|
145
|
+
[CmdletBinding()]
|
|
146
|
+
param(
|
|
147
|
+
[AllowNull()]
|
|
148
|
+
$Checkpoint,
|
|
149
|
+
|
|
150
|
+
[AllowNull()]
|
|
151
|
+
[string] $WorktreePath
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
|
|
155
|
+
return $null
|
|
156
|
+
}
|
|
157
|
+
$props = @($Checkpoint.PSObject.Properties.Name)
|
|
158
|
+
if ($props -notcontains 'items' -or $null -eq $Checkpoint.items) {
|
|
159
|
+
return $null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
$normalizedTarget = ($WorktreePath -replace '\\', '/').TrimEnd('/')
|
|
163
|
+
foreach ($item in @($Checkpoint.items)) {
|
|
164
|
+
if ($null -eq $item) {
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
$itemProps = @($item.PSObject.Properties.Name)
|
|
168
|
+
if ($itemProps -notcontains 'worktree_path') {
|
|
169
|
+
continue
|
|
170
|
+
}
|
|
171
|
+
$normalizedItemPath = (([string]$item.worktree_path) -replace '\\', '/').TrimEnd('/')
|
|
172
|
+
if ($normalizedItemPath -eq $normalizedTarget) {
|
|
173
|
+
return $item
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return $null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function Get-EpicWorktreeGateDenyDiagnostics {
|
|
180
|
+
<#
|
|
181
|
+
.SYNOPSIS
|
|
182
|
+
Build the diagnostics clause of the final deny from the two read results (issue #851).
|
|
183
|
+
.DESCRIPTION
|
|
184
|
+
Pure; reads no file. Names, for each run kind, the target status, the checkpoint
|
|
185
|
+
path read (or that none was read), and the outcome of the record match: the
|
|
186
|
+
matched record's merge_status, an absent merge_status, no matching record, an
|
|
187
|
+
absent or unparseable checkpoint, or not evaluated when no checkpoint was read.
|
|
188
|
+
.PARAMETER EpicRead
|
|
189
|
+
The epic read result (Target, Checkpoint, Path).
|
|
190
|
+
.PARAMETER ParallelRead
|
|
191
|
+
The parallel read result (Target, Checkpoint, Path).
|
|
192
|
+
.PARAMETER WorktreePath
|
|
193
|
+
The removal target the command names.
|
|
194
|
+
.OUTPUTS
|
|
195
|
+
System.String
|
|
196
|
+
#>
|
|
197
|
+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'The name is fixed by the issue #851 plan contract; the plural noun names the one diagnostics clause it returns.')]
|
|
198
|
+
[CmdletBinding()]
|
|
199
|
+
[OutputType([string])]
|
|
200
|
+
param(
|
|
201
|
+
[Parameter(Mandatory)] $EpicRead,
|
|
202
|
+
[Parameter(Mandatory)] $ParallelRead,
|
|
203
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreePath
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
$parts = foreach ($kind in @('epic', 'parallel')) {
|
|
207
|
+
$read = if ($kind -eq 'epic') { $EpicRead } else { $ParallelRead }
|
|
208
|
+
$pathText = if ($null -ne $read.Path) { "checkpoint '$($read.Path)'" } else { 'no checkpoint read' }
|
|
209
|
+
if ($null -eq $read.Path) {
|
|
210
|
+
$recordText = 'not evaluated'
|
|
211
|
+
}
|
|
212
|
+
elseif ($null -eq $read.Checkpoint) {
|
|
213
|
+
$recordText = 'checkpoint absent or unparseable'
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
$record = if ($kind -eq 'epic') {
|
|
217
|
+
Find-EpicWorktreeFeatureRecord -Checkpoint $read.Checkpoint -WorktreePath $WorktreePath
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
Find-EpicWorktreeGateParallelItemRecord -Checkpoint $read.Checkpoint -WorktreePath $WorktreePath
|
|
221
|
+
}
|
|
222
|
+
$arrayName = if ($kind -eq 'epic') { 'features' } else { 'items' }
|
|
223
|
+
$recordText = if ($null -eq $record) {
|
|
224
|
+
"no matching $arrayName[] record"
|
|
225
|
+
}
|
|
226
|
+
elseif (@($record.PSObject.Properties.Name) -contains 'merge_status') {
|
|
227
|
+
"merge_status '$([string]$record.merge_status)'"
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
'merge_status absent'
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
'{0} run {1} ({2}), {3}' -f $kind, $read.Target.Status, $pathText, $recordText
|
|
143
234
|
}
|
|
235
|
+
return ('Diagnostics: {0}; {1}.' -f $parts[0], $parts[1])
|
|
144
236
|
}
|
package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1
CHANGED
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
enforce-epic-worktree-removal-gate-resolution.ps1. An ambiguous target denies before
|
|
56
56
|
any allow; when neither kind resolves, the deny names TARGET_WORKTREE_NOT_DERIVABLE.
|
|
57
57
|
|
|
58
|
+
Issues #789 and #851: the final deny carries one leading EPIC_WORKTREE_REMOVAL_BLOCKED
|
|
59
|
+
token and ends with a diagnostics clause naming each run kind's status, checkpoint
|
|
60
|
+
path, and matched record; the allow-or-deny decision is unchanged.
|
|
61
|
+
|
|
58
62
|
.NOTES
|
|
59
63
|
Compatible with PowerShell 7+. Depends on WorktreeRunResolution.psm1 through the
|
|
60
64
|
dot-sourced resolution sibling, whose import guard denies on a failed import.
|
|
@@ -63,18 +67,19 @@
|
|
|
63
67
|
#>
|
|
64
68
|
[CmdletBinding()]
|
|
65
69
|
param()
|
|
70
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
66
71
|
|
|
67
72
|
|
|
68
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
73
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
|
|
69
74
|
# Sanctioned-removal manifest reader (issue #635), consumed by the manifest branch in
|
|
70
75
|
# Invoke-EpicWorktreeRemovalGateDecision.
|
|
71
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/cleanup-manifest/CleanupWorktreeManifest.psm1') -Force
|
|
76
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/cleanup-manifest/CleanupWorktreeManifest.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'CleanupWorktreeManifest.psm1' -ErrorRecord $_ }
|
|
72
77
|
# Shared command-line parser (issue #545), consumed by the scope filter in
|
|
73
|
-
# Invoke-EpicWorktreeRemovalGateDecision and by
|
|
74
|
-
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
75
|
-
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
78
|
+
# Invoke-EpicWorktreeRemovalGateDecision and by Resolve-CommandLineInvocationTarget.
|
|
79
|
+
try { . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-scanner.ps1' -ErrorRecord $_ }
|
|
80
|
+
try { . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-invocation.ps1' -ErrorRecord $_ }
|
|
76
81
|
# Checkpoint read seams, the import guard, and run-target resolution (issue #690).
|
|
77
|
-
. (Join-Path $PSScriptRoot 'enforce-epic-worktree-removal-gate-resolution.ps1')
|
|
82
|
+
try { . (Join-Path $PSScriptRoot 'enforce-epic-worktree-removal-gate-resolution.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-worktree-removal-gate-resolution.ps1' -ErrorRecord $_ }
|
|
78
83
|
$script:AllowedMergeStatuses = @('merged', 'worktree_removed')
|
|
79
84
|
# Sanctioned-removal manifest location. Recorded here so every hook-read document this
|
|
80
85
|
# gate consults is named; the module owns the read itself.
|
|
@@ -108,47 +113,6 @@ function ConvertFrom-EpicWorktreeGateJson {
|
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
function Get-EpicWorktreeRemovalCommandPath {
|
|
112
|
-
<#
|
|
113
|
-
.SYNOPSIS
|
|
114
|
-
Extract the target worktree path argument from a git worktree remove command.
|
|
115
|
-
.DESCRIPTION
|
|
116
|
-
The operand comes from the segment that structurally invokes git worktree remove,
|
|
117
|
-
so a 'cd <path> &&' segment chained before the removal contributes nothing and a
|
|
118
|
-
quoted mention of the phrase resolves to no operand at all. Quotes around the path
|
|
119
|
-
are already stripped by the tokenizer.
|
|
120
|
-
|
|
121
|
-
'--force' is a zero-argument flag, so it never contributes an operand and may be
|
|
122
|
-
written on either side of the target path. Its presence is read structurally through
|
|
123
|
-
Test-CommandLineFlag rather than by searching the raw text, so a '--force' spelling
|
|
124
|
-
that appears inside an unrelated quoted argument cannot change how the operand list
|
|
125
|
-
is read. When no operand resolves, a present '--force' is reported in its place, so
|
|
126
|
-
the checkpoint lookup fails closed on a value that matches no recorded worktree_path
|
|
127
|
-
- the same value the previous raw-text pattern returned for that input.
|
|
128
|
-
.PARAMETER CommandText
|
|
129
|
-
The Bash command text under evaluation.
|
|
130
|
-
.OUTPUTS
|
|
131
|
-
System.String or $null
|
|
132
|
-
#>
|
|
133
|
-
[CmdletBinding()]
|
|
134
|
-
[OutputType([string])]
|
|
135
|
-
param(
|
|
136
|
-
[Parameter(Mandatory)]
|
|
137
|
-
[string] $CommandText
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
$hasForce = Test-CommandLineFlag -CommandText $CommandText -CommandWord 'git' -SubcommandPath @('worktree', 'remove') -FlagName '--force'
|
|
141
|
-
$operands = @(Get-CommandLineOperand -CommandText $CommandText -CommandWord 'git' -SubcommandPath @('worktree', 'remove'))
|
|
142
|
-
|
|
143
|
-
if ($operands.Count -gt 0) {
|
|
144
|
-
return $operands[0]
|
|
145
|
-
}
|
|
146
|
-
if ($hasForce) {
|
|
147
|
-
return '--force'
|
|
148
|
-
}
|
|
149
|
-
return $null
|
|
150
|
-
}
|
|
151
|
-
|
|
152
116
|
function Find-EpicWorktreeFeatureRecord {
|
|
153
117
|
<#
|
|
154
118
|
.SYNOPSIS
|
|
@@ -331,10 +295,8 @@ function Invoke-EpicWorktreeRemovalGateDecision {
|
|
|
331
295
|
)
|
|
332
296
|
|
|
333
297
|
# A failed worktree-resolution import denies before any other logic (issue #690).
|
|
334
|
-
$
|
|
335
|
-
if ($null -ne $
|
|
336
|
-
return $importFailure
|
|
337
|
-
}
|
|
298
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EPIC_WORKTREE_REMOVAL_BLOCKED:'
|
|
299
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
338
300
|
|
|
339
301
|
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
340
302
|
if (-not $payload.IsValid) {
|
|
@@ -356,7 +318,43 @@ function Invoke-EpicWorktreeRemovalGateDecision {
|
|
|
356
318
|
return Get-EpicWorktreeGateAllowDecision
|
|
357
319
|
}
|
|
358
320
|
|
|
359
|
-
|
|
321
|
+
# Issue #824: the removal targets are derived structurally from every invocation.
|
|
322
|
+
# A target that cannot be derived denies before any checkpoint is read, and every
|
|
323
|
+
# derived target must be authorized on its own.
|
|
324
|
+
$resolution = Resolve-CommandLineInvocationTarget -CommandText $commandText -CommandWord 'git' -SubcommandPath @('worktree', 'remove')
|
|
325
|
+
if ($resolution.Status -eq 'NoMatch') {
|
|
326
|
+
return Get-EpicWorktreeGateAllowDecision
|
|
327
|
+
}
|
|
328
|
+
if ($resolution.Status -ne 'Targets') {
|
|
329
|
+
return Get-EpicWorktreeGateBlockDecision -Reason 'EPIC_WORKTREE_REMOVAL_BLOCKED: TARGET_WORKTREE_NOT_DERIVABLE: the git worktree remove target cannot be derived from the command text. No checkpoint can authorize this removal.'
|
|
330
|
+
}
|
|
331
|
+
foreach ($target in @($resolution.Targets)) {
|
|
332
|
+
$denial = Get-EpicWorktreeRemovalTargetDenial -WorktreePath $target
|
|
333
|
+
if ($null -ne $denial) {
|
|
334
|
+
return $denial
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return Get-EpicWorktreeGateAllowDecision
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function Get-EpicWorktreeRemovalTargetDenial {
|
|
341
|
+
<#
|
|
342
|
+
.SYNOPSIS
|
|
343
|
+
Evaluate one derived removal target and return its deny decision, or $null when authorized.
|
|
344
|
+
.DESCRIPTION
|
|
345
|
+
Holds the per-path authorization cascade (epic checkpoint, parallel checkpoint,
|
|
346
|
+
sanctioned-removal manifest) for a single worktree path (issue #824).
|
|
347
|
+
.PARAMETER WorktreePath
|
|
348
|
+
One removal target derived by Resolve-CommandLineInvocationTarget.
|
|
349
|
+
.OUTPUTS
|
|
350
|
+
System.Collections.Specialized.OrderedDictionary or $null
|
|
351
|
+
#>
|
|
352
|
+
[CmdletBinding()]
|
|
353
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
354
|
+
param(
|
|
355
|
+
[Parameter(Mandatory)]
|
|
356
|
+
[string] $WorktreePath
|
|
357
|
+
)
|
|
360
358
|
|
|
361
359
|
# Each run checkpoint is read beneath the worktree that records this path (issue #690);
|
|
362
360
|
# an ambiguous target denies before any allow or manifest evaluation.
|
|
@@ -371,12 +369,12 @@ function Invoke-EpicWorktreeRemovalGateDecision {
|
|
|
371
369
|
|
|
372
370
|
$featureRecord = Find-EpicWorktreeFeatureRecord -Checkpoint $checkpoint -WorktreePath $worktreePath
|
|
373
371
|
if (Test-EpicWorktreeRemovalAllowed -FeatureRecord $featureRecord) {
|
|
374
|
-
return
|
|
372
|
+
return $null
|
|
375
373
|
}
|
|
376
374
|
|
|
377
375
|
$parallelCheckpoint = $parallelRead.Checkpoint
|
|
378
376
|
if (Test-ParallelCheckpointAllowsWorktreeRemoval -Checkpoint $parallelCheckpoint -WorktreePath $worktreePath) {
|
|
379
|
-
return
|
|
377
|
+
return $null
|
|
380
378
|
}
|
|
381
379
|
|
|
382
380
|
# Sanctioned-removal manifest branch (issue #635). It runs last, so an
|
|
@@ -392,15 +390,18 @@ function Invoke-EpicWorktreeRemovalGateDecision {
|
|
|
392
390
|
if (-not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $checkpoint -RecordArrayName 'features' -WorktreePath $worktreePath) -and
|
|
393
391
|
-not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $parallelCheckpoint -RecordArrayName 'items' -WorktreePath $worktreePath) -and
|
|
394
392
|
(Test-CleanupWorktreeManifestAuthorizesRemoval -WorktreePath $worktreePath)) {
|
|
395
|
-
return
|
|
393
|
+
return $null
|
|
396
394
|
}
|
|
397
395
|
|
|
398
396
|
# When neither kind resolved, the deny names the resolution reason first (issue #690).
|
|
397
|
+
# The gate token leads once and a diagnostics clause for this target closes the text
|
|
398
|
+
# (issues #789, #851).
|
|
399
399
|
$prefix = ''
|
|
400
400
|
if ($epicRead.Target.Status -eq 'NoTarget' -and $parallelRead.Target.Status -eq 'NoTarget') {
|
|
401
|
-
$prefix = "
|
|
401
|
+
$prefix = "$($epicRead.Target.ReasonCode): $($epicRead.Target.Detail). "
|
|
402
402
|
}
|
|
403
|
-
|
|
403
|
+
$diagnostics = Get-EpicWorktreeGateDenyDiagnostics -EpicRead $epicRead -ParallelRead $parallelRead -WorktreePath $worktreePath
|
|
404
|
+
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)
|
|
404
405
|
}
|
|
405
406
|
|
|
406
407
|
function Invoke-EpicWorktreeRemovalGateEntryPoint {
|
|
@@ -445,6 +446,9 @@ function Invoke-EpicWorktreeRemovalGateEntryPoint {
|
|
|
445
446
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
446
447
|
return
|
|
447
448
|
}
|
|
449
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('EPIC_WORKTREE_REMOVAL_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
450
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EPIC_WORKTREE_REMOVAL_BLOCKED:'
|
|
451
|
+
if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
|
|
448
452
|
|
|
449
453
|
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
450
454
|
# decision JSON before it. `exit (<call>)` would capture BOTH into the exit
|
|
@@ -41,8 +41,9 @@
|
|
|
41
41
|
#>
|
|
42
42
|
[CmdletBinding()]
|
|
43
43
|
param()
|
|
44
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
44
45
|
|
|
45
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
46
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
|
|
46
47
|
|
|
47
48
|
function Test-EvidenceLocationForbidden {
|
|
48
49
|
<#
|
|
@@ -150,6 +151,8 @@ function Invoke-EvidenceLocationDecision {
|
|
|
150
151
|
[AllowEmptyString()]
|
|
151
152
|
[string] $ToolInputRaw
|
|
152
153
|
)
|
|
154
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EVIDENCE_LOCATION_BLOCKED:'
|
|
155
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
153
156
|
|
|
154
157
|
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
155
158
|
if (-not $payload.IsValid) {
|
|
@@ -211,6 +214,9 @@ function Invoke-EvidenceLocationEntryPoint {
|
|
|
211
214
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
212
215
|
return
|
|
213
216
|
}
|
|
217
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('EVIDENCE_LOCATION_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
218
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'EVIDENCE_LOCATION_BLOCKED:'
|
|
219
|
+
if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
|
|
214
220
|
|
|
215
221
|
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
216
222
|
# decision JSON before it. `exit (<call>)` would capture BOTH into the exit
|