@danmoisan/drm-copilot-mcp 0.0.1 → 0.0.5

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 (156) hide show
  1. package/out/mcp-server.js +5 -1
  2. package/package.json +21 -5
  3. package/resources/claude-customizations/.claude/agent-memory/orchestrator/MEMORY.md +15 -3
  4. package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_branch_base_check_unmerged_pr_deps.md +16 -0
  5. package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_every_change_through_lifecycle.md +15 -0
  6. package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_policy_compliance_not_optional.md +18 -0
  7. package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_potential_to_issue_creates_github_issue.md +13 -0
  8. package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_remediation_plan_em_dash_required.md +13 -0
  9. package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_small_bug_uses_minor_audit.md +13 -0
  10. package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_test_files_count_against_500_cap.md +13 -0
  11. package/resources/claude-customizations/.claude/agents/atomic-executor.md +7 -7
  12. package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +4 -5
  13. package/resources/claude-customizations/.claude/agents/feature-review.md +7 -3
  14. package/resources/claude-customizations/.claude/agents/orchestrator.md +16 -1
  15. package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +1 -1
  16. package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +245 -0
  17. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +273 -0
  18. package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +148 -0
  19. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +190 -0
  20. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +216 -0
  21. package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +84 -15
  22. package/resources/claude-customizations/.claude/hooks/validate-executor-output.ps1 +1 -1
  23. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage.ps1 +75 -5
  24. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +93 -0
  25. package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +68 -0
  26. package/resources/claude-customizations/.claude/rules/architecture-boundaries.md +46 -0
  27. package/resources/claude-customizations/.claude/rules/benchmark-baselines.md +35 -0
  28. package/resources/claude-customizations/.claude/rules/ci-workflows.md +36 -0
  29. package/resources/claude-customizations/.claude/rules/csharp.md +62 -16
  30. package/resources/claude-customizations/.claude/rules/general-code-change.md +12 -3
  31. package/resources/claude-customizations/.claude/rules/general-unit-test.md +47 -2
  32. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +39 -0
  33. package/resources/claude-customizations/.claude/rules/powershell.md +5 -5
  34. package/resources/claude-customizations/.claude/rules/python.md +4 -3
  35. package/resources/claude-customizations/.claude/rules/quality-tiers.md +51 -0
  36. package/resources/claude-customizations/.claude/rules/typescript.md +37 -8
  37. package/resources/claude-customizations/.claude/settings.json +37 -12
  38. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +2 -2
  39. package/resources/claude-customizations/.claude/skills/csharp-qa-gate/SKILL.md +25 -10
  40. package/resources/claude-customizations/.claude/skills/execute-hard-lock/SKILL.md +6 -6
  41. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +8 -8
  42. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +17 -6
  43. package/resources/claude-customizations/.claude/skills/human-exception-runbook/SKILL.md +52 -0
  44. package/resources/claude-customizations/.claude/skills/human-exception-runbook/example.runbook.md +36 -0
  45. package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +4 -4
  46. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +96 -3
  47. package/resources/claude-customizations/.claude/skills/policy-audit-template-usage/SKILL.md +3 -3
  48. package/resources/claude-customizations/.claude/skills/powershell-qa-gate/SKILL.md +4 -4
  49. package/resources/claude-customizations/.claude/skills/pr-base-branch-merge-base/SKILL.md +3 -3
  50. package/resources/claude-customizations/.claude/skills/python-qa-gate/SKILL.md +1 -1
  51. package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +90 -17
  52. package/resources/claude-dir-customizations/.mcp.json +3 -3
  53. package/resources/codex-and-agents-customizations/.agents/README.md +1 -1
  54. package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +0 -5
  55. package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +52 -0
  56. package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +16 -8
  57. package/resources/codex-and-agents-customizations/.agents/skills/benchmark-baselines/SKILL.md +44 -0
  58. package/resources/codex-and-agents-customizations/.agents/skills/ci-workflows/SKILL.md +45 -0
  59. package/resources/codex-and-agents-customizations/.agents/skills/commit-message/SKILL.md +3 -11
  60. package/resources/codex-and-agents-customizations/.agents/skills/csharp/SKILL.md +1 -5
  61. package/resources/codex-and-agents-customizations/.agents/skills/csharp-change-budget-router/SKILL.md +1 -6
  62. package/resources/codex-and-agents-customizations/.agents/skills/csharp-orchestration-state-machine/SKILL.md +0 -5
  63. package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +0 -5
  64. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +0 -5
  65. package/resources/codex-and-agents-customizations/.agents/skills/execute-hard-lock/SKILL.md +8 -17
  66. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +13 -14
  67. package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +1 -6
  68. package/resources/codex-and-agents-customizations/.agents/skills/fill-feature-docs/SKILL.md +0 -5
  69. package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +86 -0
  70. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +111 -0
  71. package/resources/codex-and-agents-customizations/.agents/skills/human-exception-runbook/SKILL.md +57 -0
  72. package/resources/codex-and-agents-customizations/.agents/skills/human-exception-runbook/example.runbook.md +36 -0
  73. package/resources/codex-and-agents-customizations/.agents/skills/invoke-csharp-engineer/SKILL.md +0 -9
  74. package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +0 -9
  75. package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +0 -9
  76. package/resources/codex-and-agents-customizations/.agents/skills/make-skill-template/SKILL.md +0 -5
  77. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +93 -8
  78. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +48 -0
  79. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +61 -2
  80. package/resources/codex-and-agents-customizations/.agents/skills/policy-audit-template-usage/SKILL.md +3 -8
  81. package/resources/codex-and-agents-customizations/.agents/skills/policy-compliance-order/SKILL.md +0 -10
  82. package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +4 -8
  83. package/resources/codex-and-agents-customizations/.agents/skills/powershell-change-budget-router/SKILL.md +1 -6
  84. package/resources/codex-and-agents-customizations/.agents/skills/powershell-orchestration-state-machine/SKILL.md +0 -5
  85. package/resources/codex-and-agents-customizations/.agents/skills/powershell-qa-gate/SKILL.md +3 -9
  86. package/resources/codex-and-agents-customizations/.agents/skills/pr-author/SKILL.md +1 -9
  87. package/resources/codex-and-agents-customizations/.agents/skills/pr-base-branch-merge-base/SKILL.md +4 -9
  88. package/resources/codex-and-agents-customizations/.agents/skills/pr-context-artifacts/SKILL.md +0 -5
  89. package/resources/codex-and-agents-customizations/.agents/skills/python/SKILL.md +1 -5
  90. package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +1 -6
  91. package/resources/codex-and-agents-customizations/.agents/skills/python-qa-gate/SKILL.md +0 -5
  92. package/resources/codex-and-agents-customizations/.agents/skills/python-suppressions/SKILL.md +2 -6
  93. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +57 -0
  94. package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +0 -5
  95. package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +91 -72
  96. package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/agents/openai.yaml +1 -1
  97. package/resources/codex-and-agents-customizations/.agents/skills/research-issue/SKILL.md +0 -10
  98. package/resources/codex-and-agents-customizations/.agents/skills/review-epic/SKILL.md +0 -5
  99. package/resources/codex-and-agents-customizations/.agents/skills/review-feature/SKILL.md +0 -5
  100. package/resources/codex-and-agents-customizations/.agents/skills/review-staged/SKILL.md +0 -5
  101. package/resources/codex-and-agents-customizations/.agents/skills/self-explanatory-code-commenting/SKILL.md +2 -6
  102. package/resources/codex-and-agents-customizations/.agents/skills/skill-canonical-location-audit/SKILL.md +0 -5
  103. package/resources/codex-and-agents-customizations/.agents/skills/tonality/SKILL.md +86 -0
  104. package/resources/codex-and-agents-customizations/.agents/skills/translate-claude-to-codex/SKILL.md +297 -0
  105. package/resources/codex-and-agents-customizations/.agents/skills/translate-copilot-to-claude/SKILL.md +0 -22
  106. package/resources/codex-and-agents-customizations/.agents/skills/typescript/SKILL.md +1 -5
  107. package/resources/codex-and-agents-customizations/.agents/skills/typescript-suppressions/SKILL.md +2 -6
  108. package/resources/codex-and-agents-customizations/.agents/skills/update-status/SKILL.md +0 -5
  109. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +5 -5
  110. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +91 -63
  111. package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-executor.toml +1 -1
  112. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +1 -1
  113. package/resources/codex-and-agents-customizations/.codex/config.toml +51 -136
  114. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +1 -1
  115. package/resources/codex-and-agents-customizations/.codex/prompts/orchestrate-work.md +4 -3
  116. package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +5 -0
  117. package/resources/codex-and-agents-customizations/.github/workflows/_validate-orchestrator-state.yml +68 -0
  118. package/resources/codex-and-agents-customizations/.github/workflows/validate-orchestrator-state.yml +15 -0
  119. package/resources/config/orchestration-routing.json +84 -0
  120. package/resources/customizations/.github/agents/Powershell DI Unit Test Engineer.agent.md +1 -1
  121. package/resources/customizations/.github/agents/atomic_executor.agent.md +1 -1
  122. package/resources/customizations/.github/agents/atomic_planning.agent.md +10 -10
  123. package/resources/customizations/.github/agents/csharp-orchestrator.agent.md +6 -2
  124. package/resources/customizations/.github/agents/feature-review.agent.md +2 -2
  125. package/resources/customizations/.github/agents/orchestrator.agent.md +6 -2
  126. package/resources/customizations/.github/agents/powershell-atomic-executor.agent.md +4 -4
  127. package/resources/customizations/.github/agents/powershell-atomic-planning.agent.md +10 -10
  128. package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +6 -2
  129. package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +2 -2
  130. package/resources/customizations/.github/agents/python-orchestrator.agent.md +6 -2
  131. package/resources/customizations/.github/agents/staged-review.agent.md +1 -1
  132. package/resources/customizations/.github/instructions/powershell-code-change.instructions.md +6 -6
  133. package/resources/customizations/.github/prompts/generate-commit-message-repo.prompt.md +1 -1
  134. package/resources/customizations/.github/prompts/orchestrate-csharp-work.prompt.md +5 -3
  135. package/resources/customizations/.github/prompts/orchestrate-work.prompt.md +5 -3
  136. package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +14 -1
  137. package/resources/customizations/.github/skills/feature-promotion-lifecycle/SKILL.md +11 -7
  138. package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +10 -1
  139. package/resources/customizations/.github/skills/pr-base-branch-merge-base/SKILL.md +2 -2
  140. package/resources/customizations/.github/skills/remediation-handoff-atomic-planner/SKILL.md +5 -0
  141. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +7 -0
  142. package/resources/scripts/dev_tools/_orchestrator_state_human_interaction.py +127 -0
  143. package/resources/scripts/dev_tools/_orchestrator_state_routing.py +216 -0
  144. package/resources/scripts/dev_tools/push_down_claude_customizations.py +191 -5
  145. package/resources/scripts/dev_tools/validate_orchestration_artifacts.py +103 -411
  146. package/resources/scripts/dev_tools/validate_orchestration_review_artifacts.py +107 -0
  147. package/resources/scripts/dev_tools/validate_orchestrator_state.py +428 -0
  148. package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +448 -0
  149. package/resources/templates/push_down_claude_customizations.py +227 -6
  150. package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_repo_root_is_source_of_truth.md +0 -11
  151. package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_vsce_verify_package_location.md +0 -19
  152. package/resources/claude-customizations/.claude/agent-memory/orchestrator/project_extension_location.md +0 -11
  153. package/resources/claude-customizations/.claude/agent-memory/prd-feature/MEMORY.md +0 -1
  154. package/resources/claude-customizations/.claude/agent-memory/prd-feature/project_push_down_pattern.md +0 -13
  155. package/resources/claude-customizations/.claude/agent-memory/task-researcher/MEMORY.md +0 -3
  156. package/resources/claude-customizations/.claude/agent-memory/task-researcher/project_push_down_claude_dir.md +0 -11
