@danmoisan/drm-copilot-mcp 1.1.25 → 1.1.27
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 +170 -52
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +3 -3
- package/resources/claude-customizations/.claude/agents/atomic-planner.md +2 -2
- package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +7 -7
- package/resources/claude-customizations/.claude/agents/feature-review.md +6 -3
- package/resources/claude-customizations/.claude/agents/orchestrator.md +11 -7
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +7 -3
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +6 -2
- package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +7 -7
- package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +6 -6
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-review.ps1 +699 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +16 -1
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +41 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-cleanup.ps1 +481 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +3 -1
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1 +69 -3
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +2 -2
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.cleanup.ps1 +246 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.docs-only.ps1 +1 -0
- package/resources/claude-customizations/.claude/hooks/hook-cleanup-context.ps1 +343 -0
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-rules.ps1 +50 -16
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +6 -1
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletionChecks.psm1 +51 -2
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +5 -2
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRemediationAccounting.psm1 +116 -4
- package/resources/claude-customizations/.claude/lib/parallel-admission/Invoke-ParallelAdmission.ps1 +626 -0
- package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmission.psm1 +508 -0
- package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmissionJson.psm1 +141 -0
- package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmissionPrecedence.psm1 +508 -0
- package/resources/claude-customizations/.claude/rules/csharp.md +8 -8
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +28 -2
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +17 -1
- package/resources/claude-customizations/.claude/rules/powershell.md +2 -2
- package/resources/claude-customizations/.claude/settings.json +1 -0
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +9 -13
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +93 -14
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +23 -10
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +54 -46
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_merge_lib.sh +234 -0
- package/resources/claude-customizations/.claude/skills/csharp-qa-gate/SKILL.md +14 -15
- package/resources/claude-customizations/.claude/skills/epic-progress-report/SKILL.md +338 -0
- package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.Logic.ps1 +248 -0
- package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.Metrics.ps1 +232 -0
- package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.Render.ps1 +196 -0
- package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.State.ps1 +286 -0
- package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.ps1 +475 -0
- package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +31 -16
- package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +21 -9
- package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +5 -5
- package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +4 -4
- package/resources/claude-customizations/.claude/skills/invoke-python-engineer/SKILL.md +4 -4
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +24 -2
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +71 -7
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +34 -2
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +82 -12
- package/resources/claude-customizations/.claude/skills/powershell-qa-gate/SKILL.md +16 -17
- package/resources/claude-customizations/.claude/skills/python-qa-gate/SKILL.md +15 -16
- package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +1 -1
- package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +6 -6
- package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +13 -14
- package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +4 -4
- package/resources/claude-customizations/pack-manifests/core.json +15 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +8 -12
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +13 -14
- package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +31 -16
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +5 -1
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +18 -5
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-csharp-engineer/SKILL.md +4 -4
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +4 -4
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +4 -4
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +24 -2
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +16 -1
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +3 -3
- package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/powershell-qa-gate/SKILL.md +15 -16
- package/resources/codex-and-agents-customizations/.agents/skills/python-qa-gate/SKILL.md +14 -15
- package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +2 -0
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +13 -14
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +4 -4
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-atomic-executor.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-orchestrator.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-executor.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-di-unit-test-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-orchestrator.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +7 -7
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +7 -7
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +7 -7
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +7 -7
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +7 -7
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +7 -7
- package/resources/codex-and-agents-customizations/.codex/agents/pytest-unit-test-coding.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-atomic-executor.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/python-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-execution-only-typed.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-orchestrator.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate-review.ps1 +699 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +29 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-cleanup.ps1 +481 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +4 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-cleanup-context.ps1 +343 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +51 -4
- package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +6 -6
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +3 -0
- package/resources/customizations/.github/agents/5.1-Beast-adjusted.agent.md +1 -1
- package/resources/customizations/.github/agents/Powershell DI Unit Test Engineer.agent.md +20 -26
- package/resources/customizations/.github/agents/atomic_executor.agent.md +4 -4
- package/resources/customizations/.github/agents/atomic_planning.agent.md +4 -4
- package/resources/customizations/.github/agents/commentary-remediation.agent.md +1 -1
- package/resources/customizations/.github/agents/commit-steward.agent.md +1 -1
- package/resources/customizations/.github/agents/csharp-atomic-executor.agent.md +14 -14
- package/resources/customizations/.github/agents/csharp-atomic-planning.agent.md +2 -2
- package/resources/customizations/.github/agents/csharp-orchestrator.agent.md +4 -4
- package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +19 -22
- package/resources/customizations/.github/agents/epic-review.agent.md +1 -1
- package/resources/customizations/.github/agents/gpt-5-beast-mode.agent.md +3 -3
- package/resources/customizations/.github/agents/orchestrator.agent.md +8 -8
- package/resources/customizations/.github/agents/powershell-atomic-executor.agent.md +12 -12
- package/resources/customizations/.github/agents/powershell-atomic-planning.agent.md +4 -4
- package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +6 -6
- package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +21 -24
- package/resources/customizations/.github/agents/pytest-unit-test-coding.agent.md +13 -17
- package/resources/customizations/.github/agents/python-atomic-executor.agent.md +11 -11
- package/resources/customizations/.github/agents/python-atomic-planning.agent.md +2 -2
- package/resources/customizations/.github/agents/python-execution-only-typed.agent.md +14 -17
- package/resources/customizations/.github/agents/python-orchestrator.agent.md +7 -7
- package/resources/customizations/.github/agents/python-typed-engineer.agent.md +17 -20
- package/resources/customizations/.github/agents/staged-review.agent.md +2 -2
- package/resources/customizations/.github/agents/tdd-green.agent.md +1 -1
- package/resources/customizations/.github/agents/tdd-red.agent.md +3 -3
- package/resources/customizations/.github/agents/tdd-refactor.agent.md +2 -2
- package/resources/customizations/.github/agents/typescript-engineer.agent.md +3 -3
- package/resources/customizations/.github/codex/execute-hard-lock.prompt.md +3 -3
- package/resources/customizations/.github/instructions/general-code-change.instructions.md +1 -1
- package/resources/customizations/.github/instructions/powershell-unit-test.instructions.md +1 -1
- package/resources/customizations/.github/instructions/python-code-change.instructions.md +5 -5
- package/resources/customizations/.github/instructions/python-suppressions.instructions.md +3 -3
- package/resources/customizations/.github/instructions/python-unit-test.instructions.md +1 -1
- package/resources/customizations/.github/prompts/execute-hard-lock.prompt.md +2 -2
- package/resources/customizations/.github/prompts/generate-commit-message-repo.prompt.md +2 -2
- package/resources/customizations/.github/prompts/remediate-comments.prompt.md +2 -2
- package/resources/customizations/.github/prompts/review-feature.prompt.md +1 -1
- package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +8 -12
- package/resources/customizations/.github/skills/execution-environment/SKILL.md +31 -16
- package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +15 -2
- package/resources/feature-templates/bug/plan.yyyy-MM-ddTHH-mm.md +4 -4
- package/resources/feature-templates/feature/plan.yyyy-MM-ddTHH-mm.md +3 -3
- package/resources/feature-templates/refactor/plan.yyyy-MM-ddTHH-mm.md +2 -2
- package/resources/powershell/PoshQC/PoshQC.ScanConfig.psm1 +3 -1
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +14 -12
- package/resources/powershell/PoshQC/PoshQC.psm1 +3 -3
- package/resources/powershell/PoshQC/README.md +8 -8
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +2 -2
- package/resources/templates/policy_audit/AGENTS.md +9 -10
- package/resources/templates/policy_audit/policy-audit.yyyy-MM-ddTHH-mm.md +19 -20
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
visible in the command text; gh and git run in the repository root, so a cd made by an
|
|
21
21
|
earlier shell call is not visible either; a push after the lookup can change the head
|
|
22
22
|
unless --match-head-commit is passed; and the control is policy-level, not cryptographic.
|
|
23
|
+
|
|
24
|
+
Review guard (issue #1097): under review_mode concurrent, enforce-epic-merge-gate-review.ps1
|
|
25
|
+
runs before every allow branch, with gh and git in the repository root, and denies a merge with
|
|
26
|
+
no passing review at a current head. Its shape check precedes the out-of-scope allow and denies
|
|
27
|
+
text that could be a merge and cannot be bound. Without the key it is inert and starts no process.
|
|
23
28
|
#>
|
|
24
29
|
[CmdletBinding()]
|
|
25
30
|
param()
|
|
@@ -35,6 +40,8 @@ try { . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1') } catch { Add-Ho
|
|
|
35
40
|
try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-command.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-command.ps1' -ErrorRecord $_ }
|
|
36
41
|
# Shared docs-only diff classification and the merge-side docs-only exemption (issue #927).
|
|
37
42
|
try { . (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1') } catch { Add-HookDependencyFailure -Name 'hook-docs-only-diff.ps1' -ErrorRecord $_ }
|
|
43
|
+
# Review guard under review_mode concurrent, byte-identical to the Claude copy (issue #1097).
|
|
44
|
+
try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-review.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-review.ps1' -ErrorRecord $_ }
|
|
38
45
|
|
|
39
46
|
# Reason codes for the standalone-merge authorization branch (issue #670), spelled
|
|
40
47
|
# byte-identically to the Claude helpers file. Each of the four assignments below is the only
|
|
@@ -148,16 +155,36 @@ function Invoke-CodexEpicMergeDecision {
|
|
|
148
155
|
$command = [string]$payload.tool_input.command
|
|
149
156
|
# Scope (issue #952): every gh pr merge invocation except a pure --disable-auto, and every gh api
|
|
150
157
|
# merge or enqueue mutation, is gated; Test-EpicMergeGateCommandInScope holds the predicate.
|
|
151
|
-
|
|
158
|
+
$inScope = Test-EpicMergeGateCommandInScope -CommandText $command
|
|
159
|
+
if (-not $inScope -and -not (Test-EpicMergeGateReviewMergeText -CommandText $command)) {
|
|
152
160
|
return $null
|
|
153
161
|
}
|
|
154
162
|
|
|
163
|
+
$prNumber = Get-CodexMergeCommandPrNumber -Command $command
|
|
155
164
|
$child = ConvertFrom-CodexMergeJson -Raw $ChildCheckpointRaw -Name 'child checkpoint' -Optional
|
|
165
|
+
|
|
166
|
+
# Review guard (issue #1097): inert without review_mode concurrent, so it precedes every allow branch.
|
|
167
|
+
$reviewDecision = Get-EpicMergeGateReviewShapeDecision -CommandText $command -KnownCheckpoint $child -KnownPrNumber $prNumber
|
|
168
|
+
if (-not $reviewDecision.Denied) {
|
|
169
|
+
if (-not $inScope) {
|
|
170
|
+
return $null
|
|
171
|
+
}
|
|
172
|
+
$reviewDecision = Get-EpicMergeGateReviewDecision -Checkpoint $child -CommandPrNumber $prNumber -GhRoot $RepositoryRoot -GitRoot $RepositoryRoot
|
|
173
|
+
}
|
|
174
|
+
if ($reviewDecision.Denied) {
|
|
175
|
+
return [ordered]@{
|
|
176
|
+
hookSpecificOutput = [ordered]@{
|
|
177
|
+
hookEventName = 'PreToolUse'
|
|
178
|
+
permissionDecision = 'deny'
|
|
179
|
+
permissionDecisionReason = $reviewDecision.Reason
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
156
184
|
if (Test-CodexChildMergeReady -Checkpoint $child) {
|
|
157
185
|
return $null
|
|
158
186
|
}
|
|
159
187
|
$epic = ConvertFrom-CodexMergeJson -Raw $EpicCheckpointRaw -Name 'epic checkpoint' -Optional
|
|
160
|
-
$prNumber = Get-CodexMergeCommandPrNumber -Command $command
|
|
161
188
|
if (Test-CodexEpicMergeReady -Checkpoint $epic -CommandPrNumber $prNumber) {
|
|
162
189
|
return $null
|
|
163
190
|
}
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Cleanup-context allow legs of the preimplementation gate (issue #1026).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Dot-sourced by enforce-orchestration-preimplementation-gate.ps1 on the Claude and the Codex
|
|
7
|
+
surface. The gate calls Get-OrchestrationCleanupContextResult only where it would otherwise
|
|
8
|
+
return its final PREIMPLEMENTATION_GATE_BLOCKED deny, after the epic-scope decision, the
|
|
9
|
+
delegation-mode branch, and the unresolved-target and ambiguous-resolution denies, so every
|
|
10
|
+
existing deny path, ordering, and wording is unchanged when no leg applies. Four legs:
|
|
11
|
+
|
|
12
|
+
Manifest Write - the session-root spelling of
|
|
13
|
+
artifacts/orchestration/cleanup-worktrees-manifest.json (the relative
|
|
14
|
+
literal or its join with the session root), with content that passes
|
|
15
|
+
Test-CleanupManifestText. An Edit carries no content and is denied.
|
|
16
|
+
With -AllowPatchManifest (Codex only) a patch whose single marker is
|
|
17
|
+
'*** Add File:' naming the manifest is admitted on its '+' lines.
|
|
18
|
+
Scratch Write/Edit - an absolute local path under a temporary root, outside every
|
|
19
|
+
registered worktree and the session root (Test-CleanupScratchPath).
|
|
20
|
+
Consolidation Write - an absolute path inside the consolidation worktree (porcelain branch
|
|
21
|
+
documentationandmemories) whose relative remainder is allowlisted,
|
|
22
|
+
including the pr-author receipt artifacts/pr_body_<digits>.receipt.json.
|
|
23
|
+
Consolidation git - every chained segment is git -C <absolute root> add or commit, every
|
|
24
|
+
target is the consolidation root; add takes only '--' and allowlisted
|
|
25
|
+
operands; commit takes only -m, --message, -F, --file, and --trailer
|
|
26
|
+
with values, no pathspec, and a non-empty, entirely allowlisted staged
|
|
27
|
+
index read through Get-OrchestrationGateStagedIndex.
|
|
28
|
+
|
|
29
|
+
Pure prefilters run before any process: the manifest leg compares the path literal; the
|
|
30
|
+
scratch leg requires an absolute local path under a temporary root; the consolidation Write
|
|
31
|
+
leg requires a /docs/, /.claude/agent-memory/, or receipt tail; the command leg requires
|
|
32
|
+
every segment to be the git -C add or commit shape. Only then is the listing seam
|
|
33
|
+
Get-OrchestrationGateWorktreeList called, and the staged-index seam only after every target
|
|
34
|
+
matches the consolidation root. Get-OrchestrationCleanupContextResult never throws: any
|
|
35
|
+
error answers Allowed $false, which keeps the gate's deny. Test-OrchestrationCleanupContextAllowed
|
|
36
|
+
is its Boolean wrapper.
|
|
37
|
+
|
|
38
|
+
DenySuffix, which the gate appends to its deny reason, is empty unless a leg has identified
|
|
39
|
+
the consolidation worktree as the target (the Write leg: the enclosing root is the
|
|
40
|
+
consolidation root; the git leg: every resolved target is that root) and then fails. It is
|
|
41
|
+
then ' Cleanup leg not applied: ' followed by one sub-code and a short detail:
|
|
42
|
+
|
|
43
|
+
CLEANUP_LEG_PATH_NOT_ALLOWLISTED - Write leg: the relative remainder is not allowlisted.
|
|
44
|
+
CLEANUP_LEG_STAGED_INDEX_UNREADABLE - commit: the staged-index read failed, exited
|
|
45
|
+
non-zero, or is not NUL-terminated.
|
|
46
|
+
CLEANUP_LEG_STAGED_INDEX_EMPTY - commit: the staged index lists no path.
|
|
47
|
+
CLEANUP_LEG_STAGED_PATH_NOT_ALLOWLISTED - commit: a staged path is not allowlisted.
|
|
48
|
+
|
|
49
|
+
A pure-prefilter rejection, the manifest and scratch legs, an empty or failed listing, a
|
|
50
|
+
missing consolidation record, an unresolved or other target, and any error leave DenySuffix
|
|
51
|
+
empty, so every other deny text is unchanged.
|
|
52
|
+
|
|
53
|
+
.NOTES
|
|
54
|
+
PowerShell 7+. Byte-identical at .claude/hooks, .codex/hooks, and both bundle mirrors.
|
|
55
|
+
Call-time dependencies loaded by each gate: Get-OrchestrationGateWorktreeList (epic-scope
|
|
56
|
+
sibling), ConvertFrom-HookTargetWorktreePorcelain, Get-HookTargetWorktreeEnclosingRoot, and
|
|
57
|
+
Get-HookTargetWorktreeRelativePath (HookTargetWorktree.psm1 on Claude, hook-target-worktree.ps1
|
|
58
|
+
on Codex), Get-OrchestrationCommandTarget and $script:OrchestrationTargetNonLocalPattern
|
|
59
|
+
(targets file), and Split-OrchestrationCommandLine, Test-OrchestrationCommandTextUnresolvable,
|
|
60
|
+
and ConvertTo-OrchestrationCommandToken (helpers file).
|
|
61
|
+
#>
|
|
62
|
+
|
|
63
|
+
try { . (Join-Path $PSScriptRoot 'hook-cleanup-context.ps1') } catch { Add-HookDependencyFailure -Name 'hook-cleanup-context.ps1' -ErrorRecord $_ }
|
|
64
|
+
try { . (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1') } catch { Add-HookDependencyFailure -Name 'hook-docs-only-diff.ps1' -ErrorRecord $_ }
|
|
65
|
+
|
|
66
|
+
function Get-OrchestrationGateTemporaryRoot {
|
|
67
|
+
<#
|
|
68
|
+
.SYNOPSIS
|
|
69
|
+
Return every accepted spelling of the operating-system temporary root (test seam).
|
|
70
|
+
.DESCRIPTION
|
|
71
|
+
The distinct non-empty values of [System.IO.Path]::GetTempPath(), TEMP, TMP, and TMPDIR.
|
|
72
|
+
GetTempPath() supplies the long-name spelling on Windows, while the TEMP value the
|
|
73
|
+
runtime hands to the Write tool can carry the 8.3 short-name spelling, so both are
|
|
74
|
+
accepted. Tests mock this function.
|
|
75
|
+
.OUTPUTS
|
|
76
|
+
System.String[]
|
|
77
|
+
#>
|
|
78
|
+
[CmdletBinding()]
|
|
79
|
+
[OutputType([string[]])]
|
|
80
|
+
param()
|
|
81
|
+
|
|
82
|
+
$values = [System.Collections.Generic.List[string]]::new()
|
|
83
|
+
foreach ($value in @([System.IO.Path]::GetTempPath(), $env:TEMP, $env:TMP, $env:TMPDIR)) {
|
|
84
|
+
if (-not [string]::IsNullOrWhiteSpace($value) -and $values -cnotcontains $value) {
|
|
85
|
+
$values.Add($value)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return [string[]]$values.ToArray()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function Get-OrchestrationGateStagedIndex {
|
|
92
|
+
<#
|
|
93
|
+
.SYNOPSIS
|
|
94
|
+
Read the staged paths of a worktree as NUL-terminated names (test seam).
|
|
95
|
+
.DESCRIPTION
|
|
96
|
+
Runs git diff --cached --name-only -z --no-renames --no-color --no-ext-diff --no-relative
|
|
97
|
+
in the worktree through the bounded Invoke-DocsOnlyGitExe seam, which never throws and
|
|
98
|
+
reports ExitCode -1 for a root that is not a directory. Tests mock this function.
|
|
99
|
+
.PARAMETER WorktreeRoot
|
|
100
|
+
The consolidation worktree root.
|
|
101
|
+
.OUTPUTS
|
|
102
|
+
System.Management.Automation.PSCustomObject with ExitCode and Output.
|
|
103
|
+
#>
|
|
104
|
+
[CmdletBinding()]
|
|
105
|
+
[OutputType([pscustomobject])]
|
|
106
|
+
param([Parameter(Mandatory)][string] $WorktreeRoot)
|
|
107
|
+
|
|
108
|
+
return (Invoke-DocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('diff', '--cached', '--name-only', '-z', '--no-renames', '--no-color', '--no-ext-diff', '--no-relative'))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function Get-OrchestrationCleanupListingRecord {
|
|
112
|
+
<#
|
|
113
|
+
.SYNOPSIS
|
|
114
|
+
Return the porcelain records of the session root's worktree listing, or an empty array.
|
|
115
|
+
.OUTPUTS
|
|
116
|
+
System.Object[]
|
|
117
|
+
#>
|
|
118
|
+
[CmdletBinding()]
|
|
119
|
+
[OutputType([object[]])]
|
|
120
|
+
param([Parameter(Mandatory)][string] $SessionRoot)
|
|
121
|
+
|
|
122
|
+
$text = [string](Get-OrchestrationGateWorktreeList -SessionRoot $SessionRoot)
|
|
123
|
+
return @(ConvertFrom-HookTargetWorktreePorcelain -Text $text)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function ConvertTo-OrchestrationCleanupComparablePath {
|
|
127
|
+
<#
|
|
128
|
+
.SYNOPSIS
|
|
129
|
+
Normalize separators and upper-case a leading drive letter, so only the drive letter
|
|
130
|
+
is compared without case.
|
|
131
|
+
.OUTPUTS
|
|
132
|
+
System.String
|
|
133
|
+
#>
|
|
134
|
+
[CmdletBinding()]
|
|
135
|
+
[OutputType([string])]
|
|
136
|
+
param([AllowNull()][AllowEmptyString()][string] $Path)
|
|
137
|
+
|
|
138
|
+
$normalized = ConvertTo-CleanupContextPath -Path $Path
|
|
139
|
+
if ($normalized -match '^[A-Za-z]:') {
|
|
140
|
+
$normalized = $normalized.Substring(0, 1).ToUpperInvariant() + $normalized.Substring(1)
|
|
141
|
+
}
|
|
142
|
+
return $normalized
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function Test-OrchestrationCleanupManifestPath {
|
|
146
|
+
<#
|
|
147
|
+
.SYNOPSIS
|
|
148
|
+
Return $true when a path is the session-root spelling of the cleanup manifest.
|
|
149
|
+
.OUTPUTS
|
|
150
|
+
System.Boolean
|
|
151
|
+
#>
|
|
152
|
+
[CmdletBinding()]
|
|
153
|
+
[OutputType([bool])]
|
|
154
|
+
param([AllowNull()][AllowEmptyString()][string] $Path, [Parameter(Mandatory)][string] $SessionRoot)
|
|
155
|
+
|
|
156
|
+
$relative = $script:CleanupContextManifestRelativePath
|
|
157
|
+
$candidate = ConvertTo-OrchestrationCleanupComparablePath -Path $Path
|
|
158
|
+
if ($candidate -ceq $relative) {
|
|
159
|
+
return $true
|
|
160
|
+
}
|
|
161
|
+
$root = ConvertTo-OrchestrationCleanupComparablePath -Path $SessionRoot
|
|
162
|
+
$joined = if ($root.EndsWith('/')) { $root + $relative } else { $root + '/' + $relative }
|
|
163
|
+
return ($candidate -ceq $joined)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function Get-OrchestrationCleanupPatchManifestContent {
|
|
167
|
+
<#
|
|
168
|
+
.SYNOPSIS
|
|
169
|
+
Return the added text of a patch whose single marker adds the manifest, or $null.
|
|
170
|
+
.DESCRIPTION
|
|
171
|
+
The first non-empty line must be '*** Begin Patch' and the patch must carry exactly one
|
|
172
|
+
file marker, an '*** Add File:' naming the manifest. The content is the '+' lines that
|
|
173
|
+
follow the marker, with the '+' removed, up to the next '***' line.
|
|
174
|
+
.OUTPUTS
|
|
175
|
+
System.String or $null
|
|
176
|
+
#>
|
|
177
|
+
[CmdletBinding()]
|
|
178
|
+
[OutputType([string])]
|
|
179
|
+
param([Parameter(Mandatory)][AllowEmptyString()][string] $PatchText, [Parameter(Mandatory)][string] $SessionRoot)
|
|
180
|
+
|
|
181
|
+
$markers = @(Get-OrchestrationPatchMarkerPath -PatchText $PatchText)
|
|
182
|
+
$lines = @($PatchText -split "`r?`n")
|
|
183
|
+
$markerLines = @($lines | Where-Object { $_ -cmatch '^\*\*\* (Add File|Update File|Delete File|Move to):' })
|
|
184
|
+
if ($markers.Count -ne 1 -or $markerLines.Count -ne 1 -or -not $markerLines[0].StartsWith('*** Add File:', [System.StringComparison]::Ordinal)) {
|
|
185
|
+
return $null
|
|
186
|
+
}
|
|
187
|
+
if (-not (Test-OrchestrationCleanupManifestPath -Path $markers[0] -SessionRoot $SessionRoot)) {
|
|
188
|
+
return $null
|
|
189
|
+
}
|
|
190
|
+
$added = [System.Collections.Generic.List[string]]::new()
|
|
191
|
+
$inside = $false
|
|
192
|
+
foreach ($line in $lines) {
|
|
193
|
+
if ($line.StartsWith('*** Add File:', [System.StringComparison]::Ordinal)) { $inside = $true; continue }
|
|
194
|
+
if (-not $inside) { continue }
|
|
195
|
+
if ($line.StartsWith('***', [System.StringComparison]::Ordinal)) { break }
|
|
196
|
+
if (-not $line.StartsWith('+', [System.StringComparison]::Ordinal)) { return $null }
|
|
197
|
+
$added.Add($line.Substring(1))
|
|
198
|
+
}
|
|
199
|
+
return ($added -join "`n")
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function Test-OrchestrationCleanupScratchWrite {
|
|
203
|
+
<#
|
|
204
|
+
.SYNOPSIS
|
|
205
|
+
Scratch leg: an absolute local path under a temporary root and outside every worktree.
|
|
206
|
+
.OUTPUTS
|
|
207
|
+
System.Boolean
|
|
208
|
+
#>
|
|
209
|
+
[CmdletBinding()]
|
|
210
|
+
[OutputType([bool])]
|
|
211
|
+
param([Parameter(Mandatory)][string] $FilePath, [Parameter(Mandatory)][string] $SessionRoot)
|
|
212
|
+
|
|
213
|
+
if ($FilePath -match $script:OrchestrationTargetNonLocalPattern) {
|
|
214
|
+
return $false
|
|
215
|
+
}
|
|
216
|
+
$normalized = ConvertTo-CleanupContextPath -Path $FilePath
|
|
217
|
+
if ($normalized -notmatch '^([A-Za-z]:/|/(?!/))') {
|
|
218
|
+
return $false
|
|
219
|
+
}
|
|
220
|
+
$temporary = [string[]]@(Get-OrchestrationGateTemporaryRoot | ForEach-Object { $_ })
|
|
221
|
+
if (@($temporary | Where-Object { Test-CleanupContextPathUnder -Path $normalized -Root $_ }).Count -eq 0) {
|
|
222
|
+
return $false
|
|
223
|
+
}
|
|
224
|
+
$records = @(Get-OrchestrationCleanupListingRecord -SessionRoot $SessionRoot)
|
|
225
|
+
if ($records.Count -eq 0) {
|
|
226
|
+
return $false
|
|
227
|
+
}
|
|
228
|
+
$roots = [string[]]@($records | ForEach-Object { [string]$_.Root })
|
|
229
|
+
if ($null -ne (Get-HookTargetWorktreeEnclosingRoot -Path $normalized -Roots $roots)) {
|
|
230
|
+
return $false
|
|
231
|
+
}
|
|
232
|
+
return (Test-CleanupScratchPath -Path $FilePath -RegisteredRoot $roots -SessionRoot $SessionRoot -TemporaryRoot $temporary)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function Get-OrchestrationCleanupConsolidationWriteResult {
|
|
236
|
+
<#
|
|
237
|
+
.SYNOPSIS
|
|
238
|
+
Consolidation Write/Edit leg: an allowlisted path inside the consolidation worktree.
|
|
239
|
+
.DESCRIPTION
|
|
240
|
+
DenySuffix names CLEANUP_LEG_PATH_NOT_ALLOWLISTED only when the enclosing root is the
|
|
241
|
+
consolidation root; every earlier rejection leaves it empty.
|
|
242
|
+
.OUTPUTS
|
|
243
|
+
System.Management.Automation.PSCustomObject with Allowed and DenySuffix.
|
|
244
|
+
#>
|
|
245
|
+
[CmdletBinding()]
|
|
246
|
+
[OutputType([pscustomobject])]
|
|
247
|
+
param([Parameter(Mandatory)][string] $FilePath, [Parameter(Mandatory)][string] $SessionRoot)
|
|
248
|
+
|
|
249
|
+
$denied = [pscustomobject]@{ Allowed = $false; DenySuffix = '' }
|
|
250
|
+
if ($FilePath -match $script:OrchestrationTargetNonLocalPattern) {
|
|
251
|
+
return $denied
|
|
252
|
+
}
|
|
253
|
+
$normalized = ConvertTo-CleanupContextPath -Path $FilePath
|
|
254
|
+
if ($normalized -notmatch '^([A-Za-z]:/|/(?!/))' -or $normalized -cnotmatch '/docs/|/\.claude/agent-memory/|/artifacts/pr_body_[0-9]+\.receipt\.json$') {
|
|
255
|
+
return $denied
|
|
256
|
+
}
|
|
257
|
+
$records = @(Get-OrchestrationCleanupListingRecord -SessionRoot $SessionRoot)
|
|
258
|
+
$record = Find-CleanupConsolidationRecord -Record $records
|
|
259
|
+
if ($null -eq $record) {
|
|
260
|
+
return $denied
|
|
261
|
+
}
|
|
262
|
+
$enclosing = Get-HookTargetWorktreeEnclosingRoot -Path $normalized -Roots ([string[]]@($records | ForEach-Object { [string]$_.Root }))
|
|
263
|
+
if (-not [string]::Equals([string]$enclosing, [string]$record.Root, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
264
|
+
return $denied
|
|
265
|
+
}
|
|
266
|
+
$relative = Get-HookTargetWorktreeRelativePath -Path $normalized -WorktreeRoot $record.Root
|
|
267
|
+
if (Test-CleanupConsolidationRelativePath -RelativePath $relative -AllowReceipt) {
|
|
268
|
+
return [pscustomobject]@{ Allowed = $true; DenySuffix = '' }
|
|
269
|
+
}
|
|
270
|
+
return [pscustomobject]@{ Allowed = $false; DenySuffix = " Cleanup leg not applied: CLEANUP_LEG_PATH_NOT_ALLOWLISTED: '$relative' is not an allowlisted path in the consolidation worktree." }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function Test-OrchestrationCleanupSegmentShape {
|
|
274
|
+
<#
|
|
275
|
+
.SYNOPSIS
|
|
276
|
+
Return the subcommand of a git -C <absolute> add|commit segment whose options the leg
|
|
277
|
+
admits, or $null.
|
|
278
|
+
.DESCRIPTION
|
|
279
|
+
add admits only '--' and at least one allowlisted operand. commit admits only -m,
|
|
280
|
+
--message, -F, --file, and --trailer, each with a separate value or an '=' value, and no
|
|
281
|
+
pathspec.
|
|
282
|
+
.OUTPUTS
|
|
283
|
+
System.String or $null
|
|
284
|
+
#>
|
|
285
|
+
[CmdletBinding()]
|
|
286
|
+
[OutputType([string])]
|
|
287
|
+
param([Parameter(Mandatory)][AllowEmptyCollection()][AllowEmptyString()][string[]] $Token)
|
|
288
|
+
|
|
289
|
+
if ($Token.Count -lt 4 -or $Token[0] -cne 'git' -or $Token[1] -cne '-C' -or $Token[2] -notmatch '^([A-Za-z]:/|/(?!/))') {
|
|
290
|
+
return $null
|
|
291
|
+
}
|
|
292
|
+
$subcommand = $Token[3]
|
|
293
|
+
$rest = if ($Token.Count -gt 4) { @($Token[4..($Token.Count - 1)]) } else { @() }
|
|
294
|
+
if ($subcommand -ceq 'add') {
|
|
295
|
+
$operands = @($rest | Where-Object { $_ -cne '--' })
|
|
296
|
+
if (($rest.Count - $operands.Count) -gt 1 -or $operands.Count -eq 0) {
|
|
297
|
+
return $null
|
|
298
|
+
}
|
|
299
|
+
foreach ($operand in $operands) {
|
|
300
|
+
if ($operand.StartsWith('-') -or -not (Test-CleanupConsolidationRelativePath -RelativePath $operand)) {
|
|
301
|
+
return $null
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return 'add'
|
|
305
|
+
}
|
|
306
|
+
if ($subcommand -cne 'commit') {
|
|
307
|
+
return $null
|
|
308
|
+
}
|
|
309
|
+
$valueOptions = @('-m', '--message', '-F', '--file', '--trailer')
|
|
310
|
+
for ($index = 0; $index -lt $rest.Count; $index++) {
|
|
311
|
+
$item = $rest[$index]
|
|
312
|
+
if ($valueOptions -ccontains $item) {
|
|
313
|
+
$index++
|
|
314
|
+
if ($index -ge $rest.Count) {
|
|
315
|
+
return $null
|
|
316
|
+
}
|
|
317
|
+
continue
|
|
318
|
+
}
|
|
319
|
+
$name = if ($item.IndexOf('=') -gt 0) { $item.Substring(0, $item.IndexOf('=')) } else { '' }
|
|
320
|
+
if (@('--message', '--file', '--trailer') -ccontains $name) {
|
|
321
|
+
continue
|
|
322
|
+
}
|
|
323
|
+
return $null
|
|
324
|
+
}
|
|
325
|
+
return 'commit'
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function Get-OrchestrationCleanupConsolidationCommandResult {
|
|
329
|
+
<#
|
|
330
|
+
.SYNOPSIS
|
|
331
|
+
Consolidation git leg: every segment stages or commits allowlisted paths in the
|
|
332
|
+
consolidation worktree.
|
|
333
|
+
.DESCRIPTION
|
|
334
|
+
DenySuffix names a CLEANUP_LEG_STAGED_* sub-code only after every target matched the
|
|
335
|
+
consolidation root; every earlier rejection leaves it empty.
|
|
336
|
+
.OUTPUTS
|
|
337
|
+
System.Management.Automation.PSCustomObject with Allowed and DenySuffix.
|
|
338
|
+
#>
|
|
339
|
+
[CmdletBinding()]
|
|
340
|
+
[OutputType([pscustomobject])]
|
|
341
|
+
param([Parameter(Mandatory)][string] $Command, [Parameter(Mandatory)][string] $SessionRoot)
|
|
342
|
+
|
|
343
|
+
$denied = [pscustomobject]@{ Allowed = $false; DenySuffix = '' }
|
|
344
|
+
if (Test-OrchestrationCommandTextUnresolvable -CommandText $Command) {
|
|
345
|
+
return $denied
|
|
346
|
+
}
|
|
347
|
+
$split = Split-OrchestrationCommandLine -CommandText $Command
|
|
348
|
+
$segments = @($split.Segments)
|
|
349
|
+
if (-not $split.Balanced -or $segments.Count -eq 0) {
|
|
350
|
+
return $denied
|
|
351
|
+
}
|
|
352
|
+
$commitRoots = [System.Collections.Generic.List[string]]::new()
|
|
353
|
+
foreach ($segment in $segments) {
|
|
354
|
+
$tokens = [string[]]@(ConvertTo-OrchestrationCommandToken -Segment $segment)
|
|
355
|
+
$shape = Test-OrchestrationCleanupSegmentShape -Token $tokens
|
|
356
|
+
if ($null -eq $shape) {
|
|
357
|
+
return $denied
|
|
358
|
+
}
|
|
359
|
+
if ($shape -ceq 'commit') {
|
|
360
|
+
$commitRoots.Add($tokens[2])
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
$target = Get-OrchestrationCommandTarget -Command $Command -SessionRoot $SessionRoot
|
|
365
|
+
if (-not $target.Resolved -or @($target.Targets).Count -eq 0) {
|
|
366
|
+
return $denied
|
|
367
|
+
}
|
|
368
|
+
$record = Find-CleanupConsolidationRecord -Record @(Get-OrchestrationCleanupListingRecord -SessionRoot $SessionRoot)
|
|
369
|
+
if ($null -eq $record) {
|
|
370
|
+
return $denied
|
|
371
|
+
}
|
|
372
|
+
$consolidationRoot = ConvertTo-OrchestrationCleanupComparablePath -Path $record.Root
|
|
373
|
+
foreach ($candidate in @($target.Targets)) {
|
|
374
|
+
if (-not [string]::Equals((ConvertTo-OrchestrationCleanupComparablePath -Path $candidate), $consolidationRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
375
|
+
return $denied
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if ($commitRoots.Count -gt 0) {
|
|
379
|
+
$prefix = ' Cleanup leg not applied: '
|
|
380
|
+
$unreadable = [pscustomobject]@{ Allowed = $false; DenySuffix = "${prefix}CLEANUP_LEG_STAGED_INDEX_UNREADABLE: the staged index of the consolidation worktree could not be read as NUL-terminated names." }
|
|
381
|
+
$empty = [pscustomobject]@{ Allowed = $false; DenySuffix = "${prefix}CLEANUP_LEG_STAGED_INDEX_EMPTY: the staged index of the consolidation worktree lists no path." }
|
|
382
|
+
$staged = Get-OrchestrationGateStagedIndex -WorktreeRoot $record.Root
|
|
383
|
+
if ($null -eq $staged -or $staged.ExitCode -ne 0) {
|
|
384
|
+
return $unreadable
|
|
385
|
+
}
|
|
386
|
+
$text = [string]$staged.Output
|
|
387
|
+
if ($text.Length -eq 0) {
|
|
388
|
+
return $empty
|
|
389
|
+
}
|
|
390
|
+
if (-not $text.EndsWith([char]0)) {
|
|
391
|
+
return $unreadable
|
|
392
|
+
}
|
|
393
|
+
$paths = @(ConvertFrom-DocsOnlyNameList -Text $text)
|
|
394
|
+
if ($paths.Count -eq 0) {
|
|
395
|
+
return $empty
|
|
396
|
+
}
|
|
397
|
+
foreach ($path in $paths) {
|
|
398
|
+
if (-not (Test-CleanupConsolidationRelativePath -RelativePath $path)) {
|
|
399
|
+
return [pscustomobject]@{ Allowed = $false; DenySuffix = "${prefix}CLEANUP_LEG_STAGED_PATH_NOT_ALLOWLISTED: the staged path '$path' is not under docs/ or .claude/agent-memory/." }
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return [pscustomobject]@{ Allowed = $true; DenySuffix = '' }
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function Get-OrchestrationCleanupContextResult {
|
|
407
|
+
<#
|
|
408
|
+
.SYNOPSIS
|
|
409
|
+
Decide whether a call that the gate would deny is a sanctioned cleanup operation.
|
|
410
|
+
.DESCRIPTION
|
|
411
|
+
Composes the manifest, scratch, consolidation Write/Edit, and consolidation git legs of
|
|
412
|
+
the file header and returns Allowed with the DenySuffix rule of the file header. Never
|
|
413
|
+
throws: any error answers Allowed $false with an empty DenySuffix.
|
|
414
|
+
.PARAMETER FilePath
|
|
415
|
+
The Write or Edit file path; empty for a command.
|
|
416
|
+
.PARAMETER Content
|
|
417
|
+
The Write content; empty for an Edit.
|
|
418
|
+
.PARAMETER Command
|
|
419
|
+
The Bash command, or the Codex apply_patch text; empty for a path leg.
|
|
420
|
+
.PARAMETER AllowPatchManifest
|
|
421
|
+
Admit a Codex apply_patch that adds the manifest (Codex gate only).
|
|
422
|
+
.OUTPUTS
|
|
423
|
+
System.Management.Automation.PSCustomObject with Allowed and DenySuffix.
|
|
424
|
+
#>
|
|
425
|
+
[CmdletBinding()]
|
|
426
|
+
[OutputType([pscustomobject])]
|
|
427
|
+
param(
|
|
428
|
+
[AllowNull()][AllowEmptyString()][string] $FilePath,
|
|
429
|
+
[AllowNull()][AllowEmptyString()][string] $Content,
|
|
430
|
+
[AllowNull()][AllowEmptyString()][string] $Command,
|
|
431
|
+
[switch] $AllowPatchManifest
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
$denied = [pscustomobject]@{ Allowed = $false; DenySuffix = '' }
|
|
435
|
+
try {
|
|
436
|
+
$sessionRoot = (Get-Location).Path
|
|
437
|
+
if ($FilePath) {
|
|
438
|
+
if (Test-OrchestrationCleanupManifestPath -Path $FilePath -SessionRoot $sessionRoot) {
|
|
439
|
+
return [pscustomobject]@{ Allowed = [bool](Test-CleanupManifestText -Raw $Content); DenySuffix = '' }
|
|
440
|
+
}
|
|
441
|
+
if (Test-OrchestrationCleanupScratchWrite -FilePath $FilePath -SessionRoot $sessionRoot) {
|
|
442
|
+
return [pscustomobject]@{ Allowed = $true; DenySuffix = '' }
|
|
443
|
+
}
|
|
444
|
+
return (Get-OrchestrationCleanupConsolidationWriteResult -FilePath $FilePath -SessionRoot $sessionRoot)
|
|
445
|
+
}
|
|
446
|
+
if (-not $Command) {
|
|
447
|
+
return $denied
|
|
448
|
+
}
|
|
449
|
+
if ($AllowPatchManifest -and $Command.TrimStart().StartsWith('*** Begin Patch', [System.StringComparison]::Ordinal)) {
|
|
450
|
+
$patchContent = Get-OrchestrationCleanupPatchManifestContent -PatchText $Command -SessionRoot $sessionRoot
|
|
451
|
+
return [pscustomobject]@{ Allowed = ($null -ne $patchContent -and (Test-CleanupManifestText -Raw $patchContent)); DenySuffix = '' }
|
|
452
|
+
}
|
|
453
|
+
return (Get-OrchestrationCleanupConsolidationCommandResult -Command $Command -SessionRoot $sessionRoot)
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
Write-Debug "PREIMPL_CLEANUP_LEG_ERROR: $($_.Exception.Message)"
|
|
457
|
+
return $denied
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function Test-OrchestrationCleanupContextAllowed {
|
|
462
|
+
<#
|
|
463
|
+
.SYNOPSIS
|
|
464
|
+
Return $true when a call that the gate would deny is a sanctioned cleanup operation.
|
|
465
|
+
.DESCRIPTION
|
|
466
|
+
Boolean wrapper that returns the Allowed value of Get-OrchestrationCleanupContextResult.
|
|
467
|
+
Never throws.
|
|
468
|
+
.OUTPUTS
|
|
469
|
+
System.Boolean
|
|
470
|
+
#>
|
|
471
|
+
[CmdletBinding()]
|
|
472
|
+
[OutputType([bool])]
|
|
473
|
+
param(
|
|
474
|
+
[AllowNull()][AllowEmptyString()][string] $FilePath,
|
|
475
|
+
[AllowNull()][AllowEmptyString()][string] $Content,
|
|
476
|
+
[AllowNull()][AllowEmptyString()][string] $Command,
|
|
477
|
+
[switch] $AllowPatchManifest
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
return [bool](Get-OrchestrationCleanupContextResult -FilePath $FilePath -Content $Content -Command $Command -AllowPatchManifest:$AllowPatchManifest).Allowed
|
|
481
|
+
}
|
|
@@ -30,6 +30,7 @@ try { . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-m
|
|
|
30
30
|
# Epic-scope command and path legs (issue #707, porting issue #663 decision D2), plus the
|
|
31
31
|
# two per-mode read seams relocated from this file to keep it inside the 500-line cap.
|
|
32
32
|
try { . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-epic-scope.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-orchestration-preimplementation-gate-epic-scope.ps1' -ErrorRecord $_ }
|
|
33
|
+
try { . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-cleanup.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-orchestration-preimplementation-gate-cleanup.ps1' -ErrorRecord $_ }
|
|
33
34
|
# Shared command-line parser (issue #545): per-segment scan text and structural matching.
|
|
34
35
|
try { . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-scanner.ps1' -ErrorRecord $_ }
|
|
35
36
|
try { . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-invocation.ps1' -ErrorRecord $_ }
|
|
@@ -448,7 +449,8 @@ function Invoke-OrchestrationPreimplementationGateDecision {
|
|
|
448
449
|
if (Test-OrchestrationReady -Payload $checkpoint) {
|
|
449
450
|
return Get-OrchestrationPreimplementationGateAllowDecision
|
|
450
451
|
}
|
|
451
|
-
|
|
452
|
+
if (($cleanup = Get-OrchestrationCleanupContextResult -FilePath $filePath -Content (Get-StringProperty -Value $toolInput -Name 'content') -Command $command -AllowPatchManifest).Allowed) { return Get-OrchestrationPreimplementationGateAllowDecision } # Cleanup-context legs (issue #1026).
|
|
453
|
+
return Get-OrchestrationPreimplementationGateBlockDecision -Reason ('PREIMPLEMENTATION_GATE_BLOCKED: Implementation operations require artifacts/orchestration/orchestrator-state.json to contain issue number, feature folder, route metadata, lifecycle readiness, and checkpoint state before implementation begins.' + $cleanup.DenySuffix)
|
|
452
454
|
}
|
|
453
455
|
|
|
454
456
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
@@ -477,7 +479,7 @@ try {
|
|
|
477
479
|
# path enters the same untouched Test-ImplementationPath decision flow. Any
|
|
478
480
|
# other well-formed tool name maps to no records, so the hook allows.
|
|
479
481
|
foreach ($toolInput in @(ConvertTo-CodexFileEditInput -Payload $payload)) {
|
|
480
|
-
$mappedRaw = @{ file_path = [string]$toolInput.file_path } | ConvertTo-Json -Compress
|
|
482
|
+
$mappedRaw = @{ file_path = [string]$toolInput.file_path; content = $(if ($toolName -ceq 'Write') { [string]$toolInput.content } else { '' }) } | ConvertTo-Json -Compress
|
|
481
483
|
$decision = Invoke-OrchestrationPreimplementationGateDecision -ToolInputRaw $mappedRaw
|
|
482
484
|
if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
483
485
|
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|