@danmoisan/drm-copilot-mcp 0.0.1 → 0.0.6
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 +98 -21
- 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 +22 -3
- package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +1 -1
- package/resources/claude-customizations/.claude/agents/pr-author.md +78 -0
- package/resources/claude-customizations/.claude/agents/task-researcher.md +10 -6
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +303 -0
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +300 -0
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +40 -14
- package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +148 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +210 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +333 -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-pr-author-output.ps1 +136 -0
- package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +90 -6
- 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 +59 -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/evidence-and-timestamp-conventions/SKILL.md +0 -1
- 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 +172 -5
- 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-customizations/.claude/skills/research-issue/SKILL.md +5 -3
- package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +69 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/rules/csharp.md +96 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +77 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +64 -0
- package/resources/claude-customizations/pack-manifests/core.json +65 -0
- package/resources/claude-customizations/pack-manifests/csharp-legacy.json +11 -0
- package/resources/claude-customizations/pack-manifests/csharp-modern.json +12 -0
- package/resources/claude-customizations/pack-manifests/powershell.json +14 -0
- package/resources/claude-customizations/pack-manifests/python.json +14 -0
- package/resources/claude-customizations/pack-manifests/typescript.json +9 -0
- 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 +41 -25
- 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 +206 -18
- 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 +94 -10
- 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 +96 -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 +111 -137
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +303 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +300 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +148 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +336 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +84 -18
- 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 +76 -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/pr-author.agent.md +25 -0
- 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/agents/task-researcher.agent.md +4 -4
- package/resources/customizations/.github/instructions/powershell-code-change.instructions.md +6 -6
- package/resources/customizations/.github/prompts/fillout-prd-feature.prompt.md +1 -1
- 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/prompts/research-issue.prompt.md +4 -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 +276 -61
- package/resources/scripts/dev_tools/push_down_claude_filesystem.py +472 -0
- package/resources/scripts/dev_tools/push_down_claude_pack_selection.py +401 -0
- 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 +505 -0
- package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +472 -0
- package/resources/templates/push_down_claude_customizations.py +162 -117
- 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,148 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Pre-tool-use hook that blocks writes to a feature folder's plan.md when issue.md,
|
|
4
|
+
spec.md, or user-story.md are not yet present in that same folder.
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
Invoked by the Claude Code PreToolUse hook on Write or Edit operations. Reads
|
|
8
|
+
tool input JSON from the CLAUDE_TOOL_INPUT environment variable. When the
|
|
9
|
+
target file_path matches a feature-folder plan.md path under
|
|
10
|
+
docs/features/(active|archive)/<folder>/plan.md, the script verifies that
|
|
11
|
+
each of issue.md, spec.md, and user-story.md exists in the same folder.
|
|
12
|
+
|
|
13
|
+
If any of the three sibling files is missing, the script emits a JSON
|
|
14
|
+
response with decision='block' and exits 0 so Claude Code surfaces the
|
|
15
|
+
reason. All other paths pass through with decision='allow'.
|
|
16
|
+
|
|
17
|
+
Filesystem reads go through Get-FeatureFolderFileExistence so tests can
|
|
18
|
+
inject a fake without touching disk.
|
|
19
|
+
|
|
20
|
+
.NOTES
|
|
21
|
+
Compatible with PowerShell 7+. Read-only validation gate; no state mutation.
|
|
22
|
+
#>
|
|
23
|
+
[CmdletBinding()]
|
|
24
|
+
param()
|
|
25
|
+
|
|
26
|
+
function Get-FeatureFolderFileExistence {
|
|
27
|
+
<#
|
|
28
|
+
.SYNOPSIS
|
|
29
|
+
Wrapper around Test-Path for sibling-file existence checks. Tests mock this.
|
|
30
|
+
.PARAMETER Path
|
|
31
|
+
Absolute or workspace-relative file path to check.
|
|
32
|
+
#>
|
|
33
|
+
[CmdletBinding()]
|
|
34
|
+
[OutputType([bool])]
|
|
35
|
+
param(
|
|
36
|
+
[Parameter(Mandatory)]
|
|
37
|
+
[string] $Path
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return [bool](Test-Path -LiteralPath $Path -PathType Leaf)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function Get-FeatureFolderMissingFile {
|
|
44
|
+
<#
|
|
45
|
+
.SYNOPSIS
|
|
46
|
+
Returns the list of required sibling files missing alongside plan.md.
|
|
47
|
+
.PARAMETER PlanFilePath
|
|
48
|
+
Normalized (forward-slash) path to the plan.md target.
|
|
49
|
+
#>
|
|
50
|
+
[CmdletBinding()]
|
|
51
|
+
[OutputType([string[]])]
|
|
52
|
+
param(
|
|
53
|
+
[Parameter(Mandatory)]
|
|
54
|
+
[string] $PlanFilePath
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
$folder = $PlanFilePath -replace '/plan\.md$', ''
|
|
58
|
+
$required = @('issue.md', 'spec.md', 'user-story.md')
|
|
59
|
+
[System.Collections.Generic.List[string]] $missing = [System.Collections.Generic.List[string]]::new()
|
|
60
|
+
|
|
61
|
+
foreach ($name in $required) {
|
|
62
|
+
$siblingPath = "$folder/$name"
|
|
63
|
+
if (-not (Get-FeatureFolderFileExistence -Path $siblingPath)) {
|
|
64
|
+
$missing.Add($name)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return [string[]] $missing.ToArray()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function Test-IsFeaturePlanPath {
|
|
72
|
+
<#
|
|
73
|
+
.SYNOPSIS
|
|
74
|
+
Returns $true if the normalized path targets a feature-folder plan.md.
|
|
75
|
+
#>
|
|
76
|
+
[CmdletBinding()]
|
|
77
|
+
[OutputType([bool])]
|
|
78
|
+
param(
|
|
79
|
+
[Parameter(Mandatory)]
|
|
80
|
+
[string] $NormalizedPath
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return $NormalizedPath -match '(^|/)docs/features/(active|archive)/[^/]+/plan\.md$'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function Invoke-FeatureFolderOrderDecision {
|
|
87
|
+
<#
|
|
88
|
+
.SYNOPSIS
|
|
89
|
+
Parses CLAUDE_TOOL_INPUT and produces an allow-or-block decision.
|
|
90
|
+
.PARAMETER ToolInputRaw
|
|
91
|
+
Raw JSON string. Empty/null returns allow.
|
|
92
|
+
#>
|
|
93
|
+
[CmdletBinding()]
|
|
94
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
95
|
+
param(
|
|
96
|
+
[string] $ToolInputRaw
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if (-not $ToolInputRaw) {
|
|
100
|
+
return [ordered]@{ decision = 'allow' }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
$toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
throw "enforce-feature-folder-order hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
$filePath = $toolInput.file_path
|
|
111
|
+
if (-not $filePath) {
|
|
112
|
+
return [ordered]@{ decision = 'allow' }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
$normalized = $filePath -replace '\\', '/'
|
|
116
|
+
|
|
117
|
+
if (-not (Test-IsFeaturePlanPath -NormalizedPath $normalized)) {
|
|
118
|
+
return [ordered]@{ decision = 'allow' }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
$missing = Get-FeatureFolderMissingFile -PlanFilePath $normalized
|
|
122
|
+
if ($missing.Count -eq 0) {
|
|
123
|
+
return [ordered]@{ decision = 'allow' }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
$list = ($missing -join ', ')
|
|
127
|
+
return [ordered]@{
|
|
128
|
+
decision = 'block'
|
|
129
|
+
reason = "FEATURE_FOLDER_ORDER_BLOCKED: cannot write plan.md before producing prerequisite documents. Missing in feature folder: $list. Invoke the prd-feature subagent to generate the missing file(s) before authoring plan.md."
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Guard allows dot-sourcing in tests without executing the entrypoint.
|
|
134
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
$decision = Invoke-FeatureFolderOrderDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
Write-Error $_
|
|
143
|
+
exit 1
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
$decision | ConvertTo-Json -Compress | Write-Output
|
|
147
|
+
|
|
148
|
+
exit 0
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Blocks implementation operations before orchestration readiness exists.
|
|
4
|
+
#>
|
|
5
|
+
[CmdletBinding()]
|
|
6
|
+
param()
|
|
7
|
+
|
|
8
|
+
$script:CheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
|
|
9
|
+
$script:Issue232FeatureFolder = 'docs/features/active/2026-06-24-harden-orchestrate-skill-232'
|
|
10
|
+
|
|
11
|
+
function ConvertFrom-CheckpointJson {
|
|
12
|
+
[CmdletBinding()]
|
|
13
|
+
param([Parameter(Mandatory)][string] $Json)
|
|
14
|
+
|
|
15
|
+
return $Json | ConvertFrom-Json -ErrorAction Stop
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function Get-StringProperty {
|
|
19
|
+
[CmdletBinding()]
|
|
20
|
+
[OutputType([string])]
|
|
21
|
+
param(
|
|
22
|
+
[Parameter(Mandatory)][AllowNull()] $Value,
|
|
23
|
+
[Parameter(Mandatory)][string] $Name
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if ($null -eq $Value -or -not ($Value.PSObject.Properties.Name -contains $Name)) {
|
|
27
|
+
return ''
|
|
28
|
+
}
|
|
29
|
+
return ([string]$Value.$Name).Trim()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function Test-FeatureDocumentationOrEvidencePath {
|
|
33
|
+
[CmdletBinding()]
|
|
34
|
+
[OutputType([bool])]
|
|
35
|
+
param([Parameter(Mandatory)][string] $NormalizedPath)
|
|
36
|
+
|
|
37
|
+
return $NormalizedPath.StartsWith('docs/features/active/')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function Test-ImplementationPath {
|
|
41
|
+
[CmdletBinding()]
|
|
42
|
+
[OutputType([bool])]
|
|
43
|
+
param([Parameter(Mandatory)][string] $NormalizedPath)
|
|
44
|
+
|
|
45
|
+
if (Test-FeatureDocumentationOrEvidencePath -NormalizedPath $NormalizedPath) {
|
|
46
|
+
return $false
|
|
47
|
+
}
|
|
48
|
+
if ($NormalizedPath -eq $script:CheckpointPath) {
|
|
49
|
+
return $false
|
|
50
|
+
}
|
|
51
|
+
return $NormalizedPath -match '\.(py|ps1|psm1|ts|tsx|js|jsx|cs|json|yml|yaml)$'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function Test-ImplementationCommand {
|
|
55
|
+
[CmdletBinding()]
|
|
56
|
+
[OutputType([bool])]
|
|
57
|
+
param([Parameter(Mandatory)][string] $Command)
|
|
58
|
+
|
|
59
|
+
$normalizedCommand = $Command.Trim()
|
|
60
|
+
if (-not $normalizedCommand) {
|
|
61
|
+
return $false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
$implementationCommandPatterns = @(
|
|
65
|
+
'(^|\s)git\s+(add|commit)\b',
|
|
66
|
+
'(^|\s)(poetry\s+run\s+)?(black|ruff|pyright|pytest)\b',
|
|
67
|
+
'(^|\s)npm\s+.*\s+(prettier|lint|typecheck|test:unit)\b',
|
|
68
|
+
'(^|\s)npx\s+(prettier|eslint|tsc|jest)\b',
|
|
69
|
+
'(^|\s)pwsh\s+.*(Invoke-Pester|tests/scripts/)'
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
foreach ($pattern in $implementationCommandPatterns) {
|
|
73
|
+
if ($normalizedCommand -match $pattern) {
|
|
74
|
+
return $true
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return $false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function Test-ImplementationDelegation {
|
|
81
|
+
[CmdletBinding()]
|
|
82
|
+
[OutputType([bool])]
|
|
83
|
+
param([Parameter(Mandatory)][AllowNull()] $ToolInput)
|
|
84
|
+
|
|
85
|
+
if ($null -eq $ToolInput) {
|
|
86
|
+
return $false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
$payloadText = ($ToolInput | ConvertTo-Json -Depth 20 -Compress)
|
|
90
|
+
return $payloadText -match '(python-typed-engineer|powershell-typed-engineer|typescript-engineer|csharp-typed-engineer|atomic-executor|implementation|execute)'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function Test-OrchestrationReady {
|
|
94
|
+
[CmdletBinding()]
|
|
95
|
+
[OutputType([bool])]
|
|
96
|
+
param([Parameter(Mandatory)][AllowNull()] $Payload)
|
|
97
|
+
|
|
98
|
+
if ($null -eq $Payload) {
|
|
99
|
+
return $false
|
|
100
|
+
}
|
|
101
|
+
$issueNum = Get-StringProperty -Value $Payload -Name 'issue-num'
|
|
102
|
+
$featureFolder = Get-StringProperty -Value $Payload -Name 'feature-folder'
|
|
103
|
+
$routeId = Get-StringProperty -Value $Payload -Name 'route_id'
|
|
104
|
+
if (-not $routeId) {
|
|
105
|
+
$routeId = Get-StringProperty -Value $Payload -Name 'path_selected'
|
|
106
|
+
}
|
|
107
|
+
$lifecycleReady = $false
|
|
108
|
+
if ($Payload.PSObject.Properties.Name -contains 'lifecycle_ready') {
|
|
109
|
+
$lifecycleReady = [bool]$Payload.lifecycle_ready
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (-not $issueNum -or -not $featureFolder -or -not $routeId -or -not $lifecycleReady) {
|
|
113
|
+
return $false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if ($issueNum -eq '232' -and $featureFolder -ne $script:Issue232FeatureFolder) {
|
|
117
|
+
return $false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
$featureFolder.StartsWith('docs/features/active/') -and
|
|
122
|
+
$routeId -and
|
|
123
|
+
$lifecycleReady
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function Get-CheckpointContent {
|
|
128
|
+
[CmdletBinding()]
|
|
129
|
+
[OutputType([string])]
|
|
130
|
+
param()
|
|
131
|
+
|
|
132
|
+
if (-not (Test-Path -LiteralPath $script:CheckpointPath)) {
|
|
133
|
+
return ''
|
|
134
|
+
}
|
|
135
|
+
return Get-Content -Raw -LiteralPath $script:CheckpointPath
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function Invoke-OrchestrationPreimplementationGateDecision {
|
|
139
|
+
[CmdletBinding()]
|
|
140
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
141
|
+
param(
|
|
142
|
+
[string] $ToolInputRaw,
|
|
143
|
+
[string] $CheckpointRaw
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if (-not $ToolInputRaw) {
|
|
147
|
+
return [ordered]@{ decision = 'allow' }
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
$toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
|
|
151
|
+
} catch {
|
|
152
|
+
throw "enforce-orchestration-preimplementation-gate hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
$requiresReadyCheckpoint = $false
|
|
156
|
+
$filePath = Get-StringProperty -Value $toolInput -Name 'file_path'
|
|
157
|
+
if ($filePath) {
|
|
158
|
+
$normalized = ([string]$filePath) -replace '\\', '/'
|
|
159
|
+
$requiresReadyCheckpoint = Test-ImplementationPath -NormalizedPath $normalized
|
|
160
|
+
} else {
|
|
161
|
+
$command = Get-StringProperty -Value $toolInput -Name 'command'
|
|
162
|
+
if ($command) {
|
|
163
|
+
$requiresReadyCheckpoint = Test-ImplementationCommand -Command $command
|
|
164
|
+
} else {
|
|
165
|
+
$requiresReadyCheckpoint = Test-ImplementationDelegation -ToolInput $toolInput
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (-not $requiresReadyCheckpoint) {
|
|
170
|
+
return [ordered]@{ decision = 'allow' }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (-not $CheckpointRaw) {
|
|
174
|
+
$CheckpointRaw = Get-CheckpointContent
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
$checkpoint = ConvertFrom-CheckpointJson -Json $CheckpointRaw
|
|
178
|
+
} catch {
|
|
179
|
+
$checkpoint = $null
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (Test-OrchestrationReady -Payload $checkpoint) {
|
|
183
|
+
return [ordered]@{ decision = 'allow' }
|
|
184
|
+
}
|
|
185
|
+
$issueNum = Get-StringProperty -Value $checkpoint -Name 'issue-num'
|
|
186
|
+
if ($issueNum -eq '232') {
|
|
187
|
+
return [ordered]@{
|
|
188
|
+
decision = 'block'
|
|
189
|
+
reason = 'PREIMPLEMENTATION_GATE_BLOCKED: Issue #232 implementation operations require artifacts/orchestration/orchestrator-state.json to contain route metadata, lifecycle readiness, and checkpoint state before implementation begins.'
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return [ordered]@{
|
|
193
|
+
decision = 'block'
|
|
194
|
+
reason = 'PREIMPLEMENTATION_GATE_BLOCKED: Implementation operations require artifacts/orchestration/orchestrator-state.json to contain issue number, feature folder, route metadata, lifecycle readiness, and checkpoint state before implementation begins.'
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
$decision = Invoke-OrchestrationPreimplementationGateDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
204
|
+
} catch {
|
|
205
|
+
Write-Error $_
|
|
206
|
+
exit 1
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
$decision | ConvertTo-Json -Compress | Write-Output
|
|
210
|
+
exit 0
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Pre-tool-use hook that enforces the pr-author skill is used before gh pr create or gh pr edit.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Invoked by the Claude Code PreToolUse hook before any Bash command runs. Reads
|
|
7
|
+
tool input JSON from the CLAUDE_TOOL_INPUT environment variable, inspects the
|
|
8
|
+
attempted command, and blocks gh pr create / gh pr edit commands that bypass the
|
|
9
|
+
pr-author skill workflow.
|
|
10
|
+
|
|
11
|
+
Required sequence:
|
|
12
|
+
1. mcp__drm-copilot__collect_pr_context writes artifacts/pr_context.summary.txt
|
|
13
|
+
2. pr-author skill reads that file and writes artifacts/pr_body_<N>.md
|
|
14
|
+
3. gh pr create --body-file artifacts/pr_body_<N>.md
|
|
15
|
+
(or gh pr edit --body-file ...)
|
|
16
|
+
|
|
17
|
+
Block cases:
|
|
18
|
+
Case A - gh pr create or gh pr edit with --body (inline, no --body-file): blocked.
|
|
19
|
+
Case B - gh pr create with neither --body nor --body-file: blocked.
|
|
20
|
+
Case C - gh pr create or gh pr edit with --body-file but context artifact absent: blocked.
|
|
21
|
+
Case D - --body-file present, context artifact present, but the authorization sentinel
|
|
22
|
+
artifacts/pr_author_authorization.json is absent or empty: blocked
|
|
23
|
+
(PR_AGENT_AUTHORIZATION_MISSING).
|
|
24
|
+
Malformed - sentinel present but not valid JSON, or issued_at missing/unparseable: blocked
|
|
25
|
+
(PR_AGENT_AUTHORIZATION_MALFORMED).
|
|
26
|
+
Case E - sentinel valid JSON but issued_by != "pr-author": blocked
|
|
27
|
+
(PR_AGENT_AUTHORIZATION_INVALID).
|
|
28
|
+
Case F - sentinel issued_by == "pr-author" but elapsed time since issued_at exceeds
|
|
29
|
+
ttl_seconds: blocked (PR_AGENT_AUTHORIZATION_EXPIRED).
|
|
30
|
+
|
|
31
|
+
Authorization sentinel decision order on the --body-file-with-context path:
|
|
32
|
+
missing -> malformed -> invalid issuer -> expired -> allow.
|
|
33
|
+
|
|
34
|
+
.NOTES
|
|
35
|
+
Compatible with PowerShell 7+. No external module dependencies.
|
|
36
|
+
|
|
37
|
+
Enforcement strength: the authorization sentinel is a policy guardrail, not a cryptographic
|
|
38
|
+
or security control. Any actor with Write access to artifacts/ can forge
|
|
39
|
+
artifacts/pr_author_authorization.json, because all agents share the same filesystem and the
|
|
40
|
+
runtime exposes no native agent-identity signal at Bash PreToolUse time. The mechanism prevents
|
|
41
|
+
accidental bypass and requires a deliberate, documented act to circumvent. It MUST NOT be
|
|
42
|
+
described as tamper-proof or as a security boundary.
|
|
43
|
+
#>
|
|
44
|
+
[CmdletBinding()]
|
|
45
|
+
param()
|
|
46
|
+
|
|
47
|
+
$script:PrContextArtifactPath = 'artifacts/pr_context.summary.txt'
|
|
48
|
+
$script:PrAuthorAuthorizationPath = 'artifacts/pr_author_authorization.json'
|
|
49
|
+
$script:PrAuthorAuthorizationTtlSeconds = 120
|
|
50
|
+
|
|
51
|
+
function Get-PrContextArtifactExistence {
|
|
52
|
+
<#
|
|
53
|
+
.SYNOPSIS
|
|
54
|
+
Wrapper around Test-Path for the PR context artifact. Tests mock this function.
|
|
55
|
+
.OUTPUTS
|
|
56
|
+
System.Boolean
|
|
57
|
+
#>
|
|
58
|
+
[CmdletBinding()]
|
|
59
|
+
[OutputType([bool])]
|
|
60
|
+
param()
|
|
61
|
+
|
|
62
|
+
return [bool](Test-Path -LiteralPath $script:PrContextArtifactPath)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function Get-PrAuthorAuthorizationContent {
|
|
66
|
+
<#
|
|
67
|
+
.SYNOPSIS
|
|
68
|
+
Read the raw text of the authorization sentinel. Tests mock this function (read seam).
|
|
69
|
+
.DESCRIPTION
|
|
70
|
+
Returns the raw text content of artifacts/pr_author_authorization.json, or $null when the
|
|
71
|
+
sentinel file is absent. This is the injectable boundary for sentinel content in tests; no
|
|
72
|
+
test writes the sentinel file to disk.
|
|
73
|
+
.OUTPUTS
|
|
74
|
+
System.String or $null
|
|
75
|
+
#>
|
|
76
|
+
[CmdletBinding()]
|
|
77
|
+
[OutputType([string])]
|
|
78
|
+
param()
|
|
79
|
+
|
|
80
|
+
if (-not (Test-Path -LiteralPath $script:PrAuthorAuthorizationPath)) {
|
|
81
|
+
return $null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (Get-Content -LiteralPath $script:PrAuthorAuthorizationPath -Raw)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function Get-CurrentDateTimeUtc {
|
|
88
|
+
<#
|
|
89
|
+
.SYNOPSIS
|
|
90
|
+
Return the current UTC time. Tests mock this function (clock seam) for time-travel scenarios.
|
|
91
|
+
.OUTPUTS
|
|
92
|
+
System.DateTime
|
|
93
|
+
#>
|
|
94
|
+
[CmdletBinding()]
|
|
95
|
+
[OutputType([datetime])]
|
|
96
|
+
param()
|
|
97
|
+
|
|
98
|
+
return [DateTime]::UtcNow
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function Test-PrAuthorAuthorization {
|
|
102
|
+
<#
|
|
103
|
+
.SYNOPSIS
|
|
104
|
+
Validate the authorization sentinel and return a block-reason string, or $null when authorized.
|
|
105
|
+
.DESCRIPTION
|
|
106
|
+
Reads the sentinel via Get-PrAuthorAuthorizationContent and computes elapsed time via
|
|
107
|
+
Get-CurrentDateTimeUtc. Applies the decision order missing -> malformed -> invalid issuer ->
|
|
108
|
+
expired -> allow (spec FR-2 step 3):
|
|
109
|
+
- $null/empty contents -> PR_AGENT_AUTHORIZATION_MISSING
|
|
110
|
+
- not valid JSON, or issued_at
|
|
111
|
+
missing/unparseable -> PR_AGENT_AUTHORIZATION_MALFORMED
|
|
112
|
+
- issued_by != "pr-author" -> PR_AGENT_AUTHORIZATION_INVALID
|
|
113
|
+
- elapsed seconds > ttl_seconds -> PR_AGENT_AUTHORIZATION_EXPIRED
|
|
114
|
+
- all checks pass -> $null (allow)
|
|
115
|
+
The ttl_seconds value defaults to the named constant when absent from the sentinel.
|
|
116
|
+
This is a policy guardrail, not a cryptographic control; the sentinel is forgeable by any
|
|
117
|
+
actor with Write access to artifacts/.
|
|
118
|
+
.OUTPUTS
|
|
119
|
+
System.String or $null
|
|
120
|
+
#>
|
|
121
|
+
[CmdletBinding()]
|
|
122
|
+
[OutputType([string])]
|
|
123
|
+
param()
|
|
124
|
+
|
|
125
|
+
$contents = Get-PrAuthorAuthorizationContent
|
|
126
|
+
|
|
127
|
+
if ([string]::IsNullOrWhiteSpace($contents)) {
|
|
128
|
+
return "PR_AGENT_AUTHORIZATION_MISSING: ``$script:PrAuthorAuthorizationPath`` is absent or empty. The pr-author agent must write the authorization sentinel immediately before issuing ``gh pr create``/``gh pr edit --body*``."
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
$sentinel = $contents | ConvertFrom-Json -ErrorAction Stop
|
|
133
|
+
} catch {
|
|
134
|
+
return "PR_AGENT_AUTHORIZATION_MALFORMED: ``$script:PrAuthorAuthorizationPath`` is not valid JSON. The pr-author agent must write a well-formed sentinel with ``issued_by``, ``issued_at``, and ``ttl_seconds``."
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
$issuedAtRaw = $sentinel.issued_at
|
|
138
|
+
if ([string]::IsNullOrWhiteSpace([string]$issuedAtRaw)) {
|
|
139
|
+
return "PR_AGENT_AUTHORIZATION_MALFORMED: ``$script:PrAuthorAuthorizationPath`` is missing ``issued_at``. The pr-author agent must record a UTC ISO-8601 ``issued_at`` timestamp."
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
$issuedAt = [DateTime]::MinValue
|
|
143
|
+
$parsed = [DateTime]::TryParse(
|
|
144
|
+
[string]$issuedAtRaw,
|
|
145
|
+
[System.Globalization.CultureInfo]::InvariantCulture,
|
|
146
|
+
[System.Globalization.DateTimeStyles]::AdjustToUniversal -bor [System.Globalization.DateTimeStyles]::AssumeUniversal,
|
|
147
|
+
[ref] $issuedAt)
|
|
148
|
+
if (-not $parsed) {
|
|
149
|
+
return "PR_AGENT_AUTHORIZATION_MALFORMED: ``$script:PrAuthorAuthorizationPath`` has an unparseable ``issued_at``. The pr-author agent must record a UTC ISO-8601 ``issued_at`` timestamp."
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if ($sentinel.issued_by -ne 'pr-author') {
|
|
153
|
+
return "PR_AGENT_AUTHORIZATION_INVALID: ``$script:PrAuthorAuthorizationPath`` ``issued_by`` is not ``pr-author``. Only the pr-author agent may authorize PR creation or body edits."
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
$ttlSeconds = $script:PrAuthorAuthorizationTtlSeconds
|
|
157
|
+
if ($null -ne $sentinel.ttl_seconds) {
|
|
158
|
+
$candidateTtl = 0
|
|
159
|
+
if ([int]::TryParse([string]$sentinel.ttl_seconds, [ref] $candidateTtl)) {
|
|
160
|
+
$ttlSeconds = $candidateTtl
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
$elapsedSeconds = ((Get-CurrentDateTimeUtc) - $issuedAt).TotalSeconds
|
|
165
|
+
if ($elapsedSeconds -gt $ttlSeconds) {
|
|
166
|
+
return "PR_AGENT_AUTHORIZATION_EXPIRED: ``$script:PrAuthorAuthorizationPath`` issued $([math]::Round($elapsedSeconds)) s ago, exceeding the ${ttlSeconds}s TTL. The pr-author agent must write a fresh sentinel immediately before the ``gh`` command."
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return $null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function Get-PrAuthorBypassReason {
|
|
173
|
+
<#
|
|
174
|
+
.SYNOPSIS
|
|
175
|
+
Inspect the command text and return a block reason string, or $null when the command is allowed.
|
|
176
|
+
.DESCRIPTION
|
|
177
|
+
Returns PR_AUTHOR_SKILL_BLOCKED when gh pr create or gh pr edit is run with --body (inline,
|
|
178
|
+
no --body-file), or when gh pr create is run with no body flag at all. Returns
|
|
179
|
+
PR_CONTEXT_MISSING when --body-file is present but the context artifact
|
|
180
|
+
does not exist on disk. When --body-file is present and the context artifact exists, evaluates
|
|
181
|
+
the authorization sentinel via Test-PrAuthorAuthorization and returns its block reason
|
|
182
|
+
(PR_AGENT_AUTHORIZATION_*) when authorization fails. Returns $null for all allowed patterns.
|
|
183
|
+
Cases A, B, and C are evaluated first and unchanged; the sentinel check only extends the
|
|
184
|
+
previously-allowed --body-file-with-context path.
|
|
185
|
+
.PARAMETER CommandText
|
|
186
|
+
The Bash command text extracted from CLAUDE_TOOL_INPUT.
|
|
187
|
+
.PARAMETER ContextExists
|
|
188
|
+
Whether artifacts/pr_context.summary.txt currently exists on disk.
|
|
189
|
+
.OUTPUTS
|
|
190
|
+
System.String or $null
|
|
191
|
+
#>
|
|
192
|
+
[CmdletBinding()]
|
|
193
|
+
[OutputType([string])]
|
|
194
|
+
param(
|
|
195
|
+
[Parameter(Mandatory)]
|
|
196
|
+
[string] $CommandText,
|
|
197
|
+
|
|
198
|
+
[Parameter(Mandatory)]
|
|
199
|
+
[bool] $ContextExists
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Only act on gh pr create or gh pr edit subcommands.
|
|
203
|
+
$isPrCreate = $CommandText -match '(?i)\bgh\s+pr\s+create\b'
|
|
204
|
+
$isPrEdit = $CommandText -match '(?i)\bgh\s+pr\s+edit\b'
|
|
205
|
+
|
|
206
|
+
if (-not $isPrCreate -and -not $isPrEdit) {
|
|
207
|
+
return $null
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
$hasBodyFile = $CommandText -match '(?i)--body-file\b'
|
|
211
|
+
$hasInlineBody = $CommandText -match '(?i)--body(?!-file)\b'
|
|
212
|
+
|
|
213
|
+
# Case A: gh pr create OR gh pr edit with inline --body (not --body-file). Evaluated before the
|
|
214
|
+
# gh pr edit no-body allow short-circuit so inline-body edits are blocked, not allowed.
|
|
215
|
+
if (($isPrCreate -or $isPrEdit) -and $hasInlineBody -and -not $hasBodyFile) {
|
|
216
|
+
return "PR_AUTHOR_SKILL_BLOCKED: ``gh pr create`` and ``gh pr edit`` must use ``--body-file`` with a file produced by the pr-author skill from ``$script:PrContextArtifactPath``. Run ``mcp__drm-copilot__collect_pr_context`` to generate the context file, apply the pr-author skill to produce ``artifacts/pr_body_<N>.md``, then pass that file via ``--body-file``."
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if ($isPrCreate) {
|
|
220
|
+
# Case B: gh pr create with no body flag at all.
|
|
221
|
+
if (-not $hasInlineBody -and -not $hasBodyFile) {
|
|
222
|
+
return "PR_AUTHOR_SKILL_BLOCKED: New PRs require ``--body-file``. Run ``mcp__drm-copilot__collect_pr_context`` to generate ``$script:PrContextArtifactPath``, apply the pr-author skill to produce ``artifacts/pr_body_<N>.md``, then pass that file via ``--body-file``."
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if ($isPrEdit) {
|
|
227
|
+
# gh pr edit with no --body or --body-file (e.g., --title, --add-label, --reviewer) is allowed.
|
|
228
|
+
if (-not $hasInlineBody -and -not $hasBodyFile) {
|
|
229
|
+
return $null
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# Case C: --body-file present but context artifact is absent.
|
|
234
|
+
if ($hasBodyFile -and -not $ContextExists) {
|
|
235
|
+
return "PR_CONTEXT_MISSING: ``$script:PrContextArtifactPath`` is absent. Run ``mcp__drm-copilot__collect_pr_context`` before creating or editing the PR body."
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# Cases D/E/F and malformed: --body-file present and context artifact exists; verify the
|
|
239
|
+
# authorization sentinel. This extends, and does not replace, the previously-allowed path.
|
|
240
|
+
if ($hasBodyFile -and $ContextExists) {
|
|
241
|
+
$authorizationReason = Test-PrAuthorAuthorization
|
|
242
|
+
if ($authorizationReason) {
|
|
243
|
+
return $authorizationReason
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return $null
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function Invoke-PrAuthorSkillDecision {
|
|
251
|
+
<#
|
|
252
|
+
.SYNOPSIS
|
|
253
|
+
Parse CLAUDE_TOOL_INPUT and return an allow-or-block decision.
|
|
254
|
+
.PARAMETER ToolInputRaw
|
|
255
|
+
The raw JSON tool payload supplied by Claude Code.
|
|
256
|
+
.OUTPUTS
|
|
257
|
+
System.Collections.Specialized.OrderedDictionary
|
|
258
|
+
.NOTES
|
|
259
|
+
Missing tool input or missing command text is treated as allow.
|
|
260
|
+
#>
|
|
261
|
+
[CmdletBinding()]
|
|
262
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
263
|
+
param(
|
|
264
|
+
[string] $ToolInputRaw
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
if (-not $ToolInputRaw) {
|
|
268
|
+
return [ordered]@{ decision = 'allow' }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
$toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
|
|
273
|
+
} catch {
|
|
274
|
+
throw "enforce-pr-author-skill hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
$commandText = $toolInput.command
|
|
278
|
+
if (-not $commandText) {
|
|
279
|
+
return [ordered]@{ decision = 'allow' }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
$contextExists = Get-PrContextArtifactExistence
|
|
283
|
+
$reason = Get-PrAuthorBypassReason -CommandText $commandText -ContextExists $contextExists
|
|
284
|
+
|
|
285
|
+
if ($reason) {
|
|
286
|
+
return [ordered]@{
|
|
287
|
+
decision = 'block'
|
|
288
|
+
reason = $reason
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return [ordered]@{ decision = 'allow' }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function Test-PrAuthorBypassRequired {
|
|
296
|
+
<#
|
|
297
|
+
.SYNOPSIS
|
|
298
|
+
Return $true when a Bash command requires the pr-author skill to run first.
|
|
299
|
+
.PARAMETER CommandText
|
|
300
|
+
The Bash command text extracted from CLAUDE_TOOL_INPUT.
|
|
301
|
+
.PARAMETER ContextExists
|
|
302
|
+
Whether artifacts/pr_context.summary.txt currently exists on disk.
|
|
303
|
+
.OUTPUTS
|
|
304
|
+
System.Boolean
|
|
305
|
+
#>
|
|
306
|
+
[CmdletBinding()]
|
|
307
|
+
[OutputType([bool])]
|
|
308
|
+
param(
|
|
309
|
+
[Parameter(Mandatory)]
|
|
310
|
+
[string] $CommandText,
|
|
311
|
+
|
|
312
|
+
[Parameter(Mandatory)]
|
|
313
|
+
[bool] $ContextExists
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return ($null -ne (Get-PrAuthorBypassReason -CommandText $CommandText -ContextExists $ContextExists))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
# Allow dot-sourcing in tests without executing the entrypoint.
|
|
320
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
$decision = Invoke-PrAuthorSkillDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
326
|
+
} catch {
|
|
327
|
+
Write-Error $_
|
|
328
|
+
exit 1
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
$decision | ConvertTo-Json -Compress | Write-Output
|
|
332
|
+
|
|
333
|
+
exit 0
|