@danmoisan/drm-copilot-mcp 1.1.18 → 1.1.19
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 +989 -662
- package/package.json +3 -2
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +11 -0
- package/resources/claude-customizations/.claude/agents/atomic-planner.md +10 -0
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +12 -4
- package/resources/claude-customizations/.claude/agents/epic-planner.md +5 -4
- package/resources/claude-customizations/.claude/agents/feature-review.md +6 -4
- package/resources/claude-customizations/.claude/agents/orchestrator.md +5 -4
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +31 -13
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +9 -6
- package/resources/claude-customizations/.claude/agents/pr-author.md +15 -2
- package/resources/claude-customizations/.claude/agents/prd-feature.md +1 -1
- package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +111 -46
- package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +105 -0
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +8 -1
- package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +116 -59
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +55 -23
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +74 -75
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +127 -35
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +61 -57
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +129 -39
- package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +39 -51
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +17 -12
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +65 -50
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +49 -42
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +54 -72
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +8 -2
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +279 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +129 -75
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.artifact-root.ps1 +233 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.docs-only.ps1 +224 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +69 -24
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +29 -18
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +10 -4
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +14 -5
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +8 -2
- package/resources/claude-customizations/.claude/hooks/feature-folder-resolution.ps1 +373 -0
- package/resources/claude-customizations/.claude/hooks/feature-review-coverage-thresholds.ps1 +85 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-heredoc.ps1 +119 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-invocation-operands.ps1 +198 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +254 -255
- package/resources/claude-customizations/.claude/hooks/hook-command-payload-powershell.ps1 +201 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-payload.ps1 +484 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +27 -110
- package/resources/claude-customizations/.claude/hooks/hook-dependency-guard.ps1 +114 -0
- package/resources/claude-customizations/.claude/hooks/hook-docs-only-diff.ps1 +493 -0
- package/resources/claude-customizations/.claude/hooks/subagentstop-envelope.ps1 +192 -0
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +40 -42
- package/resources/claude-customizations/.claude/hooks/validate-executor-output.ps1 +66 -38
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-metrics.ps1 +180 -0
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-rules.ps1 +195 -0
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage.ps1 +141 -395
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-resolution.ps1 +311 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-routing.ps1 +170 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +115 -190
- package/resources/claude-customizations/.claude/hooks/validate-planner-output.ps1 +64 -32
- package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +39 -44
- package/resources/claude-customizations/.claude/hooks/validate-prd-feature-output.ps1 +43 -14
- package/resources/claude-customizations/.claude/hooks/validate-required-artifact-output.ps1 +66 -36
- package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +31 -34
- package/resources/claude-customizations/.claude/lib/bash/mutate-parallel-run.sh +191 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-cohorts.sh +13 -3
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-cli.sh +152 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-ops.sh +193 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation.sh +322 -0
- package/resources/claude-customizations/.claude/lib/bash/remove-parallel-item.sh +146 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +23 -33
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +86 -6
- package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-CiGateParser.ps1 +91 -19
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateEpicWaveBarrier.psm1 +346 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +24 -3
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +79 -2
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +3 -3
- package/resources/claude-customizations/.claude/rules/architecture-boundaries.md +5 -5
- package/resources/claude-customizations/.claude/rules/csharp.md +2 -2
- package/resources/claude-customizations/.claude/rules/general-code-change.md +1 -1
- package/resources/claude-customizations/.claude/rules/general-unit-test.md +3 -2
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +37 -4
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +50 -5
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +47 -8
- package/resources/claude-customizations/.claude/rules/quality-tiers.md +11 -7
- package/resources/claude-customizations/.claude/rules/shell.md +10 -8
- package/resources/claude-customizations/.claude/rules/typescript.md +1 -1
- package/resources/claude-customizations/.claude/settings.json +3 -4
- package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +5 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_detached_lib.sh +1 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +2 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +2 -0
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +33 -6
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +4 -3
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +14 -6
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +52 -14
- package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +22 -9
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +79 -16
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +63 -7
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +35 -12
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +2 -2
- package/resources/claude-customizations/pack-manifests/core.json +23 -0
- package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +5 -5
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-executor/SKILL.md +11 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +4 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +10 -0
- package/resources/codex-and-agents-customizations/.agents/skills/csharp/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +17 -1
- package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +2 -1
- package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +13 -5
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +3 -2
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +15 -6
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +11 -7
- package/resources/codex-and-agents-customizations/.agents/skills/translate-claude-to-codex/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +2 -1
- package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/codex-web-setup.sh +318 -273
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +97 -44
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-helpers.ps1 +105 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +37 -5
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +6 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +54 -41
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +35 -14
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +24 -11
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +13 -4
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/feature-folder-resolution.ps1 +373 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/feature-review-coverage-thresholds.ps1 +85 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-heredoc.ps1 +119 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation-operands.ps1 +198 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +254 -255
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload-powershell.ps1 +201 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload.ps1 +484 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +27 -110
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-dependency-guard.ps1 +114 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-docs-only-diff.ps1 +493 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +24 -8
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +9 -0
- package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +2 -2
- package/resources/customizations/.github/instructions/csharp-code-change.instructions.md +4 -4
- package/resources/customizations/.github/instructions/csharp-unit-test.instructions.md +2 -2
- package/resources/customizations/.github/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +4 -0
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +1 -1
- package/resources/powershell/PoshQC/PoshQC.Coverage.psm1 +314 -0
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +25 -28
- package/resources/powershell/PoshQC/PoshQC.psm1 +1 -0
- package/resources/powershell/PoshQC/README.md +8 -2
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +2 -319
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
PreToolUse hook that limits the pr-author subagent's Bash calls to an allowlist of forms.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Registered in the frontmatter of .claude/agents/pr-author.md on the "Bash" matcher
|
|
7
|
+
(issue #824, #733). Every command is read with Read-CommandLineInvocationSegment, so a
|
|
8
|
+
wrapper payload, a substitution body, or an xargs-injected command is a record of its own.
|
|
9
|
+
The command is allowed only when every record is a literal, top-level, unwrapped command
|
|
10
|
+
in one of these forms, matched by token position:
|
|
11
|
+
|
|
12
|
+
- git log ... and git rev-parse ... (no git global option)
|
|
13
|
+
- gh pr create ... and gh pr edit ... (no gh global option)
|
|
14
|
+
- sha256sum artifacts/pr_body_<N>.md (only as the whole command)
|
|
15
|
+
- date -u [+<format>] (only as the whole command)
|
|
16
|
+
|
|
17
|
+
Segments may be joined only by ';', '&&', '||', '|', or a newline. A record that is
|
|
18
|
+
nested, unbalanced, opaque, non-literal, wrapper-led, led by a VAR=value assignment, or
|
|
19
|
+
whose scanner segment carries a redirection is denied. Every deny reason begins
|
|
20
|
+
PR_AUTHOR_COMMAND_NOT_ALLOWED: and names the first rejected segment.
|
|
21
|
+
|
|
22
|
+
The hook is read-only: it inspects the attempted command and emits a JSON
|
|
23
|
+
allow-or-deny decision without mutating the command text.
|
|
24
|
+
|
|
25
|
+
.NOTES
|
|
26
|
+
Compatible with PowerShell 7+. Pure string logic over the shared command-line parser;
|
|
27
|
+
no disk, process, or network access beyond payload acquisition.
|
|
28
|
+
#>
|
|
29
|
+
[CmdletBinding()]
|
|
30
|
+
param()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
34
|
+
# Shared command-line parser (issues #545 and #824).
|
|
35
|
+
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
36
|
+
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
37
|
+
|
|
38
|
+
$script:PrAuthorAllowlistReasonPrefix = 'PR_AUTHOR_COMMAND_NOT_ALLOWED: '
|
|
39
|
+
$script:PrAuthorAllowlistDelimiters = @('', ';', '&&', '||', '|', "`n")
|
|
40
|
+
$script:PrAuthorAllowlistWrapperWords = @(
|
|
41
|
+
'sh', 'bash', 'zsh', 'dash', 'ksh', 'pwsh', 'powershell', 'eval', 'xargs',
|
|
42
|
+
'env', 'timeout', 'time', 'command', 'nohup'
|
|
43
|
+
)
|
|
44
|
+
$script:PrAuthorAllowlistFormText = 'git log, git rev-parse, gh pr create, gh pr edit, sha256sum artifacts/pr_body_<N>.md, date -u [+<format>]'
|
|
45
|
+
|
|
46
|
+
function Test-PrAuthorAllowlistForm {
|
|
47
|
+
<#
|
|
48
|
+
.SYNOPSIS
|
|
49
|
+
Report whether one record's tokens match an allowed command form by token position.
|
|
50
|
+
.PARAMETER Token
|
|
51
|
+
The record's tokens, quotes already stripped.
|
|
52
|
+
.PARAMETER IsWholeCommand
|
|
53
|
+
Whether the command has exactly one top-level record; sha256sum and date require it.
|
|
54
|
+
.OUTPUTS
|
|
55
|
+
System.Boolean
|
|
56
|
+
#>
|
|
57
|
+
[CmdletBinding()]
|
|
58
|
+
[OutputType([bool])]
|
|
59
|
+
param(
|
|
60
|
+
[Parameter(Mandatory)][AllowEmptyCollection()][string[]] $Token,
|
|
61
|
+
[Parameter(Mandatory)][bool] $IsWholeCommand
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if ($Token.Count -eq 0) {
|
|
65
|
+
return $false
|
|
66
|
+
}
|
|
67
|
+
switch -CaseSensitive ($Token[0]) {
|
|
68
|
+
'git' {
|
|
69
|
+
return ($Token.Count -ge 2 -and @('log', 'rev-parse') -ccontains $Token[1])
|
|
70
|
+
}
|
|
71
|
+
'gh' {
|
|
72
|
+
return ($Token.Count -ge 3 -and $Token[1] -ceq 'pr' -and @('create', 'edit') -ccontains $Token[2])
|
|
73
|
+
}
|
|
74
|
+
'sha256sum' {
|
|
75
|
+
return ($IsWholeCommand -and $Token.Count -eq 2 -and $Token[1] -cmatch '^artifacts/pr_body_\d+\.md$')
|
|
76
|
+
}
|
|
77
|
+
'date' {
|
|
78
|
+
if (-not $IsWholeCommand -or $Token.Count -lt 2 -or $Token[1] -cne '-u') {
|
|
79
|
+
return $false
|
|
80
|
+
}
|
|
81
|
+
return ($Token.Count -eq 2 -or ($Token.Count -eq 3 -and $Token[2].StartsWith('+')))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return $false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function Get-PrAuthorAllowlistRecordProblem {
|
|
88
|
+
<#
|
|
89
|
+
.SYNOPSIS
|
|
90
|
+
Return the reason one record is rejected, or $null when the record is allowed.
|
|
91
|
+
.PARAMETER Record
|
|
92
|
+
One Read-CommandLineInvocationSegment record.
|
|
93
|
+
.PARAMETER MaskedText
|
|
94
|
+
The masked text of the record's depth-0 scanner segment, or '' for a nested record.
|
|
95
|
+
.PARAMETER IsWholeCommand
|
|
96
|
+
Whether the command has exactly one top-level record.
|
|
97
|
+
.OUTPUTS
|
|
98
|
+
System.String or $null
|
|
99
|
+
#>
|
|
100
|
+
[CmdletBinding()]
|
|
101
|
+
[OutputType([string])]
|
|
102
|
+
param(
|
|
103
|
+
[Parameter(Mandatory)][pscustomobject] $Record,
|
|
104
|
+
[Parameter(Mandatory)][AllowEmptyString()][string] $MaskedText,
|
|
105
|
+
[Parameter(Mandatory)][bool] $IsWholeCommand
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
$tokens = [string[]]@($Record.Tokens)
|
|
109
|
+
if ($Record.Depth -gt 0) { return "it is a nested $($Record.Origin) command" }
|
|
110
|
+
if ($Record.Unbalanced) { return 'its quoting or grouping is unbalanced' }
|
|
111
|
+
if ($Record.Opaque) { return "its payload cannot be read ($($Record.OpaqueReason))" }
|
|
112
|
+
if (-not $Record.Literal) { return 'it carries a variable, substitution, or other non-literal token' }
|
|
113
|
+
if ($script:PrAuthorAllowlistWrapperWords -contains $Record.CommandWord) { return "it is led by the wrapper '$($Record.CommandWord)'" }
|
|
114
|
+
if ($script:PrAuthorAllowlistDelimiters -notcontains $Record.Delimiter) { return "it is joined by the delimiter '$($Record.Delimiter)'" }
|
|
115
|
+
if ($MaskedText.Contains('<') -or $MaskedText.Contains('>')) { return 'it carries a redirection' }
|
|
116
|
+
if ($tokens.Count -gt 0 -and $tokens[0] -match '^[A-Za-z_][A-Za-z0-9_]*=') { return 'it is led by a VAR=value assignment' }
|
|
117
|
+
if (-not (Test-PrAuthorAllowlistForm -Token $tokens -IsWholeCommand $IsWholeCommand)) { return 'it is not an allowed command form' }
|
|
118
|
+
return $null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function Get-PrAuthorCommandAllowlistReason {
|
|
122
|
+
<#
|
|
123
|
+
.SYNOPSIS
|
|
124
|
+
Return the deny reason for a command outside the pr-author allowlist, or $null.
|
|
125
|
+
.PARAMETER CommandText
|
|
126
|
+
The Bash command text extracted from the envelope's tool_input.
|
|
127
|
+
.OUTPUTS
|
|
128
|
+
System.String or $null
|
|
129
|
+
#>
|
|
130
|
+
[CmdletBinding()]
|
|
131
|
+
[OutputType([string])]
|
|
132
|
+
param(
|
|
133
|
+
[Parameter(Mandatory)]
|
|
134
|
+
[AllowEmptyString()]
|
|
135
|
+
[string] $CommandText
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if ([string]::IsNullOrWhiteSpace($CommandText)) {
|
|
139
|
+
return $null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
$records = @(Read-CommandLineInvocationSegment -CommandText $CommandText)
|
|
143
|
+
$segments = @(Read-CommandLineSegment -CommandText (ConvertTo-CommandLineNormalizedText -CommandText $CommandText))
|
|
144
|
+
$topLevel = @($records | Where-Object { $_.Depth -eq 0 })
|
|
145
|
+
$isWholeCommand = $topLevel.Count -eq 1
|
|
146
|
+
$topLevelPosition = 0
|
|
147
|
+
|
|
148
|
+
foreach ($record in $records) {
|
|
149
|
+
$maskedText = ''
|
|
150
|
+
if ($record.Depth -eq 0) {
|
|
151
|
+
if ($topLevelPosition -lt $segments.Count) {
|
|
152
|
+
$maskedText = [string]$segments[$topLevelPosition].MaskedText
|
|
153
|
+
}
|
|
154
|
+
$topLevelPosition++
|
|
155
|
+
}
|
|
156
|
+
$problem = Get-PrAuthorAllowlistRecordProblem -Record $record -MaskedText $maskedText -IsWholeCommand $isWholeCommand
|
|
157
|
+
if ($null -ne $problem) {
|
|
158
|
+
return ($script:PrAuthorAllowlistReasonPrefix +
|
|
159
|
+
"the segment '$($record.RawText.Trim())' is rejected because $problem. The pr-author agent may run only: $($script:PrAuthorAllowlistFormText), each without wrappers, substitutions, or redirections.")
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return $null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function Get-PrAuthorCommandAllowlistDecision {
|
|
167
|
+
<#
|
|
168
|
+
.SYNOPSIS
|
|
169
|
+
Build an ordered allow or deny decision.
|
|
170
|
+
.PARAMETER Reason
|
|
171
|
+
The deny reason, or $null for an allow.
|
|
172
|
+
.OUTPUTS
|
|
173
|
+
System.Collections.Specialized.OrderedDictionary
|
|
174
|
+
#>
|
|
175
|
+
[CmdletBinding()]
|
|
176
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
177
|
+
param(
|
|
178
|
+
[AllowNull()]
|
|
179
|
+
[string] $Reason
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if ([string]::IsNullOrEmpty($Reason)) {
|
|
183
|
+
return [ordered]@{
|
|
184
|
+
hookSpecificOutput = [ordered]@{
|
|
185
|
+
hookEventName = 'PreToolUse'
|
|
186
|
+
permissionDecision = 'allow'
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return [ordered]@{
|
|
191
|
+
hookSpecificOutput = [ordered]@{
|
|
192
|
+
hookEventName = 'PreToolUse'
|
|
193
|
+
permissionDecision = 'deny'
|
|
194
|
+
permissionDecisionReason = $Reason
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function Invoke-PrAuthorCommandAllowlistDecision {
|
|
200
|
+
<#
|
|
201
|
+
.SYNOPSIS
|
|
202
|
+
Return the ordered allow-or-deny decision for one command text.
|
|
203
|
+
.PARAMETER CommandText
|
|
204
|
+
The Bash command text.
|
|
205
|
+
.OUTPUTS
|
|
206
|
+
System.Collections.Specialized.OrderedDictionary
|
|
207
|
+
#>
|
|
208
|
+
[CmdletBinding()]
|
|
209
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
210
|
+
param(
|
|
211
|
+
[Parameter(Mandatory)]
|
|
212
|
+
[AllowEmptyString()]
|
|
213
|
+
[string] $CommandText
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return Get-PrAuthorCommandAllowlistDecision -Reason (Get-PrAuthorCommandAllowlistReason -CommandText $CommandText)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function Invoke-PrAuthorCommandAllowlistEntryPoint {
|
|
220
|
+
<#
|
|
221
|
+
.SYNOPSIS
|
|
222
|
+
Runs the hook decision and returns the process exit code.
|
|
223
|
+
.DESCRIPTION
|
|
224
|
+
Acquires the payload through the shared reader unless the caller supplies one, emits
|
|
225
|
+
the compact decision JSON, and returns 0. It never returns 1: exit 1 is non-blocking
|
|
226
|
+
for PreToolUse, so every anomaly is already a deny decision by the time control
|
|
227
|
+
reaches here. The function does not call exit; the thin tail converts the returned
|
|
228
|
+
code into a process exit.
|
|
229
|
+
.PARAMETER ToolInputRaw
|
|
230
|
+
Optional pre-acquired payload text. When omitted the ReadPayload seam runs.
|
|
231
|
+
.PARAMETER ReadPayload
|
|
232
|
+
Seam for payload acquisition, so tests can drive the empty-on-all-transports case
|
|
233
|
+
without touching a console.
|
|
234
|
+
.OUTPUTS
|
|
235
|
+
System.Int32
|
|
236
|
+
#>
|
|
237
|
+
[CmdletBinding()]
|
|
238
|
+
[OutputType([int])]
|
|
239
|
+
param(
|
|
240
|
+
[AllowNull()]
|
|
241
|
+
[AllowEmptyString()]
|
|
242
|
+
[string] $ToolInputRaw,
|
|
243
|
+
|
|
244
|
+
[scriptblock] $ReadPayload = { Read-ClaudeHookRawPayload }
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if (-not $PSBoundParameters.ContainsKey('ToolInputRaw')) {
|
|
248
|
+
$ToolInputRaw = [string](& $ReadPayload)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
252
|
+
if (-not $payload.IsValid) {
|
|
253
|
+
$decision = Get-PrAuthorCommandAllowlistDecision -Reason (
|
|
254
|
+
$script:PrAuthorAllowlistReasonPrefix + 'payload anomaly - ' +
|
|
255
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $payload.Anomaly) +
|
|
256
|
+
'. The hook fails closed on an envelope it cannot read.')
|
|
257
|
+
} else {
|
|
258
|
+
$commandText = [string](Get-ClaudeHookToolInputString -ToolInput $payload.Value -Name 'command')
|
|
259
|
+
$decision = Invoke-PrAuthorCommandAllowlistDecision -CommandText $commandText
|
|
260
|
+
}
|
|
261
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
262
|
+
|
|
263
|
+
return 0
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Allow dot-sourcing in tests without executing the entrypoint.
|
|
267
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
272
|
+
# decision JSON before it. `exit (<call>)` would capture BOTH into the exit
|
|
273
|
+
# expression and emit nothing, so the decision is written explicitly here first.
|
|
274
|
+
$entryPointResult = @(Invoke-PrAuthorCommandAllowlistEntryPoint)
|
|
275
|
+
if ($entryPointResult.Count -gt 1) {
|
|
276
|
+
$entryPointResult[0..($entryPointResult.Count - 2)] | Write-Output
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
exit ([int]$entryPointResult[-1])
|
|
@@ -17,14 +17,19 @@
|
|
|
17
17
|
|
|
18
18
|
Both helpers read the script-scoped configuration the parent hook declares and call the
|
|
19
19
|
parent hook's injectable read seams, so this file is only ever dot-sourced from that hook
|
|
20
|
-
and never invoked on its own. $script:PrContextArtifactPath is
|
|
21
|
-
|
|
20
|
+
and never invoked on its own. $script:PrContextArtifactPath is the repository-relative
|
|
21
|
+
literal used in messages and as the composition input; every artifact read is composed
|
|
22
|
+
beneath the root Resolve-PrAuthorArtifactRoot selects (issue #850).
|
|
23
|
+
$script:OrchestratorStateCheckpointPath is different after
|
|
22
24
|
issue #673: the parent declares it null, and Get-PrAuthorBypassReason assigns it the
|
|
23
25
|
absolute checkpoint path of the worktree identity resolution selected, so every later
|
|
24
26
|
reader receives one resolved path rather than deriving its own.
|
|
25
27
|
|
|
26
28
|
.NOTES
|
|
27
|
-
Compatible with PowerShell 7+.
|
|
29
|
+
Compatible with PowerShell 7+. Dependencies are in-repository only: the command parsers
|
|
30
|
+
and worktree-resolution modules loaded below, and the artifact-root and docs-only siblings.
|
|
31
|
+
No PowerShell Gallery module is required.
|
|
32
|
+
The docs-only sibling runs the git executable at call time (issue #901).
|
|
28
33
|
#>
|
|
29
34
|
[CmdletBinding()]
|
|
30
35
|
param()
|
|
@@ -47,6 +52,10 @@ Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeItemR
|
|
|
47
52
|
# Epic scope (issue #663): the epic integration pull request is gated against the epic checkpoint.
|
|
48
53
|
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeResolution.psm1') -Force -ErrorAction Stop
|
|
49
54
|
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeReadiness.psm1') -Force -ErrorAction Stop
|
|
55
|
+
# Artifact-root resolution and the Check 1 body-path binding (issue #850).
|
|
56
|
+
. (Join-Path $PSScriptRoot 'enforce-pr-author-skill.artifact-root.ps1')
|
|
57
|
+
# Docs-only exemption from the orchestrator-state preflight (issue #901).
|
|
58
|
+
. (Join-Path $PSScriptRoot 'enforce-pr-author-skill.docs-only.ps1')
|
|
50
59
|
|
|
51
60
|
function Resolve-PrAuthorWorktreeTarget {
|
|
52
61
|
<#
|
|
@@ -98,7 +107,9 @@ function Get-PrAuthorTargetCheckpointResolution {
|
|
|
98
107
|
refuses to answer rather than answering from unrelated state. Neither reason-code
|
|
99
108
|
literal appears in this file; both come from the accessors named above.
|
|
100
109
|
.OUTPUTS
|
|
101
|
-
System.Collections.Specialized.OrderedDictionary with CheckpointPath
|
|
110
|
+
System.Collections.Specialized.OrderedDictionary with CheckpointPath, Reason,
|
|
111
|
+
WorktreeRoot (the resolved root, or $null when unresolved), and Status (the target's
|
|
112
|
+
status), so the artifact root is taken from the same resolution (issue #850).
|
|
102
113
|
#>
|
|
103
114
|
[CmdletBinding()]
|
|
104
115
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -111,15 +122,51 @@ function Get-PrAuthorTargetCheckpointResolution {
|
|
|
111
122
|
|
|
112
123
|
switch ($target.Status) {
|
|
113
124
|
{ $_ -in @('SessionRoot', 'OtherWorktree') } {
|
|
114
|
-
return [ordered]@{ CheckpointPath = (Get-WorktreeItemCheckpointPath -WorktreeRoot $target.WorktreeRoot); Reason = $null }
|
|
125
|
+
return [ordered]@{ CheckpointPath = (Get-WorktreeItemCheckpointPath -WorktreeRoot $target.WorktreeRoot); Reason = $null; WorktreeRoot = $target.WorktreeRoot; Status = $target.Status }
|
|
115
126
|
}
|
|
116
127
|
default {
|
|
117
128
|
$reason = "$($target.ReasonCode): $($target.Detail) The pr-author gate will not validate this call against the session root's checkpoint, because that checkpoint may belong to a different item. Pass --head <branch> on the gh pr create command so the call names its own target."
|
|
118
|
-
return [ordered]@{ CheckpointPath = $null; Reason = $reason }
|
|
129
|
+
return [ordered]@{ CheckpointPath = $null; Reason = $reason; WorktreeRoot = $null; Status = $target.Status }
|
|
119
130
|
}
|
|
120
131
|
}
|
|
121
132
|
}
|
|
122
133
|
|
|
134
|
+
function Get-PrAuthorBodyFileValue {
|
|
135
|
+
<#
|
|
136
|
+
.SYNOPSIS
|
|
137
|
+
Return the normalized --body-file value of the matched gh pr create or gh pr edit, or $null.
|
|
138
|
+
.DESCRIPTION
|
|
139
|
+
Reads the flag value from the first Structural gh pr create match, else gh pr edit.
|
|
140
|
+
Backslashes become forward slashes and one leading './' is removed (issue #824). A
|
|
141
|
+
rooted value is returned as written, with separators normalized, and is bound to the
|
|
142
|
+
resolved worktree by Get-PrAuthorBodyPathBindingReason (issue #850).
|
|
143
|
+
.PARAMETER CommandText
|
|
144
|
+
The Bash command text under evaluation.
|
|
145
|
+
.OUTPUTS
|
|
146
|
+
System.String or $null
|
|
147
|
+
#>
|
|
148
|
+
[CmdletBinding()]
|
|
149
|
+
[OutputType([string])]
|
|
150
|
+
param(
|
|
151
|
+
[Parameter(Mandatory)]
|
|
152
|
+
[string] $CommandText
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
$subcommandPath = @('pr', 'edit')
|
|
156
|
+
if (Test-CommandLineInvocation -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('pr', 'create')) {
|
|
157
|
+
$subcommandPath = @('pr', 'create')
|
|
158
|
+
}
|
|
159
|
+
$value = Get-CommandLineFlagValue -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath -FlagName '--body-file'
|
|
160
|
+
if ($null -eq $value) {
|
|
161
|
+
return $null
|
|
162
|
+
}
|
|
163
|
+
$value = $value.Replace('\', '/')
|
|
164
|
+
if ($value.StartsWith('./')) {
|
|
165
|
+
$value = $value.Substring(2)
|
|
166
|
+
}
|
|
167
|
+
return $value
|
|
168
|
+
}
|
|
169
|
+
|
|
123
170
|
function Test-PrAuthorReceiptVerification {
|
|
124
171
|
<#
|
|
125
172
|
.SYNOPSIS
|
|
@@ -127,8 +174,9 @@ function Test-PrAuthorReceiptVerification {
|
|
|
127
174
|
.DESCRIPTION
|
|
128
175
|
Runs the six ordered receipt checks on the --body-file-with-context path. Each check is its
|
|
129
176
|
own short-circuiting branch; the first failure returns its reason code:
|
|
130
|
-
1. PR_BODY_PATH_NONCANONICAL - --body-file path
|
|
131
|
-
artifacts/pr_body_<N>.md
|
|
177
|
+
1. PR_BODY_PATH_NONCANONICAL - --body-file path is not the canonical
|
|
178
|
+
artifacts/pr_body_<N>.md beneath the artifact
|
|
179
|
+
root (relative only for the session worktree).
|
|
132
180
|
2. PR_AUTHOR_RECEIPT_MISSING - sibling artifacts/pr_body_<N>.receipt.json absent.
|
|
133
181
|
3. PR_AUTHOR_RECEIPT_NUMBER_MISMATCH- receipt.number (integer) != <N> from the path.
|
|
134
182
|
4. PR_AUTHOR_RECEIPT_HASH_MISMATCH - inline SHA-256 (lowercase hex) of the body bytes
|
|
@@ -139,9 +187,10 @@ function Test-PrAuthorReceiptVerification {
|
|
|
139
187
|
matching --base <epic_context.integration_branch>.
|
|
140
188
|
Returns $null when all six checks pass (allow). All disk access flows through the four
|
|
141
189
|
injectable seams (Get-PrBodyFileBytes, Get-PrAuthorReceiptContent,
|
|
142
|
-
Get-PrContextSummaryLastWriteUtc, Get-PrAuthorCheckpointContent)
|
|
143
|
-
|
|
144
|
-
|
|
190
|
+
Get-PrContextSummaryLastWriteUtc, Get-PrAuthorCheckpointContent), each given an absolute
|
|
191
|
+
path beneath the artifact root (issue #850); SHA-256 is computed inline. This is a
|
|
192
|
+
policy-level integrity check, not a cryptographic control: any actor with Write access
|
|
193
|
+
to artifacts/ can replace the body file and the receipt together.
|
|
145
194
|
.PARAMETER CommandText
|
|
146
195
|
The Bash command text containing the --body-file argument.
|
|
147
196
|
.PARAMETER CheckpointPath
|
|
@@ -150,6 +199,10 @@ function Test-PrAuthorReceiptVerification {
|
|
|
150
199
|
.PARAMETER EpicScope
|
|
151
200
|
Optional. The epic-scope result Get-PrAuthorBypassReason resolved (issue #663), passed
|
|
152
201
|
through to check 6 so the call is resolved once; $null outside epic scope.
|
|
202
|
+
.PARAMETER ArtifactRoot
|
|
203
|
+
The resolved worktree root the body, receipt, and summary are read beneath.
|
|
204
|
+
.PARAMETER RelativeBodyAllowed
|
|
205
|
+
Whether a relative --body-file is read from the resolved worktree (the session worktree).
|
|
153
206
|
.OUTPUTS
|
|
154
207
|
System.String or $null
|
|
155
208
|
#>
|
|
@@ -163,18 +216,25 @@ function Test-PrAuthorReceiptVerification {
|
|
|
163
216
|
[string] $CheckpointPath,
|
|
164
217
|
|
|
165
218
|
[AllowNull()]
|
|
166
|
-
[object] $EpicScope
|
|
219
|
+
[object] $EpicScope,
|
|
220
|
+
|
|
221
|
+
[Parameter(Mandatory)]
|
|
222
|
+
[string] $ArtifactRoot,
|
|
223
|
+
|
|
224
|
+
[bool] $RelativeBodyAllowed
|
|
167
225
|
)
|
|
168
226
|
|
|
169
|
-
# Check 1: the --body-file
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
|
|
227
|
+
# Check 1: the --body-file value must be the canonical artifacts/pr_body_<N>.md beneath the
|
|
228
|
+
# artifact root (issue #850); C1a's structural read and spelling normalization are reused.
|
|
229
|
+
$binding = Get-PrAuthorBodyPathBindingReason -CommandText $CommandText -ArtifactRoot $ArtifactRoot -RelativeBodyAllowed $RelativeBodyAllowed
|
|
230
|
+
if ($binding.Reason) {
|
|
231
|
+
return $binding.Reason
|
|
173
232
|
}
|
|
174
233
|
|
|
175
|
-
$bodyNumber =
|
|
176
|
-
$bodyFilePath = "artifacts/pr_body_$bodyNumber.md"
|
|
177
|
-
$receiptFilePath = "artifacts/pr_body_$bodyNumber.receipt.json"
|
|
234
|
+
$bodyNumber = $binding.BodyNumber
|
|
235
|
+
$bodyFilePath = Join-WorktreeResolutionPath -WorktreeRoot $ArtifactRoot -RepoRelativePath "artifacts/pr_body_$bodyNumber.md"
|
|
236
|
+
$receiptFilePath = Join-WorktreeResolutionPath -WorktreeRoot $ArtifactRoot -RepoRelativePath "artifacts/pr_body_$bodyNumber.receipt.json"
|
|
237
|
+
$summaryPath = Join-WorktreeResolutionPath -WorktreeRoot $ArtifactRoot -RepoRelativePath $script:PrContextArtifactPath
|
|
178
238
|
|
|
179
239
|
# Check 2: the sibling receipt file must exist (read via the injectable seam).
|
|
180
240
|
$receiptRaw = Get-PrAuthorReceiptContent -ReceiptFilePath $receiptFilePath
|
|
@@ -228,7 +288,7 @@ function Test-PrAuthorReceiptVerification {
|
|
|
228
288
|
return "PR_AUTHOR_RECEIPT_STALE: ``$receiptFilePath`` has a missing or unparseable ``created_at``. The pr-author agent must record a UTC ISO-8601 ``created_at`` strictly newer than ``$script:PrContextArtifactPath``."
|
|
229
289
|
}
|
|
230
290
|
|
|
231
|
-
$contextLastWrite = Get-PrContextSummaryLastWriteUtc
|
|
291
|
+
$contextLastWrite = Get-PrContextSummaryLastWriteUtc -Path $summaryPath
|
|
232
292
|
if (($null -eq $contextLastWrite) -or ($createdAt -le $contextLastWrite)) {
|
|
233
293
|
return "PR_AUTHOR_RECEIPT_STALE: ``$receiptFilePath`` ``created_at`` is not strictly newer than the last-write time of ``$script:PrContextArtifactPath``. The pr-author agent must regenerate the body and receipt after refreshing the PR context."
|
|
234
294
|
}
|
|
@@ -248,17 +308,16 @@ function Get-PrAuthorBypassReason {
|
|
|
248
308
|
Inspect the command text and return a block reason string, or $null when the command is allowed.
|
|
249
309
|
.DESCRIPTION
|
|
250
310
|
Returns PR_AUTHOR_SKILL_BLOCKED when gh pr create or gh pr edit is run with --body (inline,
|
|
251
|
-
no --body-file), or when gh pr create is run with no body flag at all
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
311
|
+
no --body-file), or when gh pr create is run with no body flag at all; these cases and the
|
|
312
|
+
gh pr edit no-body allow perform no resolution. For a --body-file command the artifact root
|
|
313
|
+
is resolved once (issue #850), and the deny precedence is: the resolution reason
|
|
314
|
+
(no-target or ambiguity code, or an underivable epic worktree), PR_CONTEXT_MISSING when the
|
|
315
|
+
context summary is absent beneath the resolved worktree, ORCHESTRATOR_STATE_PREFLIGHT_FAILED
|
|
316
|
+
unless Get-PrAuthorDocsOnlyExemption exempts a docs-only gh pr create (issue #901), then
|
|
317
|
+
receipt checks 1-6 via Test-PrAuthorReceiptVerification. Returns $null for all allowed
|
|
318
|
+
patterns.
|
|
258
319
|
.PARAMETER CommandText
|
|
259
320
|
The Bash command text extracted from the envelope's tool_input.
|
|
260
|
-
.PARAMETER ContextExists
|
|
261
|
-
Whether artifacts/pr_context.summary.txt currently exists on disk.
|
|
262
321
|
.OUTPUTS
|
|
263
322
|
System.String or $null
|
|
264
323
|
#>
|
|
@@ -266,10 +325,7 @@ function Get-PrAuthorBypassReason {
|
|
|
266
325
|
[OutputType([string])]
|
|
267
326
|
param(
|
|
268
327
|
[Parameter(Mandatory)]
|
|
269
|
-
[string] $CommandText
|
|
270
|
-
|
|
271
|
-
[Parameter(Mandatory)]
|
|
272
|
-
[bool] $ContextExists
|
|
328
|
+
[string] $CommandText
|
|
273
329
|
)
|
|
274
330
|
|
|
275
331
|
# Only act on gh pr create or gh pr edit subcommands. The test is structural: a segment
|
|
@@ -290,22 +346,21 @@ function Get-PrAuthorBypassReason {
|
|
|
290
346
|
$hasBodyFile = Test-CommandLineFlag -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath -FlagName '--body-file'
|
|
291
347
|
$hasInlineBody = Test-CommandLineFlag -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath -FlagName '--body'
|
|
292
348
|
|
|
293
|
-
# Raw-scan fallback
|
|
294
|
-
#
|
|
295
|
-
#
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
# whole-text --body-file match set $hasBodyFile for exactly this input.
|
|
349
|
+
# Raw-scan fallback (issue #824). Wrapper payloads are now read structurally, so the flags
|
|
350
|
+
# above already come from a payload's own tokens. The raw text is scanned only when the
|
|
351
|
+
# invocation could not be read structurally at all: no Structural match exists for the
|
|
352
|
+
# subcommand path and at least one Indeterminate match does. The --body-file test runs
|
|
353
|
+
# first and wins, so a --body-file is never misread as an inline body.
|
|
299
354
|
if (-not $hasBodyFile -and -not $hasInlineBody) {
|
|
300
|
-
$
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
$
|
|
307
|
-
|
|
308
|
-
|
|
355
|
+
$matchList = @(Get-CommandLineInvocation -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath)
|
|
356
|
+
if (@($matchList | Where-Object { $_.Status -eq 'Structural' }).Count -eq 0) {
|
|
357
|
+
$comparison = [System.StringComparison]::OrdinalIgnoreCase
|
|
358
|
+
foreach ($match in @($matchList | Where-Object { $_.Status -eq 'Indeterminate' })) {
|
|
359
|
+
if ($match.Segment.RawText.IndexOf('--body-file', $comparison) -ge 0) {
|
|
360
|
+
$hasBodyFile = $true
|
|
361
|
+
} elseif ($match.Segment.RawText.IndexOf('--body', $comparison) -ge 0) {
|
|
362
|
+
$hasInlineBody = $true
|
|
363
|
+
}
|
|
309
364
|
}
|
|
310
365
|
}
|
|
311
366
|
}
|
|
@@ -330,36 +385,34 @@ function Get-PrAuthorBypassReason {
|
|
|
330
385
|
}
|
|
331
386
|
}
|
|
332
387
|
|
|
333
|
-
#
|
|
334
|
-
|
|
335
|
-
|
|
388
|
+
# Issue #850: the worktree whose artifacts are read is resolved once, before Case C. Epic
|
|
389
|
+
# scope (issue #663) selects the epic checkpoint's worktree and reads no per-feature
|
|
390
|
+
# checkpoint; otherwise identity resolution selects the item worktree, and an unresolved
|
|
391
|
+
# target denies here, ahead of PR_CONTEXT_MISSING.
|
|
392
|
+
$artifact = Resolve-PrAuthorArtifactRoot -CommandText $CommandText
|
|
393
|
+
if ($artifact.Reason) {
|
|
394
|
+
return $artifact.Reason
|
|
336
395
|
}
|
|
337
396
|
|
|
338
|
-
#
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
$epicScope = $null
|
|
343
|
-
if ($hasBodyFile -and $ContextExists) {
|
|
344
|
-
$epicScope = Resolve-EpicScopeCheckpoint -Text $CommandText -SessionRoot (Get-Location).Path
|
|
397
|
+
# Case C: --body-file present but the context artifact is absent beneath the resolved worktree.
|
|
398
|
+
$summaryPath = Join-WorktreeResolutionPath -WorktreeRoot $artifact.ArtifactRoot -RepoRelativePath $script:PrContextArtifactPath
|
|
399
|
+
if (-not (Get-PrContextArtifactExistence -Path $summaryPath)) {
|
|
400
|
+
return "PR_CONTEXT_MISSING: '$script:PrContextArtifactPath' in worktree '$($artifact.ArtifactRoot)' is absent. Run ``mcp__drm-copilot__collect_pr_context`` in that worktree before creating or editing the PR body."
|
|
345
401
|
}
|
|
346
402
|
|
|
347
403
|
# Orchestrator-state preflight: runs inside this same PreToolUse hook (so it cannot be
|
|
348
|
-
# bypassed by invoking gh pr create/edit directly) before receipt verification.
|
|
349
|
-
|
|
404
|
+
# bypassed by invoking gh pr create/edit directly) before receipt verification. The
|
|
405
|
+
# checkpoint path is assigned once here and read by every later consumer, so the
|
|
406
|
+
# preflight, the receipt verifier, and the epic base-branch check gate one item.
|
|
407
|
+
if ($null -ne $artifact.EpicScope) {
|
|
408
|
+
$epicScope = $artifact.EpicScope
|
|
350
409
|
$failure = Get-EpicPrCreationReadinessFailure -Checkpoint $epicScope.Checkpoint -HeadBranch $epicScope.Branch
|
|
351
410
|
if ($failure) {
|
|
352
411
|
return "ORCHESTRATOR_STATE_PREFLIGHT_FAILED: this epic-scope pull request was evaluated against $($epicScope.CheckpointPath), and the failed readiness predicate is '$failure'."
|
|
353
412
|
}
|
|
354
|
-
$script:OrchestratorStateCheckpointPath = $
|
|
355
|
-
}
|
|
356
|
-
$
|
|
357
|
-
if ($resolution.Reason) {
|
|
358
|
-
return $resolution.Reason
|
|
359
|
-
}
|
|
360
|
-
# Assigned once here and read by every later consumer, so the preflight, the receipt
|
|
361
|
-
# verifier, and the epic base-branch check cannot disagree about which item is gated.
|
|
362
|
-
$script:OrchestratorStateCheckpointPath = $resolution.CheckpointPath
|
|
413
|
+
$script:OrchestratorStateCheckpointPath = $artifact.CheckpointPath
|
|
414
|
+
} else {
|
|
415
|
+
$script:OrchestratorStateCheckpointPath = $artifact.CheckpointPath
|
|
363
416
|
$checkpointPath = $script:OrchestratorStateCheckpointPath
|
|
364
417
|
$preflightResult = Invoke-OrchestratorStatePreflight -CheckpointPath $checkpointPath
|
|
365
418
|
if ($preflightResult.HasErrors) {
|
|
@@ -368,16 +421,17 @@ function Get-PrAuthorBypassReason {
|
|
|
368
421
|
} else {
|
|
369
422
|
$preflightResult.ErrorText
|
|
370
423
|
}
|
|
371
|
-
|
|
424
|
+
$exemption = Get-PrAuthorDocsOnlyExemption -CommandText $CommandText -WorktreeRoot $artifact.ArtifactRoot -CheckpointPath $checkpointPath
|
|
425
|
+
if (-not $exemption.Exempt) {
|
|
426
|
+
return "ORCHESTRATOR_STATE_PREFLIGHT_FAILED: $preflightSummary (docs-only exemption not applied: $($exemption.Reason))"
|
|
427
|
+
}
|
|
372
428
|
}
|
|
373
429
|
}
|
|
374
430
|
|
|
375
431
|
# Receipt verification: extends, and does not replace, the previously-allowed path.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return $receiptReason
|
|
380
|
-
}
|
|
432
|
+
$receiptReason = Test-PrAuthorReceiptVerification -CommandText $CommandText -CheckpointPath $script:OrchestratorStateCheckpointPath -EpicScope $artifact.EpicScope -ArtifactRoot $artifact.ArtifactRoot -RelativeBodyAllowed $artifact.RelativeBodyAllowed
|
|
433
|
+
if ($receiptReason) {
|
|
434
|
+
return $receiptReason
|
|
381
435
|
}
|
|
382
436
|
|
|
383
437
|
return $null
|