@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
@@ -1,14 +1,12 @@
1
- # Converted hook
2
- # Review the generated hook behavior before enabling it.
3
1
 
4
2
  <#
5
3
  .SYNOPSIS
6
- Pre-tool-use hook for Claude Code that blocks forbidden patterns in PowerShell unit tests.
4
+ Pre-tool-use hook for Codex that blocks forbidden patterns in PowerShell unit tests.
7
5
 
8
6
  .DESCRIPTION
9
- This script is invoked by the Claude Code PreToolUse hook before any Write or Edit
7
+ This script is invoked by the Codex PreToolUse hook before any Write or Edit
10
8
  operation on a file path matching a Pester test file (*.Tests.ps1 or tests/**/*.ps1).
11
- It reads the tool input from the CLAUDE_TOOL_INPUT environment variable (JSON with
9
+ It reads the tool input from the Codex tool_input environment variable (JSON with
12
10
  'file_path' and a content field: 'content' for Write, 'new_string' for Edit) and
13
11
  rejects the operation when the proposed content introduces forbidden runtime
14
12
  dependencies or violates the mocking rules.
@@ -23,9 +21,9 @@
23
21
  - subprocess execution (Start-Process with raw executables)
24
22
  - time-based flakiness (Start-Sleep)
25
23
 
26
- If the content contains any forbidden pattern, the script writes a JSON response
27
- to stdout with 'decision': 'block' and exits with code 0 to let Claude Code surface
28
- the reason.
24
+ If the content contains any forbidden pattern, the script writes a PreToolUse JSON
25
+ response to stdout with hookSpecificOutput.permissionDecision = 'deny' and exits with
26
+ code 0 to let Codex surface the reason.
29
27
 
30
28
  .NOTES
31
29
  Compatible with PowerShell 7+.
@@ -35,79 +33,190 @@
35
33
  [CmdletBinding()]
36
34
  param()
37
35
 
38
- $toolInputRaw = $env:CLAUDE_TOOL_INPUT
39
- if (-not $toolInputRaw) {
40
- exit 0
36
+ function Get-PowerShellTestPurityBlockDecision {
37
+ [CmdletBinding()]
38
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
39
+ param(
40
+ [Parameter(Mandatory)]
41
+ [string] $Reason
42
+ )
43
+
44
+ [ordered]@{
45
+ hookSpecificOutput = [ordered]@{
46
+ hookEventName = 'PreToolUse'
47
+ permissionDecision = 'deny'
48
+ permissionDecisionReason = $Reason
49
+ }
50
+ }
41
51
  }
42
52
 
43
- try {
44
- $toolInput = $toolInputRaw | ConvertFrom-Json -ErrorAction Stop
45
- }
46
- catch {
47
- exit 0
48
- }
53
+ function Test-PowerShellTestFilePath {
54
+ [CmdletBinding()]
55
+ [OutputType([bool])]
56
+ param(
57
+ [Parameter(Mandatory)]
58
+ [string] $FilePath
59
+ )
49
60
 
50
- $filePath = $toolInput.file_path
51
- if (-not $filePath) {
52
- exit 0
61
+ $normalized = $FilePath -replace '\\', '/'
62
+ return (($normalized -match '(^|/)tests/.*\.ps1$') -or ($normalized -match '\.Tests\.ps1$'))
53
63
  }
54
64
 
55
- $normalized = $filePath -replace '\\', '/'
56
- $isPowerShellTestFile = ($normalized -match '(^|/)tests/.*\.ps1$') -or ($normalized -match '\.Tests\.ps1$')
57
- if (-not $isPowerShellTestFile) {
58
- exit 0
59
- }
65
+ function Invoke-PowerShellTestPurityDecision {
66
+ [CmdletBinding()]
67
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
68
+ param(
69
+ [string] $ToolInputRaw
70
+ )
60
71
 
61
- $content = $null
62
- if ($null -ne $toolInput.content) {
63
- $content = [string]$toolInput.content
64
- }
65
- elseif ($null -ne $toolInput.new_string) {
66
- $content = [string]$toolInput.new_string
67
- }
72
+ if (-not $ToolInputRaw) {
73
+ return $null
74
+ }
68
75
 
69
- if (-not $content) {
70
- exit 0
71
- }
76
+ try {
77
+ $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
78
+ }
79
+ catch {
80
+ return $null
81
+ }
82
+
83
+ $filePath = $toolInput.file_path
84
+ if (-not $filePath) {
85
+ return $null
86
+ }
87
+
88
+ if (-not (Test-PowerShellTestFilePath -FilePath $filePath)) {
89
+ return $null
90
+ }
91
+
92
+ $content = $null
93
+ if ($null -ne $toolInput.content) {
94
+ $content = [string]$toolInput.content
95
+ }
96
+ elseif ($null -ne $toolInput.new_string) {
97
+ $content = [string]$toolInput.new_string
98
+ }
99
+
100
+ if (-not $content) {
101
+ return $null
102
+ }
72
103
 
73
- $forbiddenPatterns = @(
74
- @{ Pattern = '(?m)^\s*Mock\s+git\b'; Reason = 'direct Mock git forbidden in Pester tests; mock the Invoke-GitExe wrapper instead' },
75
- @{ Pattern = '(?m)^\s*Mock\s+gh\b'; Reason = 'direct Mock gh forbidden in Pester tests; mock the Invoke-GhExe wrapper instead' },
76
- @{ Pattern = '(?m)^\s*Mock\s+actionlint\b'; Reason = 'direct Mock actionlint forbidden in Pester tests; mock the Invoke-ActionlintExe wrapper instead' },
77
- @{ Pattern = "(?m)^\s*Mock\s+['""]git['""]"; Reason = 'direct Mock ''git'' forbidden in Pester tests; mock the Invoke-GitExe wrapper instead' },
78
- @{ Pattern = "(?m)^\s*Mock\s+['""]gh['""]"; Reason = 'direct Mock ''gh'' forbidden in Pester tests; mock the Invoke-GhExe wrapper instead' },
79
- @{ Pattern = '\bNew-TemporaryFile\b'; Reason = 'New-TemporaryFile forbidden in Pester unit tests' },
80
- @{ Pattern = '\[System\.IO\.Path\]::GetTempFileName'; Reason = 'temporary files forbidden in Pester unit tests' },
81
- @{ Pattern = '\[System\.IO\.Path\]::GetTempPath'; Reason = 'temp path usage forbidden in Pester unit tests' },
82
- @{ Pattern = '\$env:TEMP\b'; Reason = '$env:TEMP usage forbidden in Pester unit tests' },
83
- @{ Pattern = '\$env:TMP\b'; Reason = '$env:TMP usage forbidden in Pester unit tests' },
84
- @{ Pattern = '\bInvoke-WebRequest\b'; Reason = 'network access (Invoke-WebRequest) forbidden in Pester unit tests' },
85
- @{ Pattern = '\bInvoke-RestMethod\b'; Reason = 'network access (Invoke-RestMethod) forbidden in Pester unit tests' },
86
- @{ Pattern = '\[System\.Net\.Http\.'; Reason = 'System.Net.Http usage forbidden in Pester unit tests' },
87
- @{ Pattern = '\[System\.Net\.WebRequest\]'; Reason = 'System.Net.WebRequest usage forbidden in Pester unit tests' },
88
- @{ Pattern = '\[System\.Net\.Sockets\.'; Reason = 'raw socket access forbidden in Pester unit tests' },
89
- @{ Pattern = '\bStart-Process\b'; Reason = 'Start-Process forbidden in Pester unit tests; mock the wrapper seam instead' },
90
- @{ Pattern = '\bStart-Sleep\b'; Reason = 'Start-Sleep forbidden in Pester unit tests; avoid timing hacks' }
91
- )
92
-
93
- $violations = @()
94
- foreach ($entry in $forbiddenPatterns) {
95
- if ($content -match $entry.Pattern) {
96
- $violations += $entry.Reason
104
+ $forbiddenPatterns = @(
105
+ @{ Pattern = '(?m)^\s*Mock\s+git\b'; Reason = 'direct Mock git forbidden in Pester tests; mock the Invoke-GitExe wrapper instead' },
106
+ @{ Pattern = '(?m)^\s*Mock\s+gh\b'; Reason = 'direct Mock gh forbidden in Pester tests; mock the Invoke-GhExe wrapper instead' },
107
+ @{ Pattern = '(?m)^\s*Mock\s+actionlint\b'; Reason = 'direct Mock actionlint forbidden in Pester tests; mock the Invoke-ActionlintExe wrapper instead' },
108
+ @{ Pattern = "(?m)^\s*Mock\s+['""]git['""]"; Reason = 'direct Mock ''git'' forbidden in Pester tests; mock the Invoke-GitExe wrapper instead' },
109
+ @{ Pattern = "(?m)^\s*Mock\s+['""]gh['""]"; Reason = 'direct Mock ''gh'' forbidden in Pester tests; mock the Invoke-GhExe wrapper instead' },
110
+ @{ Pattern = '\bNew-TemporaryFile\b'; Reason = 'New-TemporaryFile forbidden in Pester unit tests' },
111
+ @{ Pattern = '\[System\.IO\.Path\]::GetTempFileName'; Reason = 'temporary files forbidden in Pester unit tests' },
112
+ @{ Pattern = '\[System\.IO\.Path\]::GetTempPath'; Reason = 'temp path usage forbidden in Pester unit tests' },
113
+ @{ Pattern = '\$env:TEMP\b'; Reason = '$env:TEMP usage forbidden in Pester unit tests' },
114
+ @{ Pattern = '\$env:TMP\b'; Reason = '$env:TMP usage forbidden in Pester unit tests' },
115
+ @{ Pattern = '\bInvoke-WebRequest\b'; Reason = 'network access (Invoke-WebRequest) forbidden in Pester unit tests' },
116
+ @{ Pattern = '\bInvoke-RestMethod\b'; Reason = 'network access (Invoke-RestMethod) forbidden in Pester unit tests' },
117
+ @{ Pattern = '\[System\.Net\.Http\.'; Reason = 'System.Net.Http usage forbidden in Pester unit tests' },
118
+ @{ Pattern = '\[System\.Net\.WebRequest\]'; Reason = 'System.Net.WebRequest usage forbidden in Pester unit tests' },
119
+ @{ Pattern = '\[System\.Net\.Sockets\.'; Reason = 'raw socket access forbidden in Pester unit tests' },
120
+ @{ Pattern = '\bStart-Process\b'; Reason = 'Start-Process forbidden in Pester unit tests; mock the wrapper seam instead' },
121
+ @{ Pattern = '\bStart-Sleep\b'; Reason = 'Start-Sleep forbidden in Pester unit tests; avoid timing hacks' }
122
+ )
123
+
124
+ $violations = @()
125
+ foreach ($entry in $forbiddenPatterns) {
126
+ if ($content -match $entry.Pattern) {
127
+ $violations += $entry.Reason
128
+ }
97
129
  }
130
+
131
+ if ($violations.Count -eq 0) {
132
+ return $null
133
+ }
134
+
135
+ $uniqueViolations = $violations | Select-Object -Unique
136
+ $reason = "PowerShell unit test purity violations in '$filePath': " + ($uniqueViolations -join '; ') + ". Replace with wrapper-seam mocks, in-memory fakes, or pure code paths per .agents/skills/powershell/SKILL.md."
137
+
138
+ return Get-PowerShellTestPurityBlockDecision -Reason $reason
98
139
  }
99
140
 
100
- if ($violations.Count -eq 0) {
101
- exit 0
141
+ function ConvertFrom-CodexPowerShellPurityPayload {
142
+ [CmdletBinding()]
143
+ param([Parameter(Mandatory)][string] $PayloadRaw)
144
+
145
+ if ([string]::IsNullOrWhiteSpace($PayloadRaw)) {
146
+ throw 'check-powershell-test-purity hook input is empty.'
147
+ }
148
+ try {
149
+ $payload = $PayloadRaw | ConvertFrom-Json -ErrorAction Stop
150
+ } catch {
151
+ throw "check-powershell-test-purity hook input is malformed JSON: $_"
152
+ }
153
+ if ($payload.PSObject.Properties.Name -notcontains 'tool_input' -or $null -eq $payload.tool_input) {
154
+ throw 'check-powershell-test-purity hook input is missing tool_input.'
155
+ }
156
+ if ([string]$payload.hook_event_name -ne 'PreToolUse' -or [string]$payload.tool_name -ne 'apply_patch') {
157
+ throw 'check-powershell-test-purity requires a PreToolUse apply_patch payload.'
158
+ }
159
+ return $payload
102
160
  }
103
161
 
104
- $uniqueViolations = $violations | Select-Object -Unique
105
- $reason = "PowerShell unit test purity violations in '$filePath': " + ($uniqueViolations -join '; ') + ". Replace with wrapper-seam mocks, in-memory fakes, or pure code paths per .agents/skills/powershell/SKILL.md."
162
+ function ConvertTo-CodexPowerShellPurityInput {
163
+ [CmdletBinding()]
164
+ [OutputType([object[]])]
165
+ param([Parameter(Mandatory)] $Payload)
166
+
167
+ if ($Payload.tool_input.PSObject.Properties.Name -contains 'file_path') {
168
+ return , $Payload.tool_input
169
+ }
170
+
171
+ $command = [string]$Payload.tool_input.command
172
+ if ([string]::IsNullOrWhiteSpace($command)) {
173
+ throw 'check-powershell-test-purity cannot map tool_input to a file edit.'
174
+ }
175
+
176
+ $fileMatches = [regex]::Matches(
177
+ $command,
178
+ '(?ms)^\*\*\* (?:Add|Update|Delete) File:\s*(?<path>.+?)\r?\n(?<body>.*?)(?=^\*\*\* (?:(?:Add|Update|Delete) File:|End Patch)\s*|\z)'
179
+ )
180
+ if ($fileMatches.Count -eq 0) {
181
+ throw 'check-powershell-test-purity received an unrecognized apply_patch command.'
182
+ }
183
+
184
+ $inputs = [System.Collections.Generic.List[object]]::new()
185
+ foreach ($match in $fileMatches) {
186
+ $filePath = ([string]$match.Groups['path'].Value).Trim()
187
+ $moveMatch = [regex]::Match([string]$match.Groups['body'].Value, '(?m)^\*\*\* Move to:\s*(?<path>.+?)\s*$')
188
+ if ($moveMatch.Success) {
189
+ $filePath = ([string]$moveMatch.Groups['path'].Value).Trim()
190
+ }
191
+ $addedLines = foreach ($line in ([string]$match.Groups['body'].Value -split '\r?\n')) {
192
+ if ($line.StartsWith('+') -and -not $line.StartsWith('+++')) {
193
+ $line.Substring(1)
194
+ }
195
+ }
196
+ $inputs.Add([pscustomobject]@{
197
+ file_path = $filePath
198
+ content = $addedLines -join [Environment]::NewLine
199
+ })
200
+ }
201
+ return $inputs.ToArray()
202
+ }
106
203
 
107
- $response = @{
108
- decision = 'block'
109
- reason = $reason
110
- } | ConvertTo-Json -Compress
204
+ if ($MyInvocation.InvocationName -eq '.') {
205
+ return
206
+ }
111
207
 
112
- Write-Output $response
113
- exit 0
208
+ try {
209
+ $payload = ConvertFrom-CodexPowerShellPurityPayload -PayloadRaw ([Console]::In.ReadToEnd())
210
+ foreach ($toolInput in @(ConvertTo-CodexPowerShellPurityInput -Payload $payload)) {
211
+ $toolInputRaw = $toolInput | ConvertTo-Json -Compress -Depth 20
212
+ $decision = Invoke-PowerShellTestPurityDecision -ToolInputRaw $toolInputRaw
213
+ if ($null -ne $decision -and $decision.hookSpecificOutput.permissionDecision -eq 'deny') {
214
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
215
+ exit 0
216
+ }
217
+ }
218
+ exit 0
219
+ } catch {
220
+ [Console]::Error.WriteLine([string]$_)
221
+ exit 2
222
+ }
@@ -1,14 +1,12 @@
1
- # Converted hook
2
- # Review the generated hook behavior before enabling it.
3
1
 
4
2
  <#
5
3
  .SYNOPSIS
6
- Pre-tool-use hook for Claude Code that blocks forbidden patterns in Python unit tests.
4
+ Pre-tool-use hook for Codex that blocks forbidden patterns in Python unit tests.
7
5
 
8
6
  .DESCRIPTION
9
- This script is invoked by the Claude Code PreToolUse hook before any Write or Edit
7
+ This script is invoked by the Codex PreToolUse hook before any Write or Edit
10
8
  operation on a file path matching tests/**/*.py. It reads the tool input from the
11
- CLAUDE_TOOL_INPUT environment variable (JSON with 'file_path' and a content field:
9
+ Codex tool_input environment variable (JSON with 'file_path' and a content field:
12
10
  'content' for Write, 'new_string' for Edit) and rejects the operation when the
13
11
  proposed content introduces forbidden runtime dependencies.
14
12
 
@@ -21,7 +19,7 @@
21
19
  - real database drivers (psycopg2, pymysql, sqlite3.connect on real files)
22
20
 
23
21
  If the content contains any forbidden pattern, the script writes a JSON response
24
- to stdout with 'decision': 'block' and exits with code 0 to let Claude Code surface
22
+ to stdout with 'decision': 'block' and exits with code 0 to let Codex surface
25
23
  the reason.
26
24
 
27
25
  .NOTES
@@ -41,8 +39,11 @@ function Get-PythonTestPurityBlockDecision {
41
39
  )
42
40
 
43
41
  [ordered]@{
44
- decision = 'block'
45
- reason = $Reason
42
+ hookSpecificOutput = [ordered]@{
43
+ hookEventName = 'PreToolUse'
44
+ permissionDecision = 'deny'
45
+ permissionDecisionReason = $Reason
46
+ }
46
47
  }
47
48
  }
48
49
 
@@ -66,22 +67,22 @@ function Invoke-PythonTestPurityDecision {
66
67
  )
67
68
 
68
69
  if (-not $ToolInputRaw) {
69
- return [ordered]@{ decision = 'allow' }
70
+ return $null
70
71
  }
71
72
 
72
73
  try {
73
74
  $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
74
75
  } catch {
75
- return Get-PythonTestPurityBlockDecision -Reason 'Python unit test purity hook received malformed JSON in CLAUDE_TOOL_INPUT.'
76
+ return Get-PythonTestPurityBlockDecision -Reason 'Python unit test purity hook received malformed JSON in Codex tool_input.'
76
77
  }
77
78
 
78
79
  $filePath = $toolInput.file_path
79
80
  if (-not $filePath) {
80
- return [ordered]@{ decision = 'allow' }
81
+ return $null
81
82
  }
82
83
 
83
84
  if (-not (Test-PythonTestFilePath -FilePath $filePath)) {
84
- return [ordered]@{ decision = 'allow' }
85
+ return $null
85
86
  }
86
87
 
87
88
  $content = $null
@@ -92,7 +93,7 @@ function Invoke-PythonTestPurityDecision {
92
93
  }
93
94
 
94
95
  if (-not $content) {
95
- return [ordered]@{ decision = 'allow' }
96
+ return $null
96
97
  }
97
98
 
98
99
  $forbiddenPatterns = @(
@@ -128,7 +129,7 @@ function Invoke-PythonTestPurityDecision {
128
129
  }
129
130
 
130
131
  if ($violations.Count -eq 0) {
131
- return [ordered]@{ decision = 'allow' }
132
+ return $null
132
133
  }
133
134
 
134
135
  $uniqueViolations = $violations | Select-Object -Unique
@@ -137,13 +138,85 @@ function Invoke-PythonTestPurityDecision {
137
138
  return Get-PythonTestPurityBlockDecision -Reason $reason
138
139
  }
139
140
 
141
+ function ConvertFrom-CodexPythonPurityPayload {
142
+ [CmdletBinding()]
143
+ param([Parameter(Mandatory)][string] $PayloadRaw)
144
+
145
+ if ([string]::IsNullOrWhiteSpace($PayloadRaw)) {
146
+ throw 'check-python-test-purity hook input is empty.'
147
+ }
148
+ try {
149
+ $payload = $PayloadRaw | ConvertFrom-Json -ErrorAction Stop
150
+ } catch {
151
+ throw "check-python-test-purity hook input is malformed JSON: $_"
152
+ }
153
+ if ($payload.PSObject.Properties.Name -notcontains 'tool_input' -or $null -eq $payload.tool_input) {
154
+ throw 'check-python-test-purity hook input is missing tool_input.'
155
+ }
156
+ if ([string]$payload.hook_event_name -ne 'PreToolUse' -or [string]$payload.tool_name -ne 'apply_patch') {
157
+ throw 'check-python-test-purity requires a PreToolUse apply_patch payload.'
158
+ }
159
+ return $payload
160
+ }
161
+
162
+ function ConvertTo-CodexPythonPurityInput {
163
+ [CmdletBinding()]
164
+ [OutputType([object[]])]
165
+ param([Parameter(Mandatory)] $Payload)
166
+
167
+ if ($Payload.tool_input.PSObject.Properties.Name -contains 'file_path') {
168
+ return , $Payload.tool_input
169
+ }
170
+
171
+ $command = [string]$Payload.tool_input.command
172
+ if ([string]::IsNullOrWhiteSpace($command)) {
173
+ throw 'check-python-test-purity cannot map tool_input to a file edit.'
174
+ }
175
+
176
+ $fileMatches = [regex]::Matches(
177
+ $command,
178
+ '(?ms)^\*\*\* (?:Add|Update|Delete) File:\s*(?<path>.+?)\r?\n(?<body>.*?)(?=^\*\*\* (?:(?:Add|Update|Delete) File:|End Patch)\s*|\z)'
179
+ )
180
+ if ($fileMatches.Count -eq 0) {
181
+ throw 'check-python-test-purity received an unrecognized apply_patch command.'
182
+ }
183
+
184
+ $inputs = [System.Collections.Generic.List[object]]::new()
185
+ foreach ($match in $fileMatches) {
186
+ $filePath = ([string]$match.Groups['path'].Value).Trim()
187
+ $moveMatch = [regex]::Match([string]$match.Groups['body'].Value, '(?m)^\*\*\* Move to:\s*(?<path>.+?)\s*$')
188
+ if ($moveMatch.Success) {
189
+ $filePath = ([string]$moveMatch.Groups['path'].Value).Trim()
190
+ }
191
+ $addedLines = foreach ($line in ([string]$match.Groups['body'].Value -split '\r?\n')) {
192
+ if ($line.StartsWith('+') -and -not $line.StartsWith('+++')) {
193
+ $line.Substring(1)
194
+ }
195
+ }
196
+ $inputs.Add([pscustomobject]@{
197
+ file_path = $filePath
198
+ content = $addedLines -join [Environment]::NewLine
199
+ })
200
+ }
201
+ return $inputs.ToArray()
202
+ }
203
+
140
204
  if ($MyInvocation.InvocationName -eq '.') {
141
205
  return
142
206
  }
143
207
 
144
- $decision = Invoke-PythonTestPurityDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
145
- if ($decision.decision -eq 'block') {
146
- $decision | ConvertTo-Json -Compress | Write-Output
208
+ try {
209
+ $payload = ConvertFrom-CodexPythonPurityPayload -PayloadRaw ([Console]::In.ReadToEnd())
210
+ foreach ($toolInput in @(ConvertTo-CodexPythonPurityInput -Payload $payload)) {
211
+ $toolInputRaw = $toolInput | ConvertTo-Json -Compress -Depth 20
212
+ $decision = Invoke-PythonTestPurityDecision -ToolInputRaw $toolInputRaw
213
+ if ($null -ne $decision -and $decision.hookSpecificOutput.permissionDecision -eq 'deny') {
214
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
215
+ exit 0
216
+ }
217
+ }
218
+ exit 0
219
+ } catch {
220
+ [Console]::Error.WriteLine([string]$_)
221
+ exit 2
147
222
  }
148
-
149
- exit 0
@@ -0,0 +1,199 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Reads and validates the immutable identity fields of a Codex agent profile.
4
+ #>
5
+ [CmdletBinding()]
6
+ param()
7
+
8
+ function Get-CodexAgentProfileAttestation {
9
+ [CmdletBinding()]
10
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
11
+ param(
12
+ [Parameter(Mandatory)][string] $RepositoryRoot,
13
+ [Parameter(Mandatory)][string] $AgentType
14
+ )
15
+
16
+ if ($AgentType -notmatch '^[A-Za-z0-9][A-Za-z0-9._-]*$') {
17
+ throw "MODEL_ROUTING_ATTESTATION_BLOCKED: invalid agent profile name '$AgentType'."
18
+ }
19
+ $relativePath = ".codex/agents/$AgentType.toml"
20
+ $profilePath = Join-Path $RepositoryRoot ($relativePath -replace '/', [IO.Path]::DirectorySeparatorChar)
21
+ if (-not (Test-Path -LiteralPath $profilePath -PathType Leaf)) {
22
+ throw "MODEL_ROUTING_ATTESTATION_BLOCKED: agent profile '$relativePath' does not exist."
23
+ }
24
+
25
+ $bytes = [IO.File]::ReadAllBytes($profilePath)
26
+ $sha256 = [Convert]::ToHexString(
27
+ [Security.Cryptography.SHA256]::HashData($bytes)
28
+ ).ToLowerInvariant()
29
+ try {
30
+ $content = [Text.UTF8Encoding]::new($false, $true).GetString($bytes)
31
+ } catch {
32
+ throw "MODEL_ROUTING_ATTESTATION_BLOCKED: agent profile '$relativePath' is not valid UTF-8."
33
+ }
34
+
35
+ $values = @{}
36
+ $inMultilineString = $false
37
+ $multilineDelimiter = ''
38
+ $insideTable = $false
39
+ foreach ($line in ($content -split "`r?`n")) {
40
+ if ($inMultilineString) {
41
+ if ($line.Contains($multilineDelimiter)) {
42
+ $inMultilineString = $false
43
+ $multilineDelimiter = ''
44
+ }
45
+ continue
46
+ }
47
+ if ($line -match '^\s*\[') {
48
+ $insideTable = $true
49
+ continue
50
+ }
51
+ if ($insideTable -or $line -match '^\s*(?:#|$)') {
52
+ continue
53
+ }
54
+ if ($line -match '^\s*[A-Za-z0-9_.-]+\s*=\s*' -and
55
+ ($line.Contains("'''") -or $line.Contains('"""'))) {
56
+ $delimiter = if ($line.Contains("'''")) { "'''" } else { '"""' }
57
+ $remainder = $line.Substring($line.IndexOf($delimiter) + 3)
58
+ if (-not $remainder.Contains($delimiter)) {
59
+ $inMultilineString = $true
60
+ $multilineDelimiter = $delimiter
61
+ }
62
+ continue
63
+ }
64
+ if ($line -notmatch '^\s*(?<key>name|model|model_reasoning_effort)\s*=\s*"(?<value>[A-Za-z0-9._-]+)"\s*(?:#.*)?$') {
65
+ continue
66
+ }
67
+ $key = [string]$Matches.key
68
+ if ($values.ContainsKey($key)) {
69
+ throw "MODEL_ROUTING_ATTESTATION_BLOCKED: agent profile '$relativePath' repeats '$key'."
70
+ }
71
+ $values[$key] = [string]$Matches.value
72
+ }
73
+
74
+ foreach ($requiredKey in @('name', 'model', 'model_reasoning_effort')) {
75
+ if (-not $values.ContainsKey($requiredKey)) {
76
+ throw "MODEL_ROUTING_ATTESTATION_BLOCKED: agent profile '$relativePath' has no valid '$requiredKey'."
77
+ }
78
+ }
79
+ return [ordered]@{
80
+ profile_path = $relativePath
81
+ profile_sha256 = $sha256
82
+ profile_name = [string]$values.name
83
+ profile_model = [string]$values.model
84
+ profile_reasoning_effort = [string]$values.model_reasoning_effort
85
+ }
86
+ }
87
+
88
+ function Test-CodexAgentProfileBinding {
89
+ [CmdletBinding()]
90
+ [OutputType([bool])]
91
+ param(
92
+ [Parameter(Mandatory)] $AgentProfile,
93
+ [Parameter(Mandatory)][string] $AgentType,
94
+ [Parameter(Mandatory)][string] $ActualModel,
95
+ [Parameter(Mandatory)][string] $ExpectedModel,
96
+ [Parameter(Mandatory)][string] $ExpectedReasoningEffort,
97
+ [AllowNull()][AllowEmptyString()][string] $ExpectedProfilePath,
98
+ [AllowNull()][AllowEmptyString()][string] $ExpectedProfileSha256
99
+ )
100
+
101
+ if ([string]::IsNullOrWhiteSpace($ExpectedModel) -or
102
+ [string]::IsNullOrWhiteSpace($ExpectedReasoningEffort)) {
103
+ return $false
104
+ }
105
+ if ([string]$AgentProfile.profile_name -cne $AgentType -or
106
+ [string]$AgentProfile.profile_model -cne $ExpectedModel -or
107
+ $ActualModel -cne $ExpectedModel -or
108
+ [string]$AgentProfile.profile_reasoning_effort -cne $ExpectedReasoningEffort) {
109
+ return $false
110
+ }
111
+ if (-not [string]::IsNullOrWhiteSpace($ExpectedProfilePath) -and
112
+ [string]$AgentProfile.profile_path -cne $ExpectedProfilePath) {
113
+ return $false
114
+ }
115
+ if (-not [string]::IsNullOrWhiteSpace($ExpectedProfileSha256) -and
116
+ [string]$AgentProfile.profile_sha256 -cne $ExpectedProfileSha256) {
117
+ return $false
118
+ }
119
+ return $true
120
+ }
121
+
122
+ function Test-CodexRoutedAgentType {
123
+ [CmdletBinding()]
124
+ [OutputType([bool])]
125
+ param([Parameter(Mandatory)][string] $AgentType)
126
+
127
+ if ($AgentType -match '-c(?:1|2|3|4|3-elevated)$') {
128
+ return $true
129
+ }
130
+ return @(
131
+ 'orchestrator', 'atomic-planner', 'atomic-executor', 'feature-review',
132
+ 'feature-reviewer', 'task-researcher', 'prd-feature', 'pr-author',
133
+ 'python-typed-engineer', 'powershell-typed-engineer',
134
+ 'csharp-typed-engineer', 'typescript-engineer'
135
+ ) -contains $AgentType
136
+ }
137
+
138
+ function Test-CodexRoutingReceiptAgentType {
139
+ [CmdletBinding()]
140
+ [OutputType([bool])]
141
+ param(
142
+ [AllowNull()] $Receipt,
143
+ [Parameter(Mandatory)][string] $AgentType
144
+ )
145
+
146
+ if ($null -eq $Receipt) {
147
+ return $false
148
+ }
149
+ $properties = @($Receipt.PSObject.Properties.Name)
150
+ return $properties -contains 'deployment_agent' -and
151
+ -not [string]::IsNullOrWhiteSpace([string]$Receipt.deployment_agent) -and
152
+ [string]$Receipt.deployment_agent -ceq $AgentType
153
+ }
154
+
155
+ function Find-CodexModelRoutingReceipt {
156
+ [CmdletBinding()]
157
+ param(
158
+ [Parameter(Mandatory)][string] $AgentType,
159
+ [Parameter(Mandatory)][AllowEmptyCollection()][object[]] $Checkpoints
160
+ )
161
+
162
+ $matchingReceipts = [Collections.Generic.List[object]]::new()
163
+ foreach ($checkpoint in $Checkpoints) {
164
+ if ($null -eq $checkpoint) {
165
+ continue
166
+ }
167
+ $checkpointProperties = @($checkpoint.PSObject.Properties.Name)
168
+ foreach ($receiptKey in @('codex_model_routing_receipts', 'model_routing_receipts')) {
169
+ if ($checkpointProperties -notcontains $receiptKey) {
170
+ continue
171
+ }
172
+ foreach ($receipt in @($checkpoint.$receiptKey)) {
173
+ if (Test-CodexRoutingReceiptAgentType -Receipt $receipt -AgentType $AgentType) {
174
+ $matchingReceipts.Add($receipt)
175
+ }
176
+ }
177
+ }
178
+ if ($checkpointProperties -contains 'features') {
179
+ foreach ($feature in @($checkpoint.features)) {
180
+ if ($null -eq $feature -or
181
+ @($feature.PSObject.Properties.Name) -notcontains 'model_routing_receipt') {
182
+ continue
183
+ }
184
+ $receipt = $feature.model_routing_receipt
185
+ if (Test-CodexRoutingReceiptAgentType -Receipt $receipt -AgentType $AgentType) {
186
+ $matchingReceipts.Add($receipt)
187
+ }
188
+ }
189
+ }
190
+ }
191
+ if ($matchingReceipts.Count -eq 0) {
192
+ return $null
193
+ }
194
+ return $matchingReceipts[$matchingReceipts.Count - 1]
195
+ }
196
+
197
+ if ($MyInvocation.InvocationName -eq '.') {
198
+ return
199
+ }