@@ -0,0 +1,216 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Pre-tool-use hook that blocks atomic-planner delegations when the target
4
+ feature folder does not yet contain spec.md and user-story.md.
5
+
6
+ .DESCRIPTION
7
+ Invoked by the Claude Code PreToolUse hook on the Agent (Task) tool. Reads
8
+ tool input JSON from the CLAUDE_TOOL_INPUT environment variable. Activates
9
+ only when subagent_type is 'atomic-planner'.
10
+
11
+ Feature folder resolution order:
12
+ 1. Scan the prompt text for any path matching
13
+ docs/features/active/<token>, accepting both forward-slash and
14
+ backslash separators. The longest match wins; when it points at a
15
+ file (ends with .md), use its parent directory.
16
+ 2. If no candidate was found in the prompt, read the feature-folder field
17
+ from artifacts/orchestration/orchestrator-state.json.
18
+ 3. If neither yields a folder, block with a reason instructing the caller
19
+ to reference a feature folder explicitly.
20
+
21
+ Once the folder is resolved, the hook verifies that both spec.md and
22
+ user-story.md exist in that folder. If either is missing, the script blocks
23
+ with a reason naming the missing file(s) and instructing the orchestrator to
24
+ invoke prd-feature first.
25
+
26
+ Filesystem reads and orchestrator-state lookups go through wrapper functions
27
+ so tests can inject fakes without touching disk.
28
+
29
+ .NOTES
30
+ Compatible with PowerShell 7+. Read-only validation gate.
31
+ #>
32
+ [CmdletBinding()]
33
+ param()
34
+
35
+ function Get-PrdFeatureFileExistence {
36
+ <#
37
+ .SYNOPSIS
38
+ Wrapper around Test-Path for sibling-file existence checks. Tests mock this.
39
+ #>
40
+ [CmdletBinding()]
41
+ [OutputType([bool])]
42
+ param(
43
+ [Parameter(Mandatory)]
44
+ [string] $Path
45
+ )
46
+
47
+ return [bool](Test-Path -LiteralPath $Path -PathType Leaf)
48
+ }
49
+
50
+ function Get-PrdFeatureCheckpointFolder {
51
+ <#
52
+ .SYNOPSIS
53
+ Returns the feature-folder field from the orchestrator checkpoint, or
54
+ $null when the file or field is absent.
55
+ #>
56
+ [CmdletBinding()]
57
+ [OutputType([string])]
58
+ param(
59
+ [string] $CheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
60
+ )
61
+
62
+ if (-not (Test-Path -LiteralPath $CheckpointPath -PathType Leaf)) {
63
+ return $null
64
+ }
65
+
66
+ try {
67
+ $raw = Get-Content -LiteralPath $CheckpointPath -Raw -ErrorAction Stop
68
+ $obj = $raw | ConvertFrom-Json -ErrorAction Stop
69
+ }
70
+ catch {
71
+ return $null
72
+ }
73
+
74
+ if ($obj.PSObject.Properties.Name -contains 'feature-folder' -and $obj.'feature-folder') {
75
+ return [string]$obj.'feature-folder'
76
+ }
77
+ return $null
78
+ }
79
+
80
+ function Find-PrdFeatureFolderFromPrompt {
81
+ <#
82
+ .SYNOPSIS
83
+ Scans a prompt string for docs/features/active/<...> path tokens and
84
+ returns the longest unique match resolved to a folder path. Returns
85
+ $null when no match is found.
86
+ #>
87
+ [CmdletBinding()]
88
+ [OutputType([string])]
89
+ param(
90
+ [Parameter(Mandatory)]
91
+ [AllowEmptyString()]
92
+ [string] $Prompt
93
+ )
94
+
95
+ if (-not $Prompt) {
96
+ return $null
97
+ }
98
+
99
+ # Allow forward or backslash separators inside the matched path token.
100
+ $pattern = 'docs[\\/]+features[\\/]+active[\\/]+[^\s"''`]+'
101
+ $matchList = [regex]::Matches($Prompt, $pattern)
102
+ if ($matchList.Count -eq 0) {
103
+ return $null
104
+ }
105
+
106
+ $unique = @{}
107
+ foreach ($m in $matchList) {
108
+ $normalized = ($m.Value -replace '\\', '/').TrimEnd('/')
109
+ $unique[$normalized] = $true
110
+ }
111
+
112
+ $candidates = @(@($unique.Keys) | Sort-Object -Property Length -Descending)
113
+ $best = $candidates[0]
114
+
115
+ # If the longest match ends in .md, treat it as a file and use its parent.
116
+ if ($best -match '\.md$') {
117
+ $parent = $best -replace '/[^/]+\.md$', ''
118
+ return $parent
119
+ }
120
+
121
+ return $best
122
+ }
123
+
124
+ function Get-PrdFeatureMissingFile {
125
+ <#
126
+ .SYNOPSIS
127
+ Returns the list of required files (spec.md, user-story.md) missing in
128
+ the target folder.
129
+ #>
130
+ [CmdletBinding()]
131
+ [OutputType([string[]])]
132
+ param(
133
+ [Parameter(Mandatory)]
134
+ [string] $FeatureFolder
135
+ )
136
+
137
+ $required = @('spec.md', 'user-story.md')
138
+ [System.Collections.Generic.List[string]] $missing = [System.Collections.Generic.List[string]]::new()
139
+ foreach ($name in $required) {
140
+ $candidate = "$FeatureFolder/$name"
141
+ if (-not (Get-PrdFeatureFileExistence -Path $candidate)) {
142
+ $missing.Add($name)
143
+ }
144
+ }
145
+ return [string[]] $missing.ToArray()
146
+ }
147
+
148
+ function Invoke-PrdFeatureBeforePlannerDecision {
149
+ <#
150
+ .SYNOPSIS
151
+ Parses CLAUDE_TOOL_INPUT and returns an allow-or-block decision.
152
+ #>
153
+ [CmdletBinding()]
154
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
155
+ param(
156
+ [string] $ToolInputRaw
157
+ )
158
+
159
+ if (-not $ToolInputRaw) {
160
+ return [ordered]@{ decision = 'allow' }
161
+ }
162
+
163
+ try {
164
+ $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
165
+ }
166
+ catch {
167
+ throw "enforce-prd-feature-before-planner hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
168
+ }
169
+
170
+ $subagent = $toolInput.subagent_type
171
+ if (-not $subagent -or $subagent -ne 'atomic-planner') {
172
+ return [ordered]@{ decision = 'allow' }
173
+ }
174
+
175
+ $prompt = [string]$toolInput.prompt
176
+ $folder = Find-PrdFeatureFolderFromPrompt -Prompt $prompt
177
+ if (-not $folder) {
178
+ $folder = Get-PrdFeatureCheckpointFolder
179
+ }
180
+
181
+ if (-not $folder) {
182
+ return [ordered]@{
183
+ decision = 'block'
184
+ reason = "PRD_FEATURE_BLOCKED: atomic-planner delegation must reference a feature folder (either in the prompt or via orchestrator-state.json) so spec.md and user-story.md prerequisites can be verified."
185
+ }
186
+ }
187
+
188
+ $folderNormalized = ($folder -replace '\\', '/').TrimEnd('/')
189
+ $missing = Get-PrdFeatureMissingFile -FeatureFolder $folderNormalized
190
+ if ($missing.Count -eq 0) {
191
+ return [ordered]@{ decision = 'allow' }
192
+ }
193
+
194
+ $list = ($missing -join ', ')
195
+ return [ordered]@{
196
+ decision = 'block'
197
+ reason = "PRD_FEATURE_BLOCKED: cannot delegate to atomic-planner before prd-feature outputs are present in '$folderNormalized'. Missing: $list. Invoke the prd-feature subagent first."
198
+ }
199
+ }
200
+
201
+ # Guard allows dot-sourcing in tests without executing the entrypoint.
202
+ if ($MyInvocation.InvocationName -eq '.') {
203
+ return
204
+ }
205
+
206
+ try {
207
+ $decision = Invoke-PrdFeatureBeforePlannerDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
208
+ }
209
+ catch {
210
+ Write-Error $_
211
+ exit 1
212
+ }
213
+
214
+ $decision | ConvertTo-Json -Compress | Write-Output
215
+
216
+ exit 0
@@ -8,12 +8,18 @@
8
8
  variable, inspects the attempted command text, and blocks direct promotion
