@danmoisan/drm-copilot-mcp 1.1.17 → 1.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/out/mcp-server.js +500 -106
  2. package/package.json +4 -3
  3. package/resources/claude-customizations/.claude/agents/feature-review.md +10 -1
  4. package/resources/claude-customizations/.claude/agents/orchestrator.md +16 -3
  5. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +6 -0
  6. package/resources/claude-customizations/.claude/lib/bash/parallel-lane-assertion.sh +4 -2
  7. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +6 -13
  8. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +375 -0
  9. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +22 -15
  10. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRemediationAccounting.psm1 +337 -0
  11. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +9 -1
  12. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +119 -3
  13. package/resources/claude-customizations/.claude/rules/quality-tiers.md +1 -1
  14. package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md +8 -0
  15. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +2 -0
  16. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +11 -3
  17. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +11 -2
  18. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +169 -5
  19. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +1 -1
  20. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_report_records_lib.sh +6 -43
  21. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +4 -15
  22. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +13 -6
  23. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +4 -0
  24. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +10 -0
  25. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +3 -1
  26. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +30 -12
  27. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +18 -3
  28. package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +5 -1
  29. package/resources/claude-customizations/pack-manifests/core.json +2 -0
  30. package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +8 -0
  31. package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +5 -3
  32. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +4 -0
  33. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +1 -1
  34. package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +30 -6
  35. package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +3 -1
  36. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +30 -7
  37. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -2
  38. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +18 -3
  39. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +1 -1
  40. package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +2 -0
  41. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c1.toml +1 -0
  42. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +1 -0
  43. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +1 -0
  44. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +1 -0
  45. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +1 -0
  46. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +1 -0
  47. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +1 -1
  48. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +1 -1
  49. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +1 -1
  50. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +1 -1
  51. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +1 -1
  52. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +1 -1
  53. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  54. package/resources/customizations/.github/skills/acceptance-criteria-tracking/SKILL.md +8 -0
  55. package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +4 -0
  56. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +4 -0
