@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
|
@@ -45,7 +45,11 @@
|
|
|
45
45
|
enqueues the pull request; only a pure --disable-auto is out of scope. The predicate and the
|
|
46
46
|
PR selector reader live in enforce-epic-merge-gate-command.ps1.
|
|
47
47
|
|
|
48
|
-
Design decision: branches 1-4 trust the on-disk checkpoint and
|
|
48
|
+
Design decision: without review_mode concurrent, branches 1-4 trust the on-disk checkpoint and
|
|
49
|
+
never call gh. Under that key the review guard (enforce-epic-merge-gate-review.ps1, issue #1097)
|
|
50
|
+
runs before branch 1 and makes one bounded gh pr view call and at most two git calls, and it
|
|
51
|
+
denies a merge with no passing review at a current head. Its shape check precedes the
|
|
52
|
+
out-of-scope allow and denies text that could be a merge and cannot be bound. The green-CI
|
|
49
53
|
branch makes at most one bounded gh pr view call, only after its pure stages A to C pass.
|
|
50
54
|
Its parallel exclusion is best-effort: the Parallel mode: true marker is in the delegation
|
|
51
55
|
prompt, not in a Bash payload, so a parallel child that no checkpoint records yet is not
|
|
@@ -100,6 +104,8 @@ try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-command.ps1') } catch
|
|
|
100
104
|
try { . (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1') } catch { Add-HookDependencyFailure -Name 'hook-docs-only-diff.ps1' -ErrorRecord $_ }
|
|
101
105
|
# Green-CI standalone merge branch: pure checks, the live-head seam, and the stage orchestrator (issue #997).
|
|
102
106
|
try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-green-ci.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-green-ci.ps1' -ErrorRecord $_ }
|
|
107
|
+
# Review guard under review_mode concurrent, byte-identical to the Codex copy (issue #1097).
|
|
108
|
+
try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-review.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-review.ps1' -ErrorRecord $_ }
|
|
103
109
|
|
|
104
110
|
function ConvertFrom-EpicMergeGateJson {
|
|
105
111
|
<#
|
|
@@ -349,6 +355,9 @@ function Invoke-EpicMergeGateDecision {
|
|
|
349
355
|
# Scope (issue #952): every gh pr merge invocation except a pure --disable-auto, and every gh api
|
|
350
356
|
# merge or enqueue mutation, is gated; Test-EpicMergeGateCommandInScope holds the predicate.
|
|
351
357
|
if (-not (Test-EpicMergeGateCommandInScope -CommandText $commandText)) {
|
|
358
|
+
if (-not (Test-EpicMergeGateReviewMergeText -CommandText $commandText)) { return Get-EpicMergeGateAllowDecision }
|
|
359
|
+
$unscopedDecision = Get-EpicMergeGateReviewUnscopedDecision -CommandText $commandText
|
|
360
|
+
if ($unscopedDecision.Denied) { return Get-EpicMergeGateBlockDecision -Reason $unscopedDecision.Reason }
|
|
352
361
|
return Get-EpicMergeGateAllowDecision
|
|
353
362
|
}
|
|
354
363
|
|
|
@@ -366,6 +375,12 @@ function Invoke-EpicMergeGateDecision {
|
|
|
366
375
|
$childRoot = if (@('SessionRoot', 'OtherWorktree') -contains $itemTarget.Status) { $itemTarget.WorktreeRoot } else { $null }
|
|
367
376
|
}
|
|
368
377
|
$childCheckpoint = if ($childRoot) { ConvertFrom-EpicMergeGateJson -Raw (Get-ChildOrchestratorCheckpointContent -Path (Get-WorktreeRunCheckpointPath -Kind item -WorktreeRoot $childRoot)) } else { $null }
|
|
378
|
+
|
|
379
|
+
# Review guard (issue #1097): inert without review_mode concurrent, so it precedes every allow branch.
|
|
380
|
+
$reviewDecision = Get-EpicMergeGateReviewShapeDecision -CommandText $commandText -KnownCheckpoint $childCheckpoint -KnownPrNumber $commandPrNumber -ReadCheckpoint ${function:Read-EpicMergeGateReviewItemCheckpoint}
|
|
381
|
+
if (-not $reviewDecision.Denied) { $reviewDecision = Get-EpicMergeGateReviewDecision -Checkpoint $childCheckpoint -CommandPrNumber $commandPrNumber -GhRoot $sessionRoot -GitRoot $childRoot }
|
|
382
|
+
if ($reviewDecision.Denied) { return Get-EpicMergeGateBlockDecision -Reason $reviewDecision.Reason }
|
|
383
|
+
|
|
369
384
|
if ((Test-ChildCheckpointAllowsEpicMerge -Checkpoint $childCheckpoint) -and
|
|
370
385
|
(Test-ChildCheckpointPrGateBinding -Checkpoint $childCheckpoint -CommandPrNumber $commandPrNumber)) {
|
|
371
386
|
return Get-EpicMergeGateAllowDecision
|
|
@@ -62,6 +62,9 @@ try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.ps
|
|
|
62
62
|
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeItemResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeItemResolution.psm1' -ErrorRecord $_ }
|
|
63
63
|
# Epic scope (issue #663): a delegation for the epic integration branch is gated against the epic checkpoint.
|
|
64
64
|
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'EpicScopeResolution.psm1' -ErrorRecord $_ }
|
|
65
|
+
# Cleanup consolidation delegation (issue #1026): the session-root manifest and the shared predicates.
|
|
66
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/cleanup-manifest/CleanupWorktreeManifest.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'CleanupWorktreeManifest.psm1' -ErrorRecord $_ }
|
|
67
|
+
try { . (Join-Path $PSScriptRoot 'hook-cleanup-context.ps1') } catch { Add-HookDependencyFailure -Name 'hook-cleanup-context.ps1' -ErrorRecord $_ }
|
|
65
68
|
function Get-ModelRoutingCheckpoint {
|
|
66
69
|
<#
|
|
67
70
|
.SYNOPSIS
|
|
@@ -244,6 +247,38 @@ function Get-ModelRoutingAuthorAssertedReason {
|
|
|
244
247
|
return "MODEL_ROUTING_RECEIPT_BLOCKED: AUTHOR_ASSERTED_AUTOCLOSE_MISSING: the checkpoint records issue-num $issue, so the Agent(pr-author) delegation prompt must carry the line '$line' so the PR body emits '- Closes #$issue'. Add that line on its own line, or omit it only for an epic child (epic_mode true)."
|
|
245
248
|
}
|
|
246
249
|
|
|
250
|
+
function Test-ModelRoutingCleanupDelegation {
|
|
251
|
+
<#
|
|
252
|
+
.SYNOPSIS
|
|
253
|
+
Return $true when a pr-author delegation is the cleanup consolidation delegation (issue #1026).
|
|
254
|
+
.DESCRIPTION
|
|
255
|
+
The prompt must carry exactly one 'branch: documentationandmemories' label and no --head
|
|
256
|
+
or --branch token (Test-CleanupConsolidationBranchLabel), checked first so no other
|
|
257
|
+
prompt reaches the manifest read. Then the session-root manifest read through
|
|
258
|
+
Get-CleanupWorktreeManifestContent must pass Test-CleanupManifestText with the
|
|
259
|
+
Get-CleanupWorktreeManifestUtcNow clock. Never throws: any error answers $false.
|
|
260
|
+
.PARAMETER PromptText
|
|
261
|
+
The delegation prompt.
|
|
262
|
+
.OUTPUTS
|
|
263
|
+
System.Boolean
|
|
264
|
+
#>
|
|
265
|
+
[CmdletBinding()]
|
|
266
|
+
[OutputType([bool])]
|
|
267
|
+
param(
|
|
268
|
+
[Parameter(Mandatory)][AllowEmptyString()][string] $PromptText
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
if (-not (Test-CleanupConsolidationBranchLabel -PromptText $PromptText)) {
|
|
273
|
+
return $false
|
|
274
|
+
}
|
|
275
|
+
return (Test-CleanupManifestText -Raw (Get-CleanupWorktreeManifestContent) -UtcNow (Get-CleanupWorktreeManifestUtcNow))
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return $false
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
247
282
|
function Invoke-ModelRoutingReceiptDecision {
|
|
248
283
|
<#
|
|
249
284
|
.SYNOPSIS
|
|
@@ -328,6 +363,12 @@ function Invoke-ModelRoutingReceiptDecision {
|
|
|
328
363
|
return $allow
|
|
329
364
|
}
|
|
330
365
|
|
|
366
|
+
# Issue #1026: the cleanup consolidation delegation has no checkpoint by design; a fresh valid
|
|
367
|
+
# session-root manifest and the exact consolidation branch label stand in for the receipt.
|
|
368
|
+
if ($subagent -eq 'pr-author' -and $null -eq $checkpoint -and (Test-ModelRoutingCleanupDelegation -PromptText $prompt)) {
|
|
369
|
+
return $allow
|
|
370
|
+
}
|
|
371
|
+
|
|
331
372
|
return [ordered]@{
|
|
332
373
|
hookSpecificOutput = [ordered]@{
|
|
333
374
|
hookEventName = 'PreToolUse'
|
|
@@ -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
|
+
}
|
|
@@ -24,6 +24,7 @@ try { . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-m
|
|
|
24
24
|
# seams, the mode deny reason, and the target resolution with its import guard (issue
|
|
25
25
|
# #690), all relocated to this sibling to keep the gate inside the 500-line cap.
|
|
26
26
|
try { . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-epic-scope.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-orchestration-preimplementation-gate-epic-scope.ps1' -ErrorRecord $_ }
|
|
27
|
+
try { . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-cleanup.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-orchestration-preimplementation-gate-cleanup.ps1' -ErrorRecord $_ }
|
|
27
28
|
|
|
28
29
|
# Shared command-line parser (issue #545): per-segment scan text and structural matching.
|
|
29
30
|
try { . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-scanner.ps1' -ErrorRecord $_ }
|
|
@@ -411,7 +412,8 @@ function Invoke-OrchestrationPreimplementationGateDecision {
|
|
|
411
412
|
if (Test-OrchestrationReady -Payload $checkpoint) {
|
|
412
413
|
return Get-OrchestrationPreimplementationGateAllowDecision
|
|
413
414
|
}
|
|
414
|
-
|
|
415
|
+
if (($cleanup = Get-OrchestrationCleanupContextResult -FilePath $filePath -Content (Get-ClaudeHookToolInputString -ToolInput $toolInput -Name 'content') -Command $command).Allowed) { return Get-OrchestrationPreimplementationGateAllowDecision } # Cleanup-context legs (issue #1026).
|
|
416
|
+
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)
|
|
415
417
|
}
|
|
416
418
|
|
|
417
419
|
function Invoke-OrchestrationPreimplementationGateEntryPoint {
|