@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,493 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Docs-only diff classification and the merge-side docs-only exemption (issue #927).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Shared by enforce-pr-author-skill.docs-only.ps1 (create side) and by the Claude and Codex
|
|
7
|
+
copies of enforce-epic-merge-gate.ps1 (merge side). The file defines functions only: it
|
|
8
|
+
imports nothing, dot-sources nothing, and has no entry point. At call time
|
|
9
|
+
Get-DocsOnlyMergeExemption uses Read-CommandLineSegment, Test-CommandLineSegmentRawScan,
|
|
10
|
+
Get-CommandLineStructuralMatch, Test-CommandLineFlag, and Get-CommandLineFlagValue from
|
|
11
|
+
the command parsers every consumer already loads.
|
|
12
|
+
|
|
13
|
+
Docs-only merge branch: a merge gate calls Get-DocsOnlyMergeExemption only for an explicit
|
|
14
|
+
pull request number that no checkpoint records. The exemption fails closed; conditions
|
|
15
|
+
are evaluated in order and the first failure returns its code:
|
|
16
|
+
|
|
17
|
+
M1 DOCS_ONLY_EXEMPTION_PR_UNRESOLVED - a positive number; exactly one segment that is
|
|
18
|
+
not wrapper-led, has no live substitution, and
|
|
19
|
+
is balanced; a structural gh pr merge; no inline
|
|
20
|
+
VAR= prefix token (GH_REPO=, GH_HOST=); no
|
|
21
|
+
single-dash token containing R; no --repo or
|
|
22
|
+
--repo= token; no --auto token.
|
|
23
|
+
M2 DOCS_ONLY_EXEMPTION_PR_UNRESOLVED - the worktree root is an existing directory.
|
|
24
|
+
M3 DOCS_ONLY_EXEMPTION_PR_UNRESOLVED - gh pr view <N> --json baseRefName,headRefOid
|
|
25
|
+
exits 0 and returns a JSON object; headRefOid
|
|
26
|
+
is 40 or 64 lowercase hex; a --match-head-commit
|
|
27
|
+
value, when present, equals headRefOid (OD-2).
|
|
28
|
+
DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED - baseRefName is a conservative branch spelling
|
|
29
|
+
without '..'.
|
|
30
|
+
M4 DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED - refs/remotes/origin/<base> resolves to a commit.
|
|
31
|
+
M5 DOCS_ONLY_EXEMPTION_DIFF_FAILED - headRefOid is present in the local repository.
|
|
32
|
+
M6 DOCS_ONLY_EXEMPTION_DIFF_FAILED - git diff --name-only -z --no-renames --no-color
|
|
33
|
+
--no-ext-diff --no-relative <base>...<head>
|
|
34
|
+
exits 0.
|
|
35
|
+
M7 DOCS_ONLY_EXEMPTION_DIFF_FAILED - non-empty diff output ends with NUL.
|
|
36
|
+
M8 DOCS_ONLY_EXEMPTION_EMPTY_DIFF - the diff lists at least one path.
|
|
37
|
+
M9 DOCS_ONLY_EXEMPTION_NON_DOCS_PATH - every listed path starts with docs/ (ordinal).
|
|
38
|
+
|
|
39
|
+
No-fetch policy: no function here fetches or writes a ref. A missing origin/<base> ref or
|
|
40
|
+
a head commit that is not present locally denies, and the M5 reason names the command
|
|
41
|
+
the operator can run before retrying.
|
|
42
|
+
|
|
43
|
+
Residual limits: a GH_REPO or GH_HOST value exported by an earlier shell command or
|
|
44
|
+
inherited from the session environment is not visible in the command text; gh and git
|
|
45
|
+
run in the WorktreeRoot the gate passes (the Claude session worktree root or the Codex
|
|
46
|
+
repository root), so a working directory set by an earlier shell call is not visible
|
|
47
|
+
either; a push between the gh pr view lookup and the merge can change the head, which
|
|
48
|
+
only --match-head-commit closes; the control is policy-level, not cryptographic.
|
|
49
|
+
|
|
50
|
+
Time limit: Claude Code lets a tool call continue through the permission flow when a
|
|
51
|
+
PreToolUse hook exceeds its timeout, and the repository records no Codex behavior for
|
|
52
|
+
that case, so this file bounds every gh and git call itself. The default invokers run
|
|
53
|
+
the executable through Invoke-DocsOnlyBoundedNative: at most 7000 ms for gh and 3000 ms
|
|
54
|
+
for each git call, plus at most 1000 ms for the output. An expired wait stops the
|
|
55
|
+
process tree and reports exit code 124, which denies. The worst case for one gh and
|
|
56
|
+
three git calls is 20 s, below the 30 s Codex registration timeout.
|
|
57
|
+
|
|
58
|
+
.NOTES
|
|
59
|
+
PowerShell 7+. Byte-identical at .claude/hooks, .codex/hooks, and both bundle mirrors.
|
|
60
|
+
git and gh run only through Invoke-DocsOnlyGitExe and Invoke-DocsOnlyGhExe, which never
|
|
61
|
+
throw; unit tests pass a test-local scriptblock through -GitInvoker and -GhInvoker so
|
|
62
|
+
the seam bodies run without starting a process, and pass a test-local process starter
|
|
63
|
+
through the -ProcessStarter parameter of Invoke-DocsOnlyBoundedNative.
|
|
64
|
+
#>
|
|
65
|
+
|
|
66
|
+
function ConvertFrom-DocsOnlyNameList {
|
|
67
|
+
<#
|
|
68
|
+
.SYNOPSIS
|
|
69
|
+
Split NUL-separated git output into paths, discarding empty elements.
|
|
70
|
+
.DESCRIPTION
|
|
71
|
+
Callers wrap the result in @() so that an empty listing is an empty array. A path that
|
|
72
|
+
contains a line feed stays one element, because only NUL separates entries.
|
|
73
|
+
.PARAMETER Text
|
|
74
|
+
The captured git output.
|
|
75
|
+
.OUTPUTS
|
|
76
|
+
System.String, one per path
|
|
77
|
+
#>
|
|
78
|
+
[CmdletBinding()]
|
|
79
|
+
[OutputType([string])]
|
|
80
|
+
param(
|
|
81
|
+
[AllowNull()][AllowEmptyString()][string] $Text
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if ([string]::IsNullOrEmpty($Text)) {
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
foreach ($entry in $Text.Split([char]0)) {
|
|
88
|
+
if ($entry.Length -gt 0) {
|
|
89
|
+
$entry
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function Get-DocsOnlyPathViolation {
|
|
95
|
+
<#
|
|
96
|
+
.SYNOPSIS
|
|
97
|
+
Return $null when every path is under docs/, else the denial sub-code text.
|
|
98
|
+
.DESCRIPTION
|
|
99
|
+
An empty set returns DOCS_ONLY_EXEMPTION_EMPTY_DIFF. Otherwise the first path that does
|
|
100
|
+
not start with docs/ under ordinal comparison, or that is not longer than docs/ itself,
|
|
101
|
+
returns DOCS_ONLY_EXEMPTION_NON_DOCS_PATH naming that path.
|
|
102
|
+
.PARAMETER Path
|
|
103
|
+
The changed paths, repository-relative, as git reports them.
|
|
104
|
+
.OUTPUTS
|
|
105
|
+
System.String or $null
|
|
106
|
+
#>
|
|
107
|
+
[CmdletBinding()]
|
|
108
|
+
[OutputType([string])]
|
|
109
|
+
param(
|
|
110
|
+
[AllowNull()][AllowEmptyCollection()][string[]] $Path
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
$paths = @($Path | Where-Object { $null -ne $_ })
|
|
114
|
+
if ($paths.Count -eq 0) {
|
|
115
|
+
return 'DOCS_ONLY_EXEMPTION_EMPTY_DIFF: the branch diff lists no path.'
|
|
116
|
+
}
|
|
117
|
+
foreach ($entry in $paths) {
|
|
118
|
+
if ($entry.Length -le 5 -or -not $entry.StartsWith('docs/', [System.StringComparison]::Ordinal)) {
|
|
119
|
+
return "DOCS_ONLY_EXEMPTION_NON_DOCS_PATH: '$entry' is outside docs/."
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return $null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function Get-DocsOnlyDiffTextViolation {
|
|
126
|
+
<#
|
|
127
|
+
.SYNOPSIS
|
|
128
|
+
Return $null when NUL-terminated diff output lists only docs/ paths, else the sub-code text.
|
|
129
|
+
.DESCRIPTION
|
|
130
|
+
Pure. Non-empty text that does not end with NUL returns DOCS_ONLY_EXEMPTION_DIFF_FAILED,
|
|
131
|
+
because entries could otherwise run together into one path that starts with docs/.
|
|
132
|
+
Otherwise the text is split on NUL and classified by Get-DocsOnlyPathViolation, so empty
|
|
133
|
+
text reaches DOCS_ONLY_EXEMPTION_EMPTY_DIFF.
|
|
134
|
+
.PARAMETER DiffText
|
|
135
|
+
The captured output of git diff --name-only -z.
|
|
136
|
+
.PARAMETER BaseName
|
|
137
|
+
The base branch named in the denial text.
|
|
138
|
+
.OUTPUTS
|
|
139
|
+
System.String or $null
|
|
140
|
+
#>
|
|
141
|
+
[CmdletBinding()]
|
|
142
|
+
[OutputType([string])]
|
|
143
|
+
param(
|
|
144
|
+
[AllowNull()][AllowEmptyString()][string] $DiffText,
|
|
145
|
+
[AllowNull()][AllowEmptyString()][string] $BaseName
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
$text = [string]$DiffText
|
|
149
|
+
if ($text.Length -gt 0 -and -not $text.EndsWith([char]0)) {
|
|
150
|
+
return "DOCS_ONLY_EXEMPTION_DIFF_FAILED: git diff against origin/$BaseName did not return NUL-terminated output."
|
|
151
|
+
}
|
|
152
|
+
return (Get-DocsOnlyPathViolation -Path @(ConvertFrom-DocsOnlyNameList -Text $text))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function Invoke-DocsOnlyBoundedNative {
|
|
156
|
+
<#
|
|
157
|
+
.SYNOPSIS
|
|
158
|
+
Run git or gh with a fail-closed time limit; the body of the default seam invokers.
|
|
159
|
+
.DESCRIPTION
|
|
160
|
+
Starts FileName in the current file-system location with each argument passed
|
|
161
|
+
unchanged and standard output and standard error redirected, then waits at most
|
|
162
|
+
TimeoutMilliseconds for the exit and at most 1000 ms more for the output. When a
|
|
163
|
+
wait expires, the process tree is stopped, nothing is emitted, and $LASTEXITCODE is
|
|
164
|
+
set to 124, so the calling seam reports a non-zero exit and the exemption denies.
|
|
165
|
+
Otherwise the output lines are emitted and $LASTEXITCODE is set to the exit code.
|
|
166
|
+
Standard error is drained and discarded. A start failure throws to the calling
|
|
167
|
+
seam, which returns its failure object. $LASTEXITCODE is set in the global scope
|
|
168
|
+
because the seams read it after the invoker, as they do after a native command.
|
|
169
|
+
The process object is disposed on every return path once it has started.
|
|
170
|
+
After an expired wait, each read is checked once without waiting before disposal.
|
|
171
|
+
.PARAMETER FileName
|
|
172
|
+
The executable, git or gh, resolved from PATH.
|
|
173
|
+
.PARAMETER ArgumentList
|
|
174
|
+
The arguments, one element per argument.
|
|
175
|
+
.PARAMETER TimeoutMilliseconds
|
|
176
|
+
The longest wait for the process to exit.
|
|
177
|
+
.PARAMETER ProcessStarter
|
|
178
|
+
The scriptblock that starts the process from its start description; a unit test
|
|
179
|
+
passes a test-local stand-in that returns a fake process object.
|
|
180
|
+
.OUTPUTS
|
|
181
|
+
System.String, one per output line
|
|
182
|
+
#>
|
|
183
|
+
[CmdletBinding()]
|
|
184
|
+
[OutputType([string])]
|
|
185
|
+
param(
|
|
186
|
+
[Parameter(Mandatory)][string] $FileName,
|
|
187
|
+
[AllowEmptyCollection()][string[]] $ArgumentList = @(),
|
|
188
|
+
[ValidateRange(1, 10000)][int] $TimeoutMilliseconds = 3000,
|
|
189
|
+
[ValidateNotNull()][scriptblock] $ProcessStarter = { param($StartInfo) [System.Diagnostics.Process]::Start($StartInfo) }
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
$startInfo = [System.Diagnostics.ProcessStartInfo]::new($FileName)
|
|
193
|
+
foreach ($argument in $ArgumentList) {
|
|
194
|
+
$startInfo.ArgumentList.Add($argument)
|
|
195
|
+
}
|
|
196
|
+
$startInfo.WorkingDirectory = $ExecutionContext.SessionState.Path.CurrentFileSystemLocation.ProviderPath
|
|
197
|
+
$startInfo.UseShellExecute = $false
|
|
198
|
+
$startInfo.RedirectStandardOutput = $true
|
|
199
|
+
$startInfo.RedirectStandardError = $true
|
|
200
|
+
$startInfo.StandardOutputEncoding = [System.Text.Encoding]::UTF8
|
|
201
|
+
$process = & $ProcessStarter $startInfo
|
|
202
|
+
try {
|
|
203
|
+
$output = $process.StandardOutput.ReadToEndAsync()
|
|
204
|
+
$errorRead = $process.StandardError.ReadToEndAsync()
|
|
205
|
+
if (-not $process.WaitForExit($TimeoutMilliseconds) -or -not $output.Wait(1000)) {
|
|
206
|
+
try {
|
|
207
|
+
$process.Kill($true)
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
$null = $_
|
|
211
|
+
}
|
|
212
|
+
foreach ($pendingRead in @($output, $errorRead)) {
|
|
213
|
+
try {
|
|
214
|
+
$null = $pendingRead.Wait(0)
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
$null = $_
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
Set-Variable -Name LASTEXITCODE -Value 124 -Scope Global
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
Set-Variable -Name LASTEXITCODE -Value ([int]$process.ExitCode) -Scope Global
|
|
224
|
+
$text = [string]$output.Result
|
|
225
|
+
if ($text.EndsWith("`n")) {
|
|
226
|
+
$text = $text.Substring(0, $text.Length - 1)
|
|
227
|
+
}
|
|
228
|
+
if ($text.Length -eq 0) {
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
foreach ($line in $text.Split("`n")) {
|
|
232
|
+
$line.TrimEnd("`r")
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
$process.Dispose()
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function Invoke-DocsOnlyGitExe {
|
|
241
|
+
<#
|
|
242
|
+
.SYNOPSIS
|
|
243
|
+
Run git -C <root> with the given arguments; the git seam of the merge exemption.
|
|
244
|
+
.DESCRIPTION
|
|
245
|
+
Never throws. A root that is not an existing directory returns ExitCode -1 without
|
|
246
|
+
running the command, and so does a command that cannot start. Standard error is
|
|
247
|
+
discarded so that it is never read as path data. Output lines are joined with a line
|
|
248
|
+
feed.
|
|
249
|
+
.PARAMETER WorktreeRoot
|
|
250
|
+
The directory git runs in.
|
|
251
|
+
.PARAMETER GitArgs
|
|
252
|
+
The git arguments that follow -C <root>.
|
|
253
|
+
.PARAMETER GitInvoker
|
|
254
|
+
The scriptblock that runs git; a unit test passes a test-local stand-in instead.
|
|
255
|
+
.OUTPUTS
|
|
256
|
+
System.Management.Automation.PSCustomObject with ExitCode and Output.
|
|
257
|
+
#>
|
|
258
|
+
[CmdletBinding()]
|
|
259
|
+
[OutputType([pscustomobject])]
|
|
260
|
+
param(
|
|
261
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreeRoot,
|
|
262
|
+
[Parameter(Mandatory)][string[]] $GitArgs,
|
|
263
|
+
[ValidateNotNull()][scriptblock] $GitInvoker = { Invoke-DocsOnlyBoundedNative -FileName 'git' -ArgumentList $args -TimeoutMilliseconds 3000 }
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
$failure = [pscustomobject]@{ ExitCode = -1; Output = '' }
|
|
267
|
+
if ([string]::IsNullOrWhiteSpace($WorktreeRoot) -or -not (Test-Path -LiteralPath $WorktreeRoot -PathType Container)) {
|
|
268
|
+
return $failure
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
$lines = @(& $GitInvoker -C $WorktreeRoot @GitArgs 2>$null)
|
|
272
|
+
$code = $LASTEXITCODE
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return $failure
|
|
276
|
+
}
|
|
277
|
+
return [pscustomobject]@{ ExitCode = [int]$code; Output = ($lines -join "`n") }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function Invoke-DocsOnlyGhExe {
|
|
281
|
+
<#
|
|
282
|
+
.SYNOPSIS
|
|
283
|
+
Run gh with the given arguments inside a working root; the gh seam.
|
|
284
|
+
.DESCRIPTION
|
|
285
|
+
Never throws. gh has no -C option, so the seam pushes the working root and pops it in
|
|
286
|
+
finally, restoring the caller's location after success and after a throw. A root that
|
|
287
|
+
is not an existing directory returns ExitCode -1 without running the command.
|
|
288
|
+
Standard error is discarded. Output lines are joined with a line feed.
|
|
289
|
+
.PARAMETER WorkingRoot
|
|
290
|
+
The directory gh runs in.
|
|
291
|
+
.PARAMETER GhArgs
|
|
292
|
+
The gh arguments.
|
|
293
|
+
.PARAMETER GhInvoker
|
|
294
|
+
The scriptblock that runs gh; a unit test passes a test-local stand-in instead.
|
|
295
|
+
.OUTPUTS
|
|
296
|
+
System.Management.Automation.PSCustomObject with ExitCode and Output.
|
|
297
|
+
#>
|
|
298
|
+
[CmdletBinding()]
|
|
299
|
+
[OutputType([pscustomobject])]
|
|
300
|
+
param(
|
|
301
|
+
[AllowNull()][AllowEmptyString()][string] $WorkingRoot,
|
|
302
|
+
[Parameter(Mandatory)][string[]] $GhArgs,
|
|
303
|
+
[ValidateNotNull()][scriptblock] $GhInvoker = { Invoke-DocsOnlyBoundedNative -FileName 'gh' -ArgumentList $args -TimeoutMilliseconds 7000 }
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
$failure = [pscustomobject]@{ ExitCode = -1; Output = '' }
|
|
307
|
+
if ([string]::IsNullOrWhiteSpace($WorkingRoot) -or -not (Test-Path -LiteralPath $WorkingRoot -PathType Container)) {
|
|
308
|
+
return $failure
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
Push-Location -LiteralPath $WorkingRoot -ErrorAction Stop
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return $failure
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
$lines = @(& $GhInvoker @GhArgs 2>$null)
|
|
318
|
+
$code = $LASTEXITCODE
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return $failure
|
|
322
|
+
}
|
|
323
|
+
finally {
|
|
324
|
+
Pop-Location
|
|
325
|
+
}
|
|
326
|
+
return [pscustomobject]@{ ExitCode = [int]$code; Output = ($lines -join "`n") }
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function Get-DocsOnlyMergeShapeViolation {
|
|
330
|
+
<#
|
|
331
|
+
.SYNOPSIS
|
|
332
|
+
Return $null when the merge command has the M1 shape, else the denial text.
|
|
333
|
+
.DESCRIPTION
|
|
334
|
+
Pure; runs before any process. See M1 in the file header.
|
|
335
|
+
.PARAMETER CommandText
|
|
336
|
+
The Bash command text under evaluation.
|
|
337
|
+
.PARAMETER PrNumber
|
|
338
|
+
The explicit pull request number the gate read, or $null.
|
|
339
|
+
.OUTPUTS
|
|
340
|
+
System.String or $null
|
|
341
|
+
#>
|
|
342
|
+
[CmdletBinding()]
|
|
343
|
+
[OutputType([string])]
|
|
344
|
+
param(
|
|
345
|
+
[AllowNull()][AllowEmptyString()][string] $CommandText,
|
|
346
|
+
[AllowNull()][Nullable[int]] $PrNumber
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
$prefix = 'DOCS_ONLY_EXEMPTION_PR_UNRESOLVED: '
|
|
350
|
+
if ($null -eq $PrNumber -or $PrNumber -le 0) {
|
|
351
|
+
return $prefix + 'the command names no positive pull request number.'
|
|
352
|
+
}
|
|
353
|
+
$segments = @(Read-CommandLineSegment -CommandText $CommandText)
|
|
354
|
+
if ($segments.Count -ne 1 -or (Test-CommandLineSegmentRawScan -Segment $segments[0])) {
|
|
355
|
+
return $prefix + 'the command must be one gh pr merge segment with no chain, wrapper, or substitution.'
|
|
356
|
+
}
|
|
357
|
+
if ($null -eq (Get-CommandLineStructuralMatch -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('pr', 'merge'))) {
|
|
358
|
+
return $prefix + 'the command is not a structural gh pr merge.'
|
|
359
|
+
}
|
|
360
|
+
foreach ($token in @($segments[0].Tokens)) {
|
|
361
|
+
$text = [string]$token
|
|
362
|
+
if ($text -cmatch '^[A-Za-z_][A-Za-z0-9_]*=') {
|
|
363
|
+
return $prefix + "the inline environment prefix '$text' is not accepted."
|
|
364
|
+
}
|
|
365
|
+
if ($text.Length -ge 2 -and $text[0] -eq '-' -and $text[1] -ne '-' -and $text.Substring(1).Contains('R')) {
|
|
366
|
+
return $prefix + "the repository override '$text' is not accepted."
|
|
367
|
+
}
|
|
368
|
+
if ($text -eq '--repo' -or $text.StartsWith('--repo=', [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
369
|
+
return $prefix + "the repository override '$text' is not accepted."
|
|
370
|
+
}
|
|
371
|
+
if ($text -eq '--auto' -or $text.StartsWith('--auto=', [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
372
|
+
return $prefix + 'auto-merge (--auto) is not eligible.'
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return $null
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function Get-DocsOnlyPullRequestTarget {
|
|
379
|
+
<#
|
|
380
|
+
.SYNOPSIS
|
|
381
|
+
Read the base branch and head commit of a pull request through the gh seam (M3).
|
|
382
|
+
.DESCRIPTION
|
|
383
|
+
Returns an object with Reason, Base, and Head. Reason is $null when the lookup
|
|
384
|
+
succeeded and every M3 check passed; otherwise it carries the denial text.
|
|
385
|
+
.PARAMETER CommandText
|
|
386
|
+
The Bash command text, read for an optional --match-head-commit value.
|
|
387
|
+
.PARAMETER PrNumber
|
|
388
|
+
The pull request number.
|
|
389
|
+
.PARAMETER WorktreeRoot
|
|
390
|
+
The directory gh runs in.
|
|
391
|
+
.OUTPUTS
|
|
392
|
+
System.Management.Automation.PSCustomObject with Reason, Base, and Head.
|
|
393
|
+
#>
|
|
394
|
+
[CmdletBinding()]
|
|
395
|
+
[OutputType([pscustomobject])]
|
|
396
|
+
param(
|
|
397
|
+
[AllowNull()][AllowEmptyString()][string] $CommandText,
|
|
398
|
+
[Parameter(Mandatory)][int] $PrNumber,
|
|
399
|
+
[Parameter(Mandatory)][string] $WorktreeRoot
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
$unresolved = 'DOCS_ONLY_EXEMPTION_PR_UNRESOLVED: '
|
|
403
|
+
$lookup = Invoke-DocsOnlyGhExe -WorkingRoot $WorktreeRoot -GhArgs @('pr', 'view', [string]$PrNumber, '--json', 'baseRefName,headRefOid')
|
|
404
|
+
$pr = $null
|
|
405
|
+
if ($null -ne $lookup -and $lookup.ExitCode -eq 0) {
|
|
406
|
+
try {
|
|
407
|
+
$pr = ConvertFrom-Json -InputObject ([string]$lookup.Output) -ErrorAction Stop
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
$pr = $null
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if ($pr -isnot [System.Management.Automation.PSCustomObject]) {
|
|
414
|
+
return [pscustomobject]@{ Reason = $unresolved + "gh pr view $PrNumber did not return a readable pull request."; Base = $null; Head = $null }
|
|
415
|
+
}
|
|
416
|
+
$base = [string]$pr.baseRefName
|
|
417
|
+
if ($base -cnotmatch '^[A-Za-z0-9][A-Za-z0-9._/-]*$' -or $base.Contains('..')) {
|
|
418
|
+
return [pscustomobject]@{ Reason = "DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED: the base branch '$base' of pull request $PrNumber is not a conservative branch name."; Base = $null; Head = $null }
|
|
419
|
+
}
|
|
420
|
+
$head = [string]$pr.headRefOid
|
|
421
|
+
if ($head -cnotmatch '^[0-9a-f]{40}([0-9a-f]{24})?$') {
|
|
422
|
+
return [pscustomobject]@{ Reason = $unresolved + "the head commit of pull request $PrNumber is not a full lowercase object id."; Base = $null; Head = $null }
|
|
423
|
+
}
|
|
424
|
+
$subcommandPath = @('pr', 'merge')
|
|
425
|
+
if (Test-CommandLineFlag -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath -FlagName '--match-head-commit') {
|
|
426
|
+
$pinned = Get-CommandLineFlagValue -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath -FlagName '--match-head-commit'
|
|
427
|
+
if (-not [string]::Equals($pinned, $head, [System.StringComparison]::Ordinal)) {
|
|
428
|
+
return [pscustomobject]@{ Reason = $unresolved + "--match-head-commit does not equal the head commit $head of pull request $PrNumber."; Base = $null; Head = $null }
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return [pscustomobject]@{ Reason = $null; Base = $base; Head = $head }
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function Get-DocsOnlyMergeExemption {
|
|
435
|
+
<#
|
|
436
|
+
.SYNOPSIS
|
|
437
|
+
Decide whether gh pr merge <N> --merge is exempt because the pull request changes only docs/.
|
|
438
|
+
.DESCRIPTION
|
|
439
|
+
Evaluates M1 to M9 of the file header in order and returns at the first failure. M1
|
|
440
|
+
and M2 run before any process. Every seam failure denies.
|
|
441
|
+
.PARAMETER CommandText
|
|
442
|
+
The Bash command text under evaluation.
|
|
443
|
+
.PARAMETER PrNumber
|
|
444
|
+
The explicit pull request number the gate read from the command, or $null.
|
|
445
|
+
.PARAMETER WorktreeRoot
|
|
446
|
+
The directory gh and git run in.
|
|
447
|
+
.OUTPUTS
|
|
448
|
+
System.Collections.Specialized.OrderedDictionary with Exempt and Reason.
|
|
449
|
+
#>
|
|
450
|
+
[CmdletBinding()]
|
|
451
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
452
|
+
param(
|
|
453
|
+
[AllowNull()][AllowEmptyString()][string] $CommandText,
|
|
454
|
+
[AllowNull()][Nullable[int]] $PrNumber,
|
|
455
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreeRoot
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
$shape = Get-DocsOnlyMergeShapeViolation -CommandText $CommandText -PrNumber $PrNumber
|
|
459
|
+
if ($shape) {
|
|
460
|
+
return [ordered]@{ Exempt = $false; Reason = $shape }
|
|
461
|
+
}
|
|
462
|
+
if ([string]::IsNullOrWhiteSpace($WorktreeRoot) -or -not (Test-Path -LiteralPath $WorktreeRoot -PathType Container)) {
|
|
463
|
+
return [ordered]@{ Exempt = $false; Reason = 'DOCS_ONLY_EXEMPTION_PR_UNRESOLVED: the worktree root for the lookup is not an existing directory.' }
|
|
464
|
+
}
|
|
465
|
+
$target = Get-DocsOnlyPullRequestTarget -CommandText $CommandText -PrNumber ([int]$PrNumber) -WorktreeRoot $WorktreeRoot
|
|
466
|
+
if ($target.Reason) {
|
|
467
|
+
return [ordered]@{ Exempt = $false; Reason = $target.Reason }
|
|
468
|
+
}
|
|
469
|
+
$base = $target.Base
|
|
470
|
+
$head = $target.Head
|
|
471
|
+
|
|
472
|
+
$baseRev = Invoke-DocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('rev-parse', '--verify', '--quiet', ('refs/remotes/origin/' + $base + '^{commit}'))
|
|
473
|
+
$baseCommit = if ($null -ne $baseRev) { ([string]$baseRev.Output).Trim() } else { '' }
|
|
474
|
+
if ($null -eq $baseRev -or $baseRev.ExitCode -ne 0 -or $baseCommit -cnotmatch '^[0-9a-f]{40}([0-9a-f]{24})?$') {
|
|
475
|
+
return [ordered]@{ Exempt = $false; Reason = "DOCS_ONLY_EXEMPTION_BASE_UNRESOLVED: refs/remotes/origin/$base does not resolve to a commit in the worktree." }
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
$headRev = Invoke-DocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('rev-parse', '--verify', '--quiet', ($head + '^{commit}'))
|
|
479
|
+
$headCommit = if ($null -ne $headRev) { ([string]$headRev.Output).Trim() } else { '' }
|
|
480
|
+
if ($null -eq $headRev -or $headRev.ExitCode -ne 0 -or -not [string]::Equals($headCommit, $head, [System.StringComparison]::Ordinal)) {
|
|
481
|
+
return [ordered]@{ Exempt = $false; Reason = "DOCS_ONLY_EXEMPTION_DIFF_FAILED: the head commit $head of pull request $PrNumber is not present locally; run git fetch origin pull/$PrNumber/head, then retry. This hook does not fetch." }
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
$diff = Invoke-DocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('diff', '--name-only', '-z', '--no-renames', '--no-color', '--no-ext-diff', '--no-relative', ($baseCommit + '...' + $head))
|
|
485
|
+
if ($null -eq $diff -or $diff.ExitCode -ne 0) {
|
|
486
|
+
return [ordered]@{ Exempt = $false; Reason = "DOCS_ONLY_EXEMPTION_DIFF_FAILED: git diff against origin/$base could not be computed." }
|
|
487
|
+
}
|
|
488
|
+
$violation = Get-DocsOnlyDiffTextViolation -DiffText ([string]$diff.Output) -BaseName $base
|
|
489
|
+
if ($violation) {
|
|
490
|
+
return [ordered]@{ Exempt = $false; Reason = $violation }
|
|
491
|
+
}
|
|
492
|
+
return [ordered]@{ Exempt = $true; Reason = $null }
|
|
493
|
+
}
|
|
@@ -13,13 +13,14 @@
|
|
|
13
13
|
#>
|
|
14
14
|
[CmdletBinding()]
|
|
15
15
|
param()
|
|
16
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
16
17
|
|
|
17
18
|
# Shared command-line parser (issue #545). The denylist comparison below runs against the
|
|
18
19
|
# segment list rather than against the unsegmented command string, which is what keeps the
|
|
19
20
|
# two runtimes on one implementation of the same concern. This copy has no cd-chained leg
|
|
20
21
|
# and none is added.
|
|
21
|
-
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
22
|
-
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
22
|
+
try { . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-scanner.ps1' -ErrorRecord $_ }
|
|
23
|
+
try { . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-invocation.ps1' -ErrorRecord $_ }
|
|
23
24
|
|
|
24
25
|
function Get-BlockedBashPattern {
|
|
25
26
|
[CmdletBinding()]
|
|
@@ -263,6 +264,8 @@ function Invoke-ValidateBashDecision {
|
|
|
263
264
|
[AllowNull()]
|
|
264
265
|
[string] $PositionalInput
|
|
265
266
|
)
|
|
267
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'HOOK_DEPENDENCY_LOAD_FAILED:'
|
|
268
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
266
269
|
|
|
267
270
|
$commandToCheck = Get-BashCommandToCheck -ToolInputRaw $ToolInputRaw -PositionalInput $PositionalInput
|
|
268
271
|
|
|
@@ -298,6 +301,9 @@ function ConvertFrom-CodexBashHookPayload {
|
|
|
298
301
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
299
302
|
return
|
|
300
303
|
}
|
|
304
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('HOOK_DEPENDENCY_LOAD_FAILED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
305
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'HOOK_DEPENDENCY_LOAD_FAILED:'
|
|
306
|
+
if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
|
|
301
307
|
|
|
302
308
|
try {
|
|
303
309
|
$payload = ConvertFrom-CodexBashHookPayload -PayloadRaw ([Console]::In.ReadToEnd())
|
package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
#>
|
|
9
9
|
[CmdletBinding()]
|
|
10
10
|
param()
|
|
11
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
11
12
|
|
|
12
|
-
. (Join-Path $PSScriptRoot 'codex-authority-store.ps1')
|
|
13
|
+
try { . (Join-Path $PSScriptRoot 'codex-authority-store.ps1') } catch { Add-HookDependencyFailure -Name 'codex-authority-store.ps1' -ErrorRecord $_ }
|
|
13
14
|
|
|
14
15
|
function ConvertFrom-CodexStopJson {
|
|
15
16
|
[CmdletBinding()]
|
|
@@ -68,6 +69,8 @@ function Invoke-CodexSubagentStopDecision {
|
|
|
68
69
|
[Parameter(Mandatory)][string] $PayloadRaw,
|
|
69
70
|
[AllowNull()][AllowEmptyString()][string] $AttestationRaw
|
|
70
71
|
)
|
|
72
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'MODEL_ROUTING_ATTESTATION_BLOCKED:'
|
|
73
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
71
74
|
|
|
72
75
|
$payload = ConvertFrom-CodexStopJson -Raw $PayloadRaw -Name 'SubagentStop input'
|
|
73
76
|
$agentType = [string]$payload.agent_type
|
|
@@ -130,6 +133,9 @@ function Find-CodexStopAttestationRaw {
|
|
|
130
133
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
131
134
|
return
|
|
132
135
|
}
|
|
136
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('MODEL_ROUTING_ATTESTATION_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
137
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'MODEL_ROUTING_ATTESTATION_BLOCKED:'
|
|
138
|
+
if ($null -ne $dependencyDecision) { [Console]::Error.WriteLine($dependencyDecision.Reason); exit $dependencyDecision.ExitCode }
|
|
133
139
|
|
|
134
140
|
try {
|
|
135
141
|
$payloadRaw = [Console]::In.ReadToEnd()
|
package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1
CHANGED
|
@@ -20,9 +20,14 @@
|
|
|
20
20
|
"informational only", "context only", "out of plan scope", "out of scope",
|
|
21
21
|
"not applicable", "N/A", "UNVERIFIED".
|
|
22
22
|
- If the coverage artifact for a language is present, repo-wide line coverage is
|
|
23
|
-
parsed and compared against the
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
parsed and compared against the governing line floor: the line coverage
|
|
24
|
+
threshold the repository's root CLAUDE.md states, otherwise the 85 percent
|
|
25
|
+
default (read by Get-FeatureReviewCoverageThreshold from this directory's
|
|
26
|
+
feature-review-coverage-thresholds.ps1); an absent root CLAUDE.md uses the
|
|
27
|
+
defaults, and an unreadable root CLAUDE.md fails closed. When repo-wide coverage
|
|
28
|
+
is below the governing line floor and the audit does not carry a FAIL verdict on
|
|
29
|
+
a coverage row for that language, validation fails. The Codex hook enforces the
|
|
30
|
+
line floor only.
|
|
26
31
|
|
|
27
32
|
Coverage artifact paths by language:
|
|
28
33
|
TypeScript coverage/lcov.info (LCOV text)
|
|
@@ -40,6 +45,8 @@
|
|
|
40
45
|
#>
|
|
41
46
|
[CmdletBinding()]
|
|
42
47
|
param()
|
|
48
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
49
|
+
try { . (Join-Path $PSScriptRoot 'feature-review-coverage-thresholds.ps1') } catch { Add-HookDependencyFailure -Name 'feature-review-coverage-thresholds.ps1' -ErrorRecord $_ }
|
|
43
50
|
|
|
44
51
|
$ErrorActionPreference = 'Stop'
|
|
45
52
|
|
|
@@ -143,7 +150,8 @@ function Test-LanguageCoverageRow {
|
|
|
143
150
|
param(
|
|
144
151
|
[string]$AuditText,
|
|
145
152
|
[string]$Language,
|
|
146
|
-
[Nullable[double]]$RepoWidePct
|
|
153
|
+
[Nullable[double]]$RepoWidePct,
|
|
154
|
+
[double] $LineFloor = 85.0
|
|
147
155
|
)
|
|
148
156
|
|
|
149
157
|
$languageLabelMap = @{
|
|
@@ -192,12 +200,12 @@ function Test-LanguageCoverageRow {
|
|
|
192
200
|
}
|
|
193
201
|
}
|
|
194
202
|
|
|
195
|
-
if ($null -ne $RepoWidePct -and $RepoWidePct -lt
|
|
203
|
+
if ($null -ne $RepoWidePct -and $RepoWidePct -lt $LineFloor) {
|
|
196
204
|
$failLines = $coverageLines | Where-Object { $_ -match '\bFAIL\b' }
|
|
197
205
|
if (-not $failLines -or $failLines.Count -eq 0) {
|
|
198
206
|
return @{
|
|
199
207
|
Ok = $false
|
|
200
|
-
Reason = ("{0} repo-wide coverage is {1}% (below the
|
|
208
|
+
Reason = ("{0} repo-wide coverage is {1}% (below the {2}% line coverage floor) but the policy-audit contains no FAIL verdict on a coverage row for {0}." -f $Language, $RepoWidePct, $LineFloor)
|
|
201
209
|
}
|
|
202
210
|
}
|
|
203
211
|
}
|
|
@@ -229,6 +237,9 @@ function Get-FeatureReviewCoverageContinuation {
|
|
|
229
237
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
230
238
|
return
|
|
231
239
|
}
|
|
240
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('validate-feature-review-coverage: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
241
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'validate-feature-review-coverage:'
|
|
242
|
+
if ($null -ne $dependencyDecision) { [Console]::Error.WriteLine($dependencyDecision.Reason); exit $dependencyDecision.ExitCode }
|
|
232
243
|
|
|
233
244
|
try {
|
|
234
245
|
$payloadRaw = [Console]::In.ReadToEnd()
|
|
@@ -264,11 +275,16 @@ try {
|
|
|
264
275
|
exit 0
|
|
265
276
|
}
|
|
266
277
|
|
|
278
|
+
$claudeMdPath = Join-Path $repoRoot 'CLAUDE.md'
|
|
279
|
+
$claudeMdText = $null
|
|
280
|
+
if (Test-Path -Path $claudeMdPath) { try { $claudeMdText = Get-Content -Path $claudeMdPath -Raw -ErrorAction Stop } catch { throw "root CLAUDE.md exists but could not be read ($(([string]$_.Exception.Message -split "`r?`n")[0])); coverage floors cannot be resolved, so the gate fails closed." } }
|
|
281
|
+
$thresholds = Get-FeatureReviewCoverageThreshold -ClaudeMdText $claudeMdText
|
|
282
|
+
|
|
267
283
|
$failures = [System.Collections.Generic.List[string]]::new()
|
|
268
284
|
|
|
269
285
|
foreach ($lang in $changedLanguages.Keys) {
|
|
270
286
|
$repoPct = Get-LanguageRepoCoverage -RepoRoot $repoRoot -Language $lang
|
|
271
|
-
$result = Test-LanguageCoverageRow -AuditText $auditText -Language $lang -RepoWidePct $repoPct
|
|
287
|
+
$result = Test-LanguageCoverageRow -AuditText $auditText -Language $lang -RepoWidePct $repoPct -LineFloor $thresholds.Line
|
|
272
288
|
if (-not $result.Ok) {
|
|
273
289
|
$failures.Add($result.Reason)
|
|
274
290
|
}
|
|
@@ -277,7 +293,7 @@ try {
|
|
|
277
293
|
if ($failures.Count -gt 0) {
|
|
278
294
|
$header = "Feature-review coverage validation failed against branch diff."
|
|
279
295
|
$body = " - " + ($failures -join "`n - ")
|
|
280
|
-
$footer = "Audit file: $($audit.FullName)`nFix the policy-audit to carry an explicit PASS or FAIL verdict per language with changed files, and reflect repo-wide coverage below
|
|
296
|
+
$footer = "Audit file: $($audit.FullName)`nFix the policy-audit to carry an explicit PASS or FAIL verdict per language with changed files, and reflect repo-wide coverage below the governing line coverage floor ($($thresholds.Line)%) as FAIL."
|
|
281
297
|
$reason = "{0}`n{1}`n{2}" -f $header, $body, $footer
|
|
282
298
|
Get-FeatureReviewCoverageContinuation -Reason $reason -StopHookActive $stopHookActive |
|
|
283
299
|
ConvertTo-Json -Compress -Depth 5 |
|
|
@@ -42,8 +42,17 @@
|
|
|
42
42
|
".codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1",
|
|
43
43
|
".codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1",
|
|
44
44
|
".codex/hooks/enforce-orchestration-preimplementation-gate-modes.ps1",
|
|
45
|
+
".codex/hooks/enforce-orchestration-preimplementation-gate-targets.ps1",
|
|
46
|
+
".codex/hooks/feature-folder-resolution.ps1",
|
|
47
|
+
".codex/hooks/feature-review-coverage-thresholds.ps1",
|
|
45
48
|
".codex/hooks/hook-command-invocation.ps1",
|
|
46
49
|
".codex/hooks/hook-command-scanner.ps1",
|
|
50
|
+
".codex/hooks/hook-dependency-guard.ps1",
|
|
51
|
+
".codex/hooks/hook-docs-only-diff.ps1",
|
|
52
|
+
".codex/hooks/hook-command-heredoc.ps1",
|
|
53
|
+
".codex/hooks/hook-command-invocation-operands.ps1",
|
|
54
|
+
".codex/hooks/hook-command-payload.ps1",
|
|
55
|
+
".codex/hooks/hook-command-payload-powershell.ps1",
|
|
47
56
|
".codex/hooks/record-subagent-routing-attestation.ps1",
|
|
48
57
|
".codex/hooks/validate-codex-subagent-routing.ps1",
|
|
49
58
|
".codex/hooks/check-powershell-test-purity.ps1",
|