@danmoisan/drm-copilot-mcp 1.1.10 → 1.1.12
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 +3517 -1104
- package/package.json +1 -1
- 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-orchestrator.md +15 -2
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +3 -0
- package/resources/claude-customizations/.claude/agents/pr-author.md +15 -0
- 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 +82 -51
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +52 -3
- 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 +163 -15
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -28
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +103 -6
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +124 -8
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +171 -15
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +50 -10
- 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/hooks/enforce-promotion-mcp-only.ps1 +35 -7
- package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +483 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +483 -0
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +218 -6
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +18 -75
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +290 -0
- package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +415 -0
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +1 -0
- package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMerge.psm1 +355 -0
- package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMergeGrammar.psm1 +318 -0
- package/resources/claude-customizations/.claude/lib/project-file-merge/Resolve-MergeableConflict.ps1 +229 -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 +66 -2
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +323 -16
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +14 -1
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +4 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +46 -1
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +75 -8
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +6 -0
- package/resources/claude-customizations/.claude/skills/powershell-orchestration-state-machine/SKILL.md +29 -1
- 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 +7 -0
- package/resources/claude-customizations/pack-manifests/core.json +16 -0
- package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +13 -14
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +41 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -0
- package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +26 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +242 -3
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +67 -12
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +34 -8
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +163 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +34 -7
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +483 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +483 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +130 -2
- 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 +23 -1
- package/resources/config/orchestration-handoff-registry.json +138 -0
- package/resources/config/orchestration-handoff.schema.json +472 -0
- 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 +64 -0
|
@@ -27,15 +27,34 @@ $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
|
+
# issue #663). Interpolation characters are unresolvable in every quote state, because a
|
|
32
|
+
# double-quoted span still expands them. Redirection characters are unresolvable only
|
|
33
|
+
# outside a quoted span: inside single or double quotes the shell treats `<` and `>` as
|
|
34
|
+
# literal text, so a quoted Co-Authored-By trailer is not a redirection.
|
|
35
|
+
$script:InterpolationCommandCharacters = [char[]]@('$', '`')
|
|
36
|
+
$script:RedirectionCommandCharacters = [char[]]@('>', '<')
|
|
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
|
|
@@ -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 issue #663. Quote state is tracked with the same
|
|
116
|
+
state machine as Split-OrchestrationCommandLine. An interpolation character (`$` or
|
|
117
|
+
backtick) answers true in any quote state; a redirection character (`<` or `>`)
|
|
118
|
+
answers true only outside a quoted span. Backslash escapes are not modelled, so a
|
|
119
|
+
backslash before any quote character, or anywhere inside a double-quoted span,
|
|
120
|
+
answers true: the 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 quote moves a span boundary the scan cannot model (issue #663 remediation CR-1).
|
|
131
|
+
if ($CommandText.Contains('\"') -or $CommandText.Contains("\'")) {
|
|
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
|
+
# redirection characters are judged only where the shell would honour them.
|
|
139
|
+
foreach ($character in $CommandText.ToCharArray()) {
|
|
140
|
+
if ($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:RedirectionCommandCharacters -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
|
|
@@ -322,9 +464,9 @@ function Test-ExemptOrchestrationStagingCommand {
|
|
|
322
464
|
return $false
|
|
323
465
|
}
|
|
324
466
|
|
|
325
|
-
# Row 12: interpolation
|
|
326
|
-
#
|
|
327
|
-
if (
|
|
467
|
+
# Row 12: interpolation anywhere, redirection outside quotes, and unmodelled backslash
|
|
468
|
+
# escapes are not statically resolvable, so the operand list cannot be trusted.
|
|
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
|
+
# Shared command-line parser (issue #545): per-segment scan text and structural matching.
|
|
24
|
+
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
25
|
+
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
23
26
|
|
|
24
27
|
# The readiness checkpoint this gate reads and names in its block message.
|
|
25
28
|
$script:CheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
|
|
@@ -151,9 +154,11 @@ function Test-ImplementationCommand {
|
|
|
151
154
|
'(^|\s)npx\s+(prettier|eslint|tsc|jest)\b',
|
|
152
155
|
'(^|\s)pwsh\s+.*(Invoke-Pester|tests/scripts/)'
|
|
153
156
|
)
|
|
157
|
+
$scanText = @(Read-CommandLineSegment -CommandText $normalizedCommand).ScanText
|
|
158
|
+
$isStaging = @('add', 'commit').Where({ Test-CommandLineInvocation -CommandText $normalizedCommand -CommandWord 'git' -SubcommandPath @($_) }).Count -gt 0
|
|
154
159
|
|
|
155
160
|
for ($index = 0; $index -lt $implementationCommandPatterns.Count; $index++) {
|
|
156
|
-
if ($
|
|
161
|
+
if (-not (($scanText | Where-Object { $_ -match $implementationCommandPatterns[$index] }) -or ($index -eq 0 -and $isStaging))) {
|
|
157
162
|
continue
|
|
158
163
|
}
|
|
159
164
|
# Allow-side only (issue #539). Index 0 is the git staging trigger, whose pattern
|
package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1
CHANGED
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
[CmdletBinding()]
|
|
30
30
|
param()
|
|
31
31
|
|
|
32
|
+
# Shared command-line parser (issue #545). Every forbidden-token scan and both `gh`
|
|
33
|
+
# expressions below run against each segment's clause-ordered scan text, and the
|
|
34
|
+
# structural matcher reaches the relocating `gh` spellings adjacency cannot.
|
|
35
|
+
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
36
|
+
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
37
|
+
|
|
32
38
|
$script:PromotionMcpOnlyBlockedReason = 'PROMOTION_MCP_ONLY_BLOCKED: Direct Bash promotion-script execution is not allowed in agent sessions. Use the drm-copilot MCP promotion tools instead.'
|
|
33
39
|
|
|
34
40
|
$script:PromotionMcpOnlyGhIssueBlockedReason = 'PROMOTION_MCP_ONLY_BLOCKED: Direct GitHub issue creation via `gh` bypasses the approved drm-copilot MCP promotion path (`mcp__drm-copilot__new_potential_entry` -> `mcp__drm-copilot__potential_to_issue` -> `mcp__drm-copilot__new_active_feature_folder`). Use those MCP tools instead.'
|
|
@@ -88,15 +94,34 @@ function Get-PromotionBypassReason {
|
|
|
88
94
|
'new_active_feature_folder'
|
|
89
95
|
)
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
# Issue #545: the scan text of a segment is its raw text only when the segment is
|
|
98
|
+
# wrapper-led, carries a live substitution, or could not be balanced; otherwise
|
|
99
|
+
# quoted spans and attached heredoc bodies are masked. A promotion tool name that
|
|
100
|
+
# appears only as a JSON receipt value is therefore a mention, not an invocation.
|
|
101
|
+
$segments = @(Read-CommandLineSegment -CommandText $CommandText)
|
|
102
|
+
|
|
103
|
+
foreach ($segment in $segments) {
|
|
104
|
+
foreach ($token in $forbiddenTokens) {
|
|
105
|
+
if ($segment.ScanText.IndexOf($token, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) {
|
|
106
|
+
return (Get-PromotionMcpOnlyBlockedReason)
|
|
107
|
+
}
|
|
94
108
|
}
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
# `gh issue create` and `gh issue new` are direct bypasses of the MCP
|
|
98
112
|
# promotion path. Tolerate any flags after the subcommand.
|
|
99
|
-
|
|
113
|
+
foreach ($segment in $segments) {
|
|
114
|
+
if ($segment.ScanText -match '(?i)\bgh\s+issue\s+(?:create|new)\b') {
|
|
115
|
+
return (Get-PromotionMcpOnlyGhIssueBlockedReason)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# The adjacency-requiring expression above cannot see a relocating spelling, in
|
|
120
|
+
# which a `gh` global option such as --repo or -R sits between the command word
|
|
121
|
+
# and its subcommand (D10). The structural matcher absorbs those options and
|
|
122
|
+
# reads the subcommand path positionally, so both spellings classify.
|
|
123
|
+
if ((Test-CommandLineInvocation -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('issue', 'create')) -or
|
|
124
|
+
(Test-CommandLineInvocation -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('issue', 'new'))) {
|
|
100
125
|
return (Get-PromotionMcpOnlyGhIssueBlockedReason)
|
|
101
126
|
}
|
|
102
127
|
|
|
@@ -104,10 +129,12 @@ function Get-PromotionBypassReason {
|
|
|
104
129
|
# explicit POST method is supplied. `gh api` defaults to GET, so we only
|
|
105
130
|
# block when -X POST or --method POST is present, to avoid false positives
|
|
106
131
|
# on issue read operations. Use a single regex with lookaheads against the
|
|
107
|
-
#
|
|
132
|
+
# scan text of one segment.
|
|
108
133
|
$ghApiIssuesPostPattern = '(?i)(?=.*\bgh\s+api\b)(?=.*repos/[^/\s]+/[^/\s]+/issues(?:\b|/[^/\s]*$))(?=.*(?:-X\s+POST|--method\s+POST))'
|
|
109
|
-
|
|
110
|
-
|
|
134
|
+
foreach ($segment in $segments) {
|
|
135
|
+
if ($segment.ScanText -match $ghApiIssuesPostPattern) {
|
|
136
|
+
return (Get-PromotionMcpOnlyGhIssueBlockedReason)
|
|
137
|
+
}
|
|
111
138
|
}
|
|
112
139
|
|
|
113
140
|
return $null
|