@@ -0,0 +1,337 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Portable remediation-loop review-outcome and accounting checks (R5-R11).
4
+
5
+ .DESCRIPTION
6
+ Destination-runtime PowerShell port of the issue #484 invariants the
7
+ authoritative Python validator applies to a checkpoint's remediation_loop:
8
+ the review-outcome rules R8a, R8b, R9a-R9d, and R10, and the accounting
9
+ rules R5, R6, R7a, R7b, and R11, matching the error-string templates in
10
+ `scripts/dev_tools/_orchestrator_state_remediation_loop.py` and the rules
11
+ document `.claude/rules/orchestrator-state.md`.
12
+
13
+ The receipts module calls the exported entry point after its unchanged
14
+ per-cycle checks, passing the cycle list or $null when cycles is absent or
15
+ not a list, so the new checks also run for a loop with review outcomes and
16
+ no cycles.
17
+
18
+ Every comparison against the verdict and class vocabulary is case-sensitive
19
+ (-ccontains, -cnotcontains, -ceq). An integer is a JSON number deserialized
20
+ as [int] or [long]; a boolean is never an integer. Values interpolated into
21
+ a message ({v}) render through ConvertTo-PythonDisplayText so the text
22
+ matches Python's str().
23
+
24
+ Every function is pure: it reads no file, starts no process, and never
25
+ mutates its input. Each check returns a string array, empty when valid.
26
+ CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
27
+ #>
28
+
29
+ Set-StrictMode -Version Latest
30
+ $ErrorActionPreference = 'Stop'
31
+
32
+ # Import the shared checkpoint-value primitives, resolved relative to this
33
+ # module's directory so the import travels with the pushed-down pack.
34
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'OrchestratorStateCheckpointValue.psm1') -Force -ErrorAction Stop
35
+
36
+ # Loop and cycle keys read by the accounting rules.
37
+ $script:REVIEW_OUTCOMES_KEY = 'review_outcomes'
38
+ $script:COMPLETED_ATTEMPTS_KEY = 'completed_attempts'
39
+ $script:CANDIDATE_APPLIED_KEY = 'candidate_applied'
40
+ $script:OPENED_BY_REVIEW_KEY = 'opened_by_review'
41
+ $script:COMPLETED_EXECUTION_STATUS = 'complete'
42
+
43
+ # Review verdicts and remediability classes in spec table order, and the two
44
+ # derived class partitions. Pinned to the Python constants of the same names.
45
+ $script:REVIEW_VERDICTS = @('PASS', 'REMEDIATION_REQUIRED', 'HALT_NON_REMEDIABLE', 'AWAITING_CI')
46
+ $script:REMEDIABILITY_CLASSES = @('autonomous', 'external_dependency', 'policy_hold', 'awaiting_ci', 'human_decision_required')
47
+ $script:NON_REMEDIABLE_CLASSES = @('external_dependency', 'policy_hold', 'awaiting_ci', 'human_decision_required')
48
+ $script:HALT_CLASSES = @('external_dependency', 'policy_hold', 'human_decision_required')
49
+ $script:REMEDIABLE_CLASS = 'autonomous'
50
+ $script:REMEDIATION_REQUIRED_VERDICT = 'REMEDIATION_REQUIRED'
51
+
52
+
53
+ function Test-RemediationStrictInteger {
54
+ <#
55
+ .SYNOPSIS
56
+ Report whether a deserialized JSON value is an integer and not a boolean.
57
+ .DESCRIPTION
58
+ Private predicate mirroring Python's isinstance(value, int) and not
59
+ isinstance(value, bool). ConvertFrom-Json materializes a JSON integer as
60
+ [int] or [long] and a JSON boolean as [bool].
61
+ .PARAMETER Value
62
+ The deserialized JSON value to classify. May be $null.
63
+ .OUTPUTS
64
+ System.Boolean - $true only for an [int] or [long] value.
65
+ #>
66
+ [CmdletBinding()]
67
+ [OutputType([bool])]
68
+ param(
69
+ [Parameter(Mandatory = $true)]
70
+ [AllowNull()]
71
+ [object] $Value
72
+ )
73
+
74
+ return (($Value -is [int]) -or ($Value -is [long])) -and -not ($Value -is [bool])
75
+ }
76
+
77
+ function Get-RemediationReviewVerdict {
78
+ <#
79
+ .SYNOPSIS
80
+ Derive a review verdict from its findings' remediability classes.
81
+ .DESCRIPTION
82
+ Private helper mirroring derive_review_verdict. Evaluates, in order: no
83
+ classes yields PASS; any autonomous class yields REMEDIATION_REQUIRED;
84
+ any member of HALT_CLASSES yields HALT_NON_REMEDIABLE; otherwise
85
+ AWAITING_CI. A member outside REMEDIABILITY_CLASSES (compared
86
+ case-sensitively) raises a terminating error whose message begins
87
+ 'invalid remediability: '.
88
+ .PARAMETER Remediability
89
+ The remediability class of every blocking finding; may be empty and may
90
+ repeat a class.
91
+ .OUTPUTS
92
+ System.String - one of the four review verdicts.
93
+ #>
94
+ [CmdletBinding()]
95
+ [OutputType([string])]
96
+ param(
97
+ [Parameter(Mandatory = $true)]
98
+ [AllowEmptyCollection()]
99
+ [string[]] $Remediability
100
+ )
101
+
102
+ foreach ($value in $Remediability) {
103
+ if ($script:REMEDIABILITY_CLASSES -cnotcontains $value) {
104
+ throw "invalid remediability: $value"
105
+ }
106
+ }
107
+
108
+ if ($Remediability.Count -eq 0) { return 'PASS' }
109
+ if ($Remediability -ccontains $script:REMEDIABLE_CLASS) { return 'REMEDIATION_REQUIRED' }
110
+ foreach ($value in $Remediability) {
111
+ if ($script:HALT_CLASSES -ccontains $value) { return 'HALT_NON_REMEDIABLE' }
112
+ }
113
+ return 'AWAITING_CI'
114
+ }
115
+
116
+ function Get-RemediationReviewOutcomeError {
117
+ <#
118
+ .SYNOPSIS
119
+ Return one review outcome's errors (R9a-R9d, then R10).
120
+ .DESCRIPTION
121
+ Private helper mirroring _validate_review_outcome. Checks the verdict
122
+ (string check before membership), the findings list, and each finding's
123
+ remediability (string check before membership); R10 runs only when none
124
+ of those checks produced an error for the outcome.
125
+ .PARAMETER Index
126
+ The outcome's zero-based position, used for error context.
127
+ .PARAMETER Outcome
128
+ The deserialized outcome object.
129
+ .OUTPUTS
130
+ System.String[] - zero or more error strings.
131
+ #>
132
+ [CmdletBinding()]
133
+ [OutputType([string[]])]
134
+ param(
135
+ [Parameter(Mandatory = $true)]
136
+ [int] $Index,
137
+
138
+ [Parameter(Mandatory = $true)]
139
+ [psobject] $Outcome
140
+ )
141
+
142
+ $errors = [System.Collections.Generic.List[string]]::new()
143
+ $prefix = "Checkpoint remediation review outcome #$Index"
144
+
145
+ $verdict = (Get-CheckpointObjectMember -Owner $Outcome -Name 'verdict').Value
146
+ if (-not ($verdict -is [string]) -or ($script:REVIEW_VERDICTS -cnotcontains [string]$verdict)) {
147
+ $rendered = ConvertTo-PythonDisplayText -Value $verdict
148
+ $errors.Add("$prefix verdict must be one of PASS, REMEDIATION_REQUIRED, HALT_NON_REMEDIABLE, AWAITING_CI; got: $rendered")
149
+ }
150
+
151
+ $classes = [System.Collections.Generic.List[string]]::new()
152
+ $findings = (Get-CheckpointObjectMember -Owner $Outcome -Name 'findings').Value
153
+ if (-not (Test-CheckpointListValue -Value $findings)) {
154
+ $errors.Add("$prefix findings must be a list.")
155
+ } else {
156
+ $findingIndex = 0
157
+ foreach ($finding in @($findings)) {
158
+ if (-not (Test-CheckpointObjectValue -Value $finding)) {
159
+ $errors.Add("$prefix finding #$findingIndex must be an object.")
160
+ } else {
161
+ $remediability = (Get-CheckpointObjectMember -Owner $finding -Name 'remediability').Value
162
+ if (-not ($remediability -is [string]) -or ($script:REMEDIABILITY_CLASSES -cnotcontains [string]$remediability)) {
163
+ $rendered = ConvertTo-PythonDisplayText -Value $remediability
164
+ $errors.Add("$prefix finding #$findingIndex remediability must be one of autonomous, external_dependency, policy_hold, awaiting_ci, human_decision_required; got: $rendered")
165
+ } else {
166
+ $classes.Add([string]$remediability)
167
+ }
168
+ }
169
+ $findingIndex++
170
+ }
171
+ }
172
+
173
+ # R10 compares only a well-formed outcome; any shape error above suppresses it.
174
+ if ($errors.Count -eq 0) {
175
+ $expected = Get-RemediationReviewVerdict -Remediability $classes.ToArray()
176
+ if (-not ([string]$verdict -ceq $expected)) {
177
+ $errors.Add("$prefix verdict $verdict does not match its findings (expected $expected).")
178
+ }
179
+ }
180
+
181
+ return $errors.ToArray()
182
+ }
183
+
184
+ function Get-RemediationReviewOutcomeListError {
185
+ <#
186
+ .SYNOPSIS
187
+ Return the review-outcome errors (R8a, R8b, then each outcome).
188
+ .DESCRIPTION
189
+ Private helper mirroring _validate_review_outcomes. An absent
190
+ review_outcomes key yields no errors; a present non-list value yields R8a
191
+ alone; otherwise each non-object outcome yields R8b and is skipped.
192
+ .PARAMETER RemediationLoop
193
+ The deserialized remediation_loop object.
194
+ .OUTPUTS
195
+ System.String[] - zero or more error strings.
196
+ #>
197
+ [CmdletBinding()]
198
+ [OutputType([string[]])]
199
+ param(
200
+ [Parameter(Mandatory = $true)]
201
+ [psobject] $RemediationLoop
202
+ )
203
+
204
+ $errors = [System.Collections.Generic.List[string]]::new()
205
+ $member = Get-CheckpointObjectMember -Owner $RemediationLoop -Name $script:REVIEW_OUTCOMES_KEY
206
+ if (-not $member.Present) { return $errors.ToArray() }
207
+ if (-not (Test-CheckpointListValue -Value $member.Value)) {
208
+ $errors.Add('Checkpoint remediation_loop review_outcomes must be a list.')
209
+ return $errors.ToArray()
210
+ }
211
+
212
+ $index = 0
213
+ foreach ($outcome in @($member.Value)) {
214
+ if (-not (Test-CheckpointObjectValue -Value $outcome)) {
215
+ $errors.Add("Checkpoint remediation review outcome #$index must be an object.")
216
+ } else {
217
+ $errors.AddRange([string[]]@(Get-RemediationReviewOutcomeError -Index $index -Outcome $outcome))
218
+ }
219
+ $index++
220
+ }
221
+
222
+ return $errors.ToArray()
223
+ }
224
+
225
+ function Test-RemediationOpenedByReview {
226
+ <#
227
+ .SYNOPSIS
228
+ Report whether an index names a REMEDIATION_REQUIRED outcome object.
229
+ .DESCRIPTION
230
+ Private predicate for R11: the value must be an integer (not a boolean)
231
+ from 0 to one less than the outcome count of a list review_outcomes, and
232
+ the indexed entry must be an object whose verdict is exactly
233
+ REMEDIATION_REQUIRED. A negative index is out of range.
234
+ .PARAMETER OpenedByReview
235
+ The raw opened_by_review value. May be $null.
236
+ .PARAMETER Outcomes
237
+ The raw review_outcomes value. May be $null.
238
+ .OUTPUTS
239
+ System.Boolean - $true only when the reference is valid.
240
+ #>
241
+ [CmdletBinding()]
242
+ [OutputType([bool])]
243
+ param(
244
+ [Parameter(Mandatory = $true)]
245
+ [AllowNull()]
246
+ [object] $OpenedByReview,
247
+
248
+ [Parameter(Mandatory = $true)]
249
+ [AllowNull()]
250
+ [object] $Outcomes
251
+ )
252
+
253
+ if (-not (Test-RemediationStrictInteger -Value $OpenedByReview)) { return $false }
254
+ if (-not (Test-CheckpointListValue -Value $Outcomes)) { return $false }
255
+ $outcomeList = @($Outcomes)
256
+ if ($OpenedByReview -lt 0 -or $OpenedByReview -ge $outcomeList.Count) { return $false }
257
+ $target = $outcomeList[[int]$OpenedByReview]
258
+ if (-not (Test-CheckpointObjectValue -Value $target)) { return $false }
259
+ $verdict = (Get-CheckpointObjectMember -Owner $target -Name 'verdict').Value
260
+ return ($verdict -is [string]) -and ([string]$verdict -ceq $script:REMEDIATION_REQUIRED_VERDICT)
261
+ }
262
+
263
+ function Get-OrchestratorStateRemediationAccountingError {
264
+ <#
265
+ .SYNOPSIS
266
+ Return the remediation_loop review-outcome and accounting errors (R5-R11).
267
+ .DESCRIPTION
268
+ Public entry mirroring the issue #484 tail of _validate_remediation_loop.
269
+ Returns, in order: the review-outcome errors (R8a; otherwise per outcome
270
+ R8b, R9a, R9b, R9c or R9d per finding, then R10); then, for each object
271
+ cycle in order, R5, R6, and R11; then R7a, otherwise R7b. With none of the
272
+ new keys present it returns nothing.
273
+ .PARAMETER RemediationLoop
274
+ The deserialized remediation_loop object.
275
+ .PARAMETER Cycle
276
+ The loop's cycles list, or $null when cycles is absent or not a list.
277
+ .OUTPUTS
278
+ System.String[] - zero or more error strings.
279
+ #>
280
+ [CmdletBinding()]
281
+ [OutputType([string[]])]
282
+ param(
283
+ [Parameter(Mandatory = $true)]
284
+ [psobject] $RemediationLoop,
285
+
286
+ [Parameter(Mandatory = $true)]
287
+ [AllowNull()]
288
+ [AllowEmptyCollection()]
289
+ [object] $Cycle
290
+ )
291
+
292
+ $errors = [System.Collections.Generic.List[string]]::new()
293
+ $errors.AddRange([string[]]@(Get-RemediationReviewOutcomeListError -RemediationLoop $RemediationLoop))
294
+
295
+ $outcomes = (Get-CheckpointObjectMember -Owner $RemediationLoop -Name $script:REVIEW_OUTCOMES_KEY).Value
296
+ $appliedCount = 0
297
+ $cycleItems = @()
298
+ if ($null -ne $Cycle) { $cycleItems = @($Cycle) }
299
+ $index = 0
300
+ foreach ($item in $cycleItems) {
301
+ if (Test-CheckpointObjectValue -Value $item) {
302
+ $prefix = "Checkpoint remediation cycle #$index"
303
+ $applied = Get-CheckpointObjectMember -Owner $item -Name $script:CANDIDATE_APPLIED_KEY
304
+ if ($applied.Present) {
305
+ if (-not ($applied.Value -is [bool])) {
306
+ $errors.Add("$prefix candidate_applied must be a boolean.")
307
+ } elseif ($applied.Value) {
308
+ $appliedCount++
309
+ $executionStatus = (Get-CheckpointObjectMember -Owner $item -Name 'execution_status').Value
310
+ if (-not ($executionStatus -is [string]) -or -not ([string]$executionStatus -ceq $script:COMPLETED_EXECUTION_STATUS)) {
311
+ $errors.Add("$prefix candidate_applied is true but execution_status is not 'complete'.")
312
+ }
313
+ }
314
+ }
315
+ $opened = Get-CheckpointObjectMember -Owner $item -Name $script:OPENED_BY_REVIEW_KEY
316
+ if ($opened.Present -and -not (Test-RemediationOpenedByReview -OpenedByReview $opened.Value -Outcomes $outcomes)) {
317
+ $errors.Add("$prefix opened_by_review must reference a review outcome whose verdict is REMEDIATION_REQUIRED.")
318
+ }
319
+ }
320
+ $index++
321
+ }
322
+
323
+ $attempts = Get-CheckpointObjectMember -Owner $RemediationLoop -Name $script:COMPLETED_ATTEMPTS_KEY
324
+ if ($attempts.Present) {
325
+ if (-not (Test-RemediationStrictInteger -Value $attempts.Value) -or $attempts.Value -lt 0) {
326
+ $errors.Add('Checkpoint remediation_loop completed_attempts must be a non-negative integer.')
327
+ } elseif ($attempts.Value -ne $appliedCount) {
328
+ $errors.Add("Checkpoint remediation_loop completed_attempts is $($attempts.Value) but $appliedCount cycles have candidate_applied true.")
329
+ }
330
+ }
331
+
332
+ return $errors.ToArray()
333
+ }
334
+
335
+ # Only the entry point is exported; the verdict helper and the per-outcome and
336
+ # per-reference checks stay private to this module.
337
+ Export-ModuleMember -Function Get-OrchestratorStateRemediationAccountingError
@@ -5,7 +5,8 @@
5
5
  .DESCRIPTION
