@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.
Files changed (131) hide show
  1. package/out/mcp-server.js +3160 -458
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +24 -0
  4. package/resources/claude-customizations/.claude/agents/epic-planner.md +121 -0
  5. package/resources/claude-customizations/.claude/agents/orchestrator.md +2 -2
  6. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +246 -0
  7. package/resources/claude-customizations/.claude/settings.json +9 -2
  8. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +7 -0
  9. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +189 -0
  10. package/resources/claude-customizations/.claude/skills/epic-run/SKILL.md +38 -0
  11. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +21 -2
  12. package/resources/claude-customizations/pack-manifests/core.json +4 -0
  13. package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +0 -1
  14. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +100 -0
  15. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/agents/openai.yaml +4 -0
  16. package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +170 -0
  17. package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +230 -0
  18. package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +39 -0
  19. package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +0 -1
  20. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +0 -1
  21. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +89 -9
  22. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +39 -4
  23. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +0 -1
  24. package/resources/codex-and-agents-customizations/.agents/skills/tonality/SKILL.md +0 -1
  25. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +153 -0
  26. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +153 -0
  27. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +153 -0
  28. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +153 -0
  29. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +153 -0
  30. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +2 -0
  31. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +95 -0
  32. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +95 -0
  33. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +95 -0
  34. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +95 -0
  35. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +95 -0
  36. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +2 -0
  37. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +99 -0
  38. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +99 -0
  39. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +99 -0
  40. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +99 -0
  41. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +99 -0
  42. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +2 -0
  43. package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +96 -0
  44. package/resources/codex-and-agents-customizations/.codex/agents/epic-planner.toml +97 -0
  45. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c1.toml +62 -0
  46. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +62 -0
  47. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +62 -0
  48. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +62 -0
  49. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +62 -0
  50. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +2 -0
  51. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +200 -0
  52. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +200 -0
  53. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +200 -0
  54. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +200 -0
  55. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +200 -0
  56. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +53 -3
  57. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +110 -0
  58. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +110 -0
  59. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +110 -0
  60. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +110 -0
  61. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +110 -0
  62. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +2 -0
  63. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c1.toml +28 -0
  64. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c2.toml +28 -0
  65. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c3-elevated.toml +28 -0
  66. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c3.toml +28 -0
  67. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c4.toml +28 -0
  68. package/resources/codex-and-agents-customizations/.codex/agents/pr-author.toml +2 -0
  69. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c1.toml +55 -0
  70. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c2.toml +55 -0
  71. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c3-elevated.toml +55 -0
  72. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c3.toml +55 -0
  73. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c4.toml +55 -0
  74. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature.toml +2 -0
  75. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +102 -0
  76. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +102 -0
  77. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +102 -0
  78. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +102 -0
  79. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +102 -0
  80. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +2 -0
  81. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c1.toml +105 -0
  82. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c2.toml +105 -0
  83. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3-elevated.toml +105 -0
  84. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3.toml +105 -0
  85. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c4.toml +105 -0
  86. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher.toml +2 -0
  87. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c1.toml +50 -0
  88. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c2.toml +50 -0
  89. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c3-elevated.toml +50 -0
  90. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c3.toml +50 -0
  91. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c4.toml +50 -0
  92. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer.toml +2 -0
  93. package/resources/codex-and-agents-customizations/.codex/config.toml +202 -37
  94. package/resources/codex-and-agents-customizations/.codex/hooks/authorize-root-epic-invocation.ps1 +236 -0
  95. package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +178 -69
  96. package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +92 -19
  97. package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +199 -0
  98. package/resources/codex-and-agents-customizations/.codex/hooks/codex-authority-store.ps1 +182 -0
  99. package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +129 -0
  100. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +148 -31
  101. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +198 -0
  102. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +28 -19
  103. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +325 -0
  104. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +140 -0
  105. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +292 -0
  106. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +135 -0
  107. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +295 -0
  108. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +151 -0
  109. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +99 -34
  110. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +143 -27
  111. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +88 -43
  112. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +65 -20
  113. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +88 -42
  114. package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +414 -0
  115. package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +164 -51
  116. package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +154 -0
  117. package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +58 -23
  118. package/resources/codex-and-agents-customizations/AGENTS.md +27 -0
  119. package/resources/codex-and-agents-customizations/pack-manifests/core.json +66 -1
  120. package/resources/codex-and-agents-customizations/pack-manifests/csharp-legacy.json +6 -1
  121. package/resources/codex-and-agents-customizations/pack-manifests/csharp-modern.json +6 -1
  122. package/resources/codex-and-agents-customizations/pack-manifests/powershell.json +6 -1
  123. package/resources/codex-and-agents-customizations/pack-manifests/python.json +6 -1
  124. package/resources/codex-and-agents-customizations/pack-manifests/typescript.json +6 -1
  125. package/resources/config/orchestration-routing.json +167 -0
  126. package/resources/powershell/PoshQC/PoshQC.Analyzer.psm1 +1 -1
  127. package/resources/powershell/PoshQC/PoshQC.ScanConfig.psm1 +125 -0
  128. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +14 -2
  129. package/resources/powershell/PoshQC/PoshQC.psd1 +0 -4
  130. package/resources/powershell/PoshQC/PoshQC.psm1 +26 -6
  131. 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
+ }
@@ -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 Claude Code PreToolUse hook before any Write or Edit
10
- operation. It reads the tool input from the CLAUDE_TOOL_INPUT environment variable
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/research/, artifacts/pr_context,
27
- artifacts/reviews/, artifacts/status/, artifacts/python/, artifacts/pester/,
28
- and artifacts/csharp/.
29
-
30
- If the file_path resolves to a forbidden prefix, the script writes a JSON response
31
- to stdout with 'decision': 'block' and exits with code 0 so Claude Code surfaces
32
- the reason. For allowed paths, 'decision': 'allow' is written to stdout and the
33
- script exits 0. On hard failure (malformed JSON input), the script exits 1.
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 Claude Code tool-input JSON.
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 block-decision ordered dictionary for the supplied forbidden path.
88
+ Constructs a deny-decision ordered dictionary for the supplied forbidden path.
86
89
  .PARAMETER FilePath
87
- The file path that triggered the block.
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
- decision = 'block'
98
- reason = "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."
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 Claude Code tool-input JSON and returns an allow-or-block decision.
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:CLAUDE_TOOL_INPUT. An empty or null value
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]@{ decision = 'allow' }
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 CLAUDE_TOOL_INPUT: $_"
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]@{ decision = 'allow' }
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]@{ decision = 'allow' }
142
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
137
143
  }
138
144
 
139
- # Guard allows dot-sourcing in tests without executing the entrypoint.
140
- if ($MyInvocation.InvocationName -eq '.') {
141
- return
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
- try {
145
- $decision = Invoke-EvidenceLocationDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
146
- } catch {
147
- Write-Error $_
148
- exit 1
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
- $decision | ConvertTo-Json -Compress | Write-Output
213
+ # Guard allows dot-sourcing in tests without executing the entrypoint.
214
+ if ($MyInvocation.InvocationName -eq '.') {
215
+ return
216
+ }
152
217
 
153
- exit 0
218
+ exit (Invoke-EvidenceLocationEntryPoint -PayloadRaw ([Console]::In.ReadToEnd()))