@danmoisan/drm-copilot-mcp 1.0.13 → 1.0.15
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 +3160 -458
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +24 -0
- package/resources/claude-customizations/.claude/agents/epic-planner.md +121 -0
- package/resources/claude-customizations/.claude/agents/orchestrator.md +2 -2
- package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +246 -0
- package/resources/claude-customizations/.claude/settings.json +9 -2
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +7 -0
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +189 -0
- package/resources/claude-customizations/.claude/skills/epic-run/SKILL.md +38 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +21 -2
- package/resources/claude-customizations/pack-manifests/core.json +4 -0
- package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +100 -0
- package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/agents/openai.yaml +4 -0
- package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +170 -0
- package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +230 -0
- package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +39 -0
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +89 -9
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +39 -4
- package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/tonality/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +153 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +153 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +153 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +153 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +153 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +95 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +95 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +95 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +95 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +95 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +99 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +99 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +99 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +99 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +99 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +96 -0
- package/resources/codex-and-agents-customizations/.codex/agents/epic-planner.toml +97 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c1.toml +62 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +62 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +62 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +62 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +62 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +200 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +200 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +200 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +200 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +200 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +53 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +110 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +110 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +110 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +110 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +110 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c1.toml +28 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c2.toml +28 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c3-elevated.toml +28 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c3.toml +28 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c4.toml +28 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c1.toml +55 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c2.toml +55 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c3-elevated.toml +55 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c3.toml +55 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c4.toml +55 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +102 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +102 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +102 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +102 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +102 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c1.toml +105 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c2.toml +105 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3-elevated.toml +105 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3.toml +105 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c4.toml +105 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c1.toml +50 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c2.toml +50 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c3-elevated.toml +50 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c3.toml +50 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c4.toml +50 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +202 -37
- package/resources/codex-and-agents-customizations/.codex/hooks/authorize-root-epic-invocation.ps1 +236 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +178 -69
- package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +92 -19
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +199 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-authority-store.ps1 +182 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +129 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +148 -31
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +198 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +28 -19
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +325 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +140 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +292 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +135 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +295 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +151 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +99 -34
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +143 -27
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +88 -43
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +65 -20
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +88 -42
- package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +414 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +164 -51
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +154 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +58 -23
- package/resources/codex-and-agents-customizations/AGENTS.md +27 -0
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +66 -1
- package/resources/codex-and-agents-customizations/pack-manifests/csharp-legacy.json +6 -1
- package/resources/codex-and-agents-customizations/pack-manifests/csharp-modern.json +6 -1
- package/resources/codex-and-agents-customizations/pack-manifests/powershell.json +6 -1
- package/resources/codex-and-agents-customizations/pack-manifests/python.json +6 -1
- package/resources/codex-and-agents-customizations/pack-manifests/typescript.json +6 -1
- package/resources/config/orchestration-routing.json +167 -0
- package/resources/powershell/PoshQC/PoshQC.Analyzer.psm1 +1 -1
- package/resources/powershell/PoshQC/PoshQC.ScanConfig.psm1 +125 -0
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +14 -2
- package/resources/powershell/PoshQC/PoshQC.psd1 +0 -4
- package/resources/powershell/PoshQC/PoshQC.psm1 +26 -6
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +34 -18
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Denies covered child mutations before all epic dependencies are durably merged.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
For launcher-bound execution children, this PreToolUse gate resolves the exact feature and
|
|
7
|
+
epic checkpoint from the active session receipt before any local checkpoint exists. Native
|
|
8
|
+
compatibility paths use the local epic context. Mutation remains blocked until every
|
|
9
|
+
dependency is merged or worktree_removed.
|
|
10
|
+
#>
|
|
11
|
+
[CmdletBinding()]
|
|
12
|
+
param()
|
|
13
|
+
|
|
14
|
+
. (Join-Path $PSScriptRoot 'codex-epic-child-launch-attestation.ps1')
|
|
15
|
+
|
|
16
|
+
$script:AllowedDependencyStatuses = @('merged', 'worktree_removed')
|
|
17
|
+
|
|
18
|
+
function ConvertFrom-CodexWaveJson {
|
|
19
|
+
[CmdletBinding()]
|
|
20
|
+
param(
|
|
21
|
+
[AllowNull()][AllowEmptyString()][string] $Raw,
|
|
22
|
+
[Parameter(Mandatory)][string] $Name,
|
|
23
|
+
[switch] $Optional
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if ([string]::IsNullOrWhiteSpace($Raw)) {
|
|
27
|
+
if ($Optional) {
|
|
28
|
+
return $null
|
|
29
|
+
}
|
|
30
|
+
throw "EPIC_WAVE_BARRIER_BLOCKED: $Name is empty."
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return $Raw | ConvertFrom-Json -ErrorAction Stop
|
|
34
|
+
} catch {
|
|
35
|
+
throw "EPIC_WAVE_BARRIER_BLOCKED: $Name is malformed JSON: $_"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function Get-CodexWaveFeatureKey {
|
|
40
|
+
[CmdletBinding()]
|
|
41
|
+
[OutputType([string])]
|
|
42
|
+
param([Parameter(Mandatory)] $LocalCheckpoint)
|
|
43
|
+
|
|
44
|
+
foreach ($name in @('issue-num', 'issue_num', 'feature-folder', 'feature_folder')) {
|
|
45
|
+
if (@($LocalCheckpoint.PSObject.Properties.Name) -contains $name -and
|
|
46
|
+
-not [string]::IsNullOrWhiteSpace([string]$LocalCheckpoint.$name)) {
|
|
47
|
+
return (([string]$LocalCheckpoint.$name) -replace '\\', '/').TrimEnd('/').Split('/')[-1]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (@($LocalCheckpoint.PSObject.Properties.Name) -contains 'epic_context' -and
|
|
51
|
+
$null -ne $LocalCheckpoint.epic_context) {
|
|
52
|
+
foreach ($name in @('issue_num', 'feature_folder', 'target_feature_folder')) {
|
|
53
|
+
if (@($LocalCheckpoint.epic_context.PSObject.Properties.Name) -contains $name -and
|
|
54
|
+
-not [string]::IsNullOrWhiteSpace([string]$LocalCheckpoint.epic_context.$name)) {
|
|
55
|
+
return (([string]$LocalCheckpoint.epic_context.$name) -replace '\\', '/').TrimEnd('/').Split('/')[-1]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return ''
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function Find-CodexWaveFeature {
|
|
63
|
+
[CmdletBinding()]
|
|
64
|
+
param([AllowNull()] $Checkpoint, [Parameter(Mandatory)][string] $Key)
|
|
65
|
+
|
|
66
|
+
if ($null -eq $Checkpoint -or
|
|
67
|
+
@($Checkpoint.PSObject.Properties.Name) -notcontains 'features') {
|
|
68
|
+
return $null
|
|
69
|
+
}
|
|
70
|
+
foreach ($feature in @($Checkpoint.features)) {
|
|
71
|
+
if ($null -eq $feature) {
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
$properties = @($feature.PSObject.Properties.Name)
|
|
75
|
+
if ($properties -contains 'issue_num' -and [string]$feature.issue_num -eq $Key) {
|
|
76
|
+
return $feature
|
|
77
|
+
}
|
|
78
|
+
if ($properties -contains 'feature_folder') {
|
|
79
|
+
$basename = (([string]$feature.feature_folder) -replace '\\', '/').TrimEnd('/').Split('/')[-1]
|
|
80
|
+
if ($basename -eq $Key) {
|
|
81
|
+
return $feature
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return $null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function Find-CodexWaveLaunchFeature {
|
|
89
|
+
[CmdletBinding()]
|
|
90
|
+
param([AllowNull()] $Checkpoint, [Parameter(Mandatory)] $Receipt)
|
|
91
|
+
|
|
92
|
+
if ($null -eq $Checkpoint -or
|
|
93
|
+
@($Checkpoint.PSObject.Properties.Name) -notcontains 'features') {
|
|
94
|
+
return $null
|
|
95
|
+
}
|
|
96
|
+
$expectedFolder = (([string]$Receipt.feature_folder) -replace '\\', '/').TrimEnd('/')
|
|
97
|
+
foreach ($feature in @($Checkpoint.features)) {
|
|
98
|
+
if ($null -eq $feature) {
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
$actualFolder = (([string]$feature.feature_folder) -replace '\\', '/').TrimEnd('/')
|
|
102
|
+
$issueMatches = ($feature.issue_num | ConvertTo-Json -Compress) -ceq
|
|
103
|
+
($Receipt.issue_num | ConvertTo-Json -Compress)
|
|
104
|
+
if ($issueMatches -and $actualFolder -ceq $expectedFolder) {
|
|
105
|
+
return $feature
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return $null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function Test-CodexWaveDependenciesReady {
|
|
112
|
+
[CmdletBinding()]
|
|
113
|
+
[OutputType([bool])]
|
|
114
|
+
param([AllowNull()] $EpicCheckpoint, [AllowNull()] $Feature)
|
|
115
|
+
|
|
116
|
+
if ($null -eq $EpicCheckpoint -or $null -eq $Feature) {
|
|
117
|
+
return $false
|
|
118
|
+
}
|
|
119
|
+
if (@($Feature.PSObject.Properties.Name) -notcontains 'depends_on' -or
|
|
120
|
+
@($Feature.depends_on).Count -eq 0) {
|
|
121
|
+
return $true
|
|
122
|
+
}
|
|
123
|
+
foreach ($dependency in @($Feature.depends_on)) {
|
|
124
|
+
$record = Find-CodexWaveFeature -Checkpoint $EpicCheckpoint -Key ([string]$dependency)
|
|
125
|
+
if ($null -eq $record -or
|
|
126
|
+
@($record.PSObject.Properties.Name) -notcontains 'merge_status' -or
|
|
127
|
+
$script:AllowedDependencyStatuses -notcontains [string]$record.merge_status) {
|
|
128
|
+
return $false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return $true
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function Test-CodexWaveMutation {
|
|
135
|
+
[CmdletBinding()]
|
|
136
|
+
[OutputType([bool])]
|
|
137
|
+
param([Parameter(Mandatory)] $Payload)
|
|
138
|
+
|
|
139
|
+
$toolName = [string]$Payload.tool_name
|
|
140
|
+
if ($toolName -in @('apply_patch', 'Edit', 'Write')) {
|
|
141
|
+
return $true
|
|
142
|
+
}
|
|
143
|
+
if ($toolName -like 'mcp__*' -and $toolName -notlike 'mcp__drm-copilot__*') {
|
|
144
|
+
return $true
|
|
145
|
+
}
|
|
146
|
+
if ($toolName -like 'mcp__drm-copilot__*') {
|
|
147
|
+
return $toolName -notmatch '__(?:collect_|validate_|resolve_)'
|
|
148
|
+
}
|
|
149
|
+
if ($toolName -in @('Bash', 'shell_command')) {
|
|
150
|
+
return $true
|
|
151
|
+
}
|
|
152
|
+
return $false
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function Get-CodexWaveDenyDecision {
|
|
156
|
+
[CmdletBinding()]
|
|
157
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
158
|
+
param([Parameter(Mandatory)][string] $FeatureKey, [Parameter(Mandatory)][string] $Reason)
|
|
159
|
+
|
|
160
|
+
return [ordered]@{
|
|
161
|
+
hookSpecificOutput = [ordered]@{
|
|
162
|
+
hookEventName = 'PreToolUse'
|
|
163
|
+
permissionDecision = 'deny'
|
|
164
|
+
permissionDecisionReason = "EPIC_WAVE_BARRIER_BLOCKED: '$FeatureKey' $Reason"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function Invoke-CodexEpicWaveDecision {
|
|
170
|
+
[CmdletBinding()]
|
|
171
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
172
|
+
param(
|
|
173
|
+
[Parameter(Mandatory)][string] $PayloadRaw,
|
|
174
|
+
[AllowNull()][AllowEmptyString()][string] $LocalCheckpointRaw,
|
|
175
|
+
[AllowNull()][AllowEmptyString()][string] $EpicCheckpointRaw,
|
|
176
|
+
[AllowNull()][AllowEmptyString()][string] $LauncherReceiptRaw = '',
|
|
177
|
+
[AllowNull()] $LauncherEnvironment,
|
|
178
|
+
[AllowEmptyString()][string] $RepositoryRoot = '',
|
|
179
|
+
[datetimeoffset] $Now = [datetimeoffset]::UtcNow
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
$payload = ConvertFrom-CodexWaveJson -Raw $PayloadRaw -Name 'PreToolUse input'
|
|
183
|
+
if (-not (Test-CodexWaveMutation -Payload $payload)) {
|
|
184
|
+
return $null
|
|
185
|
+
}
|
|
186
|
+
$launchId = if ($null -eq $LauncherEnvironment) { '' } else {
|
|
187
|
+
[string]$LauncherEnvironment.launch_id
|
|
188
|
+
}
|
|
189
|
+
if (-not [string]::IsNullOrWhiteSpace($launchId)) {
|
|
190
|
+
$context = [string]$LauncherEnvironment.execution_context
|
|
191
|
+
if ($context -eq 'epic_preparation_child') {
|
|
192
|
+
return $null
|
|
193
|
+
}
|
|
194
|
+
if ($context -ne 'epic_execution_child') {
|
|
195
|
+
return Get-CodexWaveDenyDecision -FeatureKey $launchId -Reason 'has an invalid launcher execution context.'
|
|
196
|
+
}
|
|
197
|
+
$routingReceipt = [pscustomobject]@{ execution_context = $context }
|
|
198
|
+
$authorityValid = Test-CodexEpicChildRoutingLaunchAuthority -RoutingReceipt $routingReceipt `
|
|
199
|
+
-Payload $payload -RepositoryRoot $RepositoryRoot -LaunchEnvironment $LauncherEnvironment `
|
|
200
|
+
-LaunchReceiptRaw $LauncherReceiptRaw -Now $Now
|
|
201
|
+
if (-not $authorityValid) {
|
|
202
|
+
return Get-CodexWaveDenyDecision -FeatureKey $launchId -Reason 'has no valid session-bound launcher receipt.'
|
|
203
|
+
}
|
|
204
|
+
$launchReceipt = ConvertFrom-CodexWaveJson -Raw $LauncherReceiptRaw -Name 'launcher receipt'
|
|
205
|
+
$epicCheckpoint = ConvertFrom-CodexWaveJson -Raw $EpicCheckpointRaw -Name 'receipt-bound epic checkpoint'
|
|
206
|
+
$feature = Find-CodexWaveLaunchFeature -Checkpoint $epicCheckpoint -Receipt $launchReceipt
|
|
207
|
+
$featureKey = (([string]$launchReceipt.feature_folder) -replace '\\', '/').TrimEnd('/').Split('/')[-1]
|
|
208
|
+
if (Test-CodexWaveDependenciesReady -EpicCheckpoint $epicCheckpoint -Feature $feature) {
|
|
209
|
+
return $null
|
|
210
|
+
}
|
|
211
|
+
return Get-CodexWaveDenyDecision -FeatureKey $featureKey `
|
|
212
|
+
-Reason 'cannot mutate until every depends_on edge is merged or worktree_removed in the receipt-bound epic checkpoint.'
|
|
213
|
+
}
|
|
214
|
+
if ([string]::IsNullOrWhiteSpace($LocalCheckpointRaw)) {
|
|
215
|
+
return $null
|
|
216
|
+
}
|
|
217
|
+
$localCheckpoint = ConvertFrom-CodexWaveJson -Raw $LocalCheckpointRaw -Name 'child checkpoint'
|
|
218
|
+
if (@($localCheckpoint.PSObject.Properties.Name) -notcontains 'epic_mode' -or
|
|
219
|
+
$localCheckpoint.epic_mode -isnot [bool] -or
|
|
220
|
+
-not [bool]$localCheckpoint.epic_mode) {
|
|
221
|
+
return $null
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
$epicCheckpoint = ConvertFrom-CodexWaveJson -Raw $EpicCheckpointRaw -Name 'epic checkpoint'
|
|
225
|
+
$featureKey = Get-CodexWaveFeatureKey -LocalCheckpoint $localCheckpoint
|
|
226
|
+
$feature = if ($featureKey) {
|
|
227
|
+
Find-CodexWaveFeature -Checkpoint $epicCheckpoint -Key $featureKey
|
|
228
|
+
} else {
|
|
229
|
+
$null
|
|
230
|
+
}
|
|
231
|
+
if (Test-CodexWaveDependenciesReady -EpicCheckpoint $epicCheckpoint -Feature $feature) {
|
|
232
|
+
return $null
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return Get-CodexWaveDenyDecision -FeatureKey $featureKey `
|
|
236
|
+
-Reason 'cannot mutate until every depends_on edge is merged or worktree_removed in the epic checkpoint.'
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function Get-CodexPrimaryWorktreeRoot {
|
|
240
|
+
[CmdletBinding()]
|
|
241
|
+
[OutputType([string])]
|
|
242
|
+
param([Parameter(Mandatory)][string] $RepositoryRoot)
|
|
243
|
+
|
|
244
|
+
$output = & git -C $RepositoryRoot worktree list --porcelain 2>$null
|
|
245
|
+
$first = @($output | Where-Object { $_ -like 'worktree *' } | Select-Object -First 1)
|
|
246
|
+
if ($first.Count -eq 0) {
|
|
247
|
+
return $RepositoryRoot
|
|
248
|
+
}
|
|
249
|
+
return ([string]$first[0]).Substring('worktree '.Length)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
$payloadRaw = [Console]::In.ReadToEnd()
|
|
258
|
+
$repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
|
259
|
+
$localPath = Join-Path $repositoryRoot 'artifacts/orchestration/orchestrator-state.json'
|
|
260
|
+
$localRaw = if (Test-Path -LiteralPath $localPath -PathType Leaf) {
|
|
261
|
+
Get-Content -Raw -LiteralPath $localPath
|
|
262
|
+
} else {
|
|
263
|
+
''
|
|
264
|
+
}
|
|
265
|
+
$launcherEnvironment = Get-CodexEpicChildLaunchEnvironment
|
|
266
|
+
$launcherReceiptRaw = if (-not [string]::IsNullOrWhiteSpace($launcherEnvironment.receipt_path) -and
|
|
267
|
+
(Test-Path -LiteralPath $launcherEnvironment.receipt_path -PathType Leaf)) {
|
|
268
|
+
Get-Content -Raw -LiteralPath $launcherEnvironment.receipt_path
|
|
269
|
+
} else {
|
|
270
|
+
''
|
|
271
|
+
}
|
|
272
|
+
$launcherReceipt = ConvertFrom-CodexWaveJson -Raw $launcherReceiptRaw -Name 'launcher receipt' -Optional
|
|
273
|
+
$primaryRoot = Get-CodexPrimaryWorktreeRoot -RepositoryRoot $repositoryRoot
|
|
274
|
+
$epicPath = if ($null -ne $launcherReceipt -and
|
|
275
|
+
[string]$launcherEnvironment.execution_context -eq 'epic_execution_child') {
|
|
276
|
+
[string]$launcherReceipt.checkpoint_path
|
|
277
|
+
} else {
|
|
278
|
+
Join-Path $primaryRoot 'artifacts/orchestration/epic-orchestrator-state.json'
|
|
279
|
+
}
|
|
280
|
+
$epicRaw = if (Test-Path -LiteralPath $epicPath -PathType Leaf) {
|
|
281
|
+
Get-Content -Raw -LiteralPath $epicPath
|
|
282
|
+
} else {
|
|
283
|
+
''
|
|
284
|
+
}
|
|
285
|
+
$decision = Invoke-CodexEpicWaveDecision -PayloadRaw $payloadRaw -LocalCheckpointRaw $localRaw `
|
|
286
|
+
-EpicCheckpointRaw $epicRaw -LauncherReceiptRaw $launcherReceiptRaw `
|
|
287
|
+
-LauncherEnvironment $launcherEnvironment -RepositoryRoot $repositoryRoot
|
|
288
|
+
if ($null -ne $decision) {
|
|
289
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
290
|
+
}
|
|
291
|
+
exit 0
|
|
292
|
+
} catch {
|
|
293
|
+
[Console]::Error.WriteLine([string]$_)
|
|
294
|
+
exit 2
|
|
295
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Denies git worktree removal until the matching epic feature is safely merged.
|
|
4
|
+
#>
|
|
5
|
+
[CmdletBinding()]
|
|
6
|
+
param()
|
|
7
|
+
|
|
8
|
+
$script:SafeWorktreeStatuses = @('merged', 'worktree_removed')
|
|
9
|
+
|
|
10
|
+
function ConvertFrom-CodexWorktreeJson {
|
|
11
|
+
[CmdletBinding()]
|
|
12
|
+
param([AllowNull()][AllowEmptyString()][string] $Raw, [Parameter(Mandatory)][string] $Name, [switch] $Optional)
|
|
13
|
+
|
|
14
|
+
if ([string]::IsNullOrWhiteSpace($Raw)) {
|
|
15
|
+
if ($Optional) {
|
|
16
|
+
return $null
|
|
17
|
+
}
|
|
18
|
+
throw "EPIC_WORKTREE_REMOVAL_BLOCKED: $Name is empty."
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return $Raw | ConvertFrom-Json -ErrorAction Stop
|
|
22
|
+
} catch {
|
|
23
|
+
if ($Optional) {
|
|
24
|
+
return $null
|
|
25
|
+
}
|
|
26
|
+
throw "EPIC_WORKTREE_REMOVAL_BLOCKED: $Name is malformed JSON: $_"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function Get-CodexWorktreeRemovalPath {
|
|
31
|
+
[CmdletBinding()]
|
|
32
|
+
[OutputType([string])]
|
|
33
|
+
param([Parameter(Mandatory)][AllowEmptyString()][string] $Command)
|
|
34
|
+
|
|
35
|
+
$pattern = '(?i)\bgit\s+worktree\s+remove(?:\s+--force)?\s+(?:"(?<double>[^"]+)"|''(?<single>[^'']+)''|(?<bare>\S+))'
|
|
36
|
+
if ($Command -notmatch $pattern) {
|
|
37
|
+
return ''
|
|
38
|
+
}
|
|
39
|
+
foreach ($name in @('double', 'single', 'bare')) {
|
|
40
|
+
if ($Matches[$name]) {
|
|
41
|
+
return [string]$Matches[$name]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return ''
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function Get-NormalizedCodexWorktreePath {
|
|
48
|
+
[CmdletBinding()]
|
|
49
|
+
[OutputType([string])]
|
|
50
|
+
param([Parameter(Mandatory)][string] $Path, [Parameter(Mandatory)][string] $WorkingDirectory)
|
|
51
|
+
|
|
52
|
+
$resolved = if ([System.IO.Path]::IsPathRooted($Path)) {
|
|
53
|
+
[System.IO.Path]::GetFullPath($Path)
|
|
54
|
+
} else {
|
|
55
|
+
[System.IO.Path]::GetFullPath((Join-Path $WorkingDirectory $Path))
|
|
56
|
+
}
|
|
57
|
+
return ($resolved -replace '\\', '/').TrimEnd('/')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function Find-CodexWorktreeFeature {
|
|
61
|
+
[CmdletBinding()]
|
|
62
|
+
param(
|
|
63
|
+
[AllowNull()] $Checkpoint,
|
|
64
|
+
[Parameter(Mandatory)][string] $TargetPath,
|
|
65
|
+
[Parameter(Mandatory)][string] $WorkingDirectory
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if ($null -eq $Checkpoint -or
|
|
69
|
+
@($Checkpoint.PSObject.Properties.Name) -notcontains 'features') {
|
|
70
|
+
return $null
|
|
71
|
+
}
|
|
72
|
+
$normalizedTarget = Get-NormalizedCodexWorktreePath -Path $TargetPath -WorkingDirectory $WorkingDirectory
|
|
73
|
+
foreach ($feature in @($Checkpoint.features)) {
|
|
74
|
+
if ($null -eq $feature -or
|
|
75
|
+
@($feature.PSObject.Properties.Name) -notcontains 'worktree_path' -or
|
|
76
|
+
[string]::IsNullOrWhiteSpace([string]$feature.worktree_path)) {
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
$normalizedFeature = Get-NormalizedCodexWorktreePath -Path ([string]$feature.worktree_path) -WorkingDirectory $WorkingDirectory
|
|
80
|
+
if ($normalizedFeature -eq $normalizedTarget) {
|
|
81
|
+
return $feature
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return $null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function Invoke-CodexWorktreeRemovalDecision {
|
|
88
|
+
[CmdletBinding()]
|
|
89
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
90
|
+
param(
|
|
91
|
+
[Parameter(Mandatory)][string] $PayloadRaw,
|
|
92
|
+
[AllowNull()][AllowEmptyString()][string] $EpicCheckpointRaw
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
$payload = ConvertFrom-CodexWorktreeJson -Raw $PayloadRaw -Name 'PreToolUse input'
|
|
96
|
+
if ([string]$payload.tool_name -ne 'Bash') {
|
|
97
|
+
return $null
|
|
98
|
+
}
|
|
99
|
+
$command = [string]$payload.tool_input.command
|
|
100
|
+
if ($command -notmatch '(?i)\bgit\s+worktree\s+remove\b') {
|
|
101
|
+
return $null
|
|
102
|
+
}
|
|
103
|
+
$target = Get-CodexWorktreeRemovalPath -Command $command
|
|
104
|
+
$checkpoint = ConvertFrom-CodexWorktreeJson -Raw $EpicCheckpointRaw -Name 'epic checkpoint' -Optional
|
|
105
|
+
$workingDirectory = if ([string]::IsNullOrWhiteSpace([string]$payload.cwd)) {
|
|
106
|
+
(Get-Location).Path
|
|
107
|
+
} else {
|
|
108
|
+
[string]$payload.cwd
|
|
109
|
+
}
|
|
110
|
+
$feature = if ($target) {
|
|
111
|
+
Find-CodexWorktreeFeature -Checkpoint $checkpoint -TargetPath $target -WorkingDirectory $workingDirectory
|
|
112
|
+
} else {
|
|
113
|
+
$null
|
|
114
|
+
}
|
|
115
|
+
if ($null -ne $feature -and
|
|
116
|
+
@($feature.PSObject.Properties.Name) -contains 'merge_status' -and
|
|
117
|
+
$script:SafeWorktreeStatuses -contains [string]$feature.merge_status) {
|
|
118
|
+
return $null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return [ordered]@{
|
|
122
|
+
hookSpecificOutput = [ordered]@{
|
|
123
|
+
hookEventName = 'PreToolUse'
|
|
124
|
+
permissionDecision = 'deny'
|
|
125
|
+
permissionDecisionReason = "EPIC_WORKTREE_REMOVAL_BLOCKED: '$target' requires a matching epic feature with merge_status merged or worktree_removed."
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
$payloadRaw = [Console]::In.ReadToEnd()
|
|
136
|
+
$repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
|
137
|
+
$checkpointPath = Join-Path $repositoryRoot 'artifacts/orchestration/epic-orchestrator-state.json'
|
|
138
|
+
$checkpointRaw = if (Test-Path -LiteralPath $checkpointPath -PathType Leaf) {
|
|
139
|
+
Get-Content -Raw -LiteralPath $checkpointPath
|
|
140
|
+
} else {
|
|
141
|
+
''
|
|
142
|
+
}
|
|
143
|
+
$decision = Invoke-CodexWorktreeRemovalDecision -PayloadRaw $payloadRaw -EpicCheckpointRaw $checkpointRaw
|
|
144
|
+
if ($null -ne $decision) {
|
|
145
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
146
|
+
}
|
|
147
|
+
exit 0
|
|
148
|
+
} catch {
|
|
149
|
+
[Console]::Error.WriteLine([string]$_)
|
|
150
|
+
exit 2
|
|
151
|
+
}
|
package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
# Converted hook
|
|
2
|
-
# Review the generated hook behavior before enabling it.
|
|
3
1
|
|
|
4
2
|
<#
|
|
5
3
|
.SYNOPSIS
|
|
6
4
|
Pre-tool-use hook that blocks writes to non-canonical evidence storage locations.
|
|
7
5
|
|
|
8
6
|
.DESCRIPTION
|
|
9
|
-
This script is invoked by the
|
|
10
|
-
operation. It reads the tool input from the
|
|
7
|
+
This script is invoked by the Codex PreToolUse hook before any Write or Edit
|
|
8
|
+
operation. It reads the tool input from the Codex tool_input environment variable
|
|
11
9
|
(JSON with a 'file_path' field) and rejects the operation when the target path is
|
|
12
10
|
a non-canonical evidence location.
|
|
13
11
|
|
|
@@ -20,17 +18,21 @@
|
|
|
20
18
|
- artifacts/evidence/
|
|
21
19
|
- artifacts/regression-testing/
|
|
22
20
|
- artifacts/post-change/
|
|
21
|
+
- artifacts/research/
|
|
23
22
|
|
|
24
23
|
All other paths pass through, including canonical evidence paths of the form
|
|
25
24
|
<FEATURE>/evidence/<kind>/ and permitted artifacts/ sub-paths such as
|
|
26
|
-
artifacts/orchestration/, artifacts/
|
|
27
|
-
artifacts/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
the
|
|
33
|
-
|
|
25
|
+
artifacts/orchestration/, artifacts/pr_context, artifacts/reviews/,
|
|
26
|
+
artifacts/status/, artifacts/python/, artifacts/pester/, and
|
|
27
|
+
artifacts/csharp/. Research output is no longer an artifacts/ sub-path; it
|
|
28
|
+
is written to the tracked roots docs/features/<feature>/research/
|
|
29
|
+
(feature-associated) or docs/research/ (one-off).
|
|
30
|
+
|
|
31
|
+
If the file_path resolves to a forbidden prefix, the script writes a PreToolUse JSON
|
|
32
|
+
response to stdout with hookSpecificOutput.permissionDecision = 'deny' and exits with
|
|
33
|
+
code 0 so Codex surfaces the reason. For allowed paths, a PreToolUse response
|
|
34
|
+
with permissionDecision = 'allow' is written to stdout and the script exits 0. On hard
|
|
35
|
+
failure (malformed JSON input), the script exits 2.
|
|
34
36
|
|
|
35
37
|
.NOTES
|
|
36
38
|
Compatible with PowerShell 7+.
|
|
@@ -44,7 +46,7 @@ function Test-EvidenceLocationForbidden {
|
|
|
44
46
|
.SYNOPSIS
|
|
45
47
|
Returns $true when the supplied file path targets a forbidden evidence sub-path.
|
|
46
48
|
.PARAMETER FilePath
|
|
47
|
-
The raw file_path value from the
|
|
49
|
+
The raw file_path value from the Codex tool-input JSON.
|
|
48
50
|
#>
|
|
49
51
|
[CmdletBinding()]
|
|
50
52
|
[OutputType([bool])]
|
|
@@ -64,7 +66,8 @@ function Test-EvidenceLocationForbidden {
|
|
|
64
66
|
'artifacts/coverage/',
|
|
65
67
|
'artifacts/evidence/',
|
|
66
68
|
'artifacts/regression-testing/',
|
|
67
|
-
'artifacts/post-change/'
|
|
69
|
+
'artifacts/post-change/',
|
|
70
|
+
'artifacts/research/'
|
|
68
71
|
)
|
|
69
72
|
|
|
70
73
|
# Match the prefix either at the start of the string or after any directory separator,
|
|
@@ -82,9 +85,9 @@ function Test-EvidenceLocationForbidden {
|
|
|
82
85
|
function Get-EvidenceLocationBlockDecision {
|
|
83
86
|
<#
|
|
84
87
|
.SYNOPSIS
|
|
85
|
-
Constructs a
|
|
88
|
+
Constructs a deny-decision ordered dictionary for the supplied forbidden path.
|
|
86
89
|
.PARAMETER FilePath
|
|
87
|
-
The file path that triggered the
|
|
90
|
+
The file path that triggered the deny decision.
|
|
88
91
|
#>
|
|
89
92
|
[CmdletBinding()]
|
|
90
93
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -94,17 +97,20 @@ function Get-EvidenceLocationBlockDecision {
|
|
|
94
97
|
)
|
|
95
98
|
|
|
96
99
|
[ordered]@{
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
hookSpecificOutput = [ordered]@{
|
|
101
|
+
hookEventName = 'PreToolUse'
|
|
102
|
+
permissionDecision = 'deny'
|
|
103
|
+
permissionDecisionReason = "EVIDENCE_LOCATION_BLOCKED: '$FilePath' is not a canonical evidence location. Use <FEATURE>/evidence/<kind>/ instead. See .agents/skills/evidence-and-timestamp-conventions/SKILL.md for the canonical scheme."
|
|
104
|
+
}
|
|
99
105
|
}
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
function Invoke-EvidenceLocationDecision {
|
|
103
109
|
<#
|
|
104
110
|
.SYNOPSIS
|
|
105
|
-
Parses the
|
|
111
|
+
Parses the Codex tool-input JSON and returns an allow-or-block decision.
|
|
106
112
|
.PARAMETER ToolInputRaw
|
|
107
|
-
The raw JSON string from $env:
|
|
113
|
+
The raw JSON string from $env:Codex tool_input. An empty or null value
|
|
108
114
|
results in an allow decision (non-file tool calls have no file_path).
|
|
109
115
|
#>
|
|
110
116
|
[CmdletBinding()]
|
|
@@ -114,40 +120,99 @@ function Invoke-EvidenceLocationDecision {
|
|
|
114
120
|
)
|
|
115
121
|
|
|
116
122
|
if (-not $ToolInputRaw) {
|
|
117
|
-
return [ordered]@{
|
|
123
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
try {
|
|
121
127
|
$toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
|
|
122
128
|
} catch {
|
|
123
129
|
# Malformed JSON is a hard failure; caller exits 1 to surface the issue.
|
|
124
|
-
throw "enforce-evidence-locations hook received malformed JSON in
|
|
130
|
+
throw "enforce-evidence-locations hook received malformed JSON in Codex tool_input: $_"
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
$filePath = $toolInput.file_path
|
|
128
134
|
if (-not $filePath) {
|
|
129
|
-
return [ordered]@{
|
|
135
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
if (Test-EvidenceLocationForbidden -FilePath $filePath) {
|
|
133
139
|
return Get-EvidenceLocationBlockDecision -FilePath $filePath
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
return [ordered]@{
|
|
142
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
137
143
|
}
|
|
138
144
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
function ConvertFrom-CodexEvidenceLocationPayload {
|
|
146
|
+
[CmdletBinding()]
|
|
147
|
+
param([Parameter(Mandatory)][string] $PayloadRaw)
|
|
148
|
+
|
|
149
|
+
if ([string]::IsNullOrWhiteSpace($PayloadRaw)) {
|
|
150
|
+
throw 'enforce-evidence-locations hook input is empty.'
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
$payload = $PayloadRaw | ConvertFrom-Json -ErrorAction Stop
|
|
154
|
+
} catch {
|
|
155
|
+
throw "enforce-evidence-locations hook input is malformed JSON: $_"
|
|
156
|
+
}
|
|
157
|
+
if ($payload.PSObject.Properties.Name -notcontains 'tool_input' -or $null -eq $payload.tool_input) {
|
|
158
|
+
throw 'enforce-evidence-locations hook input is missing tool_input.'
|
|
159
|
+
}
|
|
160
|
+
if ([string]$payload.hook_event_name -ne 'PreToolUse' -or [string]$payload.tool_name -ne 'apply_patch') {
|
|
161
|
+
throw 'enforce-evidence-locations requires a PreToolUse apply_patch payload.'
|
|
162
|
+
}
|
|
163
|
+
return $payload
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function Get-CodexEvidenceLocationPath {
|
|
167
|
+
[CmdletBinding()]
|
|
168
|
+
[OutputType([string])]
|
|
169
|
+
param([Parameter(Mandatory)] $Payload)
|
|
170
|
+
|
|
171
|
+
if ($Payload.tool_input.PSObject.Properties.Name -contains 'file_path') {
|
|
172
|
+
return [string]$Payload.tool_input.file_path
|
|
173
|
+
}
|
|
174
|
+
$command = [string]$Payload.tool_input.command
|
|
175
|
+
if ([string]::IsNullOrWhiteSpace($command)) {
|
|
176
|
+
throw 'enforce-evidence-locations cannot map tool_input to a file edit.'
|
|
177
|
+
}
|
|
178
|
+
[string[]] $paths = @(
|
|
179
|
+
[regex]::Matches($command, '(?m)^\*\*\* (?:(?:Add|Update|Delete) File|Move to):\s*(?<path>.+?)\s*$') |
|
|
180
|
+
ForEach-Object { ([string]$_.Groups['path'].Value).Trim() }
|
|
181
|
+
)
|
|
182
|
+
$paths = @($paths | Select-Object -Unique)
|
|
183
|
+
if ($paths.Count -eq 0) {
|
|
184
|
+
throw 'enforce-evidence-locations received an unrecognized apply_patch command.'
|
|
185
|
+
}
|
|
186
|
+
foreach ($path in $paths) {
|
|
187
|
+
Write-Output ([string]$path)
|
|
188
|
+
}
|
|
142
189
|
}
|
|
143
190
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
191
|
+
function Invoke-EvidenceLocationEntryPoint {
|
|
192
|
+
[CmdletBinding()]
|
|
193
|
+
[OutputType([int])]
|
|
194
|
+
param([Parameter(Mandatory)][string] $PayloadRaw)
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
$payload = ConvertFrom-CodexEvidenceLocationPayload -PayloadRaw $PayloadRaw
|
|
198
|
+
foreach ($path in @(Get-CodexEvidenceLocationPath -Payload $payload)) {
|
|
199
|
+
$toolInputRaw = @{ file_path = $path } | ConvertTo-Json -Compress
|
|
200
|
+
$decision = Invoke-EvidenceLocationDecision -ToolInputRaw $toolInputRaw
|
|
201
|
+
if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
202
|
+
[Console]::Out.WriteLine(($decision | ConvertTo-Json -Compress -Depth 5))
|
|
203
|
+
return 0
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return 0
|
|
207
|
+
} catch {
|
|
208
|
+
[Console]::Error.WriteLine([string]$_)
|
|
209
|
+
return 2
|
|
210
|
+
}
|
|
149
211
|
}
|
|
150
212
|
|
|
151
|
-
|
|
213
|
+
# Guard allows dot-sourcing in tests without executing the entrypoint.
|
|
214
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
215
|
+
return
|
|
216
|
+
}
|
|
152
217
|
|
|
153
|
-
exit
|
|
218
|
+
exit (Invoke-EvidenceLocationEntryPoint -PayloadRaw ([Console]::In.ReadToEnd()))
|