@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.
Files changed (261) hide show
  1. package/out/mcp-server.js +18758 -12030
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agent-memory/orchestrator/MEMORY.md +1 -1
  4. package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_test_files_count_against_500_cap.md +4 -4
  5. package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +1 -1
  6. package/resources/claude-customizations/.claude/agents/discovery-capability-sentinel.md +34 -0
  7. package/resources/claude-customizations/.claude/agents/feature-review.md +8 -0
  8. package/resources/claude-customizations/.claude/agents/legacy-parity-analyst.md +101 -43
  9. package/resources/claude-customizations/.claude/agents/migration-coverage-reviewer.md +107 -43
  10. package/resources/claude-customizations/.claude/agents/orchestrator.md +1 -1
  11. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +60 -6
  12. package/resources/claude-customizations/.claude/agents/parallel-planner.md +21 -1
  13. package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +1 -1
  14. package/resources/claude-customizations/.claude/agents/pr-author.md +48 -7
  15. package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +1 -1
  16. package/resources/claude-customizations/.claude/agents/requirements-reconciler.md +117 -42
  17. package/resources/claude-customizations/.claude/agents/runtime-characterization-analyst.md +103 -43
  18. package/resources/claude-customizations/.claude/hooks/discovery-capability-probe.ps1 +102 -0
  19. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +69 -194
  20. package/resources/claude-customizations/.claude/hooks/enforce-discovery-planning-gate.ps1 +87 -0
  21. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-green-ci.ps1 +325 -0
  22. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +29 -7
  23. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +23 -10
  24. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +29 -21
  25. package/resources/claude-customizations/.claude/hooks/enforce-file-size-limit.ps1 +244 -0
  26. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +51 -0
  27. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +168 -48
  28. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +1 -1
  29. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +22 -11
  30. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +59 -20
  31. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +51 -4
  32. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +72 -11
  33. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.current-branch.ps1 +149 -0
  34. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +4 -2
  35. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +59 -20
  36. package/resources/claude-customizations/.claude/hooks/record-discovery-agent-start.ps1 +132 -0
  37. package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +56 -212
  38. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-feature-folder.ps1 +146 -0
  39. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-rejection.ps1 +172 -0
  40. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +34 -4
  41. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +9 -3
  42. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusAggregate.psm1 +154 -0
  43. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusCombination.psm1 +228 -0
  44. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +1 -0
  45. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +4 -2
  46. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +63 -16
  47. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +56 -1
  48. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +4 -1
  49. package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +33 -6
  50. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +5 -1
  51. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
  52. package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +25 -2
  53. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
  54. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
  55. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
  56. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
  57. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
  58. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
  59. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
  60. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
  61. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
  62. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
  63. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
  64. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
  65. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
  66. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
  67. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +188 -341
  68. package/resources/claude-customizations/.claude/lib/file-size-policy/FileSizePolicy.psm1 +434 -0
  69. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateFeatureFolderRecord.psm1 +166 -0
  70. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +8 -3
  71. package/resources/claude-customizations/.claude/lib/worktree-resolution/HookTargetWorktree.psm1 +391 -0
  72. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunRecordPath.psm1 +185 -0
  73. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +11 -21
  74. package/resources/claude-customizations/.claude/rules/general-code-change.md +4 -1
  75. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +29 -5
  76. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +152 -20
  77. package/resources/claude-customizations/.claude/rules/powershell.md +1 -1
  78. package/resources/claude-customizations/.claude/rules/shell.md +1 -1
  79. package/resources/claude-customizations/.claude/settings.json +36 -3
  80. package/resources/claude-customizations/.claude/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  81. package/resources/claude-customizations/.claude/skills/characterize-runtime-behavior/SKILL.md +134 -0
  82. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +2 -2
  83. package/resources/claude-customizations/.claude/skills/discovery-behavior-reconciliation/SKILL.md +28 -47
  84. package/resources/claude-customizations/.claude/skills/discovery-coverage-ledger/SKILL.md +32 -48
  85. package/resources/claude-customizations/.claude/skills/discovery-parity-matrix/SKILL.md +28 -45
  86. package/resources/claude-customizations/.claude/skills/discovery-repo-inventory/SKILL.md +28 -62
  87. package/resources/claude-customizations/.claude/skills/discovery-runtime-characterization/SKILL.md +28 -45
  88. package/resources/claude-customizations/.claude/skills/discovery-validate-artifacts/SKILL.md +28 -61
  89. package/resources/claude-customizations/.claude/skills/discovery-workflow/SKILL.md +55 -139
  90. package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +25 -10
  91. package/resources/claude-customizations/.claude/skills/extract-feature-contract/SKILL.md +133 -0
  92. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +1 -0
  93. package/resources/claude-customizations/.claude/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  94. package/resources/claude-customizations/.claude/skills/initialize-discovery-workspace/SKILL.md +131 -0
  95. package/resources/claude-customizations/.claude/skills/inventory-legacy-system/SKILL.md +131 -0
  96. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +15 -7
  97. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +25 -2
  98. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +11 -1
  99. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +124 -14
  100. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +72 -7
  101. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +15 -0
  102. package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +6 -0
  103. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +56 -9
  104. package/resources/claude-customizations/.claude/skills/reconcile-parity-matrix/SKILL.md +131 -0
  105. package/resources/claude-customizations/.claude/skills/review-discovery-coverage/SKILL.md +129 -0
  106. package/resources/claude-customizations/.claude/skills/validate-discovery-artifacts/SKILL.md +127 -0
  107. package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +1 -1
  108. package/resources/claude-customizations/config/blast-radius.json +6 -5
  109. package/resources/claude-customizations/config/orchestration-routing.json +61 -1
  110. package/resources/claude-customizations/pack-manifests/core.json +40 -1
  111. package/resources/codex-and-agents-customizations/.agents/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  112. package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +7 -0
  113. package/resources/codex-and-agents-customizations/.agents/skills/characterize-runtime-behavior/SKILL.md +134 -0
  114. package/resources/codex-and-agents-customizations/.agents/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
  115. package/resources/codex-and-agents-customizations/.agents/skills/discovery-coverage-ledger/SKILL.md +50 -0
  116. package/resources/codex-and-agents-customizations/.agents/skills/discovery-parity-matrix/SKILL.md +46 -0
  117. package/resources/codex-and-agents-customizations/.agents/skills/discovery-repo-inventory/SKILL.md +46 -0
  118. package/resources/codex-and-agents-customizations/.agents/skills/discovery-runtime-characterization/SKILL.md +46 -0
  119. package/resources/codex-and-agents-customizations/.agents/skills/discovery-validate-artifacts/SKILL.md +46 -0
  120. package/resources/codex-and-agents-customizations/.agents/skills/discovery-workflow/SKILL.md +62 -0
  121. package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +25 -10
  122. package/resources/codex-and-agents-customizations/.agents/skills/extract-feature-contract/SKILL.md +133 -0
  123. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +1 -1
  124. package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +8 -0
  125. package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +4 -1
  126. package/resources/codex-and-agents-customizations/.agents/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  127. package/resources/codex-and-agents-customizations/.agents/skills/initialize-discovery-workspace/SKILL.md +131 -0
  128. package/resources/codex-and-agents-customizations/.agents/skills/inventory-legacy-system/SKILL.md +131 -0
  129. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +8 -0
  130. package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +1 -1
  131. package/resources/codex-and-agents-customizations/.agents/skills/reconcile-parity-matrix/SKILL.md +131 -0
  132. package/resources/codex-and-agents-customizations/.agents/skills/review-discovery-coverage/SKILL.md +129 -0
  133. package/resources/codex-and-agents-customizations/.agents/skills/validate-discovery-artifacts/SKILL.md +127 -0
  134. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +1 -1
  135. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +1 -1
  136. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +1 -1
  137. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +1 -1
  138. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +1 -1
  139. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +1 -1
  140. package/resources/codex-and-agents-customizations/.codex/agents/discovery-capability-sentinel.toml +51 -0
  141. package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +8 -0
  142. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c1.toml +148 -0
  143. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c2.toml +148 -0
  144. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3-elevated.toml +148 -0
  145. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3.toml +148 -0
  146. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c4.toml +148 -0
  147. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst.toml +148 -0
  148. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c1.toml +154 -0
  149. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c2.toml +154 -0
  150. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3-elevated.toml +154 -0
  151. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3.toml +154 -0
  152. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c4.toml +154 -0
  153. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer.toml +154 -0
  154. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +1 -1
  155. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +1 -1
  156. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +1 -1
  157. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +1 -1
  158. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +1 -1
  159. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +1 -1
  160. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +1 -1
  161. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +1 -1
  162. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +1 -1
  163. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +1 -1
  164. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +1 -1
  165. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +1 -1
  166. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c1.toml +164 -0
  167. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c2.toml +164 -0
  168. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3-elevated.toml +164 -0
  169. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3.toml +164 -0
  170. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c4.toml +164 -0
  171. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler.toml +164 -0
  172. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c1.toml +150 -0
  173. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c2.toml +150 -0
  174. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3-elevated.toml +150 -0
  175. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3.toml +150 -0
  176. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c4.toml +150 -0
  177. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst.toml +150 -0
  178. package/resources/codex-and-agents-customizations/.codex/config.toml +39 -3
  179. package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +22 -0
  180. package/resources/codex-and-agents-customizations/.codex/hooks/discovery-capability-probe.ps1 +97 -0
  181. package/resources/codex-and-agents-customizations/.codex/hooks/discovery-start-attestation.ps1 +171 -0
  182. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +4 -1
  183. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-artifact-gate.ps1 +187 -0
  184. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-planning-gate.ps1 +149 -0
  185. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-file-size-limit.ps1 +282 -0
  186. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +239 -2
  187. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -15
  188. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +54 -15
  189. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +54 -15
  190. package/resources/codex-and-agents-customizations/.codex/hooks/file-size-policy.ps1 +421 -0
  191. package/resources/codex-and-agents-customizations/.codex/hooks/hook-target-worktree.ps1 +369 -0
  192. package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +8 -0
  193. package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +22 -1
  194. package/resources/codex-and-agents-customizations/.codex/hooks/validate-discovery-agent-output.ps1 +178 -0
  195. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +11 -1
  196. package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +1 -1
  197. package/resources/codex-and-agents-customizations/AGENTS.md +4 -1
  198. package/resources/codex-and-agents-customizations/pack-manifests/core.json +65 -1
  199. package/resources/config/orchestration-routing.json +61 -1
  200. package/resources/customizations/.github/agents/commentary-remediation.agent.md +1 -1
  201. package/resources/customizations/.github/agents/discovery-capability-sentinel.agent.md +34 -0
  202. package/resources/customizations/.github/agents/legacy-parity-analyst.agent.md +122 -0
  203. package/resources/customizations/.github/agents/migration-coverage-reviewer.agent.md +128 -0
  204. package/resources/customizations/.github/agents/python-execution-only-typed.agent.md +1 -1
  205. package/resources/customizations/.github/agents/python-typed-engineer.agent.md +1 -1
  206. package/resources/customizations/.github/agents/requirements-reconciler.agent.md +138 -0
  207. package/resources/customizations/.github/agents/runtime-characterization-analyst.agent.md +124 -0
  208. package/resources/customizations/.github/hooks/discovery-agent-start.ps1 +139 -0
  209. package/resources/customizations/.github/hooks/discovery-agent-stop.ps1 +123 -0
  210. package/resources/customizations/.github/hooks/discovery-capability-probe.ps1 +98 -0
  211. package/resources/customizations/.github/hooks/discovery-planning.ps1 +91 -0
  212. package/resources/customizations/.github/hooks/discovery-pre-tool.ps1 +123 -0
  213. package/resources/customizations/.github/hooks/discovery.json +35 -0
  214. package/resources/customizations/.github/instructions/general-code-change.instructions.md +5 -2
  215. package/resources/customizations/.github/instructions/mermaid.instructions.md +85 -0
  216. package/resources/customizations/.github/instructions/powershell-code-change.instructions.md +1 -1
  217. package/resources/customizations/.github/prompts/execute-hard-lock.prompt.md +105 -0
  218. package/resources/customizations/.github/prompts/remediate-comments.prompt.md +1 -1
  219. package/resources/customizations/.github/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  220. package/resources/customizations/.github/skills/characterize-runtime-behavior/SKILL.md +134 -0
  221. package/resources/customizations/.github/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
  222. package/resources/customizations/.github/skills/discovery-coverage-ledger/SKILL.md +50 -0
  223. package/resources/customizations/.github/skills/discovery-parity-matrix/SKILL.md +46 -0
  224. package/resources/customizations/.github/skills/discovery-repo-inventory/SKILL.md +46 -0
  225. package/resources/customizations/.github/skills/discovery-runtime-characterization/SKILL.md +46 -0
  226. package/resources/customizations/.github/skills/discovery-validate-artifacts/SKILL.md +46 -0
  227. package/resources/customizations/.github/skills/discovery-workflow/SKILL.md +62 -0
  228. package/resources/customizations/.github/skills/execution-environment/SKILL.md +25 -10
  229. package/resources/customizations/.github/skills/extract-feature-contract/SKILL.md +133 -0
  230. package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +8 -0
  231. package/resources/customizations/.github/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  232. package/resources/customizations/.github/skills/initialize-discovery-workspace/SKILL.md +131 -0
  233. package/resources/customizations/.github/skills/inventory-legacy-system/SKILL.md +131 -0
  234. package/resources/customizations/.github/skills/reconcile-parity-matrix/SKILL.md +131 -0
  235. package/resources/customizations/.github/skills/review-discovery-coverage/SKILL.md +129 -0
  236. package/resources/customizations/.github/skills/validate-discovery-artifacts/SKILL.md +127 -0
  237. package/resources/lib/codex-routing/CodexDeployment.psm1 +5 -1
  238. package/resources/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
  239. package/resources/lib/codex-routing/CodexTopology.psm1 +25 -2
  240. package/resources/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
  241. package/resources/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
  242. package/resources/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
  243. package/resources/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
  244. package/resources/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
  245. package/resources/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
  246. package/resources/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
  247. package/resources/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
  248. package/resources/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
  249. package/resources/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
  250. package/resources/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
  251. package/resources/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
  252. package/resources/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
  253. package/resources/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
  254. package/resources/powershell/PoshQC/PoshQC.Parallel.psm1 +452 -0
  255. package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +106 -8
  256. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +12 -0
  257. package/resources/powershell/PoshQC/PoshQC.psd1 +6 -1
  258. package/resources/powershell/PoshQC/PoshQC.psm1 +7 -1
  259. package/resources/powershell/PoshQC/README.md +20 -1
  260. package/resources/templates/policy_audit/code-review.yyyy-MM-ddTHH-mm.md +4 -4
  261. 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
+ }