@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
package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.artifact-root.ps1
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Artifact-root resolution and the Check 1 body-path binding for enforce-pr-author-skill.ps1 (issue #850).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Dot-sourced by enforce-pr-author-skill-helpers.ps1 after its module imports. Holds:
|
|
7
|
+
|
|
8
|
+
- Resolve-PrAuthorArtifactRoot, which resolves once per call the worktree whose
|
|
9
|
+
artifacts/ tree the gate reads: the epic checkpoint's worktree under epic scope, or
|
|
10
|
+
the item worktree identity resolution selects. The PR context summary, the body, and
|
|
11
|
+
the receipt are composed beneath that root, so a gate running in the session worktree
|
|
12
|
+
no longer reads another item's artifacts from its own process directory.
|
|
13
|
+
- Get-PrAuthorBodyPathBindingReason, the Check 1 binding: a relative canonical
|
|
14
|
+
--body-file value is accepted only when the resolved worktree is the session worktree,
|
|
15
|
+
because gh reads a relative path from its own process directory; an absolute value is
|
|
16
|
+
accepted only when it equals the canonical path beneath the resolved root.
|
|
17
|
+
- Test-PrAuthorBodyPathEqual and Get-PrAuthorEpicArtifactRoot, the pure path helpers.
|
|
18
|
+
- ConvertTo-PrAuthorBodyNumber, the strict body-number parse (issue #901, #850 note L19).
|
|
19
|
+
|
|
20
|
+
.NOTES
|
|
21
|
+
PowerShell 7+. Depends, at call time, on Get-PrAuthorBodyFileValue and
|
|
22
|
+
Get-PrAuthorTargetCheckpointResolution from the helpers file and on the worktree-resolution
|
|
23
|
+
modules it imports. It reads no file. Mirrored byte-identically under
|
|
24
|
+
extensions/drm-copilot/resources/claude-customizations/.
|
|
25
|
+
#>
|
|
26
|
+
[CmdletBinding()]
|
|
27
|
+
param()
|
|
28
|
+
|
|
29
|
+
function Test-PrAuthorBodyPathEqual {
|
|
30
|
+
<#
|
|
31
|
+
.SYNOPSIS
|
|
32
|
+
Compare two paths: case-insensitive for drive-letter and UNC paths, case-sensitive otherwise.
|
|
33
|
+
.PARAMETER Left
|
|
34
|
+
The first path, in either separator style.
|
|
35
|
+
.PARAMETER Right
|
|
36
|
+
The second path, in either separator style.
|
|
37
|
+
.OUTPUTS
|
|
38
|
+
System.Boolean
|
|
39
|
+
#>
|
|
40
|
+
[CmdletBinding()]
|
|
41
|
+
[OutputType([bool])]
|
|
42
|
+
param(
|
|
43
|
+
[AllowNull()][AllowEmptyString()][string] $Left,
|
|
44
|
+
[AllowNull()][AllowEmptyString()][string] $Right
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
$a = ConvertTo-WorktreeResolutionNormalizedPath -Path $Left
|
|
48
|
+
$b = ConvertTo-WorktreeResolutionNormalizedPath -Path $Right
|
|
49
|
+
if ($null -eq $a -or $null -eq $b) {
|
|
50
|
+
return $false
|
|
51
|
+
}
|
|
52
|
+
$comparison = if ($a -match '^([A-Za-z]:|//)' -or $b -match '^([A-Za-z]:|//)') {
|
|
53
|
+
[System.StringComparison]::OrdinalIgnoreCase
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
[System.StringComparison]::Ordinal
|
|
57
|
+
}
|
|
58
|
+
return [string]::Equals($a, $b, $comparison)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function Get-PrAuthorEpicArtifactRoot {
|
|
62
|
+
<#
|
|
63
|
+
.SYNOPSIS
|
|
64
|
+
Return the worktree root that holds an epic checkpoint path, or $null.
|
|
65
|
+
.PARAMETER CheckpointPath
|
|
66
|
+
The absolute epic checkpoint path the epic-scope resolver composed.
|
|
67
|
+
.OUTPUTS
|
|
68
|
+
System.String or $null
|
|
69
|
+
#>
|
|
70
|
+
[CmdletBinding()]
|
|
71
|
+
[OutputType([string])]
|
|
72
|
+
param(
|
|
73
|
+
[AllowNull()][AllowEmptyString()][string] $CheckpointPath
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
$normalized = ConvertTo-WorktreeResolutionNormalizedPath -Path $CheckpointPath
|
|
77
|
+
$suffix = '/' + (Get-EpicScopeCheckpointRelativePath)
|
|
78
|
+
if ($null -eq $normalized -or -not $normalized.EndsWith($suffix, [System.StringComparison]::Ordinal)) {
|
|
79
|
+
return $null
|
|
80
|
+
}
|
|
81
|
+
$root = $normalized.Substring(0, $normalized.Length - $suffix.Length)
|
|
82
|
+
if ($root.Length -eq 0) {
|
|
83
|
+
return $null
|
|
84
|
+
}
|
|
85
|
+
return $root
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function Resolve-PrAuthorArtifactRoot {
|
|
89
|
+
<#
|
|
90
|
+
.SYNOPSIS
|
|
91
|
+
Resolve, once per call, the worktree whose artifacts the gate reads (issue #850).
|
|
92
|
+
.DESCRIPTION
|
|
93
|
+
Epic scope: the root is the worktree holding the epic checkpoint, and a relative body
|
|
94
|
+
path is allowed only when that root is the session worktree. Otherwise the item target
|
|
95
|
+
is resolved by identity; an unresolved target returns its resolution reason unchanged,
|
|
96
|
+
and a relative body path is allowed only for a SessionRoot target.
|
|
97
|
+
.PARAMETER CommandText
|
|
98
|
+
The Bash command text under evaluation.
|
|
99
|
+
.OUTPUTS
|
|
100
|
+
System.Collections.Specialized.OrderedDictionary with Reason, ArtifactRoot,
|
|
101
|
+
CheckpointPath, EpicScope, Status, and RelativeBodyAllowed.
|
|
102
|
+
#>
|
|
103
|
+
[CmdletBinding()]
|
|
104
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
105
|
+
param(
|
|
106
|
+
[Parameter(Mandatory)]
|
|
107
|
+
[string] $CommandText
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
$epicScope = Resolve-EpicScopeCheckpoint -Text $CommandText -SessionRoot (Get-Location).Path
|
|
111
|
+
if ($null -ne $epicScope -and $epicScope.IsEpicScope) {
|
|
112
|
+
$root = Get-PrAuthorEpicArtifactRoot -CheckpointPath $epicScope.CheckpointPath
|
|
113
|
+
$reason = $null
|
|
114
|
+
$relativeAllowed = $false
|
|
115
|
+
if ($null -eq $root) {
|
|
116
|
+
$reason = "ORCHESTRATOR_STATE_PREFLIGHT_FAILED: the epic checkpoint path '$($epicScope.CheckpointPath)' does not end with '$(Get-EpicScopeCheckpointRelativePath)', so the worktree that holds it cannot be derived."
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
$relativeAllowed = Test-PrAuthorBodyPathEqual -Left $root -Right $epicScope.WorktreeRoot
|
|
120
|
+
}
|
|
121
|
+
return [ordered]@{
|
|
122
|
+
Reason = $reason
|
|
123
|
+
ArtifactRoot = $root
|
|
124
|
+
CheckpointPath = $epicScope.CheckpointPath
|
|
125
|
+
EpicScope = $epicScope
|
|
126
|
+
Status = 'Epic'
|
|
127
|
+
RelativeBodyAllowed = $relativeAllowed
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
$resolution = Get-PrAuthorTargetCheckpointResolution -CommandText $CommandText
|
|
132
|
+
$resolved = -not $resolution.Reason
|
|
133
|
+
return [ordered]@{
|
|
134
|
+
Reason = $resolution.Reason
|
|
135
|
+
ArtifactRoot = if ($resolved) { ConvertTo-WorktreeResolutionNormalizedPath -Path $resolution.WorktreeRoot } else { $null }
|
|
136
|
+
CheckpointPath = $resolution.CheckpointPath
|
|
137
|
+
EpicScope = $null
|
|
138
|
+
Status = $resolution.Status
|
|
139
|
+
RelativeBodyAllowed = ($resolved -and $resolution.Status -eq 'SessionRoot')
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function ConvertTo-PrAuthorBodyNumber {
|
|
144
|
+
<#
|
|
145
|
+
.SYNOPSIS
|
|
146
|
+
Parse the <N> of artifacts/pr_body_<N>.md, or return $null.
|
|
147
|
+
.DESCRIPTION
|
|
148
|
+
Accepts only ASCII digits that parse as an Int32 and whose canonical invariant spelling
|
|
149
|
+
equals the input, so a value above Int32.MaxValue, a leading zero, and a non-ASCII
|
|
150
|
+
decimal digit all return $null instead of throwing or naming a different file.
|
|
151
|
+
.PARAMETER Digits
|
|
152
|
+
The digit run captured from the body-file name.
|
|
153
|
+
.OUTPUTS
|
|
154
|
+
System.Int32 or $null
|
|
155
|
+
#>
|
|
156
|
+
[CmdletBinding()]
|
|
157
|
+
[OutputType([int])]
|
|
158
|
+
param(
|
|
159
|
+
[AllowNull()][AllowEmptyString()][string] $Digits
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
$number = 0
|
|
163
|
+
$parsed = [int]::TryParse($Digits, [System.Globalization.NumberStyles]::None, [System.Globalization.CultureInfo]::InvariantCulture, [ref] $number)
|
|
164
|
+
if (-not $parsed -or $number.ToString([System.Globalization.CultureInfo]::InvariantCulture) -cne $Digits) {
|
|
165
|
+
return $null
|
|
166
|
+
}
|
|
167
|
+
return $number
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function Get-PrAuthorBodyPathBindingReason {
|
|
171
|
+
<#
|
|
172
|
+
.SYNOPSIS
|
|
173
|
+
Check 1: bind the --body-file value to the canonical body path beneath the artifact root.
|
|
174
|
+
.DESCRIPTION
|
|
175
|
+
Reads the value once through Get-PrAuthorBodyFileValue. A relative
|
|
176
|
+
artifacts/pr_body_<N>.md (case-sensitive) is accepted only when RelativeBodyAllowed is
|
|
177
|
+
true; otherwise the deny names the absolute canonical path to pass instead. An absolute
|
|
178
|
+
value is accepted only when it equals that canonical path under Test-PrAuthorBodyPathEqual.
|
|
179
|
+
Any other value, including no readable value, denies with the generic text.
|
|
180
|
+
.PARAMETER CommandText
|
|
181
|
+
The Bash command text under evaluation.
|
|
182
|
+
.PARAMETER ArtifactRoot
|
|
183
|
+
The resolved worktree root the artifacts are read beneath.
|
|
184
|
+
.PARAMETER RelativeBodyAllowed
|
|
185
|
+
Whether a relative body path is read from the resolved worktree.
|
|
186
|
+
.OUTPUTS
|
|
187
|
+
System.Collections.Specialized.OrderedDictionary with Reason and BodyNumber.
|
|
188
|
+
#>
|
|
189
|
+
[CmdletBinding()]
|
|
190
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
191
|
+
param(
|
|
192
|
+
[Parameter(Mandatory)]
|
|
193
|
+
[string] $CommandText,
|
|
194
|
+
|
|
195
|
+
[Parameter(Mandatory)]
|
|
196
|
+
[string] $ArtifactRoot,
|
|
197
|
+
|
|
198
|
+
[bool] $RelativeBodyAllowed
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
$generic = "PR_BODY_PATH_NONCANONICAL: ``--body-file`` must reference the canonical ``artifacts/pr_body_<N>.md`` file produced by the pr-author skill beneath the target worktree '$ArtifactRoot' (relative only when that worktree is the session worktree). The path supplied does not match."
|
|
202
|
+
$value = ConvertTo-WorktreeResolutionNormalizedPath -Path (Get-PrAuthorBodyFileValue -CommandText $CommandText)
|
|
203
|
+
if ($null -eq $value) {
|
|
204
|
+
return [ordered]@{ Reason = $generic; BodyNumber = $null }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if ($value -cmatch '^artifacts/pr_body_(\d+)\.md$') {
|
|
208
|
+
$number = ConvertTo-PrAuthorBodyNumber -Digits $Matches[1]
|
|
209
|
+
if ($null -eq $number) {
|
|
210
|
+
return [ordered]@{ Reason = $generic; BodyNumber = $null }
|
|
211
|
+
}
|
|
212
|
+
if (-not $RelativeBodyAllowed) {
|
|
213
|
+
$canonical = Join-WorktreeResolutionPath -WorktreeRoot $ArtifactRoot -RepoRelativePath "artifacts/pr_body_$number.md"
|
|
214
|
+
return [ordered]@{
|
|
215
|
+
Reason = "PR_BODY_PATH_NONCANONICAL: the target worktree '$ArtifactRoot' is not the session worktree, so gh would read a relative --body-file from the session worktree. Pass the absolute canonical path '$canonical'."
|
|
216
|
+
BodyNumber = $null
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return [ordered]@{ Reason = $null; BodyNumber = $number }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if ($value -match '/artifacts/pr_body_(\d+)\.md$') {
|
|
223
|
+
$number = ConvertTo-PrAuthorBodyNumber -Digits $Matches[1]
|
|
224
|
+
if ($null -ne $number) {
|
|
225
|
+
$canonical = Join-WorktreeResolutionPath -WorktreeRoot $ArtifactRoot -RepoRelativePath "artifacts/pr_body_$number.md"
|
|
226
|
+
if (Test-PrAuthorBodyPathEqual -Left $value -Right $canonical) {
|
|
227
|
+
return [ordered]@{ Reason = $null; BodyNumber = $number }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return [ordered]@{ Reason = $generic; BodyNumber = $null }
|
|
233
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Docs-only exemption from the checkpoint preflight of enforce-pr-author-skill.ps1 (issue #901).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Dot-sourced by enforce-pr-author-skill-helpers.ps1. Get-PrAuthorBypassReason calls
|
|
7
|
+
Get-PrAuthorDocsOnlyExemption only in its non-epic branch and only after
|
|
8
|
+
Invoke-OrchestratorStatePreflight reports errors. The exemption lets a gh pr create call
|
|
9
|
+
continue to the receipt checks when the branch changes nothing outside docs/. It fails
|
|
10
|
+
closed: every condition below must hold, and the first that does not names the sub-code
|
|
11
|
+
the caller appends to its ORCHESTRATOR_STATE_PREFLIGHT_FAILED denial.
|
|
12
|
+
|
|
13
|
+
E1 DOCS_ONLY_EXEMPTION_NOT_CREATE - the command is a structural gh pr create.
|
|
14
|
+
E2 DOCS_ONLY_EXEMPTION_CHECKPOINT_PRESENT - no checkpoint file exists at the resolved path.
|
|
15
|
+
E3 DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED - exactly one explicit --base, a conservative branch
|
|
16
|
+
spelling without '..', and no base or repository
|
|
17
|
+
override: no single-dash token whose letters
|
|
18
|
+
include B or R (-B, -R, -dB, -Rx), no --repo or
|
|
19
|
+
--repo= token, and no inline GH_REPO= prefix.
|
|
20
|
+
E4 DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED - refs/remotes/origin/<base> resolves to a commit.
|
|
21
|
+
E5 DOCS_ONLY_EXEMPTION_DIFF_FAILED - git diff <commit>...HEAD exits 0 without throwing,
|
|
22
|
+
and non-empty output ends with NUL.
|
|
23
|
+
E6 DOCS_ONLY_EXEMPTION_EMPTY_DIFF - the diff lists at least one path.
|
|
24
|
+
E7 DOCS_ONLY_EXEMPTION_NON_DOCS_PATH - every path starts with docs/ (ordinal).
|
|
25
|
+
|
|
26
|
+
The diff runs with --no-renames so a rename from outside docs/ reports its old path, with -z
|
|
27
|
+
so no path is quoted, and with --no-relative, --no-color, and --no-ext-diff so repository
|
|
28
|
+
configuration cannot change the listing.
|
|
29
|
+
|
|
30
|
+
Residual limit: a GH_REPO value exported by an earlier shell command or inherited from the
|
|
31
|
+
session environment is not visible in the command text, so this file cannot detect it.
|
|
32
|
+
|
|
33
|
+
.NOTES
|
|
34
|
+
PowerShell 7+. Depends, at call time, on Test-CommandLineInvocation,
|
|
35
|
+
Get-CommandLineStructuralMatch, and Get-CommandLineFlagValue from the command parsers the
|
|
36
|
+
helpers file loads. It reads no file content. The git executable runs only through
|
|
37
|
+
Invoke-PrAuthorDocsOnlyGitExe, the single executable seam, which never throws. Mirrored
|
|
38
|
+
byte-identically under extensions/drm-copilot/resources/claude-customizations/.
|
|
39
|
+
Dot-sources hook-docs-only-diff.ps1 (issue #927), whose Get-DocsOnlyDiffTextViolation
|
|
40
|
+
performs the E5 NUL check and the E6 and E7 path classification.
|
|
41
|
+
#>
|
|
42
|
+
[CmdletBinding()]
|
|
43
|
+
param()
|
|
44
|
+
. (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1')
|
|
45
|
+
|
|
46
|
+
function Invoke-PrAuthorDocsOnlyGitExe {
|
|
47
|
+
<#
|
|
48
|
+
.SYNOPSIS
|
|
49
|
+
Run git -C <root> with the given arguments. The single executable seam; tests mock it.
|
|
50
|
+
.DESCRIPTION
|
|
51
|
+
Never throws: an uncaught error ends the hook with exit 1, which PreToolUse treats as
|
|
52
|
+
non-blocking, so a throw here would allow the call. A root that is not an existing
|
|
53
|
+
directory, or a git that cannot start, returns ExitCode -1. Standard error is discarded
|
|
54
|
+
so that it is never read as path data.
|
|
55
|
+
.PARAMETER WorktreeRoot
|
|
56
|
+
The resolved worktree root git runs in.
|
|
57
|
+
.PARAMETER GitArgs
|
|
58
|
+
The git arguments that follow -C <root>.
|
|
59
|
+
.PARAMETER GitInvoker
|
|
60
|
+
The scriptblock that runs git; a unit test passes a test-local stand-in so that this
|
|
61
|
+
body runs without starting a process.
|
|
62
|
+
.OUTPUTS
|
|
63
|
+
System.Management.Automation.PSCustomObject with ExitCode and Output.
|
|
64
|
+
#>
|
|
65
|
+
[CmdletBinding()]
|
|
66
|
+
[OutputType([pscustomobject])]
|
|
67
|
+
param(
|
|
68
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreeRoot,
|
|
69
|
+
[Parameter(Mandatory)][string[]] $GitArgs,
|
|
70
|
+
[ValidateNotNull()][scriptblock] $GitInvoker = { git @args 2>$null }
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
$failure = [pscustomobject]@{ ExitCode = -1; Output = '' }
|
|
74
|
+
if ([string]::IsNullOrWhiteSpace($WorktreeRoot) -or -not (Test-Path -LiteralPath $WorktreeRoot -PathType Container)) {
|
|
75
|
+
return $failure
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
$lines = @(& $GitInvoker -C $WorktreeRoot @GitArgs 2>$null)
|
|
79
|
+
$code = $LASTEXITCODE
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return $failure
|
|
83
|
+
}
|
|
84
|
+
return [pscustomobject]@{ ExitCode = [int]$code; Output = ($lines -join "`n") }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function Test-PrAuthorDocsOnlyCheckpointPresence {
|
|
88
|
+
<#
|
|
89
|
+
.SYNOPSIS
|
|
90
|
+
Return $true when a checkpoint file exists at the path, and also for a blank path.
|
|
91
|
+
.DESCRIPTION
|
|
92
|
+
A blank path cannot prove that no checkpoint exists, so it reports presence (fail closed).
|
|
93
|
+
.PARAMETER CheckpointPath
|
|
94
|
+
The absolute checkpoint path identity resolution composed.
|
|
95
|
+
.OUTPUTS
|
|
96
|
+
System.Boolean
|
|
97
|
+
#>
|
|
98
|
+
[CmdletBinding()]
|
|
99
|
+
[OutputType([bool])]
|
|
100
|
+
param(
|
|
101
|
+
[AllowNull()][AllowEmptyString()][string] $CheckpointPath
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if ([string]::IsNullOrWhiteSpace($CheckpointPath)) {
|
|
105
|
+
return $true
|
|
106
|
+
}
|
|
107
|
+
return (Test-Path -LiteralPath $CheckpointPath -PathType Leaf)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function Get-PrAuthorDocsOnlyBaseName {
|
|
111
|
+
<#
|
|
112
|
+
.SYNOPSIS
|
|
113
|
+
Return the single explicit --base value of a gh pr create command, or $null.
|
|
114
|
+
.DESCRIPTION
|
|
115
|
+
Returns $null when the command is not a structural gh pr create; when a single-dash
|
|
116
|
+
token has B or R among its letters (-B base and -R repository, alone or clustered as in
|
|
117
|
+
-dB or -dR); when a token is --repo, starts with --repo=, or starts with GH_REPO=; when
|
|
118
|
+
--base appears other than exactly once; when its value is missing or dash-leading; when
|
|
119
|
+
the value is not a conservative branch spelling; or when it contains '..'.
|
|
120
|
+
.PARAMETER CommandText
|
|
121
|
+
The Bash command text under evaluation.
|
|
122
|
+
.OUTPUTS
|
|
123
|
+
System.String or $null
|
|
124
|
+
#>
|
|
125
|
+
[CmdletBinding()]
|
|
126
|
+
[OutputType([string])]
|
|
127
|
+
param(
|
|
128
|
+
[Parameter(Mandatory)][AllowEmptyString()][string] $CommandText
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
$subcommandPath = @('pr', 'create')
|
|
132
|
+
$match = Get-CommandLineStructuralMatch -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath
|
|
133
|
+
if ($null -eq $match) {
|
|
134
|
+
return $null
|
|
135
|
+
}
|
|
136
|
+
$baseTokenCount = 0
|
|
137
|
+
foreach ($token in @($match.Segment.Tokens)) {
|
|
138
|
+
$text = [string]$token
|
|
139
|
+
if ($text.Length -ge 2 -and $text[0] -eq '-' -and $text[1] -ne '-' -and $text.Substring(1).IndexOfAny([char[]]@('B', 'R')) -ge 0) {
|
|
140
|
+
return $null
|
|
141
|
+
}
|
|
142
|
+
if ($text -eq '--repo' -or $text.StartsWith('--repo=', [System.StringComparison]::OrdinalIgnoreCase) -or $text.StartsWith('GH_REPO=', [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
143
|
+
return $null
|
|
144
|
+
}
|
|
145
|
+
if ($text -eq '--base' -or $text.StartsWith('--base=', [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
146
|
+
$baseTokenCount++
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if ($baseTokenCount -ne 1) {
|
|
150
|
+
return $null
|
|
151
|
+
}
|
|
152
|
+
$base = Get-CommandLineFlagValue -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath -FlagName '--base'
|
|
153
|
+
if ($null -eq $base -or $base -cnotmatch '^[A-Za-z0-9][A-Za-z0-9._/-]*$' -or $base.Contains('..')) {
|
|
154
|
+
return $null
|
|
155
|
+
}
|
|
156
|
+
return $base
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function Get-PrAuthorDocsOnlyExemption {
|
|
160
|
+
<#
|
|
161
|
+
.SYNOPSIS
|
|
162
|
+
Decide whether a failed preflight is exempt because the branch changes only docs/.
|
|
163
|
+
.DESCRIPTION
|
|
164
|
+
Evaluates E1 to E7 of the file header in order and returns at the first failure.
|
|
165
|
+
Each git call is wrapped so that a throwing seam denies rather than escapes.
|
|
166
|
+
.PARAMETER CommandText
|
|
167
|
+
The Bash command text under evaluation.
|
|
168
|
+
.PARAMETER WorktreeRoot
|
|
169
|
+
The resolved worktree root the diff is computed in.
|
|
170
|
+
.PARAMETER CheckpointPath
|
|
171
|
+
The resolved checkpoint path whose absence E2 requires.
|
|
172
|
+
.OUTPUTS
|
|
173
|
+
System.Collections.Specialized.OrderedDictionary with Exempt and Reason.
|
|
174
|
+
#>
|
|
175
|
+
[CmdletBinding()]
|
|
176
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
177
|
+
param(
|
|
178
|
+
[Parameter(Mandatory)][string] $CommandText,
|
|
179
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreeRoot,
|
|
180
|
+
[AllowNull()][AllowEmptyString()][string] $CheckpointPath
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if (-not (Test-CommandLineInvocation -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('pr', 'create'))) {
|
|
184
|
+
return [ordered]@{ Exempt = $false; Reason = 'DOCS_ONLY_EXEMPTION_NOT_CREATE: only gh pr create is eligible.' }
|
|
185
|
+
}
|
|
186
|
+
if (Test-PrAuthorDocsOnlyCheckpointPresence -CheckpointPath $CheckpointPath) {
|
|
187
|
+
return [ordered]@{ Exempt = $false; Reason = "DOCS_ONLY_EXEMPTION_CHECKPOINT_PRESENT: a checkpoint file exists at '$CheckpointPath', so its preflight governs." }
|
|
188
|
+
}
|
|
189
|
+
$base = Get-PrAuthorDocsOnlyBaseName -CommandText $CommandText
|
|
190
|
+
if ($null -eq $base) {
|
|
191
|
+
return [ordered]@{ Exempt = $false; Reason = 'DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED: pass exactly one explicit --base <branch>; -B, -R, --repo, a single-dash shorthand cluster containing B or R, and an inline GH_REPO= prefix are not accepted.' }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
$baseRef = 'refs/remotes/origin/' + $base + '^{commit}'
|
|
195
|
+
try {
|
|
196
|
+
$revParse = Invoke-PrAuthorDocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('rev-parse', '--verify', '--quiet', $baseRef)
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
$revParse = $null
|
|
200
|
+
}
|
|
201
|
+
$commit = ''
|
|
202
|
+
if ($null -ne $revParse) {
|
|
203
|
+
$commit = ([string]$revParse.Output).Trim()
|
|
204
|
+
}
|
|
205
|
+
if ($null -eq $revParse -or $revParse.ExitCode -ne 0 -or $commit -cnotmatch '^[0-9a-f]{40}([0-9a-f]{24})?$') {
|
|
206
|
+
return [ordered]@{ Exempt = $false; Reason = "DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED: refs/remotes/origin/$base does not resolve to a commit in the target worktree." }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
$diff = Invoke-PrAuthorDocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('diff', '--name-only', '-z', '--no-renames', '--no-color', '--no-ext-diff', '--no-relative', ($commit + '...HEAD'))
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
$diff = $null
|
|
214
|
+
}
|
|
215
|
+
if ($null -eq $diff -or $diff.ExitCode -ne 0) {
|
|
216
|
+
return [ordered]@{ Exempt = $false; Reason = "DOCS_ONLY_EXEMPTION_DIFF_FAILED: git diff against origin/$base could not be computed." }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
$violation = Get-DocsOnlyDiffTextViolation -DiffText ([string]$diff.Output) -BaseName $base
|
|
220
|
+
if ($violation) {
|
|
221
|
+
return [ordered]@{ Exempt = $false; Reason = $violation }
|
|
222
|
+
}
|
|
223
|
+
return [ordered]@{ Exempt = $true; Reason = $null }
|
|
224
|
+
}
|
|
@@ -19,18 +19,49 @@
|
|
|
19
19
|
Block cases:
|
|
20
20
|
Case A - gh pr create or gh pr edit with --body (inline, no --body-file): blocked.
|
|
21
21
|
Case B - gh pr create with neither --body nor --body-file: blocked.
|
|
22
|
-
Case C - gh pr create or gh pr edit with --body-file but context artifact absent
|
|
22
|
+
Case C - gh pr create or gh pr edit with --body-file but context artifact absent beneath
|
|
23
|
+
the resolved worktree: blocked.
|
|
23
24
|
Preflight - --body-file/context present: orchestrator-state checkpoint must pass
|
|
24
25
|
--require-pr-creation-ready before receipt verification runs, else blocked.
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
Docs-only - issue #901: when the preflight fails, gh pr create proceeds to receipt
|
|
27
|
+
verification only when every condition holds: no checkpoint file exists
|
|
28
|
+
beneath the resolved worktree; the command carries exactly one explicit
|
|
29
|
+
--base <branch> and no base or repository override (issue #927: no
|
|
30
|
+
single-dash token whose letters include B or R, such as -B, -R, or -dB; no
|
|
31
|
+
--repo or --repo= token; no inline GH_REPO= prefix); refs/remotes/origin/<branch>
|
|
32
|
+
resolves to a commit; git diff --name-only -z --no-renames <commit>...HEAD
|
|
33
|
+
exits 0, ends its non-empty output with NUL, and lists at least one path; and
|
|
34
|
+
every listed path starts with docs/ (ordinal, case-sensitive). Otherwise the
|
|
35
|
+
deny keeps ORCHESTRATOR_STATE_PREFLIGHT_FAILED and appends one sub-code:
|
|
36
|
+
DOCS_ONLY_EXEMPTION_NOT_CREATE - gh pr edit is never exempt.
|
|
37
|
+
DOCS_ONLY_EXEMPTION_CHECKPOINT_PRESENT - a checkpoint file exists.
|
|
38
|
+
DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED - no single explicit --base, a base or
|
|
39
|
+
repository override is present, or
|
|
40
|
+
the base does not resolve on origin.
|
|
41
|
+
DOCS_ONLY_EXEMPTION_DIFF_FAILED - the diff exits non-zero, throws, or
|
|
42
|
+
returns output not ending with NUL.
|
|
43
|
+
DOCS_ONLY_EXEMPTION_EMPTY_DIFF - the diff lists no path.
|
|
44
|
+
DOCS_ONLY_EXEMPTION_NON_DOCS_PATH - a listed path is outside docs/.
|
|
45
|
+
The exemption never applies under epic scope.
|
|
46
|
+
Receipt - preflight passed or docs-only exemption applied: the SHA-256 receipt is
|
|
47
|
+
verified in the ordered checks (Section below). The first failing check blocks.
|
|
27
48
|
|
|
28
49
|
Receipt verification decision order on the --body-file-with-context path:
|
|
29
50
|
PR_BODY_PATH_NONCANONICAL -> PR_AUTHOR_RECEIPT_MISSING -> PR_AUTHOR_RECEIPT_NUMBER_MISMATCH
|
|
30
51
|
-> PR_AUTHOR_RECEIPT_HASH_MISMATCH -> PR_AUTHOR_RECEIPT_STALE -> allow.
|
|
31
52
|
|
|
53
|
+
Issue #850: the context summary, the body, and the receipt are read beneath the worktree
|
|
54
|
+
the call resolves to (the item worktree, or the epic checkpoint's worktree under epic
|
|
55
|
+
scope), not beneath the process directory, so a gate running in the session worktree
|
|
56
|
+
validates the artifacts of the item it gates.
|
|
57
|
+
|
|
32
58
|
.NOTES
|
|
33
|
-
Compatible with PowerShell 7+.
|
|
59
|
+
Compatible with PowerShell 7+. Dependencies are in-repository only: hook-dependency-guard.ps1,
|
|
60
|
+
the HookPayload, OrchestratorState, and OrchestratorStateUnconditional modules, and the
|
|
61
|
+
epic-base-branch and helpers siblings; the helpers load the command parsers, the
|
|
62
|
+
worktree-resolution modules, and the artifact-root and docs-only siblings.
|
|
63
|
+
No PowerShell Gallery module is required.
|
|
64
|
+
The docs-only exemption runs the git executable at call time (issue #901).
|
|
34
65
|
|
|
35
66
|
Enforcement strength: the SHA-256 receipt is a policy-level integrity check that binds the
|
|
36
67
|
PR body bytes to the receipt the pr-author agent wrote. It is not a cryptographic or security
|
|
@@ -42,28 +73,34 @@
|
|
|
42
73
|
#>
|
|
43
74
|
[CmdletBinding()]
|
|
44
75
|
param()
|
|
76
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
45
77
|
|
|
46
78
|
|
|
47
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
79
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
|
|
48
80
|
$script:PrContextArtifactPath = 'artifacts/pr_context.summary.txt'
|
|
49
81
|
# Null until the resolution step assigns the absolute path of the resolved worktree's
|
|
50
82
|
# checkpoint (issue #673); a relative default here was the process-directory binding.
|
|
51
83
|
$script:OrchestratorStateCheckpointPath = $null
|
|
52
84
|
|
|
53
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorState.psm1') -Force
|
|
85
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorState.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'OrchestratorState.psm1' -ErrorRecord $_ }
|
|
54
86
|
|
|
55
87
|
function Get-PrContextArtifactExistence {
|
|
56
88
|
<#
|
|
57
89
|
.SYNOPSIS
|
|
58
90
|
Wrapper around Test-Path for the PR context artifact. Tests mock this function.
|
|
91
|
+
.PARAMETER Path
|
|
92
|
+
The absolute path of artifacts/pr_context.summary.txt beneath the resolved worktree.
|
|
59
93
|
.OUTPUTS
|
|
60
94
|
System.Boolean
|
|
61
95
|
#>
|
|
62
96
|
[CmdletBinding()]
|
|
63
97
|
[OutputType([bool])]
|
|
64
|
-
param(
|
|
98
|
+
param(
|
|
99
|
+
[Parameter(Mandatory)]
|
|
100
|
+
[string] $Path
|
|
101
|
+
)
|
|
65
102
|
|
|
66
|
-
return [bool](Test-Path -LiteralPath $
|
|
103
|
+
return [bool](Test-Path -LiteralPath $Path)
|
|
67
104
|
}
|
|
68
105
|
|
|
69
106
|
function Get-PrBodyFileBytes {
|
|
@@ -75,7 +112,8 @@ function Get-PrBodyFileBytes {
|
|
|
75
112
|
This is the injectable boundary for body-file bytes in tests; no test writes the body file
|
|
76
113
|
to disk. The bytes are hashed inline by the receipt verification function.
|
|
77
114
|
.PARAMETER BodyFilePath
|
|
78
|
-
The
|
|
115
|
+
The absolute path to the PR body file beneath the resolved worktree (for example
|
|
116
|
+
<worktree>/artifacts/pr_body_5.md).
|
|
79
117
|
.OUTPUTS
|
|
80
118
|
System.Byte[] or $null
|
|
81
119
|
#>
|
|
@@ -103,7 +141,8 @@ function Get-PrAuthorReceiptContent {
|
|
|
103
141
|
or $null when the receipt file is absent. This is the injectable boundary for receipt
|
|
104
142
|
content in tests; no test writes the receipt file to disk.
|
|
105
143
|
.PARAMETER ReceiptFilePath
|
|
106
|
-
The
|
|
144
|
+
The absolute path to the receipt file beneath the resolved worktree (for example
|
|
145
|
+
<worktree>/artifacts/pr_body_5.receipt.json).
|
|
107
146
|
.OUTPUTS
|
|
108
147
|
System.String or $null
|
|
109
148
|
#>
|
|
@@ -129,25 +168,32 @@ function Get-PrContextSummaryLastWriteUtc {
|
|
|
129
168
|
Returns the LastWriteTimeUtc of artifacts/pr_context.summary.txt, or $null when the file is
|
|
130
169
|
absent. The staleness check compares the receipt's created_at against this value; both are
|
|
131
170
|
artifact metadata, so no wall-clock seam is required.
|
|
171
|
+
.PARAMETER Path
|
|
172
|
+
The absolute path of artifacts/pr_context.summary.txt beneath the resolved worktree.
|
|
132
173
|
.OUTPUTS
|
|
133
174
|
System.DateTime or $null
|
|
134
175
|
#>
|
|
135
176
|
[CmdletBinding()]
|
|
136
177
|
[OutputType([datetime])]
|
|
137
|
-
param(
|
|
178
|
+
param(
|
|
179
|
+
[Parameter(Mandatory)]
|
|
180
|
+
[string] $Path
|
|
181
|
+
)
|
|
138
182
|
|
|
139
|
-
if (-not (Test-Path -LiteralPath $
|
|
183
|
+
if (-not (Test-Path -LiteralPath $Path)) {
|
|
140
184
|
return $null
|
|
141
185
|
}
|
|
142
186
|
|
|
143
|
-
return (Get-Item -LiteralPath $
|
|
187
|
+
return (Get-Item -LiteralPath $Path).LastWriteTimeUtc
|
|
144
188
|
}
|
|
145
189
|
|
|
146
|
-
. (Join-Path $PSScriptRoot 'enforce-pr-author-skill.epic-base-branch.ps1')
|
|
190
|
+
try { . (Join-Path $PSScriptRoot 'enforce-pr-author-skill.epic-base-branch.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-pr-author-skill.epic-base-branch.ps1' -ErrorRecord $_ }
|
|
147
191
|
|
|
148
192
|
# Dot-source the receipt-verification and bypass-reason helpers. Guarded so dot-sourcing this
|
|
149
193
|
# hook in tests loads the helpers too (issue #501 headroom split).
|
|
150
|
-
. (Join-Path $PSScriptRoot 'enforce-pr-author-skill-helpers.ps1')
|
|
194
|
+
try { . (Join-Path $PSScriptRoot 'enforce-pr-author-skill-helpers.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-pr-author-skill-helpers.ps1' -ErrorRecord $_ }
|
|
195
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorStateUnconditional.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'OrchestratorStateUnconditional.psm1' -ErrorRecord $_ }
|
|
196
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorState.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'OrchestratorState.psm1' -ErrorRecord $_ }
|
|
151
197
|
|
|
152
198
|
function Invoke-PrAuthorSkillDecision {
|
|
153
199
|
<#
|
|
@@ -168,6 +214,8 @@ function Invoke-PrAuthorSkillDecision {
|
|
|
168
214
|
[AllowEmptyString()]
|
|
169
215
|
[string] $ToolInputRaw
|
|
170
216
|
)
|
|
217
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PR_AUTHOR_SKILL_BLOCKED:'
|
|
218
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
171
219
|
|
|
172
220
|
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
173
221
|
if (-not $payload.IsValid) {
|
|
@@ -182,8 +230,7 @@ function Invoke-PrAuthorSkillDecision {
|
|
|
182
230
|
return Get-PrAuthorSkillAllowDecision
|
|
183
231
|
}
|
|
184
232
|
|
|
185
|
-
$
|
|
186
|
-
$reason = Get-PrAuthorBypassReason -CommandText $commandText -ContextExists $contextExists
|
|
233
|
+
$reason = Get-PrAuthorBypassReason -CommandText $commandText
|
|
187
234
|
|
|
188
235
|
if ($reason) {
|
|
189
236
|
return Get-PrAuthorSkillBlockDecision -Reason $reason
|
|
@@ -242,8 +289,6 @@ function Test-PrAuthorBypassRequired {
|
|
|
242
289
|
Return $true when a Bash command requires the pr-author skill to run first.
|
|
243
290
|
.PARAMETER CommandText
|
|
244
291
|
The Bash command text extracted from the envelope's tool_input.
|
|
245
|
-
.PARAMETER ContextExists
|
|
246
|
-
Whether artifacts/pr_context.summary.txt currently exists on disk.
|
|
247
292
|
.OUTPUTS
|
|
248
293
|
System.Boolean
|
|
249
294
|
#>
|
|
@@ -251,13 +296,10 @@ function Test-PrAuthorBypassRequired {
|
|
|
251
296
|
[OutputType([bool])]
|
|
252
297
|
param(
|
|
253
298
|
[Parameter(Mandatory)]
|
|
254
|
-
[string] $CommandText
|
|
255
|
-
|
|
256
|
-
[Parameter(Mandatory)]
|
|
257
|
-
[bool] $ContextExists
|
|
299
|
+
[string] $CommandText
|
|
258
300
|
)
|
|
259
301
|
|
|
260
|
-
return ($null -ne (Get-PrAuthorBypassReason -CommandText $CommandText
|
|
302
|
+
return ($null -ne (Get-PrAuthorBypassReason -CommandText $CommandText))
|
|
261
303
|
}
|
|
262
304
|
|
|
263
305
|
function Invoke-PrAuthorSkillEntryPoint {
|
|
@@ -302,6 +344,9 @@ function Invoke-PrAuthorSkillEntryPoint {
|
|
|
302
344
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
303
345
|
return
|
|
304
346
|
}
|
|
347
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('PR_AUTHOR_SKILL_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
348
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PR_AUTHOR_SKILL_BLOCKED:'
|
|
349
|
+
if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
|
|
305
350
|
|
|
306
351
|
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
307
352
|
# decision JSON before it. `exit (<call>)` would capture BOTH into the exit
|