@danmoisan/drm-copilot-mcp 1.1.11 → 1.1.14
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 +368 -209
- package/package.json +2 -2
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +1 -1
- package/resources/claude-customizations/.claude/agents/epic-planner.md +4 -1
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +18 -1
- package/resources/claude-customizations/.claude/agents/pr-author.md +15 -0
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +16 -8
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-authorization.ps1 +443 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +33 -37
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +120 -5
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +127 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +172 -24
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +14 -27
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +66 -5
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +134 -10
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +37 -8
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +3 -1
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +332 -0
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +211 -182
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +43 -6
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +486 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +5 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +446 -0
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +1 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeReadiness.psm1 +176 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +369 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +392 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeResolution.psm1 +500 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeTargetResolution.psm1 +347 -0
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +27 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +150 -2
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +5 -5
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +12 -0
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +14 -1
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +15 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +4 -1
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +32 -17
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +39 -6
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +39 -10
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +15 -0
- package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +221 -0
- package/resources/claude-customizations/config/blast-radius.json +14 -1
- package/resources/claude-customizations/pack-manifests/core.json +11 -0
- package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +13 -14
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +193 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +15 -5
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-resolution.ps1 +493 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +189 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +172 -24
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +14 -27
- package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexDeployment.ps1 +87 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +96 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/codex-routing-cli-common.ps1 +370 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-contract.ps1 +475 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-runtime.ps1 +445 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-persistence-runtime.ps1 +118 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-sandbox-preflight.ps1 +69 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/launch-epic-child-wave.ps1 +471 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +280 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/resume-epic-child.ps1 +263 -0
- package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +2 -1
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +20 -1
- package/resources/lib/codex-routing/CodexDeployment.psm1 +315 -0
- package/resources/lib/codex-routing/CodexTopology.psm1 +394 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +37 -0
|
@@ -27,24 +27,42 @@ $script:OrchestrationBookkeepingTrees = @(
|
|
|
27
27
|
'artifacts/orchestration/'
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
# Characters that make a command line statically unresolvable
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
|
|
30
|
+
# Characters that make a command line statically unresolvable (D4 row 12, as narrowed by
|
|
31
|
+
# issues #663 and #713): interpolation characters outside single quotes; outside-quote
|
|
32
|
+
# characters (`<`, `>`, and the `#` comment introducer) outside any quote; and typographic
|
|
33
|
+
# quotes (U+2018 to U+201E) anywhere, because a PowerShell host reads them as quotes.
|
|
34
|
+
$script:InterpolationCommandCharacters = [char[]]@('$', '`')
|
|
35
|
+
$script:OutsideQuoteCommandCharacters = [char[]]@('>', '<', '#')
|
|
36
|
+
$script:TypographicQuoteCharacters = [char[]]@(0x2018, 0x2019, 0x201A, 0x201B, 0x201C, 0x201D, 0x201E)
|
|
34
37
|
|
|
35
38
|
# Wildcards that make an operand a glob (D4 row 15). Only the literal prefix before the
|
|
36
39
|
# first of these is prefix-tested.
|
|
37
40
|
$script:PathspecWildcardCharacters = [char[]]@('*', '?', '[')
|
|
38
41
|
|
|
42
|
+
# The single repository selector modelled between the command name and the subcommand: D4
|
|
43
|
+
# row 14 as narrowed by issue #671, the Lexical Absolute-Canonical Selector (LACS) rule in
|
|
44
|
+
# docs/features/active/2026-09-13-preimplementation-gate-worktree-selector-671/spec.md.
|
|
45
|
+
# Compared case-sensitively; no other relocating spelling is modelled.
|
|
46
|
+
$script:OrchestrationSelectorOptionName = '-C'
|
|
47
|
+
|
|
48
|
+
# Accepted widening (issue #671): a selector naming a nested subdirectory of a worktree
|
|
49
|
+
# passes L1 through L8 lexically yet relocates what a relative operand denotes, so
|
|
50
|
+
# `docs/features/active/X` under `-C <root>/tests/fixtures/resolve_execute_plan_prompt`
|
|
51
|
+
# stages `tests/fixtures/resolve_execute_plan_prompt/docs/features/active/X`. Measured
|
|
52
|
+
# exposure in this repository is seven Markdown test fixtures under the
|
|
53
|
+
# resolve_execute_plan_prompt fixture tree, all of which the gate's file_path leg already
|
|
54
|
+
# classifies as non-implementation. This module stays pure string logic; the epic's F1
|
|
55
|
+
# resolution module composes upstream to close the escape later without a schema change,
|
|
56
|
+
# the same posture D4 row 16 takes toward issue #516.
|
|
57
|
+
|
|
39
58
|
function Split-OrchestrationCommandLine {
|
|
40
59
|
<#
|
|
41
60
|
.SYNOPSIS
|
|
42
61
|
Splits a command line into segments on chain operators outside quotes.
|
|
43
62
|
.DESCRIPTION
|
|
44
|
-
Realizes D4 row 13. Quote state
|
|
45
|
-
|
|
46
|
-
the
|
|
47
|
-
caller denies (D4 rows 11 and 13). Empty and whitespace-only segments are dropped.
|
|
63
|
+
Realizes D4 row 13. Quote state and POSIX backslash escapes (issue #710) are tracked, so a quoted or
|
|
64
|
+
escaped chain operator does not split. `Balanced` reports whether the scan ended outside every quote;
|
|
65
|
+
the caller denies unbalanced text (D4 rows 11 and 13). Empty and whitespace-only segments are dropped.
|
|
48
66
|
.OUTPUTS
|
|
49
67
|
System.Collections.Hashtable with keys `Balanced` (bool) and `Segments` (string[]).
|
|
50
68
|
#>
|
|
@@ -55,8 +73,9 @@ function Split-OrchestrationCommandLine {
|
|
|
55
73
|
$segments = [System.Collections.Generic.List[string]]::new()
|
|
56
74
|
$current = [System.Text.StringBuilder]::new()
|
|
57
75
|
$openQuote = [char]0
|
|
58
|
-
|
|
76
|
+
$escaped = $false
|
|
59
77
|
foreach ($character in $CommandText.ToCharArray()) {
|
|
78
|
+
if ($escaped -or ($character -eq '\' -and $openQuote -ne "'")) { $escaped = -not $escaped; [void]$current.Append($character); continue }
|
|
60
79
|
if ($openQuote -ne [char]0) {
|
|
61
80
|
if ($character -eq $openQuote) {
|
|
62
81
|
$openQuote = [char]0
|
|
@@ -88,6 +107,59 @@ function Split-OrchestrationCommandLine {
|
|
|
88
107
|
}
|
|
89
108
|
}
|
|
90
109
|
|
|
110
|
+
function Test-OrchestrationCommandTextUnresolvable {
|
|
111
|
+
<#
|
|
112
|
+
.SYNOPSIS
|
|
113
|
+
Reports whether a command line carries a statically unresolvable character.
|
|
114
|
+
.DESCRIPTION
|
|
115
|
+
Realizes D4 row 12 as narrowed by issues #663 and #713, with quote state tracked as in
|
|
116
|
+
Split-OrchestrationCommandLine. `$` or backtick answers true outside quotes or inside
|
|
117
|
+
double quotes (single quotes keep it literal); `<`, `>`, or a `#` comment answers true
|
|
118
|
+
only outside a quoted span. A typographic quote (U+2018 to U+201E), a backslash before
|
|
119
|
+
any quote character, or a backslash inside a double-quoted span answers true anywhere:
|
|
120
|
+
a shell may end the span where this scan does not (fail closed).
|
|
121
|
+
.PARAMETER CommandText
|
|
122
|
+
The full command line as the shell would receive it.
|
|
123
|
+
.OUTPUTS
|
|
124
|
+
System.Boolean
|
|
125
|
+
#>
|
|
126
|
+
[CmdletBinding()]
|
|
127
|
+
[OutputType([bool])]
|
|
128
|
+
param([Parameter(Mandatory)][AllowEmptyString()][string] $CommandText)
|
|
129
|
+
|
|
130
|
+
# An escaped or typographic quote moves a span boundary the scan cannot model (#663, #713).
|
|
131
|
+
if ($CommandText.Contains('\"') -or $CommandText.Contains("\'") -or $CommandText.IndexOfAny($script:TypographicQuoteCharacters) -ge 0) {
|
|
132
|
+
return $true
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
$openQuote = [char]0
|
|
136
|
+
|
|
137
|
+
# Scan every character once, tracking whether it sits inside a quoted span so that
|
|
138
|
+
# outside-quote characters are judged only where the shell would honour them.
|
|
139
|
+
foreach ($character in $CommandText.ToCharArray()) {
|
|
140
|
+
if ($openQuote -ne "'" -and $script:InterpolationCommandCharacters -contains $character) {
|
|
141
|
+
return $true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# Decide by quote state: inside a span only the closing quote matters, and any
|
|
145
|
+
# backslash inside a double-quoted span is an unmodelled escape; outside a span a
|
|
146
|
+
# quote opens one and a redirection character is unresolvable.
|
|
147
|
+
if ($openQuote -ne [char]0) {
|
|
148
|
+
if ($openQuote -eq '"' -and $character -eq '\') {
|
|
149
|
+
return $true
|
|
150
|
+
}
|
|
151
|
+
if ($character -eq $openQuote) {
|
|
152
|
+
$openQuote = [char]0
|
|
153
|
+
}
|
|
154
|
+
} elseif ($character -eq '"' -or $character -eq "'") {
|
|
155
|
+
$openQuote = $character
|
|
156
|
+
} elseif ($script:OutsideQuoteCommandCharacters -contains $character) {
|
|
157
|
+
return $true
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return $false
|
|
161
|
+
}
|
|
162
|
+
|
|
91
163
|
function ConvertTo-OrchestrationCommandToken {
|
|
92
164
|
<#
|
|
93
165
|
.SYNOPSIS
|
|
@@ -203,6 +275,67 @@ function Test-ExemptOrchestrationOperand {
|
|
|
203
275
|
return $false
|
|
204
276
|
}
|
|
205
277
|
|
|
278
|
+
function Test-ExemptOrchestrationSelector {
|
|
279
|
+
<#
|
|
280
|
+
.SYNOPSIS
|
|
281
|
+
Tests the repository selector of one tokenized segment against LACS L1 through L8.
|
|
282
|
+
.DESCRIPTION
|
|
283
|
+
Realizes D4 row 14 as narrowed by issue #671 (the Lexical Absolute-Canonical Selector
|
|
284
|
+
rule in docs/features/active/2026-09-13-preimplementation-gate-worktree-selector-671/spec.md).
|
|
285
|
+
Returns true only when the segment reads `git -C <value> <subcommand> ...` and the
|
|
286
|
+
value is decidable from the command text alone: exactly one case-sensitive `-C`, at
|
|
287
|
+
index 1 (L1, L2); a value at index 2 and the subcommand at index 3 (L3); a
|
|
288
|
+
drive-lettered or rooted, non-UNC value after separator normalization (L4); no `.` or
|
|
289
|
+
`..` segment (L5); no wildcard (L6); no colon other than the drive colon (L7); and a
|
|
290
|
+
non-empty value (L8). Each rejection writes a diagnostic token through Write-Debug.
|
|
291
|
+
The tokens are not contractual and change no decision.
|
|
292
|
+
L3 checks only that a non-option token follows the value; the caller
|
|
293
|
+
Test-ExemptOrchestrationSegmentToken rejects any subcommand other than add or commit.
|
|
294
|
+
.OUTPUTS
|
|
295
|
+
System.Boolean
|
|
296
|
+
#>
|
|
297
|
+
[CmdletBinding()]
|
|
298
|
+
[OutputType([bool])]
|
|
299
|
+
param([Parameter(Mandatory)][AllowEmptyCollection()][AllowEmptyString()][string[]] $Token)
|
|
300
|
+
|
|
301
|
+
$selector = $script:OrchestrationSelectorOptionName
|
|
302
|
+
if ($Token.Count -lt 2 -or $Token[1] -cne $selector) {
|
|
303
|
+
Write-Debug 'PREIMPL_SELECTOR_UNMODELLED_OPTION: the token after the command name is not the -C selector.'
|
|
304
|
+
return $false
|
|
305
|
+
}
|
|
306
|
+
if (@($Token | Where-Object { $_ -ceq $selector }).Count -gt 1) {
|
|
307
|
+
Write-Debug 'PREIMPL_SELECTOR_REPEATED: the segment carries more than one -C selector.'
|
|
308
|
+
return $false
|
|
309
|
+
}
|
|
310
|
+
if ($Token.Count -lt 4 -or $Token[3].StartsWith('-')) {
|
|
311
|
+
Write-Debug 'PREIMPL_SELECTOR_MALFORMED: no subcommand immediately follows the selector value.'
|
|
312
|
+
return $false
|
|
313
|
+
}
|
|
314
|
+
$value = $Token[2]
|
|
315
|
+
if (-not $value) {
|
|
316
|
+
Write-Debug 'PREIMPL_SELECTOR_MALFORMED: the selector value is empty.'
|
|
317
|
+
return $false
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
$normalized = $value -replace '\\', '/'
|
|
321
|
+
$isDriveRooted = $normalized -match '^[A-Za-z]:/'
|
|
322
|
+
if ($normalized.StartsWith('//') -or -not ($isDriveRooted -or $normalized.StartsWith('/'))) {
|
|
323
|
+
Write-Debug 'PREIMPL_SELECTOR_NOT_ROOTED: the selector value is relative or UNC.'
|
|
324
|
+
return $false
|
|
325
|
+
}
|
|
326
|
+
$segments = $normalized -split '/'
|
|
327
|
+
if ($segments -contains '..' -or $segments -contains '.') {
|
|
328
|
+
Write-Debug 'PREIMPL_SELECTOR_TRAVERSAL: the selector value carries a dot segment.'
|
|
329
|
+
return $false
|
|
330
|
+
}
|
|
331
|
+
$afterDrive = if ($isDriveRooted) { $normalized.Substring(2) } else { $normalized }
|
|
332
|
+
if ($normalized.IndexOfAny($script:PathspecWildcardCharacters) -ge 0 -or $afterDrive.Contains(':')) {
|
|
333
|
+
Write-Debug 'PREIMPL_SELECTOR_NOT_LITERAL: the selector value carries a wildcard or a stray colon.'
|
|
334
|
+
return $false
|
|
335
|
+
}
|
|
336
|
+
return $true
|
|
337
|
+
}
|
|
338
|
+
|
|
206
339
|
function Test-ExemptOrchestrationSegmentToken {
|
|
207
340
|
<#
|
|
208
341
|
.SYNOPSIS
|
|
@@ -218,18 +351,27 @@ function Test-ExemptOrchestrationSegmentToken {
|
|
|
218
351
|
#>
|
|
219
352
|
[CmdletBinding()]
|
|
220
353
|
[OutputType([bool])]
|
|
221
|
-
param([Parameter(Mandatory)][AllowEmptyCollection()][string[]] $Token)
|
|
354
|
+
param([Parameter(Mandatory)][AllowEmptyCollection()][AllowEmptyString()][string[]] $Token)
|
|
222
355
|
|
|
223
356
|
if ($Token.Count -lt 2) {
|
|
224
357
|
return $false
|
|
225
358
|
}
|
|
226
359
|
|
|
227
|
-
# Row 14: the command name leads and the subcommand follows
|
|
228
|
-
#
|
|
229
|
-
#
|
|
360
|
+
# Row 14, narrowed by issue #671: the command name leads and the subcommand follows it,
|
|
361
|
+
# either immediately or after one lexically absolute `-C <value>` selector (LACS L1-L8).
|
|
362
|
+
# Anything else in between - an unmodelled or unresolvable relocating option, or an
|
|
363
|
+
# env-style prefix - moves the pathspec base undecidably and is rejected here.
|
|
230
364
|
if ($Token[0] -cne 'git') {
|
|
231
365
|
return $false
|
|
232
366
|
}
|
|
367
|
+
if ($Token[1] -cne 'add' -and $Token[1] -cne 'commit') {
|
|
368
|
+
if (-not (Test-ExemptOrchestrationSelector -Token $Token)) {
|
|
369
|
+
return $false
|
|
370
|
+
}
|
|
371
|
+
# Drop the accepted selector and its value so the subcommand is again at index 1
|
|
372
|
+
# and operand collection below starts at index 2 exactly as before.
|
|
373
|
+
$Token = @($Token[0]) + @($Token[3..($Token.Count - 1)])
|
|
374
|
+
}
|
|
233
375
|
$subcommand = $Token[1]
|
|
234
376
|
if ($subcommand -cne 'add' -and $subcommand -cne 'commit') {
|
|
235
377
|
return $false
|
|
@@ -251,21 +393,21 @@ function Test-ExemptOrchestrationSegmentToken {
|
|
|
251
393
|
}
|
|
252
394
|
|
|
253
395
|
if ($candidate.StartsWith('-')) {
|
|
254
|
-
#
|
|
396
|
+
# Only the message and trailer options of commit are modelled (issue #713). Rows
|
|
255
397
|
# 2, 5, 6, 8, and 10 all land here and deny, including a dash-leading
|
|
256
398
|
# operand supplied without a preceding separator.
|
|
257
399
|
if ($subcommand -cne 'commit') {
|
|
258
400
|
return $false
|
|
259
401
|
}
|
|
260
|
-
if ($candidate -ceq '-m' -or $candidate -ceq '--message') {
|
|
261
|
-
# The message value is the following token and is not a pathspec.
|
|
402
|
+
if ($candidate -ceq '-m' -or $candidate -ceq '--message' -or $candidate -ceq '--trailer') {
|
|
403
|
+
# The message or trailer value is the following token and is not a pathspec.
|
|
262
404
|
$index += 2
|
|
263
405
|
if ($index -gt $Token.Count) {
|
|
264
406
|
return $false
|
|
265
407
|
}
|
|
266
408
|
continue
|
|
267
409
|
}
|
|
268
|
-
if ($candidate.StartsWith('--message=') -or
|
|
410
|
+
if ($candidate.StartsWith('--message=') -or $candidate.StartsWith('--trailer=') -or
|
|
269
411
|
($candidate.Length -gt 2 -and $candidate.StartsWith('-m'))) {
|
|
270
412
|
$index++
|
|
271
413
|
continue
|
|
@@ -322,9 +464,9 @@ function Test-ExemptOrchestrationStagingCommand {
|
|
|
322
464
|
return $false
|
|
323
465
|
}
|
|
324
466
|
|
|
325
|
-
# Row 12:
|
|
326
|
-
#
|
|
327
|
-
if (
|
|
467
|
+
# Row 12: `$` or backtick outside single quotes, `<`, `>`, or `#` outside quotes, any
|
|
468
|
+
# typographic quote, and unmodelled backslash escapes make the operand list untrustworthy.
|
|
469
|
+
if (Test-OrchestrationCommandTextUnresolvable -CommandText $CommandText) {
|
|
328
470
|
return $false
|
|
329
471
|
}
|
|
330
472
|
|
|
@@ -339,11 +481,17 @@ function Test-ExemptOrchestrationStagingCommand {
|
|
|
339
481
|
return $false
|
|
340
482
|
}
|
|
341
483
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
484
|
+
# Fail closed (issue #671): an error raised while classifying any segment is a parse
|
|
485
|
+
# ambiguity, so it answers false instead of letting the caller continue past it.
|
|
486
|
+
try {
|
|
487
|
+
foreach ($segment in $segments) {
|
|
488
|
+
$tokens = @(ConvertTo-OrchestrationCommandToken -Segment $segment)
|
|
489
|
+
if (-not (Test-ExemptOrchestrationSegmentToken -Token $tokens)) {
|
|
490
|
+
return $false
|
|
491
|
+
}
|
|
346
492
|
}
|
|
493
|
+
} catch {
|
|
494
|
+
return $false
|
|
347
495
|
}
|
|
348
496
|
return $true
|
|
349
497
|
}
|
|
@@ -20,6 +20,9 @@ param()
|
|
|
20
20
|
# normative contract and which lacks headroom; leaving that file byte-untouched is the
|
|
21
21
|
# proof the issue #539 exemption is behaviourally unchanged.
|
|
22
22
|
. (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-modes.ps1')
|
|
23
|
+
# Epic-scope command and path legs (issue #707, porting issue #663 decision D2), plus the
|
|
24
|
+
# two per-mode read seams relocated from this file to keep it inside the 500-line cap.
|
|
25
|
+
. (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-epic-scope.ps1')
|
|
23
26
|
# Shared command-line parser (issue #545): per-segment scan text and structural matching.
|
|
24
27
|
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
25
28
|
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
@@ -286,33 +289,6 @@ function Get-CheckpointContent {
|
|
|
286
289
|
return Get-Content -Raw -LiteralPath $script:CheckpointPath
|
|
287
290
|
}
|
|
288
291
|
|
|
289
|
-
# The two per-mode read seams (issue #554). Each takes its path from the fixed mode
|
|
290
|
-
# table and never from a delegation's own text; an absent file returns an empty
|
|
291
|
-
# string, which the readiness predicate then treats as a deny.
|
|
292
|
-
function Get-EpicCheckpointContent {
|
|
293
|
-
[CmdletBinding()]
|
|
294
|
-
[OutputType([string])]
|
|
295
|
-
param()
|
|
296
|
-
|
|
297
|
-
$path = Get-OrchestrationDelegationCheckpointPath -Mode 'epic'
|
|
298
|
-
if (-not (Test-Path -LiteralPath $path)) {
|
|
299
|
-
return ''
|
|
300
|
-
}
|
|
301
|
-
return Get-Content -Raw -LiteralPath $path
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function Get-ParallelCheckpointContent {
|
|
305
|
-
[CmdletBinding()]
|
|
306
|
-
[OutputType([string])]
|
|
307
|
-
param()
|
|
308
|
-
|
|
309
|
-
$path = Get-OrchestrationDelegationCheckpointPath -Mode 'parallel'
|
|
310
|
-
if (-not (Test-Path -LiteralPath $path)) {
|
|
311
|
-
return ''
|
|
312
|
-
}
|
|
313
|
-
return Get-Content -Raw -LiteralPath $path
|
|
314
|
-
}
|
|
315
|
-
|
|
316
292
|
function Get-OrchestrationPreimplementationGateAllowDecision {
|
|
317
293
|
[CmdletBinding()]
|
|
318
294
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -398,6 +374,7 @@ function Invoke-OrchestrationPreimplementationGateDecision {
|
|
|
398
374
|
# default wording they have today.
|
|
399
375
|
$mode = $script:OrchestrationDelegationDefaultMode
|
|
400
376
|
$prompt = ''
|
|
377
|
+
$command = ''
|
|
401
378
|
$filePath = Get-StringProperty -Value $toolInput -Name 'file_path'
|
|
402
379
|
if ($filePath) {
|
|
403
380
|
$normalized = ([string]$filePath) -replace '\\', '/'
|
|
@@ -427,6 +404,16 @@ function Invoke-OrchestrationPreimplementationGateDecision {
|
|
|
427
404
|
Get-OrchestrationModeDenyReason -Mode $mode -Failure 'declared-checkpoint-path')
|
|
428
405
|
}
|
|
429
406
|
|
|
407
|
+
# The command, apply_patch, and path legs consult the epic checkpoint first (issue #707,
|
|
408
|
+
# porting issue #663). A call that is not epic scope yields $null and continues to the
|
|
409
|
+
# unchanged single-feature path.
|
|
410
|
+
if ($filePath -or $command) {
|
|
411
|
+
$epicDecision = Get-OrchestrationEpicScopeDecision -Command $command -FilePath $filePath
|
|
412
|
+
if ($null -ne $epicDecision) {
|
|
413
|
+
return $epicDecision
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
430
417
|
if ($mode -eq 'epic' -or $mode -eq 'parallel') {
|
|
431
418
|
$isEpic = ($mode -eq 'epic')
|
|
432
419
|
$injected = if ($isEpic) { 'EpicCheckpointRaw' } else { 'ParallelCheckpointRaw' }
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#Requires -Version 7.0
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Resolve the Codex deployment agent, model, and reasoning effort (issue #697).
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
PowerShell CLI equivalent of `python -m scripts.dev_tools.resolve_codex_deployment`
|
|
8
|
+
for destinations without a Python toolchain. Accepts the same GNU-style flags,
|
|
9
|
+
imports CodexDeployment.psm1 from .codex/lib/codex-routing (published) or
|
|
10
|
+
.claude/lib/codex-routing (self-hosting), and prints the same JSON receipt.
|
|
11
|
+
Exit codes: 0 success, 1 resolver error or missing module, 2 usage error.
|
|
12
|
+
|
|
13
|
+
.EXAMPLE
|
|
14
|
+
pwsh -NoProfile -File .codex/scripts/Resolve-CodexDeployment.ps1 --logical-agent orchestrator --complexity-band C2 --execution-context standalone --orchestration-complexity-ceiling C3
|
|
15
|
+
#>
|
|
16
|
+
|
|
17
|
+
. (Join-Path $PSScriptRoot 'codex-routing-cli-common.ps1')
|
|
18
|
+
|
|
19
|
+
function Invoke-CodexDeploymentCli {
|
|
20
|
+
<#
|
|
21
|
+
.SYNOPSIS
|
|
22
|
+
Run the deployment CLI over a token list and return its streams and exit code.
|
|
23
|
+
.PARAMETER Arguments
|
|
24
|
+
CLI tokens, for example @('--logical-agent', 'orchestrator', '--complexity-band', 'C2', ...).
|
|
25
|
+
.PARAMETER ModuleCandidates
|
|
26
|
+
Ordered CodexDeployment.psm1 candidate paths; the first existing one is used.
|
|
27
|
+
.OUTPUTS
|
|
28
|
+
PSCustomObject with ExitCode, Stdout, and Stderr.
|
|
29
|
+
#>
|
|
30
|
+
[CmdletBinding()]
|
|
31
|
+
[OutputType([pscustomobject])]
|
|
32
|
+
param(
|
|
33
|
+
[Parameter(Mandatory = $true)][AllowEmptyCollection()][AllowEmptyString()][string[]] $Arguments,
|
|
34
|
+
[string[]] $ModuleCandidates = (Get-CodexRoutingModuleCandidate -ScriptRoot $PSScriptRoot -ModuleName 'CodexDeployment.psm1')
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
$program = 'Resolve-CodexDeployment.ps1'
|
|
38
|
+
$bands = @('C1', 'C2', 'C3', 'C4')
|
|
39
|
+
# Flags, requirement, and choices match build_parser() in
|
|
40
|
+
# scripts/dev_tools/resolve_codex_deployment.py.
|
|
41
|
+
$specification = [ordered]@{
|
|
42
|
+
'--logical-agent' = @{ Key = 'logical_agent'; Kind = 'value'; Required = $true; Default = $null }
|
|
43
|
+
'--complexity-band' = @{ Key = 'complexity_band'; Kind = 'value'; Required = $true; Choices = $bands; Default = $null }
|
|
44
|
+
'--execution-context' = @{
|
|
45
|
+
Key = 'execution_context'
|
|
46
|
+
Kind = 'value'
|
|
47
|
+
Required = $true
|
|
48
|
+
Choices = @('epic_execution_child', 'epic_preparation_child', 'standalone')
|
|
49
|
+
Default = $null
|
|
50
|
+
}
|
|
51
|
+
'--orchestration-complexity-ceiling' = @{ Key = 'orchestration_complexity_ceiling'; Kind = 'value'; Required = $true; Choices = $bands; Default = $null }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
$parsed = ConvertFrom-CodexRoutingArgument -Arguments $Arguments -Specification $specification
|
|
55
|
+
if ($null -ne $parsed.Error) {
|
|
56
|
+
return New-CodexRoutingCliResult -ExitCode 2 -Stderr "${program}: $($parsed.Error)`n"
|
|
57
|
+
}
|
|
58
|
+
$modulePath = Resolve-CodexRoutingModulePath -Candidate $ModuleCandidates
|
|
59
|
+
if ($null -eq $modulePath) {
|
|
60
|
+
return New-CodexRoutingCliResult -ExitCode 1 -Stderr "CODEX_ROUTING_MODULE_NOT_FOUND: none of the candidate module paths exist: $($ModuleCandidates -join '; ')`n"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
$module = Import-Module -Name $modulePath -Force -PassThru -ErrorAction Stop
|
|
64
|
+
$resolver = $module.ExportedCommands['Resolve-CodexDeployment']
|
|
65
|
+
$values = $parsed.Values
|
|
66
|
+
# The module throws ArgumentException for every invalid combination; that is
|
|
67
|
+
# the equivalent of the Python ValueError and maps to exit 1.
|
|
68
|
+
try {
|
|
69
|
+
$receipt = & $resolver `
|
|
70
|
+
-LogicalAgent $values['logical_agent'] `
|
|
71
|
+
-ComplexityBand $values['complexity_band'] `
|
|
72
|
+
-ExecutionContext $values['execution_context'] `
|
|
73
|
+
-OrchestrationComplexityCeiling $values['orchestration_complexity_ceiling']
|
|
74
|
+
} catch [System.ArgumentException] {
|
|
75
|
+
return New-CodexRoutingCliResult -ExitCode 1 -Stderr "$($_.Exception.Message)`n"
|
|
76
|
+
}
|
|
77
|
+
return New-CodexRoutingCliResult -ExitCode 0 -Stdout ((ConvertTo-CodexRoutingJson -InputObject $receipt) + "`n")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
$cliResult = Invoke-CodexDeploymentCli -Arguments ([string[]]$args)
|
|
85
|
+
[Console]::Out.Write($cliResult.Stdout)
|
|
86
|
+
[Console]::Error.Write($cliResult.Stderr)
|
|
87
|
+
exit $cliResult.ExitCode
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#Requires -Version 7.0
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Resolve the initial Codex implementation agent from scope data (issue #697).
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
PowerShell CLI equivalent of `python -m scripts.dev_tools.resolve_codex_topology`
|
|
8
|
+
for destinations without a Python toolchain. Accepts the same GNU-style flags,
|
|
9
|
+
imports CodexTopology.psm1 from .codex/lib/codex-routing (published) or
|
|
10
|
+
.claude/lib/codex-routing (self-hosting), and prints the same JSON receipt.
|
|
11
|
+
Exit codes: 0 success, 1 resolver error or missing module, 2 usage error.
|
|
12
|
+
|
|
13
|
+
.EXAMPLE
|
|
14
|
+
pwsh -NoProfile -File .codex/scripts/Resolve-CodexTopology.ps1 --language python --production-file-count 2 --test-file-count 3 --execution-context standalone
|
|
15
|
+
#>
|
|
16
|
+
|
|
17
|
+
. (Join-Path $PSScriptRoot 'codex-routing-cli-common.ps1')
|
|
18
|
+
|
|
19
|
+
function Invoke-CodexTopologyCli {
|
|
20
|
+
<#
|
|
21
|
+
.SYNOPSIS
|
|
22
|
+
Run the topology CLI over a token list and return its streams and exit code.
|
|
23
|
+
.PARAMETER Arguments
|
|
24
|
+
CLI tokens, for example @('--language', 'python', '--execution-context', 'standalone').
|
|
25
|
+
.PARAMETER ModuleCandidates
|
|
26
|
+
Ordered CodexTopology.psm1 candidate paths; the first existing one is used.
|
|
27
|
+
.OUTPUTS
|
|
28
|
+
PSCustomObject with ExitCode, Stdout, and Stderr.
|
|
29
|
+
#>
|
|
30
|
+
[CmdletBinding()]
|
|
31
|
+
[OutputType([pscustomobject])]
|
|
32
|
+
param(
|
|
33
|
+
[Parameter(Mandatory = $true)][AllowEmptyCollection()][AllowEmptyString()][string[]] $Arguments,
|
|
34
|
+
[string[]] $ModuleCandidates = (Get-CodexRoutingModuleCandidate -ScriptRoot $PSScriptRoot -ModuleName 'CodexTopology.psm1')
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
$program = 'Resolve-CodexTopology.ps1'
|
|
38
|
+
# Flags, kinds, defaults, and choices match build_parser() in
|
|
39
|
+
# scripts/dev_tools/resolve_codex_topology.py.
|
|
40
|
+
$specification = [ordered]@{
|
|
41
|
+
'--language' = @{ Key = 'language'; Kind = 'append'; Required = $false; Default = $null }
|
|
42
|
+
'--production-file-count' = @{ Key = 'production_file_count'; Kind = 'int'; Required = $false; Default = 0 }
|
|
43
|
+
'--test-file-count' = @{ Key = 'test_file_count'; Kind = 'int'; Required = $false; Default = 0 }
|
|
44
|
+
'--execution-context' = @{
|
|
45
|
+
Key = 'execution_context'
|
|
46
|
+
Kind = 'value'
|
|
47
|
+
Required = $true
|
|
48
|
+
Choices = @('epic_execution_child', 'epic_preparation_child', 'standalone')
|
|
49
|
+
Default = $null
|
|
50
|
+
}
|
|
51
|
+
'--cross-cutting' = @{ Key = 'cross_cutting'; Kind = 'switch'; Required = $false; Default = $false }
|
|
52
|
+
'--root-persona' = @{
|
|
53
|
+
Key = 'root_persona'
|
|
54
|
+
Kind = 'value'
|
|
55
|
+
Required = $false
|
|
56
|
+
Choices = @('epic-orchestrator', 'epic-planner')
|
|
57
|
+
Default = $null
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
$parsed = ConvertFrom-CodexRoutingArgument -Arguments $Arguments -Specification $specification
|
|
62
|
+
if ($null -ne $parsed.Error) {
|
|
63
|
+
return New-CodexRoutingCliResult -ExitCode 2 -Stderr "${program}: $($parsed.Error)`n"
|
|
64
|
+
}
|
|
65
|
+
$modulePath = Resolve-CodexRoutingModulePath -Candidate $ModuleCandidates
|
|
66
|
+
if ($null -eq $modulePath) {
|
|
67
|
+
return New-CodexRoutingCliResult -ExitCode 1 -Stderr "CODEX_ROUTING_MODULE_NOT_FOUND: none of the candidate module paths exist: $($ModuleCandidates -join '; ')`n"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
$module = Import-Module -Name $modulePath -Force -PassThru -ErrorAction Stop
|
|
71
|
+
$resolver = $module.ExportedCommands['Resolve-CodexTopology']
|
|
72
|
+
$values = $parsed.Values
|
|
73
|
+
# The module throws ArgumentException for every invalid combination; that is
|
|
74
|
+
# the equivalent of the Python ValueError and maps to exit 1.
|
|
75
|
+
try {
|
|
76
|
+
$receipt = & $resolver `
|
|
77
|
+
-Language ([string[]]$values['language']) `
|
|
78
|
+
-ProductionFileCount $values['production_file_count'] `
|
|
79
|
+
-TestFileCount $values['test_file_count'] `
|
|
80
|
+
-ExecutionContext $values['execution_context'] `
|
|
81
|
+
-CrossCutting ([bool]$values['cross_cutting']) `
|
|
82
|
+
-RootPersona $values['root_persona']
|
|
83
|
+
} catch [System.ArgumentException] {
|
|
84
|
+
return New-CodexRoutingCliResult -ExitCode 1 -Stderr "$($_.Exception.Message)`n"
|
|
85
|
+
}
|
|
86
|
+
return New-CodexRoutingCliResult -ExitCode 0 -Stdout ((ConvertTo-CodexRoutingJson -InputObject $receipt) + "`n")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
$cliResult = Invoke-CodexTopologyCli -Arguments ([string[]]$args)
|
|
94
|
+
[Console]::Out.Write($cliResult.Stdout)
|
|
95
|
+
[Console]::Error.Write($cliResult.Stderr)
|
|
96
|
+
exit $cliResult.ExitCode
|