@danmoisan/drm-copilot-mcp 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/mcp-server.js +3160 -458
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +24 -0
- package/resources/claude-customizations/.claude/agents/epic-planner.md +121 -0
- package/resources/claude-customizations/.claude/agents/orchestrator.md +2 -2
- package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +246 -0
- package/resources/claude-customizations/.claude/settings.json +9 -2
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +7 -0
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +189 -0
- package/resources/claude-customizations/.claude/skills/epic-run/SKILL.md +38 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +21 -2
- package/resources/claude-customizations/pack-manifests/core.json +4 -0
- package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +100 -0
- package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/agents/openai.yaml +4 -0
- package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +170 -0
- package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +230 -0
- package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +39 -0
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +89 -9
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +39 -4
- package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/tonality/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +153 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +153 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +153 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +153 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +153 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +95 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +95 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +95 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +95 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +95 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +99 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +99 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +99 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +99 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +99 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +96 -0
- package/resources/codex-and-agents-customizations/.codex/agents/epic-planner.toml +97 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c1.toml +62 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +62 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +62 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +62 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +62 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +200 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +200 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +200 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +200 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +200 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +53 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +110 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +110 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +110 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +110 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +110 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c1.toml +28 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c2.toml +28 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c3-elevated.toml +28 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c3.toml +28 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c4.toml +28 -0
- package/resources/codex-and-agents-customizations/.codex/agents/pr-author.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c1.toml +55 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c2.toml +55 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c3-elevated.toml +55 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c3.toml +55 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c4.toml +55 -0
- package/resources/codex-and-agents-customizations/.codex/agents/prd-feature.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +102 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +102 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +102 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +102 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +102 -0
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c1.toml +105 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c2.toml +105 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3-elevated.toml +105 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3.toml +105 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c4.toml +105 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c1.toml +50 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c2.toml +50 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c3-elevated.toml +50 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c3.toml +50 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c4.toml +50 -0
- package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +202 -37
- package/resources/codex-and-agents-customizations/.codex/hooks/authorize-root-epic-invocation.ps1 +236 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +178 -69
- package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +92 -19
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +199 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-authority-store.ps1 +182 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +129 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +148 -31
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +198 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +28 -19
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +325 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +140 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +292 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +135 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +295 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +151 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +99 -34
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +143 -27
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +88 -43
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +65 -20
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +88 -42
- package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +414 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +164 -51
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +154 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +58 -23
- package/resources/codex-and-agents-customizations/AGENTS.md +27 -0
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +66 -1
- package/resources/codex-and-agents-customizations/pack-manifests/csharp-legacy.json +6 -1
- package/resources/codex-and-agents-customizations/pack-manifests/csharp-modern.json +6 -1
- package/resources/codex-and-agents-customizations/pack-manifests/powershell.json +6 -1
- package/resources/codex-and-agents-customizations/pack-manifests/python.json +6 -1
- package/resources/codex-and-agents-customizations/pack-manifests/typescript.json +6 -1
- package/resources/config/orchestration-routing.json +167 -0
- package/resources/powershell/PoshQC/PoshQC.Analyzer.psm1 +1 -1
- package/resources/powershell/PoshQC/PoshQC.ScanConfig.psm1 +125 -0
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +14 -2
- package/resources/powershell/PoshQC/PoshQC.psd1 +0 -4
- package/resources/powershell/PoshQC/PoshQC.psm1 +26 -6
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +34 -18
package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
27
|
-
to stdout with
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
function Test-PowerShellTestFilePath {
|
|
54
|
+
[CmdletBinding()]
|
|
55
|
+
[OutputType([bool])]
|
|
56
|
+
param(
|
|
57
|
+
[Parameter(Mandatory)]
|
|
58
|
+
[string] $FilePath
|
|
59
|
+
)
|
|
49
60
|
|
|
50
|
-
$
|
|
51
|
-
|
|
52
|
-
exit 0
|
|
61
|
+
$normalized = $FilePath -replace '\\', '/'
|
|
62
|
+
return (($normalized -match '(^|/)tests/.*\.ps1$') -or ($normalized -match '\.Tests\.ps1$'))
|
|
53
63
|
}
|
|
54
64
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
function Invoke-PowerShellTestPurityDecision {
|
|
66
|
+
[CmdletBinding()]
|
|
67
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
68
|
+
param(
|
|
69
|
+
[string] $ToolInputRaw
|
|
70
|
+
)
|
|
60
71
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
$violations = @()
|
|
94
|
-
foreach ($entry in $forbiddenPatterns) {
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
$
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
} | ConvertTo-Json -Compress
|
|
204
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
205
|
+
return
|
|
206
|
+
}
|
|
111
207
|
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
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
|
|
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
|
|
81
|
+
return $null
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
if (-not (Test-PythonTestFilePath -FilePath $filePath)) {
|
|
84
|
-
return
|
|
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
|
|
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
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
$
|
|
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
|
package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1
ADDED
|
@@ -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
|
+
}
|