6
6
  Destination-runtime PowerShell port of `validate_routing_contract` in
7
7
  `scripts/dev_tools/_orchestrator_state_routing.py`, covering parity-inventory
8
- rows C6.1 through C6.14:
8
+ rows C6.1 through C6.15. The C6.15 adoption waiver affects only receipt
9
+ presence (C6.9); the declared-list equality checks are unchanged:
9
10
 
10
11
  C6.1 routing matrix carries no routes object (returns)
11
12
  C6.2 no route selected (returns)
@@ -21,6 +22,7 @@
21
22
  C6.12 lifecycle_operations must be a list when present
22
23
  C6.13 per non-object lifecycle operation
23
24
  C6.14 per lifecycle operation that did not use the MCP surface
25
+ C6.15 issue_adoption field and waiver rules (OrchestratorStateIssueAdoption.psm1)
24
26
 
25
27
  The routing matrix is read from `OrchestratorStateRoutingMatrix.psm1`, which
26
28
  implements deviation PD-1: the constants are pinned and no disk read occurs at
@@ -56,6 +58,7 @@ $ErrorActionPreference = 'Stop'
56
58
  # pushed-down pack regardless of the working directory.
57
59
  Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'OrchestratorStateCheckpointValue.psm1') -Force -ErrorAction Stop
