@danmoisan/drm-copilot-mcp 1.1.22 → 1.1.24
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 +18758 -12030
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/MEMORY.md +1 -1
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_test_files_count_against_500_cap.md +4 -4
- package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +1 -1
- package/resources/claude-customizations/.claude/agents/discovery-capability-sentinel.md +34 -0
- package/resources/claude-customizations/.claude/agents/feature-review.md +8 -0
- package/resources/claude-customizations/.claude/agents/legacy-parity-analyst.md +101 -43
- package/resources/claude-customizations/.claude/agents/migration-coverage-reviewer.md +107 -43
- package/resources/claude-customizations/.claude/agents/orchestrator.md +1 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +60 -6
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +21 -1
- package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +1 -1
- package/resources/claude-customizations/.claude/agents/pr-author.md +48 -7
- package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +1 -1
- package/resources/claude-customizations/.claude/agents/requirements-reconciler.md +117 -42
- package/resources/claude-customizations/.claude/agents/runtime-characterization-analyst.md +103 -43
- package/resources/claude-customizations/.claude/hooks/discovery-capability-probe.ps1 +102 -0
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +69 -194
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-planning-gate.ps1 +87 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-green-ci.ps1 +325 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +29 -7
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +23 -10
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +29 -21
- package/resources/claude-customizations/.claude/hooks/enforce-file-size-limit.ps1 +244 -0
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +51 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +168 -48
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +1 -1
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +22 -11
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +59 -20
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +51 -4
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +72 -11
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.current-branch.ps1 +149 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +4 -2
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +59 -20
- package/resources/claude-customizations/.claude/hooks/record-discovery-agent-start.ps1 +132 -0
- package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +56 -212
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-feature-folder.ps1 +146 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-rejection.ps1 +172 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +34 -4
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +9 -3
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusAggregate.psm1 +154 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusCombination.psm1 +228 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +1 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +63 -16
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +56 -1
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +4 -1
- package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +33 -6
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +5 -1
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +25 -2
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +188 -341
- package/resources/claude-customizations/.claude/lib/file-size-policy/FileSizePolicy.psm1 +434 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateFeatureFolderRecord.psm1 +166 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +8 -3
- package/resources/claude-customizations/.claude/lib/worktree-resolution/HookTargetWorktree.psm1 +391 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunRecordPath.psm1 +185 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +11 -21
- package/resources/claude-customizations/.claude/rules/general-code-change.md +4 -1
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +29 -5
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +152 -20
- package/resources/claude-customizations/.claude/rules/powershell.md +1 -1
- package/resources/claude-customizations/.claude/rules/shell.md +1 -1
- package/resources/claude-customizations/.claude/settings.json +36 -3
- package/resources/claude-customizations/.claude/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/claude-customizations/.claude/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +2 -2
- package/resources/claude-customizations/.claude/skills/discovery-behavior-reconciliation/SKILL.md +28 -47
- package/resources/claude-customizations/.claude/skills/discovery-coverage-ledger/SKILL.md +32 -48
- package/resources/claude-customizations/.claude/skills/discovery-parity-matrix/SKILL.md +28 -45
- package/resources/claude-customizations/.claude/skills/discovery-repo-inventory/SKILL.md +28 -62
- package/resources/claude-customizations/.claude/skills/discovery-runtime-characterization/SKILL.md +28 -45
- package/resources/claude-customizations/.claude/skills/discovery-validate-artifacts/SKILL.md +28 -61
- package/resources/claude-customizations/.claude/skills/discovery-workflow/SKILL.md +55 -139
- package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +25 -10
- package/resources/claude-customizations/.claude/skills/extract-feature-contract/SKILL.md +133 -0
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +1 -0
- package/resources/claude-customizations/.claude/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/claude-customizations/.claude/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +15 -7
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +25 -2
- package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +11 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +124 -14
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +72 -7
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +15 -0
- package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +6 -0
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +56 -9
- package/resources/claude-customizations/.claude/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/claude-customizations/.claude/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +1 -1
- package/resources/claude-customizations/config/blast-radius.json +6 -5
- package/resources/claude-customizations/config/orchestration-routing.json +61 -1
- package/resources/claude-customizations/pack-manifests/core.json +40 -1
- package/resources/codex-and-agents-customizations/.agents/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +7 -0
- package/resources/codex-and-agents-customizations/.agents/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-coverage-ledger/SKILL.md +50 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-parity-matrix/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-repo-inventory/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-runtime-characterization/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-validate-artifacts/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-workflow/SKILL.md +62 -0
- package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +25 -10
- package/resources/codex-and-agents-customizations/.agents/skills/extract-feature-contract/SKILL.md +133 -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 +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +4 -1
- package/resources/codex-and-agents-customizations/.agents/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/codex-and-agents-customizations/.agents/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/codex-and-agents-customizations/.agents/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/discovery-capability-sentinel.toml +51 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +8 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c1.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c2.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3-elevated.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c4.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c1.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c2.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3-elevated.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c4.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c1.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c2.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3-elevated.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c4.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c1.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c2.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3-elevated.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c4.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +39 -3
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +22 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/discovery-capability-probe.ps1 +97 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/discovery-start-attestation.ps1 +171 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +4 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-artifact-gate.ps1 +187 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-planning-gate.ps1 +149 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-file-size-limit.ps1 +282 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +239 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +54 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +54 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/file-size-policy.ps1 +421 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-target-worktree.ps1 +369 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +8 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +22 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-discovery-agent-output.ps1 +178 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +11 -1
- package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/AGENTS.md +4 -1
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +65 -1
- package/resources/config/orchestration-routing.json +61 -1
- package/resources/customizations/.github/agents/commentary-remediation.agent.md +1 -1
- package/resources/customizations/.github/agents/discovery-capability-sentinel.agent.md +34 -0
- package/resources/customizations/.github/agents/legacy-parity-analyst.agent.md +122 -0
- package/resources/customizations/.github/agents/migration-coverage-reviewer.agent.md +128 -0
- package/resources/customizations/.github/agents/python-execution-only-typed.agent.md +1 -1
- package/resources/customizations/.github/agents/python-typed-engineer.agent.md +1 -1
- package/resources/customizations/.github/agents/requirements-reconciler.agent.md +138 -0
- package/resources/customizations/.github/agents/runtime-characterization-analyst.agent.md +124 -0
- package/resources/customizations/.github/hooks/discovery-agent-start.ps1 +139 -0
- package/resources/customizations/.github/hooks/discovery-agent-stop.ps1 +123 -0
- package/resources/customizations/.github/hooks/discovery-capability-probe.ps1 +98 -0
- package/resources/customizations/.github/hooks/discovery-planning.ps1 +91 -0
- package/resources/customizations/.github/hooks/discovery-pre-tool.ps1 +123 -0
- package/resources/customizations/.github/hooks/discovery.json +35 -0
- package/resources/customizations/.github/instructions/general-code-change.instructions.md +5 -2
- package/resources/customizations/.github/instructions/mermaid.instructions.md +85 -0
- package/resources/customizations/.github/instructions/powershell-code-change.instructions.md +1 -1
- package/resources/customizations/.github/prompts/execute-hard-lock.prompt.md +105 -0
- package/resources/customizations/.github/prompts/remediate-comments.prompt.md +1 -1
- package/resources/customizations/.github/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/customizations/.github/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/customizations/.github/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-coverage-ledger/SKILL.md +50 -0
- package/resources/customizations/.github/skills/discovery-parity-matrix/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-repo-inventory/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-runtime-characterization/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-validate-artifacts/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-workflow/SKILL.md +62 -0
- package/resources/customizations/.github/skills/execution-environment/SKILL.md +25 -10
- package/resources/customizations/.github/skills/extract-feature-contract/SKILL.md +133 -0
- package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +8 -0
- package/resources/customizations/.github/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/customizations/.github/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/customizations/.github/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/customizations/.github/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/customizations/.github/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/customizations/.github/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/lib/codex-routing/CodexDeployment.psm1 +5 -1
- package/resources/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
- package/resources/lib/codex-routing/CodexTopology.psm1 +25 -2
- package/resources/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
- package/resources/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
- package/resources/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
- package/resources/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
- package/resources/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
- package/resources/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
- package/resources/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
- package/resources/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
- package/resources/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
- package/resources/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
- package/resources/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
- package/resources/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
- package/resources/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
- package/resources/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
- package/resources/powershell/PoshQC/PoshQC.Parallel.psm1 +452 -0
- package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +106 -8
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +12 -0
- package/resources/powershell/PoshQC/PoshQC.psd1 +6 -1
- package/resources/powershell/PoshQC/PoshQC.psm1 +7 -1
- package/resources/powershell/PoshQC/README.md +20 -1
- package/resources/templates/policy_audit/code-review.yyyy-MM-ddTHH-mm.md +4 -4
- package/resources/templates/policy_audit/policy-audit.yyyy-MM-ddTHH-mm.md +4 -4
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Tool-name routing, controlled-operation derivation and request digests for the discovery gates.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Every native discovery PreToolUse adapter classifies the host tool name into a
|
|
7
|
+
family (controlled discovery MCP tool, foreign MCP server, shell, mutation,
|
|
8
|
+
spawn, read-only native, other), derives the controlled operation a discovery
|
|
9
|
+
tool performs from its input, and digests the request exactly as the gateway
|
|
10
|
+
does: `sha256(canonical_json({inputs, operation, tool}))`. The fixed tables
|
|
11
|
+
(controlled tools and their operations, root-only tools and operations, the
|
|
12
|
+
caller identity fields tool input must never carry) live here and are exposed
|
|
13
|
+
through read-only accessors, so DiscoveryAuthorization.psm1 and
|
|
14
|
+
DiscoveryPlanning.psm1 share one routing surface.
|
|
15
|
+
|
|
16
|
+
Pure functions over strings and objects: no filesystem access, no process, no
|
|
17
|
+
network, no clock and no environment read.
|
|
18
|
+
|
|
19
|
+
.NOTES
|
|
20
|
+
Issue #980. Compatible with PowerShell 7+. Depends only on the sibling
|
|
21
|
+
DiscoveryEncoding module.
|
|
22
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
23
|
+
#>
|
|
24
|
+
|
|
25
|
+
Set-StrictMode -Version Latest
|
|
26
|
+
$ErrorActionPreference = 'Stop'
|
|
27
|
+
|
|
28
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryEncoding.psm1') -ErrorAction Stop
|
|
29
|
+
|
|
30
|
+
$script:ControlledToolOperations = [ordered]@{
|
|
31
|
+
read_discovery_source = @('read-source')
|
|
32
|
+
run_discovery_repo_inventory = @('inventory')
|
|
33
|
+
run_discovery_dotnet_analyzer = @('inventory')
|
|
34
|
+
run_discovery_vsto_analyzer = @('inventory')
|
|
35
|
+
create_discovery_artifact = @('new-contract', 'new-scenario')
|
|
36
|
+
update_discovery_artifact = @('update-artifact')
|
|
37
|
+
link_discovery_evidence = @('link-evidence')
|
|
38
|
+
run_discovery_scenario_generation = @('generate-acceptance')
|
|
39
|
+
run_discovery_report = @('coverage-report', 'parity-report', 'completion-report')
|
|
40
|
+
validate_discovery_artifacts = @('profile', 'validate-profile', 'validate-feature-contract', 'validate-coverage-ledger', 'validate-runtime-scenario', 'validate-parity-matrix', 'validate-unspecified-behavior', 'validate-product-decision', 'validate-evidence-reference', 'validate-decisions', 'validate-all')
|
|
41
|
+
finalize_discovery_agent = @('finalize-agent')
|
|
42
|
+
# The sentinel tool is listed so the gate and the server agree on its operation; the gate routes it to an observation, never to a grant.
|
|
43
|
+
discovery_capability_sentinel = @('sentinel')
|
|
44
|
+
}
|
|
45
|
+
$script:RootOnlyOperations = @('init', 'configure', 'snapshot-export', 'snapshot-import', 'import-inventory', 'migrate', 'doctor', 'prepare-agent')
|
|
46
|
+
$script:RootOnlyTools = @('run_discovery_init', 'configure_discovery_workspace', 'prepare_discovery_agent', 'run_discovery_doctor', 'transfer_discovery_snapshot', 'import_discovery_inventory', 'migrate_discovery_artifacts')
|
|
47
|
+
$script:DiscoveryServerAliases = @('drm-copilot', 'drmCopilotExtension', 'drm_copilot')
|
|
48
|
+
$script:CallerIdentityKeys = @('agent_id', 'host', 'invocation_id', 'request_id', 'role', 'session_id')
|
|
49
|
+
$script:ValidateArtifactOperations = @{
|
|
50
|
+
'profile' = 'validate-profile'; 'domain-profile' = 'validate-profile'; 'feature-contract' = 'validate-feature-contract'
|
|
51
|
+
'coverage-ledger' = 'validate-coverage-ledger'; 'runtime-scenario' = 'validate-runtime-scenario'
|
|
52
|
+
'runtime-characterization-scenario' = 'validate-runtime-scenario'; 'parity-matrix' = 'validate-parity-matrix'
|
|
53
|
+
'unspecified-behavior' = 'validate-unspecified-behavior'; 'product-decision' = 'validate-product-decision'
|
|
54
|
+
'evidence-reference' = 'validate-evidence-reference'; 'decisions' = 'validate-decisions'; 'all' = 'validate-all'
|
|
55
|
+
}
|
|
56
|
+
# Tool-name families. A shell-like token anywhere in a native tool name denies it;
|
|
57
|
+
# the mutation, spawn and read families are matched on the whole name.
|
|
58
|
+
$script:ShellToolPattern = '(?i)(^|[_.-])(bash|sh|zsh|shell|terminal|exec|execute|eval|cmd|powershell|pwsh|command)([_.-]|$)'
|
|
59
|
+
$script:MutationToolPattern = '(?i)^(write|edit|multiedit|notebookedit|apply_patch|create_file|edit_file|write_file|delete_file|delete|insert_edit_into_file|replace_string_in_file|create_directory|move_file|rename_file|remove_file)$'
|
|
60
|
+
$script:SpawnToolPattern = '(?i)^(agent|task|spawn_agent|run_subagent|runsubagent|delegate)$'
|
|
61
|
+
$script:ReadToolPattern = '(?i)^(read|glob|grep|ls|list_dir|list_directory|read_file|file_search|grep_search|semantic_search|get_file|todoread)$'
|
|
62
|
+
|
|
63
|
+
function Get-DiscoveryControlledToolName {
|
|
64
|
+
<#
|
|
65
|
+
.SYNOPSIS
|
|
66
|
+
Returns the names of the controlled discovery tools, in table order.
|
|
67
|
+
#>
|
|
68
|
+
[CmdletBinding()]
|
|
69
|
+
[OutputType([string[]])]
|
|
70
|
+
param()
|
|
71
|
+
|
|
72
|
+
return [string[]]@($script:ControlledToolOperations.Keys)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function Get-DiscoveryRootOnlyToolName {
|
|
76
|
+
<#
|
|
77
|
+
.SYNOPSIS
|
|
78
|
+
Returns the names of the root-only discovery tools.
|
|
79
|
+
#>
|
|
80
|
+
[CmdletBinding()]
|
|
81
|
+
[OutputType([string[]])]
|
|
82
|
+
param()
|
|
83
|
+
|
|
84
|
+
return [string[]]$script:RootOnlyTools
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function Get-DiscoveryRootOnlyOperationName {
|
|
88
|
+
<#
|
|
89
|
+
.SYNOPSIS
|
|
90
|
+
Returns the names of the root-only discovery operations.
|
|
91
|
+
#>
|
|
92
|
+
[CmdletBinding()]
|
|
93
|
+
[OutputType([string[]])]
|
|
94
|
+
param()
|
|
95
|
+
|
|
96
|
+
return [string[]]$script:RootOnlyOperations
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function Get-DiscoveryCallerIdentityKey {
|
|
100
|
+
<#
|
|
101
|
+
.SYNOPSIS
|
|
102
|
+
Returns the tool-input field names that would assert an identity and are therefore refused.
|
|
103
|
+
#>
|
|
104
|
+
[CmdletBinding()]
|
|
105
|
+
[OutputType([string[]])]
|
|
106
|
+
param()
|
|
107
|
+
|
|
108
|
+
return [string[]]$script:CallerIdentityKeys
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function Resolve-DiscoveryToolName {
|
|
112
|
+
<#
|
|
113
|
+
.SYNOPSIS
|
|
114
|
+
Classifies a host tool name into a family and, for MCP tools, its server and tool.
|
|
115
|
+
.OUTPUTS
|
|
116
|
+
[pscustomobject] Family (mcp-discovery, mcp-other, shell, mutation, spawn, read, other), Server, Name.
|
|
117
|
+
#>
|
|
118
|
+
[CmdletBinding()]
|
|
119
|
+
[OutputType([pscustomobject])]
|
|
120
|
+
param([Parameter(Mandatory = $false)][AllowNull()][AllowEmptyString()][string]$ToolName)
|
|
121
|
+
|
|
122
|
+
$name = [string]$ToolName
|
|
123
|
+
$match = [regex]::Match($name, '^mcp__(?<server>[^_][^_]*?(?:_[^_]+)*?)__(?<tool>[A-Za-z0-9_.-]+)$')
|
|
124
|
+
# Decision order: an MCP tool is routed by its server; a native tool by the
|
|
125
|
+
# shell, mutation, spawn and read families; anything else is unknown.
|
|
126
|
+
if ($match.Success) {
|
|
127
|
+
$server = $match.Groups['server'].Value
|
|
128
|
+
$family = $(if ($script:DiscoveryServerAliases -contains $server) { 'mcp-discovery' } else { 'mcp-other' })
|
|
129
|
+
return [pscustomobject]@{ Family = $family; Server = $server; Name = $match.Groups['tool'].Value }
|
|
130
|
+
}
|
|
131
|
+
$family = 'other'
|
|
132
|
+
if ($name -match $script:ShellToolPattern) { $family = 'shell' }
|
|
133
|
+
elseif ($name -match $script:MutationToolPattern) { $family = 'mutation' }
|
|
134
|
+
elseif ($name -match $script:SpawnToolPattern) { $family = 'spawn' }
|
|
135
|
+
elseif ($name -match $script:ReadToolPattern) { $family = 'read' }
|
|
136
|
+
return [pscustomobject]@{ Family = $family; Server = $null; Name = $name }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function Get-DiscoveryInputMember {
|
|
140
|
+
<#
|
|
141
|
+
.SYNOPSIS
|
|
142
|
+
Reads one member of a tool-input object (hashtable or PSCustomObject), or $null.
|
|
143
|
+
#>
|
|
144
|
+
[CmdletBinding()]
|
|
145
|
+
[OutputType([object])]
|
|
146
|
+
param(
|
|
147
|
+
[Parameter(Mandatory = $true)][object]$ToolInput,
|
|
148
|
+
[Parameter(Mandatory = $true)][string]$Name
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if ($ToolInput -is [System.Collections.IDictionary]) {
|
|
152
|
+
if ($ToolInput.Contains($Name)) { return $ToolInput[$Name] }
|
|
153
|
+
return $null
|
|
154
|
+
}
|
|
155
|
+
$property = $ToolInput.PSObject.Properties[$Name]
|
|
156
|
+
if ($null -eq $property) { return $null }
|
|
157
|
+
return $property.Value
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function Get-DiscoveryToolOperation {
|
|
161
|
+
<#
|
|
162
|
+
.SYNOPSIS
|
|
163
|
+
Derives the controlled operation a tool call performs from the tool and its input.
|
|
164
|
+
.DESCRIPTION
|
|
165
|
+
Single-operation tools map directly. create_discovery_artifact selects by
|
|
166
|
+
artifact_kind, run_discovery_report by report_type, and
|
|
167
|
+
validate_discovery_artifacts by artifact_type (validate-all when absent).
|
|
168
|
+
Returns $null when the operation cannot be derived, which the caller denies.
|
|
169
|
+
#>
|
|
170
|
+
[CmdletBinding()]
|
|
171
|
+
[OutputType([string])]
|
|
172
|
+
param(
|
|
173
|
+
[Parameter(Mandatory = $true)][string]$Tool,
|
|
174
|
+
[Parameter(Mandatory = $true)][object]$ToolInput
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if (-not $script:ControlledToolOperations.Contains($Tool)) { return $null }
|
|
178
|
+
$operations = @($script:ControlledToolOperations[$Tool])
|
|
179
|
+
if ($operations.Count -eq 1) { return $operations[0] }
|
|
180
|
+
# Multi-operation tools select by one input field; an unknown value is refused.
|
|
181
|
+
switch ($Tool) {
|
|
182
|
+
'create_discovery_artifact' {
|
|
183
|
+
$kind = [string](Get-DiscoveryInputMember -ToolInput $ToolInput -Name 'artifact_kind')
|
|
184
|
+
if ($kind -eq 'feature-contract') { return 'new-contract' }
|
|
185
|
+
if ($kind -eq 'runtime-scenario' -or $kind -eq 'runtime-characterization-scenario') { return 'new-scenario' }
|
|
186
|
+
return $null
|
|
187
|
+
}
|
|
188
|
+
'run_discovery_report' {
|
|
189
|
+
$report = [string](Get-DiscoveryInputMember -ToolInput $ToolInput -Name 'report_type')
|
|
190
|
+
if ($report -in @('coverage', 'parity', 'completion')) { return "$report-report" }
|
|
191
|
+
return $null
|
|
192
|
+
}
|
|
193
|
+
'validate_discovery_artifacts' {
|
|
194
|
+
$type = Get-DiscoveryInputMember -ToolInput $ToolInput -Name 'artifact_type'
|
|
195
|
+
if ($null -eq $type) { return 'validate-all' }
|
|
196
|
+
if ($script:ValidateArtifactOperations.ContainsKey([string]$type)) { return $script:ValidateArtifactOperations[[string]$type] }
|
|
197
|
+
return $null
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return $null
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function Get-DiscoveryInputDigest {
|
|
204
|
+
<#
|
|
205
|
+
.SYNOPSIS
|
|
206
|
+
Returns sha256(canonical_json({inputs, operation, tool})) for a controlled request.
|
|
207
|
+
#>
|
|
208
|
+
[CmdletBinding()]
|
|
209
|
+
[OutputType([string])]
|
|
210
|
+
param(
|
|
211
|
+
[Parameter(Mandatory = $true)][string]$Tool,
|
|
212
|
+
[Parameter(Mandatory = $true)][string]$Operation,
|
|
213
|
+
[Parameter(Mandatory = $true)][object]$Inputs
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
$payload = [ordered]@{ inputs = $Inputs; operation = $Operation; tool = $Tool }
|
|
217
|
+
return Get-DiscoverySha256Hex -Text (ConvertTo-DiscoveryCanonicalJson -Value $payload)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
Export-ModuleMember -Function `
|
|
221
|
+
Get-DiscoveryControlledToolName, `
|
|
222
|
+
Get-DiscoveryRootOnlyToolName, `
|
|
223
|
+
Get-DiscoveryRootOnlyOperationName, `
|
|
224
|
+
Get-DiscoveryCallerIdentityKey, `
|
|
225
|
+
Resolve-DiscoveryToolName, `
|
|
226
|
+
Get-DiscoveryInputMember, `
|
|
227
|
+
Get-DiscoveryToolOperation, `
|
|
228
|
+
Get-DiscoveryInputDigest
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Returns the default worker count of the local concurrent Pester runner (issue #1048).
|
|
4
|
+
.DESCRIPTION
|
|
5
|
+
Returns the floor of half the logical processor count, at least 1 and at most 8. Each worker is a pwsh
|
|
6
|
+
process that runs coverage instrumentation and, in some suites, starts further processes, so half the
|
|
7
|
+
logical processors leaves capacity for those children.
|
|
8
|
+
.PARAMETER LogicalProcessorCount
|
|
9
|
+
The logical processor count of the machine, for example [Environment]::ProcessorCount.
|
|
10
|
+
#>
|
|
11
|
+
function Get-PoshQCParallelWorkerCount {
|
|
12
|
+
[CmdletBinding()]
|
|
13
|
+
[OutputType([int])]
|
|
14
|
+
param(
|
|
15
|
+
[Parameter(Mandatory)][int] $LogicalProcessorCount
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
[int] [math]::Max(1, [math]::Min(8, [math]::Floor($LogicalProcessorCount / 2)))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
<#
|
|
22
|
+
.SYNOPSIS
|
|
23
|
+
Loads one JUnit XML text into an XmlDocument for the merge and weight functions (issue #1048).
|
|
24
|
+
.DESCRIPTION
|
|
25
|
+
Disables external resolution and throws a message that names the report position when the text is not
|
|
26
|
+
well-formed XML or its root element is not testsuites.
|
|
27
|
+
.PARAMETER JunitText
|
|
28
|
+
The XML text of one JUnit report.
|
|
29
|
+
.PARAMETER Position
|
|
30
|
+
The one-based position of the report, used in error messages.
|
|
31
|
+
#>
|
|
32
|
+
function ConvertFrom-PoshQCJunitXml {
|
|
33
|
+
[CmdletBinding()]
|
|
34
|
+
[OutputType([System.Xml.XmlDocument])]
|
|
35
|
+
param(
|
|
36
|
+
[Parameter(Mandatory)][AllowEmptyString()][string] $JunitText,
|
|
37
|
+
[Parameter(Mandatory)][int] $Position
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
$document = New-Object System.Xml.XmlDocument
|
|
41
|
+
$document.XmlResolver = $null
|
|
42
|
+
try {
|
|
43
|
+
$document.LoadXml($JunitText)
|
|
44
|
+
} catch {
|
|
45
|
+
throw "JUnit report $Position is not well-formed XML: $($_.Exception.Message)"
|
|
46
|
+
}
|
|
47
|
+
if ($document.DocumentElement.LocalName -ne 'testsuites') {
|
|
48
|
+
throw "JUnit report $Position has the root element '$($document.DocumentElement.LocalName)'; expected 'testsuites'."
|
|
49
|
+
}
|
|
50
|
+
$document
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
<#
|
|
54
|
+
.SYNOPSIS
|
|
55
|
+
Merges the JUnit reports written by the shards of one test run into one report (issue #1048).
|
|
56
|
+
.DESCRIPTION
|
|
57
|
+
Takes XML text only (no file I/O). The merged root keeps the attributes of the first report, carries every
|
|
58
|
+
testsuite element of every report in input order with id renumbered from 0, and sets tests, errors,
|
|
59
|
+
failures, and disabled to the sums over the reports. The root time is the largest root time of any report:
|
|
60
|
+
the shards run at the same time, so the longest shard is the wall-clock time of the run. Throws when no
|
|
61
|
+
report is supplied or a report is not well-formed JUnit XML. Returns the merged XML text.
|
|
62
|
+
.PARAMETER JunitText
|
|
63
|
+
The XML text of each shard's JUnit report; at least one is required.
|
|
64
|
+
#>
|
|
65
|
+
function Merge-PoshQCJunitReport {
|
|
66
|
+
[CmdletBinding()]
|
|
67
|
+
[OutputType([string])]
|
|
68
|
+
param(
|
|
69
|
+
[Parameter(Mandatory)][AllowEmptyCollection()][string[]] $JunitText
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if ($JunitText.Count -lt 1) {
|
|
73
|
+
throw 'Merge-PoshQCJunitReport requires at least one JUnit report.'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
$documents = @(for ($position = 0; $position -lt $JunitText.Count; $position++) {
|
|
77
|
+
ConvertFrom-PoshQCJunitXml -JunitText $JunitText[$position] -Position ($position + 1)
|
|
78
|
+
})
|
|
79
|
+
# Capture every report's suites first, because the merged document is the first report and its suites are
|
|
80
|
+
# removed from the root before all suites are appended again in input order.
|
|
81
|
+
$suiteLists = @(foreach ($document in $documents) { , @($document.DocumentElement.SelectNodes('testsuite')) })
|
|
82
|
+
$merged = $documents[0]
|
|
83
|
+
$root = $merged.DocumentElement
|
|
84
|
+
foreach ($suite in $suiteLists[0]) {
|
|
85
|
+
[void] $root.RemoveChild($suite)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Sum the root counters and keep the original text of the largest root time over all reports.
|
|
89
|
+
$totals = [ordered]@{ tests = [long] 0; errors = [long] 0; failures = [long] 0; disabled = [long] 0 }
|
|
90
|
+
$longestTime = [double] 0
|
|
91
|
+
$longestTimeText = '0'
|
|
92
|
+
$nextId = 0
|
|
93
|
+
for ($position = 0; $position -lt $documents.Count; $position++) {
|
|
94
|
+
$shardRoot = $documents[$position].DocumentElement
|
|
95
|
+
foreach ($name in @($totals.Keys)) {
|
|
96
|
+
$totals[$name] += [long] ('0' + $shardRoot.GetAttribute($name))
|
|
97
|
+
}
|
|
98
|
+
$timeText = $shardRoot.GetAttribute('time')
|
|
99
|
+
$time = [double] 0
|
|
100
|
+
if ([double]::TryParse($timeText, [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref] $time) -and $time -gt $longestTime) {
|
|
101
|
+
$longestTime = $time
|
|
102
|
+
$longestTimeText = $timeText
|
|
103
|
+
}
|
|
104
|
+
foreach ($suite in $suiteLists[$position]) {
|
|
105
|
+
$imported = $merged.ImportNode($suite, $true)
|
|
106
|
+
$imported.SetAttribute('id', [string] $nextId)
|
|
107
|
+
$nextId++
|
|
108
|
+
[void] $root.AppendChild($imported)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
foreach ($name in $totals.Keys) {
|
|
113
|
+
$root.SetAttribute($name, [string] $totals[$name])
|
|
114
|
+
}
|
|
115
|
+
$root.SetAttribute('time', $longestTimeText)
|
|
116
|
+
$merged.OuterXml
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
<#
|
|
120
|
+
.SYNOPSIS
|
|
121
|
+
Converts shard JUnit reports into the measured shard-weights document (issue #1048).
|
|
122
|
+
.DESCRIPTION
|
|
123
|
+
Maps the name of each testsuite element, an absolute path on the machine that ran it, to a
|
|
124
|
+
repository-relative forward-slash path that starts at its first 'tests' or 'scripts' segment; a name with
|
|
125
|
+
neither segment is skipped. Sums the seconds of each file over every report and returns an ordered
|
|
126
|
+
dictionary with version 1, unit 'ms', and files keyed in ordinal order with values rounded to integer
|
|
127
|
+
milliseconds. Takes XML text only (no file I/O).
|
|
128
|
+
.PARAMETER JunitText
|
|
129
|
+
The XML text of each JUnit report to read.
|
|
130
|
+
#>
|
|
131
|
+
function ConvertTo-PoshQCShardWeight {
|
|
132
|
+
[CmdletBinding()]
|
|
133
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
134
|
+
param(
|
|
135
|
+
[Parameter(Mandatory)][AllowEmptyCollection()][string[]] $JunitText
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
$seconds = New-Object 'System.Collections.Generic.Dictionary[string,double]' ([System.StringComparer]::Ordinal)
|
|
139
|
+
for ($position = 0; $position -lt $JunitText.Count; $position++) {
|
|
140
|
+
$document = ConvertFrom-PoshQCJunitXml -JunitText $JunitText[$position] -Position ($position + 1)
|
|
141
|
+
foreach ($suite in @($document.DocumentElement.SelectNodes('testsuite'))) {
|
|
142
|
+
$segments = @(($suite.GetAttribute('name') -replace '\\', '/') -split '/' | Where-Object { $_ -ne '' })
|
|
143
|
+
# The repository-relative path starts at the first 'tests' or 'scripts' segment, the scan-folder roots.
|
|
144
|
+
$anchor = -1
|
|
145
|
+
for ($index = 0; $index -lt $segments.Count; $index++) {
|
|
146
|
+
if ($segments[$index] -ceq 'tests' -or $segments[$index] -ceq 'scripts') {
|
|
147
|
+
$anchor = $index
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if ($anchor -lt 0) {
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
154
|
+
$relativePath = $segments[$anchor..($segments.Count - 1)] -join '/'
|
|
155
|
+
$time = [double] 0
|
|
156
|
+
[void] [double]::TryParse($suite.GetAttribute('time'), [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref] $time)
|
|
157
|
+
$current = [double] 0
|
|
158
|
+
[void] $seconds.TryGetValue($relativePath, [ref] $current)
|
|
159
|
+
$seconds[$relativePath] = $current + $time
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
[string[]] $keys = @($seconds.Keys)
|
|
164
|
+
[System.Array]::Sort($keys, [System.StringComparer]::Ordinal)
|
|
165
|
+
$files = [ordered]@{}
|
|
166
|
+
foreach ($key in $keys) {
|
|
167
|
+
$files[$key] = [long] [math]::Round($seconds[$key] * 1000, [System.MidpointRounding]::AwayFromZero)
|
|
168
|
+
}
|
|
169
|
+
[ordered]@{ version = 1; unit = 'ms'; files = $files }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
<#
|
|
173
|
+
.SYNOPSIS
|
|
174
|
+
Writes config/poshqc-shard-weights.json from JUnit report files (issue #1048).
|
|
175
|
+
.DESCRIPTION
|
|
176
|
+
Reads each JUnit file through ReadText, converts the reports with ConvertTo-PoshQCShardWeight, and writes the
|
|
177
|
+
JSON document through WriteText. Use the shard JUnit files of a full CI run, because local durations differ
|
|
178
|
+
from the CI runner's.
|
|
179
|
+
.PARAMETER JunitPath
|
|
180
|
+
Paths of the JUnit report files to read.
|
|
181
|
+
.PARAMETER OutputPath
|
|
182
|
+
Path of the weights file to write.
|
|
183
|
+
.PARAMETER ReadText
|
|
184
|
+
Seam that returns the text of a file from its path. Defaults to Get-Content -Raw.
|
|
185
|
+
.PARAMETER WriteText
|
|
186
|
+
Seam that writes text to a path. Defaults to Set-Content with UTF-8 encoding.
|
|
187
|
+
#>
|
|
188
|
+
function Export-PoshQCShardWeight {
|
|
189
|
+
[CmdletBinding()]
|
|
190
|
+
param(
|
|
191
|
+
[Parameter(Mandatory)][string[]] $JunitPath,
|
|
192
|
+
[Parameter(Mandatory)][string] $OutputPath,
|
|
193
|
+
[scriptblock] $ReadText = { param([string] $Path) Get-Content -Raw -LiteralPath $Path },
|
|
194
|
+
[scriptblock] $WriteText = { param([string] $Path, [string] $Text) Set-Content -LiteralPath $Path -Value $Text -Encoding utf8 }
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
$texts = [string[]] @($JunitPath | ForEach-Object { [string] (& $ReadText $_) })
|
|
198
|
+
$document = ConvertTo-PoshQCShardWeight -JunitText $texts
|
|
199
|
+
& $WriteText $OutputPath ($document | ConvertTo-Json -Depth 4)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
<#
|
|
203
|
+
.SYNOPSIS
|
|
204
|
+
Returns the pwsh argument list that runs one shard of Invoke-PoshQCTest (issue #1048).
|
|
205
|
+
.DESCRIPTION
|
|
206
|
+
The command imports the PoshQC manifest and runs Invoke-PoshQCTest with the shard index and count and
|
|
207
|
+
the per-shard JUnit, coverage, and manifest paths, without the Koverage copy. Paths are single-quoted.
|
|
208
|
+
.PARAMETER ModuleManifestPath
|
|
209
|
+
Path of PoshQC.psd1.
|
|
210
|
+
.PARAMETER Root
|
|
211
|
+
Workspace root.
|
|
212
|
+
.PARAMETER ShardIndex
|
|
213
|
+
One-based shard index.
|
|
214
|
+
.PARAMETER ShardCount
|
|
215
|
+
Number of shards.
|
|
216
|
+
.PARAMETER ShardDirectory
|
|
217
|
+
Directory that receives the shard's JUnit, coverage, and manifest files.
|
|
218
|
+
#>
|
|
219
|
+
function Get-PoshQCParallelShardArgument {
|
|
220
|
+
[CmdletBinding()]
|
|
221
|
+
[OutputType([string[]])]
|
|
222
|
+
param(
|
|
223
|
+
[Parameter(Mandatory)][string] $ModuleManifestPath,
|
|
224
|
+
[Parameter(Mandatory)][string] $Root,
|
|
225
|
+
[Parameter(Mandatory)][int] $ShardIndex,
|
|
226
|
+
[Parameter(Mandatory)][int] $ShardCount,
|
|
227
|
+
[Parameter(Mandatory)][string] $ShardDirectory
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
$quote = { param([string] $Text) "'" + $Text.Replace("'", "''") + "'" }
|
|
231
|
+
$values = @(
|
|
232
|
+
(& $quote $ModuleManifestPath),
|
|
233
|
+
(& $quote $Root),
|
|
234
|
+
$ShardIndex,
|
|
235
|
+
$ShardCount,
|
|
236
|
+
(& $quote (Join-Path $ShardDirectory 'shard-files.json')),
|
|
237
|
+
(& $quote (Join-Path $ShardDirectory 'pester-junit.xml')),
|
|
238
|
+
(& $quote (Join-Path $ShardDirectory 'powershell-coverage.xml'))
|
|
239
|
+
)
|
|
240
|
+
$command = 'Import-Module {0}; Invoke-PoshQCTest -Root {1} -ShardIndex {2} -ShardCount {3} -ShardFileListPath {4} -TestResultPath {5} -CoverageOutputPath {6} -DisableKoverageCopy' -f $values
|
|
241
|
+
[string[]] @('-NoLogo', '-NoProfile', '-NonInteractive', '-Command', $command)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
<#
|
|
245
|
+
.SYNOPSIS
|
|
246
|
+
Starts one pwsh child process with its output redirected to a log file (issue #1048).
|
|
247
|
+
.DESCRIPTION
|
|
248
|
+
Wrapper seam around the pwsh executable, so tests mock this function instead of the process cmdlet.
|
|
249
|
+
Start-Process joins the arguments with spaces, so an argument that contains whitespace is double-quoted.
|
|
250
|
+
Standard output goes to LogPath and standard error to LogPath with '.err' appended. Returns the process.
|
|
251
|
+
.PARAMETER PwshArgs
|
|
252
|
+
Arguments passed to pwsh.
|
|
253
|
+
.PARAMETER LogPath
|
|
254
|
+
Path of the standard-output log file.
|
|
255
|
+
#>
|
|
256
|
+
function Invoke-PoshQCPwshExe {
|
|
257
|
+
[CmdletBinding()]
|
|
258
|
+
param(
|
|
259
|
+
[Parameter(Mandatory)][string[]] $PwshArgs,
|
|
260
|
+
[Parameter(Mandatory)][string] $LogPath
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
$quoted = @($PwshArgs | ForEach-Object { if ($_ -match '\s') { '"' + ($_ -replace '"', '\"') + '"' } else { $_ } })
|
|
264
|
+
$process = Start-Process -FilePath 'pwsh' -ArgumentList $quoted -RedirectStandardOutput $LogPath -RedirectStandardError ($LogPath + '.err') -NoNewWindow -PassThru
|
|
265
|
+
# Reading Handle keeps the exit code available after the process has exited.
|
|
266
|
+
$null = $process.Handle
|
|
267
|
+
$process
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
<#
|
|
271
|
+
.SYNOPSIS
|
|
272
|
+
Checks and merges the outputs of the finished shards of a concurrent run (issue #1048).
|
|
273
|
+
.DESCRIPTION
|
|
274
|
+
Returns 1 without writing a merged artifact when a shard exited non-zero, when a shard wrote no JUnit,
|
|
275
|
+
coverage, or manifest file, when the manifests fail Test-PoshQCShardCompleteness, or when a merge fails,
|
|
276
|
+
so a partial result never replaces a full one. Otherwise writes artifacts/pester/pester-junit.xml,
|
|
277
|
+
artifacts/pester/powershell-coverage.xml, and its Koverage copy, and returns 0.
|
|
278
|
+
.PARAMETER Root
|
|
279
|
+
Workspace root.
|
|
280
|
+
.PARAMETER Shard
|
|
281
|
+
The shard records (ShardIndex, Directory, ExitCode) of the run.
|
|
282
|
+
.PARAMETER ReadText
|
|
283
|
+
Seam that returns the text of a file, or $null when it does not exist.
|
|
284
|
+
.PARAMETER WriteText
|
|
285
|
+
Seam that writes text to a path.
|
|
286
|
+
.PARAMETER Logger
|
|
287
|
+
Seam that receives progress and failure messages.
|
|
288
|
+
#>
|
|
289
|
+
function Invoke-PoshQCParallelMerge {
|
|
290
|
+
[CmdletBinding()]
|
|
291
|
+
[OutputType([int])]
|
|
292
|
+
param(
|
|
293
|
+
[Parameter(Mandatory)][string] $Root,
|
|
294
|
+
[Parameter(Mandatory)][object[]] $Shard,
|
|
295
|
+
[Parameter(Mandatory)][scriptblock] $ReadText,
|
|
296
|
+
[Parameter(Mandatory)][scriptblock] $WriteText,
|
|
297
|
+
[Parameter(Mandatory)][scriptblock] $Logger
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
$failed = @($Shard | Where-Object { $_.ExitCode -ne 0 })
|
|
301
|
+
if ($failed.Count -gt 0) {
|
|
302
|
+
& $Logger ("Shards {0} exited non-zero; see their logs. No merged artifact was written." -f (@($failed | ForEach-Object { $_.ShardIndex }) -join ', '))
|
|
303
|
+
return 1
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
# Read every shard's three outputs; a missing output stops the merge before anything is written.
|
|
307
|
+
$outputs = @(foreach ($record in $Shard) {
|
|
308
|
+
[pscustomobject]@{
|
|
309
|
+
ShardIndex = $record.ShardIndex
|
|
310
|
+
Junit = & $ReadText (Join-Path $record.Directory 'pester-junit.xml')
|
|
311
|
+
Coverage = & $ReadText (Join-Path $record.Directory 'powershell-coverage.xml')
|
|
312
|
+
Manifest = & $ReadText (Join-Path $record.Directory 'shard-files.json')
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
foreach ($output in $outputs) {
|
|
316
|
+
foreach ($name in @('Junit', 'Coverage', 'Manifest')) {
|
|
317
|
+
if ([string]::IsNullOrWhiteSpace($output.$name)) {
|
|
318
|
+
& $Logger "Shard $($output.ShardIndex) wrote no $name output. No merged artifact was written."
|
|
319
|
+
return 1
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
$null = Test-PoshQCShardCompleteness -ShardManifest @($outputs | ForEach-Object { $_.Manifest | ConvertFrom-Json })
|
|
326
|
+
} catch {
|
|
327
|
+
& $Logger "Shard completeness check failed: $($_.Exception.Message) No merged artifact was written."
|
|
328
|
+
return 1
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
$junit = Merge-PoshQCJunitReport -JunitText @($outputs | ForEach-Object { [string] $_.Junit })
|
|
333
|
+
$coverage = Merge-PoshQCCoverageReport -CoverageXml @($outputs | ForEach-Object { [string] $_.Coverage })
|
|
334
|
+
$koverage = Convert-PoshQCCoverageToRelative -InputContent $coverage -RepoRoot $Root -PassThru
|
|
335
|
+
} catch {
|
|
336
|
+
& $Logger "Merging the shard outputs failed: $($_.Exception.Message) No merged artifact was written."
|
|
337
|
+
return 1
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
$outputDirectory = Join-Path $Root 'artifacts/pester'
|
|
341
|
+
& $WriteText (Join-Path $outputDirectory 'pester-junit.xml') $junit
|
|
342
|
+
& $WriteText (Join-Path $outputDirectory 'powershell-coverage.xml') $coverage
|
|
343
|
+
& $WriteText (Join-Path $outputDirectory 'powershell-coverage.koverage.xml') ([string] $koverage)
|
|
344
|
+
& $Logger "Wrote the merged JUnit and coverage reports under $outputDirectory."
|
|
345
|
+
0
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
<#
|
|
349
|
+
.SYNOPSIS
|
|
350
|
+
Runs the Pester suite as concurrent shard processes on one machine and merges the results (issue #1048).
|
|
351
|
+
.DESCRIPTION
|
|
352
|
+
Starts one pwsh child per shard. Each child imports the PoshQC module and runs Invoke-PoshQCTest with
|
|
353
|
+
-ShardIndex, -ShardCount, and per-shard JUnit, coverage, and manifest paths under
|
|
354
|
+
artifacts/pester/parallel/shard-<i>/, writing its console output to shard.log in that directory. After
|
|
355
|
+
every child has exited, Invoke-PoshQCParallelMerge verifies completeness and writes the merged
|
|
356
|
+
artifacts/pester/pester-junit.xml, artifacts/pester/powershell-coverage.xml, and the Koverage copy, but
|
|
357
|
+
only when every child exited 0 and wrote all of its outputs. Never calls exit.
|
|
358
|
+
Returns an object with ExitCode (0 only on full success), WorkerCount, WallClockSeconds, and Shards
|
|
359
|
+
(ShardIndex, ExitCode, LogPath).
|
|
360
|
+
.PARAMETER Root
|
|
361
|
+
Workspace root. Defaults to the current location.
|
|
362
|
+
.PARAMETER WorkerCount
|
|
363
|
+
Number of shard processes. Defaults to Get-PoshQCParallelWorkerCount over [Environment]::ProcessorCount.
|
|
364
|
+
.PARAMETER ResetShardDirectory
|
|
365
|
+
Seam that empties or creates one shard directory.
|
|
366
|
+
.PARAMETER StartShardProcess
|
|
367
|
+
Seam that starts pwsh with an argument list and a log path and returns the process.
|
|
368
|
+
.PARAMETER WaitShardProcess
|
|
369
|
+
Seam that waits for a started process and returns its exit code.
|
|
370
|
+
.PARAMETER ReadText
|
|
371
|
+
Seam that returns the text of a file, or $null when it does not exist.
|
|
372
|
+
.PARAMETER WriteText
|
|
373
|
+
Seam that writes text to a path.
|
|
374
|
+
.PARAMETER Clock
|
|
375
|
+
Seam that returns the current UTC time.
|
|
376
|
+
.PARAMETER Logger
|
|
377
|
+
Seam that receives progress and summary messages.
|
|
378
|
+
#>
|
|
379
|
+
function Invoke-PoshQCParallelTest {
|
|
380
|
+
[CmdletBinding()]
|
|
381
|
+
param(
|
|
382
|
+
[string] $Root,
|
|
383
|
+
[ValidateRange(1, [int]::MaxValue)][int] $WorkerCount,
|
|
384
|
+
[scriptblock] $ResetShardDirectory = {
|
|
385
|
+
param([string] $Path)
|
|
386
|
+
if (Test-Path -LiteralPath $Path) { Remove-Item -LiteralPath $Path -Recurse -Force }
|
|
387
|
+
$null = New-Item -ItemType Directory -Path $Path -Force
|
|
388
|
+
},
|
|
389
|
+
[scriptblock] $StartShardProcess = { param([string[]] $ArgumentList, [string] $LogPath) Invoke-PoshQCPwshExe -PwshArgs $ArgumentList -LogPath $LogPath },
|
|
390
|
+
[scriptblock] $WaitShardProcess = {
|
|
391
|
+
param($Process)
|
|
392
|
+
# Wait on the held process object: a lookup by id fails once a fast shard has already exited.
|
|
393
|
+
$Process.WaitForExit()
|
|
394
|
+
[int] $Process.ExitCode
|
|
395
|
+
},
|
|
396
|
+
[scriptblock] $ReadText = {
|
|
397
|
+
param([string] $Path)
|
|
398
|
+
if (Test-Path -LiteralPath $Path) { Get-Content -Raw -LiteralPath $Path } else { $null }
|
|
399
|
+
},
|
|
400
|
+
[scriptblock] $WriteText = { param([string] $Path, [string] $Text) Set-Content -LiteralPath $Path -Value $Text -Encoding utf8 },
|
|
401
|
+
[scriptblock] $Clock = { [DateTime]::UtcNow },
|
|
402
|
+
[scriptblock] $Logger = { param([string] $Message) Write-Information $Message -InformationAction Continue }
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
$ErrorActionPreference = 'Stop'
|
|
406
|
+
|
|
407
|
+
if (-not $Root) {
|
|
408
|
+
$Root = $PWD.ProviderPath
|
|
409
|
+
}
|
|
410
|
+
if (-not [IO.Path]::IsPathRooted($Root)) {
|
|
411
|
+
$Root = [IO.Path]::GetFullPath([IO.Path]::Combine($PWD.ProviderPath, $Root))
|
|
412
|
+
}
|
|
413
|
+
if (-not $PSBoundParameters.ContainsKey('WorkerCount')) {
|
|
414
|
+
$WorkerCount = Get-PoshQCParallelWorkerCount -LogicalProcessorCount ([Environment]::ProcessorCount)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
$started = & $Clock
|
|
418
|
+
$manifestPath = Join-Path $script:ModuleRoot 'PoshQC.psd1'
|
|
419
|
+
$parallelDirectory = Join-Path $Root 'artifacts/pester/parallel'
|
|
420
|
+
|
|
421
|
+
# Start every shard before waiting for any, so the shards run at the same time.
|
|
422
|
+
$shards = @(foreach ($index in 1..$WorkerCount) {
|
|
423
|
+
$directory = Join-Path $parallelDirectory "shard-$index"
|
|
424
|
+
& $ResetShardDirectory $directory
|
|
425
|
+
$logPath = Join-Path $directory 'shard.log'
|
|
426
|
+
$arguments = Get-PoshQCParallelShardArgument -ModuleManifestPath $manifestPath -Root $Root -ShardIndex $index -ShardCount $WorkerCount -ShardDirectory $directory
|
|
427
|
+
[pscustomobject]@{
|
|
428
|
+
ShardIndex = $index
|
|
429
|
+
Directory = $directory
|
|
430
|
+
LogPath = $logPath
|
|
431
|
+
Process = & $StartShardProcess $arguments $logPath
|
|
432
|
+
ExitCode = $null
|
|
433
|
+
}
|
|
434
|
+
})
|
|
435
|
+
& $Logger "Started $WorkerCount shard processes; logs are under $parallelDirectory."
|
|
436
|
+
|
|
437
|
+
foreach ($shard in $shards) {
|
|
438
|
+
$shard.ExitCode = [int] (& $WaitShardProcess $shard.Process)
|
|
439
|
+
& $Logger "Shard $($shard.ShardIndex) exited with code $($shard.ExitCode); log: $($shard.LogPath)"
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
$exitCode = Invoke-PoshQCParallelMerge -Root $Root -Shard $shards -ReadText $ReadText -WriteText $WriteText -Logger $Logger
|
|
443
|
+
$wallClockSeconds = [math]::Round(((& $Clock) - $started).TotalSeconds, 2)
|
|
444
|
+
& $Logger ('Invoke-PoshQCParallelTest: workers={0}; wall-clock={1}s; exit code={2}' -f $WorkerCount, $wallClockSeconds.ToString([System.Globalization.CultureInfo]::InvariantCulture), $exitCode)
|
|
445
|
+
|
|
446
|
+
[pscustomobject]@{
|
|
447
|
+
ExitCode = [int] $exitCode
|
|
448
|
+
WorkerCount = $WorkerCount
|
|
449
|
+
WallClockSeconds = $wallClockSeconds
|
|
450
|
+
Shards = @($shards | ForEach-Object { [pscustomobject]@{ ShardIndex = $_.ShardIndex; ExitCode = $_.ExitCode; LogPath = $_.LogPath } })
|
|
451
|
+
}
|
|
452
|
+
}
|