@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,182 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Resolves the non-workspace authority store used by Codex provenance hooks.
4
+
5
+ .DESCRIPTION
6
+ Epic-entry receipts and routed-subagent attestations must not be writable by
7
+ workspace-scoped agents. These helpers derive a repository- and session-bound
8
+ directory under CODEX_HOME and exact receipt/attestation paths within it.
9
+ #>
10
+ [CmdletBinding()]
11
+ param()
12
+
13
+ function Get-CodexAuthoritySha256 {
14
+ [CmdletBinding()]
15
+ [OutputType([string])]
16
+ param([Parameter(Mandatory)][AllowEmptyString()][string] $Text)
17
+
18
+ $bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
19
+ return [Convert]::ToHexString(
20
+ [System.Security.Cryptography.SHA256]::HashData($bytes)
21
+ ).ToLowerInvariant()
22
+ }
23
+
24
+ function Get-CodexCanonicalAuthorityPath {
25
+ [CmdletBinding()]
26
+ [OutputType([string])]
27
+ param([Parameter(Mandatory)][string] $Path)
28
+
29
+ $fullPath = [System.IO.Path]::GetFullPath($Path).TrimEnd(
30
+ [System.IO.Path]::DirectorySeparatorChar,
31
+ [System.IO.Path]::AltDirectorySeparatorChar
32
+ )
33
+ if ($IsWindows) {
34
+ return $fullPath.ToLowerInvariant()
35
+ }
36
+ return $fullPath
37
+ }
38
+
39
+ function Get-CodexAuthorityRepositoryKey {
40
+ [CmdletBinding()]
41
+ [OutputType([string])]
42
+ param([Parameter(Mandatory)][string] $RepositoryRoot)
43
+
44
+ return Get-CodexAuthoritySha256 -Text (
45
+ Get-CodexCanonicalAuthorityPath -Path $RepositoryRoot
46
+ )
47
+ }
48
+
49
+ function Get-CodexResolvedAuthorityPath {
50
+ [CmdletBinding()]
51
+ [OutputType([string])]
52
+ param([Parameter(Mandatory)][string] $Path)
53
+
54
+ $candidate = [System.IO.Path]::GetFullPath($Path)
55
+ $suffix = [System.Collections.Generic.Stack[string]]::new()
56
+ while (-not (Test-Path -LiteralPath $candidate)) {
57
+ $leaf = Split-Path $candidate -Leaf
58
+ if ([string]::IsNullOrWhiteSpace($leaf)) {
59
+ break
60
+ }
61
+ $suffix.Push($leaf)
62
+ $candidate = Split-Path $candidate -Parent
63
+ }
64
+ if (-not (Test-Path -LiteralPath $candidate)) {
65
+ return Get-CodexCanonicalAuthorityPath -Path $Path
66
+ }
67
+ $item = Get-Item -Force -LiteralPath $candidate
68
+ $resolved = if ($null -ne $item.LinkTarget) {
69
+ $target = [string]$item.LinkTarget
70
+ if (-not [System.IO.Path]::IsPathFullyQualified($target)) {
71
+ $target = Join-Path (Split-Path $candidate -Parent) $target
72
+ }
73
+ [System.IO.Path]::GetFullPath($target)
74
+ } else {
75
+ [string](Resolve-Path -LiteralPath $candidate).ProviderPath
76
+ }
77
+ while ($suffix.Count -gt 0) {
78
+ $resolved = Join-Path $resolved $suffix.Pop()
79
+ }
80
+ return Get-CodexCanonicalAuthorityPath -Path $resolved
81
+ }
82
+
83
+ function Assert-CodexAuthorityOutsideRepository {
84
+ [CmdletBinding()]
85
+ param(
86
+ [Parameter(Mandatory)][string] $AuthorityPath,
87
+ [Parameter(Mandatory)][string] $RepositoryRoot
88
+ )
89
+
90
+ $authority = Get-CodexResolvedAuthorityPath -Path $AuthorityPath
91
+ $repository = Get-CodexResolvedAuthorityPath -Path $RepositoryRoot
92
+ $prefix = $repository + [System.IO.Path]::DirectorySeparatorChar
93
+ $comparison = if ($IsWindows) {
94
+ [System.StringComparison]::OrdinalIgnoreCase
95
+ } else {
96
+ [System.StringComparison]::Ordinal
97
+ }
98
+ if ($authority.Equals($repository, $comparison) -or $authority.StartsWith($prefix, $comparison)) {
99
+ throw 'EPIC_INVOCATION_ORIGIN_BLOCKED: the Codex authority store must be outside the repository workspace.'
100
+ }
101
+ }
102
+
103
+ function Get-CodexAuthorityHome {
104
+ [CmdletBinding()]
105
+ [OutputType([string])]
106
+ param()
107
+
108
+ if (-not [string]::IsNullOrWhiteSpace($env:CODEX_HOME)) {
109
+ return [System.IO.Path]::GetFullPath($env:CODEX_HOME)
110
+ }
111
+ $profileRoot = [Environment]::GetFolderPath(
112
+ [Environment+SpecialFolder]::UserProfile
113
+ )
114
+ if ([string]::IsNullOrWhiteSpace($profileRoot)) {
115
+ throw 'EPIC_INVOCATION_ORIGIN_BLOCKED: CODEX_HOME and the user profile are unavailable.'
116
+ }
117
+ return Join-Path $profileRoot '.codex'
118
+ }
119
+
120
+ function ConvertTo-CodexAuthorityPathSegment {
121
+ [CmdletBinding()]
122
+ [OutputType([string])]
123
+ param([Parameter(Mandatory)][string] $Value)
124
+
125
+ if ([string]::IsNullOrWhiteSpace($Value)) {
126
+ throw 'EPIC_INVOCATION_ORIGIN_BLOCKED: authority path identity is empty.'
127
+ }
128
+ return $Value -replace '[^A-Za-z0-9._-]', '_'
129
+ }
130
+
131
+ function Get-CodexAuthorityStateRoot {
132
+ [CmdletBinding()]
133
+ [OutputType([string])]
134
+ param(
135
+ [Parameter(Mandatory)][string] $RepositoryRoot,
136
+ [Parameter(Mandatory)][string] $SessionId
137
+ )
138
+
139
+ $repositoryKey = Get-CodexAuthorityRepositoryKey -RepositoryRoot $RepositoryRoot
140
+ $safeSession = ConvertTo-CodexAuthorityPathSegment -Value $SessionId
141
+ $stateRoot = Join-Path (
142
+ Join-Path (Join-Path (Get-CodexAuthorityHome) 'authority/epic-entry') $repositoryKey
143
+ ) $safeSession
144
+ Assert-CodexAuthorityOutsideRepository -AuthorityPath $stateRoot -RepositoryRoot $RepositoryRoot
145
+ return $stateRoot
146
+ }
147
+
148
+ function Get-CodexAuthorityReceiptPath {
149
+ [CmdletBinding()]
150
+ [OutputType([string])]
151
+ param(
152
+ [Parameter(Mandatory)][string] $RepositoryRoot,
153
+ [Parameter(Mandatory)][string] $SessionId,
154
+ [Parameter(Mandatory)][string] $TurnId
155
+ )
156
+
157
+ $stateRoot = Get-CodexAuthorityStateRoot `
158
+ -RepositoryRoot $RepositoryRoot `
159
+ -SessionId $SessionId
160
+ $safeTurn = ConvertTo-CodexAuthorityPathSegment -Value $TurnId
161
+ return Join-Path $stateRoot "epic-root-invocation.$safeTurn.json"
162
+ }
163
+
164
+ function Get-CodexAuthorityAttestationPath {
165
+ [CmdletBinding()]
166
+ [OutputType([string])]
167
+ param(
168
+ [Parameter(Mandatory)][string] $RepositoryRoot,
169
+ [Parameter(Mandatory)][string] $SessionId,
170
+ [Parameter(Mandatory)][string] $AttestationKey
171
+ )
172
+
173
+ $stateRoot = Get-CodexAuthorityStateRoot `
174
+ -RepositoryRoot $RepositoryRoot `
175
+ -SessionId $SessionId
176
+ $safeKey = ConvertTo-CodexAuthorityPathSegment -Value $AttestationKey
177
+ return Join-Path $stateRoot "codex-routing-attestation.$safeKey.json"
178
+ }
179
+
180
+ if ($MyInvocation.InvocationName -ne '.') {
181
+ throw 'codex-authority-store.ps1 is a helper and must be dot-sourced.'
182
+ }
@@ -0,0 +1,129 @@
1
+ # Validate inherited external-launch authority for routed epic child subagents.
2
+
3
+ $contractPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'scripts/epic-child-launch-contract.ps1'
4
+ if (Test-Path -LiteralPath $contractPath -PathType Leaf) {
5
+ . $contractPath
6
+ }
7
+
8
+ $script:CodexEpicChildContexts = @('epic_preparation_child', 'epic_execution_child')
9
+
10
+ function Get-CodexEpicChildLaunchEnvironment {
11
+ [CmdletBinding()]
12
+ [OutputType([pscustomobject])]
13
+ param()
14
+
15
+ return [pscustomobject]@{
16
+ launch_id = [string]$env:CODEX_EPIC_CHILD_LAUNCH_ID
17
+ receipt_path = [string]$env:CODEX_EPIC_CHILD_LAUNCH_RECEIPT
18
+ spec_path = [string]$env:CODEX_EPIC_CHILD_LAUNCH_SPEC
19
+ worktree_path = [string]$env:CODEX_EPIC_CHILD_EXPECTED_WORKTREE
20
+ delegation_id = [string]$env:CODEX_EPIC_CHILD_DELEGATION_ID
21
+ execution_context = [string]$env:CODEX_EPIC_CHILD_EXECUTION_CONTEXT
22
+ deployment_agent = [string]$env:CODEX_EPIC_CHILD_AGENT
23
+ model = [string]$env:CODEX_EPIC_CHILD_MODEL
24
+ reasoning_effort = [string]$env:CODEX_EPIC_CHILD_REASONING_EFFORT
25
+ profile_sha256 = [string]$env:CODEX_EPIC_CHILD_PROFILE_SHA256
26
+ }
27
+ }
28
+
29
+ function Get-CodexEpicChildCanonicalPath {
30
+ [CmdletBinding()]
31
+ [OutputType([string])]
32
+ param([Parameter(Mandatory)][string] $Path, [Parameter(Mandatory)][string] $BasePath)
33
+
34
+ $candidate = if ([System.IO.Path]::IsPathFullyQualified($Path)) {
35
+ $Path
36
+ } else {
37
+ Join-Path $BasePath $Path
38
+ }
39
+ return [System.IO.Path]::GetFullPath($candidate).TrimEnd(
40
+ [System.IO.Path]::DirectorySeparatorChar,
41
+ [System.IO.Path]::AltDirectorySeparatorChar
42
+ )
43
+ }
44
+
45
+ function Test-CodexEpicChildRoutingLaunchAuthority {
46
+ [CmdletBinding()]
47
+ [OutputType([bool])]
48
+ param(
49
+ [AllowNull()] $RoutingReceipt,
50
+ [Parameter(Mandatory)] $Payload,
51
+ [Parameter(Mandatory)][string] $RepositoryRoot,
52
+ [AllowNull()] $LaunchEnvironment,
53
+ [AllowNull()][AllowEmptyString()][string] $LaunchReceiptRaw,
54
+ [datetimeoffset] $Now = [datetimeoffset]::UtcNow
55
+ )
56
+
57
+ $context = if ($null -eq $RoutingReceipt) { '' } else {
58
+ [string]$RoutingReceipt.execution_context
59
+ }
60
+ if ($script:CodexEpicChildContexts -cnotcontains $context) {
61
+ return $true
62
+ }
63
+ $environment = if ($null -eq $LaunchEnvironment) {
64
+ Get-CodexEpicChildLaunchEnvironment
65
+ } else {
66
+ $LaunchEnvironment
67
+ }
68
+ try {
69
+ $receiptPath = Get-CodexEpicChildCanonicalPath -Path ([string]$environment.receipt_path) -BasePath $RepositoryRoot
70
+ $raw = if ($PSBoundParameters.ContainsKey('LaunchReceiptRaw')) {
71
+ $LaunchReceiptRaw
72
+ } else {
73
+ Get-Content -Raw -LiteralPath $receiptPath -ErrorAction Stop
74
+ }
75
+ $receipt = $raw | ConvertFrom-Json -Depth 32 -ErrorAction Stop
76
+ if ((Get-CodexChildActiveReceiptErrorList -Receipt $receipt -ExpectedSessionId ([string]$Payload.session_id) -Now $Now).Count -gt 0) {
77
+ return $false
78
+ }
79
+
80
+ $comparison = if ($IsWindows) {
81
+ [System.StringComparison]::OrdinalIgnoreCase
82
+ } else {
83
+ [System.StringComparison]::Ordinal
84
+ }
85
+ $worktree = Get-CodexEpicChildCanonicalPath -Path ([string]$receipt.worktree_path) -BasePath $RepositoryRoot
86
+ $trustedRoot = Get-CodexEpicChildCanonicalPath -Path ([string]$receipt.trusted_repository_root) -BasePath $RepositoryRoot
87
+ $launchRoot = Join-Path $trustedRoot 'artifacts/orchestration/epic-child-launches'
88
+ $specPath = Get-CodexEpicChildCanonicalPath -Path ([string]$receipt.spec_path) -BasePath $trustedRoot
89
+ $checkpointName = if ($context -ceq 'epic_preparation_child') {
90
+ 'epic-planner-state.json'
91
+ } else {
92
+ 'epic-orchestrator-state.json'
93
+ }
94
+ $expectedCheckpoint = Join-Path $trustedRoot "artifacts/orchestration/$checkpointName"
95
+ $pathPairs = @(
96
+ @($receiptPath, (Get-CodexEpicChildCanonicalPath -Path ([string]$receipt.receipt_path) -BasePath $trustedRoot)),
97
+ @((Get-CodexEpicChildCanonicalPath -Path ([string]$environment.spec_path) -BasePath $trustedRoot), $specPath),
98
+ @((Get-CodexEpicChildCanonicalPath -Path ([string]$environment.worktree_path) -BasePath $RepositoryRoot), $worktree)
99
+ )
100
+ foreach ($pair in $pathPairs) {
101
+ if (-not ([string]$pair[0]).Equals([string]$pair[1], $comparison)) {
102
+ return $false
103
+ }
104
+ }
105
+ $identityPairs = @(
106
+ @([string]$environment.launch_id, [string]$receipt.launch_id),
107
+ @([string]$environment.delegation_id, [string]$receipt.delegation_id),
108
+ @([string]$environment.execution_context, [string]$receipt.execution_context),
109
+ @([string]$environment.deployment_agent, [string]$receipt.deployment_agent),
110
+ @([string]$environment.model, [string]$receipt.model),
111
+ @([string]$environment.reasoning_effort, [string]$receipt.model_reasoning_effort),
112
+ @([string]$environment.profile_sha256, [string]$receipt.profile_sha256)
113
+ )
114
+ foreach ($pair in $identityPairs) {
115
+ if (-not ([string]$pair[0]).Equals([string]$pair[1], [System.StringComparison]::Ordinal)) {
116
+ return $false
117
+ }
118
+ }
119
+ $repository = Get-CodexEpicChildCanonicalPath -Path $RepositoryRoot -BasePath $RepositoryRoot
120
+ $checkpoint = Get-CodexEpicChildCanonicalPath -Path ([string]$receipt.checkpoint_path) -BasePath $trustedRoot
121
+ return $repository.Equals($worktree, $comparison) -and
122
+ $context -ceq [string]$receipt.execution_context -and
123
+ $checkpoint.Equals($expectedCheckpoint, $comparison) -and
124
+ $receiptPath.StartsWith($launchRoot + [System.IO.Path]::DirectorySeparatorChar, $comparison) -and
125
+ $specPath.StartsWith($launchRoot + [System.IO.Path]::DirectorySeparatorChar, $comparison)
126
+ } catch {
127
+ return $false
128
+ }
129
+ }
@@ -1,10 +1,11 @@
1
+
1
2
  <#