58
60
  Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'OrchestratorStateRoutingMatrix.psm1') -Force -ErrorAction Stop
61
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'OrchestratorStateIssueAdoption.psm1') -Force -ErrorAction Stop
59
62
 
60
63
  # The promotion-entry MCP tools and the promotion type that triggers substitution.
61
64
  $script:FEATURE_PROMOTION_ENTRY_TOOL = 'new_potential_entry'
@@ -410,11 +413,16 @@ function Get-OrchestratorStateRoutingContractError {
410
413
  }
411
414
 
412
415
  $actualTools = @(Get-CheckpointAcknowledgedName -State $State -ArrayKey 'mcp_call_receipts' -NameKey 'tool' -FlagKey 'ok')
416
+ # C6.15: a valid issue_adoption record waives the receipt requirement for the
417
+ # tools it lists; any adoption error waives nothing.
418
+ $adoption = Get-OrchestratorStateIssueAdoptionResult -State $State -RouteId $routeId -RequiredMcpTool ([string[]]$requiredMcpTools) -SuccessfulTool ([string[]]$actualTools)
413
419
  foreach ($tool in $requiredMcpTools) {
420
+ if (@($adoption.WaivedTools) -ccontains $tool) { continue }
414
421
  if ($actualTools -cnotcontains $tool) {
415
422
  $errors.Add("Checkpoint missing successful MCP receipt: $tool.")
416
423
  }
417
424
  }
425
+ $errors.AddRange([string[]]@($adoption.Errors))
418
426
 
419
427
  # C6.10 to C6.14: the two empty-list fields and the lifecycle operations.
