@danmoisan/drm-copilot-mcp 1.1.25 → 1.1.26
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/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +7 -3
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +6 -2
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +41 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-cleanup.ps1 +481 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +3 -1
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1 +69 -3
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +2 -2
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.cleanup.ps1 +246 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.docs-only.ps1 +1 -0
- package/resources/claude-customizations/.claude/hooks/hook-cleanup-context.ps1 +343 -0
- package/resources/claude-customizations/.claude/lib/parallel-admission/Invoke-ParallelAdmission.ps1 +626 -0
- package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmission.psm1 +508 -0
- package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmissionJson.psm1 +141 -0
- package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmissionPrecedence.psm1 +508 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +17 -1
- package/resources/claude-customizations/.claude/settings.json +1 -0
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +93 -14
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +23 -10
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +54 -46
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_merge_lib.sh +234 -0
- package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +15 -5
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +71 -7
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +26 -2
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +82 -12
- package/resources/claude-customizations/pack-manifests/core.json +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +15 -5
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-cleanup.ps1 +481 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +4 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-cleanup-context.ps1 +343 -0
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +2 -0
- package/resources/customizations/.github/skills/execution-environment/SKILL.md +15 -5
- package/resources/powershell/PoshQC/PoshQC.ScanConfig.psm1 +3 -1
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +12 -10
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Pure cleanup-context predicates shared by the cleanup legs of the enforcement gates (issue #1026).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
The cleanup-merged-worktrees skill writes no per-feature, epic, or parallel checkpoint, so
|
|
7
|
+
the checkpoint-based gates cannot recognize its writes, commits, delegation, and pull
|
|
8
|
+
request. Its integrity record is the sanctioned-removal manifest. This file holds the pure
|
|
9
|
+
decisions the gates' cleanup legs share:
|
|
10
|
+
|
|
11
|
+
Test-CleanupManifestText - the manifest header (tool, schema_version) and,
|
|
12
|
+
with -UtcNow, its freshness and run_id.
|
|
13
|
+
Test-CleanupConsolidationRelativePath - the allowlist: the repository-relative prefixes
|
|
14
|
+
docs/ and .claude/agent-memory/ (ordinal,
|
|
15
|
+
case-sensitive), plus the pr-author receipt
|
|
16
|
+
artifacts/pr_body_<digits>.receipt.json with
|
|
17
|
+
-AllowReceipt.
|
|
18
|
+
Find-CleanupConsolidationRecord - the consolidation worktree, identified by its
|
|
19
|
+
porcelain branch, never by its path.
|
|
20
|
+
Test-CleanupScratchPath - the scratch rule: an absolute path outside every
|
|
21
|
+
registered worktree and the session root, under a
|
|
22
|
+
temporary root.
|
|
23
|
+
Test-CleanupConsolidationBranchLabel - the delegation label rule of the model-routing leg.
|
|
24
|
+
|
|
25
|
+
Every function fails closed: a malformed or ambiguous input answers $false or $null.
|
|
26
|
+
|
|
27
|
+
The literals below restate values owned elsewhere: the branch equals
|
|
28
|
+
CLEANUP_WT_CONSOLIDATION_BRANCH in
|
|
29
|
+
.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_actions_lib.sh, and the
|
|
30
|
+
manifest tool, schema version, and freshness bound equal $script:ExpectedManifestTool,
|
|
31
|
+
$script:ExpectedManifestSchemaVersion, and $script:ManifestFreshnessBound in
|
|
32
|
+
.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1. The Codex hooks cannot import
|
|
33
|
+
.claude/lib, so the values are restated here and pinned equal by
|
|
34
|
+
tests/scripts/claude-hooks/hook-cleanup-context.Parity.Tests.ps1. The names avoid the
|
|
35
|
+
removal gates' $script:CleanupWorktreeManifestPath.
|
|
36
|
+
|
|
37
|
+
.NOTES
|
|
38
|
+
PowerShell 7+. Functions and literals only: no file, process, network, clock, or
|
|
39
|
+
environment access, no dot-source, and no import. Byte-identical at .claude/hooks,
|
|
40
|
+
.codex/hooks, and both bundle mirrors under extensions/drm-copilot/resources/.
|
|
41
|
+
#>
|
|
42
|
+
|
|
43
|
+
$script:CleanupContextBranch = 'documentationandmemories'
|
|
44
|
+
$script:CleanupContextManifestRelativePath = 'artifacts/orchestration/cleanup-worktrees-manifest.json'
|
|
45
|
+
$script:CleanupContextManifestTool = 'cleanup-merged-worktrees'
|
|
46
|
+
$script:CleanupContextManifestSchemaVersion = 1
|
|
47
|
+
$script:CleanupContextFreshnessHours = 24
|
|
48
|
+
|
|
49
|
+
function Test-CleanupManifestText {
|
|
50
|
+
<#
|
|
51
|
+
.SYNOPSIS
|
|
52
|
+
Return $true when the text is a cleanup manifest with the expected header.
|
|
53
|
+
.DESCRIPTION
|
|
54
|
+
The text must parse as a JSON object whose tool is the string cleanup-merged-worktrees
|
|
55
|
+
(ordinal) and whose schema_version is the integer 1. When -UtcNow is supplied, the
|
|
56
|
+
manifest must also carry a generated_at that parses, is not after UtcNow, and lies
|
|
57
|
+
within the freshness bound, and a run_id that is a non-blank string. The generated_at
|
|
58
|
+
value is read exactly as CleanupWorktreeManifest.psm1 reads it, so the PR and
|
|
59
|
+
delegation legs agree with the removal gates on freshness.
|
|
60
|
+
.PARAMETER Raw
|
|
61
|
+
The manifest text, or $null when no manifest exists.
|
|
62
|
+
.PARAMETER UtcNow
|
|
63
|
+
The current UTC time from the caller's clock seam.
|
|
64
|
+
.OUTPUTS
|
|
65
|
+
System.Boolean
|
|
66
|
+
#>
|
|
67
|
+
[CmdletBinding()]
|
|
68
|
+
[OutputType([bool])]
|
|
69
|
+
param(
|
|
70
|
+
[AllowNull()][AllowEmptyString()][string] $Raw,
|
|
71
|
+
[datetime] $UtcNow
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if ([string]::IsNullOrWhiteSpace($Raw)) {
|
|
75
|
+
return $false
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
$manifest = $Raw | ConvertFrom-Json -ErrorAction Stop
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return $false
|
|
82
|
+
}
|
|
83
|
+
if ($manifest -isnot [System.Management.Automation.PSCustomObject]) {
|
|
84
|
+
return $false
|
|
85
|
+
}
|
|
86
|
+
$names = @($manifest.PSObject.Properties.Name)
|
|
87
|
+
if ($names -notcontains 'tool' -or $names -notcontains 'schema_version') {
|
|
88
|
+
return $false
|
|
89
|
+
}
|
|
90
|
+
if ($manifest.tool -isnot [string] -or $manifest.tool -cne $script:CleanupContextManifestTool) {
|
|
91
|
+
return $false
|
|
92
|
+
}
|
|
93
|
+
$schemaVersion = $manifest.schema_version
|
|
94
|
+
if (($schemaVersion -isnot [int] -and $schemaVersion -isnot [long]) -or [long]$schemaVersion -ne $script:CleanupContextManifestSchemaVersion) {
|
|
95
|
+
return $false
|
|
96
|
+
}
|
|
97
|
+
if (-not $PSBoundParameters.ContainsKey('UtcNow')) {
|
|
98
|
+
return $true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if ($names -notcontains 'generated_at' -or $names -notcontains 'run_id') {
|
|
102
|
+
return $false
|
|
103
|
+
}
|
|
104
|
+
$generatedAt = [datetime]::MinValue
|
|
105
|
+
$styles = [System.Globalization.DateTimeStyles]::AdjustToUniversal -bor [System.Globalization.DateTimeStyles]::AssumeUniversal
|
|
106
|
+
if (-not [datetime]::TryParse([string]$manifest.generated_at, [cultureinfo]::InvariantCulture, $styles, [ref] $generatedAt)) {
|
|
107
|
+
return $false
|
|
108
|
+
}
|
|
109
|
+
if ($generatedAt -gt $UtcNow -or ($UtcNow - $generatedAt) -gt [timespan]::FromHours($script:CleanupContextFreshnessHours)) {
|
|
110
|
+
return $false
|
|
111
|
+
}
|
|
112
|
+
return ($manifest.run_id -is [string] -and -not [string]::IsNullOrWhiteSpace($manifest.run_id))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function Test-CleanupConsolidationRelativePath {
|
|
116
|
+
<#
|
|
117
|
+
.SYNOPSIS
|
|
118
|
+
Return $true when a repository-relative path is on the consolidation allowlist.
|
|
119
|
+
.DESCRIPTION
|
|
120
|
+
The path must use only the plain characters A-Z a-z 0-9 . _ / -, must not be rooted,
|
|
121
|
+
must carry no '..' segment, and must start with docs/ or .claude/agent-memory/ under
|
|
122
|
+
ordinal, case-sensitive comparison with at least one character after the prefix. Any
|
|
123
|
+
other Markdown path, such as CLAUDE.md or .claude/rules/x.md, is not admitted. With
|
|
124
|
+
-AllowReceipt the pr-author receipt artifacts/pr_body_<digits>.receipt.json is also
|
|
125
|
+
admitted; it never enters the staging allowlist.
|
|
126
|
+
.PARAMETER RelativePath
|
|
127
|
+
The repository-relative path or operand.
|
|
128
|
+
.PARAMETER AllowReceipt
|
|
129
|
+
Admit the pr-author receipt path (Write and Edit leg only).
|
|
130
|
+
.OUTPUTS
|
|
131
|
+
System.Boolean
|
|
132
|
+
#>
|
|
133
|
+
[CmdletBinding()]
|
|
134
|
+
[OutputType([bool])]
|
|
135
|
+
param(
|
|
136
|
+
[AllowNull()][AllowEmptyString()][string] $RelativePath,
|
|
137
|
+
[switch] $AllowReceipt
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
$path = [string]$RelativePath
|
|
141
|
+
if ($path -cnotmatch '^[A-Za-z0-9._/-]+$' -or $path.StartsWith('/')) {
|
|
142
|
+
return $false
|
|
143
|
+
}
|
|
144
|
+
if (@($path -split '/') -contains '..') {
|
|
145
|
+
return $false
|
|
146
|
+
}
|
|
147
|
+
if ($AllowReceipt -and $path -cmatch '^artifacts/pr_body_[0-9]+\.receipt\.json$') {
|
|
148
|
+
return $true
|
|
149
|
+
}
|
|
150
|
+
foreach ($prefix in @('docs/', '.claude/agent-memory/')) {
|
|
151
|
+
if ($path.Length -gt $prefix.Length -and $path.StartsWith($prefix, [System.StringComparison]::Ordinal)) {
|
|
152
|
+
return $true
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return $false
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function Find-CleanupConsolidationRecord {
|
|
159
|
+
<#
|
|
160
|
+
.SYNOPSIS
|
|
161
|
+
Return the registered consolidation worktree record, or $null.
|
|
162
|
+
.DESCRIPTION
|
|
163
|
+
Takes the records ConvertFrom-HookTargetWorktreePorcelain emits. The first record (the
|
|
164
|
+
main worktree stanza), prunable records, and detached records are skipped. The single
|
|
165
|
+
remaining record whose Branch is ordinal-equal to documentationandmemories is returned;
|
|
166
|
+
none, or more than one, returns $null. The path is never consulted, because the
|
|
167
|
+
consolidation path can be overridden by CLEANUP_WT_CONSOLIDATION_PATH.
|
|
168
|
+
.PARAMETER Record
|
|
169
|
+
The porcelain records, in listing order.
|
|
170
|
+
.OUTPUTS
|
|
171
|
+
System.Management.Automation.PSCustomObject or $null
|
|
172
|
+
#>
|
|
173
|
+
[CmdletBinding()]
|
|
174
|
+
[OutputType([pscustomobject])]
|
|
175
|
+
param(
|
|
176
|
+
[AllowNull()][AllowEmptyCollection()][object[]] $Record
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
$records = @($Record | Where-Object { $null -ne $_ })
|
|
180
|
+
$found = [System.Collections.Generic.List[object]]::new()
|
|
181
|
+
for ($index = 1; $index -lt $records.Count; $index++) {
|
|
182
|
+
$candidate = $records[$index]
|
|
183
|
+
if ([bool]$candidate.Prunable -or $null -eq $candidate.Branch) {
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
if ([string]$candidate.Branch -ceq $script:CleanupContextBranch) {
|
|
187
|
+
$found.Add($candidate)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if ($found.Count -ne 1) {
|
|
191
|
+
return $null
|
|
192
|
+
}
|
|
193
|
+
return $found[0]
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function ConvertTo-CleanupContextPath {
|
|
197
|
+
<#
|
|
198
|
+
.SYNOPSIS
|
|
199
|
+
Normalize a path to forward slashes with one trailing slash removed (bare roots kept).
|
|
200
|
+
.OUTPUTS
|
|
201
|
+
System.String
|
|
202
|
+
#>
|
|
203
|
+
[CmdletBinding()]
|
|
204
|
+
[OutputType([string])]
|
|
205
|
+
param(
|
|
206
|
+
[AllowNull()][AllowEmptyString()][string] $Path
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
$normalized = ([string]$Path).Replace('\', '/')
|
|
210
|
+
if ($normalized.Length -gt 1 -and $normalized.EndsWith('/') -and $normalized -notmatch '^[A-Za-z]:/$') {
|
|
211
|
+
$normalized = $normalized.Substring(0, $normalized.Length - 1)
|
|
212
|
+
}
|
|
213
|
+
return $normalized
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function Test-CleanupContextPathUnder {
|
|
217
|
+
<#
|
|
218
|
+
.SYNOPSIS
|
|
219
|
+
Return $true when a normalized path equals a root or lies beneath it (OrdinalIgnoreCase).
|
|
220
|
+
.OUTPUTS
|
|
221
|
+
System.Boolean
|
|
222
|
+
#>
|
|
223
|
+
[CmdletBinding()]
|
|
224
|
+
[OutputType([bool])]
|
|
225
|
+
param(
|
|
226
|
+
[Parameter(Mandatory)][AllowEmptyString()][string] $Path,
|
|
227
|
+
[AllowNull()][AllowEmptyString()][string] $Root
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
$normalizedRoot = ConvertTo-CleanupContextPath -Path $Root
|
|
231
|
+
if ([string]::IsNullOrEmpty($normalizedRoot)) {
|
|
232
|
+
return $false
|
|
233
|
+
}
|
|
234
|
+
if ([string]::Equals($Path, $normalizedRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
235
|
+
return $true
|
|
236
|
+
}
|
|
237
|
+
$prefix = if ($normalizedRoot.EndsWith('/')) { $normalizedRoot } else { $normalizedRoot + '/' }
|
|
238
|
+
return $Path.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function Test-CleanupScratchPath {
|
|
242
|
+
<#
|
|
243
|
+
.SYNOPSIS
|
|
244
|
+
Return $true when a path is a scratch path under a temporary root and outside every worktree.
|
|
245
|
+
.DESCRIPTION
|
|
246
|
+
The path must be absolute (drive-rooted or slash-rooted, not UNC), carry no '.' or '..'
|
|
247
|
+
segment and no surrounding whitespace, lie outside every registered worktree root and
|
|
248
|
+
outside the session root, and lie under at least one accepted temporary-root spelling.
|
|
249
|
+
Comparisons use forward-slash normalization and OrdinalIgnoreCase. The rule fails
|
|
250
|
+
closed when no registered root or no temporary root is supplied, and when any
|
|
251
|
+
registered worktree root lies under an accepted temporary root, because a path under
|
|
252
|
+
such a worktree could then be spelled so that it matches no registered root.
|
|
253
|
+
.PARAMETER Path
|
|
254
|
+
The Write or Edit file path.
|
|
255
|
+
.PARAMETER RegisteredRoot
|
|
256
|
+
Every registered worktree root from the listing, prunable ones included.
|
|
257
|
+
.PARAMETER SessionRoot
|
|
258
|
+
The session root.
|
|
259
|
+
.PARAMETER TemporaryRoot
|
|
260
|
+
Every accepted spelling of the operating-system temporary root.
|
|
261
|
+
.OUTPUTS
|
|
262
|
+
System.Boolean
|
|
263
|
+
#>
|
|
264
|
+
[CmdletBinding()]
|
|
265
|
+
[OutputType([bool])]
|
|
266
|
+
param(
|
|
267
|
+
[AllowNull()][AllowEmptyString()][string] $Path,
|
|
268
|
+
[AllowNull()][AllowEmptyCollection()][string[]] $RegisteredRoot,
|
|
269
|
+
[AllowNull()][AllowEmptyString()][string] $SessionRoot,
|
|
270
|
+
[AllowNull()][AllowEmptyCollection()][string[]] $TemporaryRoot
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
$raw = [string]$Path
|
|
274
|
+
if ([string]::IsNullOrEmpty($raw) -or $raw.Trim() -cne $raw) {
|
|
275
|
+
return $false
|
|
276
|
+
}
|
|
277
|
+
$normalized = ConvertTo-CleanupContextPath -Path $raw
|
|
278
|
+
if ($normalized -notmatch '^([A-Za-z]:/|/(?!/))') {
|
|
279
|
+
return $false
|
|
280
|
+
}
|
|
281
|
+
$segments = @($normalized -split '/')
|
|
282
|
+
if ($segments -contains '.' -or $segments -contains '..') {
|
|
283
|
+
return $false
|
|
284
|
+
}
|
|
285
|
+
$roots = @($RegisteredRoot | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
|
286
|
+
$temporary = @($TemporaryRoot | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
|
287
|
+
if ($roots.Count -eq 0 -or $temporary.Count -eq 0 -or [string]::IsNullOrWhiteSpace($SessionRoot)) {
|
|
288
|
+
return $false
|
|
289
|
+
}
|
|
290
|
+
foreach ($registeredCandidate in $roots) {
|
|
291
|
+
$normalizedRoot = ConvertTo-CleanupContextPath -Path $registeredCandidate
|
|
292
|
+
foreach ($temporaryCandidate in $temporary) {
|
|
293
|
+
if (Test-CleanupContextPathUnder -Path $normalizedRoot -Root $temporaryCandidate) {
|
|
294
|
+
return $false
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (Test-CleanupContextPathUnder -Path $normalized -Root $registeredCandidate) {
|
|
298
|
+
return $false
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (Test-CleanupContextPathUnder -Path $normalized -Root $SessionRoot) {
|
|
302
|
+
return $false
|
|
303
|
+
}
|
|
304
|
+
foreach ($temporaryCandidate in $temporary) {
|
|
305
|
+
$normalizedTemporary = ConvertTo-CleanupContextPath -Path $temporaryCandidate
|
|
306
|
+
if (-not [string]::Equals($normalized, $normalizedTemporary, [System.StringComparison]::OrdinalIgnoreCase) -and
|
|
307
|
+
(Test-CleanupContextPathUnder -Path $normalized -Root $normalizedTemporary)) {
|
|
308
|
+
return $true
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return $false
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function Test-CleanupConsolidationBranchLabel {
|
|
315
|
+
<#
|
|
316
|
+
.SYNOPSIS
|
|
317
|
+
Return $true when a delegation prompt names the consolidation branch by exactly one label.
|
|
318
|
+
.DESCRIPTION
|
|
319
|
+
The prompt must contain exactly one 'branch:' label (case-insensitive count), on its own
|
|
320
|
+
line in the lowercase form 'branch: <value>', with the value ordinal-equal to
|
|
321
|
+
documentationandmemories, and no --head or --branch token anywhere, so the worktree
|
|
322
|
+
resolvers that read the first branch signal cannot select a different branch.
|
|
323
|
+
.PARAMETER PromptText
|
|
324
|
+
The delegation prompt.
|
|
325
|
+
.OUTPUTS
|
|
326
|
+
System.Boolean
|
|
327
|
+
#>
|
|
328
|
+
[CmdletBinding()]
|
|
329
|
+
[OutputType([bool])]
|
|
330
|
+
param(
|
|
331
|
+
[AllowNull()][AllowEmptyString()][string] $PromptText
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
$text = [string]$PromptText
|
|
335
|
+
if ([regex]::Matches($text, '(?i)\bbranch:').Count -ne 1) {
|
|
336
|
+
return $false
|
|
337
|
+
}
|
|
338
|
+
if ([regex]::IsMatch($text, '(?i)(?<![\w-])--(?:head|branch)(?![\w-])')) {
|
|
339
|
+
return $false
|
|
340
|
+
}
|
|
341
|
+
$label = [regex]::Match($text, '(?m)^[ \t]*branch:[ \t]*(?<value>[^\r\n]*?)[ \t]*\r?$')
|
|
342
|
+
return ($label.Success -and $label.Groups['value'].Value -ceq $script:CleanupContextBranch)
|
|
343
|
+
}
|
|
@@ -57,6 +57,8 @@
|
|
|
57
57
|
".codex/hooks/enforce-epic-root-invocation.ps1",
|
|
58
58
|
".codex/hooks/enforce-epic-wave-barrier.ps1",
|
|
59
59
|
".codex/hooks/enforce-epic-worktree-removal-gate.ps1",
|
|
60
|
+
".codex/hooks/enforce-orchestration-preimplementation-gate-cleanup.ps1",
|
|
61
|
+
".codex/hooks/hook-cleanup-context.ps1",
|
|
60
62
|
".codex/hooks/enforce-orchestration-preimplementation-gate-epic-resolution.ps1",
|
|
61
63
|
".codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1",
|
|
62
64
|
".codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1",
|
|
@@ -11,6 +11,14 @@ This brief states the environment facts that every researcher, planner, prefligh
|
|
|
11
11
|
|
|
12
12
|
Maintenance rule: this brief is updated in the same change that alters any fact it states. It carries no line numbers and no commit identifiers; it cites files, symbols, headings, job names, and test names. The same text is byte-identical in six locations: `.claude/skills/execution-environment/SKILL.md`, `.agents/skills/execution-environment/SKILL.md`, `.github/skills/execution-environment/SKILL.md`, and their bundled copies under `extensions/drm-copilot/resources/`. Where the Claude and Codex runtimes differ, both paths are named (`.claude/hooks/...` and `.codex/hooks/...`).
|
|
13
13
|
|
|
14
|
+
## Development Toolchain Baseline
|
|
15
|
+
|
|
16
|
+
The host manifest, both devcontainer Dockerfiles, and development CI use Python 3.13.17, Node.js 26.13.0/npm 11.20.0, PowerShell 7.6.9, and Poetry 2.3.2. Shell validation pins shfmt 3.14.1, ShellCheck 0.11.0, and actionlint 1.7.12. Host bootstrap treats manifest versions as minima and preserves equal/newer installations; the container verifier accepts later Python patches within 3.13 and checks the other selected pins.
|
|
17
|
+
|
|
18
|
+
The devcontainer minor image can lag the Python patch, so both Dockerfiles copy the verified official Python runtime stage and select it and NodeSource ahead of older base-feature shims. Full configuration builds include the selected devcontainer features; a raw Dockerfile image alone does not prove feature-provided commands. Runtime verification uses an isolated Linux virtual environment rather than the host Windows environment.
|
|
19
|
+
|
|
20
|
+
Python 3.11/3.12 compatibility jobs, consumer package engines, and independent release-publishing runtime pins remain unchanged. The shared version predicate lives in `scripts/bash/container_versions.sh`; `.devcontainer/verify-container.sh` resolves it relative to its own repository location.
|
|
21
|
+
|
|
14
22
|
## Tool Routes by Agent
|
|
15
23
|
|
|
16
24
|
- `atomic-executor` has no PowerShell tool. Its `tools:` list is Read, Grep, Glob, Edit, Write, several `Bash(...)` entries, the four `mcp__drm-copilot__run_poshqc_*` tools, and `mcp__drm-copilot__validate_orchestration_artifacts`. A `(pattern)` suffix on a `tools:` entry admits the bare tool, so the executor has Bash, subject to the repository hooks and the runtime guard described under Command-Text Restrictions.
|
|
@@ -50,6 +58,8 @@ Two separate mechanisms restrict Bash command text. They are kept apart here bec
|
|
|
50
58
|
- Hooks for an isolated subagent run inside the subagent's worktree with that worktree's hook scripts, which are `main`'s copies. A hook change on a branch is not exercised by isolated subagents until it merges.
|
|
51
59
|
- Hook behavior on a branch is therefore proven by Pester suites, not by a live subagent run. A live check of a branch hook needs a non-isolated delegation from the item worktree.
|
|
52
60
|
- The batch-budget hooks evaluate the checkpoint of the target worktree that `Resolve-HookTargetWorktree` resolves for the edited file, so an edit made from a coordinating session into an item worktree is evaluated against the item worktree's checkpoint. The preimplementation gate (#1010) resolves the file path of a Write or Edit call and the `-C` values and absolute operands of `git add` and `git commit` through the same resolver, and denies when the worktree listing fails or names no worktree; its delegation checks still resolve by portable identity. The PR authoring gate (#1011) resolves the branch that a `gh pr create` or `gh pr edit` command names (`--head`, `--branch`, or a `branch:` label) through the same resolver when the command carries no canonical issue number line, and otherwise keeps its portable-identity and current-branch resolution. The other gates that read `artifacts/orchestration/orchestrator-state.json` are unchanged and still use the root that holds the hook script.
|
|
61
|
+
- Cleanup legs (#1026): a `cleanup-merged-worktrees` run writes no checkpoint, so the preimplementation gate (`.claude/hooks/enforce-orchestration-preimplementation-gate-cleanup.ps1` and its `.codex/hooks/` twin), the PR-authoring gate (`.claude/hooks/enforce-pr-author-skill.cleanup.ps1`), and the model-routing gate (`.claude/hooks/enforce-model-routing-receipt.ps1`) carry allow-side cleanup legs, evaluated only where each gate would otherwise deny, that share the pure predicates of `hook-cleanup-context.ps1`. The legs admit a Write of the session-root manifest `artifacts/orchestration/cleanup-worktrees-manifest.json` with valid content (an Edit stays denied), Write or Edit of paths under `docs/` or `.claude/agent-memory/` and of the pr-author receipt inside the consolidation worktree on branch `documentationandmemories`, `git -C <consolidation root> add` and `commit` of those paths, `Agent(pr-author)` with `branch: documentationandmemories`, and `gh pr create --head documentationandmemories --base main` while a fresh valid manifest exists at the session root and the pushed `refs/remotes/origin/documentationandmemories` resolves to the same commit as the consolidation worktree `HEAD`. A preimplementation-gate denial appends a cleanup sub-code (`Cleanup leg not applied`) only after a cleanup leg has identified the consolidation worktree as the target and a later condition fails; a call rejected by a prefilter keeps the unchanged deny text even when its target is the consolidation worktree, as does every other call.
|
|
62
|
+
- Scratch rule (#1026): the preimplementation gate allows a Write or Edit of an absolute local path that lies outside every registered worktree and the session root and under a temporary root, read through the seam `Get-OrchestrationGateTemporaryRoot` (the long-name `GetTempPath()` value and the `TEMP`, `TMP`, and `TMPDIR` values, which can carry the 8.3 short-name spelling). The rule fails closed when any registered worktree lies under a temporary root.
|
|
53
63
|
- Enforcement hooks do not invoke Python; bash is preferred and PowerShell is acceptable (`tests/scripts/claude-runtime/enforcement-hooks-no-python-invocation.Tests.ps1`).
|
|
54
64
|
|
|
55
65
|
## MCP Surfaces Run the Installed Extension
|
|
@@ -61,14 +71,14 @@ Two separate mechanisms restrict Bash command text. They are kept apart here bec
|
|
|
61
71
|
## CI Shape and Required Checks
|
|
62
72
|
|
|
63
73
|
- `.github/workflows/ci.yml` triggers on `push` to `main` and `development`, on `pull_request` to `main`, `development`, and `epic/**`, on `workflow_dispatch`, and on `merge_group` (`types: [checks_requested]`, added by issue #952), with no path filters. When the merge queue on `main` is enabled, every required check is produced by a `merge_group` run of `ci.yml` as well as by the pull-request run; `.github/workflows/README.md` (`## Triggers`) records the trigger set.
|
|
64
|
-
- Jobs in `ci.yml` and their check names: `Change scope` (job `changes`, the change-scope decision below); `CI full run` (job `full-run-marker`, which runs only on a full run, marks that run as a valid base for later decisions, and is not a required check); `quality-checks7 / Code Quality & Tests (<python-version>)` on Python 3.11, 3.12, and 3.13 (`_quality-checks.yml`: Black check, Ruff, Pyright, the Codex generator `--check`, the tier classification, and Pytest on every version; Pytest with line and branch coverage and the coverage-threshold check on the JSON report totals run on 3.13 only); `security-scan / Security Scanning` (its `safety check` step carries `continue-on-error: true` and does not fail the check); `docs-validation / Documentation Validation`; `build-check / Build Package`; `shell-coverage / Shell Coverage (Bats + kcov)`; `drm-copilot-extension-tests / drm-copilot Extension Tests (<os>)` on `ubuntu-latest` and `windows-latest`; `root-typescript-tests / Root TypeScript Tests (<os>)` on `ubuntu-latest` and `windows-latest`; `NPM Audit Gate / npm audit (<manifest>)` (the calling job carries the name `NPM Audit Gate`);
|
|
74
|
+
- Jobs in `ci.yml` and their check names: `Change scope` (job `changes`, the change-scope decision below); `CI full run` (job `full-run-marker`, which runs only on a full run, marks that run as a valid base for later decisions, and is not a required check); `quality-checks7 / Code Quality & Tests (<python-version>)` on Python 3.11, 3.12, and 3.13 (`_quality-checks.yml`: Black check, Ruff, Pyright, the Codex generator `--check`, the tier classification, and Pytest on every version; Pytest with line and branch coverage and the coverage-threshold check on the JSON report totals run on 3.13 only); `security-scan / Security Scanning` (its `safety check` step carries `continue-on-error: true` and does not fail the check); `docs-validation / Documentation Validation`; `build-check / Build Package`; `shell-coverage / Shell Coverage (Bats + kcov)`; `drm-copilot-extension-tests / drm-copilot Extension Tests (<os>)` on `ubuntu-latest` and `windows-latest`; `root-typescript-tests / Root TypeScript Tests (<os>)` on `ubuntu-latest` and `windows-latest`; `NPM Audit Gate / npm audit (<manifest>)` (the calling job carries the name `NPM Audit Gate`); the `poshqc` group below; and `CI Required Gate` (job `ci-required-gate`, issue #1074), which needs every other `ci.yml` job, runs under `if: always()` on `ubuntu-latest`, and passes `toJSON(needs)` to `scripts/ci/Invoke-CiRequiredGate.ps1`. The gate is green on a full run only when every needed job is `success`, and on a docs-only run only when `CI full run`, `root-typescript-tests`, and `NPM Audit Gate` are `success` or `skipped` and every other needed job is `success`; a `failure`, `cancelled`, or unknown result, a `Change scope` result other than `success`, and unreadable input are red. `tests/scripts/workflows/CiRequiredGateWorkflow.Tests.ps1` pins its `needs:` set against the other job ids of `ci.yml`.
|
|
65
75
|
- PowerShell CI (`_poshqc.yml`): `poshqc / PowerShell QC static` (format and analyze on `windows-latest`); the shard matrix `poshqc / PowerShell QC shard 1`, `shard 2`, and `shard 3` (`Invoke-PoshQCTest -ShardIndex -ShardCount` over the scan folders of `config/poshqc-scan.json`, with coverage, on `windows-latest`); the aggregator `poshqc / PowerShell QC`, which requires the static and shard jobs to succeed, verifies shard completeness against a Pester discovery count, and merges shard coverage; and `poshqc / PowerShell hook suites (Linux)`, which runs only `tests/scripts/claude-hooks` and `tests/scripts/codex-hooks` on `ubuntu-latest` with coverage disabled. A new test file under a scanned folder is assigned to a shard automatically and is counted by the completeness check.
|
|
66
76
|
- Change-scope decision (issue #962): the `Change scope` job runs `scripts/ci/Invoke-CiChangeScope.ps1` and publishes the job output `code`. The decision is `code=false` (docs-only) only on a `pull_request` event from this repository, through one of two comparisons. Green-run base: every file changed since the newest successful `ci.yml` run on the pull-request branch whose `CI full run` job succeeded, and whose head commit is present and an ancestor of the current head, is on the allowlist `config/ci-docs-only-paths.json`; the reason is `DOCS_ONLY_SINCE_<green head sha>`. Merge-base fallback (issue #973): when the green-run step ends with no earlier full green run (`NO_FULL_GREEN_RUN`), a missing base (`BASE_NOT_PRESENT`), or a non-ancestor base (`BASE_NOT_ANCESTOR`), and the pull request's base ref is exactly `main`, the script computes `git merge-base` of the payload base SHA (`BASE_SHA`, from `github.event.pull_request.base.sha`; the base ref arrives as `BASE_REF`) and the head, lists the files changed since that merge base with the same diff arguments, and decides `DOCS_ONLY_SINCE_MERGE_BASE_<merge base sha>` when every changed file is allowlisted. Both comparisons run the diff with rename detection off, so both the old and the new path of a rename are classified. A path is allowlisted when it starts with `docs/features/active/`, `docs/features/archive/`, `docs/features/completed/`, `docs/features/epics/`, `docs/features/parallel/`, `docs/features/potential/`, or `docs/research/`, does not start with `docs/features/templates/` or `docs/features/potential/template.md`, and ends in `.md`, `.txt`, `.log`, `.json`, or `.xml`; the allowlist file itself is not allowlisted. Every other case is a full run (`code=true`): `push`, `merge_group`, and `workflow_dispatch` events, a fork pull request, unreadable run or job data, no earlier full green run or a missing or non-ancestor base when the base ref is not `main`, a merge base that cannot be resolved (`MERGE_BASE_UNRESOLVED`, for example a blank base SHA or a base commit missing from the checkout), a failed ancestry check, an empty diff, any path outside the allowlist, and any error. The decision step carries `continue-on-error: true` and the callers compare the output with `!= 'false'`, so a failed or missing decision also gives a full run.
|
|
67
|
-
- Docs-only behavior (`code=false`): `ci.yml` passes `run-heavy: false` to `_quality-checks.yml`, `_security-scan.yml`, `_build-check.yml`, `_poshqc.yml`, `_shell-coverage.yml`, and `_drm-copilot-extension-tests.yml`. Each
|
|
77
|
+
- Docs-only behavior (`code=false`): `ci.yml` passes `run-heavy: false` to `_quality-checks.yml`, `_security-scan.yml`, `_build-check.yml`, `_poshqc.yml`, `_shell-coverage.yml`, and `_drm-copilot-extension-tests.yml`. Each job that runs with its steps gated still starts under its full-run check name with every step skipped, so it reports `success` without running its suite. In `_poshqc.yml` the aggregator `poshqc / PowerShell QC` runs with every step gated, and `poshqc / PowerShell QC static`, the shard jobs, and `poshqc / PowerShell hook suites (Linux)` are skipped at job level. `docs-validation / Documentation Validation` runs in full. `root-typescript-tests`, `NPM Audit Gate`, and `CI full run` are skipped at job level. Eight currently required contexts therefore never report on a docs-only run: `poshqc / PowerShell QC shard 1`, `poshqc / PowerShell QC shard 2`, `poshqc / PowerShell QC shard 3`, `root-typescript-tests / Root TypeScript Tests (ubuntu-latest)`, `root-typescript-tests / Root TypeScript Tests (windows-latest)`, `NPM Audit Gate / npm audit (.)`, `NPM Audit Gate / npm audit (extensions/drm-copilot)`, and `NPM Audit Gate / npm audit (packages/mcp-server)`, so a docs-only head stays blocked under the 20-context ruleset. `CI Required Gate` passes on a docs-only run, because it accepts `skipped` for exactly those skipped callers. A `success` on a docs-only commit is therefore not evidence that the heavy suites pass for that commit; that evidence is the newest run whose `CI full run` job succeeded.
|
|
68
78
|
- A commit that changes any path outside the allowlist (any code, workflow, configuration, script, test, or customization file) runs the full set of jobs, and its `CI full run` job marks it as a base for later docs-only decisions. A standalone `workflow_dispatch` of a reusable workflow, or a caller that omits `run-heavy`, also runs in full. `.github/workflows/README.md` (`## Change-Scope Decision (Issue #962)`) records the reason codes and the per-context behavior table.
|
|
69
|
-
- Required contexts of ruleset 15241672,
|
|
70
|
-
- Refresh rule: a change that renames a job, adds or removes a matrix entry, or changes the ruleset refreshes this list in the same change, and the orchestrator re-confirms the contexts with a read-only `gh api repos/drmoisan/drm-copilot/rulesets/15241672` call before relying on them.
|
|
71
|
-
- Skipped-check semantics: a job skipped by an `if:` condition reports success to a required check; a workflow that does not run at all reports nothing, which leaves the required check pending.
|
|
79
|
+
- Required contexts of ruleset 15241672, read through a read-only `gh api` call on 2026-11-04 (20 contexts, each with `integration_id` 15368): `drm-copilot-extension-tests / drm-copilot Extension Tests (ubuntu-latest)`, `drm-copilot-extension-tests / drm-copilot Extension Tests (windows-latest)`, `docs-validation / Documentation Validation`, `poshqc / PowerShell QC`, `shell-coverage / Shell Coverage (Bats + kcov)`, `security-scan / Security Scanning`, `quality-checks7 / Code Quality & Tests (3.11)`, `quality-checks7 / Code Quality & Tests (3.12)`, `quality-checks7 / Code Quality & Tests (3.13)`, `build-check / Build Package`, `NPM Audit Gate / npm audit (extensions/drm-copilot)`, `NPM Audit Gate / npm audit (.)`, `NPM Audit Gate / npm audit (packages/mcp-server)`, `root-typescript-tests / Root TypeScript Tests (ubuntu-latest)`, `root-typescript-tests / Root TypeScript Tests (windows-latest)`, `poshqc / PowerShell QC shard 1`, `poshqc / PowerShell QC shard 2`, `poshqc / PowerShell QC shard 3`, `poshqc / PowerShell QC static`, `poshqc / PowerShell hook suites (Linux)`. After the owner's post-merge ruleset edit (issue #1074 runbook), the single required context is `CI Required Gate` (`integration_id` 15368), and the individual job contexts remain visible but are no longer required.
|
|
80
|
+
- Refresh rule: a change that renames a job, adds or removes a `ci.yml` job or a matrix entry, or changes the ruleset refreshes this list in the same change, and the orchestrator re-confirms the contexts with a read-only `gh api repos/drmoisan/drm-copilot/rulesets/15241672` call before relying on them. A new `ci.yml` job must also be added to the `needs:` list of `ci-required-gate`; `tests/scripts/workflows/CiRequiredGateWorkflow.Tests.ps1` fails until it is.
|
|
81
|
+
- Skipped-check semantics: a job skipped by an `if:` condition reports success to a required check; a workflow that does not run at all reports nothing, which leaves the required check pending. A matrix job skipped at job level reports no expanded context (its matrix name is never expanded), and a reusable-workflow caller skipped in `ci.yml` reports no nested contexts, so each such required context stays pending.
|
|
72
82
|
- A pull request into a branch other than `main`, `development`, or `epic/**` gets no CI run; dispatch `ci.yml` manually for it.
|
|
73
83
|
|
|
74
84
|
## CI-Only Failure Classes
|
|
@@ -93,7 +93,9 @@ function Get-PoshQCScanConfigFolder {
|
|
|
93
93
|
if ([string]::IsNullOrWhiteSpace($folderText)) {
|
|
94
94
|
throw "Scan configuration '$ConfigRelativePath' contains a blank 'test.scanFolders' entry."
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
# Host-independent rule: a drive-qualified entry or one beginning with '/' or '\' is
|
|
97
|
+
# rejected on every host; the .NET rooted-path test accepts 'C:/x' on Linux and rejects it on Windows.
|
|
98
|
+
if ($folderText -match '^(?:[A-Za-z]:|[\\/])') {
|
|
97
99
|
throw "Scan configuration '$ConfigRelativePath' entry '$folderText' must be a workspace-relative path, not an absolute path."
|
|
98
100
|
}
|
|
99
101
|
# Reject parent-directory traversal in any path segment.
|
|
@@ -185,8 +185,9 @@ function Invoke-PoshQCTest {
|
|
|
185
185
|
# Issue #953 (CR-1): only an installed version inside the supported range counts as present, so a host
|
|
186
186
|
# whose only Pester is 6.x or the inbox 3.4.0 receives the actionable message instead of an import error.
|
|
187
187
|
if (-not (Get-Module -ListAvailable -FullyQualifiedName @{ ModuleName = $Name; ModuleVersion = $MinimumVersion; MaximumVersion = $MaximumVersion })) { throw $ErrorMessage }
|
|
188
|
-
# Import globally so Pester's Describe/It/Mock resolve in test containers
|
|
189
|
-
#
|
|
188
|
+
# Import globally so Pester's Describe/It/Mock resolve in test containers. Under the default
|
|
189
|
+
# $InvokePester trampoline, test code runs in this module's session state, not the global
|
|
190
|
+
# session state (issue #392; see the $InvokePester comment for the Mock consequence).
|
|
190
191
|
# Issue #953 (D8, CR-2): -MinimumVersion and -MaximumVersion keep the import inside the supported range.
|
|
191
192
|
Import-Module $Name -Global -MinimumVersion $MinimumVersion -MaximumVersion $MaximumVersion -ErrorAction Stop
|
|
192
193
|
},
|
|
@@ -286,14 +287,15 @@ function Invoke-PoshQCTest {
|
|
|
286
287
|
},
|
|
287
288
|
[scriptblock] $InvokePester = {
|
|
288
289
|
param($Config)
|
|
289
|
-
#
|
|
290
|
-
# not
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
#
|
|
294
|
-
#
|
|
295
|
-
#
|
|
296
|
-
# removes the temporary global function so no state leaks between suite
|
|
290
|
+
# Run Invoke-Pester through a temporary global function built with [scriptblock]::Create,
|
|
291
|
+
# so the function itself is not bound to this module's session state (issue #392). The
|
|
292
|
+
# discovered test code nevertheless runs in this module's session state: a test reports
|
|
293
|
+
# $ExecutionContext.SessionState.Module as PoshQC here, and $null under a plain
|
|
294
|
+
# Invoke-Pester run. Consequence: after a test re-imports PoshQC, Mock -ModuleName PoshQC
|
|
295
|
+
# from test code does not reach the re-imported instance; such a test runs its body in
|
|
296
|
+
# InModuleScope PoshQC and mocks without -ModuleName (observed in issue #1067). The
|
|
297
|
+
# try/finally pair removes the temporary global function so no state leaks between suite
|
|
298
|
+
# invocations.
|
|
297
299
|
$trampoline = [scriptblock]::Create('param($c) Invoke-Pester -Configuration $c')
|
|
298
300
|
$null = New-Item -Path 'function:global:Invoke-PoshQCPesterRun' -Value $trampoline -Force
|
|
299
301
|
try {
|