@danmoisan/drm-copilot-mcp 1.1.26 → 1.1.27

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 (159) hide show
  1. package/out/mcp-server.js +170 -52
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/atomic-executor.md +3 -3
  4. package/resources/claude-customizations/.claude/agents/atomic-planner.md +2 -2
  5. package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +7 -7
  6. package/resources/claude-customizations/.claude/agents/feature-review.md +6 -3
  7. package/resources/claude-customizations/.claude/agents/orchestrator.md +11 -7
  8. package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +7 -7
  9. package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +6 -6
  10. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-review.ps1 +699 -0
  11. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +16 -1
  12. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-rules.ps1 +50 -16
  13. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +6 -1
  14. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1 +4 -2
  15. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletionChecks.psm1 +51 -2
  16. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +5 -2
  17. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRemediationAccounting.psm1 +116 -4
  18. package/resources/claude-customizations/.claude/rules/csharp.md +8 -8
  19. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +28 -2
  20. package/resources/claude-customizations/.claude/rules/powershell.md +2 -2
  21. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +8 -12
  22. package/resources/claude-customizations/.claude/skills/csharp-qa-gate/SKILL.md +14 -15
  23. package/resources/claude-customizations/.claude/skills/epic-progress-report/SKILL.md +338 -0
  24. package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.Logic.ps1 +248 -0
  25. package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.Metrics.ps1 +232 -0
  26. package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.Render.ps1 +196 -0
  27. package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.State.ps1 +286 -0
  28. package/resources/claude-customizations/.claude/skills/epic-progress-report/scripts/Get-EpicProgressReport.ps1 +475 -0
  29. package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +18 -13
  30. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +21 -9
  31. package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +5 -5
  32. package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +4 -4
  33. package/resources/claude-customizations/.claude/skills/invoke-python-engineer/SKILL.md +4 -4
  34. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +24 -2
  35. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +8 -0
  36. package/resources/claude-customizations/.claude/skills/powershell-qa-gate/SKILL.md +16 -17
  37. package/resources/claude-customizations/.claude/skills/python-qa-gate/SKILL.md +15 -16
  38. package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +1 -1
  39. package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +6 -6
  40. package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +13 -14
  41. package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +4 -4
  42. package/resources/claude-customizations/pack-manifests/core.json +7 -0
  43. package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +8 -12
  44. package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +1 -1
  45. package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +13 -14
  46. package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +18 -13
  47. package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +5 -1
  48. package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +18 -5
  49. package/resources/codex-and-agents-customizations/.agents/skills/invoke-csharp-engineer/SKILL.md +4 -4
  50. package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +4 -4
  51. package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +4 -4
  52. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +24 -2
  53. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +16 -1
  54. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +3 -3
  55. package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +2 -2
  56. package/resources/codex-and-agents-customizations/.agents/skills/powershell-qa-gate/SKILL.md +15 -16
  57. package/resources/codex-and-agents-customizations/.agents/skills/python-qa-gate/SKILL.md +14 -15
  58. package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +2 -0
  59. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +13 -14
  60. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +4 -4
  61. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +3 -3
  62. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +3 -3
  63. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +3 -3
  64. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +3 -3
  65. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +3 -3
  66. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +3 -3
  67. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +2 -2
  68. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +2 -2
  69. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +2 -2
  70. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +2 -2
  71. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +2 -2
  72. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +2 -2
  73. package/resources/codex-and-agents-customizations/.codex/agents/csharp-atomic-executor.toml +2 -2
  74. package/resources/codex-and-agents-customizations/.codex/agents/csharp-atomic-planning.toml +1 -1
  75. package/resources/codex-and-agents-customizations/.codex/agents/csharp-orchestrator.toml +1 -1
  76. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +6 -6
  77. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +6 -6
  78. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +6 -6
  79. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +6 -6
  80. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +6 -6
  81. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +6 -6
  82. package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +2 -2
  83. package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-executor.toml +2 -2
  84. package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-planning.toml +1 -1
  85. package/resources/codex-and-agents-customizations/.codex/agents/powershell-di-unit-test-engineer.toml +1 -1
  86. package/resources/codex-and-agents-customizations/.codex/agents/powershell-orchestrator.toml +1 -1
  87. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +7 -7
  88. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +7 -7
  89. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +7 -7
  90. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +7 -7
  91. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +7 -7
  92. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +7 -7
  93. package/resources/codex-and-agents-customizations/.codex/agents/pytest-unit-test-coding.toml +1 -1
  94. package/resources/codex-and-agents-customizations/.codex/agents/python-atomic-executor.toml +2 -2
  95. package/resources/codex-and-agents-customizations/.codex/agents/python-atomic-planning.toml +1 -1
  96. package/resources/codex-and-agents-customizations/.codex/agents/python-execution-only-typed.toml +1 -1
  97. package/resources/codex-and-agents-customizations/.codex/agents/python-orchestrator.toml +1 -1
  98. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +6 -6
  99. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +6 -6
  100. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +6 -6
  101. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +6 -6
  102. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +6 -6
  103. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +6 -6
  104. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  105. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate-review.ps1 +699 -0
  106. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +29 -2
  107. package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +51 -4
  108. package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +6 -6
  109. package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
  110. package/resources/customizations/.github/agents/5.1-Beast-adjusted.agent.md +1 -1
  111. package/resources/customizations/.github/agents/Powershell DI Unit Test Engineer.agent.md +20 -26
  112. package/resources/customizations/.github/agents/atomic_executor.agent.md +4 -4
  113. package/resources/customizations/.github/agents/atomic_planning.agent.md +4 -4
  114. package/resources/customizations/.github/agents/commentary-remediation.agent.md +1 -1
  115. package/resources/customizations/.github/agents/commit-steward.agent.md +1 -1
  116. package/resources/customizations/.github/agents/csharp-atomic-executor.agent.md +14 -14
  117. package/resources/customizations/.github/agents/csharp-atomic-planning.agent.md +2 -2
  118. package/resources/customizations/.github/agents/csharp-orchestrator.agent.md +4 -4
  119. package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +19 -22
  120. package/resources/customizations/.github/agents/epic-review.agent.md +1 -1
  121. package/resources/customizations/.github/agents/gpt-5-beast-mode.agent.md +3 -3
  122. package/resources/customizations/.github/agents/orchestrator.agent.md +8 -8
  123. package/resources/customizations/.github/agents/powershell-atomic-executor.agent.md +12 -12
  124. package/resources/customizations/.github/agents/powershell-atomic-planning.agent.md +4 -4
  125. package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +6 -6
  126. package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +21 -24
  127. package/resources/customizations/.github/agents/pytest-unit-test-coding.agent.md +13 -17
  128. package/resources/customizations/.github/agents/python-atomic-executor.agent.md +11 -11
  129. package/resources/customizations/.github/agents/python-atomic-planning.agent.md +2 -2
  130. package/resources/customizations/.github/agents/python-execution-only-typed.agent.md +14 -17
  131. package/resources/customizations/.github/agents/python-orchestrator.agent.md +7 -7
  132. package/resources/customizations/.github/agents/python-typed-engineer.agent.md +17 -20
  133. package/resources/customizations/.github/agents/staged-review.agent.md +2 -2
  134. package/resources/customizations/.github/agents/tdd-green.agent.md +1 -1
  135. package/resources/customizations/.github/agents/tdd-red.agent.md +3 -3
  136. package/resources/customizations/.github/agents/tdd-refactor.agent.md +2 -2
  137. package/resources/customizations/.github/agents/typescript-engineer.agent.md +3 -3
  138. package/resources/customizations/.github/codex/execute-hard-lock.prompt.md +3 -3
  139. package/resources/customizations/.github/instructions/general-code-change.instructions.md +1 -1
  140. package/resources/customizations/.github/instructions/powershell-unit-test.instructions.md +1 -1
  141. package/resources/customizations/.github/instructions/python-code-change.instructions.md +5 -5
  142. package/resources/customizations/.github/instructions/python-suppressions.instructions.md +3 -3
  143. package/resources/customizations/.github/instructions/python-unit-test.instructions.md +1 -1
  144. package/resources/customizations/.github/prompts/execute-hard-lock.prompt.md +2 -2
  145. package/resources/customizations/.github/prompts/generate-commit-message-repo.prompt.md +2 -2
  146. package/resources/customizations/.github/prompts/remediate-comments.prompt.md +2 -2
  147. package/resources/customizations/.github/prompts/review-feature.prompt.md +1 -1
  148. package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +8 -12
  149. package/resources/customizations/.github/skills/execution-environment/SKILL.md +18 -13
  150. package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +15 -2
  151. package/resources/feature-templates/bug/plan.yyyy-MM-ddTHH-mm.md +4 -4
  152. package/resources/feature-templates/feature/plan.yyyy-MM-ddTHH-mm.md +3 -3
  153. package/resources/feature-templates/refactor/plan.yyyy-MM-ddTHH-mm.md +2 -2
  154. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +2 -2
  155. package/resources/powershell/PoshQC/PoshQC.psm1 +3 -3
  156. package/resources/powershell/PoshQC/README.md +8 -8
  157. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +2 -2
  158. package/resources/templates/policy_audit/AGENTS.md +9 -10
  159. package/resources/templates/policy_audit/policy-audit.yyyy-MM-ddTHH-mm.md +19 -20
