@danmoisan/drm-copilot-mcp 1.1.18 → 1.1.19
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 +989 -662
- package/package.json +3 -2
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +11 -0
- package/resources/claude-customizations/.claude/agents/atomic-planner.md +10 -0
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +12 -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 +5 -4
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +31 -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/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-resolution.ps1 +116 -59
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +55 -23
- 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 +493 -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 +66 -38
- 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 +64 -32
- 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 +31 -34
- 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/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 +47 -8
- 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 +5 -1
- 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/epic-orchestrate/SKILL.md +33 -6
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- 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 +14 -6
- 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 +79 -16
- 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/pr-author/SKILL.md +3 -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 +2 -2
- package/resources/claude-customizations/pack-manifests/core.json +23 -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 +11 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +4 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +10 -0
- package/resources/codex-and-agents-customizations/.agents/skills/csharp/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 +8 -0
- 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/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 +15 -6
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/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/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 +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +2 -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 +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +2 -2
- 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.ps1 +37 -5
- 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 +493 -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/pack-manifests/core.json +9 -0
- package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +2 -2
- 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/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +4 -0
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +1 -1
- package/resources/powershell/PoshQC/PoshQC.Coverage.psm1 +314 -0
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +25 -28
- package/resources/powershell/PoshQC/PoshQC.psm1 +1 -0
- package/resources/powershell/PoshQC/README.md +8 -2
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +2 -319
|
@@ -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
|
|
@@ -1,32 +1,59 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
Pre-tool-use hook that blocks writes to a feature folder's plan
|
|
4
|
-
|
|
3
|
+
Pre-tool-use hook that blocks writes to a feature folder's plan file until the
|
|
4
|
+
prerequisite documents its persisted work mode requires exist in that same folder.
|
|
5
5
|
|
|
6
6
|
.DESCRIPTION
|
|
7
7
|
Invoked by the Claude Code PreToolUse hook on Write or Edit operations. Acquires
|
|
8
8
|
the hook payload through the shared reader and reads file_path from the envelope's
|
|
9
|
-
nested tool_input.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
each of issue.md, spec.md, and user-story.md exists in the same folder.
|
|
13
|
-
|
|
14
|
-
If any of the three sibling files is missing, the script emits a PreToolUse
|
|
15
|
-
JSON response with hookSpecificOutput.permissionDecision='deny' and exits 0 so
|
|
16
|
-
Claude Code surfaces the reason. All other paths pass through with
|
|
9
|
+
nested tool_input. The gate applies to a feature-folder plan file under
|
|
10
|
+
docs/features/(active|archive)/<folder>/: a literal plan.md or a timestamped
|
|
11
|
+
plan.<yyyy-MM-ddTHH-mm>.md (issue #568). Other paths pass through with
|
|
17
12
|
permissionDecision='allow'.
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
For a plan file, the script reads the folder's issue.md through
|
|
15
|
+
Get-FeatureFolderIssueContent, resolves the persisted '- Work Mode:' marker with the
|
|
16
|
+
shared Resolve-FeatureFolderWorkMode, and takes the required set from the shared
|
|
17
|
+
Get-FeatureFolderPlanPrerequisite:
|
|
18
|
+
- minor-audit -> issue.md
|
|
19
|
+
- full-bug -> issue.md, spec.md
|
|
20
|
+
- full-feature and legacy full -> issue.md, spec.md, user-story.md
|
|
21
|
+
A missing, empty, malformed, or unrecognized marker, or an unreadable issue.md,
|
|
22
|
+
fails closed to the full-feature set. Because issue.md is in every set, an absent
|
|
23
|
+
issue.md always denies.
|
|
24
|
+
|
|
25
|
+
If any required file is missing, the script emits a PreToolUse JSON response with
|
|
26
|
+
hookSpecificOutput.permissionDecision='deny' whose reason names the plan file, the
|
|
27
|
+
resolved work mode, and the missing files, and exits 0 so Claude Code surfaces it.
|
|
28
|
+
|
|
29
|
+
Filesystem reads go through Get-FeatureFolderFileExistence and
|
|
30
|
+
Get-FeatureFolderIssueContent so tests can inject fakes without touching disk.
|
|
21
31
|
|
|
22
32
|
.NOTES
|
|
23
|
-
Compatible with PowerShell 7+. Read-only validation gate; no state mutation.
|
|
33
|
+
Compatible with PowerShell 7+. Read-only validation gate; no state mutation. The pure
|
|
34
|
+
sibling feature-folder-resolution.ps1 (issue #565) is dot-sourced inside a guard: when
|
|
35
|
+
it cannot be loaded, every plan-file write denies and every other path stays allowed.
|
|
24
36
|
#>
|
|
25
37
|
[CmdletBinding()]
|
|
26
38
|
param()
|
|
39
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
|
|
43
|
+
|
|
44
|
+
# Shared work-mode parser and prerequisite map (issue #565). Guarded so a failed
|
|
45
|
+
# dot-source denies plan writes rather than failing open.
|
|
46
|
+
$script:FeatureFolderOrderResolutionImportFailure = $null
|
|
47
|
+
try {
|
|
48
|
+
. (Join-Path $PSScriptRoot 'feature-folder-resolution.ps1')
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
$script:FeatureFolderOrderResolutionImportFailure = 'feature-folder-resolution.ps1'
|
|
52
|
+
}
|
|
27
53
|
|
|
54
|
+
# The plan leaf a gated path ends in: plan.md or plan.<yyyy-MM-ddTHH-mm>.md (issue #568).
|
|
55
|
+
$script:FeaturePlanLeafPattern = '/plan(\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2})?\.md$'
|
|
28
56
|
|
|
29
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
30
57
|
function Get-FeatureFolderFileExistence {
|
|
31
58
|
<#
|
|
32
59
|
.SYNOPSIS
|
|
@@ -44,25 +71,57 @@ function Get-FeatureFolderFileExistence {
|
|
|
44
71
|
return [bool](Test-Path -LiteralPath $Path -PathType Leaf)
|
|
45
72
|
}
|
|
46
73
|
|
|
74
|
+
function Get-FeatureFolderIssueContent {
|
|
75
|
+
<#
|
|
76
|
+
.SYNOPSIS
|
|
77
|
+
Reads a feature folder's issue.md, or returns $null when the file is absent or
|
|
78
|
+
cannot be read. Tests mock this seam; a $null result fails closed to the
|
|
79
|
+
full-feature prerequisite set.
|
|
80
|
+
.PARAMETER FeatureFolder
|
|
81
|
+
Normalized (forward-slash) feature-folder path.
|
|
82
|
+
#>
|
|
83
|
+
[CmdletBinding()]
|
|
84
|
+
[OutputType([string])]
|
|
85
|
+
param(
|
|
86
|
+
[Parameter(Mandatory)]
|
|
87
|
+
[string] $FeatureFolder
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
$issuePath = "$FeatureFolder/issue.md"
|
|
91
|
+
if (-not (Test-Path -LiteralPath $issuePath -PathType Leaf)) {
|
|
92
|
+
return $null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
return Get-Content -LiteralPath $issuePath -Raw -ErrorAction Stop
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return $null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
47
103
|
function Get-FeatureFolderMissingFile {
|
|
48
104
|
<#
|
|
49
105
|
.SYNOPSIS
|
|
50
|
-
Returns the list of required sibling files missing alongside plan.
|
|
106
|
+
Returns the list of required sibling files missing alongside the plan file.
|
|
51
107
|
.PARAMETER PlanFilePath
|
|
52
|
-
Normalized (forward-slash) path to the plan.md target.
|
|
108
|
+
Normalized (forward-slash) path to the plan.md or plan.<timestamp>.md target.
|
|
109
|
+
.PARAMETER RequiredFile
|
|
110
|
+
The prerequisite file names to probe. Defaults to the full-feature set.
|
|
53
111
|
#>
|
|
54
112
|
[CmdletBinding()]
|
|
55
113
|
[OutputType([string[]])]
|
|
56
114
|
param(
|
|
57
115
|
[Parameter(Mandatory)]
|
|
58
|
-
[string] $PlanFilePath
|
|
116
|
+
[string] $PlanFilePath,
|
|
117
|
+
|
|
118
|
+
[string[]] $RequiredFile = @('issue.md', 'spec.md', 'user-story.md')
|
|
59
119
|
)
|
|
60
120
|
|
|
61
|
-
$folder = $PlanFilePath -replace
|
|
62
|
-
$required = @('issue.md', 'spec.md', 'user-story.md')
|
|
121
|
+
$folder = $PlanFilePath -replace $script:FeaturePlanLeafPattern, ''
|
|
63
122
|
[System.Collections.Generic.List[string]] $missing = [System.Collections.Generic.List[string]]::new()
|
|
64
123
|
|
|
65
|
-
foreach ($name in $
|
|
124
|
+
foreach ($name in $RequiredFile) {
|
|
66
125
|
$siblingPath = "$folder/$name"
|
|
67
126
|
if (-not (Get-FeatureFolderFileExistence -Path $siblingPath)) {
|
|
68
127
|
$missing.Add($name)
|
|
@@ -75,7 +134,9 @@ function Get-FeatureFolderMissingFile {
|
|
|
75
134
|
function Test-IsFeaturePlanPath {
|
|
76
135
|
<#
|
|
77
136
|
.SYNOPSIS
|
|
78
|
-
Returns $true if the normalized path targets a feature-folder plan.md
|
|
137
|
+
Returns $true if the normalized path targets a feature-folder plan.md or a
|
|
138
|
+
timestamped plan.<yyyy-MM-ddTHH-mm>.md directly inside an active or archive
|
|
139
|
+
feature folder.
|
|
79
140
|
#>
|
|
80
141
|
[CmdletBinding()]
|
|
81
142
|
[OutputType([bool])]
|
|
@@ -84,7 +145,30 @@ function Test-IsFeaturePlanPath {
|
|
|
84
145
|
[string] $NormalizedPath
|
|
85
146
|
)
|
|
86
147
|
|
|
87
|
-
return $NormalizedPath -match '(^|/)docs/features/(active|archive)/[^/]+/plan
|
|
148
|
+
return $NormalizedPath -match '(^|/)docs/features/(active|archive)/[^/]+/plan(\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2})?\.md$'
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function Get-FeatureFolderOrderDenyDecision {
|
|
152
|
+
<#
|
|
153
|
+
.SYNOPSIS
|
|
154
|
+
Builds the PreToolUse deny decision carrying the given reason.
|
|
155
|
+
.PARAMETER Reason
|
|
156
|
+
The deny reason, beginning with FEATURE_FOLDER_ORDER_BLOCKED:.
|
|
157
|
+
#>
|
|
158
|
+
[CmdletBinding()]
|
|
159
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
160
|
+
param(
|
|
161
|
+
[Parameter(Mandatory)]
|
|
162
|
+
[string] $Reason
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return [ordered]@{
|
|
166
|
+
hookSpecificOutput = [ordered]@{
|
|
167
|
+
hookEventName = 'PreToolUse'
|
|
168
|
+
permissionDecision = 'deny'
|
|
169
|
+
permissionDecisionReason = $Reason
|
|
170
|
+
}
|
|
171
|
+
}
|
|
88
172
|
}
|
|
89
173
|
|
|
90
174
|
function Invoke-FeatureFolderOrderDecision {
|
|
@@ -103,18 +187,15 @@ function Invoke-FeatureFolderOrderDecision {
|
|
|
103
187
|
[AllowEmptyString()]
|
|
104
188
|
[string] $ToolInputRaw
|
|
105
189
|
)
|
|
190
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'FEATURE_FOLDER_ORDER_BLOCKED:'
|
|
191
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
106
192
|
|
|
107
193
|
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
108
194
|
if (-not $payload.IsValid) {
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
permissionDecisionReason = 'FEATURE_FOLDER_ORDER_BLOCKED: payload anomaly - ' +
|
|
114
|
-
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $payload.Anomaly) +
|
|
115
|
-
'. The gate fails closed on an envelope it cannot read.'
|
|
116
|
-
}
|
|
117
|
-
}
|
|
195
|
+
return Get-FeatureFolderOrderDenyDecision -Reason (
|
|
196
|
+
'FEATURE_FOLDER_ORDER_BLOCKED: payload anomaly - ' +
|
|
197
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $payload.Anomaly) +
|
|
198
|
+
'. The gate fails closed on an envelope it cannot read.')
|
|
118
199
|
}
|
|
119
200
|
|
|
120
201
|
$filePath = Get-ClaudeHookToolInputString -ToolInput $payload.Value -Name 'file_path'
|
|
@@ -128,19 +209,25 @@ function Invoke-FeatureFolderOrderDecision {
|
|
|
128
209
|
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
129
210
|
}
|
|
130
211
|
|
|
131
|
-
$
|
|
212
|
+
$planLeaf = ($normalized -split '/')[-1]
|
|
213
|
+
if ($script:FeatureFolderOrderResolutionImportFailure) {
|
|
214
|
+
return Get-FeatureFolderOrderDenyDecision -Reason (
|
|
215
|
+
"FEATURE_FOLDER_ORDER_BLOCKED: the shared resolver '$($script:FeatureFolderOrderResolutionImportFailure)' " +
|
|
216
|
+
"failed to load, so the prerequisite documents for $planLeaf cannot be determined; the gate fails closed.")
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
$folder = $normalized -replace $script:FeaturePlanLeafPattern, ''
|
|
220
|
+
$workMode = Resolve-FeatureFolderWorkMode -IssueContent (Get-FeatureFolderIssueContent -FeatureFolder $folder)
|
|
221
|
+
$required = @(Get-FeatureFolderPlanPrerequisite -WorkMode $workMode)
|
|
222
|
+
$missing = @(Get-FeatureFolderMissingFile -PlanFilePath $normalized -RequiredFile $required)
|
|
132
223
|
if ($missing.Count -eq 0) {
|
|
133
224
|
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
134
225
|
}
|
|
135
226
|
|
|
136
227
|
$list = ($missing -join ', ')
|
|
137
|
-
return
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
permissionDecision = 'deny'
|
|
141
|
-
permissionDecisionReason = "FEATURE_FOLDER_ORDER_BLOCKED: cannot write plan.md before producing prerequisite documents. Missing in feature folder: $list. Invoke the prd-feature subagent to generate the missing file(s) before authoring plan.md."
|
|
142
|
-
}
|
|
143
|
-
}
|
|
228
|
+
return Get-FeatureFolderOrderDenyDecision -Reason (
|
|
229
|
+
"FEATURE_FOLDER_ORDER_BLOCKED: cannot write $planLeaf before producing prerequisite documents for work mode '$workMode'. " +
|
|
230
|
+
"Missing in feature folder: $list. Invoke the prd-feature subagent to generate the missing file(s) before authoring the plan.")
|
|
144
231
|
}
|
|
145
232
|
|
|
146
233
|
function Invoke-FeatureFolderOrderEntryPoint {
|
|
@@ -185,6 +272,9 @@ function Invoke-FeatureFolderOrderEntryPoint {
|
|
|
185
272
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
186
273
|
return
|
|
187
274
|
}
|
|
275
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('FEATURE_FOLDER_ORDER_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
276
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'FEATURE_FOLDER_ORDER_BLOCKED:'
|
|
277
|
+
if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
|
|
188
278
|
|
|
189
279
|
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
190
280
|
# decision JSON before it. `exit (<call>)` would capture BOTH into the exit
|