420
428
  foreach ($key in $script:COMPLETION_EMPTY_LIST_KEYS) {
@@ -24,7 +24,7 @@ description: Checkpoint invariants for the orchestration state artifact and the
24
24
 
25
25
  # Orchestrator-State Remediation-Cycle and Human-Interaction Invariants
26
26
 
27
- This rule governs remediation-cycle records and the optional `human_interaction` block in the orchestrator-state checkpoint at `artifacts/orchestration/orchestrator-state.json`. It documents three invariants that must hold for each remediation cycle, plus three invariants for the `human_interaction` block, so that resume and review workflows do not depend on a structurally invalid checkpoint.
27
+ This rule governs remediation-cycle records and the optional `human_interaction` block in the orchestrator-state checkpoint at `artifacts/orchestration/orchestrator-state.json`. It documents the invariants that must hold for each remediation cycle and for the remediation-loop review outcomes and attempt count, plus three invariants for the `human_interaction` block, so that resume and review workflows do not depend on a structurally invalid checkpoint.
28
28
 
29
29
  ## Foreign Schema Warning (do not copy verbatim)
30
30
 
@@ -34,7 +34,7 @@ This prohibition is specific to the disqualified foreign schema identified by th
34
34
 
35
35
  ## Scope and Backward Compatibility
36
36
 
37
- These invariants apply only when the checkpoint contains a top-level `remediation_loop` with a `cycles` array. A checkpoint with no `remediation_loop` (the existing step-based checkpoint shape) is unaffected: it validates exactly as before and produces no new errors. The invariants are additive.
37
+ These invariants apply only when the checkpoint contains a top-level `remediation_loop`; the per-cycle invariants apply when it has a `cycles` array, and the review-outcome and attempt-count invariants apply when their keys are present. A checkpoint with no `remediation_loop` (the existing step-based checkpoint shape) is unaffected: it validates exactly as before and produces no new errors. The invariants are additive.
38
38
 
39
39
  ## Invariants (per remediation cycle)
40
40
 
@@ -44,6 +44,45 @@ These invariants apply only when the checkpoint contains a top-level `remediatio
44
44
 
45
45
  3. **Exit gate requires zero blocking findings.** When a cycle's `exit_condition_met == true`, its `blocking_count` must be `0`. A non-zero `blocking_count` with `exit_condition_met == true` is a malformed cycle (the exit gate was marked satisfied while blocking findings remained).
46
46
 
47
+ 4. **`candidate_applied` is a boolean (R5).** When a cycle object carries `candidate_applied`, its value must be a JSON boolean, and any other value is a malformed cycle.
48
+
49
+ 5. **A completed attempt requires completed execution (R6).** A cycle whose `candidate_applied == true` must have `execution_status == "complete"`, and `candidate_applied == true` with any other execution status is a malformed cycle.
50
+
51
+ 6. **`opened_by_review` references a remediation review (R11).** When a cycle object carries `opened_by_review`, its value must be a non-negative integer (not a boolean) that indexes an object entry of the `remediation_loop.review_outcomes` list whose `verdict` is `REMEDIATION_REQUIRED`, and a value that is not such an integer, is out of range, or references an outcome with any other verdict is a malformed cycle.
52
+
53
+ 7. **`completed_attempts` counts completed attempts (R7).** When `remediation_loop.completed_attempts` is present, it must be a non-negative integer (not a boolean) equal to the number of object cycles whose `candidate_applied` is `true` (zero when `cycles` is absent or not a list), and any other value is a malformed loop.
54
+
55
+ ## Invariants (remediation_loop.review_outcomes)
56
+
57
+ These invariants apply only when `remediation_loop` contains a `review_outcomes` key. The orchestrator appends one entry per review, including a `PASS` review, in the shape `{"verdict": <verdict>, "findings": [{"remediability": <class>}, ...]}`, where `findings` lists the review's blocking findings only. A halt or wait verdict records no remediation cycle.
58
+
59
+ Review verdicts (exact, case-sensitive):
60
+
61
+ | Verdict | Condition over the classes of the review's blocking findings |
62
+ |---|---|
63
+ | `PASS` | No blocking findings. |
64
+ | `REMEDIATION_REQUIRED` | At least one `autonomous` finding. |
65
+ | `HALT_NON_REMEDIABLE` | No `autonomous` finding, and at least one `external_dependency`, `policy_hold`, or `human_decision_required` finding. |
66
+ | `AWAITING_CI` | At least one finding, and every finding is `awaiting_ci`. |
67
+
68
+ Remediability classes (exact, case-sensitive) and the `blocked_reason` member (#523) that a halt or wait records for each class:
69
+
70
+ | Class | Meaning | `blocked_reason` member |
71
+ |---|---|---|
72
+ | `autonomous` | Repository remediation can resolve the finding without a human decision. | none (remediation loop) |
73
+ | `external_dependency` | A system, service, runtime, or published artifact outside the repository is unavailable or mismatched. | `external_dependency` |
74
+ | `policy_hold` | Proceeding requires a policy decision, exception, or authorization that the orchestrator may not grant. | `policy_hold` |
75
+ | `awaiting_ci` | The finding resolves when a CI result that has not completed becomes available. | `awaiting_ci` |
76
+ | `human_decision_required` | A human must choose between alternatives or approve a direction. | `human_decision_required` |
77
+
78
+ Derivation order. The verdict is a pure function of the findings' classes, evaluated in this order: no findings yields `PASS`; any `autonomous` finding yields `REMEDIATION_REQUIRED`; any `external_dependency`, `policy_hold`, or `human_decision_required` finding yields `HALT_NON_REMEDIABLE`; otherwise the verdict is `AWAITING_CI`.
79
+
80
+ 1. **`review_outcomes` is a list of objects (R8).** `review_outcomes` must be a list, and each entry must be an object. A non-list value or a non-object entry is malformed.
81
+
82
+ 2. **Verdict and class membership (R9).** Each outcome's `verdict` must be a string among the four verdicts, its `findings` must be a list (a missing key is not a list), and each finding must be an object whose `remediability` is a string among the five classes. Comparisons are case-sensitive, so a case variant such as `Pass` or `Autonomous` is malformed.
83
+
84
+ 3. **Verdict matches its findings (R10).** When an outcome passes the membership checks, its `verdict` must equal the verdict that the derivation order yields for its findings' classes. A mismatch is a malformed outcome.
85
+
47
86
  ## Human-Interaction Scope and Backward Compatibility
48
87
 
49
88
  These invariants apply only when the checkpoint contains a top-level `human_interaction` block. A checkpoint with no `human_interaction` key (the existing checkpoint shape) is unaffected: it validates exactly as before and produces no new errors. The invariants are additive and support the autonomous-execution mandate documented in `.claude/skills/orchestrate/SKILL.md`.
@@ -56,6 +95,41 @@ These invariants apply only when the checkpoint contains a top-level `human_inte
56
95
 
57
96
  3. **Exception requires `runbook_path`.** A requirement whose `response == "exception"` must carry a non-empty `runbook_path` string. A missing, non-string, or empty/whitespace-only `runbook_path` on an `exception` requirement is a malformed requirement.
58
97
 
98
+ ## Blocked-Reason Vocabulary
99
+
100
+ The checkpoint key `blocked_reason` accepts exactly the twelve members below. The authoritative definition is `scripts/dev_tools/_orchestrator_state_blocked_reason.py`; the TypeScript module `extensions/drm-copilot/src/lib/validate/orchestrator-state-blocked-reason.ts` and the grouped arrays in `.claude/lib/orchestrator-state/OrchestratorState.psm1` mirror it.
101
+
102
+ | Member | Partition | Definition |
103
+ |---|---|---|
104
+ | `none` | not blocked | The run is not blocked. JSON `null` is equivalent for classification. |
105
+ | `spawn_agent_unavailable` | mechanical | Existing member; meaning unchanged. |
106
+ | `delegation_launch_failed` | mechanical | Existing member; meaning unchanged. |
107
+ | `delegate_no_receipt` | mechanical | Existing member; meaning unchanged. |
108
+ | `delegate_contract_incomplete` | mechanical | Existing member; meaning unchanged. |
109
+ | `validator_failed` | mechanical | Existing member; meaning unchanged. |
110
+ | `user_requested_stop` | mechanical | Existing member; meaning unchanged. |
111
+ | `premise_falsified` | non-mechanical | Every delegation and validator succeeded, but evidence gathered during execution falsified the premise on which the plan was built, so continuing would implement an invalid plan. |
112
+ | `external_dependency` | non-mechanical | The run cannot proceed because a system, service, runtime, or artifact outside the repository's control is unavailable or mismatched, and no in-repository remediation can resolve it. |
113
+ | `policy_hold` | non-mechanical | The run is stopped because proceeding requires a policy decision, exception, or authorization that the orchestrator is not permitted to grant autonomously. |
114
+ | `awaiting_ci` | non-mechanical | The run is waiting for a CI result that has not yet completed, and no remediation is warranted until that result is available. |
115
+ | `human_decision_required` | non-mechanical | The run requires a human to choose between alternatives or approve a direction before it can continue. |
116
+
117
+ Partition definitions:
118
+
119
+ - **Not blocked:** `none`, JSON `null`, or key absent at a gate. Plain validation still reports an absent key through the required-key check.
120
+ - **Mechanical:** the orchestration process itself did not complete a step: a tool or delegation failed, a delegate returned no or an incomplete receipt, a validator failed, or the operator stopped execution. Membership is defined by the published constant, not by the wording.
121
+ - **Non-mechanical:** every process step could proceed, but the work cannot continue for a reason that no retry or remediation cycle of the process resolves.
122
+
123
+ The classification is recoverable from `blocked_reason` alone by looking the value up in the published partition (`classify_blocked_reason` in Python, `classifyBlockedReason` in TypeScript).
124
+
125
+ Enforcement parity: the Python, PowerShell, and TypeScript validators enforce this vocabulary identically, with case-sensitive comparison. A value outside the vocabulary, including a case variant of any member, is rejected with `Checkpoint has invalid blocked_reason: <value>`. Every non-`none` member, including the five non-mechanical members, blocks completion and PR-creation readiness with the existing messages.
126
+
127
+ Relationship to `human_interaction.requirements[].response == "halt"`: a human-decision halt may appear in both places. `blocked_reason: "human_decision_required"` classifies the run's halt, and a `human_interaction.requirements[]` entry with `response: "halt"` describes the specific requirement. There is no cross-field rule between them: either may appear without the other, and neither is validated against the other.
128
+
129
+ Producer guidance for `premise_falsified`: set it when every delegation and validator succeeded but evidence gathered during execution falsified the plan's premise. The evidence path is still recorded in free-form checkpoint keys; `blocked_reason` carries the classification only.
130
+
131
+ Extension point for #484: #484 consumes `external_dependency`, `policy_hold`, `awaiting_ci`, and `human_decision_required` without renaming, removing, or re-partitioning them; any further halt class is a separate contract change that appends the literal to the non-mechanical partition in all three runtimes, updates the partition oracle and corpus, and updates this section.
132
+
59
133
  ## Complexity-Assessment Scope and Backward Compatibility
60
134
 
61
135
  These invariants apply only when the checkpoint contains a top-level `complexity_assessments` array. A checkpoint with no `complexity_assessments` key (the existing checkpoint shape) is unaffected: it validates exactly as before and produces no new errors. The invariants are additive and support the two-axis model-selection mechanism documented in `.claude/skills/orchestrate/SKILL.md` (`## Model Selection`). Enforcement is the Python validator, not an imported schema.
@@ -92,7 +166,11 @@ The session `model_budget.fable_policy` switch is a three-way enum `disabled | a
92
166
 
93
167
  ## Require-Model-Routing Mode Scope and Backward Compatibility
94
168
 
95
- The complexity-assessment and model-routing-receipt invariants above are key-gated: they run only when their key is present, so a checkpoint that omits both arrays passes at every stage. The `require_model_routing` mode adds an existence gate that closes that gap without changing the default behavior. It is an opt-in keyword on `validate_orchestrator_state_text(..., require_model_routing=False)` (CLI flag `--require-model-routing`; MCP parameter `require_model_routing`), defaulting off. Plain, `require_complete`, and `require_pr_creation_ready` calls are unaffected and produce byte-identical results.
169
+ The complexity-assessment and model-routing-receipt invariants above are key-gated: they run only when their key is present, so a checkpoint that omits both arrays passes at every stage. The `require_model_routing` mode adds an existence gate that closes that gap without changing the default behavior. It is an opt-in keyword on `validate_orchestrator_state_text(..., require_model_routing=False)` (CLI flag `--require-model-routing`, accepted by the dispatcher `orchestrator-state` subcommand and by the bare-module CLI; MCP parameter `require_model_routing`), defaulting off. Plain, `require_complete`, and `require_pr_creation_ready` calls are unaffected and produce byte-identical results.
170
+
171
+ ## Bare-Module CLI Contract
172
+
173
+ The validator module runs directly as `python -m scripts.dev_tools.validate_orchestrator_state <path> [--require-complete] [--require-model-routing] [--require-pr-creation-ready] [--require-codex-model-routing] [--require-codex-topology]`. The dispatcher `python -m scripts.dev_tools.validate_orchestration_artifacts orchestrator-state` stays available and accepts the same flags. Exit code 0 means the checkpoint passed and the success line `orchestrator-state validation passed: <path>` is written to stdout. Exit code 1 means validation errors, written one per stderr line. Exit code 2 means the path is missing, unreadable, or not UTF-8, with one stderr diagnostic and no traceback; argparse usage errors also exit 2.
96
174
 
97
175
  ## Invariants (require_model_routing mode)
98
176
 
@@ -146,6 +224,43 @@ not a cryptographic or security control.
146
224
 
147
225
  Field checks 3 through 9 run in the fixed order `pr_url`, `issue_num`, `branch_name`, `authorized_by`, `authorized_at`, `basis`, `run_slug`, `session_id`; the first failure wins and the gate denies with `STANDALONE_MERGE_AUTHORIZATION_MALFORMED` naming that field.
148
226
 
227
+ ## Issue-Adoption Scope and Backward Compatibility
228
+
229
+ These invariants apply only when a checkpoint contains a top-level `issue_adoption` key. The key records that the orchestration adopted a GitHub issue that already existed before orchestration started (transferred, filed by hand, or created by epic decomposition), so the issue was never created through `potential_to_issue`. The key is presence-gated: a checkpoint without it validates byte-identically to before and produces no new errors. It is evaluated only by the routing-contract completion check (`validate_routing_contract` and its TypeScript and PowerShell ports). It is not a member of `REQUIRED_STATE_KEYS`, it is not read by plain (non-completion) validation, and it is not read by the PR-creation-readiness gate. No JSON Schema file is authored, imported, or read for it.
230
+
231
+ Honest disclosure: the validators perform no network I/O. They cannot confirm that the issue exists, so `evidence` and `verified_via` are an auditable declaration of the read-only verification the orchestrator performed, not proof of it. This matches the `standalone_merge_authorizations` precedent above: the record is a policy-level declaration, not a security control.
232
+
233
+ ## Invariants (issue_adoption object)
234
+
235
+ The object has the shape `{ issue_num, issue_url, origin, verified_via, verified_at, evidence, waived_tools, potential_record? }`.
236
+
237
+ | Field | Rule |
238
+ | --- | --- |
239
+ | `issue_num` | A string of decimal digits without a leading zero; equal to the checkpoint `issue-num` when that is a string. |
240
+ | `issue_url` | A string ending with `/issues/` followed by `issue_num`. |
241
+ | `origin` | One of `transferred`, `filed_before_orchestration`, `epic_decomposition`. |
242
+ | `verified_via` | One of `gh_issue_view`, `gh_api_get`, `github_mcp_issue_read`. |
243
+ | `verified_at` | Present, not `null`, and not a blank string. |
244
+ | `evidence` | A string with non-whitespace content. |
245
+ | `waived_tools` | A non-empty list of non-blank tool names that includes `potential_to_issue`. |
246
+ | `potential_record` | Required when waiving a promotion-entry tool: a path under `docs/features/potential/` ending in `.md`. |
247
+
248
+ The rules run in this order, and their errors accumulate:
249
+
250
+ 1. A present value that is not an object (including `null`) yields `Checkpoint issue_adoption must be an object when present.` and evaluation stops.
251
+ 2. `issue_num` must be a string of decimal digits without a leading zero (a JSON number is invalid). Only when it is valid and the checkpoint `issue-num` is a different string, `issue_num` must equal the checkpoint issue-num.
252
+ 3. `issue_url` must end with `/issues/` followed by a valid `issue_num`.
253
+ 4. `origin` must be one of the three origin values.
254
+ 5. `verified_via` must be one of the three verification values.
255
+ 6. `verified_at` must be present: absent, `null`, and blank strings are rejected; any other value passes.
256
+ 7. `evidence` must be a non-empty string.
257
+ 8. `waived_tools` must be a non-empty list of non-blank strings; otherwise no further rule-8 or rule-9 check runs. Each entry, in list order, receives the first applicable error of: listed more than once; not in the closed waivable set; not required by the selected route after promotion-type resolution; already holding a successful MCP receipt. The list must include `potential_to_issue`.
258
+ 9. Only when the rule-8 shape check passed: each distinct waived promotion-entry tool (`new_potential_entry` or `new_potential_bug_entry`) requires a valid `potential_record`.
259
+
260
+ The closed waivable set is `potential_to_issue`, `new_potential_entry`, and `new_potential_bug_entry`. `new_active_feature_folder`, `collect_pr_context`, `validate_orchestration_artifacts`, and every other tool can never be waived. All comparisons are ordinal and case-sensitive.
261
+
262
+ Fail-closed behavior: any adoption error waives nothing, so each missing receipt is still reported as `Checkpoint missing successful MCP receipt: <tool>.`. Adoption errors are placed after the receipt-loop errors and before the `local_execution_overrides` errors. The waiver affects only receipt presence: the declared `required_mcp_tools` equality check with the routing matrix is unchanged, so a checkpoint must still declare every route tool it waives.
263
+
149
264
  ## Enforcement
150
265
 
151
266
  - `scripts/dev_tools/validate_orchestrator_state.py` appends one error per violated invariant when a `remediation_loop` is present, using the existing validator message style (literal, checkpoint-context prefixed). The validator returns a list of error strings and does not mutate its input.
@@ -157,3 +272,4 @@ Field checks 3 through 9 run in the fixed order `pr_url`, `issue_num`, `branch_n
157
272
  - The standalone-merge authorization invariants are enforced by the `PreToolUse` merge gate (`.claude/hooks/enforce-epic-merge-gate.ps1` with its dot-sourced helpers file, and `.codex/hooks/enforce-epic-merge-gate.ps1`) at merge time. The Python checkpoint validator
158
273
  does not currently validate standalone_merge_authorizations
159
274
  entries; adding that check is recorded as follow-up FU-3.
275
+ - The `issue_adoption` invariants are enforced at completion by the routing-contract check in all three runtimes: `scripts/dev_tools/_orchestrator_state_issue_adoption.py` (Python authority), `extensions/drm-copilot/src/lib/validate/orchestrator-state-issue-adoption.ts`, and `.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1`, with byte-identical error strings pinned by the shared corpus under `tests/fixtures/orchestrator_state_issue_adoption/`.
@@ -6,7 +6,7 @@ description: Module rigor tier system and uniform coverage thresholds.
6
6
 
7
7
  # Module Rigor Tiers
8
8
 
9
- This rule defines the T1–T4 module rigor tier system used by all CI gates in this repository. The tier system source of truth is `docs/ci.research.md` section 1; the file `quality-tiers.yml` at the repository root maps every project to a tier. Adding a project without a tier classification fails CI.
9
+ This rule defines the T1–T4 module rigor tier system used by all CI gates in this repository. The tier definitions and gate matrix in this document are the tier system's source of truth; the file `quality-tiers.yml` at the repository root maps every project to a tier. Adding a project without a tier classification fails CI.
10
10
 
11
11
  ## Tiers
12
12
 
@@ -70,6 +70,14 @@ During plan execution, after completing a task whose work satisfies an acceptanc
70
70
 
71
71
  Timing: Check off AC items as soon as the corresponding plan task passes verification — do not defer all AC updates to the end of plan execution.
72
72
 
73
+ ### CI-Dependent Criteria
74
+
75
+ A CI-dependent criterion is one whose verification requires the result of CI on the PR head. It cannot be verified before the pull request exists, so it stays unchecked and is listed as pending-CI in the AC verification artifact until CI has run.
76
+
77
+ - Owner: the item's own orchestrator run owns the check-off and performs it at S9; this is the one case in which an orchestrator run checks off AC. A parent or coordinating session never commits the check-off from its own root.
78
+ - Timing: after the CI green gate (S9) records `ci_gate.conclusion` as `success`, and before the run reports DONE.
79
+ - Push and re-verify: the check-off is committed in the item's own worktree and pushed to the PR branch, and the CI green gate is re-run against the new head SHA so that `ci_gate.head_sha` equals the final PR head before DONE.
80
+
73
81
  ### When Reviewers Check Off AC
74
82
 
75
83
  During feature review (feature-audit phase):
@@ -212,6 +212,8 @@ Rules:
212
212
  - **Observe a command's success-case output before asserting over that output (mandatory).** Run the command, or read a recorded run of it, and confirm the value you intend to assert is actually printed on a *successful* run. Do not infer it from the tool's documentation, from the plan, or from what the tool prints on failure. This requirement exists because the class of defect it prevents is not detectable by reading: an assertion on a line the tool prints only when it changed something is unsatisfiable on a clean run; a demand for a zero-diagnostic count from a tool that returns only an ok flag names a value with no source; a demand for separate line and branch percentages from a coverage run that prints one combined column reads two numbers that are never printed. G7 and G9 cover two decidable slices of this class. The remainder is covered by this requirement and by nothing else, so a reviewer cannot rely on the gate to catch it.
213
213
  - **Fix the evidence in the plan; never leave the executor to select it.** An executor free to choose the evidence it is judged against cannot fail. Do not write an acceptance condition that asks the executor to identify "the known-genuine pair", to choose a survivor list, or to pick any suitable instance. Name the instances, or state the mechanical derivation that produces them, so a third party re-running it obtains the same set. This is authoring guidance and not a rule: a validator rule that scanned acceptance text for selection vocabulary was proposed and rejected, because that vocabulary is ordinary plan prose used in roles carrying no selection semantics and a keyword scan over prose is not statically decidable for the property it would claim to detect. The judgment is the author's, and it is not automated. `.claude/rules/plan-acceptance-gates.md` records the rejection and the reason for it.
214
214
  - **Check that the task-ordering does not make the condition unsatisfiable.** No rule covers this. A gate that runs a test path containing deliberately-failing cases added by an earlier task, before the later task that makes them pass, cannot exit 0. A baseline captured after a write-mode formatter has already repaired pre-existing drift becomes either a blanket waiver or makes a later gate unsatisfiable. Read each acceptance condition against the state the plan will actually be in when its task runs.
215
+ - **Name the runner and job for a Pester acceptance criterion.** CI runs Pester in two jobs of `.github/workflows/_poshqc.yml`: `poshqc / PowerShell QC` runs every suite with coverage on `windows-latest`, and `poshqc / PowerShell hook suites (Linux)` runs only `tests/scripts/claude-hooks` and `tests/scripts/codex-hooks` on `ubuntu-latest`, with coverage disabled. A Pester acceptance criterion may cite `ubuntu-latest` only for suites under `tests/scripts/claude-hooks` or `tests/scripts/codex-hooks`, never for coverage, and must name the job it relies on (`poshqc / PowerShell QC` or `poshqc / PowerShell hook suites (Linux)`).
216
+ - **Do not assert a fixed-string search literal that contains a backslash.** Git for Windows grep 3.0 reads a doubled backslash in a `grep -F` pattern as one backslash, so the same check returns a different count locally than on a CI runner and cannot pass in both places. Assert a backslash-free substring of the literal, a named test, or a hash or byte-identity check instead.
215
217
 
216
218
  ## Plan-Path Continuity Contract (Mandatory)
217
219
 
@@ -128,9 +128,17 @@ The script emits pipe-delimited, `LC_ALL=C`-ordered records, one per line:
128
128
  per-file outcome is reported by the companion `ACTION|preserve-stage|...` record.
129
129
  - `ORPHAN_DIR|<path>|<size>` — a directory under a worktree-tracking root that carries
130
130
  no `.git` pointer file and no `git worktree list` entry. `<size>` is best-effort and
131
- may be the literal `unknown`. The record is advisory: it reports the directory, and
132
- nothing in apply mode acts on it. For the disposition, see the Dirty Worktree Triage
133
- Procedure's step 7, which governs how an orphaned directory is handled.
131
+ may be the literal `unknown`. The scanned roots are the default pair
132
+ `<main>/.claude/worktrees` and `<main>-wt`, or the `CLEANUP_WT_ORPHAN_ROOTS` entries
133
+ when that variable is set (the override replaces the default pair), plus the parent
134
+ directory of every non-main registered worktree, which is always added. A derived
135
+ parent is skipped when it is the main worktree or one of its ancestors, or is equal to
136
+ or inside a registered worktree. `CLEANUP_WT_ORPHAN_ROOTS` entries are separated by a
137
+ semicolon, a newline, or a colon; a colon that follows a single drive letter and
138
+ precedes `/` or `\` is part of the path, and empty or relative entries are dropped.
139
+ The record is advisory: it reports the directory, and nothing in apply mode acts on
140
+ it. For the disposition, see the Dirty Worktree Triage Procedure's step 7, which
141
+ governs how an orphaned directory is handled.
134
142
  - `STALE_REF|<refname>` — a `refs/remotes/<name>/*` ref whose `<name>` is not a
135
143
  configured remote, named in full ref form. Advisory only; no ref is ever pruned by
136
144
  this tool.
@@ -139,8 +139,17 @@ Environment overrides:
139
139
  nonexistent value is treated as missing (falls back to
140
140
  the bundled cleanup_worktrees_scan_helper.sh). This is
141
141
  the filesystem-scan test-stub seam.
142
- CLEANUP_WT_ORPHAN_ROOTS Colon-separated override for the worktree-tracking roots
143
- scanned for ORPHAN_DIR and WARN|registration-lost.
142
+ CLEANUP_WT_ORPHAN_ROOTS Worktree-tracking roots scanned for ORPHAN_DIR and
143
+ WARN|registration-lost. Entries are separated by a
144
+ semicolon, a newline, or a colon; a colon that follows
145
+ a single drive letter and precedes / or \ is part of
146
+ the path (C:/x). Empty and relative entries are
147
+ dropped. When set, it replaces the default pair
148
+ <main>/.claude/worktrees and <main>-wt. The parent of
149
+ every non-main registered worktree is always added,
150
+ except a parent that is the main worktree or its
151
+ ancestor, or is equal to or inside a registered
152
+ worktree.
144
153
  CLEANUP_WT_STUB_SCENARIO Scenario directory consumed by the checked-in git and
145
154
  scan stubs (tests only).
146
155
  CLEANUP_WT_CONSOLIDATION_PATH Override the derived consolidation worktree path