2
3
  .SYNOPSIS
3
4
  Pre-tool-use hook that blocks Write operations on the orchestrator checkpoint
4
5
  file when completed_steps appear out of declared canonical order.
5
6
 
6
7
  .DESCRIPTION
7
- Invoked by the Claude Code PreToolUse hook on Write or Edit operations. The
8
+ Invoked by the Codex PreToolUse hook on Write or Edit operations. The
8
9
  hook activates only when the target file_path is
9
10
  artifacts/orchestration/orchestrator-state.json.
10
11
 
@@ -27,9 +28,11 @@
27
28
 
28
29
  Entries that do not match any canonical prefix are treated as informational
29
30
  and ignored. If any pair (i, j) with i < j has a higher canonical index at
30
- position i than at position j, the script blocks with a reason listing the
31
- offending pair. A non-empty rollback_history array suppresses this check
32
- because rollbacks legitimately reorder steps.
31
+ position i than at position j, the script denies via a PreToolUse JSON
32
+ response with hookSpecificOutput.permissionDecision='deny' and a reason
33
+ listing the offending pair. A non-empty rollback_history array suppresses
34
+ this check because rollbacks legitimately reorder steps. All allow paths
35
+ emit hookSpecificOutput.permissionDecision='allow'.
33
36
 
34
37
  Edit tool calls supply only old_string/new_string (a partial patch) and
