@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,325 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Green-CI standalone merge branch of the merge gate (issue #997).
4
+
5
+ .DESCRIPTION
6
+ Dot-sourced by enforce-epic-merge-gate.ps1. Invoke-EpicMergeGateDecision calls
7
+ Get-EpicMergeGateGreenCiDecision after the parallel allow-branch declines and before the
8
+ standalone authorization branch, so branches 1 to 3 keep their order and behavior. The
9
+ branch allows a standalone run (neither an epic child nor a parallel item) to merge its own
10
+ pull request once its per-feature checkpoint records a green S9 gate for that exact pull
11
+ request and the live pull-request head still equals the head that CI passed on.
12
+
13
+ Stages run cheapest first, and only stage D starts a process:
14
+ A. The item target resolved to one worktree and its per-feature checkpoint records a green
15
+ standalone S9 gate bound to the explicit PR number (Test-ChildCheckpointGreenCiRecorded).
16
+ B. No epic or parallel run checkpoint records the PR number (both run targets NoTarget),
17
+ and no parallel run checkpoint records the item worktree as a parallel item.
18
+ C. The command is one structural gh pr merge with no chain, wrapper, inline environment
19
+ prefix, -R or --repo, /pull/ URL selector, or --auto (Get-DocsOnlyMergeShapeViolation),
20
+ and carries no --admin flag (declined silently).
21
+ D. gh pr view <PR> --json headRefOid returns a well-formed head (one bounded call).
22
+ E. The live head equals ci_gate.head_sha, and --match-head-commit, when present, equals it.
23
+ Stages A to C decline silently, so every existing deny text is unchanged for a non-green
24
+ case. A stage D or E decline returns a suffix that the gate appends to its deny reason:
25
+ GREEN_CI_LIVE_HEAD_UNRESOLVED, GREEN_CI_HEAD_STALE, or GREEN_CI_MATCH_HEAD_MISMATCH.
26
+
27
+ Residual parallel-exclusion window: the Parallel mode: true marker lives in the Agent
28
+ delegation prompt and is not in a Bash PreToolUse payload, so this hook cannot see it. The
29
+ exclusion is layered and best-effort: a run target recording the PR number (layer 1a), a
30
+ parallel item recording the worktree path (layer 1b), and child-recorded parallel_mode or
31
+ parallel_slug keys (layer 2), with skill prose as layer 3. A parallel child whose parent has
32
+ recorded neither the PR number nor the worktree path, and which omits the layer 2 keys, is
33
+ not excluded mechanically. Like the standalone authorization record, this is a policy-level
34
+ control, not a cryptographic one. A push after the stage D lookup can also change the head
35
+ unless the command passes --match-head-commit; the hook does not require the flag.
36
+
37
+ .NOTES
38
+ Compatible with PowerShell 7+. This file dot-sources nothing and imports no module. It
39
+ calls, at run time only, Invoke-DocsOnlyGhExe and Get-DocsOnlyMergeShapeViolation
40
+ (hook-docs-only-diff.ps1), Test-CommandLineFlag and Get-CommandLineFlagValue (the shared
41
+ command parser), and Resolve-WorktreeRunTargetByRecord (WorktreeRunResolution.psm1), all of
42
+ which the gate loads before it calls this file's functions.
43
+ #>
44
+
45
+ function Test-EpicMergeGateGreenCiObjectId {
46
+ <#
47
+ .SYNOPSIS
48
+ True when a value is a full lowercase hexadecimal object id of 40 or 64 characters.
49
+ .PARAMETER Value
50
+ The candidate value; any non-string value returns $false.
51
+ .OUTPUTS
52
+ System.Boolean
53
+ #>
54
+ [CmdletBinding()]
55
+ [OutputType([bool])]
56
+ param([AllowNull()] $Value)
57
+
58
+ return ($Value -is [string]) -and ($Value -cmatch '^[0-9a-f]{40}([0-9a-f]{24})?$')
59
+ }
60
+
61
+ function Test-EpicMergeGateGreenCiNotParallel {
62
+ <#
63
+ .SYNOPSIS
64
+ True when a parsed per-feature checkpoint carries no epic or parallel marker (pure).
65
+ .DESCRIPTION
66
+ epic_mode must be absent, JSON null, or JSON false; epic_mode true belongs to branch 1,
67
+ and any other value (for example the string "false") fails closed. parallel_mode must
68
+ be absent, JSON null, or JSON false, and parallel_slug must be absent or blank.
69
+ .PARAMETER Checkpoint
70
+ The parsed checkpoint object.
71
+ .OUTPUTS
72
+ System.Boolean
73
+ #>
74
+ [CmdletBinding()]
75
+ [OutputType([bool])]
76
+ param([Parameter(Mandatory)] $Checkpoint)
77
+
78
+ $props = @($Checkpoint.PSObject.Properties.Name)
79
+ # Each marker key passes only when it is absent, JSON null, or the JSON boolean false.
80
+ foreach ($marker in @('epic_mode', 'parallel_mode')) {
81
+ if ($props -ccontains $marker -and $null -ne $Checkpoint.$marker -and
82
+ -not (($Checkpoint.$marker -is [bool]) -and -not $Checkpoint.$marker)) {
83
+ return $false
84
+ }
85
+ }
86
+ return -not ($props -ccontains 'parallel_slug' -and -not [string]::IsNullOrWhiteSpace([string]$Checkpoint.parallel_slug))
87
+ }
88
+
89
+ function Test-ChildCheckpointGreenCiRecorded {
90
+ <#
91
+ .SYNOPSIS
92
+ True when a per-feature checkpoint records a green standalone S9 gate for the PR (pure).
93
+ .DESCRIPTION
94
+ Every condition must hold: the command names a PR number above zero; the checkpoint
95
+ carries no epic or parallel marker (Test-EpicMergeGateGreenCiNotParallel);
96
+ step9_status is exactly "passed"; ci_gate is an object whose conclusion is exactly
97
+ "success"; pr_gate.pr_number is a JSON integer equal to the command number, with no
98
+ fallback to a standalone_merge_authorizations record; ci_gate.head_sha is a lowercase
99
+ 40 or 64 character object id; and pr_gate.head_sha, when present and not null, equals
100
+ ci_gate.head_sha. Comparisons are ordinal, so a case variant fails closed.
101
+ .PARAMETER Checkpoint
102
+ Parsed per-feature checkpoint, or $null when absent or unreadable.
103
+ .PARAMETER CommandPrNumber
104
+ The explicit PR number the command names, or $null for a bare command.
105
+ .OUTPUTS
106
+ System.Boolean
107
+ #>
108
+ [CmdletBinding()]
109
+ [OutputType([bool])]
110
+ param(
111
+ [AllowNull()] $Checkpoint,
112
+ [AllowNull()][Nullable[int]] $CommandPrNumber
113
+ )
114
+
115
+ if ($null -eq $CommandPrNumber -or $CommandPrNumber -le 0) { return $false }
116
+ if ($Checkpoint -isnot [System.Management.Automation.PSCustomObject]) { return $false }
117
+ if (-not (Test-EpicMergeGateGreenCiNotParallel -Checkpoint $Checkpoint)) { return $false }
118
+ if (-not (($Checkpoint.step9_status -is [string]) -and ($Checkpoint.step9_status -ceq 'passed'))) { return $false }
119
+
120
+ $ciGate = $Checkpoint.ci_gate
121
+ if ($ciGate -isnot [System.Management.Automation.PSCustomObject]) { return $false }
122
+ if (-not (($ciGate.conclusion -is [string]) -and ($ciGate.conclusion -ceq 'success'))) { return $false }
123
+ if (-not (Test-EpicMergeGateGreenCiObjectId -Value $ciGate.head_sha)) { return $false }
124
+
125
+ # The PR binding comes from pr_gate alone; an authorization record must not stand in for a CI result.
126
+ $prGate = $Checkpoint.pr_gate
127
+ if ($prGate -isnot [System.Management.Automation.PSCustomObject]) { return $false }
128
+ $recordedNumber = $prGate.pr_number
129
+ if (-not (($recordedNumber -is [int]) -or ($recordedNumber -is [long])) -or [long]$recordedNumber -ne $CommandPrNumber) { return $false }
130
+
131
+ # A recorded PR head must agree with the head CI passed on; null or absent means not recorded.
132
+ if (@($prGate.PSObject.Properties.Name) -ccontains 'head_sha' -and $null -ne $prGate.head_sha) {
133
+ return [string]::Equals([string]$prGate.head_sha, [string]$ciGate.head_sha, [System.StringComparison]::Ordinal)
134
+ }
135
+ return $true
136
+ }
137
+
138
+ function Get-EpicMergeGateLivePrHeadSha {
139
+ <#
140
+ .SYNOPSIS
141
+ Return the live head commit of a pull request, or $null on any failure (the live-head seam).
142
+ .DESCRIPTION
143
+ Runs gh pr view <PR> --json headRefOid through Invoke-DocsOnlyGhExe, which bounds the
144
+ call at 7 seconds and never throws. Returns the head only for exit code 0, output that
145
+ parses to a JSON object, and a well-formed lowercase object id; a non-zero exit
146
+ (including 124 for a timeout and -1 for a missing root), unparseable output, and a
147
+ malformed head all return $null. Tests mock this function by name.
148
+ .PARAMETER PrNumber
149
+ The pull request number.
150
+ .PARAMETER WorktreeRoot
151
+ The directory gh runs in.
152
+ .OUTPUTS
153
+ System.String or $null
154
+ #>
155
+ [CmdletBinding()]
156
+ [OutputType([string])]
157
+ param(
158
+ [Parameter(Mandatory)][int] $PrNumber,
159
+ [AllowNull()][AllowEmptyString()][string] $WorktreeRoot
160
+ )
161
+
162
+ $lookup = Invoke-DocsOnlyGhExe -WorkingRoot $WorktreeRoot -GhArgs @('pr', 'view', [string]$PrNumber, '--json', 'headRefOid')
163
+ if ($null -eq $lookup -or $lookup.ExitCode -ne 0) { return $null }
164
+ try {
165
+ $pr = ConvertFrom-Json -InputObject ([string]$lookup.Output) -ErrorAction Stop
166
+ }
167
+ catch {
168
+ # Unreadable gh output is a lookup failure; the gate fails closed on $null.
169
+ return $null
170
+ }
171
+ if ($pr -isnot [System.Management.Automation.PSCustomObject]) { return $null }
172
+ if (-not (Test-EpicMergeGateGreenCiObjectId -Value $pr.headRefOid)) { return $null }
173
+ return [string]$pr.headRefOid
174
+ }
175
+
176
+ function Test-EpicMergeGateGreenCiHeadAgreement {
177
+ <#
178
+ .SYNOPSIS
179
+ True when the live head equals ci_gate.head_sha and any pinned head equals the live head (pure).
180
+ .PARAMETER Checkpoint
181
+ Parsed per-feature checkpoint carrying ci_gate.head_sha.
182
+ .PARAMETER LiveHeadSha
183
+ The live pull-request head, or $null when unresolved.
184
+ .PARAMETER PinnedHeadSha
185
+ The --match-head-commit value, or $null when the flag is absent. An empty string means
186
+ the flag is present without a usable value and fails closed.
187
+ .OUTPUTS
188
+ System.Boolean
189
+ #>
190
+ [CmdletBinding()]
191
+ [OutputType([bool])]
192
+ param(
193
+ [AllowNull()] $Checkpoint,
194
+ [AllowNull()] $LiveHeadSha,
195
+ [AllowNull()] $PinnedHeadSha
196
+ )
197
+
198
+ if ($null -eq $Checkpoint -or $LiveHeadSha -isnot [string] -or $null -eq $Checkpoint.ci_gate) { return $false }
199
+ if (-not [string]::Equals($LiveHeadSha, [string]$Checkpoint.ci_gate.head_sha, [System.StringComparison]::Ordinal)) { return $false }
200
+ if ($null -ne $PinnedHeadSha -and -not [string]::Equals([string]$PinnedHeadSha, $LiveHeadSha, [System.StringComparison]::Ordinal)) { return $false }
201
+ return $true
202
+ }
203
+
204
+ function Resolve-EpicMergeGateParallelItemByWorktree {
205
+ <#
206
+ .SYNOPSIS
207
+ Resolve the parallel run whose items[] record a worktree path (layer 1b seam).
208
+ .DESCRIPTION
209
+ A one-call seam over Resolve-WorktreeRunTargetByRecord so tests can mock it. Status
210
+ NoTarget means no parallel checkpoint records the worktree as a parallel item.
211
+ .PARAMETER WorktreeRoot
212
+ The item worktree whose per-feature checkpoint was read.
213
+ .OUTPUTS
214
+ System.Management.Automation.PSCustomObject (the worktree-resolution target result).
215
+ #>
216
+ [CmdletBinding()]
217
+ [OutputType([pscustomobject])]
218
+ param([AllowNull()][AllowEmptyString()][string] $WorktreeRoot)
219
+
220
+ return Resolve-WorktreeRunTargetByRecord -Kind parallel -RecordField worktree_path -Value $WorktreeRoot -SessionRoot (Get-Location).Path
221
+ }
222
+
223
+ function Test-EpicMergeGateGreenCiNoRunRecord {
224
+ <#
225
+ .SYNOPSIS
226
+ True when no epic or parallel run checkpoint records the PR or the item worktree (stage B).
227
+ .DESCRIPTION
228
+ Both run targets must have status NoTarget; a resolved or Ambiguous target declines.
229
+ The worktree lookup declines on any status other than NoTarget, on $null, and on a
230
+ thrown error, because an unreadable parallel record cannot prove the item is standalone.
231
+ .PARAMETER EpicTarget
232
+ The epic run target resolved for the PR number.
233
+ .PARAMETER ParallelTarget
234
+ The parallel run target resolved for the PR number.
235
+ .PARAMETER WorktreeRoot
236
+ The item worktree root.
237
+ .OUTPUTS
238
+ System.Boolean
239
+ #>
240
+ [CmdletBinding()]
241
+ [OutputType([bool])]
242
+ param(
243
+ [AllowNull()] $EpicTarget,
244
+ [AllowNull()] $ParallelTarget,
245
+ [AllowNull()][AllowEmptyString()][string] $WorktreeRoot
246
+ )
247
+
248
+ foreach ($target in @($EpicTarget, $ParallelTarget)) {
249
+ if ($null -eq $target -or [string]$target.Status -cne 'NoTarget') { return $false }
250
+ }
251
+ try {
252
+ $byWorktree = Resolve-EpicMergeGateParallelItemByWorktree -WorktreeRoot $WorktreeRoot
253
+ }
254
+ catch {
255
+ # A failed lookup cannot show the worktree is not a parallel item, so the stage declines.
256
+ return $false
257
+ }
258
+ return ($null -ne $byWorktree -and [string]$byWorktree.Status -ceq 'NoTarget')
259
+ }
260
+
261
+ function Get-EpicMergeGateGreenCiDecision {
262
+ <#
263
+ .SYNOPSIS
264
+ Evaluate stages A to E of the green-CI branch and return Allowed and a deny suffix.
265
+ Stage C declines silently a command that carries --admin.
266
+ .PARAMETER CommandText
267
+ The Bash command text under evaluation.
268
+ .PARAMETER CommandPrNumber
269
+ The explicit PR number, or $null for a bare command.
270
+ .PARAMETER ChildCheckpoint
271
+ The parsed per-feature checkpoint the gate read, or $null.
272
+ .PARAMETER ItemTarget
273
+ The item target, or $null for a bare command.
274
+ .PARAMETER EpicTarget
275
+ The epic run target, or $null for a bare command.
276
+ .PARAMETER ParallelTarget
277
+ The parallel run target, or $null for a bare command.
278
+ .PARAMETER SessionRoot
279
+ The session worktree root, where gh runs.
280
+ .OUTPUTS
281
+ System.Collections.Specialized.OrderedDictionary with Allowed and Suffix.
282
+ #>
283
+ [CmdletBinding()]
284
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
285
+ param(
286
+ [AllowNull()][AllowEmptyString()][string] $CommandText,
287
+ [AllowNull()][Nullable[int]] $CommandPrNumber,
288
+ [AllowNull()] $ChildCheckpoint,
289
+ [AllowNull()] $ItemTarget,
290
+ [AllowNull()] $EpicTarget,
291
+ [AllowNull()] $ParallelTarget,
292
+ [AllowNull()][AllowEmptyString()][string] $SessionRoot
293
+ )
294
+
295
+ $silent = [ordered]@{ Allowed = $false; Suffix = '' }
296
+ # Stage A: one resolved item worktree whose checkpoint records a green S9 gate for this PR.
297
+ if ($null -eq $ItemTarget -or @('SessionRoot', 'OtherWorktree') -cnotcontains [string]$ItemTarget.Status) { return $silent }
298
+ if (-not (Test-ChildCheckpointGreenCiRecorded -Checkpoint $ChildCheckpoint -CommandPrNumber $CommandPrNumber)) { return $silent }
299
+ # Stage B: no epic or parallel run records the PR or the item worktree.
300
+ if (-not (Test-EpicMergeGateGreenCiNoRunRecord -EpicTarget $EpicTarget -ParallelTarget $ParallelTarget -WorktreeRoot ([string]$ItemTarget.WorktreeRoot))) { return $silent }
301
+ # Stage C: a single structural gh pr merge naming the PR, with no repository or auto override.
302
+ if ($null -ne (Get-DocsOnlyMergeShapeViolation -CommandText $CommandText -PrNumber $CommandPrNumber)) { return $silent }
303
+ # --admin bypasses branch protection, so a green checkpoint must not authorize it.
304
+ if (Test-CommandLineFlag -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('pr', 'merge') -FlagName '--admin') { return $silent }
305
+
306
+ # Stage D: the only process call, made after every pure stage passed.
307
+ $liveHead = Get-EpicMergeGateLivePrHeadSha -PrNumber $CommandPrNumber -WorktreeRoot $SessionRoot
308
+ if ($null -eq $liveHead) {
309
+ return [ordered]@{ Allowed = $false; Suffix = ' (green-CI merge not applied: GREEN_CI_LIVE_HEAD_UNRESOLVED)' }
310
+ }
311
+
312
+ # Stage E: a stale checkpoint head is reported before a mismatched --match-head-commit pin.
313
+ $subcommandPath = @('pr', 'merge')
314
+ $pinned = $null
315
+ if (Test-CommandLineFlag -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath -FlagName '--match-head-commit') {
316
+ $pinned = [string](Get-CommandLineFlagValue -CommandText $CommandText -CommandWord 'gh' -SubcommandPath $subcommandPath -FlagName '--match-head-commit')
317
+ }
318
+ if (Test-EpicMergeGateGreenCiHeadAgreement -Checkpoint $ChildCheckpoint -LiveHeadSha $liveHead -PinnedHeadSha $pinned) {
319
+ return [ordered]@{ Allowed = $true; Suffix = '' }
320
+ }
321
+ if (-not (Test-EpicMergeGateGreenCiHeadAgreement -Checkpoint $ChildCheckpoint -LiveHeadSha $liveHead -PinnedHeadSha $null)) {
322
+ return [ordered]@{ Allowed = $false; Suffix = ' (green-CI merge not applied: GREEN_CI_HEAD_STALE)' }
323
+ }
324
+ return [ordered]@{ Allowed = $false; Suffix = ' (green-CI merge not applied: GREEN_CI_MATCH_HEAD_MISMATCH)' }
325
+ }
@@ -12,6 +12,8 @@
12
12
  1. Child-feature path: artifacts/orchestration/orchestrator-state.json exists,
13
13
  epic_mode == true, and step9_status == "passed" (the per-feature orchestrator has
14
14
  already run S9 step 6's CI-green gate before attempting its own merge-on-green).
15
+ Only the JSON boolean true counts as epic mode; a string or number such as "false"
16
+ does not (issue #1001).
15
17
  2. Epic-integration path: artifacts/orchestration/epic-orchestrator-state.json exists,
16
18
  epic_merge_pr.ci_gate.conclusion == "success", and, when the command names an
17
19
  explicit PR number, that number matches epic_merge_pr.pr_number.
@@ -20,6 +22,11 @@
20
22
  entry whose merge_status == "ci_green". A parallel run always names an explicit PR
21
23
  number (each item merges from its own isolated worktree), so a bare command with no
22
24
  PR number cannot satisfy this branch.
25
+ 3b. Green-CI standalone path (issue #997): the command names an explicit PR number, no
26
+ epic or parallel checkpoint records that number or the item worktree, the per-feature
27
+ checkpoint records step9_status == "passed", ci_gate.conclusion == "success", and a
28
+ pr_gate.pr_number equal to it, and the live PR head equals ci_gate.head_sha. The
29
+ dot-sourced enforce-epic-merge-gate-green-ci.ps1 implements this branch.
23
30
  4. Standalone path (issue #670), evaluated last: the command names an explicit PR
24
31
  number and one of the three checkpoints above carries a
25
32
  standalone_merge_authorizations record whose pr_number equals it, whose fields are
@@ -29,15 +36,20 @@
29
36
  Otherwise the command is denied with reason EPIC_MERGE_GATE_BLOCKED; a denial from
30
37
  condition 4 also carries a standalone reason code after that token. A missing or
31
38
  unreadable checkpoint in any branch fails closed (denies). A standalone (non-epic,
32
- non-parallel) run never satisfies conditions 1-3, so its only route to a merge is an
33
- explicit, PR-specific, session-bound record under condition 4; a bare command with no
34
- PR number never satisfies condition 4, and a blanket flag is rejected rather than read.
39
+ non-parallel) run never satisfies conditions 1-3; it merges through the green-CI branch
40
+ 3b or through an explicit, PR-specific, session-bound record under condition 4. A bare
41
+ command with no PR number satisfies neither, and a blanket flag is rejected rather than
42
+ read. A 3b decline after its live-head lookup appends a GREEN_CI_* code to the deny.
35
43
  Trigger scope (issue #952): every merge method (--merge/-m, --squash/-s, --rebase/-r), --auto,
36
44
  --admin, and a bare gh pr merge are gated alike, because under a merge queue each spelling
37
45
  enqueues the pull request; only a pure --disable-auto is out of scope. The predicate and the
38
46
  PR selector reader live in enforce-epic-merge-gate-command.ps1.
39
47
 
40
- Design decision: branches 1-4 trust the on-disk checkpoint and never call gh. Issue #927
48
+ Design decision: branches 1-4 trust the on-disk checkpoint and never call gh. The green-CI
49
+ branch makes at most one bounded gh pr view call, only after its pure stages A to C pass.
50
+ Its parallel exclusion is best-effort: the Parallel mode: true marker is in the delegation
51
+ prompt, not in a Bash payload, so a parallel child that no checkpoint records yet is not
52
+ excluded mechanically (residual window; see enforce-epic-merge-gate-green-ci.ps1). Issue #927
41
53
  adds a docs-only branch after branch 4 fails, only for an explicit PR number whose item,
42
54
  epic, and parallel targets are all NoTarget (no checkpoint records the PR): it calls
43
55
  Get-DocsOnlyMergeExemption (hook-docs-only-diff.ps1), which runs gh pr view and git in the
@@ -86,6 +98,8 @@ try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-resolution.ps1') } cat
86
98
  try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-command.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-command.ps1' -ErrorRecord $_ }
87
99
  # Shared docs-only diff classification and the merge-side docs-only exemption (issue #927).
88
100
  try { . (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1') } catch { Add-HookDependencyFailure -Name 'hook-docs-only-diff.ps1' -ErrorRecord $_ }
101
+ # Green-CI standalone merge branch: pure checks, the live-head seam, and the stage orchestrator (issue #997).
102
+ try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-green-ci.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-green-ci.ps1' -ErrorRecord $_ }
89
103
 
90
104
  function ConvertFrom-EpicMergeGateJson {
91
105
  <#
@@ -143,6 +157,10 @@ function Test-ChildCheckpointAllowsEpicMerge {
143
157
  <#
144
158
  .SYNOPSIS
145
159
  Decision logic for the child-feature checkpoint path (branch 1).
160
+ .DESCRIPTION
161
+ Only the JSON boolean true counts as epic mode: epic_mode must be a System.Boolean
162
+ $true after ConvertFrom-Json. A string or number such as "false", "true", or 1 is not
163
+ epic mode, so branch 1 declines and later branches decide (issue #1001).
146
164
  .PARAMETER Checkpoint
147
165
  Parsed child orchestrator checkpoint, or $null when absent/unreadable.
148
166
  .OUTPUTS
@@ -159,7 +177,7 @@ function Test-ChildCheckpointAllowsEpicMerge {
159
177
  return $false
160
178
  }
161
179
  $props = @($Checkpoint.PSObject.Properties.Name)
162
- if ($props -notcontains 'epic_mode' -or -not [bool]$Checkpoint.epic_mode) {
180
+ if ($props -notcontains 'epic_mode' -or -not (($Checkpoint.epic_mode -is [bool]) -and $Checkpoint.epic_mode)) {
163
181
  return $false
164
182
  }
165
183
  if ($props -notcontains 'step9_status') {
@@ -376,6 +394,10 @@ function Invoke-EpicMergeGateDecision {
376
394
  return Get-EpicMergeGateAllowDecision
377
395
  }
378
396
 
397
+ # Branch 3b (issue #997): a standalone run whose checkpoint records a green S9 gate merges its own PR.
398
+ $greenCi = Get-EpicMergeGateGreenCiDecision -CommandText $commandText -CommandPrNumber $commandPrNumber -ChildCheckpoint $childCheckpoint -ItemTarget $itemTarget -EpicTarget $epicTarget -ParallelTarget $parallelTarget -SessionRoot $sessionRoot
399
+ if ($greenCi.Allowed) { return Get-EpicMergeGateAllowDecision }
400
+
379
401
  # Branch 4, evaluated last and only for an explicit PR number: a PR-specific standalone
380
402
  # authorization record bound to the live session. Its deny keeps the gate token first.
381
403
  if ($null -ne $commandPrNumber) {
@@ -397,10 +419,10 @@ function Invoke-EpicMergeGateDecision {
397
419
  # three details precede the standalone reason (issue #850).
398
420
  $unresolved = Get-EpicMergeGateUnresolvedReason -ItemTarget $itemTarget -EpicTarget $epicTarget -ParallelTarget $parallelTarget
399
421
  $prefix = if ($unresolved) { $unresolved + '; ' } else { '' }
400
- return Get-EpicMergeGateBlockDecision -Reason ('EPIC_MERGE_GATE_BLOCKED: ' + $prefix + $standalone.ReasonCode + ': ' + $standalone.Message + $docsOnlySuffix)
422
+ return Get-EpicMergeGateBlockDecision -Reason ('EPIC_MERGE_GATE_BLOCKED: ' + $prefix + $standalone.ReasonCode + ': ' + $standalone.Message + $docsOnlySuffix + $greenCi.Suffix)
401
423
  }
402
424
 
403
- return Get-EpicMergeGateBlockDecision -Reason 'EPIC_MERGE_GATE_BLOCKED: a pull-request merge or merge-queue enqueue (gh pr merge with any method, --auto, or --admin, or a gh api merge or enqueue mutation) requires either a per-feature checkpoint with epic_mode == true and step9_status == "passed", an epic checkpoint with epic_merge_pr.ci_gate.conclusion == "success" and a matching pr_number, or a parallel-orchestrator checkpoint with route_id == "parallel" whose target item (matched by pr_number) has merge_status == "ci_green". No checkpoint satisfied this gate.'
425
+ return Get-EpicMergeGateBlockDecision -Reason 'EPIC_MERGE_GATE_BLOCKED: a pull-request merge or merge-queue enqueue (gh pr merge with any method, --auto, or --admin, or a gh api merge or enqueue mutation) requires either a per-feature checkpoint with epic_mode == true and step9_status == "passed", an epic checkpoint with epic_merge_pr.ci_gate.conclusion == "success" and a matching pr_number, a parallel-orchestrator checkpoint with route_id == "parallel" whose target item (matched by pr_number) has merge_status == "ci_green", or a standalone per-feature checkpoint (epic_mode absent or false, no parallel marker) with step9_status == "passed", ci_gate.conclusion == "success", pr_gate.pr_number equal to the explicit PR number, and ci_gate.head_sha equal to the live pull-request head. No checkpoint satisfied this gate.'
404
426
  }
405
427
 
406
428
  function Invoke-EpicMergeGateEntryPoint {
@@ -21,6 +21,9 @@
21
21
  for the removal target, and Get-EpicWorktreeGateDenyDiagnostics, which builds the
22
22
  diagnostics clause of the final deny from two read results without reading any
23
23
  file (issue #851).
24
+ - The import guard for WorktreeRunRecordPath.psm1, whose Get-WorktreeRunRecordBaseRoot
25
+ pairs the main checkout root with a read's own root so a relative recorded
26
+ worktree_path is compared through the shared matcher (issue #995).
24
27
 
25
28
  .NOTES
26
29
  PowerShell 7+. Depends on Get-EpicWorktreeGateBlockDecision,
@@ -29,6 +32,8 @@
29
32
  extensions/drm-copilot/resources/claude-customizations/.
30
33
  #>
31
34
 
35
+ # Shared recorded-path matcher and main-checkout-root seam (issue #995).
36
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunRecordPath.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunRecordPath.psm1' -ErrorRecord $_ }
32
37
  try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunResolution.psm1' -ErrorRecord $_ }
33
38
 
34
39
  function Get-EpicWorktreeGateCheckpointContent {
@@ -132,13 +137,15 @@ function Find-EpicWorktreeGateParallelItemRecord {
132
137
  .SYNOPSIS
133
138
  Locate the parallel items[] record whose worktree_path matches the target path.
134
139
  .DESCRIPTION
135
- Pure. Normalizes both paths exactly as Test-ParallelCheckpointAllowsWorktreeRemoval
136
- does (backslash to slash, trailing slash trimmed, PowerShell -eq) and returns the
137
- first matching entry (issue #851).
140
+ Pure. Compares through Test-WorktreeRunRecordedPathMatchesTarget, exactly as
141
+ Test-ParallelCheckpointAllowsWorktreeRemoval does, and returns the first matching
142
+ entry (issues #851, #995).
138
143
  .PARAMETER Checkpoint
139
144
  Parsed parallel-orchestrator checkpoint, or $null when absent/unreadable.
140
145
  .PARAMETER WorktreePath
141
146
  The removal target the command names.
147
+ .PARAMETER BaseRoot
148
+ Optional. The roots a relative recorded worktree_path is joined to.
142
149
  .OUTPUTS
143
150
  System.Object or $null
144
151
  #>
@@ -148,7 +155,9 @@ function Find-EpicWorktreeGateParallelItemRecord {
148
155
  $Checkpoint,
149
156
 
150
157
  [AllowNull()]
151
- [string] $WorktreePath
158
+ [string] $WorktreePath,
159
+
160
+ [AllowNull()][AllowEmptyCollection()][string[]] $BaseRoot = @()
152
161
  )
153
162
 
154
163
  if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
@@ -159,7 +168,7 @@ function Find-EpicWorktreeGateParallelItemRecord {
159
168
  return $null
160
169
  }
161
170
 
162
- $normalizedTarget = ($WorktreePath -replace '\\', '/').TrimEnd('/')
171
+ # Scan the items for the first record the shared matcher accepts for the target.
163
172
  foreach ($item in @($Checkpoint.items)) {
164
173
  if ($null -eq $item) {
165
174
  continue
@@ -168,8 +177,7 @@ function Find-EpicWorktreeGateParallelItemRecord {
168
177
  if ($itemProps -notcontains 'worktree_path') {
169
178
  continue
170
179
  }
171
- $normalizedItemPath = (([string]$item.worktree_path) -replace '\\', '/').TrimEnd('/')
172
- if ($normalizedItemPath -eq $normalizedTarget) {
180
+ if (Test-WorktreeRunRecordedPathMatchesTarget -RecordedPath ([string]$item.worktree_path) -TargetPath $WorktreePath -BaseRoot $BaseRoot) {
173
181
  return $item
174
182
  }
175
183
  }
@@ -191,6 +199,9 @@ function Get-EpicWorktreeGateDenyDiagnostics {
191
199
  The parallel read result (Target, Checkpoint, Path).
192
200
  .PARAMETER WorktreePath
193
201
  The removal target the command names.
202
+ .PARAMETER MainRoot
203
+ Optional. The main checkout root the gate derived; with each read's own root it
204
+ forms the base roots a relative recorded worktree_path is joined to (issue #995).
194
205
  .OUTPUTS
195
206
  System.String
196
207
  #>
@@ -200,7 +211,8 @@ function Get-EpicWorktreeGateDenyDiagnostics {
200
211
  param(
201
212
  [Parameter(Mandatory)] $EpicRead,
202
213
  [Parameter(Mandatory)] $ParallelRead,
203
- [AllowNull()][AllowEmptyString()][string] $WorktreePath
214
+ [AllowNull()][AllowEmptyString()][string] $WorktreePath,
215
+ [AllowNull()][AllowEmptyString()][string] $MainRoot
204
216
  )
205
217
 
206
218
  $parts = foreach ($kind in @('epic', 'parallel')) {
@@ -213,11 +225,12 @@ function Get-EpicWorktreeGateDenyDiagnostics {
213
225
  $recordText = 'checkpoint absent or unparseable'
214
226
  }
215
227
  else {
228
+ $baseRoot = Get-WorktreeRunRecordBaseRoot -MainRoot $MainRoot -Read $read
216
229
  $record = if ($kind -eq 'epic') {
217
- Find-EpicWorktreeFeatureRecord -Checkpoint $read.Checkpoint -WorktreePath $WorktreePath
230
+ Find-EpicWorktreeFeatureRecord -Checkpoint $read.Checkpoint -WorktreePath $WorktreePath -BaseRoot $baseRoot
218
231
  }
219
232
  else {
220
- Find-EpicWorktreeGateParallelItemRecord -Checkpoint $read.Checkpoint -WorktreePath $WorktreePath
233
+ Find-EpicWorktreeGateParallelItemRecord -Checkpoint $read.Checkpoint -WorktreePath $WorktreePath -BaseRoot $baseRoot
221
234
  }
222
235
  $arrayName = if ($kind -eq 'epic') { 'features' } else { 'items' }
223
236
  $recordText = if ($null -eq $record) {
@@ -26,8 +26,9 @@
26
26
  before either checkpoint is read.
27
27
 
28
28
  Both branches key on the worktree PATH because the command names a path, and both
29
- normalize separators and trim a trailing slash on each side of the comparison, so
30
- Windows- and POSIX-style paths compare equal. Path is also why the two branches are
29
+ compare through Test-WorktreeRunRecordedPathMatchesTarget (issue #995), so Windows-
30
+ and POSIX-style paths compare equal and a relative record is joined to the main
31
+ checkout root and the checkpoint's own root. Path is also why the two branches are
31
32
  mutually exclusive in practice: an epic run and a parallel run allocate worktrees
32
33
  under distinct per-run, per-item paths, so a path recorded in one checkpoint is not a
33
34
  path recorded in the other, and adding branch 2 cannot widen what branch 1 authorizes.
@@ -121,6 +122,8 @@ function Find-EpicWorktreeFeatureRecord {
121
122
  Parsed epic checkpoint, or $null when absent/unreadable.
122
123
  .PARAMETER WorktreePath
123
124
  The target worktree path extracted from the command text.
125
+ .PARAMETER BaseRoot
126
+ Optional. The roots a relative recorded worktree_path is joined to (issue #995).
124
127
  .OUTPUTS
125
128
  System.Object or $null
126
129
  #>
@@ -130,7 +133,9 @@ function Find-EpicWorktreeFeatureRecord {
130
133
  $Checkpoint,
131
134
 
132
135
  [AllowNull()]
133
- [string] $WorktreePath
136
+ [string] $WorktreePath,
137
+
138
+ [AllowNull()][AllowEmptyCollection()][string[]] $BaseRoot = @()
134
139
  )
135
140
 
136
141
  if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
@@ -141,17 +146,14 @@ function Find-EpicWorktreeFeatureRecord {
141
146
  return $null
142
147
  }
143
148
 
144
- $normalizedTarget = ($WorktreePath -replace '\\', '/').TrimEnd('/')
145
-
146
- # Scan every recorded feature for a worktree_path that matches the removal target;
147
- # path separators are normalized so Windows- and POSIX-style paths compare equal.
149
+ # Scan every recorded feature for a worktree_path that names the removal target
150
+ # through the shared matcher (separator, trailing slash, case rule, relative join).
148
151
  foreach ($feature in @($Checkpoint.features)) {
149
152
  $featureProps = @($feature.PSObject.Properties.Name)
150
153
  if ($featureProps -notcontains 'worktree_path') {
151
154
  continue
152
155
  }
153
- $normalizedFeaturePath = (([string]$feature.worktree_path) -replace '\\', '/').TrimEnd('/')
154
- if ($normalizedFeaturePath -eq $normalizedTarget) {
156
+ if (Test-WorktreeRunRecordedPathMatchesTarget -RecordedPath ([string]$feature.worktree_path) -TargetPath $WorktreePath -BaseRoot $BaseRoot) {
155
157
  return $feature
156
158
  }
157
159
  }
@@ -198,6 +200,8 @@ function Test-ParallelCheckpointAllowsWorktreeRemoval {
198
200
  Parsed parallel-orchestrator checkpoint, or $null when absent/unreadable.
199
201
  .PARAMETER WorktreePath
200
202
  The target worktree path extracted from the command text.
203
+ .PARAMETER BaseRoot
204
+ Optional. The roots a relative recorded worktree_path is joined to (issue #995).
201
205
  .OUTPUTS
202
206
  System.Boolean
203
207
  #>
@@ -208,7 +212,9 @@ function Test-ParallelCheckpointAllowsWorktreeRemoval {
208
212
  $Checkpoint,
209
213
 
210
214
  [AllowNull()]
211
- [string] $WorktreePath
215
+ [string] $WorktreePath,
216
+
217
+ [AllowNull()][AllowEmptyCollection()][string[]] $BaseRoot = @()
212
218
  )
213
219
 
214
220
  if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
@@ -224,10 +230,8 @@ function Test-ParallelCheckpointAllowsWorktreeRemoval {
224
230
  return $false
225
231
  }
226
232
 
227
- $normalizedTarget = ($WorktreePath -replace '\\', '/').TrimEnd('/')
228
-
229
- # Scan every recorded item for a worktree_path that matches the removal target;
230
- # path separators are normalized exactly as the epic branch normalizes them.
233
+ # Scan every recorded item for a worktree_path that names the removal target through
234
+ # the same shared matcher the epic branch uses.
231
235
  foreach ($item in @($Checkpoint.items)) {
232
236
  if ($null -eq $item) {
233
237
  continue
@@ -236,8 +240,7 @@ function Test-ParallelCheckpointAllowsWorktreeRemoval {
236
240
  if ($itemProps -notcontains 'worktree_path') {
237
241
  continue
238
242
  }
239
- $normalizedItemPath = (([string]$item.worktree_path) -replace '\\', '/').TrimEnd('/')
240
- if ($normalizedItemPath -ne $normalizedTarget) {
243
+ if (-not (Test-WorktreeRunRecordedPathMatchesTarget -RecordedPath ([string]$item.worktree_path) -TargetPath $WorktreePath -BaseRoot $BaseRoot)) {
241
244
  continue
242
245
  }
243
246
  if ($itemProps -notcontains 'merge_status') {
@@ -366,14 +369,19 @@ function Get-EpicWorktreeRemovalTargetDenial {
366
369
  }
367
370
  }
368
371
  $checkpoint = $epicRead.Checkpoint
372
+ # A relative record is joined to the main checkout root and to the root holding the
373
+ # checkpoint read (issue #995); the main root is derived once per target.
374
+ $mainRoot = Get-WorktreeRunMainCheckoutRoot -SessionRoot (Get-Location).Path
375
+ $epicBaseRoot = Get-WorktreeRunRecordBaseRoot -MainRoot $mainRoot -Read $epicRead
376
+ $parallelBaseRoot = Get-WorktreeRunRecordBaseRoot -MainRoot $mainRoot -Read $parallelRead
369
377
 
370
- $featureRecord = Find-EpicWorktreeFeatureRecord -Checkpoint $checkpoint -WorktreePath $worktreePath
378
+ $featureRecord = Find-EpicWorktreeFeatureRecord -Checkpoint $checkpoint -WorktreePath $worktreePath -BaseRoot $epicBaseRoot
371
379
  if (Test-EpicWorktreeRemovalAllowed -FeatureRecord $featureRecord) {
372
380
  return $null
373
381
  }
374
382
 
375
383
  $parallelCheckpoint = $parallelRead.Checkpoint
376
- if (Test-ParallelCheckpointAllowsWorktreeRemoval -Checkpoint $parallelCheckpoint -WorktreePath $worktreePath) {
384
+ if (Test-ParallelCheckpointAllowsWorktreeRemoval -Checkpoint $parallelCheckpoint -WorktreePath $worktreePath -BaseRoot $parallelBaseRoot) {
377
385
  return $null
378
386
  }
379
387
 
@@ -387,8 +395,8 @@ function Get-EpicWorktreeRemovalTargetDenial {
387
395
  # the unchanged deny below. That exclusion is the only path by which the manifest
388
396
  # could widen what the gates protect, which is why it is required rather than
389
397
  # advisory, and why it is evaluated before the manifest predicate.
390
- if (-not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $checkpoint -RecordArrayName 'features' -WorktreePath $worktreePath) -and
391
- -not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $parallelCheckpoint -RecordArrayName 'items' -WorktreePath $worktreePath) -and
398
+ if (-not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $checkpoint -RecordArrayName 'features' -WorktreePath $worktreePath -BaseRoot $epicBaseRoot) -and
399
+ -not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $parallelCheckpoint -RecordArrayName 'items' -WorktreePath $worktreePath -BaseRoot $parallelBaseRoot) -and
392
400
  (Test-CleanupWorktreeManifestAuthorizesRemoval -WorktreePath $worktreePath)) {
393
401
  return $null
394
402
  }
@@ -400,7 +408,7 @@ function Get-EpicWorktreeRemovalTargetDenial {
400
408
  if ($epicRead.Target.Status -eq 'NoTarget' -and $parallelRead.Target.Status -eq 'NoTarget') {
401
409
  $prefix = "$($epicRead.Target.ReasonCode): $($epicRead.Target.Detail). "
402
410
  }
403
- $diagnostics = Get-EpicWorktreeGateDenyDiagnostics -EpicRead $epicRead -ParallelRead $parallelRead -WorktreePath $worktreePath
411
+ $diagnostics = Get-EpicWorktreeGateDenyDiagnostics -EpicRead $epicRead -ParallelRead $parallelRead -WorktreePath $worktreePath -MainRoot $mainRoot
404
412
  return Get-EpicWorktreeGateBlockDecision -Reason ('EPIC_WORKTREE_REMOVAL_BLOCKED: ' + $prefix + "git worktree remove for '$worktreePath' requires either an epic checkpoint features[] record with merge_status in {merged, worktree_removed}, or a parallel-orchestrator checkpoint with route_id == ""parallel"" whose matching items[] record (matched by worktree_path) has merge_status in {merged, worktree_removed}. No checkpoint authorized this removal. " + $diagnostics)
405
413
  }
406
414