@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.
- package/out/mcp-server.js +5 -1
- package/package.json +21 -5
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/MEMORY.md +15 -3
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_branch_base_check_unmerged_pr_deps.md +16 -0
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_every_change_through_lifecycle.md +15 -0
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_policy_compliance_not_optional.md +18 -0
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_potential_to_issue_creates_github_issue.md +13 -0
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_remediation_plan_em_dash_required.md +13 -0
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_small_bug_uses_minor_audit.md +13 -0
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_test_files_count_against_500_cap.md +13 -0
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +7 -7
- package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +4 -5
- package/resources/claude-customizations/.claude/agents/feature-review.md +7 -3
- package/resources/claude-customizations/.claude/agents/orchestrator.md +16 -1
- package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +1 -1
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +245 -0
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +273 -0
- package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +148 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +190 -0
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +216 -0
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +84 -15
- package/resources/claude-customizations/.claude/hooks/validate-executor-output.ps1 +1 -1
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage.ps1 +75 -5
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +93 -0
- package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +68 -0
- package/resources/claude-customizations/.claude/rules/architecture-boundaries.md +46 -0
- package/resources/claude-customizations/.claude/rules/benchmark-baselines.md +35 -0
- package/resources/claude-customizations/.claude/rules/ci-workflows.md +36 -0
- package/resources/claude-customizations/.claude/rules/csharp.md +62 -16
- package/resources/claude-customizations/.claude/rules/general-code-change.md +12 -3
- package/resources/claude-customizations/.claude/rules/general-unit-test.md +47 -2
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +39 -0
- package/resources/claude-customizations/.claude/rules/powershell.md +5 -5
- package/resources/claude-customizations/.claude/rules/python.md +4 -3
- package/resources/claude-customizations/.claude/rules/quality-tiers.md +51 -0
- package/resources/claude-customizations/.claude/rules/typescript.md +37 -8
- package/resources/claude-customizations/.claude/settings.json +37 -12
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +2 -2
- package/resources/claude-customizations/.claude/skills/csharp-qa-gate/SKILL.md +25 -10
- package/resources/claude-customizations/.claude/skills/execute-hard-lock/SKILL.md +6 -6
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +8 -8
- package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +17 -6
- package/resources/claude-customizations/.claude/skills/human-exception-runbook/SKILL.md +52 -0
- package/resources/claude-customizations/.claude/skills/human-exception-runbook/example.runbook.md +36 -0
- package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +4 -4
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +96 -3
- package/resources/claude-customizations/.claude/skills/policy-audit-template-usage/SKILL.md +3 -3
- package/resources/claude-customizations/.claude/skills/powershell-qa-gate/SKILL.md +4 -4
- package/resources/claude-customizations/.claude/skills/pr-base-branch-merge-base/SKILL.md +3 -3
- package/resources/claude-customizations/.claude/skills/python-qa-gate/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +90 -17
- package/resources/claude-dir-customizations/.mcp.json +3 -3
- package/resources/codex-and-agents-customizations/.agents/README.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +52 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +16 -8
- package/resources/codex-and-agents-customizations/.agents/skills/benchmark-baselines/SKILL.md +44 -0
- package/resources/codex-and-agents-customizations/.agents/skills/ci-workflows/SKILL.md +45 -0
- package/resources/codex-and-agents-customizations/.agents/skills/commit-message/SKILL.md +3 -11
- package/resources/codex-and-agents-customizations/.agents/skills/csharp/SKILL.md +1 -5
- package/resources/codex-and-agents-customizations/.agents/skills/csharp-change-budget-router/SKILL.md +1 -6
- package/resources/codex-and-agents-customizations/.agents/skills/csharp-orchestration-state-machine/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/execute-hard-lock/SKILL.md +8 -17
- package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +13 -14
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +1 -6
- package/resources/codex-and-agents-customizations/.agents/skills/fill-feature-docs/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +86 -0
- package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +111 -0
- package/resources/codex-and-agents-customizations/.agents/skills/human-exception-runbook/SKILL.md +57 -0
- package/resources/codex-and-agents-customizations/.agents/skills/human-exception-runbook/example.runbook.md +36 -0
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-csharp-engineer/SKILL.md +0 -9
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +0 -9
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +0 -9
- package/resources/codex-and-agents-customizations/.agents/skills/make-skill-template/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +93 -8
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +48 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +61 -2
- package/resources/codex-and-agents-customizations/.agents/skills/policy-audit-template-usage/SKILL.md +3 -8
- package/resources/codex-and-agents-customizations/.agents/skills/policy-compliance-order/SKILL.md +0 -10
- package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +4 -8
- package/resources/codex-and-agents-customizations/.agents/skills/powershell-change-budget-router/SKILL.md +1 -6
- package/resources/codex-and-agents-customizations/.agents/skills/powershell-orchestration-state-machine/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/powershell-qa-gate/SKILL.md +3 -9
- package/resources/codex-and-agents-customizations/.agents/skills/pr-author/SKILL.md +1 -9
- package/resources/codex-and-agents-customizations/.agents/skills/pr-base-branch-merge-base/SKILL.md +4 -9
- package/resources/codex-and-agents-customizations/.agents/skills/pr-context-artifacts/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/python/SKILL.md +1 -5
- package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +1 -6
- package/resources/codex-and-agents-customizations/.agents/skills/python-qa-gate/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/python-suppressions/SKILL.md +2 -6
- package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +57 -0
- package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +91 -72
- package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/agents/openai.yaml +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/research-issue/SKILL.md +0 -10
- package/resources/codex-and-agents-customizations/.agents/skills/review-epic/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/review-feature/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/review-staged/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/self-explanatory-code-commenting/SKILL.md +2 -6
- package/resources/codex-and-agents-customizations/.agents/skills/skill-canonical-location-audit/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.agents/skills/tonality/SKILL.md +86 -0
- package/resources/codex-and-agents-customizations/.agents/skills/translate-claude-to-codex/SKILL.md +297 -0
- package/resources/codex-and-agents-customizations/.agents/skills/translate-copilot-to-claude/SKILL.md +0 -22
- package/resources/codex-and-agents-customizations/.agents/skills/typescript/SKILL.md +1 -5
- package/resources/codex-and-agents-customizations/.agents/skills/typescript-suppressions/SKILL.md +2 -6
- package/resources/codex-and-agents-customizations/.agents/skills/update-status/SKILL.md +0 -5
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +5 -5
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +91 -63
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-executor.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/config.toml +51 -136
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +1 -1
- package/resources/codex-and-agents-customizations/.codex/prompts/orchestrate-work.md +4 -3
- package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +5 -0
- package/resources/codex-and-agents-customizations/.github/workflows/_validate-orchestrator-state.yml +68 -0
- package/resources/codex-and-agents-customizations/.github/workflows/validate-orchestrator-state.yml +15 -0
- package/resources/config/orchestration-routing.json +84 -0
- package/resources/customizations/.github/agents/Powershell DI Unit Test Engineer.agent.md +1 -1
- package/resources/customizations/.github/agents/atomic_executor.agent.md +1 -1
- package/resources/customizations/.github/agents/atomic_planning.agent.md +10 -10
- package/resources/customizations/.github/agents/csharp-orchestrator.agent.md +6 -2
- package/resources/customizations/.github/agents/feature-review.agent.md +2 -2
- package/resources/customizations/.github/agents/orchestrator.agent.md +6 -2
- package/resources/customizations/.github/agents/powershell-atomic-executor.agent.md +4 -4
- package/resources/customizations/.github/agents/powershell-atomic-planning.agent.md +10 -10
- package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +6 -2
- package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +2 -2
- package/resources/customizations/.github/agents/python-orchestrator.agent.md +6 -2
- package/resources/customizations/.github/agents/staged-review.agent.md +1 -1
- package/resources/customizations/.github/instructions/powershell-code-change.instructions.md +6 -6
- package/resources/customizations/.github/prompts/generate-commit-message-repo.prompt.md +1 -1
- package/resources/customizations/.github/prompts/orchestrate-csharp-work.prompt.md +5 -3
- package/resources/customizations/.github/prompts/orchestrate-work.prompt.md +5 -3
- package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +14 -1
- package/resources/customizations/.github/skills/feature-promotion-lifecycle/SKILL.md +11 -7
- package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +10 -1
- package/resources/customizations/.github/skills/pr-base-branch-merge-base/SKILL.md +2 -2
- package/resources/customizations/.github/skills/remediation-handoff-atomic-planner/SKILL.md +5 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +7 -0
- package/resources/scripts/dev_tools/_orchestrator_state_human_interaction.py +127 -0
- package/resources/scripts/dev_tools/_orchestrator_state_routing.py +216 -0
- package/resources/scripts/dev_tools/push_down_claude_customizations.py +191 -5
- package/resources/scripts/dev_tools/validate_orchestration_artifacts.py +103 -411
- package/resources/scripts/dev_tools/validate_orchestration_review_artifacts.py +107 -0
- package/resources/scripts/dev_tools/validate_orchestrator_state.py +428 -0
- package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +448 -0
- package/resources/templates/push_down_claude_customizations.py +227 -6
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_repo_root_is_source_of_truth.md +0 -11
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_vsce_verify_package_location.md +0 -19
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/project_extension_location.md +0 -11
- package/resources/claude-customizations/.claude/agent-memory/prd-feature/MEMORY.md +0 -1
- package/resources/claude-customizations/.claude/agent-memory/prd-feature/project_push_down_pattern.md +0 -13
- package/resources/claude-customizations/.claude/agent-memory/task-researcher/MEMORY.md +0 -3
- 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
|
|
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
|
|
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
|
-
|
|
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.
|
|
75
|
+
System.String or $null.
|
|
50
76
|
#>
|
|
51
77
|
[CmdletBinding()]
|
|
52
|
-
[OutputType([
|
|
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
|
|
93
|
+
return (Get-PromotionMcpOnlyBlockedReason)
|
|
70
94
|
}
|
|
71
95
|
}
|
|
72
96
|
|
|
73
|
-
|
|
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 =
|
|
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
|
-
|
|
127
|
-
|
|
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 |
|
|
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', '
|
|
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
|
|
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
|
|
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
|
-
$
|
|
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
|
|