@danmoisan/drm-copilot-mcp 1.1.18 → 1.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/mcp-server.js +989 -662
- package/package.json +3 -2
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +11 -0
- package/resources/claude-customizations/.claude/agents/atomic-planner.md +10 -0
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +12 -4
- package/resources/claude-customizations/.claude/agents/epic-planner.md +5 -4
- package/resources/claude-customizations/.claude/agents/feature-review.md +6 -4
- package/resources/claude-customizations/.claude/agents/orchestrator.md +5 -4
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +31 -13
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +9 -6
- package/resources/claude-customizations/.claude/agents/pr-author.md +15 -2
- package/resources/claude-customizations/.claude/agents/prd-feature.md +1 -1
- package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +111 -46
- package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +105 -0
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +8 -1
- package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +116 -59
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +55 -23
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +74 -75
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +127 -35
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +61 -57
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +129 -39
- package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +39 -51
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +17 -12
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +65 -50
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +49 -42
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +54 -72
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +8 -2
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +279 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +129 -75
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.artifact-root.ps1 +233 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.docs-only.ps1 +224 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +69 -24
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +29 -18
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +10 -4
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +14 -5
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +8 -2
- package/resources/claude-customizations/.claude/hooks/feature-folder-resolution.ps1 +373 -0
- package/resources/claude-customizations/.claude/hooks/feature-review-coverage-thresholds.ps1 +85 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-heredoc.ps1 +119 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-invocation-operands.ps1 +198 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +254 -255
- package/resources/claude-customizations/.claude/hooks/hook-command-payload-powershell.ps1 +201 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-payload.ps1 +484 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +27 -110
- package/resources/claude-customizations/.claude/hooks/hook-dependency-guard.ps1 +114 -0
- package/resources/claude-customizations/.claude/hooks/hook-docs-only-diff.ps1 +493 -0
- package/resources/claude-customizations/.claude/hooks/subagentstop-envelope.ps1 +192 -0
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +40 -42
- package/resources/claude-customizations/.claude/hooks/validate-executor-output.ps1 +66 -38
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-metrics.ps1 +180 -0
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-rules.ps1 +195 -0
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage.ps1 +141 -395
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-resolution.ps1 +311 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-routing.ps1 +170 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +115 -190
- package/resources/claude-customizations/.claude/hooks/validate-planner-output.ps1 +64 -32
- package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +39 -44
- package/resources/claude-customizations/.claude/hooks/validate-prd-feature-output.ps1 +43 -14
- package/resources/claude-customizations/.claude/hooks/validate-required-artifact-output.ps1 +66 -36
- package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +31 -34
- package/resources/claude-customizations/.claude/lib/bash/mutate-parallel-run.sh +191 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-cohorts.sh +13 -3
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-cli.sh +152 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-ops.sh +193 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation.sh +322 -0
- package/resources/claude-customizations/.claude/lib/bash/remove-parallel-item.sh +146 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +23 -33
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +86 -6
- package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-CiGateParser.ps1 +91 -19
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateEpicWaveBarrier.psm1 +346 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +24 -3
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +79 -2
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +3 -3
- package/resources/claude-customizations/.claude/rules/architecture-boundaries.md +5 -5
- package/resources/claude-customizations/.claude/rules/csharp.md +2 -2
- package/resources/claude-customizations/.claude/rules/general-code-change.md +1 -1
- package/resources/claude-customizations/.claude/rules/general-unit-test.md +3 -2
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +37 -4
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +50 -5
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +47 -8
- package/resources/claude-customizations/.claude/rules/quality-tiers.md +11 -7
- package/resources/claude-customizations/.claude/rules/shell.md +10 -8
- package/resources/claude-customizations/.claude/rules/typescript.md +1 -1
- package/resources/claude-customizations/.claude/settings.json +3 -4
- package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +5 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_detached_lib.sh +1 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +2 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +2 -0
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +33 -6
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +4 -3
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +14 -6
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +52 -14
- package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +22 -9
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +79 -16
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +63 -7
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +35 -12
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +2 -2
- package/resources/claude-customizations/pack-manifests/core.json +23 -0
- package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +5 -5
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-executor/SKILL.md +11 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +4 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +10 -0
- package/resources/codex-and-agents-customizations/.agents/skills/csharp/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +17 -1
- package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +2 -1
- package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +13 -5
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +3 -2
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +15 -6
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +11 -7
- package/resources/codex-and-agents-customizations/.agents/skills/translate-claude-to-codex/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +2 -1
- package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/codex-web-setup.sh +318 -273
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +97 -44
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-helpers.ps1 +105 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +37 -5
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +6 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +54 -41
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +35 -14
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +24 -11
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +13 -4
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/feature-folder-resolution.ps1 +373 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/feature-review-coverage-thresholds.ps1 +85 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-heredoc.ps1 +119 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation-operands.ps1 +198 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +254 -255
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload-powershell.ps1 +201 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload.ps1 +484 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +27 -110
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-dependency-guard.ps1 +114 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-docs-only-diff.ps1 +493 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +24 -8
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +9 -0
- package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +2 -2
- package/resources/customizations/.github/instructions/csharp-code-change.instructions.md +4 -4
- package/resources/customizations/.github/instructions/csharp-unit-test.instructions.md +2 -2
- package/resources/customizations/.github/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +4 -0
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +1 -1
- package/resources/powershell/PoshQC/PoshQC.Coverage.psm1 +314 -0
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +25 -28
- package/resources/powershell/PoshQC/PoshQC.psm1 +1 -0
- package/resources/powershell/PoshQC/README.md +8 -2
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +2 -319
|
@@ -26,434 +26,180 @@
|
|
|
26
26
|
- For each changed language, the policy audit must contain a
|
|
27
27
|
coverage-scoped PASS or FAIL verdict.
|
|
28
28
|
- Scope-narrowing phrases on coverage rows are treated as failures.
|
|
29
|
-
-
|
|
30
|
-
the
|
|
29
|
+
- Coverage floors follow the threshold precedence of the pushed quality-tiers
|
|
30
|
+
rule: when the repository's root CLAUDE.md states a line or branch coverage
|
|
31
|
+
threshold, that threshold governs; otherwise the 85 percent line and 75 percent
|
|
32
|
+
branch defaults govern.
|
|
33
|
+
- Each metric falls back independently, so a root CLAUDE.md that states only
|
|
34
|
+
a line threshold leaves the 75 percent branch default in force. The figures are
|
|
35
|
+
read by Get-FeatureReviewCoverageThreshold (feature-review-coverage-thresholds.ps1).
|
|
36
|
+
- The root CLAUDE.md is read by Get-FeatureReviewClaudeMdText: an absent root
|
|
37
|
+
CLAUDE.md uses the defaults, and an unreadable root CLAUDE.md fails closed (the
|
|
38
|
+
gate blocks, because the governing floors cannot be resolved).
|
|
39
|
+
- When repo-wide line coverage for an available artifact is below the governing
|
|
40
|
+
line threshold, the policy audit must carry a FAIL verdict for that language;
|
|
41
|
+
branch coverage below the governing branch threshold blocks termination.
|
|
31
42
|
|
|
32
43
|
.NOTES
|
|
33
|
-
Reads the
|
|
34
|
-
|
|
44
|
+
Reads the SubagentStop envelope from stdin through Read-SubagentStopEnvelope
|
|
45
|
+
(subagentstop-envelope.ps1). Exits 0 to allow termination. Every block path,
|
|
46
|
+
including a payload anomaly and a validator error, blocks through
|
|
47
|
+
Exit-SubagentStopBlock, which writes the reason to stderr and exits 2. A hook
|
|
48
|
+
dependency failure blocks through the inline guard tail (stderr reason, exit 2).
|
|
49
|
+
A condition the subagent can repair (a missing token, path, file, or coverage
|
|
50
|
+
verdict) may block again; a condition it cannot repair blocks once and does not
|
|
51
|
+
block again when stop_hook_active is true.
|
|
35
52
|
#>
|
|
36
53
|
[CmdletBinding()]
|
|
37
54
|
param()
|
|
55
|
+
$script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
|
|
56
|
+
try { . (Join-Path $PSScriptRoot 'feature-review-coverage-thresholds.ps1') } catch { Add-HookDependencyFailure -Name 'feature-review-coverage-thresholds.ps1' -ErrorRecord $_ }
|
|
57
|
+
try { . (Join-Path $PSScriptRoot 'validate-feature-review-coverage-metrics.ps1') } catch { Add-HookDependencyFailure -Name 'validate-feature-review-coverage-metrics.ps1' -ErrorRecord $_ }
|
|
58
|
+
try { . (Join-Path $PSScriptRoot 'validate-feature-review-coverage-rules.ps1') } catch { Add-HookDependencyFailure -Name 'validate-feature-review-coverage-rules.ps1' -ErrorRecord $_ }
|
|
59
|
+
try { . (Join-Path $PSScriptRoot 'subagentstop-envelope.ps1') } catch { Add-HookDependencyFailure -Name 'subagentstop-envelope.ps1' -ErrorRecord $_ }
|
|
38
60
|
|
|
39
61
|
$ErrorActionPreference = 'Stop'
|
|
40
62
|
|
|
41
|
-
function Get-ArtifactFileContent {
|
|
42
|
-
[CmdletBinding()]
|
|
43
|
-
[OutputType([hashtable])]
|
|
44
|
-
param(
|
|
45
|
-
[Parameter(Mandatory = $true)]
|
|
46
|
-
[string] $Path
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
|
50
|
-
return @{ Exists = $false; Text = $null; Lines = @() }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
$text = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
|
|
54
|
-
$lines = Get-Content -LiteralPath $Path -ErrorAction Stop
|
|
55
|
-
if ($null -eq $lines) {
|
|
56
|
-
$lines = @()
|
|
57
|
-
}
|
|
58
|
-
elseif ($lines -isnot [array]) {
|
|
59
|
-
$lines = @($lines)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return @{ Exists = $true; Text = $text; Lines = $lines }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function Get-ReviewArtifactPathFromOutput {
|
|
66
|
-
[CmdletBinding()]
|
|
67
|
-
[OutputType([string])]
|
|
68
|
-
param(
|
|
69
|
-
[Parameter(Mandatory = $true)]
|
|
70
|
-
[string] $AgentOutput,
|
|
71
|
-
|
|
72
|
-
[Parameter(Mandatory = $true)]
|
|
73
|
-
[string] $Token
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
$escapedToken = [regex]::Escape($Token)
|
|
77
|
-
$pattern = "$escapedToken\s*[:=]\s*[""']?([^\s""'\)`]+)|\[$escapedToken\]\(([^)]+)\)"
|
|
78
|
-
$match = [regex]::Match(
|
|
79
|
-
$AgentOutput,
|
|
80
|
-
$pattern,
|
|
81
|
-
[System.Text.RegularExpressions.RegexOptions]::IgnoreCase
|
|
82
|
-
)
|
|
83
|
-
if (-not $match.Success) {
|
|
84
|
-
return $null
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
$value = if ($match.Groups[1].Success) { $match.Groups[1].Value } else { $match.Groups[2].Value }
|
|
88
|
-
$value = $value.Trim()
|
|
89
|
-
if ([string]::IsNullOrWhiteSpace($value)) {
|
|
90
|
-
return $null
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return $value
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function Get-ReviewArtifactInfo {
|
|
97
|
-
[CmdletBinding()]
|
|
98
|
-
[OutputType([hashtable])]
|
|
99
|
-
param(
|
|
100
|
-
[Parameter(Mandatory = $true)]
|
|
101
|
-
[string] $Path,
|
|
102
|
-
|
|
103
|
-
[Parameter(Mandatory = $true)]
|
|
104
|
-
[string] $Stem
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
$normalized = $Path -replace '\\', '/'
|
|
108
|
-
$pattern = "^docs/features/active/(?<Folder>.+)/$Stem\.(?<Timestamp>\d{4}-\d{2}-\d{2}T\d{2}-\d{2})\.md$"
|
|
109
|
-
$match = [regex]::Match($normalized, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
|
|
110
|
-
if (-not $match.Success) {
|
|
111
|
-
return $null
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return @{
|
|
115
|
-
Folder = $match.Groups['Folder'].Value
|
|
116
|
-
Timestamp = $match.Groups['Timestamp'].Value
|
|
117
|
-
Path = $normalized
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function Get-ChangedLanguageSet {
|
|
122
|
-
[OutputType([System.Collections.Hashtable])]
|
|
123
|
-
param([string[]]$Lines)
|
|
124
|
-
|
|
125
|
-
$langs = [ordered]@{}
|
|
126
|
-
foreach ($line in $Lines) {
|
|
127
|
-
if ($line -notmatch '^\s*-\s+(\S+)\s+\(\+\d+/-\d+\)\s*$') { continue }
|
|
128
|
-
$path = $matches[1]
|
|
129
|
-
$ext = [IO.Path]::GetExtension($path).ToLowerInvariant()
|
|
130
|
-
switch -Regex ($ext) {
|
|
131
|
-
'^\.(ts|tsx)$' { $langs['TypeScript'] = $true }
|
|
132
|
-
'^\.py$' { $langs['Python'] = $true }
|
|
133
|
-
'^\.(ps1|psm1)$' { $langs['PowerShell'] = $true }
|
|
134
|
-
'^\.cs$' { $langs['CSharp'] = $true }
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return $langs
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function Get-LcovRepoCoverage {
|
|
141
|
-
[OutputType([Nullable[double]])]
|
|
142
|
-
param([string]$Path)
|
|
143
|
-
|
|
144
|
-
$file = Get-ArtifactFileContent -Path $Path
|
|
145
|
-
if (-not $file.Exists) { return $null }
|
|
146
|
-
|
|
147
|
-
$totalFound = 0
|
|
148
|
-
$totalHit = 0
|
|
149
|
-
foreach ($line in $file.Lines) {
|
|
150
|
-
if ($line.StartsWith('LF:')) {
|
|
151
|
-
$totalFound += [int]($line.Substring(3))
|
|
152
|
-
}
|
|
153
|
-
elseif ($line.StartsWith('LH:')) {
|
|
154
|
-
$totalHit += [int]($line.Substring(3))
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if ($totalFound -le 0) { return $null }
|
|
158
|
-
return [math]::Round(($totalHit * 100.0) / $totalFound, 2)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function Get-LcovBranchCoverage {
|
|
162
|
-
# Parses LCOV branch counters (BRF: branches found, BRH: branches hit) and returns a percent.
|
|
163
|
-
# LCOV emits one BRF/BRH line per source file in the report; we sum across the file to compute
|
|
164
|
-
# repo-wide branch coverage. Returns $null when the artifact is absent or when no branch
|
|
165
|
-
# information is recorded (BRF total = 0).
|
|
166
|
-
[OutputType([Nullable[double]])]
|
|
167
|
-
param([string]$Path)
|
|
168
|
-
|
|
169
|
-
$file = Get-ArtifactFileContent -Path $Path
|
|
170
|
-
if (-not $file.Exists) { return $null }
|
|
171
|
-
|
|
172
|
-
$totalFound = 0
|
|
173
|
-
$totalHit = 0
|
|
174
|
-
foreach ($line in $file.Lines) {
|
|
175
|
-
if ($line.StartsWith('BRF:')) {
|
|
176
|
-
$totalFound += [int]($line.Substring(4))
|
|
177
|
-
}
|
|
178
|
-
elseif ($line.StartsWith('BRH:')) {
|
|
179
|
-
$totalHit += [int]($line.Substring(4))
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if ($totalFound -le 0) { return $null }
|
|
183
|
-
return [math]::Round(($totalHit * 100.0) / $totalFound, 2)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function Get-JacocoBranchCoverage {
|
|
187
|
-
[OutputType([Nullable[double]])]
|
|
188
|
-
param([string]$Path)
|
|
189
|
-
|
|
190
|
-
$file = Get-ArtifactFileContent -Path $Path
|
|
191
|
-
if (-not $file.Exists) { return $null }
|
|
192
|
-
|
|
193
|
-
[xml]$doc = $file.Text
|
|
194
|
-
$counters = $doc.SelectNodes('//counter[@type="BRANCH"]')
|
|
195
|
-
if (-not $counters -or $counters.Count -eq 0) { return $null }
|
|
196
|
-
|
|
197
|
-
$missed = 0
|
|
198
|
-
$covered = 0
|
|
199
|
-
foreach ($counter in $counters) {
|
|
200
|
-
$missed += [int]$counter.missed
|
|
201
|
-
$covered += [int]$counter.covered
|
|
202
|
-
}
|
|
203
|
-
$total = $missed + $covered
|
|
204
|
-
if ($total -le 0) { return $null }
|
|
205
|
-
return [math]::Round(($covered * 100.0) / $total, 2)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function Get-LanguageBranchCoverage {
|
|
209
|
-
[OutputType([Nullable[double]])]
|
|
210
|
-
param([string]$Language)
|
|
211
|
-
|
|
212
|
-
switch ($Language) {
|
|
213
|
-
'TypeScript' { return Get-LcovBranchCoverage -Path 'coverage/lcov.info' }
|
|
214
|
-
'Python' { return Get-LcovBranchCoverage -Path 'artifacts/python/lcov.info' }
|
|
215
|
-
'PowerShell' { return Get-JacocoBranchCoverage -Path 'artifacts/pester/powershell-coverage.xml' }
|
|
216
|
-
'CSharp' { return Get-JacocoBranchCoverage -Path 'artifacts/csharp/coverage.xml' }
|
|
217
|
-
}
|
|
218
|
-
return $null
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function Get-JacocoRepoCoverage {
|
|
222
|
-
[OutputType([Nullable[double]])]
|
|
223
|
-
param([string]$Path)
|
|
224
|
-
|
|
225
|
-
$file = Get-ArtifactFileContent -Path $Path
|
|
226
|
-
if (-not $file.Exists) { return $null }
|
|
227
|
-
|
|
228
|
-
[xml]$doc = $file.Text
|
|
229
|
-
$counters = $doc.SelectNodes('//counter[@type="LINE"]')
|
|
230
|
-
if (-not $counters -or $counters.Count -eq 0) { return $null }
|
|
231
|
-
|
|
232
|
-
$missed = 0
|
|
233
|
-
$covered = 0
|
|
234
|
-
foreach ($counter in $counters) {
|
|
235
|
-
$missed += [int]$counter.missed
|
|
236
|
-
$covered += [int]$counter.covered
|
|
237
|
-
}
|
|
238
|
-
$total = $missed + $covered
|
|
239
|
-
if ($total -le 0) { return $null }
|
|
240
|
-
return [math]::Round(($covered * 100.0) / $total, 2)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function Get-LanguageRepoCoverage {
|
|
244
|
-
[OutputType([Nullable[double]])]
|
|
245
|
-
param(
|
|
246
|
-
[string]$Language
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
switch ($Language) {
|
|
250
|
-
'TypeScript' { return Get-LcovRepoCoverage -Path 'coverage/lcov.info' }
|
|
251
|
-
'Python' { return Get-LcovRepoCoverage -Path 'artifacts/python/lcov.info' }
|
|
252
|
-
'PowerShell' { return Get-JacocoRepoCoverage -Path 'artifacts/pester/powershell-coverage.xml' }
|
|
253
|
-
'CSharp' { return Get-JacocoRepoCoverage -Path 'artifacts/csharp/coverage.xml' }
|
|
254
|
-
}
|
|
255
|
-
return $null
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function Test-LanguageCoverageRow {
|
|
259
|
-
[OutputType([System.Collections.Hashtable])]
|
|
260
|
-
param(
|
|
261
|
-
[string]$AuditText,
|
|
262
|
-
[string]$Language,
|
|
263
|
-
[Nullable[double]]$RepoWidePct,
|
|
264
|
-
[Nullable[double]]$BranchPct
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
$languageLabelMap = @{
|
|
268
|
-
'TypeScript' = @('TypeScript', 'typescript')
|
|
269
|
-
'Python' = @('Python', 'python', 'pytest')
|
|
270
|
-
'PowerShell' = @('PowerShell', 'powershell', 'pester')
|
|
271
|
-
'CSharp' = @('C#', 'CSharp', 'csharp', '.NET', 'dotnet')
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
$labels = $languageLabelMap[$Language]
|
|
275
|
-
$labelPattern = '(?i)(' + (($labels | ForEach-Object { [regex]::Escape($_) }) -join '|') + ')'
|
|
276
|
-
|
|
277
|
-
$lines = $AuditText -split "`r?`n"
|
|
278
|
-
$languageLines = $lines | Where-Object { $_ -match $labelPattern }
|
|
279
|
-
|
|
280
|
-
if (-not $languageLines -or $languageLines.Count -eq 0) {
|
|
281
|
-
return @{
|
|
282
|
-
Ok = $false
|
|
283
|
-
Reason = "$Language has changed files on the branch but the policy-audit does not mention $Language."
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
$coverageLines = $languageLines | Where-Object { $_ -match '(?i)(coverage|lcov|line[s]?\s+hit|pester)' }
|
|
288
|
-
if (-not $coverageLines -or $coverageLines.Count -eq 0) {
|
|
289
|
-
return @{
|
|
290
|
-
Ok = $false
|
|
291
|
-
Reason = "$Language has changed files on the branch but no coverage-scoped row in the policy-audit mentions $Language."
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
$narrowingPattern = '(?i)(informational only|context only|out of plan scope|out of scope|not applicable|\bN/A\b|\bUNVERIFIED\b)'
|
|
296
|
-
$narrowing = $coverageLines | Where-Object { $_ -match $narrowingPattern }
|
|
297
|
-
if ($narrowing -and $narrowing.Count -gt 0) {
|
|
298
|
-
$first = ($narrowing | Select-Object -First 1).ToString().Trim()
|
|
299
|
-
return @{
|
|
300
|
-
Ok = $false
|
|
301
|
-
Reason = "$Language has changed files on the branch but a coverage-scoped row narrows scope: '$first'. Scope narrowing is not permitted for languages with changed files."
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
$verdictLines = $coverageLines | Where-Object { $_ -match '\b(PASS|FAIL)\b' }
|
|
306
|
-
if (-not $verdictLines -or $verdictLines.Count -eq 0) {
|
|
307
|
-
return @{
|
|
308
|
-
Ok = $false
|
|
309
|
-
Reason = "$Language coverage rows contain neither a PASS nor a FAIL verdict."
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if ($null -ne $RepoWidePct -and $RepoWidePct -lt 85.0) {
|
|
314
|
-
$failLines = $coverageLines | Where-Object { $_ -match '\bFAIL\b' }
|
|
315
|
-
if (-not $failLines -or $failLines.Count -eq 0) {
|
|
316
|
-
return @{
|
|
317
|
-
Ok = $false
|
|
318
|
-
Reason = ("{0} repo-wide coverage is {1}% (below the 85% line coverage floor) but the policy-audit contains no FAIL verdict on a coverage row for {0}." -f $Language, $RepoWidePct)
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
$BranchFloor = 75.0
|
|
324
|
-
if ($null -ne $BranchPct -and $BranchPct -lt $BranchFloor) {
|
|
325
|
-
return @{
|
|
326
|
-
Ok = $false
|
|
327
|
-
Reason = ("{0} branch coverage is {1}% (below the 75% branch coverage floor); policy-audit must record FAIL on the corresponding coverage row." -f $Language, $BranchPct)
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return @{ Ok = $true; Reason = $null }
|
|
332
|
-
}
|
|
333
|
-
|
|
334
63
|
function Invoke-FeatureReviewCoverageValidation {
|
|
335
64
|
[CmdletBinding()]
|
|
336
65
|
[OutputType([hashtable])]
|
|
337
66
|
param(
|
|
338
|
-
[string] $
|
|
67
|
+
[AllowNull()] [AllowEmptyString()] [string] $InputText
|
|
339
68
|
)
|
|
69
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'validate-feature-review-coverage:'
|
|
70
|
+
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
340
71
|
|
|
341
|
-
if (
|
|
342
|
-
|
|
72
|
+
$envelope = if ($PSBoundParameters.ContainsKey('InputText')) { Read-SubagentStopEnvelope -InputText $InputText } else { Read-SubagentStopEnvelope }
|
|
73
|
+
if (-not $envelope.IsValid) {
|
|
74
|
+
return @{ Ok = $false; Message = ('validate-feature-review-coverage: payload anomaly - ' + (Get-SubagentStopEnvelopeAnomalyReason -Anomaly $envelope.Anomaly)) }
|
|
343
75
|
}
|
|
344
76
|
|
|
77
|
+
# A single-pass do-while block carries the validation so that each finding exits with break; a
|
|
78
|
+
# script-block invocation would be a non-constant command position, which the enforcement-hook
|
|
79
|
+
# invocation scan rejects as unverifiable.
|
|
80
|
+
$finding = $null
|
|
345
81
|
try {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
$agentOutput = $null
|
|
353
|
-
if ($payload.PSObject.Properties.Name -contains 'output') {
|
|
354
|
-
$agentOutput = $payload.output
|
|
355
|
-
}
|
|
356
|
-
if ([string]::IsNullOrWhiteSpace($agentOutput)) {
|
|
357
|
-
return @{ Ok = $false; Message = 'feature-review hook: agent output is empty; feature-review must report required artifact paths before termination.' }
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
$requiredArtifacts = @(
|
|
361
|
-
@{ Token = 'policy-audit-path'; Stem = 'policy-audit'; Description = 'policy audit artifact' },
|
|
362
|
-
@{ Token = 'code-review-path'; Stem = 'code-review'; Description = 'code review artifact' },
|
|
363
|
-
@{ Token = 'feature-audit-path'; Stem = 'feature-audit'; Description = 'feature audit artifact' }
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
$artifactMeta = @{}
|
|
367
|
-
$errors = [System.Collections.Generic.List[string]]::new()
|
|
368
|
-
|
|
369
|
-
foreach ($artifact in $requiredArtifacts) {
|
|
370
|
-
$path = Get-ReviewArtifactPathFromOutput -AgentOutput $agentOutput -Token $artifact.Token
|
|
371
|
-
if ($null -eq $path) {
|
|
372
|
-
$errors.Add("missing $($artifact.Token): <path> for the required $($artifact.Description)")
|
|
373
|
-
continue
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
$metadata = Get-ReviewArtifactInfo -Path $path -Stem $artifact.Stem
|
|
377
|
-
if ($null -eq $metadata) {
|
|
378
|
-
$errors.Add("$($artifact.Token) '$path' is outside the required docs/features/active/.../$($artifact.Stem).<timestamp>.md location")
|
|
379
|
-
continue
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
$file = Get-ArtifactFileContent -Path $path
|
|
383
|
-
if (-not $file.Exists) {
|
|
384
|
-
$errors.Add("$($artifact.Token) '$path' was advertised for the $($artifact.Description) but no file exists at that location")
|
|
385
|
-
continue
|
|
386
|
-
}
|
|
82
|
+
do {
|
|
83
|
+
$agentOutput = [string]$envelope.AssistantText
|
|
84
|
+
if ([string]::IsNullOrWhiteSpace($agentOutput)) {
|
|
85
|
+
$finding = @{ Message = 'validate-feature-review-coverage: agent output is empty; feature-review must report required artifact paths before termination.'; HasRepairable = $true; HasNonRepairable = $false }
|
|
86
|
+
break
|
|
87
|
+
}
|
|
387
88
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
89
|
+
$requiredArtifacts = @(
|
|
90
|
+
@{ Token = 'policy-audit-path'; Stem = 'policy-audit'; Description = 'policy audit artifact' },
|
|
91
|
+
@{ Token = 'code-review-path'; Stem = 'code-review'; Description = 'code review artifact' },
|
|
92
|
+
@{ Token = 'feature-audit-path'; Stem = 'feature-audit'; Description = 'feature audit artifact' }
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
$artifactMeta = @{}
|
|
96
|
+
$errors = [System.Collections.Generic.List[string]]::new()
|
|
97
|
+
|
|
98
|
+
foreach ($artifact in $requiredArtifacts) {
|
|
99
|
+
$path = Get-ReviewArtifactPathFromOutput -AgentOutput $agentOutput -Token $artifact.Token
|
|
100
|
+
if ($null -eq $path) {
|
|
101
|
+
$errors.Add("missing $($artifact.Token): <path> for the required $($artifact.Description)")
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
$metadata = Get-ReviewArtifactInfo -Path $path -Stem $artifact.Stem
|
|
106
|
+
if ($null -eq $metadata) {
|
|
107
|
+
$errors.Add("$($artifact.Token) '$path' is outside the required docs/features/active/.../$($artifact.Stem).<timestamp>.md location")
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
$file = Get-ArtifactFileContent -Path $path
|
|
112
|
+
if (-not $file.Exists) {
|
|
113
|
+
$errors.Add("$($artifact.Token) '$path' was advertised for the $($artifact.Description) but no file exists at that location")
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
$metadata['Text'] = $file.Text
|
|
118
|
+
$artifactMeta[$artifact.Stem] = $metadata
|
|
119
|
+
}
|
|
391
120
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
121
|
+
if ($artifactMeta.ContainsKey('policy-audit')) {
|
|
122
|
+
$reference = $artifactMeta['policy-audit']
|
|
123
|
+
foreach ($stem in @('code-review', 'feature-audit')) {
|
|
124
|
+
if (-not $artifactMeta.ContainsKey($stem)) {
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
$candidate = $artifactMeta[$stem]
|
|
128
|
+
if ($candidate.Folder -ne $reference.Folder -or $candidate.Timestamp -ne $reference.Timestamp) {
|
|
129
|
+
$errors.Add("$stem artifact must share the same feature folder and timestamp as the policy audit artifact.")
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
$remediationPath = Get-ReviewArtifactPathFromOutput -AgentOutput $agentOutput -Token 'remediation-inputs-path'
|
|
134
|
+
if ($null -ne $remediationPath) {
|
|
135
|
+
$remediation = Get-ReviewArtifactInfo -Path $remediationPath -Stem 'remediation-inputs'
|
|
136
|
+
if ($null -eq $remediation) {
|
|
137
|
+
$errors.Add("remediation-inputs-path '$remediationPath' is outside the required docs/features/active/.../remediation-inputs.<timestamp>.md location")
|
|
138
|
+
}
|
|
139
|
+
elseif ($remediation.Folder -ne $reference.Folder -or $remediation.Timestamp -ne $reference.Timestamp) {
|
|
140
|
+
$errors.Add('remediation-inputs artifact must share the same feature folder and timestamp as the policy audit artifact.')
|
|
141
|
+
}
|
|
142
|
+
elseif (-not (Get-ArtifactFileContent -Path $remediationPath).Exists) {
|
|
143
|
+
$errors.Add("remediation-inputs-path '$remediationPath' was advertised but no file exists at that location")
|
|
144
|
+
}
|
|
145
|
+
}
|
|
397
146
|
}
|
|
398
|
-
|
|
399
|
-
if ($
|
|
400
|
-
$
|
|
147
|
+
|
|
148
|
+
if ($errors.Count -gt 0) {
|
|
149
|
+
$message = "validate-feature-review-coverage: required review artifact validation failed:`n - " + ($errors -join "`n - ")
|
|
150
|
+
$finding = @{ Message = $message; HasRepairable = $true; HasNonRepairable = $false }
|
|
151
|
+
break
|
|
401
152
|
}
|
|
402
|
-
}
|
|
403
153
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
$
|
|
407
|
-
|
|
408
|
-
$errors.Add("remediation-inputs-path '$remediationPath' is outside the required docs/features/active/.../remediation-inputs.<timestamp>.md location")
|
|
154
|
+
$prSummary = Get-ArtifactFileContent -Path 'artifacts/pr_context.summary.txt'
|
|
155
|
+
$changedLanguages = if ($prSummary.Exists) { Get-ChangedLanguageSet -Lines $prSummary.Lines } else { [ordered]@{} }
|
|
156
|
+
if ($changedLanguages.Count -eq 0) {
|
|
157
|
+
break
|
|
409
158
|
}
|
|
410
|
-
|
|
411
|
-
|
|
159
|
+
|
|
160
|
+
$policyAuditText = $artifactMeta['policy-audit'].Text
|
|
161
|
+
$claudeMd = Get-FeatureReviewClaudeMdText
|
|
162
|
+
if ($null -ne $claudeMd.ReadError) {
|
|
163
|
+
$finding = @{ Message = "validate-feature-review-coverage: root CLAUDE.md exists but could not be read ($($claudeMd.ReadError)); coverage floors cannot be resolved, so the gate fails closed."; HasRepairable = $false; HasNonRepairable = $true }
|
|
164
|
+
break
|
|
412
165
|
}
|
|
413
|
-
|
|
414
|
-
|
|
166
|
+
$thresholds = Get-FeatureReviewCoverageThreshold -ClaudeMdText $claudeMd.Text
|
|
167
|
+
$coverageFailures = [System.Collections.Generic.List[hashtable]]::new()
|
|
168
|
+
foreach ($lang in $changedLanguages.Keys) {
|
|
169
|
+
$repoPct = Get-LanguageRepoCoverage -Language $lang
|
|
170
|
+
$branchPct = Get-LanguageBranchCoverage -Language $lang
|
|
171
|
+
$result = Test-LanguageCoverageRow -AuditText $policyAuditText -Language $lang -RepoWidePct $repoPct -BranchPct $branchPct -LineFloor $thresholds.Line -BranchFloor $thresholds.Branch -LineSource $thresholds.LineSource -BranchSource $thresholds.BranchSource
|
|
172
|
+
if (-not $result.Ok) {
|
|
173
|
+
$coverageFailures.Add($result)
|
|
174
|
+
}
|
|
415
175
|
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if ($errors.Count -gt 0) {
|
|
420
|
-
$message = "feature-review hook: required review artifact validation failed:`n - " + ($errors -join "`n - ")
|
|
421
|
-
return @{ Ok = $false; Message = $message }
|
|
422
|
-
}
|
|
423
176
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
$coverageFailures = [System.Collections.Generic.List[string]]::new()
|
|
432
|
-
foreach ($lang in $changedLanguages.Keys) {
|
|
433
|
-
$repoPct = Get-LanguageRepoCoverage -Language $lang
|
|
434
|
-
$branchPct = Get-LanguageBranchCoverage -Language $lang
|
|
435
|
-
$result = Test-LanguageCoverageRow -AuditText $policyAuditText -Language $lang -RepoWidePct $repoPct -BranchPct $branchPct
|
|
436
|
-
if (-not $result.Ok) {
|
|
437
|
-
$coverageFailures.Add($result.Reason)
|
|
438
|
-
}
|
|
177
|
+
if ($coverageFailures.Count -gt 0) {
|
|
178
|
+
$message = "validate-feature-review-coverage: coverage validation failed against branch diff:`n - " + (@($coverageFailures | ForEach-Object { $_.Reason }) -join "`n - ")
|
|
179
|
+
$hasRepairable = @($coverageFailures | Where-Object { $_.Repairable -eq $true }).Count -gt 0
|
|
180
|
+
$hasNonRepairable = @($coverageFailures | Where-Object { $_.Repairable -eq $false }).Count -gt 0
|
|
181
|
+
$finding = @{ Message = $message; HasRepairable = $hasRepairable; HasNonRepairable = $hasNonRepairable }
|
|
182
|
+
}
|
|
183
|
+
} while ($false)
|
|
439
184
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
$message = "feature-review hook: coverage validation failed against branch diff:`n - " + ($coverageFailures -join "`n - ")
|
|
443
|
-
return @{ Ok = $false; Message = $message }
|
|
185
|
+
catch {
|
|
186
|
+
$finding = @{ Message = ('validate-feature-review-coverage: validator error: ' + ([string]$_.Exception.Message -split "`r?`n")[0] + '; the gate fails closed.'); HasRepairable = $false; HasNonRepairable = $true }
|
|
444
187
|
}
|
|
445
188
|
|
|
446
|
-
return @{ Ok = $true; Message = $null }
|
|
189
|
+
if ($null -eq $finding) { return @{ Ok = $true; Message = $null } }
|
|
190
|
+
if ($envelope.StopHookActive -and -not $finding.HasRepairable) { return @{ Ok = $true; Message = $null } }
|
|
191
|
+
$message = if ($finding.HasNonRepairable) { "$($finding.Message) The subagent cannot repair this condition; report it in the final response and stop." } else { $finding.Message }
|
|
192
|
+
return @{ Ok = $false; Message = $message }
|
|
447
193
|
}
|
|
448
194
|
|
|
449
195
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
450
196
|
return
|
|
451
197
|
}
|
|
198
|
+
if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('validate-feature-review-coverage: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
|
|
199
|
+
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'validate-feature-review-coverage:'
|
|
200
|
+
if ($null -ne $dependencyDecision) { [Console]::Error.WriteLine($dependencyDecision.Reason); exit $dependencyDecision.ExitCode }
|
|
452
201
|
|
|
453
|
-
$result = Invoke-FeatureReviewCoverageValidation
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
exit 1
|
|
457
|
-
}
|
|
458
|
-
|
|
202
|
+
try { $result = Invoke-FeatureReviewCoverageValidation }
|
|
203
|
+
catch { Exit-SubagentStopBlock -Message ('validate-feature-review-coverage: validator error: ' + ([string]$_.Exception.Message -split "`r?`n")[0] + '; the gate fails closed. The subagent cannot repair this condition; report it in the final response and stop.') }
|
|
204
|
+
if (-not $result.Ok) { Exit-SubagentStopBlock -Message $result.Message }
|
|
459
205
|
exit 0
|