9
9
  script execution that would bypass the repository's MCP-only promotion path.
10
10
 
11
- Forbidden command tokens:
11
+ Forbidden command tokens (legacy promotion-script bypass):
12
12
  - new-potential-entry.ps1
13
13
  - new_potential_bug_entry
14
14
  - potential_to_issue
15
15
  - new_active_feature_folder
16
16
 
17
+ Forbidden gh-CLI patterns (raw GitHub issue creation bypass):
18
+ - gh issue create (with any flag suffix)
19
+ - gh issue new
20
+ - gh api against repos/<owner>/<repo>/issues with explicit POST method
21
+ (-X POST or --method POST)
22
+
17
23
  The hook is read-only: it inspects the attempted command and emits a JSON
18
24
  allow-or-block decision without mutating the command text.
19
25
 
@@ -23,12 +29,14 @@
23
29
  [CmdletBinding()]
24
30
  param()
25
31
 
26
- $script:PromotionMcpOnlyBlockedReason = 'PROMOTION_MCP_ONLY_BLOCKED: Direct Bash promotion-script execution is not allowed in agent sessions. Use the drmCopilotExtension MCP promotion tools instead.'
32
+ $script:PromotionMcpOnlyBlockedReason = 'PROMOTION_MCP_ONLY_BLOCKED: Direct Bash promotion-script execution is not allowed in agent sessions. Use the drm-copilot MCP promotion tools instead.'
33
+
34
+ $script:PromotionMcpOnlyGhIssueBlockedReason = 'PROMOTION_MCP_ONLY_BLOCKED: Direct GitHub issue creation via `gh` bypasses the approved drm-copilot MCP promotion path (`mcp__drm-copilot__new_potential_entry` -> `mcp__drm-copilot__potential_to_issue` -> `mcp__drm-copilot__new_active_feature_folder`). Use those MCP tools instead.'
27
35
 