35
38
  cannot be reliably validated without the full target file content, so they
@@ -195,7 +198,7 @@ function Test-IsCheckpointPath {
195
198
  function Invoke-CheckpointMonotonicDecision {
196
199
  <#
197
200
  .SYNOPSIS
198
- Parses CLAUDE_TOOL_INPUT and returns an allow-or-block decision.
201
+ Parses Codex tool_input and returns an allow-or-block decision.
199
202
  #>
200
203
  [CmdletBinding()]
201
204
  [OutputType([System.Collections.Specialized.OrderedDictionary])]
@@ -204,44 +207,49 @@ function Invoke-CheckpointMonotonicDecision {
204
207
  )
205
208
 
206
209
  if (-not $ToolInputRaw) {
207
- return [ordered]@{ decision = 'allow' }
210
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
208
211
  }
209
212
 
210
213
  try {
211
214
  $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
212
215
  }
213
216
  catch {
214
- throw "enforce-checkpoint-monotonic hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
217
+ throw "enforce-checkpoint-monotonic hook received malformed JSON in Codex tool_input: $_"
215
218
  }
216
219
 
217
220
  $filePath = $toolInput.file_path
218
221
  if (-not $filePath) {
219
- return [ordered]@{ decision = 'allow' }
222
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
220
223
  }
221
224
 
222
225
  $normalized = $filePath -replace '\\', '/'
223
226
  if (-not (Test-IsCheckpointPath -NormalizedPath $normalized)) {
224
- return [ordered]@{ decision = 'allow' }
227
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
225
228
  }
226
229
 
227
- # Write tool: validate the content payload. Edit tool: partial new_string is
228
- # not reliable without the full target file content, so allow.
230
+ # The adapter supplies complete post-patch content for every recognized
231
+ # operation. Empty content therefore represents deletion and must fail
232
+ # closed for the canonical checkpoint.
229
233
  $content = $toolInput.content
230
234
  if (-not $content) {
231
- return [ordered]@{ decision = 'allow' }
235
+ return [ordered]@{ hookSpecificOutput = [ordered]@{
236
+ hookEventName = 'PreToolUse'; permissionDecision = 'deny'
237
+ permissionDecisionReason = 'CHECKPOINT_ORDER_BLOCKED: the canonical checkpoint cannot be deleted or replaced with empty content.'
238
+ } }
232
239
  }
233
240
 
234
241
  try {
235
242
  $payload = ConvertFrom-CheckpointJson -Json $content
236
243
  }
237
244
  catch {
238
- # The content itself is not valid JSON. Let downstream tools surface the
239
- # error rather than blocking with a misleading reason here.
240
- return [ordered]@{ decision = 'allow' }
245
+ return [ordered]@{ hookSpecificOutput = [ordered]@{
246
+ hookEventName = 'PreToolUse'; permissionDecision = 'deny'
247
+ permissionDecisionReason = 'CHECKPOINT_ORDER_BLOCKED: the canonical checkpoint must remain valid JSON.'
248
+ } }
241
249
  }
242
250
 
243
251
  if (-not $payload.PSObject.Properties.Name -contains 'completed_steps') {
244
- return [ordered]@{ decision = 'allow' }
252
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
245
253
  }
246
254
 
247
255
  $steps = @()
@@ -256,14 +264,17 @@ function Invoke-CheckpointMonotonicDecision {
256
264
  $rollbackHistory = $payload.rollback_history
257
265
  }
258
266
  if ($rollbackHistory -and @($rollbackHistory).Count -gt 0) {
259
- return [ordered]@{ decision = 'allow' }
267
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
260
268
  }
261
269
 
262
270
  $pair = if ($steps.Count -ge 2) { Get-OutOfOrderPair -CompletedSteps $steps } else { $null }
263
271
  if ($null -ne $pair) {
264
272
  return [ordered]@{
265
- decision = 'block'
266
- reason = "CHECKPOINT_ORDER_BLOCKED: completed_steps lists '$($pair.EarlierEntry)' at position $($pair.EarlierPos) before '$($pair.LaterEntry)' at position $($pair.LaterPos), but the canonical orchestrator workflow requires the later step to follow the earlier one. Reorder completed_steps or, if a rollback occurred, record it in rollback_history."
273
+ hookSpecificOutput = [ordered]@{
274
+ hookEventName = 'PreToolUse'
275
+ permissionDecision = 'deny'
276
+ permissionDecisionReason = "CHECKPOINT_ORDER_BLOCKED: completed_steps lists '$($pair.EarlierEntry)' at position $($pair.EarlierPos) before '$($pair.LaterEntry)' at position $($pair.LaterPos), but the canonical orchestrator workflow requires the later step to follow the earlier one. Reorder completed_steps or, if a rollback occurred, record it in rollback_history."
277
+ }
267
278
  }
268
279
  }
269
280
 
@@ -277,27 +288,133 @@ function Invoke-CheckpointMonotonicDecision {
277
288
  $missing += 'S4_atomic_planning'
278
289
  }
279
290
  return [ordered]@{
280
- decision = 'block'
281
- reason = "CHECKPOINT_ORDER_BLOCKED: completed_steps lists '$($missingPrerequisite.Step)' before required prerequisite step(s): $($missing -join ', '). Record promotion and planning completion before implementation, review, PR, CI, or DONE steps."
291
+ hookSpecificOutput = [ordered]@{
292
+ hookEventName = 'PreToolUse'
293
+ permissionDecision = 'deny'
294
+ permissionDecisionReason = "CHECKPOINT_ORDER_BLOCKED: completed_steps lists '$($missingPrerequisite.Step)' before required prerequisite step(s): $($missing -join ', '). Record promotion and planning completion before implementation, review, PR, CI, or DONE steps."
295
+ }
282
296
  }
283
297
  }
284
298
 
285
- return [ordered]@{ decision = 'allow' }
299
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
286
300
  }