@@ -45,7 +45,11 @@
45
45
  enqueues the pull request; only a pure --disable-auto is out of scope. The predicate and the
46
46
  PR selector reader live in enforce-epic-merge-gate-command.ps1.
47
47
 
48
- Design decision: branches 1-4 trust the on-disk checkpoint and never call gh. The green-CI
48
+ Design decision: without review_mode concurrent, branches 1-4 trust the on-disk checkpoint and
49
+ never call gh. Under that key the review guard (enforce-epic-merge-gate-review.ps1, issue #1097)
50
+ runs before branch 1 and makes one bounded gh pr view call and at most two git calls, and it
51
+ denies a merge with no passing review at a current head. Its shape check precedes the
52
+ out-of-scope allow and denies text that could be a merge and cannot be bound. The green-CI
49
53
  branch makes at most one bounded gh pr view call, only after its pure stages A to C pass.
50
54
  Its parallel exclusion is best-effort: the Parallel mode: true marker is in the delegation
51
55
  prompt, not in a Bash payload, so a parallel child that no checkpoint records yet is not
@@ -100,6 +104,8 @@ try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-command.ps1') } catch
100
104
  try { . (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1') } catch { Add-HookDependencyFailure -Name 'hook-docs-only-diff.ps1' -ErrorRecord $_ }
101
105
  # Green-CI standalone merge branch: pure checks, the live-head seam, and the stage orchestrator (issue #997).
102
106
  try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-green-ci.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-green-ci.ps1' -ErrorRecord $_ }
107
+ # Review guard under review_mode concurrent, byte-identical to the Codex copy (issue #1097).
108
+ try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-review.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-review.ps1' -ErrorRecord $_ }
103
109
 
104
110
  function ConvertFrom-EpicMergeGateJson {
105
111
  <#
@@ -349,6 +355,9 @@ function Invoke-EpicMergeGateDecision {
349
355
  # Scope (issue #952): every gh pr merge invocation except a pure --disable-auto, and every gh api
350
356
  # merge or enqueue mutation, is gated; Test-EpicMergeGateCommandInScope holds the predicate.
351
357
  if (-not (Test-EpicMergeGateCommandInScope -CommandText $commandText)) {
358
+ if (-not (Test-EpicMergeGateReviewMergeText -CommandText $commandText)) { return Get-EpicMergeGateAllowDecision }
359
+ $unscopedDecision = Get-EpicMergeGateReviewUnscopedDecision -CommandText $commandText
360
+ if ($unscopedDecision.Denied) { return Get-EpicMergeGateBlockDecision -Reason $unscopedDecision.Reason }
352
361
  return Get-EpicMergeGateAllowDecision
353
362
  }
354
363
 
@@ -366,6 +375,12 @@ function Invoke-EpicMergeGateDecision {
366
375
  $childRoot = if (@('SessionRoot', 'OtherWorktree') -contains $itemTarget.Status) { $itemTarget.WorktreeRoot } else { $null }
367
376
  }
368
377
  $childCheckpoint = if ($childRoot) { ConvertFrom-EpicMergeGateJson -Raw (Get-ChildOrchestratorCheckpointContent -Path (Get-WorktreeRunCheckpointPath -Kind item -WorktreeRoot $childRoot)) } else { $null }
378
+
379
+ # Review guard (issue #1097): inert without review_mode concurrent, so it precedes every allow branch.
380
+ $reviewDecision = Get-EpicMergeGateReviewShapeDecision -CommandText $commandText -KnownCheckpoint $childCheckpoint -KnownPrNumber $commandPrNumber -ReadCheckpoint ${function:Read-EpicMergeGateReviewItemCheckpoint}
381
+ if (-not $reviewDecision.Denied) { $reviewDecision = Get-EpicMergeGateReviewDecision -Checkpoint $childCheckpoint -CommandPrNumber $commandPrNumber -GhRoot $sessionRoot -GitRoot $childRoot }
382
+ if ($reviewDecision.Denied) { return Get-EpicMergeGateBlockDecision -Reason $reviewDecision.Reason }
383
+
369
384
  if ((Test-ChildCheckpointAllowsEpicMerge -Checkpoint $childCheckpoint) -and
370
385
  (Test-ChildCheckpointPrGateBinding -Checkpoint $childCheckpoint -CommandPrNumber $commandPrNumber)) {
371
386
  return Get-EpicMergeGateAllowDecision
@@ -1,12 +1,14 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- Artifact-path rules, changed-language set, and coverage-row rule for the feature-review coverage hook.
3
+ Artifact-path rules, changed-language set, narrowing-line reader, and coverage-row rule for the
4
+ feature-review coverage hook.
4
5
 
5
6
  .DESCRIPTION
6
7
  Dot-sourced by validate-feature-review-coverage.ps1. Defines the advertised-path reader
7
8
  (Get-ReviewArtifactPathFromOutput), the active-feature location rule (Get-ReviewArtifactInfo),
8
- the changed-language reader for the PR context summary (Get-ChangedLanguageSet), and the
9
- per-language coverage-row rule (Test-LanguageCoverageRow).
9
+ the changed-language reader for the PR context summary (Get-ChangedLanguageSet), the
10
+ scope-narrowing line reader (Get-CoverageNarrowingLine), and the per-language coverage-row
11
+ rule (Test-LanguageCoverageRow).
10
12
 
11
13
  The file has no param() block, writes nothing to the output stream, and runs no top-level
12
14
  statement other than function definitions.
@@ -95,6 +97,50 @@ function Get-ChangedLanguageSet {
95
97
  return $langs
96
98
  }
97
99
 
100
+ function Get-CoverageNarrowingLine {
101
+ [CmdletBinding()]
102
+ [OutputType([string])]
103
+ param(
104
+ [Parameter(Mandatory = $true)]
105
+ [AllowEmptyString()]
106
+ [string[]]$Lines,
107
+
108
+ [Parameter(Mandatory = $true)]
109
+ [string]$LabelPattern,
110
+
111
+ [Parameter(Mandatory = $true)]
112
+ [string]$Language
113
+ )
114
+
115
+ # Sanctioned narrowing text is not a scope narrowing by the audit: a line inside a
116
+ # '## Rejected Scope Narrowing' section (caller text recorded verbatim), and a PowerShell
117
+ # line naming branch coverage but not line coverage (Pester measures no branch coverage).
118
+ # The policy-audit template prescribes the no-pre-change-run record in three positions only,
119
+ # so the record is removed there and stays a narrowing form everywhere else.
120
+ $narrowingPattern = '(?i)(informational only|context only|out of plan scope|out of scope|not applicable|\bN/A\b|\bUNVERIFIED\b)'
121
+ $recordPatterns = @(
122
+ '(?<=(?:\bBaseline|\bbaseline coverage artifact):\s*)N/A - no pre-change run'
123
+ '(?<=^\s*\|(?:[^|]*\|){4}\s*)N/A - no pre-change run(?=\s*\|)'
124
+ '(?<!:\s*)`N/A - no pre-change run`'
125
+ )
126
+ $insideRejectedSection = $false
127
+ foreach ($line in $Lines) {
128
+ if ($line -match '^##\s') { $insideRejectedSection = $line -match '^##\s+Rejected Scope Narrowing\b' }
129
+ elseif ($insideRejectedSection) { continue }
130
+ if ($line -notmatch $LabelPattern -or $line -notmatch '(?i)(coverage|lcov|line[s]?\s+hit|pester)') { continue }
131
+ $reducedLine = $line
132
+ foreach ($recordPattern in $recordPatterns) { $reducedLine = [regex]::Replace($reducedLine, $recordPattern, '') }
133
+ # Tested on the original line, because the removals above strip the record it names.
134
+ if ($line.Contains('Baseline: N/A - no pre-change run')) {
135
+ $reducedLine = [regex]::Replace($reducedLine, '(?<![\w/-])Change: N/A', '')
136
+ $reducedLine = $reducedLine.Replace('New/changed-code coverage: N/A - no production file changed', '')
137
+ }
138
+ if ($reducedLine -notmatch $narrowingPattern) { continue }
139
+ if ($Language -eq 'PowerShell' -and $line -match '(?i)\bbranch\b' -and $line -notmatch '(?i)\bline\s+coverage\b') { continue }
140
+ $line
141
+ }
142
+ }
143
+
98
144
  function Test-LanguageCoverageRow {
99
145
  [OutputType([System.Collections.Hashtable])]
100
146
  param(
@@ -141,19 +187,7 @@ function Test-LanguageCoverageRow {
141
187
  }
142
188
  }
143
189
 
144
- # Sanctioned narrowing text is not a scope narrowing by the audit: a line inside a
145
- # '## Rejected Scope Narrowing' section (caller text recorded verbatim), and a PowerShell
146
- # line naming branch coverage but not line coverage (Pester measures no branch coverage).
147
- $narrowingPattern = '(?i)(informational only|context only|out of plan scope|out of scope|not applicable|\bN/A\b|\bUNVERIFIED\b)'
148
- $insideRejectedSection = $false
149
- $narrowing = foreach ($line in $lines) {
150
- if ($line -match '^##\s') { $insideRejectedSection = $line -match '^##\s+Rejected Scope Narrowing\b' }
151
- elseif ($insideRejectedSection) { continue }
152
- if ($line -notmatch $labelPattern -or $line -notmatch '(?i)(coverage|lcov|line[s]?\s+hit|pester)' -or $line -notmatch $narrowingPattern) { continue }
153
- if ($Language -eq 'PowerShell' -and $line -match '(?i)\bbranch\b' -and $line -notmatch '(?i)\bline\s+coverage\b') { continue }
154
- $line
155
- }
156
- $narrowing = @($narrowing)
190
+ $narrowing = @(Get-CoverageNarrowingLine -Lines $lines -LabelPattern $labelPattern -Language $Language)
157
191
  if ($narrowing.Count -gt 0) {
158
192
  $first = ($narrowing | Select-Object -First 1).ToString().Trim()
159
193
  return @{
@@ -297,7 +297,8 @@ function Get-OrchestratorStatePrCreationReadinessError {
297
297
  .DESCRIPTION
298
298
  Private readiness check mirroring validate_orchestrator_state_pr_creation_readiness in
299
299
  _orchestrator_state_pr_creation_readiness.py: steps 5-8 must not be pending, blocked, or
300
- blocked_remediation_loop_limit; blocked_reason must be `none` or absent; and the
300
+ blocked_remediation_loop_limit, except a pending step 7 under review_mode concurrent;
301
+ blocked_reason must be `none` or absent; and the
301
302
  local_execution_overrides / delegation_bypasses lists must be empty when present. It does
302
303
  not enforce completion, CI, PR, or routing-contract gates.
303
304
  .PARAMETER State
@@ -313,11 +314,15 @@ function Get-OrchestratorStatePrCreationReadinessError {
313
314
  )
314
315
 
315
316
  $errors = [System.Collections.Generic.List[string]]::new()
317
+ $stateNames = @($State.PSObject.Properties | ForEach-Object { $_.Name })
318
+ $reviewMode = if ($stateNames -ccontains 'review_mode') { (Get-OrchestratorStateField -State $State -Name 'review_mode').Value } else { $null }
316
319
 
317
320
  # Reject an upstream step recorded as pending, blocked, or blocked_remediation_loop_limit; steps
318
321
  # 5-8 must have finished before the first PR of a branch is created.
319
322
  foreach ($key in $script:PR_CREATION_READY_STEP_KEYS) {
320
323
  $field = Get-OrchestratorStateField -State $State -Name $key
324
+ # Under review_mode concurrent the review runs beside CI and the merge guard holds the merge.
325
+ if ($key -ceq 'step7_status' -and $field.Value -ceq 'pending' -and $reviewMode -is [string] -and $reviewMode -ceq 'concurrent') { continue }
321
326
  if ($field.Present -and (@('pending', 'blocked', 'blocked_remediation_loop_limit') -contains $field.Value)) {
322
327
  $errors.Add("Checkpoint PR-creation readiness validation failed: $key is $($field.Value).")
323
328
  }
@@ -23,6 +23,7 @@
23
23
  C5 mandatory route phases
24
24
  C6 the routing contract, from `OrchestratorStateRoutingContract.psm1`
25
25
  C7 the preparation terminal contract
26
+ C8 the review record under review_mode concurrent
26
27
  M1 model-routing receipt required once delegated
27
28
  M2 a complexity assessment for every matched receipt phase
28
29
  M3 per-entry re-validation of the U6.C and U6.M families
@@ -408,8 +409,8 @@ function Test-OrchestratorStateCompletionReadiness {
408
409
  $errors.AddRange([string[]]@(Get-OrchestratorStateUnconditionalError -State $state))
409
410
 
410
411
  # C family, in the reference's require_complete order: step statuses,
411
- # blocked_reason, pr_gate, ci_gate, phase completeness, routing contract, and
412
- # the preparation terminal contract.
412
+ # blocked_reason, pr_gate, ci_gate, phase completeness, routing contract, the
413
+ # preparation terminal contract, and the concurrent review record.
413
414
  $errors.AddRange([string[]]@(Get-OrchestratorStateCompletionStepStatusError -State $state))
414
415
  $errors.AddRange([string[]]@(Get-OrchestratorStateCompletionBlockedReasonError -State $state))
415
416
  $errors.AddRange([string[]]@(Get-OrchestratorStateCompletionPrGateError -State $state))
@@ -417,6 +418,7 @@ function Test-OrchestratorStateCompletionReadiness {
417
418
  $errors.AddRange([string[]]@(Get-OrchestratorStatePhaseCompletenessError -State $state))
418
419
  $errors.AddRange([string[]]@(Get-OrchestratorStateRoutingContractError -State $state))
419
420
  $errors.AddRange([string[]]@(Get-OrchestratorStatePreparationTerminalError -State $state))
421
+ $errors.AddRange([string[]]@(Get-OrchestratorStateCompletionConcurrentReviewError -State $state))
420
422
 
421
423
  # M family. The gate's M3 leg re-invokes the U6.C and U6.M validators, so its
422
424
  # output is merged under the PD-2 single-emission rule rather than appended
@@ -1,6 +1,6 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- Portable completion-gate checks C1, C2, C3, C4, C5, and C7.
3
+ Portable completion-gate checks C1, C2, C3, C4, C5, C7, and C8.
4
4
 
5
5
  .DESCRIPTION
6
6
  Destination-runtime PowerShell port of the `--require-complete` checks the
@@ -19,6 +19,7 @@
19
19
  C5.1 mandatory route phases, from a static map
20
20
  C7.1 preparation terminal next_step (value-gated, repr rendering)
21
21
  C7.2 preparation terminal step statuses (value-gated, repr rendering)
22
+ C8 last review outcome under review_mode concurrent (value-gated)
22
23
 
23
24
  Route gating for C3 and C4 is resolved through
24
25
  `OrchestratorStateRoutingMatrix.psm1`, implementing deviation PD-1: the gate
@@ -407,6 +408,53 @@ function Get-OrchestratorStatePreparationTerminalError {
407
408
  return $errors.ToArray()
408
409
  }
409
410
 
411
+ function Get-OrchestratorStateCompletionConcurrentReviewError {
412
+ <#
413
+ .SYNOPSIS
414
+ Return the review-record error under review_mode concurrent (row C8).
415
+ .DESCRIPTION
416
+ Mirrors validate_concurrent_review_completion. Only the exact string concurrent
417
+ activates the check, and only the final review_outcomes element counts. The head
418
+ error is reported only when the verdict error does not apply. The check does no
419
+ git work: head currency stays with the merge guard.
420
+ .PARAMETER State
421
+ The parsed checkpoint object.
422
+ .OUTPUTS
423
+ System.String[] - zero or one error string.
424
+ #>
425
+ [CmdletBinding()]
426
+ [OutputType([string[]])]
427
+ param(
428
+ [Parameter(Mandatory = $true)]
429
+ [psobject] $State
430
+ )
431
+
432
+ $mode = $null
433
+ if ((Get-CheckpointObjectMemberName -Owner $State) -ccontains 'review_mode') { $mode = (Get-CheckpointObjectMember -Owner $State -Name 'review_mode').Value }
434
+ if (-not (($mode -is [string]) -and ($mode -ceq 'concurrent'))) {
435
+ return [string[]]@()
436
+ }
437
+
438
+ # A missing loop, a non-list value, an empty list, and a non-object last entry all
439
+ # leave $last unset, so each reports the verdict error.
440
+ $last = $null
441
+ $loop = (Get-CheckpointObjectMember -Owner $State -Name 'remediation_loop').Value
442
+ $outcomes = (Get-CheckpointObjectMember -Owner $loop -Name 'review_outcomes').Value
443
+ if ((Test-CheckpointListValue -Value $outcomes) -and $outcomes.Count -gt 0) {
444
+ $last = $outcomes[$outcomes.Count - 1]
445
+ }
446
+ $verdict = (Get-CheckpointObjectMember -Owner $last -Name 'verdict').Value
447
+ if (-not (($verdict -is [string]) -and ($verdict -ceq 'PASS'))) {
448
+ return [string[]]@('Checkpoint completion validation failed: review_mode concurrent requires a last review_outcomes entry with verdict PASS.')
449
+ }
450
+
451
+ $head = (Get-CheckpointObjectMember -Owner $last -Name 'head_sha').Value
452
+ if (-not (($head -is [string]) -and ($head -cmatch '\A[0-9a-f]{40}([0-9a-f]{24})?\z'))) {
453
+ return [string[]]@('Checkpoint completion validation failed: review_mode concurrent requires head_sha on the last review_outcomes entry.')
454
+ }
455
+ return [string[]]@()
456
+ }
457
+
410
458
  # Each check family is exported individually so the completion entry point can
411
459
  # compose them in the Python reference's order; the gate-key helper stays private.
412
460
  Export-ModuleMember -Function `
@@ -415,4 +463,5 @@ Export-ModuleMember -Function `
415
463
  Get-OrchestratorStateCompletionPrGateError, `
416
464
  Get-OrchestratorStateCompletionCiGateError, `
417
465
  Get-OrchestratorStatePhaseCompletenessError, `
418
- Get-OrchestratorStatePreparationTerminalError
466
+ Get-OrchestratorStatePreparationTerminalError, `
467
+ Get-OrchestratorStateCompletionConcurrentReviewError
@@ -264,9 +264,12 @@ function Get-RemediationCycleError {
264
264
 
265
265
  # Invariant 2: an execution status in the blocked set requires the nested
266
266
  # preflight to report exactly the cleared status; a missing or non-object
267
- # preflight cannot satisfy it.
267
+ # preflight cannot satisfy it. An exact light cycle runs no preflight and is
268
+ # checked by invariant 8 instead.
268
269
  $executionStatus = (Get-CheckpointObjectMember -Owner $Cycle -Name 'execution_status').Value
269
- if ($executionStatus -is [string] -and
270
+ $remediationPath = (Get-CheckpointObjectMember -Owner $Cycle -Name 'remediation_path').Value
271
+ $isLightCycle = ($remediationPath -is [string]) -and ([string]$remediationPath -ceq 'light')
272
+ if (-not $isLightCycle -and $executionStatus -is [string] -and
270
273
  ($script:EXECUTION_STATUSES_REQUIRING_CLEAR_PREFLIGHT -contains [string]$executionStatus)) {
271
274
  $preflight = (Get-CheckpointObjectMember -Owner $Cycle -Name 'preflight').Value
272
275
  $preflightStatus = (Get-CheckpointObjectMember -Owner $preflight -Name 'final_status').Value
@@ -6,8 +6,10 @@
6
6
  Destination-runtime PowerShell port of the issue #484 invariants the
7
7
  authoritative Python validator applies to a checkpoint's remediation_loop:
8
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
9
+ rules R5, R6, R7a, R7b, and R11, and the light-remediation invariant 8
10
+ (issue #1097), matching the error-string templates in
11
+ `scripts/dev_tools/_orchestrator_state_remediation_loop.py`,
12
+ `scripts/dev_tools/_orchestrator_state_light_cycle.py`, and the rules
11
13
  document `.claude/rules/orchestrator-state.md`.
12
14
 
13
15
  The receipts module calls the exported entry point after its unchanged
@@ -40,6 +42,14 @@ $script:CANDIDATE_APPLIED_KEY = 'candidate_applied'
40
42
  $script:OPENED_BY_REVIEW_KEY = 'opened_by_review'
41
43
  $script:COMPLETED_EXECUTION_STATUS = 'complete'
42
44
 
45
+ # Invariant 8 (light remediation) keys and limits. Pinned to the Python constants
46
+ # in _orchestrator_state_light_cycle.py.
47
+ $script:REMEDIATION_PATH_KEY = 'remediation_path'
48
+ $script:LIGHT_REMEDIATION_PATH = 'light'
49
+ $script:FULL_REMEDIATION_PATH = 'full'
50
+ $script:LIGHT_FINDING_LIMIT = 3
51
+ $script:CONTAINED_SCOPE = 'contained'
52
+
43
53
  # Review verdicts and remediability classes in spec table order, and the two
44
54
  # derived class partitions. Pinned to the Python constants of the same names.
45
55
  $script:REVIEW_VERDICTS = @('PASS', 'REMEDIATION_REQUIRED', 'HALT_NON_REMEDIABLE', 'AWAITING_CI')
@@ -260,6 +270,106 @@ function Test-RemediationOpenedByReview {
260
270
  return ($verdict -is [string]) -and ([string]$verdict -ceq $script:REMEDIATION_REQUIRED_VERDICT)
261
271
  }
262
272
 
273
+ function Get-RemediationLightCycleError {
274
+ <#
275
+ .SYNOPSIS
276
+ Return the invariant 8 errors of every cycle that carries remediation_path.
277
+ .DESCRIPTION
278
+ Private helper mirroring validate_light_cycles (issue #1097). Per object cycle
279
+ whose key is present, in order: a value other than the exact string light or
280
+ full yields rule 1 alone; full yields nothing; light yields rule 2 (a preflight
281
+ key is present), rule 3 (opened_by_review is absent or fails the R11 predicate,
282
+ which skips rules 4 to 6), rule 4 (more than 3 findings; a non-list findings
283
+ value counts as empty), rule 5 per finding whose remediability is not exactly
284
+ autonomous, rule 6 per finding whose scope is not exactly contained, and rule 7
285
+ (the immediately prior element is an object whose remediation_path is exactly
286
+ light). Every string value is tested with -is [string] before -ceq, because
287
+ -ceq on an array filters rather than compares.
288
+ .PARAMETER Cycle
289
+ The loop's cycles list, or $null when cycles is absent or not a list.
290
+ .PARAMETER Outcomes
291
+ The raw review_outcomes value. May be $null.
292
+ .OUTPUTS
293
+ System.String[] - zero or more error strings.
294
+ #>
295
+ [CmdletBinding()]
296
+ [OutputType([string[]])]
297
+ param(
298
+ [Parameter(Mandatory = $true)]
299
+ [AllowNull()]
300
+ [AllowEmptyCollection()]
301
+ [object] $Cycle,
302
+
303
+ [Parameter(Mandatory = $true)]
304
+ [AllowNull()]
305
+ [object] $Outcomes
306
+ )
307
+
308
+ $errors = [System.Collections.Generic.List[string]]::new()
309
+ $cycleItems = @()
310
+ if ($null -ne $Cycle) { $cycleItems = @($Cycle) }
311
+
312
+ # Walk the cycles in order so each light cycle can see its immediate prior.
313
+ for ($index = 0; $index -lt $cycleItems.Count; $index++) {
314
+ $item = $cycleItems[$index]
315
+ if (-not (Test-CheckpointObjectValue -Value $item)) { continue }
316
+ $member = Get-CheckpointObjectMember -Owner $item -Name $script:REMEDIATION_PATH_KEY
317
+ if (-not $member.Present) { continue }
318
+
319
+ # Rule 1 routes by exact value: full has no further rule, and anything other
320
+ # than light is reported once and stops there.
321
+ $path = $member.Value
322
+ $isString = $path -is [string]
323
+ if ($isString -and ([string]$path -ceq $script:FULL_REMEDIATION_PATH)) { continue }
324
+ if (-not ($isString -and ([string]$path -ceq $script:LIGHT_REMEDIATION_PATH))) {
325
+ $errors.Add("Checkpoint remediation cycle #$index remediation_path must be light or full.")
326
+ continue
327
+ }
328
+
329
+ $prefix = "Checkpoint remediation cycle #$index remediation_path is light but"
330
+ if ((Get-CheckpointObjectMember -Owner $item -Name 'preflight').Present) {
331
+ $errors.Add("$prefix the cycle carries preflight.")
332
+ }
333
+
334
+ $opened = Get-CheckpointObjectMember -Owner $item -Name $script:OPENED_BY_REVIEW_KEY
335
+ if (-not $opened.Present -or -not (Test-RemediationOpenedByReview -OpenedByReview $opened.Value -Outcomes $Outcomes)) {
336
+ $errors.Add("$prefix opened_by_review does not name its opening review.")
337
+ } else {
338
+ $outcome = @($Outcomes)[[int]$opened.Value]
339
+ $findings = (Get-CheckpointObjectMember -Owner $outcome -Name 'findings').Value
340
+ $findingList = @()
341
+ if (Test-CheckpointListValue -Value $findings) { $findingList = @($findings) }
342
+ if ($findingList.Count -gt $script:LIGHT_FINDING_LIMIT) {
343
+ $errors.Add("$prefix its opening review has $($findingList.Count) findings; the limit is $($script:LIGHT_FINDING_LIMIT).")
344
+ }
345
+ # Report every finding a light cycle may not fix autonomously, then every
346
+ # finding whose fix is not contained; a non-object finding fails both.
347
+ foreach ($key in @('remediability', 'scope')) {
348
+ $required = if ($key -ceq 'remediability') { $script:REMEDIABLE_CLASS } else { $script:CONTAINED_SCOPE }
349
+ for ($findingIndex = 0; $findingIndex -lt $findingList.Count; $findingIndex++) {
350
+ $finding = $findingList[$findingIndex]
351
+ $value = $null
352
+ if (Test-CheckpointObjectValue -Value $finding) { $value = (Get-CheckpointObjectMember -Owner $finding -Name $key).Value }
353
+ if (-not (($value -is [string]) -and ([string]$value -ceq $required))) {
354
+ $suffix = if ($key -ceq 'remediability') { 'is not autonomous.' } else { 'scope is not contained.' }
355
+ $errors.Add("$prefix finding #$findingIndex $suffix")
356
+ }
357
+ }
358
+ }
359
+ }
360
+
361
+ # Rule 7: two light cycles may not run back to back.
362
+ if ($index -gt 0 -and (Test-CheckpointObjectValue -Value $cycleItems[$index - 1])) {
363
+ $priorPath = (Get-CheckpointObjectMember -Owner $cycleItems[$index - 1] -Name $script:REMEDIATION_PATH_KEY).Value
364
+ if (($priorPath -is [string]) -and ([string]$priorPath -ceq $script:LIGHT_REMEDIATION_PATH)) {
365
+ $errors.Add("$prefix the prior cycle is light.")
366
+ }
367
+ }
368
+ }
369
+
370
+ return $errors.ToArray()
371
+ }
372
+
263
373
  function Get-OrchestratorStateRemediationAccountingError {
264
374
  <#
265
375
  .SYNOPSIS
@@ -268,8 +378,9 @@ function Get-OrchestratorStateRemediationAccountingError {
268
378
  Public entry mirroring the issue #484 tail of _validate_remediation_loop.
269
379
  Returns, in order: the review-outcome errors (R8a; otherwise per outcome
270
380
  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.
381
+ cycle in order, R5, R6, and R11; then R7a, otherwise R7b; then invariant 8
382
+ through Get-RemediationLightCycleError. With none of the new keys present it
383
+ returns nothing.
273
384
  .PARAMETER RemediationLoop
274
385
  The deserialized remediation_loop object.
275
386
  .PARAMETER Cycle
@@ -329,6 +440,7 @@ function Get-OrchestratorStateRemediationAccountingError {
329
440
  }
330
441
  }
331
442
 
443
+ $errors.AddRange([string[]]@(Get-RemediationLightCycleError -Cycle $Cycle -Outcomes $outcomes))
332
444
  return $errors.ToArray()
333
445
  }
334
446
 
@@ -2,19 +2,19 @@
2
2
  paths:
3
3
  - "**/*.cs"
4
4
  - "**/*.csproj"
5
- description: C#-specific toolchain and coding standards (xUnit).
5
+ description: C#-specific toolchain and coding standards (MSTest).
6
6
  ---
7
7
 
8
8
  # C# Code Standards
9
9
 
10
- This rule file summarizes the C#-specific policies for this repository. It targets a host-neutral .NET foundation: xUnit, NSubstitute, FluentAssertions, `dotnet build`, the analyzer stack, `TimeProvider`, and uniform coverage thresholds.
10
+ This rule file summarizes the C#-specific policies for this repository. It targets a host-neutral .NET foundation: MSTest, NSubstitute, FluentAssertions, `dotnet build`, the analyzer stack, `TimeProvider`, and uniform coverage thresholds.
11
11
 
12
12
  ## Toolchain
13
13
 
14
14
  1. **Formatting — CSharpier**: All C# source files must be formatted with CSharpier. Do not use `dotnet format`. Command: `dotnet tool restore` followed by `dotnet csharpier check .` (or `dotnet csharpier .` to auto-format).
15
15
  2. **Linting — .NET Analyzers**: C# code must pass Roslyn/.NET analyzer diagnostics. Analyzer enforcement is centralized in `Directory.Build.props` (`AnalysisLevel=latest-all`, `AnalysisMode=All`, `TreatWarningsAsErrors=true`). Command: `dotnet build` runs analyzers as part of the build.
16
16
  3. **Type Checking — Nullable Analysis**: Nullable reference types are enabled solution-wide via `Directory.Build.props` (`Nullable=enable`, `TreatWarningsAsErrors=true`). Command: `dotnet build` enforces nullable warnings as errors.
17
- 4. **Testing — xUnit + NSubstitute + FluentAssertions**: Run tests with: `dotnet test --collect:"XPlat Code Coverage"`.
17
+ 4. **Testing — MSTest + NSubstitute + FluentAssertions**: Run tests with: `dotnet test --collect:"XPlat Code Coverage"`.
18
18
 
19
19
  Run the toolchain in order: format → lint → type-check → architecture → test. Restart from step 1 if any step fails or changes files.
20
20
 
@@ -31,11 +31,11 @@ Run the toolchain in order: format → lint → type-check → architecture →
31
31
 
32
32
  ## Testing Standards
33
33
 
34
- - Use **xUnit** as the test framework with `[Fact]` and `[Theory]` attributes.
35
- - Use **`[Theory]` + `[InlineData]`** for parameterized tests.
36
- - Use **`IClassFixture<T>`** to share expensive setup across tests within a class.
34
+ - Use **MSTest** (`Microsoft.VisualStudio.TestTools.UnitTesting`) as the test framework with `[TestClass]` and `[TestMethod]` attributes, on the VSTest runner (the `MSTest` package with `Microsoft.NET.Test.Sdk` and `coverlet.collector`).
35
+ - Use **`[TestMethod]` + `[DataRow]`** for parameterized tests.
36
+ - Use **`[ClassInitialize]`** to share expensive setup across tests within a class.
37
37
  - Use **NSubstitute** for test doubles. Example: `var sut = Substitute.For<IService>(); sut.Get().Returns(value);`.
38
- - Prefer **FluentAssertions** for assertions; use xUnit `Assert` only when FluentAssertions is not practical.
38
+ - Prefer **FluentAssertions** for assertions; use MSTest `Assert` only when FluentAssertions is not practical.
39
39
  - Follow Arrange–Act–Assert structure.
40
40
  - No external dependencies in unit tests.
41
41
 
@@ -53,7 +53,7 @@ Run the toolchain in order: format → lint → type-check → architecture →
53
53
 
54
54
  ### Golden Tests
55
55
 
56
- - **Verify.Xunit**: required for T1 classifier-output modules, tested against a versioned corpus.
56
+ - **Verify.MSTest**: required for T1 classifier-output modules, tested against a versioned corpus.
57
57
 
58
58
  ## Analyzer Stack
59
59
 
@@ -71,7 +71,7 @@ These invariants apply only when the checkpoint contains a top-level `remediatio
71
71
 
72
72
  1. **Non-empty `plan_path`.** Each cycle's `plan_path` must be a non-empty string. A missing value, a non-string value, or an empty/whitespace-only string is a malformed cycle.
73
73
 
74
- 2. **Execution requires cleared preflight.** A cycle's `execution_status` may be in `{in_progress, complete, failed}` only when that cycle's `preflight.final_status` is exactly `'clear'`. Any other preflight status with one of those execution statuses is a malformed cycle (execution was recorded before preflight cleared).
74
+ 2. **Execution requires cleared preflight.** A cycle's `execution_status` may be in `{in_progress, complete, failed}` only when that cycle's `preflight.final_status` is exactly `'clear'`. Any other preflight status with one of those execution statuses is a malformed cycle (execution was recorded before preflight cleared). The invariant does not apply to a cycle whose `remediation_path` is exactly the string `light`, compared ordinally, because a light cycle runs no preflight; invariant 8 governs that cycle.
75
75
 
76
76
  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).
77
77
 
@@ -83,6 +83,17 @@ These invariants apply only when the checkpoint contains a top-level `remediatio
83
83
 
84
84
  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.
85
85
 
86
+ 8. **Light remediation (issue #1097).** When a cycle object carries `remediation_path`, the rules below run in the accounting stage, after the R7 checks, per cycle in order, with byte-identical messages in `scripts/dev_tools/_orchestrator_state_light_cycle.py`, `extensions/drm-copilot/src/lib/validate/orchestrator-state-light-cycle.ts`, and `Get-RemediationLightCycleError` in `.claude/lib/orchestrator-state/OrchestratorStateRemediationAccounting.psm1`, pinned by the corpus `tests/fixtures/orchestrator_state_remediation_loop/`. A cycle without the key reaches none of them, so every existing checkpoint validates as before. `scope` on a finding is read only by these rules.
87
+ 1. A value other than exactly `light` or `full`, null included: `Checkpoint remediation cycle #N remediation_path must be light or full.` Nothing further runs for the cycle, and nothing further runs for `full`.
88
+ 2. A `preflight` key, whatever its value: `Checkpoint remediation cycle #N remediation_path is light but the cycle carries preflight.`
89
+ 3. `opened_by_review` absent, or not naming a `REMEDIATION_REQUIRED` outcome by the R11 predicate: `Checkpoint remediation cycle #N remediation_path is light but opened_by_review does not name its opening review.` Rules 4 to 6 are then skipped.
90
+ 4. More than 3 findings in that outcome (a non-list `findings` value counts as empty): `Checkpoint remediation cycle #N remediation_path is light but its opening review has M findings; the limit is 3.`
91
+ 5. Per finding K whose `remediability` is not exactly `autonomous`, a non-object finding included: `Checkpoint remediation cycle #N remediation_path is light but finding #K is not autonomous.`
92
+ 6. Per finding K whose `scope` is not exactly `contained`, absent included: `Checkpoint remediation cycle #N remediation_path is light but finding #K scope is not contained.`
93
+ 7. The immediately prior list element is an object whose `remediation_path` is exactly `light`: `Checkpoint remediation cycle #N remediation_path is light but the prior cycle is light.`
94
+
95
+ Route (D9): the installed MCP validator applies invariant 8 only after the extension release that carries it is installed. Until then, a Claude run validates a light checkpoint through the repository-local Python CLI, and on Codex, whose completion boundary is the installed MCP validator, the light path is unavailable.
96
+
86
97
  ## Invariants (remediation_loop.review_outcomes)
87
98
 
88
99
  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.
@@ -264,7 +275,7 @@ A standalone run (neither an epic child nor a parallel item) merges its own pull
264
275
  3. The per-feature checkpoint has `epic_mode` absent, `null`, or JSON `false`; carries no `parallel_mode: true` and no non-blank `parallel_slug`; records `step9_status` exactly `passed` and `ci_gate.conclusion` exactly `success`; records `pr_gate.pr_number` as a JSON integer equal to the command number (an authorization record does not substitute); records `ci_gate.head_sha` as a lowercase 40 or 64 character object id; and, when `pr_gate.head_sha` is present and not `null`, records it equal to `ci_gate.head_sha`.
265
276
  4. The live pull-request head from one bounded `gh pr view` call equals `ci_gate.head_sha`, and a `--match-head-commit` value, when present, equals the live head.
266
277
 
267
- `parallel_mode`, `parallel_slug`, and `pr_merge` are tolerated, unvalidated top-level checkpoint keys: no validator reads them. A parallel child records `parallel_mode: true` and `parallel_slug` at kickoff, and a standalone run records `pr_merge: { merge_commit_sha, target_branch, merged_at }` only after its merge is confirmed.
278
+ `parallel_mode`, `parallel_slug`, `pr_merge`, and `stage_timing` are tolerated, unvalidated top-level checkpoint keys: no validator reads them. A parallel child records `parallel_mode: true` and `parallel_slug` at kickoff, and a standalone run records `pr_merge: { merge_commit_sha, target_branch, merged_at }` only after its merge is confirmed. The orchestrator main session writes `stage_timing`, a list of `{ stage, attempt, started_at, ended_at }` entries (issue 1097) of wall-clock times in which `ended_at` is null while a stage is open, and commits the same list as `FEATURE/evidence/other/stage-timing.<timestamp>.json` before the merge. The merge stage is not captured. The writer protocol is in the orchestrate skill.
268
279
 
269
280
  Honest disclosure: the parallel exclusion is best-effort. The `Parallel mode: true` marker lives in the delegation prompt and is not present in a Bash `PreToolUse` payload, so a parallel child whose number and worktree path the parallel checkpoint has not yet recorded, and which omits the `parallel_mode` key, is not excluded by the hook. Like the authorization record above, the green-CI condition is a policy-level control, not a security control. A push after the live-head lookup can also change the head unless the command passes `--match-head-commit`, which the hook does not require.
270
281
 
@@ -317,6 +328,21 @@ A lifecycle record moved to `docs/features/potential/promoted/` satisfies the `p
317
328
 
318
329
  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. Rule 10 and rule 11 errors follow the rule 9 errors in the adoption error list, so a checkpoint that waives `new_active_feature_folder` without a valid record reports the missing-receipt line for each unreceipted required tool first, in `required_mcp_tools` order, and the rule 10 or rule 11 error after them. 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.
319
330
 
331
+ ## Concurrent-Review Scope and Backward Compatibility
332
+
333
+ These rules apply only when a checkpoint contains the top-level key `review_mode` with the exact string `concurrent`, compared ordinally. An absent key, `null`, `sequential`, a case variant, a number, and every other value mean sequential, and a checkpoint without the key validates exactly as before: no `gh` call, no new denial, and no readiness relaxation. The key is tolerated and is not a member of `REQUIRED_STATE_KEYS`. `.claude/skills/orchestrate/SKILL.md` (`## Concurrent Review and CI (review_mode)`) defines the run sequence.
334
+
335
+ - **`head_sha`.** Each `remediation_loop.review_outcomes[]` entry of a concurrent run carries `head_sha`: the head the review ran against, a full lowercase object id of 40 or 64 characters. Plain validation does not read it.
336
+ - **Last-entry semantics.** Only the final `review_outcomes[]` element counts, so an earlier `PASS` never satisfies a rule.
337
+ - **Readiness.** Under the key, PR-creation readiness stops rejecting `step7_status` for the value `pending` only. `blocked`, and every other step, keep today's result. TypeScript has no readiness port.
338
+ - **Completion.** Under the key, `require_complete` appends exactly one of these strings after the existing completion checks, byte-identical in `scripts/dev_tools/_orchestrator_state_concurrent_review.py`, `extensions/drm-copilot/src/lib/validate/orchestrator-state-concurrent-review.ts`, and `Get-OrchestratorStateCompletionConcurrentReviewError` in `.claude/lib/orchestrator-state/OrchestratorStateCompletionChecks.psm1`, and pinned by the corpus `tests/fixtures/orchestrator_state_concurrent_review/`. The second is reported only when the first does not apply. Validators do no git work.
339
+ - `Checkpoint completion validation failed: review_mode concurrent requires a last review_outcomes entry with verdict PASS.`
340
+ - `Checkpoint completion validation failed: review_mode concurrent requires head_sha on the last review_outcomes entry.`
341
+ - **Merge guard.** `.claude/hooks/enforce-epic-merge-gate-review.ps1`, byte-identical at `.codex/hooks/`, runs before every allow branch of both merge gates. It denies with `REVIEW_GATE_NOT_PASSED`, `REVIEW_GATE_HEAD_STALE`, or `REVIEW_GATE_HEAD_UNRESOLVED`, each after the token `EPIC_MERGE_GATE_BLOCKED`. A later head is current only when the reviewed head is its ancestor and every path changed since then lies inside `feature-folder`, so a code change after the review, including a merge of `origin/main`, is stale. A merge must be one plain `gh pr merge` of one number as its own Bash call: under the key, text that could be a merge and cannot be bound to one pull request is denied with `REVIEW_GATE_HEAD_UNRESOLVED`, before any process and before the out-of-scope allow of both gates.
342
+ - **Validator route (D9).** The TypeScript rule reaches the MCP surface only after an extension release. Until then, installed MCP accepts a checkpoint that the PowerShell stop hook and the Python CLI reject, so a concurrent checkpoint is checked through the Python CLI.
343
+
344
+ Honest disclosure (D10): the guard is a policy-level control, not a security control. A writer in the same session can edit the checkpoint. A merge outside the Bash hook surface is not gated. A merge whose item target does not resolve (`NoTarget` or `Ambiguous`), or whose child checkpoint does not parse, sees no key, and the parallel skill step is then the only backstop. A push after the lookup can change the head unless the command passes `--match-head-commit`. The `CI full run` requirement of the join is skill-level, because a docs-only head passes `CI Required Gate`. The authorization path reads no CI result, so the required `CI Required Gate` context is the control there. A text scan cannot read every merge: a script file performing the merge cannot be read, and neither can encoded or run-time composed text, a `gh` alias or shell function from an earlier call, or an endpoint held in a variable. A merge through a tool other than Bash is not gated: the Claude PowerShell tool and `mcp__*` tools, because the gate is registered on the `Bash` matcher only, and the Codex `shell_command` tool, because the gate is registered under `^Bash$` and returns for any other tool name. An already-queued auto-merge fires with no command, and `gh pr merge <PR> --disable-auto` removes it. A direct push to the base branch is not a pull-request merge, and the gate does not read it. A number-less merge text sees only the session checkpoint, so a key in another worktree is not found. The extra checkpoint read of the shape rule is best-effort: a failed read sees no key and keeps the decision of a run without the key. The extra read covers at most the first 4 numbers of the text, so a key held only by an item named after them is not found. Under the key the rule is fail-closed, so prose containing `pr merge` is denied in any command, including `git commit -m` and `grep`. The trigger is wider than `pr merge`: prose such as `pr (#12)` or `pr $HOME` is denied the same way, and a plain merge followed by a line feed or `;` is denied, because a bound merge must be the whole text. This trade is accepted because only concurrent runs carry the key and a merge is issued in its own call.
345
+
320
346
  ## Enforcement
321
347
 
322
348
  - `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.
@@ -15,7 +15,7 @@ This rule file summarizes the PowerShell-specific policies for this repository.
15
15
  1. **Formatting — Invoke-Formatter**: Format all PowerShell files via PoshQC. MCP command: `mcp__drm-copilot__run_poshqc_format`
16
16
  2. **Linting — PSScriptAnalyzer**: Run PoshQC analyzer with repo settings. MCP command: `mcp__drm-copilot__run_poshqc_analyze`. Optional autofix: `mcp__drm-copilot__run_poshqc_analyze_autofix`
17
17
  3. **Type checking**: Not applicable for PowerShell; skip to testing.
18
- 4. **Testing — Pester (v5.x)**: Run tests via MCP. MCP command: `mcp__drm-copilot__run_poshqc_test`. Use repo config at `scripts/powershell/PoshQC/settings/pester.runsettings.psd1`.
18
+ 4. **Testing — Pester (v6.x)**: Run tests via MCP. MCP command: `mcp__drm-copilot__run_poshqc_test`. Use repo config at `scripts/powershell/PoshQC/settings/pester.runsettings.psd1`.
19
19
 
20
20
  Run the toolchain in order: format → analyze → test. Restart from step 1 if any step fails or changes files. Use the MCP server functions; do not substitute VS Code task wrappers.
21
21
 
@@ -53,7 +53,7 @@ Introduce the smallest seam that enables reliable mocking. Apply these options i
53
53
 
54
54
  ## Testing Standards
55
55
 
56
- - Use **Pester** (v5.x) as the test framework.
56
+ - Use **Pester** (v6.x) as the test framework.
57
57
  - Organize tests to mirror code structure (e.g., `tests/scripts/dev-tools/ScriptName.Tests.ps1`).
58
58
  - Name test files `*.Tests.ps1`.
59
59
  - Use `Describe`/`Context`/`It` blocks; one behavior per `It`.