28
36
  function Get-PromotionMcpOnlyBlockedReason {
29
37
  <#
30
38
  .SYNOPSIS
31
- Return the canonical deny message for promotion-script bypass attempts.
39
+ Return the canonical deny message for legacy promotion-script bypass attempts.
32
40
  .OUTPUTS
33
41
  System.String
34
42
  #>
@@ -39,24 +47,40 @@ function Get-PromotionMcpOnlyBlockedReason {
39
47
  return $script:PromotionMcpOnlyBlockedReason
40
48
  }
41
49
 
42
- function Test-PromotionBypassToken {
50
+ function Get-PromotionMcpOnlyGhIssueBlockedReason {
51
+ <#
52
+ .SYNOPSIS
53
+ Return the deny message for raw gh-CLI issue creation bypass attempts.
54
+ .OUTPUTS
55
+ System.String
56
+ #>
57
+ [CmdletBinding()]
58
+ [OutputType([string])]
59
+ param()
60
+
61
+ return $script:PromotionMcpOnlyGhIssueBlockedReason
62
+ }
63
+
64
+ function Get-PromotionBypassReason {
43
65
  <#
44
66
  .SYNOPSIS
45
- Return $true when a Bash command contains a forbidden promotion token.
67
+ Inspect the command text and return the specific deny reason, or $null when allowed.
68
+ .DESCRIPTION
69
+ Returns the legacy promotion-script reason when any forbidden token is present.
70
+ Returns the gh-CLI issue creation reason when a forbidden gh pattern is matched.
71
+ Returns $null when the command is allowed.
46
72
  .PARAMETER CommandText
47
73
  The Bash command text extracted from CLAUDE_TOOL_INPUT.
48
74
  .OUTPUTS
49
- System.Boolean
75
+ System.String or $null.
50
76
  #>
51
77
  [CmdletBinding()]
52
- [OutputType([bool])]
78
+ [OutputType([string])]
53
79
  param(
54
80
  [Parameter(Mandatory)]
55
81
  [string] $CommandText
56
82
  )
57
83
 
58
- # Inspect only the command text so the hook remains a narrow, non-mutating
59
- # policy gate for direct promotion-script bypass attempts.
60
84
  $forbiddenTokens = @(
61
85
  'new-potential-entry.ps1',
62
86
  'new_potential_bug_entry',
@@ -66,27 +90,71 @@ function Test-PromotionBypassToken {
66
90
 
67
91
  foreach ($token in $forbiddenTokens) {
68
92
  if ($CommandText.IndexOf($token, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) {
69
- return $true
93
+ return (Get-PromotionMcpOnlyBlockedReason)
70
94
  }
71
95
  }
72
96
 
73
- return $false
97
+ # `gh issue create` and `gh issue new` are direct bypasses of the MCP
98
+ # promotion path. Tolerate any flags after the subcommand.
99
+ if ($CommandText -match '(?i)\bgh\s+issue\s+(?:create|new)\b') {
100
+ return (Get-PromotionMcpOnlyGhIssueBlockedReason)
101
+ }
102
+
103
+ # `gh api repos/<owner>/<repo>/issues` is a write surface only when an
104
+ # explicit POST method is supplied. `gh api` defaults to GET, so we only
105
+ # block when -X POST or --method POST is present, to avoid false positives
106
+ # on issue read operations. Use a single regex with lookaheads against the
107
+ # whole command string.
108
+ $ghApiIssuesPostPattern = '(?i)(?=.*\bgh\s+api\b)(?=.*repos/[^/\s]+/[^/\s]+/issues(?:\b|/[^/\s]*$))(?=.*(?:-X\s+POST|--method\s+POST))'
109
+ if ($CommandText -match $ghApiIssuesPostPattern) {
110
+ return (Get-PromotionMcpOnlyGhIssueBlockedReason)
111
+ }
112
+
113
+ return $null
114
+ }
115
+
116
+ function Test-PromotionBypassToken {
117
+ <#
118
+ .SYNOPSIS
119
+ Return $true when a Bash command contains a forbidden promotion bypass pattern.
120
+ .PARAMETER CommandText
121
+ The Bash command text extracted from CLAUDE_TOOL_INPUT.
122
+ .OUTPUTS
123
+ System.Boolean
124
+ #>
125
+ [CmdletBinding()]
126
+ [OutputType([bool])]
127
+ param(
128
+ [Parameter(Mandatory)]
129
+ [string] $CommandText
130
+ )
131
+
132
+ return ($null -ne (Get-PromotionBypassReason -CommandText $CommandText))
74
133
  }
75
134
 
76
135
  function Get-PromotionMcpOnlyBlockDecision {
77
136
  <#
78
137
  .SYNOPSIS
79
138
  Construct the structured block decision for a forbidden Bash command.
139
+ .PARAMETER Reason
140
+ The specific deny reason to surface in the block decision. Defaults to the
141
+ legacy promotion-script reason for backward compatibility.
80
142
  .OUTPUTS
81
143
  System.Collections.Specialized.OrderedDictionary
82
144
  #>
83
145
  [CmdletBinding()]
84
146
  [OutputType([System.Collections.Specialized.OrderedDictionary])]
85
- param()
147
+ param(
148
+ [string] $Reason
149
+ )
150
+
151
+ if (-not $Reason) {
152
+ $Reason = Get-PromotionMcpOnlyBlockedReason
153
+ }
86
154
 
87
155
  return [ordered]@{
88
156
  decision = 'block'
89
- reason = (Get-PromotionMcpOnlyBlockedReason)
157
+ reason = $Reason
90
158
  }
91
159
  }
92
160
 
@@ -123,8 +191,9 @@ function Invoke-PromotionMcpOnlyDecision {
123
191
  return [ordered]@{ decision = 'allow' }
124
192
  }
125
193
 
126
- if (Test-PromotionBypassToken -CommandText $commandText) {
127
- return Get-PromotionMcpOnlyBlockDecision
194
+ $reason = Get-PromotionBypassReason -CommandText $commandText
195
+ if ($reason) {
196
+ return Get-PromotionMcpOnlyBlockDecision -Reason $reason
128
197
  }
129
198
 
130
199
  return [ordered]@{ decision = 'allow' }
@@ -262,7 +262,7 @@ function Invoke-ExecutorOutputValidation {
262
262
  return @{ Ok = $false; Message = 'atomic-executor hook: completion output must include an `AC Status Summary` section.' }
263
263
  }
264
264
 
265
- $hasCommandEvidence = $agentOutput -match '(?i)(Commands Run|Command[s]?:|poetry run |npx |pwsh |git |mcp__drmCopilotExtension__)'
265
+ $hasCommandEvidence = $agentOutput -match '(?i)(Commands Run|Command[s]?:|poetry run |npx |pwsh |git |mcp__drm-copilot__)'
266
266
  $hasStatusEvidence = $agentOutput -match '(?i)\b(PASS|FAIL)\b'
267
267
  if (-not $hasCommandEvidence -or -not $hasStatusEvidence) {
268
268
  return @{ Ok = $false; Message = 'atomic-executor hook: completion output must report commands run and pass/fail status results.' }
@@ -158,6 +158,66 @@ function Get-LcovRepoCoverage {
158
158
  return [math]::Round(($totalHit * 100.0) / $totalFound, 2)
159
159
  }
160
160
 
161
+ function Get-LcovBranchCoverage {
162
+ # Parses LCOV branch counters (BRF: branches found, BRH: branches hit) and returns a percent.
163
+ # LCOV emits one BRF/BRH line per source file in the report; we sum across the file to compute
164
+ # repo-wide branch coverage. Returns $null when the artifact is absent or when no branch
165
+ # information is recorded (BRF total = 0).
166
+ [OutputType([Nullable[double]])]
167
+ param([string]$Path)
168
+
169
+ $file = Get-ArtifactFileContent -Path $Path
170
+ if (-not $file.Exists) { return $null }
171
+
172
+ $totalFound = 0
173
+ $totalHit = 0
174
+ foreach ($line in $file.Lines) {
175
+ if ($line.StartsWith('BRF:')) {
176
+ $totalFound += [int]($line.Substring(4))
177
+ }
178
+ elseif ($line.StartsWith('BRH:')) {
179
+ $totalHit += [int]($line.Substring(4))
180
+ }
181
+ }
182
+ if ($totalFound -le 0) { return $null }
183
+ return [math]::Round(($totalHit * 100.0) / $totalFound, 2)
184
+ }
185
+
186
+ function Get-JacocoBranchCoverage {
187
+ [OutputType([Nullable[double]])]
188
+ param([string]$Path)
189
+
190
+ $file = Get-ArtifactFileContent -Path $Path
191
+ if (-not $file.Exists) { return $null }
192
+
193
+ [xml]$doc = $file.Text
194
+ $counters = $doc.SelectNodes('//counter[@type="BRANCH"]')
195
+ if (-not $counters -or $counters.Count -eq 0) { return $null }
196
+
197
+ $missed = 0
198
+ $covered = 0
199
+ foreach ($counter in $counters) {
200
+ $missed += [int]$counter.missed
201
+ $covered += [int]$counter.covered
202
+ }
203
+ $total = $missed + $covered
204
+ if ($total -le 0) { return $null }
205
+ return [math]::Round(($covered * 100.0) / $total, 2)
206
+ }
207
+
208
+ function Get-LanguageBranchCoverage {
209
+ [OutputType([Nullable[double]])]
210
+ param([string]$Language)
211
+
212
+ switch ($Language) {
213
+ 'TypeScript' { return Get-LcovBranchCoverage -Path 'coverage/lcov.info' }
214
+ 'Python' { return Get-LcovBranchCoverage -Path 'artifacts/python/lcov.info' }
215
+ 'PowerShell' { return Get-JacocoBranchCoverage -Path 'artifacts/pester/powershell-coverage.xml' }
216
+ 'CSharp' { return Get-JacocoBranchCoverage -Path 'artifacts/csharp/coverage.xml' }
217
+ }
218
+ return $null
219
+ }
220
+
161
221
  function Get-JacocoRepoCoverage {
162
222
  [OutputType([Nullable[double]])]
163
223
  param([string]$Path)
@@ -200,14 +260,15 @@ function Test-LanguageCoverageRow {
200
260
  param(
201
261
  [string]$AuditText,
202
262
  [string]$Language,
203
- [Nullable[double]]$RepoWidePct
263
+ [Nullable[double]]$RepoWidePct,
264
+ [Nullable[double]]$BranchPct
204
265
  )
205
266
 
206
267
  $languageLabelMap = @{
207
268
  'TypeScript' = @('TypeScript', 'typescript')
208
269
  'Python' = @('Python', 'python', 'pytest')
209
270
  'PowerShell' = @('PowerShell', 'powershell', 'pester')
210
- 'CSharp' = @('C#', 'CSharp', 'csharp', '\.NET', 'dotnet')
271
+ 'CSharp' = @('C#', 'CSharp', 'csharp', '.NET', 'dotnet')
211
272
  }
212
273
 
213
274
  $labels = $languageLabelMap[$Language]
@@ -249,16 +310,24 @@ function Test-LanguageCoverageRow {
249
310
  }
250
311
  }
251
312
 
252
- if ($null -ne $RepoWidePct -and $RepoWidePct -lt 80.0) {
313
+ if ($null -ne $RepoWidePct -and $RepoWidePct -lt 85.0) {
253
314
  $failLines = $coverageLines | Where-Object { $_ -match '\bFAIL\b' }
254
315
  if (-not $failLines -or $failLines.Count -eq 0) {
255
316
  return @{
256
317
  Ok = $false
257
- Reason = ("{0} repo-wide coverage is {1}% (below the 80% floor) but the policy-audit contains no FAIL verdict on a coverage row for {0}." -f $Language, $RepoWidePct)
318
+ Reason = ("{0} repo-wide coverage is {1}% (below the 85% line coverage floor) but the policy-audit contains no FAIL verdict on a coverage row for {0}." -f $Language, $RepoWidePct)
258
319
  }
259
320
  }
260
321
  }
261
322
 
323
+ $BranchFloor = 75.0
324
+ if ($null -ne $BranchPct -and $BranchPct -lt $BranchFloor) {
325
+ return @{
326
+ Ok = $false
327
+ Reason = ("{0} branch coverage is {1}% (below the 75% branch coverage floor); policy-audit must record FAIL on the corresponding coverage row." -f $Language, $BranchPct)
328
+ }
329
+ }
330
+
262
331
  return @{ Ok = $true; Reason = $null }
263
332
  }
264
333
 
@@ -362,7 +431,8 @@ function Invoke-FeatureReviewCoverageValidation {
362
431
  $coverageFailures = [System.Collections.Generic.List[string]]::new()
363
432
  foreach ($lang in $changedLanguages.Keys) {
364
433
  $repoPct = Get-LanguageRepoCoverage -Language $lang
365
- $result = Test-LanguageCoverageRow -AuditText $policyAuditText -Language $lang -RepoWidePct $repoPct
434
+ $branchPct = Get-LanguageBranchCoverage -Language $lang
435
+ $result = Test-LanguageCoverageRow -AuditText $policyAuditText -Language $lang -RepoWidePct $repoPct -BranchPct $branchPct
366
436
  if (-not $result.Ok) {
367
437
  $coverageFailures.Add($result.Reason)
368
438
  }
@@ -57,6 +57,90 @@ function Get-CheckpointFileContent {
57
57
  return @{ Exists = $true; Content = $content }
58
58
  }
59
59
 
60
+ function Test-HumanInteractionShape {
61
+ <#
62
+ .SYNOPSIS
63
+ Validates the optional human_interaction sub-object against the
64
+ invariants documented in .claude/rules/orchestrator-state.md and
65
+ enforced by scripts/dev_tools/validate_orchestrator_state.py,
66
+ enforcing the autonomous-execution mandate at the completion gate.
67
+ .DESCRIPTION
68
+ Returns a hashtable with keys:
69
+ - Ok: $true if the field is absent or every requirement is resolved.
70
+ - Message: rejection message naming the first unresolved requirement; $null on success.
71
+ A null human_interaction (absent key) passes the gate. When present,
72
+ the requirements array is inspected and DONE is blocked when, in order:
73
+ - requirements is missing or non-array.
74
+ - any requirement has a missing/blank response.
75
+ - any requirement has a response outside the enum
76
+ (scope_change | exception | halt).
77
+ - any requirement has response == 'halt'.
78
+ - any requirement has response == 'exception' with a missing/empty
79
+ runbook_path, or a runbook_path whose file does not exist on disk
80
+ (existence is checked through the injected FileExistsCheck seam).
81
+ FileExistsCheck is an injectable scriptblock so tests can exercise the
82
+ existence branch without writing temporary files. It defaults to
83
+ Test-Path -PathType Leaf.
84
+ #>
85
+ [CmdletBinding()]
86
+ [OutputType([hashtable])]
87
+ param(
88
+ [Parameter(Mandatory = $true)]
89
+ [AllowNull()]
90
+ $HumanInteraction,
91
+
92
+ [Parameter(Mandatory = $false)]
93
+ [scriptblock] $FileExistsCheck = { param($Path) Test-Path -LiteralPath $Path -PathType Leaf }
94
+ )
95
+
96
+ if ($null -eq $HumanInteraction) {
97
+ return @{ Ok = $true; Message = $null }
98
+ }
99
+
100
+ $hiProps = @($HumanInteraction.PSObject.Properties.Name)
101
+ if ($hiProps -notcontains 'requirements') {
102
+ return @{ Ok = $false; Message = "orchestrator hook: 'human_interaction' is present but 'requirements' array is missing." }
103
+ }
104
+
105
+ $requirements = @($HumanInteraction.requirements)
106
+ $allowedResponses = @('scope_change', 'exception', 'halt')
107
+
108
+ for ($i = 0; $i -lt $requirements.Count; $i++) {
109
+ $req = $requirements[$i]
110
+ $reqProps = @($req.PSObject.Properties.Name)
111
+
112
+ $response = $null
113
+ if ($reqProps -contains 'response') { $response = [string]$req.response }
114
+
115
+ if ([string]::IsNullOrWhiteSpace($response)) {
116
+ return @{ Ok = $false; Message = "orchestrator hook: 'human_interaction.requirements[$i]' has no resolved 'response'. Every unautomatable requirement must resolve to one of: scope_change, exception, halt." }
117
+ }
118
+
119
+ if ($allowedResponses -notcontains $response) {
120
+ return @{ Ok = $false; Message = "orchestrator hook: 'human_interaction.requirements[$i]' has 'response' value '$response' outside the allowed set (scope_change, exception, halt)." }
121
+ }
122
+
123
+ if ($response -eq 'halt') {
124
+ return @{ Ok = $false; Message = "orchestrator hook: 'human_interaction.requirements[$i]' has 'response' == 'halt'; DONE is blocked while a halt is present." }
125
+ }
126
+
127
+ if ($response -eq 'exception') {
128
+ $runbookPath = $null
129
+ if ($reqProps -contains 'runbook_path') { $runbookPath = [string]$req.runbook_path }
130
+
131
+ if ([string]::IsNullOrWhiteSpace($runbookPath)) {
132
+ return @{ Ok = $false; Message = "orchestrator hook: 'human_interaction.requirements[$i]' has 'response' == 'exception' but no non-empty 'runbook_path'. A permitted exception requires a runbook." }
133
+ }
134
+
135
+ if (-not (& $FileExistsCheck $runbookPath)) {
136
+ return @{ Ok = $false; Message = "orchestrator hook: 'human_interaction.requirements[$i]' references runbook_path '$runbookPath' but no file exists at that location." }
137
+ }
138
+ }
139
+ }
140
+
141
+ return @{ Ok = $true; Message = $null }
142
+ }
143
+
60
144
  function Invoke-OrchestratorOutputValidation {
61
145
  <#
62
146
  .SYNOPSIS
@@ -124,6 +208,15 @@ function Invoke-OrchestratorOutputValidation {
124
208
  return @{ Ok = $false; Message = "orchestrator hook: checkpoint file '$CheckpointPath' has an empty 'objective' field; orchestrator must record the active objective." }
125
209
  }
126
210
 
211
+ $humanInteraction = $null
212
+ if ($checkpointProps -contains 'human_interaction') {
213
+ $humanInteraction = $checkpoint.human_interaction
214
+ }
215
+ $hiResult = Test-HumanInteractionShape -HumanInteraction $humanInteraction
216
+ if (-not $hiResult.Ok) {
217
+ return @{ Ok = $false; Message = $hiResult.Message }
218
+ }
219
+
127
220
  return @{ Ok = $true; Message = $null }
128
221
  }
129
222