287
301
 
288
302
  # Guard allows dot-sourcing in tests without executing the entrypoint.
303
+ function ConvertFrom-CodexCheckpointHookPayload {
304
+ [CmdletBinding()]
305
+ param([Parameter(Mandatory)][string] $PayloadRaw)
306
+
307
+ if ([string]::IsNullOrWhiteSpace($PayloadRaw)) {
308
+ throw 'enforce-checkpoint-monotonic hook input is empty.'
309
+ }
310
+ try {
311
+ $payload = $PayloadRaw | ConvertFrom-Json -ErrorAction Stop
312
+ } catch {
313
+ throw "enforce-checkpoint-monotonic hook input is malformed JSON: $_"
314
+ }
315
+ if ($payload.PSObject.Properties.Name -notcontains 'tool_input' -or $null -eq $payload.tool_input) {
316
+ throw 'enforce-checkpoint-monotonic hook input is missing tool_input.'
317
+ }
318
+ if ([string]$payload.hook_event_name -ne 'PreToolUse' -or [string]$payload.tool_name -ne 'apply_patch') {
319
+ throw 'enforce-checkpoint-monotonic requires a PreToolUse apply_patch payload.'
320
+ }
321
+ return $payload
322
+ }
323
+
324
+ function ConvertTo-CodexApplyPatchCheckpointInput {
325
+ [CmdletBinding()]
326
+ [OutputType([object[]])]
327
+ param([Parameter(Mandatory)] $Payload)
328
+
329
+ if ($Payload.tool_input.PSObject.Properties.Name -contains 'file_path') {
330
+ return , $Payload.tool_input
331
+ }
332
+ $command = [string]$Payload.tool_input.command
333
+ if ([string]::IsNullOrWhiteSpace($command)) {
334
+ throw 'enforce-checkpoint-monotonic cannot map tool_input to a file edit.'
335
+ }
336
+
337
+ $fileMatches = [regex]::Matches(
338
+ $command,
339
+ '(?ms)^\*\*\* (?<operation>Add|Update|Delete) File:\s*(?<path>.+?)\r?\n(?<body>.*?)(?=^\*\*\* (?:(?:Add|Update|Delete) File:|End Patch)\s*|\z)'
340
+ )
341
+ if ($fileMatches.Count -eq 0) {
342
+ throw 'enforce-checkpoint-monotonic received an unrecognized apply_patch command.'
343
+ }
344
+
345
+ $inputs = [System.Collections.Generic.List[object]]::new()
346
+ foreach ($match in $fileMatches) {
347
+ $sourcePath = ([string]$match.Groups['path'].Value).Trim()
348
+ $path = $sourcePath
349
+ $operation = [string]$match.Groups['operation'].Value
350
+ $body = ([string]$match.Groups['body'].Value) -replace '\r\n', "`n"
351
+ $moveMatch = [regex]::Match($body, '(?m)^\*\*\* Move to:\s*(?<path>.+?)\s*$')
352
+ if ($moveMatch.Success) {
353
+ $path = ([string]$moveMatch.Groups['path'].Value).Trim()
354
+ }
355
+ if ($operation -eq 'Delete') {
356
+ $content = ''
357
+ } elseif ($operation -eq 'Add') {
358
+ $content = (($body -split "`n") | ForEach-Object {
359
+ if ($_.StartsWith('+') -and -not $_.StartsWith('+++')) {
360
+ $_.Substring(1)
361
+ }
362
+ }) -join "`n"
363
+ } else {
364
+ if (-not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) {
365
+ throw "enforce-checkpoint-monotonic cannot read update source '$sourcePath'."
366
+ }
367
+ $content = (Get-Content -Raw -LiteralPath $sourcePath) -replace '\r\n', "`n"
368
+ $hunks = @([regex]::Split($body, '(?m)^@@[^\r\n]*\r?\n') | Where-Object { $_ -match '\S' })
369
+ foreach ($hunk in $hunks) {
370
+ $oldLines = [System.Collections.Generic.List[string]]::new()
371
+ $newLines = [System.Collections.Generic.List[string]]::new()
372
+ foreach ($line in ($hunk -split "`n")) {
373
+ if ($line -match '^\*\*\* (?:Move to|End of File)') {
374
+ continue
375
+ }
376
+ if ($line.StartsWith('+') -and -not $line.StartsWith('+++')) {
377
+ $newLines.Add($line.Substring(1))
378
+ } elseif ($line.StartsWith('-') -and -not $line.StartsWith('---')) {
379
+ $oldLines.Add($line.Substring(1))
380
+ } elseif ($line.StartsWith(' ')) {
381
+ $oldLines.Add($line.Substring(1))
382
+ $newLines.Add($line.Substring(1))
383
+ } else {
384
+ $oldLines.Add($line)
385
+ $newLines.Add($line)
386
+ }
387
+ }
388
+ $oldText = $oldLines -join "`n"
389
+ $newText = $newLines -join "`n"
390
+ $index = $content.IndexOf($oldText, [System.StringComparison]::Ordinal)
391
+ if ($index -lt 0) {
392
+ throw "enforce-checkpoint-monotonic could not apply an in-memory patch hunk for '$path'."
393
+ }
394
+ $content = $content.Substring(0, $index) + $newText + $content.Substring($index + $oldText.Length)
395
+ }
396
+ }
397
+ $inputs.Add([pscustomobject]@{ file_path = $path; content = $content })
398
+ }
399
+ return $inputs.ToArray()
400
+ }
401
+
289
402
  if ($MyInvocation.InvocationName -eq '.') {
290
403
  return
291
404
  }
292
405
 
293
406
  try {
294
- $decision = Invoke-CheckpointMonotonicDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
295
- }
296
- catch {
297
- Write-Error $_
298
- exit 1
407
+ $payload = ConvertFrom-CodexCheckpointHookPayload -PayloadRaw ([Console]::In.ReadToEnd())
408
+ foreach ($toolInput in @(ConvertTo-CodexApplyPatchCheckpointInput -Payload $payload)) {
409
+ $toolInputRaw = $toolInput | ConvertTo-Json -Compress -Depth 20
410
+ $decision = Invoke-CheckpointMonotonicDecision -ToolInputRaw $toolInputRaw
411
+ if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
412
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
413
+ exit 0
414
+ }
415
+ }
416
+ exit 0
417
+ } catch {
418
+ [Console]::Error.WriteLine([string]$_)
419
+ exit 2
299
420
  }
300
-
301
- $decision | ConvertTo-Json -Compress | Write-Output
302
-
303
- exit 0