@danmoisan/drm-copilot-mcp 1.1.17 → 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 +1405 -684
- package/package.json +6 -4
- 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 +16 -5
- package/resources/claude-customizations/.claude/agents/orchestrator.md +21 -7
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +37 -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-lane-assertion.sh +4 -2
- 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/OrchestratorState.psm1 +6 -13
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateEpicWaveBarrier.psm1 +346 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +396 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +22 -15
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRemediationAccounting.psm1 +337 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +9 -1
- 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 +153 -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 +9 -1
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +7 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +11 -3
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +11 -2
- 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 +171 -5
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +1 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_report_records_lib.sh +6 -43
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +6 -15
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +45 -11
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +10 -0
- package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +7 -4
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +42 -16
- 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 +97 -19
- 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 +7 -3
- package/resources/claude-customizations/pack-manifests/core.json +25 -0
- package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +9 -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 +13 -3
- 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 +13 -0
- package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +30 -6
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +16 -6
- 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 +43 -11
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -2
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +19 -4
- 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 +2 -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 +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/feature-reviewer-c1.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +3 -3
- 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 +9 -1
- package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +4 -0
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- 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 -315
package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1
CHANGED
|
@@ -10,13 +10,16 @@
|
|
|
10
10
|
cap as target resolution grows.
|
|
11
11
|
|
|
12
12
|
The file declares no file-scope parameter block, no requires directive, and no
|
|
13
|
-
entrypoint. Apart from its function declarations,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
entrypoint. Apart from its function declarations, its file-scope statements are
|
|
14
|
+
the resolution-module import that follows this block, which brings in issue
|
|
15
|
+
#669's path normalisation and ambiguity reason code, and the guarded dot-source
|
|
16
|
+
of the pure shared resolver feature-folder-resolution.ps1 (issue #565), which
|
|
17
|
+
owns the single work-mode parser. The module import is deliberately unguarded,
|
|
18
|
+
for the same reason the parent hook's is: a resolution module that cannot be
|
|
19
|
+
loaded is itself the target-not-resolvable state, so letting the failure surface
|
|
20
|
+
fails the gate closed rather than degrading it to a permissive path. A failed
|
|
21
|
+
dot-source of the shared resolver makes Resolve-PrdFeatureWorkMode return $null,
|
|
22
|
+
which the planner gate's deny-on-unknown branch treats as an unreadable marker.
|
|
20
23
|
.NOTES
|
|
21
24
|
Compatible with PowerShell 7+. Read-only resolution logic.
|
|
22
25
|
#>
|
|
@@ -24,7 +27,17 @@
|
|
|
24
27
|
# Issue #669 owns path normalisation and the ambiguity reason code. The import is
|
|
25
28
|
# unguarded for the same reason the parent's is: a resolution module that cannot be
|
|
26
29
|
# loaded is itself the target-not-resolvable state.
|
|
27
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeResolution.psm1') -Force
|
|
30
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeResolution.psm1') -Force -ErrorAction Stop
|
|
31
|
+
|
|
32
|
+
# Shared work-mode parser (issue #565). Guarded so a failed dot-source fails closed
|
|
33
|
+
# through the planner gate's deny-on-unknown branch.
|
|
34
|
+
$script:PrdFeatureFolderResolutionImportFailure = $null
|
|
35
|
+
try {
|
|
36
|
+
. (Join-Path $PSScriptRoot 'feature-folder-resolution.ps1')
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
$script:PrdFeatureFolderResolutionImportFailure = 'feature-folder-resolution.ps1'
|
|
40
|
+
}
|
|
28
41
|
|
|
29
42
|
function Resolve-PrdFeatureWorkMode {
|
|
30
43
|
<#
|
|
@@ -36,7 +49,10 @@ function Resolve-PrdFeatureWorkMode {
|
|
|
36
49
|
Recognizes minor-audit, full-feature, full-bug, and the legacy full
|
|
37
50
|
marker (normalized to full-feature), mirroring the regex convention
|
|
38
51
|
used by scripts/dev_tools/prompt_mode_contract.py so both runtimes
|
|
39
|
-
agree on what counts as a valid marker line.
|
|
52
|
+
agree on what counts as a valid marker line. Delegates to the single
|
|
53
|
+
shared parser, Resolve-FeatureFolderWorkMode (issue #565), with an empty
|
|
54
|
+
-UnresolvedMode so an unresolved marker still returns $null here; a
|
|
55
|
+
failed dot-source of the shared resolver also returns $null.
|
|
40
56
|
#>
|
|
41
57
|
[CmdletBinding()]
|
|
42
58
|
[OutputType([string])]
|
|
@@ -45,20 +61,15 @@ function Resolve-PrdFeatureWorkMode {
|
|
|
45
61
|
[string] $IssueContent
|
|
46
62
|
)
|
|
47
63
|
|
|
48
|
-
if (
|
|
64
|
+
if ($script:PrdFeatureFolderResolutionImportFailure) {
|
|
49
65
|
return $null
|
|
50
66
|
}
|
|
51
67
|
|
|
52
|
-
$
|
|
53
|
-
if (-not $
|
|
68
|
+
$mode = Resolve-FeatureFolderWorkMode -IssueContent $IssueContent -UnresolvedMode ''
|
|
69
|
+
if (-not $mode) {
|
|
54
70
|
return $null
|
|
55
71
|
}
|
|
56
|
-
|
|
57
|
-
$rawMode = $match.Groups[1].Value
|
|
58
|
-
if ($rawMode -eq 'full') {
|
|
59
|
-
return 'full-feature'
|
|
60
|
-
}
|
|
61
|
-
return $rawMode
|
|
72
|
+
return $mode
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
function Get-PrdFeatureRequiredFile {
|
package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1
CHANGED
|
@@ -98,9 +98,10 @@
|
|
|
98
98
|
#>
|
|
99
99
|
[CmdletBinding()]
|
|
100
100
|
param()
|
|
101
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
101
102
|
|
|
102
103
|
|
|
103
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
104
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
|
|
104
105
|
# Issue #669 owns worktree location and path normalisation; issue #673 owns the
|
|
105
106
|
# portable-identity resolution this gate now selects a worktree with. Both imports are
|
|
106
107
|
# unguarded on purpose: a resolution module that cannot be loaded is itself the
|
|
@@ -108,9 +109,9 @@ Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -
|
|
|
108
109
|
# a permissive path. Both are required rather than one: the absolute-path join the probe
|
|
109
110
|
# step below calls is exported only by the first, because a module whose exports are pinned
|
|
110
111
|
# by an explicit list does not re-export a sibling's functions to its own importer.
|
|
111
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeTargetResolution.psm1') -Force
|
|
112
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeItemResolution.psm1') -Force
|
|
113
|
-
. (Join-Path $PSScriptRoot 'enforce-prd-feature-before-planner-helpers.ps1')
|
|
112
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeTargetResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeTargetResolution.psm1' -ErrorRecord $_ }
|
|
113
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeItemResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeItemResolution.psm1' -ErrorRecord $_ }
|
|
114
|
+
try { . (Join-Path $PSScriptRoot 'enforce-prd-feature-before-planner-helpers.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-prd-feature-before-planner-helpers.ps1' -ErrorRecord $_ }
|
|
114
115
|
function Get-PrdFeatureFileExistence {
|
|
115
116
|
<#
|
|
116
117
|
.SYNOPSIS
|
|
@@ -316,6 +317,8 @@ function Invoke-PrdFeatureBeforePlannerDecision {
|
|
|
316
317
|
[AllowNull()]
|
|
317
318
|
[object] $ResolvedTarget
|
|
318
319
|
)
|
|
320
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PRD_FEATURE_BLOCKED:'
|
|
321
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
319
322
|
|
|
320
323
|
$envelope = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
321
324
|
if (-not $envelope.IsValid) {
|
|
@@ -469,6 +472,9 @@ function Invoke-PrdFeatureBeforePlannerDecision {
|
|
|
469
472
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
470
473
|
return
|
|
471
474
|
}
|
|
475
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('PRD_FEATURE_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
476
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PRD_FEATURE_BLOCKED:'
|
|
477
|
+
if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
|
|
472
478
|
|
|
473
479
|
$decision = Invoke-PrdFeatureBeforePlannerDecision -ToolInputRaw (Read-ClaudeHookRawPayload)
|
|
474
480
|
|
|
@@ -28,15 +28,16 @@
|
|
|
28
28
|
#>
|
|
29
29
|
[CmdletBinding()]
|
|
30
30
|
param()
|
|
31
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
34
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
|
|
34
35
|
|
|
35
36
|
# Shared command-line parser (issue #545). Every forbidden-token scan and both `gh`
|
|
36
37
|
# expressions below run against each segment's clause-ordered scan text, and the
|
|
37
38
|
# structural matcher reaches the relocating `gh` spellings adjacency cannot.
|
|
38
|
-
. (Join-Path $PSScriptRoot 'hook-command-scanner.ps1')
|
|
39
|
-
. (Join-Path $PSScriptRoot 'hook-command-invocation.ps1')
|
|
39
|
+
try { . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-scanner.ps1' -ErrorRecord $_ }
|
|
40
|
+
try { . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-invocation.ps1' -ErrorRecord $_ }
|
|
40
41
|
|
|
41
42
|
$script:PromotionMcpOnlyBlockedReason = 'PROMOTION_MCP_ONLY_BLOCKED: Direct Bash promotion-script execution is not allowed in agent sessions. Use the drm-copilot MCP promotion tools instead.'
|
|
42
43
|
|
|
@@ -112,9 +113,12 @@ function Get-PromotionBypassReason {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
# `gh issue create` and `gh issue new` are direct bypasses of the MCP
|
|
115
|
-
# promotion path. Tolerate any flags after the subcommand.
|
|
116
|
+
# promotion path. Tolerate any flags after the subcommand. The adjacency
|
|
117
|
+
# expression reads the masked text, so a quoted mention is not a match; wrapper
|
|
118
|
+
# payloads and substitution bodies are covered by the structural check below
|
|
119
|
+
# (issue #824).
|
|
116
120
|
foreach ($segment in $segments) {
|
|
117
|
-
if ($segment.
|
|
121
|
+
if ($segment.MaskedText -match '(?i)\bgh\s+issue\s+(?:create|new)\b') {
|
|
118
122
|
return (Get-PromotionMcpOnlyGhIssueBlockedReason)
|
|
119
123
|
}
|
|
120
124
|
}
|
|
@@ -227,6 +231,8 @@ function Invoke-PromotionMcpOnlyDecision {
|
|
|
227
231
|
param(
|
|
228
232
|
[string] $ToolInputRaw
|
|
229
233
|
)
|
|
234
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PROMOTION_MCP_ONLY_BLOCKED:'
|
|
235
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
230
236
|
|
|
231
237
|
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
232
238
|
if (-not $payload.IsValid) {
|
|
@@ -291,6 +297,9 @@ function Invoke-PromotionMcpOnlyEntryPoint {
|
|
|
291
297
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
292
298
|
return
|
|
293
299
|
}
|
|
300
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('PROMOTION_MCP_ONLY_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
301
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PROMOTION_MCP_ONLY_BLOCKED:'
|
|
302
|
+
if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
|
|
294
303
|
|
|
295
304
|
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
296
305
|
# 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-PythonBatchBudgetPathInRoot {
|
|
66
67
|
<#
|
|
@@ -280,6 +281,8 @@ function Invoke-PythonBatchBudgetDecision {
|
|
|
280
281
|
[AllowEmptyString()]
|
|
281
282
|
[string] $ObservedRoute = ''
|
|
282
283
|
)
|
|
284
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PYTHON_LARGE_PATH_REQUIRED:'
|
|
285
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
283
286
|
|
|
284
287
|
$normalized = $FilePath -replace '\\', '/'
|
|
285
288
|
if ($normalized -notmatch '\.py$') {
|
|
@@ -477,6 +480,9 @@ function Invoke-PythonBatchBudgetEntryPoint {
|
|
|
477
480
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
478
481
|
return
|
|
479
482
|
}
|
|
483
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('PYTHON_LARGE_PATH_REQUIRED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
484
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'PYTHON_LARGE_PATH_REQUIRED:'
|
|
485
|
+
if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
|
|
480
486
|
|
|
481
487
|
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
482
488
|
# decision JSON before it. `exit (<call>)` would capture BOTH into the exit
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Pure feature-folder target resolution shared by the Claude and Codex enforcement hooks
|
|
4
|
+
(issue #565).
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
One resolution rule, implemented once and parameterized per gate. A feature folder is
|
|
8
|
+
identified structurally, as the single path segment immediately below
|
|
9
|
+
docs/features/active/, and never by string length or prompt position.
|
|
10
|
+
|
|
11
|
+
- Find-FeatureFolderCandidate extracts the distinct cited folder basenames (R1).
|
|
12
|
+
- ConvertTo-FeatureFolderBasename normalizes a record value or token (R2).
|
|
13
|
+
- Find-FeatureFolderRecord is the union-index record lookup (issue_num or folder).
|
|
14
|
+
- Select-FeatureFolderTarget applies dependency pruning, the declared-issue-number
|
|
15
|
+
tie-break, and the fallback issue number, and reports Resolved, NoTarget, or
|
|
16
|
+
Ambiguous (R3-R6).
|
|
17
|
+
- Resolve-FeatureFolderWorkMode parses the persisted work-mode marker of issue.md.
|
|
18
|
+
- Get-FeatureFolderPlanPrerequisite maps a work mode to its plan prerequisites.
|
|
19
|
+
|
|
20
|
+
The file is byte-identical in .claude/hooks/ and .codex/hooks/ and is dot-sourced by its
|
|
21
|
+
callers, each of which guards the dot-source and fails closed when it cannot be loaded.
|
|
22
|
+
|
|
23
|
+
.NOTES
|
|
24
|
+
Compatible with PowerShell 7+. PURITY: every function is pure string and object logic.
|
|
25
|
+
The file opens no file, probes no path, reads no environment value, launches no process,
|
|
26
|
+
issues no web request, and loads no module. It declares functions only.
|
|
27
|
+
#>
|
|
28
|
+
|
|
29
|
+
function Find-FeatureFolderCandidate {
|
|
30
|
+
<#
|
|
31
|
+
.SYNOPSIS
|
|
32
|
+
Returns the distinct feature-folder basenames cited in a text, in first-occurrence
|
|
33
|
+
order (R1).
|
|
34
|
+
.DESCRIPTION
|
|
35
|
+
Scans for docs/features/active/ path tokens with any prefix and either separator.
|
|
36
|
+
Each token is normalized to forward slashes and split into segments; empty and '.'
|
|
37
|
+
segments are dropped. A token with fewer than four segments yields no candidate.
|
|
38
|
+
The fourth segment is the folder, with trailing '.', ',', ';', ':', ')', ']', '>', '*', and '_' trimmed.
|
|
39
|
+
Duplicates are removed ordinally, keeping the first occurrence.
|
|
40
|
+
.PARAMETER Text
|
|
41
|
+
The prompt or other text to scan. Null or empty text yields no output.
|
|
42
|
+
.OUTPUTS
|
|
43
|
+
System.String[] (no output when no qualifying token is present)
|
|
44
|
+
#>
|
|
45
|
+
[CmdletBinding()]
|
|
46
|
+
[OutputType([string[]])]
|
|
47
|
+
param(
|
|
48
|
+
[AllowNull()]
|
|
49
|
+
[AllowEmptyString()]
|
|
50
|
+
[string] $Text
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if ([string]::IsNullOrEmpty($Text)) {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
$found = [System.Collections.Generic.List[string]]::new()
|
|
58
|
+
foreach ($match in [regex]::Matches($Text, 'docs[\\/]+features[\\/]+active[\\/]+[^\s"''`]+')) {
|
|
59
|
+
$segments = @(($match.Value -replace '\\', '/') -split '/' | Where-Object { $_ -and $_ -ne '.' })
|
|
60
|
+
if ($segments.Count -lt 4) {
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
$folder = $segments[3].TrimEnd('.', ',', ';', ':', ')', ']', '>', '*', '_')
|
|
64
|
+
if (-not $folder -or $found.Contains($folder)) {
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
$found.Add($folder)
|
|
68
|
+
}
|
|
69
|
+
return $found.ToArray()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function ConvertTo-FeatureFolderBasename {
|
|
73
|
+
<#
|
|
74
|
+
.SYNOPSIS
|
|
75
|
+
Normalizes a feature-folder record value or token to its basename (R2).
|
|
76
|
+
.DESCRIPTION
|
|
77
|
+
Separators are normalized to forward slashes, surrounding whitespace and trailing
|
|
78
|
+
slashes are trimmed, and one leading docs/features/<lifecycle>/, active/, or
|
|
79
|
+
completed/ prefix is stripped. The last remaining segment is returned.
|
|
80
|
+
.PARAMETER Value
|
|
81
|
+
The record value or token to normalize.
|
|
82
|
+
.OUTPUTS
|
|
83
|
+
System.String, or $null when no folder segment remains.
|
|
84
|
+
#>
|
|
85
|
+
[CmdletBinding()]
|
|
86
|
+
[OutputType([string])]
|
|
87
|
+
param(
|
|
88
|
+
[AllowNull()]
|
|
89
|
+
[AllowEmptyString()]
|
|
90
|
+
[string] $Value
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if ([string]::IsNullOrWhiteSpace($Value)) {
|
|
94
|
+
return $null
|
|
95
|
+
}
|
|
96
|
+
$normalized = ($Value -replace '\\', '/').Trim().TrimEnd('/')
|
|
97
|
+
$normalized = $normalized -replace '^(docs/features/[^/]+(/|$)|active/|completed/)', ''
|
|
98
|
+
$segment = ($normalized -split '/')[-1]
|
|
99
|
+
if (-not $segment -or $segment -eq '.') {
|
|
100
|
+
return $null
|
|
101
|
+
}
|
|
102
|
+
return $segment
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function Find-FeatureFolderRecord {
|
|
106
|
+
<#
|
|
107
|
+
.SYNOPSIS
|
|
108
|
+
Finds the single record a reference identifies, through a union index of issue_num
|
|
109
|
+
and normalized feature_folder.
|
|
110
|
+
.DESCRIPTION
|
|
111
|
+
A non-string reference, or a string of digits only, matches records whose issue_num
|
|
112
|
+
string form equals it. Any other string matches records whose normalized
|
|
113
|
+
feature_folder basename ordinally equals the reference's normalized basename.
|
|
114
|
+
.PARAMETER Records
|
|
115
|
+
The checkpoint records (features[] or items[]).
|
|
116
|
+
.PARAMETER Reference
|
|
117
|
+
An issue number, a folder basename, or a folder path.
|
|
118
|
+
.OUTPUTS
|
|
119
|
+
The matching record, or $null when zero or several records match.
|
|
120
|
+
#>
|
|
121
|
+
[CmdletBinding()]
|
|
122
|
+
[OutputType([object])]
|
|
123
|
+
param(
|
|
124
|
+
[AllowNull()]
|
|
125
|
+
[AllowEmptyCollection()]
|
|
126
|
+
[object[]] $Records,
|
|
127
|
+
|
|
128
|
+
[AllowNull()]
|
|
129
|
+
[object] $Reference
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if ($null -eq $Records -or $null -eq $Reference) {
|
|
133
|
+
return $null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
$byIssue = (-not ($Reference -is [string])) -or ($Reference -match '^\d+$')
|
|
137
|
+
$key = if ($byIssue) { [string]$Reference } else { ConvertTo-FeatureFolderBasename -Value $Reference }
|
|
138
|
+
if (-not $key) {
|
|
139
|
+
return $null
|
|
140
|
+
}
|
|
141
|
+
$field = if ($byIssue) { 'issue_num' } else { 'feature_folder' }
|
|
142
|
+
|
|
143
|
+
$matched = @(foreach ($record in $Records) {
|
|
144
|
+
if ($null -eq $record) {
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
$property = $record.PSObject.Properties[$field]
|
|
148
|
+
if ($null -eq $property -or $null -eq $property.Value) {
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
$value = if ($byIssue) { [string]$property.Value } else { ConvertTo-FeatureFolderBasename -Value ([string]$property.Value) }
|
|
152
|
+
if ($value -ceq $key) {
|
|
153
|
+
$record
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
if ($matched.Count -ne 1) {
|
|
157
|
+
return $null
|
|
158
|
+
}
|
|
159
|
+
return $matched[0]
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function Select-FeatureFolderTarget {
|
|
163
|
+
<#
|
|
164
|
+
.SYNOPSIS
|
|
165
|
+
Selects the target feature folder from the cited candidates and the run records
|
|
166
|
+
(R2-R6).
|
|
167
|
+
.DESCRIPTION
|
|
168
|
+
Each candidate maps to at most one record. Unmatched candidates stay in the set.
|
|
169
|
+
With -DependencyAware, a matched candidate is removed when its record lies in the
|
|
170
|
+
transitive depends_on closure of another cited candidate's record and that record
|
|
171
|
+
does not lie in its own closure, so a cycle removes neither. One remaining candidate
|
|
172
|
+
is Resolved. Several remaining candidates are Resolved only when
|
|
173
|
+
-DeclaredIssueNumber names exactly one remaining record. Zero candidates resolve
|
|
174
|
+
through -FallbackIssueNumber when it names a record. Otherwise the result is
|
|
175
|
+
NoTarget for zero and Ambiguous for several. Neither string length nor candidate
|
|
176
|
+
position affects the status.
|
|
177
|
+
.PARAMETER Records
|
|
178
|
+
The checkpoint records (features[] or items[]).
|
|
179
|
+
.PARAMETER Candidate
|
|
180
|
+
The cited folder basenames, in first-occurrence order.
|
|
181
|
+
.PARAMETER DeclaredIssueNumber
|
|
182
|
+
Optional tie-break from the canonical issue-number line.
|
|
183
|
+
.PARAMETER FallbackIssueNumber
|
|
184
|
+
Optional issue number used only when no candidate is cited.
|
|
185
|
+
.PARAMETER DependencyAware
|
|
186
|
+
Removes cited candidates that are dependencies of another cited candidate.
|
|
187
|
+
.OUTPUTS
|
|
188
|
+
System.Management.Automation.PSCustomObject with Status, Record, Basename,
|
|
189
|
+
Remaining, and Detail.
|
|
190
|
+
#>
|
|
191
|
+
[CmdletBinding()]
|
|
192
|
+
[OutputType([pscustomobject])]
|
|
193
|
+
param(
|
|
194
|
+
[AllowNull()]
|
|
195
|
+
[AllowEmptyCollection()]
|
|
196
|
+
[object[]] $Records,
|
|
197
|
+
|
|
198
|
+
[AllowNull()]
|
|
199
|
+
[AllowEmptyCollection()]
|
|
200
|
+
[string[]] $Candidate,
|
|
201
|
+
|
|
202
|
+
[int] $DeclaredIssueNumber,
|
|
203
|
+
|
|
204
|
+
[int] $FallbackIssueNumber,
|
|
205
|
+
|
|
206
|
+
[switch] $DependencyAware
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
$recordList = @($Records | Where-Object { $null -ne $_ })
|
|
210
|
+
$entries = @(foreach ($name in @($Candidate | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })) {
|
|
211
|
+
[pscustomobject]@{ Basename = $name; Record = Find-FeatureFolderRecord -Records $recordList -Reference $name }
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
if ($DependencyAware -and $entries.Count -gt 1) {
|
|
215
|
+
# Transitive depends_on closure of each matched candidate's record.
|
|
216
|
+
$closures = @(foreach ($entry in $entries) {
|
|
217
|
+
$closure = [System.Collections.Generic.List[object]]::new()
|
|
218
|
+
$pending = [System.Collections.Generic.Stack[object]]::new()
|
|
219
|
+
if ($null -ne $entry.Record) {
|
|
220
|
+
$pending.Push($entry.Record)
|
|
221
|
+
}
|
|
222
|
+
while ($pending.Count -gt 0) {
|
|
223
|
+
$dependsOn = $pending.Pop().PSObject.Properties['depends_on']
|
|
224
|
+
if ($null -eq $dependsOn) {
|
|
225
|
+
continue
|
|
226
|
+
}
|
|
227
|
+
foreach ($reference in @($dependsOn.Value)) {
|
|
228
|
+
$dependency = Find-FeatureFolderRecord -Records $recordList -Reference $reference
|
|
229
|
+
if ($null -ne $dependency -and -not $closure.Contains($dependency)) {
|
|
230
|
+
$closure.Add($dependency)
|
|
231
|
+
$pending.Push($dependency)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
, $closure
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
$kept = for ($i = 0; $i -lt $entries.Count; $i++) {
|
|
239
|
+
$pruned = $false
|
|
240
|
+
for ($j = 0; $j -lt $entries.Count -and $null -ne $entries[$i].Record; $j++) {
|
|
241
|
+
if ($i -eq $j -or $null -eq $entries[$j].Record) {
|
|
242
|
+
continue
|
|
243
|
+
}
|
|
244
|
+
if ($closures[$j].Contains($entries[$i].Record) -and -not $closures[$i].Contains($entries[$j].Record)) {
|
|
245
|
+
$pruned = $true
|
|
246
|
+
break
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (-not $pruned) {
|
|
250
|
+
$entries[$i]
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
$entries = @($kept)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
$remaining = [string[]]@($entries | ForEach-Object { $_.Basename })
|
|
257
|
+
$selected = $null
|
|
258
|
+
if ($entries.Count -eq 1) {
|
|
259
|
+
$selected = $entries[0]
|
|
260
|
+
}
|
|
261
|
+
elseif ($entries.Count -gt 1 -and $PSBoundParameters.ContainsKey('DeclaredIssueNumber')) {
|
|
262
|
+
$declared = [string]$DeclaredIssueNumber
|
|
263
|
+
$byIssue = @($entries | Where-Object {
|
|
264
|
+
$null -ne $_.Record -and $null -ne $_.Record.PSObject.Properties['issue_num'] -and
|
|
265
|
+
([string]$_.Record.issue_num) -ceq $declared
|
|
266
|
+
})
|
|
267
|
+
if ($byIssue.Count -eq 1) {
|
|
268
|
+
$selected = $byIssue[0]
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
elseif ($entries.Count -eq 0 -and $PSBoundParameters.ContainsKey('FallbackIssueNumber')) {
|
|
272
|
+
$fallback = Find-FeatureFolderRecord -Records $recordList -Reference $FallbackIssueNumber
|
|
273
|
+
if ($null -ne $fallback) {
|
|
274
|
+
$folder = $fallback.PSObject.Properties['feature_folder']
|
|
275
|
+
$basename = if ($null -ne $folder) { ConvertTo-FeatureFolderBasename -Value ([string]$folder.Value) } else { $null }
|
|
276
|
+
$selected = [pscustomobject]@{ Basename = $basename; Record = $fallback }
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if ($null -ne $selected) {
|
|
281
|
+
return [pscustomobject]@{
|
|
282
|
+
Status = 'Resolved'
|
|
283
|
+
Record = $selected.Record
|
|
284
|
+
Basename = $selected.Basename
|
|
285
|
+
Remaining = $remaining
|
|
286
|
+
Detail = "resolved target feature folder '$($selected.Basename)'"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if ($entries.Count -eq 0) {
|
|
290
|
+
return [pscustomobject]@{
|
|
291
|
+
Status = 'NoTarget'
|
|
292
|
+
Record = $null
|
|
293
|
+
Basename = $null
|
|
294
|
+
Remaining = $remaining
|
|
295
|
+
Detail = 'no target feature folder is cited'
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return [pscustomobject]@{
|
|
299
|
+
Status = 'Ambiguous'
|
|
300
|
+
Record = $null
|
|
301
|
+
Basename = $null
|
|
302
|
+
Remaining = $remaining
|
|
303
|
+
Detail = 'ambiguous target feature folder; remaining candidates: ' + ($remaining -join ', ')
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function Resolve-FeatureFolderWorkMode {
|
|
308
|
+
<#
|
|
309
|
+
.SYNOPSIS
|
|
310
|
+
Parses the persisted '- Work Mode:' marker out of issue.md content.
|
|
311
|
+
.DESCRIPTION
|
|
312
|
+
Returns minor-audit, full-bug, or full-feature. The legacy full marker normalizes to
|
|
313
|
+
full-feature. Missing, empty, malformed, or unrecognized content returns
|
|
314
|
+
-UnresolvedMode.
|
|
315
|
+
.PARAMETER IssueContent
|
|
316
|
+
The raw issue.md text, or $null when it could not be read.
|
|
317
|
+
.PARAMETER UnresolvedMode
|
|
318
|
+
The value returned when no recognized marker is present. Defaults to full-feature.
|
|
319
|
+
.OUTPUTS
|
|
320
|
+
System.String
|
|
321
|
+
#>
|
|
322
|
+
[CmdletBinding()]
|
|
323
|
+
[OutputType([string])]
|
|
324
|
+
param(
|
|
325
|
+
[AllowNull()]
|
|
326
|
+
[AllowEmptyString()]
|
|
327
|
+
[string] $IssueContent,
|
|
328
|
+
|
|
329
|
+
[AllowNull()]
|
|
330
|
+
[AllowEmptyString()]
|
|
331
|
+
[string] $UnresolvedMode = 'full-feature'
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
if ([string]::IsNullOrWhiteSpace($IssueContent)) {
|
|
335
|
+
return $UnresolvedMode
|
|
336
|
+
}
|
|
337
|
+
$match = [regex]::Match($IssueContent, '(?im)^-\s*Work Mode:\s*(minor-audit|full-feature|full-bug|full)\s*$')
|
|
338
|
+
if (-not $match.Success) {
|
|
339
|
+
return $UnresolvedMode
|
|
340
|
+
}
|
|
341
|
+
$rawMode = $match.Groups[1].Value
|
|
342
|
+
if ($rawMode -eq 'full') {
|
|
343
|
+
return 'full-feature'
|
|
344
|
+
}
|
|
345
|
+
return $rawMode
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function Get-FeatureFolderPlanPrerequisite {
|
|
349
|
+
<#
|
|
350
|
+
.SYNOPSIS
|
|
351
|
+
Maps a work mode to the files a feature folder must contain before a plan is written.
|
|
352
|
+
.DESCRIPTION
|
|
353
|
+
minor-audit requires issue.md; full-bug requires issue.md and spec.md; full-feature
|
|
354
|
+
and every other value require issue.md, spec.md, and user-story.md.
|
|
355
|
+
.PARAMETER WorkMode
|
|
356
|
+
The resolved work mode.
|
|
357
|
+
.OUTPUTS
|
|
358
|
+
System.String[]
|
|
359
|
+
#>
|
|
360
|
+
[CmdletBinding()]
|
|
361
|
+
[OutputType([string[]])]
|
|
362
|
+
param(
|
|
363
|
+
[AllowNull()]
|
|
364
|
+
[AllowEmptyString()]
|
|
365
|
+
[string] $WorkMode
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
switch ($WorkMode) {
|
|
369
|
+
'minor-audit' { return [string[]]@('issue.md') }
|
|
370
|
+
'full-bug' { return [string[]]@('issue.md', 'spec.md') }
|
|
371
|
+
default { return [string[]]@('issue.md', 'spec.md', 'user-story.md') }
|
|
372
|
+
}
|
|
373
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Resolve the coverage thresholds that govern the feature-review coverage validation.
|
|
4
|
+
.DESCRIPTION
|
|
5
|
+
Implements the threshold precedence of the pushed quality-tiers rule for the
|
|
6
|
+
feature-review SubagentStop hook (issue #824, follow-up FU-823-1). When the
|
|
7
|
+
repository's root CLAUDE.md states a line or branch coverage threshold, that threshold
|
|
8
|
+
governs; otherwise the default governs (85 percent line, 75 percent branch). Each
|
|
9
|
+
metric is resolved on its own, so a root CLAUDE.md that states only a line threshold
|
|
10
|
+
leaves the branch default in force.
|
|
11
|
+
|
|
12
|
+
Pure string logic only: the caller supplies the CLAUDE.md text. No disk, process,
|
|
13
|
+
network, clock, or environment access. It is dot-sourced by
|
|
14
|
+
validate-feature-review-coverage.ps1 as:
|
|
15
|
+
. (Join-Path $PSScriptRoot 'feature-review-coverage-thresholds.ps1')
|
|
16
|
+
#>
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
function Get-FeatureReviewCoverageThreshold {
|
|
20
|
+
<#
|
|
21
|
+
.SYNOPSIS
|
|
22
|
+
Return the governing line and branch coverage thresholds for a root CLAUDE.md text.
|
|
23
|
+
.DESCRIPTION
|
|
24
|
+
For each metric M in 'line' and 'branch', a figure is read only when a comparator
|
|
25
|
+
or floor phrase links it to the metric on the same line as 'M coverage'. Two forms
|
|
26
|
+
are accepted, and any text without a percent sign may sit between 'M coverage' and
|
|
27
|
+
the comparator or the figure:
|
|
28
|
+
|
|
29
|
+
- Comparator before the figure: one of '>=', the U+2265 sign, 'at least',
|
|
30
|
+
'minimum', 'minimum of', or 'no less than', an optional colon, and the figure
|
|
31
|
+
followed by a percent sign (for example 'line coverage >= 85%').
|
|
32
|
+
- Trailing floor phrase after the figure: the figure followed by a percent sign,
|
|
33
|
+
then one of 'minimum', 'at least', 'or more', 'or higher', 'or above', or
|
|
34
|
+
'or greater' (for example 'line coverage: 70% minimum').
|
|
35
|
+
|
|
36
|
+
Prose such as 'line coverage was 62% last release' is not read. The first match in
|
|
37
|
+
text order across both forms whose value parses (invariant culture) to a number
|
|
38
|
+
from 0 to 100 sets that metric and its source to 'claude-md'. Otherwise the metric
|
|
39
|
+
keeps its default (line 85, branch 75) and its source is 'default'.
|
|
40
|
+
|
|
41
|
+
Known limitation: a combined statement such as 'line and branch coverage >= 70%'
|
|
42
|
+
matches neither metric, because the metric name must be followed directly by
|
|
43
|
+
'coverage' and must not be joined to the other metric name by 'and', 'or', '&',
|
|
44
|
+
or '/'. Both metrics then keep their defaults, which is the stricter direction.
|
|
45
|
+
.PARAMETER ClaudeMdText
|
|
46
|
+
The text of the repository's root CLAUDE.md; $null or empty when it is absent.
|
|
47
|
+
.OUTPUTS
|
|
48
|
+
System.Management.Automation.PSCustomObject - Line, Branch, LineSource, BranchSource.
|
|
49
|
+
#>
|
|
50
|
+
[CmdletBinding()]
|
|
51
|
+
[OutputType([pscustomobject])]
|
|
52
|
+
param(
|
|
53
|
+
[Parameter(Mandatory)][AllowNull()][AllowEmptyString()][string] $ClaudeMdText
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
$defaults = [ordered]@{ line = 85.0; branch = 75.0 }
|
|
57
|
+
$resolved = @{}
|
|
58
|
+
$culture = [System.Globalization.CultureInfo]::InvariantCulture
|
|
59
|
+
$style = [System.Globalization.NumberStyles]::Float
|
|
60
|
+
|
|
61
|
+
foreach ($metric in $defaults.Keys) {
|
|
62
|
+
$value = [double]$defaults[$metric]
|
|
63
|
+
$source = 'default'
|
|
64
|
+
if (-not [string]::IsNullOrEmpty($ClaudeMdText)) {
|
|
65
|
+
$pattern = '(?im)(?<!\b(?:line|branch)\s*(?:and|or|&|/)\s*)\b' + $metric + '\s+coverage\b[^\r\n%]*?(?:(?:>=|' + [char]0x2265 + '|\bat\s+least\b|\bminimum(?:\s+of)?\b|\bno\s+less\s+than\b)\s*:?\s*(?<![\d.])(?<pct>\d{1,3}(?:\.\d+)?)\s*%|(?<![\d.])(?<pct>\d{1,3}(?:\.\d+)?)\s*%\s*(?:minimum|at\s+least|or\s+(?:more|higher|above|greater))\b)'
|
|
66
|
+
foreach ($match in [regex]::Matches($ClaudeMdText, $pattern)) {
|
|
67
|
+
$parsed = 0.0
|
|
68
|
+
$isNumber = [double]::TryParse($match.Groups['pct'].Value, $style, $culture, [ref]$parsed)
|
|
69
|
+
if ($isNumber -and $parsed -ge 0 -and $parsed -le 100) {
|
|
70
|
+
$value = $parsed
|
|
71
|
+
$source = 'claude-md'
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
$resolved[$metric] = [pscustomobject]@{ Value = $value; Source = $source }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return [pscustomobject]@{
|
|
80
|
+
Line = $resolved['line'].Value
|
|
81
|
+
Branch = $resolved['branch'].Value
|
|
82
|
+
LineSource = $resolved['line'].Source
|
|
83
|
+
BranchSource = $resolved['branch'].Source
|
|
84
|
+
}
|
|
85
|
+
}
|