@danmoisan/drm-copilot-mcp 1.1.18 → 1.1.20
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 +1149 -661
- package/package.json +3 -2
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +28 -6
- package/resources/claude-customizations/.claude/agents/atomic-planner.md +20 -2
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +17 -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 +9 -5
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +35 -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/agents/task-researcher.md +8 -0
- 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-command.ps1 +190 -0
- 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 +78 -77
- 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 +497 -0
- package/resources/claude-customizations/.claude/hooks/premortem-section.ps1 +149 -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 +267 -113
- 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 +77 -33
- 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 +52 -36
- 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/ci-gate/Invoke-MergeConfirmationParser.ps1 +203 -0
- 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 +85 -10
- 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 +52 -16
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +7 -3
- 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/csharp-change-budget-router/SKILL.md +2 -2
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +45 -9
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +5 -3
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +134 -0
- 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 +36 -15
- 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 +108 -21
- 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/powershell-change-budget-router/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +1 -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 +16 -8
- package/resources/claude-customizations/.claude/skills/research-issue/SKILL.md +6 -0
- package/resources/claude-customizations/pack-manifests/core.json +27 -0
- package/resources/codex-and-agents-customizations/.agents/skills/README.md +1 -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 +19 -7
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +43 -4
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +18 -6
- package/resources/codex-and-agents-customizations/.agents/skills/csharp/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/csharp-change-budget-router/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 +16 -1
- 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/execution-environment/SKILL.md +134 -0
- 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 +27 -8
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/powershell-change-budget-router/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/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/remediation-handoff-atomic-planner/SKILL.md +7 -3
- package/resources/codex-and-agents-customizations/.agents/skills/research-issue/SKILL.md +6 -0
- 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 +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +21 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +15 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +4 -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 +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +7 -4
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c1.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c2.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3-elevated.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c4.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher.toml +6 -0
- 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-command.ps1 +190 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +54 -43
- 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 +497 -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/.codex/prompts/feature-review-remediate.md +6 -4
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +11 -0
- package/resources/customizations/.github/agents/atomic_executor.agent.md +21 -11
- package/resources/customizations/.github/agents/atomic_planning.agent.md +8 -3
- package/resources/customizations/.github/agents/csharp-atomic-executor.agent.md +13 -9
- package/resources/customizations/.github/agents/csharp-atomic-planning.agent.md +2 -2
- package/resources/customizations/.github/agents/csharp-orchestrator.agent.md +2 -2
- package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +5 -5
- package/resources/customizations/.github/agents/orchestrator.agent.md +8 -5
- package/resources/customizations/.github/agents/powershell-atomic-executor.agent.md +13 -9
- package/resources/customizations/.github/agents/powershell-atomic-planning.agent.md +2 -2
- package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +2 -2
- package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +3 -3
- package/resources/customizations/.github/agents/python-atomic-executor.agent.md +13 -9
- package/resources/customizations/.github/agents/python-atomic-planning.agent.md +2 -2
- package/resources/customizations/.github/agents/python-orchestrator.agent.md +2 -2
- package/resources/customizations/.github/agents/task-researcher.agent.md +6 -0
- 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/prompts/generate-atomic-plan.prompt.md +16 -10
- package/resources/customizations/.github/prompts/orchestrate-csharp-work.prompt.md +2 -2
- package/resources/customizations/.github/prompts/orchestrate-powershell-work.prompt.md +1 -1
- package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +1 -1
- package/resources/customizations/.github/prompts/orchestrate-work.prompt.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 +43 -4
- package/resources/customizations/.github/skills/csharp-change-budget-router/SKILL.md +1 -1
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/customizations/.github/skills/execution-environment/SKILL.md +134 -0
- package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +1 -1
- package/resources/customizations/.github/skills/powershell-change-budget-router/SKILL.md +1 -1
- package/resources/customizations/.github/skills/remediation-handoff-atomic-planner/SKILL.md +7 -3
- package/resources/feature-templates/bug/plan.yyyy-MM-ddTHH-mm.md +10 -0
- package/resources/feature-templates/feature/plan.yyyy-MM-ddTHH-mm.md +11 -0
- package/resources/feature-templates/refactor/plan.yyyy-MM-ddTHH-mm.md +11 -0
- package/resources/powershell/PoshQC/PoshQC.Coverage.psm1 +314 -0
- package/resources/powershell/PoshQC/PoshQC.CoverageMerge.psm1 +163 -0
- package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +296 -0
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +48 -32
- package/resources/powershell/PoshQC/PoshQC.psd1 +7 -1
- package/resources/powershell/PoshQC/PoshQC.psm1 +18 -5
- package/resources/powershell/PoshQC/README.md +21 -3
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +5 -319
package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1
CHANGED
|
@@ -32,27 +32,21 @@
|
|
|
32
32
|
#>
|
|
33
33
|
[CmdletBinding()]
|
|
34
34
|
param()
|
|
35
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
35
36
|
|
|
36
37
|
|
|
37
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
38
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
|
|
38
39
|
# Sanctioned-removal manifest reader (issue #635), consumed by the manifest branch in
|
|
39
40
|
# Invoke-ParallelWorktreeRemovalGateDecision. Same module the epic gate imports.
|
|
40
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/cleanup-manifest/CleanupWorktreeManifest.psm1') -Force
|
|
41
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/cleanup-manifest/CleanupWorktreeManifest.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'CleanupWorktreeManifest.psm1' -ErrorRecord $_ }
|
|
41
42
|
# Shared command-line parser (issue #545), consumed by the scope filter in
|
|
42
|
-
# Invoke-ParallelWorktreeRemovalGateDecision and by
|
|
43
|
+
# Invoke-ParallelWorktreeRemovalGateDecision and by Resolve-CommandLineInvocationTarget.
|
|
43
44
|
# Both call sites are byte-for-byte the calls the epic gate makes, so the duplicated
|
|
44
45
|
# concern now has one implementation.
|
|
45
|
-
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
46
|
-
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
46
|
+
try { . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-scanner.ps1' -ErrorRecord $_ }
|
|
47
|
+
try { . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-invocation.ps1' -ErrorRecord $_ }
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
$script:ParallelWorktreeGateResolutionImportFailure = $null
|
|
50
|
-
try {
|
|
51
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
$script:ParallelWorktreeGateResolutionImportFailure = 'WorktreeRunResolution.psm1'
|
|
55
|
-
}
|
|
49
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunResolution.psm1' -ErrorRecord $_ }
|
|
56
50
|
|
|
57
51
|
$script:AllowedMergeStatuses = @('merged', 'worktree_removed')
|
|
58
52
|
# Sanctioned-removal manifest location. Recorded here so every hook-read document this
|
|
@@ -164,52 +158,6 @@ function Read-ParallelWorktreeGateRunCheckpoint {
|
|
|
164
158
|
}
|
|
165
159
|
}
|
|
166
160
|
|
|
167
|
-
function Get-ParallelWorktreeRemovalCommandPath {
|
|
168
|
-
<#
|
|
169
|
-
.SYNOPSIS
|
|
170
|
-
Extract the target worktree path argument from a git worktree remove command.
|
|
171
|
-
.DESCRIPTION
|
|
172
|
-
The operand comes from the segment that structurally invokes git worktree remove,
|
|
173
|
-
so a 'cd <path> &&' segment chained before the removal contributes nothing and a
|
|
174
|
-
quoted mention of the phrase resolves to no operand at all. Quotes around the path
|
|
175
|
-
are already stripped by the tokenizer.
|
|
176
|
-
|
|
177
|
-
'--force' is a zero-argument flag, so it never contributes an operand and may be
|
|
178
|
-
written on either side of the target path. Its presence is read structurally through
|
|
179
|
-
Test-CommandLineFlag rather than by searching the raw text, so a '--force' spelling
|
|
180
|
-
that appears inside an unrelated quoted argument cannot change how the operand list
|
|
181
|
-
is read. When no operand resolves, a present '--force' is reported in its place, so
|
|
182
|
-
the checkpoint lookup fails closed on a value that matches no recorded worktree_path
|
|
183
|
-
- the same value the previous raw-text pattern returned for that input.
|
|
184
|
-
|
|
185
|
-
This body is identical to Get-EpicWorktreeRemovalCommandPath in
|
|
186
|
-
enforce-epic-worktree-removal-gate.ps1. The two gates fire on the same command and
|
|
187
|
-
now delegate the shared concern to one parser rather than to two patterns that had
|
|
188
|
-
already diverged from the Codex copy.
|
|
189
|
-
.PARAMETER CommandText
|
|
190
|
-
The Bash command text under evaluation.
|
|
191
|
-
.OUTPUTS
|
|
192
|
-
System.String or $null
|
|
193
|
-
#>
|
|
194
|
-
[CmdletBinding()]
|
|
195
|
-
[OutputType([string])]
|
|
196
|
-
param(
|
|
197
|
-
[Parameter(Mandatory)]
|
|
198
|
-
[string] $CommandText
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
$hasForce = Test-CommandLineFlag -CommandText $CommandText -CommandWord 'git' -SubcommandPath @('worktree', 'remove') -FlagName '--force'
|
|
202
|
-
$operands = @(Get-CommandLineOperand -CommandText $CommandText -CommandWord 'git' -SubcommandPath @('worktree', 'remove'))
|
|
203
|
-
|
|
204
|
-
if ($operands.Count -gt 0) {
|
|
205
|
-
return $operands[0]
|
|
206
|
-
}
|
|
207
|
-
if ($hasForce) {
|
|
208
|
-
return '--force'
|
|
209
|
-
}
|
|
210
|
-
return $null
|
|
211
|
-
}
|
|
212
|
-
|
|
213
161
|
function Find-ParallelWorktreeItemRecord {
|
|
214
162
|
<#
|
|
215
163
|
.SYNOPSIS
|
|
@@ -334,13 +282,8 @@ function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
|
334
282
|
param(
|
|
335
283
|
[string] $ToolInputRaw
|
|
336
284
|
)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if ($script:ParallelWorktreeGateResolutionImportFailure) {
|
|
340
|
-
return Get-ParallelWorktreeGateBlockDecision -Reason (
|
|
341
|
-
"PARALLEL_WORKTREE_REMOVAL_BLOCKED: the worktree-resolution module '$($script:ParallelWorktreeGateResolutionImportFailure)' " +
|
|
342
|
-
'failed to import, so the run checkpoint that governs this removal cannot be located; the gate fails closed.')
|
|
343
|
-
}
|
|
285
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PARALLEL_WORKTREE_REMOVAL_BLOCKED:'
|
|
286
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
344
287
|
|
|
345
288
|
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
346
289
|
if (-not $payload.IsValid) {
|
|
@@ -362,7 +305,43 @@ function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
|
362
305
|
return Get-ParallelWorktreeGateAllowDecision
|
|
363
306
|
}
|
|
364
307
|
|
|
365
|
-
|
|
308
|
+
# Issue #824: the removal targets are derived structurally from every invocation.
|
|
309
|
+
# A target that cannot be derived denies before any checkpoint is read, and every
|
|
310
|
+
# derived target must be authorized on its own.
|
|
311
|
+
$resolution = Resolve-CommandLineInvocationTarget -CommandText $commandText -CommandWord 'git' -SubcommandPath @('worktree', 'remove')
|
|
312
|
+
if ($resolution.Status -eq 'NoMatch') {
|
|
313
|
+
return Get-ParallelWorktreeGateAllowDecision
|
|
314
|
+
}
|
|
315
|
+
if ($resolution.Status -ne 'Targets') {
|
|
316
|
+
return Get-ParallelWorktreeGateBlockDecision -Reason 'PARALLEL_WORKTREE_REMOVAL_BLOCKED: TARGET_WORKTREE_NOT_DERIVABLE: the git worktree remove target cannot be derived from the command text. No checkpoint can authorize this removal.'
|
|
317
|
+
}
|
|
318
|
+
foreach ($target in @($resolution.Targets)) {
|
|
319
|
+
$denial = Get-ParallelWorktreeRemovalTargetDenial -WorktreePath $target
|
|
320
|
+
if ($null -ne $denial) {
|
|
321
|
+
return $denial
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return Get-ParallelWorktreeGateAllowDecision
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function Get-ParallelWorktreeRemovalTargetDenial {
|
|
328
|
+
<#
|
|
329
|
+
.SYNOPSIS
|
|
330
|
+
Evaluate one derived removal target and return its deny decision, or $null when authorized.
|
|
331
|
+
.DESCRIPTION
|
|
332
|
+
Holds the per-path authorization cascade (parallel checkpoint, epic checkpoint,
|
|
333
|
+
sanctioned-removal manifest) for a single worktree path (issue #824).
|
|
334
|
+
.PARAMETER WorktreePath
|
|
335
|
+
One removal target derived by Resolve-CommandLineInvocationTarget.
|
|
336
|
+
.OUTPUTS
|
|
337
|
+
System.Collections.Specialized.OrderedDictionary or $null
|
|
338
|
+
#>
|
|
339
|
+
[CmdletBinding()]
|
|
340
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
341
|
+
param(
|
|
342
|
+
[Parameter(Mandatory)]
|
|
343
|
+
[string] $WorktreePath
|
|
344
|
+
)
|
|
366
345
|
|
|
367
346
|
# Each run checkpoint is read beneath the worktree that records this path (issue #690),
|
|
368
347
|
# parallel kind first; an ambiguous target denies before any allow or manifest check.
|
|
@@ -377,7 +356,7 @@ function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
|
377
356
|
|
|
378
357
|
$itemRecord = Find-ParallelWorktreeItemRecord -Checkpoint $checkpoint -WorktreePath $worktreePath
|
|
379
358
|
if (Test-ParallelWorktreeRemovalAllowed -ItemRecord $itemRecord) {
|
|
380
|
-
return
|
|
359
|
+
return $null
|
|
381
360
|
}
|
|
382
361
|
|
|
383
362
|
# Epic-authorization branch (issue #688). It runs after the parallel allow above and
|
|
@@ -394,7 +373,7 @@ function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
|
394
373
|
|
|
395
374
|
$featureRecord = Find-ParallelWorktreeItemRecord -Checkpoint $epicCheckpoint -WorktreePath $worktreePath -RecordArrayName 'features'
|
|
396
375
|
if (Test-ParallelWorktreeRemovalAllowed -ItemRecord $featureRecord) {
|
|
397
|
-
return
|
|
376
|
+
return $null
|
|
398
377
|
}
|
|
399
378
|
|
|
400
379
|
# Sanctioned-removal manifest branch (issue #635). It runs last, so a
|
|
@@ -409,15 +388,15 @@ function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
|
409
388
|
# only; the epic gate covers the features array with its own exclusion.
|
|
410
389
|
if (-not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $checkpoint -RecordArrayName 'items' -WorktreePath $worktreePath) -and
|
|
411
390
|
(Test-CleanupWorktreeManifestAuthorizesRemoval -WorktreePath $worktreePath)) {
|
|
412
|
-
return
|
|
391
|
+
return $null
|
|
413
392
|
}
|
|
414
393
|
|
|
415
394
|
# When neither kind resolved, the deny names the resolution reason first (issue #690).
|
|
416
395
|
$prefix = ''
|
|
417
396
|
if ($parallelRead.Target.Status -eq 'NoTarget' -and $epicRead.Target.Status -eq 'NoTarget') {
|
|
418
|
-
$prefix = "
|
|
397
|
+
$prefix = "$($parallelRead.Target.ReasonCode): $($parallelRead.Target.Detail). "
|
|
419
398
|
}
|
|
420
|
-
return Get-ParallelWorktreeGateBlockDecision -Reason ($prefix + "
|
|
399
|
+
return Get-ParallelWorktreeGateBlockDecision -Reason ('PARALLEL_WORKTREE_REMOVAL_BLOCKED: ' + $prefix + "git worktree remove for '$worktreePath' requires a matching parallel checkpoint items[] record with merge_status in {merged, worktree_removed}. The checkpoint was unreadable, no matching record was found, or merge_status was not yet safe for removal.")
|
|
421
400
|
}
|
|
422
401
|
|
|
423
402
|
function Invoke-ParallelWorktreeRemovalGateEntryPoint {
|
|
@@ -462,6 +441,9 @@ function Invoke-ParallelWorktreeRemovalGateEntryPoint {
|
|
|
462
441
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
463
442
|
return
|
|
464
443
|
}
|
|
444
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('PARALLEL_WORKTREE_REMOVAL_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
445
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PARALLEL_WORKTREE_REMOVAL_BLOCKED:'
|
|
446
|
+
if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
|
|
465
447
|
|
|
466
448
|
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
467
449
|
# decision JSON before it. `exit (<call>)` would capture BOTH into the exit
|
|
@@ -57,10 +57,11 @@
|
|
|
57
57
|
#>
|
|
58
58
|
[CmdletBinding()]
|
|
59
59
|
param()
|
|
60
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
60
61
|
|
|
61
62
|
|
|
62
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
63
|
-
. (Join-Path $PSScriptRoot 'enforce-batch-budget-route.ps1')
|
|
63
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
|
|
64
|
+
try { . (Join-Path $PSScriptRoot 'enforce-batch-budget-route.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-batch-budget-route.ps1' -ErrorRecord $_ }
|
|
64
65
|
|
|
65
66
|
function Test-PowerShellBatchBudgetPathInRoot {
|
|
66
67
|
<#
|
|
@@ -277,6 +278,8 @@ function Invoke-PowerShellBatchBudgetDecision {
|
|
|
277
278
|
[AllowEmptyString()]
|
|
278
279
|
[string] $ObservedRoute = ''
|
|
279
280
|
)
|
|
281
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'POWERSHELL_LARGE_PATH_REQUIRED:'
|
|
282
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
280
283
|
|
|
281
284
|
$normalized = $FilePath -replace '\\', '/'
|
|
282
285
|
if ($normalized -notmatch '\.(ps1|psm1|psd1)$') {
|
|
@@ -474,6 +477,9 @@ function Invoke-PowerShellBatchBudgetEntryPoint {
|
|
|
474
477
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
475
478
|
return
|
|
476
479
|
}
|
|
480
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('POWERSHELL_LARGE_PATH_REQUIRED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
481
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'POWERSHELL_LARGE_PATH_REQUIRED:'
|
|
482
|
+
if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
|
|
477
483
|
|
|
478
484
|
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
479
485
|
# decision JSON before it. `exit (<call>)` would capture BOTH into the exit
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
PreToolUse hook that limits the pr-author subagent's Bash calls to an allowlist of forms.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Registered in the frontmatter of .claude/agents/pr-author.md on the "Bash" matcher
|
|
7
|
+
(issue #824, #733). Every command is read with Read-CommandLineInvocationSegment, so a
|
|
8
|
+
wrapper payload, a substitution body, or an xargs-injected command is a record of its own.
|
|
9
|
+
The command is allowed only when every record is a literal, top-level, unwrapped command
|
|
10
|
+
in one of these forms, matched by token position:
|
|
11
|
+
|
|
12
|
+
- git log ... and git rev-parse ... (no git global option)
|
|
13
|
+
- gh pr create ... and gh pr edit ... (no gh global option)
|
|
14
|
+
- sha256sum artifacts/pr_body_<N>.md (only as the whole command)
|
|
15
|
+
- date -u [+<format>] (only as the whole command)
|
|
16
|
+
|
|
17
|
+
Segments may be joined only by ';', '&&', '||', '|', or a newline. A record that is
|
|
18
|
+
nested, unbalanced, opaque, non-literal, wrapper-led, led by a VAR=value assignment, or
|
|
19
|
+
whose scanner segment carries a redirection is denied. Every deny reason begins
|
|
20
|
+
PR_AUTHOR_COMMAND_NOT_ALLOWED: and names the first rejected segment.
|
|
21
|
+
|
|
22
|
+
The hook is read-only: it inspects the attempted command and emits a JSON
|
|
23
|
+
allow-or-deny decision without mutating the command text.
|
|
24
|
+
|
|
25
|
+
.NOTES
|
|
26
|
+
Compatible with PowerShell 7+. Pure string logic over the shared command-line parser;
|
|
27
|
+
no disk, process, or network access beyond payload acquisition.
|
|
28
|
+
#>
|
|
29
|
+
[CmdletBinding()]
|
|
30
|
+
param()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
34
|
+
# Shared command-line parser (issues #545 and #824).
|
|
35
|
+
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
36
|
+
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
37
|
+
|
|
38
|
+
$script:PrAuthorAllowlistReasonPrefix = 'PR_AUTHOR_COMMAND_NOT_ALLOWED: '
|
|
39
|
+
$script:PrAuthorAllowlistDelimiters = @('', ';', '&&', '||', '|', "`n")
|
|
40
|
+
$script:PrAuthorAllowlistWrapperWords = @(
|
|
41
|
+
'sh', 'bash', 'zsh', 'dash', 'ksh', 'pwsh', 'powershell', 'eval', 'xargs',
|
|
42
|
+
'env', 'timeout', 'time', 'command', 'nohup'
|
|
43
|
+
)
|
|
44
|
+
$script:PrAuthorAllowlistFormText = 'git log, git rev-parse, gh pr create, gh pr edit, sha256sum artifacts/pr_body_<N>.md, date -u [+<format>]'
|
|
45
|
+
|
|
46
|
+
function Test-PrAuthorAllowlistForm {
|
|
47
|
+
<#
|
|
48
|
+
.SYNOPSIS
|
|
49
|
+
Report whether one record's tokens match an allowed command form by token position.
|
|
50
|
+
.PARAMETER Token
|
|
51
|
+
The record's tokens, quotes already stripped.
|
|
52
|
+
.PARAMETER IsWholeCommand
|
|
53
|
+
Whether the command has exactly one top-level record; sha256sum and date require it.
|
|
54
|
+
.OUTPUTS
|
|
55
|
+
System.Boolean
|
|
56
|
+
#>
|
|
57
|
+
[CmdletBinding()]
|
|
58
|
+
[OutputType([bool])]
|
|
59
|
+
param(
|
|
60
|
+
[Parameter(Mandatory)][AllowEmptyCollection()][string[]] $Token,
|
|
61
|
+
[Parameter(Mandatory)][bool] $IsWholeCommand
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if ($Token.Count -eq 0) {
|
|
65
|
+
return $false
|
|
66
|
+
}
|
|
67
|
+
switch -CaseSensitive ($Token[0]) {
|
|
68
|
+
'git' {
|
|
69
|
+
return ($Token.Count -ge 2 -and @('log', 'rev-parse') -ccontains $Token[1])
|
|
70
|
+
}
|
|
71
|
+
'gh' {
|
|
72
|
+
return ($Token.Count -ge 3 -and $Token[1] -ceq 'pr' -and @('create', 'edit') -ccontains $Token[2])
|
|
73
|
+
}
|
|
74
|
+
'sha256sum' {
|
|
75
|
+
return ($IsWholeCommand -and $Token.Count -eq 2 -and $Token[1] -cmatch '^artifacts/pr_body_\d+\.md$')
|
|
76
|
+
}
|
|
77
|
+
'date' {
|
|
78
|
+
if (-not $IsWholeCommand -or $Token.Count -lt 2 -or $Token[1] -cne '-u') {
|
|
79
|
+
return $false
|
|
80
|
+
}
|
|
81
|
+
return ($Token.Count -eq 2 -or ($Token.Count -eq 3 -and $Token[2].StartsWith('+')))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return $false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function Get-PrAuthorAllowlistRecordProblem {
|
|
88
|
+
<#
|
|
89
|
+
.SYNOPSIS
|
|
90
|
+
Return the reason one record is rejected, or $null when the record is allowed.
|
|
91
|
+
.PARAMETER Record
|
|
92
|
+
One Read-CommandLineInvocationSegment record.
|
|
93
|
+
.PARAMETER MaskedText
|
|
94
|
+
The masked text of the record's depth-0 scanner segment, or '' for a nested record.
|
|
95
|
+
.PARAMETER IsWholeCommand
|
|
96
|
+
Whether the command has exactly one top-level record.
|
|
97
|
+
.OUTPUTS
|
|
98
|
+
System.String or $null
|
|
99
|
+
#>
|
|
100
|
+
[CmdletBinding()]
|
|
101
|
+
[OutputType([string])]
|
|
102
|
+
param(
|
|
103
|
+
[Parameter(Mandatory)][pscustomobject] $Record,
|
|
104
|
+
[Parameter(Mandatory)][AllowEmptyString()][string] $MaskedText,
|
|
105
|
+
[Parameter(Mandatory)][bool] $IsWholeCommand
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
$tokens = [string[]]@($Record.Tokens)
|
|
109
|
+
if ($Record.Depth -gt 0) { return "it is a nested $($Record.Origin) command" }
|
|
110
|
+
if ($Record.Unbalanced) { return 'its quoting or grouping is unbalanced' }
|
|
111
|
+
if ($Record.Opaque) { return "its payload cannot be read ($($Record.OpaqueReason))" }
|
|
112
|
+
if (-not $Record.Literal) { return 'it carries a variable, substitution, or other non-literal token' }
|
|
113
|
+
if ($script:PrAuthorAllowlistWrapperWords -contains $Record.CommandWord) { return "it is led by the wrapper '$($Record.CommandWord)'" }
|
|
114
|
+
if ($script:PrAuthorAllowlistDelimiters -notcontains $Record.Delimiter) { return "it is joined by the delimiter '$($Record.Delimiter)'" }
|
|
115
|
+
if ($MaskedText.Contains('<') -or $MaskedText.Contains('>')) { return 'it carries a redirection' }
|
|
116
|
+
if ($tokens.Count -gt 0 -and $tokens[0] -match '^[A-Za-z_][A-Za-z0-9_]*=') { return 'it is led by a VAR=value assignment' }
|
|
117
|
+
if (-not (Test-PrAuthorAllowlistForm -Token $tokens -IsWholeCommand $IsWholeCommand)) { return 'it is not an allowed command form' }
|
|
118
|
+
return $null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function Get-PrAuthorCommandAllowlistReason {
|
|
122
|
+
<#
|
|
123
|
+
.SYNOPSIS
|
|
124
|
+
Return the deny reason for a command outside the pr-author allowlist, or $null.
|
|
125
|
+
.PARAMETER CommandText
|
|
126
|
+
The Bash command text extracted from the envelope's tool_input.
|
|
127
|
+
.OUTPUTS
|
|
128
|
+
System.String or $null
|
|
129
|
+
#>
|
|
130
|
+
[CmdletBinding()]
|
|
131
|
+
[OutputType([string])]
|
|
132
|
+
param(
|
|
133
|
+
[Parameter(Mandatory)]
|
|
134
|
+
[AllowEmptyString()]
|
|
135
|
+
[string] $CommandText
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if ([string]::IsNullOrWhiteSpace($CommandText)) {
|
|
139
|
+
return $null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
$records = @(Read-CommandLineInvocationSegment -CommandText $CommandText)
|
|
143
|
+
$segments = @(Read-CommandLineSegment -CommandText (ConvertTo-CommandLineNormalizedText -CommandText $CommandText))
|
|
144
|
+
$topLevel = @($records | Where-Object { $_.Depth -eq 0 })
|
|
145
|
+
$isWholeCommand = $topLevel.Count -eq 1
|
|
146
|
+
$topLevelPosition = 0
|
|
147
|
+
|
|
148
|
+
foreach ($record in $records) {
|
|
149
|
+
$maskedText = ''
|
|
150
|
+
if ($record.Depth -eq 0) {
|
|
151
|
+
if ($topLevelPosition -lt $segments.Count) {
|
|
152
|
+
$maskedText = [string]$segments[$topLevelPosition].MaskedText
|
|
153
|
+
}
|
|
154
|
+
$topLevelPosition++
|
|
155
|
+
}
|
|
156
|
+
$problem = Get-PrAuthorAllowlistRecordProblem -Record $record -MaskedText $maskedText -IsWholeCommand $isWholeCommand
|
|
157
|
+
if ($null -ne $problem) {
|
|
158
|
+
return ($script:PrAuthorAllowlistReasonPrefix +
|
|
159
|
+
"the segment '$($record.RawText.Trim())' is rejected because $problem. The pr-author agent may run only: $($script:PrAuthorAllowlistFormText), each without wrappers, substitutions, or redirections.")
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return $null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function Get-PrAuthorCommandAllowlistDecision {
|
|
167
|
+
<#
|
|
168
|
+
.SYNOPSIS
|
|
169
|
+
Build an ordered allow or deny decision.
|
|
170
|
+
.PARAMETER Reason
|
|
171
|
+
The deny reason, or $null for an allow.
|
|
172
|
+
.OUTPUTS
|
|
173
|
+
System.Collections.Specialized.OrderedDictionary
|
|
174
|
+
#>
|
|
175
|
+
[CmdletBinding()]
|
|
176
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
177
|
+
param(
|
|
178
|
+
[AllowNull()]
|
|
179
|
+
[string] $Reason
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if ([string]::IsNullOrEmpty($Reason)) {
|
|
183
|
+
return [ordered]@{
|
|
184
|
+
hookSpecificOutput = [ordered]@{
|
|
185
|
+
hookEventName = 'PreToolUse'
|
|
186
|
+
permissionDecision = 'allow'
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return [ordered]@{
|
|
191
|
+
hookSpecificOutput = [ordered]@{
|
|
192
|
+
hookEventName = 'PreToolUse'
|
|
193
|
+
permissionDecision = 'deny'
|
|
194
|
+
permissionDecisionReason = $Reason
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function Invoke-PrAuthorCommandAllowlistDecision {
|
|
200
|
+
<#
|
|
201
|
+
.SYNOPSIS
|
|
202
|
+
Return the ordered allow-or-deny decision for one command text.
|
|
203
|
+
.PARAMETER CommandText
|
|
204
|
+
The Bash command text.
|
|
205
|
+
.OUTPUTS
|
|
206
|
+
System.Collections.Specialized.OrderedDictionary
|
|
207
|
+
#>
|
|
208
|
+
[CmdletBinding()]
|
|
209
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
210
|
+
param(
|
|
211
|
+
[Parameter(Mandatory)]
|
|
212
|
+
[AllowEmptyString()]
|
|
213
|
+
[string] $CommandText
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return Get-PrAuthorCommandAllowlistDecision -Reason (Get-PrAuthorCommandAllowlistReason -CommandText $CommandText)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function Invoke-PrAuthorCommandAllowlistEntryPoint {
|
|
220
|
+
<#
|
|
221
|
+
.SYNOPSIS
|
|
222
|
+
Runs the hook decision and returns the process exit code.
|
|
223
|
+
.DESCRIPTION
|
|
224
|
+
Acquires the payload through the shared reader unless the caller supplies one, emits
|
|
225
|
+
the compact decision JSON, and returns 0. It never returns 1: exit 1 is non-blocking
|
|
226
|
+
for PreToolUse, so every anomaly is already a deny decision by the time control
|
|
227
|
+
reaches here. The function does not call exit; the thin tail converts the returned
|
|
228
|
+
code into a process exit.
|
|
229
|
+
.PARAMETER ToolInputRaw
|
|
230
|
+
Optional pre-acquired payload text. When omitted the ReadPayload seam runs.
|
|
231
|
+
.PARAMETER ReadPayload
|
|
232
|
+
Seam for payload acquisition, so tests can drive the empty-on-all-transports case
|
|
233
|
+
without touching a console.
|
|
234
|
+
.OUTPUTS
|
|
235
|
+
System.Int32
|
|
236
|
+
#>
|
|
237
|
+
[CmdletBinding()]
|
|
238
|
+
[OutputType([int])]
|
|
239
|
+
param(
|
|
240
|
+
[AllowNull()]
|
|
241
|
+
[AllowEmptyString()]
|
|
242
|
+
[string] $ToolInputRaw,
|
|
243
|
+
|
|
244
|
+
[scriptblock] $ReadPayload = { Read-ClaudeHookRawPayload }
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if (-not $PSBoundParameters.ContainsKey('ToolInputRaw')) {
|
|
248
|
+
$ToolInputRaw = [string](& $ReadPayload)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
252
|
+
if (-not $payload.IsValid) {
|
|
253
|
+
$decision = Get-PrAuthorCommandAllowlistDecision -Reason (
|
|
254
|
+
$script:PrAuthorAllowlistReasonPrefix + 'payload anomaly - ' +
|
|
255
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $payload.Anomaly) +
|
|
256
|
+
'. The hook fails closed on an envelope it cannot read.')
|
|
257
|
+
} else {
|
|
258
|
+
$commandText = [string](Get-ClaudeHookToolInputString -ToolInput $payload.Value -Name 'command')
|
|
259
|
+
$decision = Invoke-PrAuthorCommandAllowlistDecision -CommandText $commandText
|
|
260
|
+
}
|
|
261
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
262
|
+
|
|
263
|
+
return 0
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Allow dot-sourcing in tests without executing the entrypoint.
|
|
267
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
272
|
+
# decision JSON before it. `exit (<call>)` would capture BOTH into the exit
|
|
273
|
+
# expression and emit nothing, so the decision is written explicitly here first.
|
|
274
|
+
$entryPointResult = @(Invoke-PrAuthorCommandAllowlistEntryPoint)
|
|
275
|
+
if ($entryPointResult.Count -gt 1) {
|
|
276
|
+
$entryPointResult[0..($entryPointResult.Count - 2)] | Write-Output
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
exit ([int]$entryPointResult[-1])
|