@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,391 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Pure target-worktree resolver for PreToolUse hooks (issue #1009).
4
+
5
+ .DESCRIPTION
6
+ Resolves the registered git worktree that a hook call acts on, from the first present
7
+ source in a strict order: (1) file-tool target paths, (2) command operand paths or a
8
+ branch the caller has already parsed, (3) the identity carried by an Agent delegation
9
+ prompt, and (4) the session root as the final fallback. Registered worktrees come only
10
+ from `git worktree list --porcelain` text returned by the injectable
11
+ WorktreeListInvoker seam, and a path resolves to its nearest enclosing registered
12
+ worktree (the longest normalized root prefix). Paths are compared after forward-slash
13
+ normalization with OrdinalIgnoreCase on every platform.
14
+
15
+ Known limitation: an 8.3 short-name path, or a path reached through a junction, that
16
+ differs textually from the long path git reports matches no registered worktree and
17
+ falls back to the session root.
18
+
19
+ The TARGET_WORKTREE_AMBIGUOUS and TARGET_WORKTREE_NOT_DERIVABLE literals are restated
20
+ inside Resolve-HookTargetWorktree rather than read from WorktreeResolution.psm1,
21
+ because this module imports nothing. A test pins both literals equal to the
22
+ WorktreeResolution.psm1 accessors, following the precedent of
23
+ validate-orchestrator-output-resolution.ps1.
24
+
25
+ .NOTES
26
+ Compatible with PowerShell 7+. No module dependency, no file read, no environment
27
+ read, and no clock read; the only process is the default WorktreeListInvoker body.
28
+ Functions reference no module-scope state, so the Codex twin
29
+ .codex/hooks/hook-target-worktree.ps1 carries byte-identical function text.
30
+ Mirrored byte-identically under extensions/drm-copilot/resources/claude-customizations/.
31
+ CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
32
+ #>
33
+
34
+ Set-StrictMode -Version Latest
35
+ $ErrorActionPreference = 'Stop'
36
+
37
+ function ConvertTo-HookTargetWorktreeNormalizedPath {
38
+ <#
39
+ .SYNOPSIS
40
+ Normalize a path to forward slashes with repeated and trailing slashes removed.
41
+ .DESCRIPTION
42
+ Backslashes become forward slashes, runs of slashes collapse to one, and a trailing
43
+ slash is trimmed except from a bare root ('/' or a drive root such as 'C:/'). Blank
44
+ input returns an empty string. Case is preserved; callers compare with
45
+ OrdinalIgnoreCase.
46
+ .PARAMETER Path
47
+ The path to normalize.
48
+ #>
49
+ [CmdletBinding()]
50
+ [OutputType([string])]
51
+ param(
52
+ [AllowNull()] [AllowEmptyString()] [string] $Path
53
+ )
54
+
55
+ if ([string]::IsNullOrWhiteSpace($Path)) { return '' }
56
+ $normalized = ($Path.Trim() -replace '\\', '/') -replace '/{2,}', '/'
57
+ if ($normalized.Length -gt 1 -and $normalized.EndsWith('/') -and $normalized -notmatch '^[A-Za-z]:/$') {
58
+ $normalized = $normalized.Substring(0, $normalized.Length - 1)
59
+ }
60
+ return $normalized
61
+ }
62
+
63
+ function Get-HookTargetWorktreeRelativePath {
64
+ <#
65
+ .SYNOPSIS
66
+ Return a path relative to a worktree root, or $null when it lies outside the root.
67
+ .DESCRIPTION
68
+ Both values are normalized first. A path equal to the root returns an empty string;
69
+ a path under the root returns its forward-slash remainder; any other path, or a
70
+ blank value, returns $null.
71
+ .PARAMETER Path
72
+ The path to express relative to the root.
73
+ .PARAMETER WorktreeRoot
74
+ The worktree root.
75
+ #>
76
+ [CmdletBinding()]
77
+ [OutputType([string])]
78
+ param(
79
+ [AllowNull()] [AllowEmptyString()] [string] $Path,
80
+ [AllowNull()] [AllowEmptyString()] [string] $WorktreeRoot
81
+ )
82
+
83
+ $normalizedPath = ConvertTo-HookTargetWorktreeNormalizedPath -Path $Path
84
+ $normalizedRoot = ConvertTo-HookTargetWorktreeNormalizedPath -Path $WorktreeRoot
85
+ if ([string]::IsNullOrEmpty($normalizedPath) -or [string]::IsNullOrEmpty($normalizedRoot)) { return $null }
86
+ if ([string]::Equals($normalizedPath, $normalizedRoot, [System.StringComparison]::OrdinalIgnoreCase)) { return '' }
87
+ $prefix = if ($normalizedRoot.EndsWith('/')) { $normalizedRoot } else { $normalizedRoot + '/' }
88
+ if ($normalizedPath.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase)) {
89
+ return $normalizedPath.Substring($prefix.Length)
90
+ }
91
+ return $null
92
+ }
93
+
94
+ function Get-HookTargetWorktreeEnclosingRoot {
95
+ <#
96
+ .SYNOPSIS
97
+ Return the nearest root that encloses a path, or $null when none does.
98
+ .DESCRIPTION
99
+ A root encloses a path when the normalized path equals the normalized root or
100
+ starts with the root followed by a slash. When several roots enclose the path, the
101
+ longest one wins, so a worktree nested inside another wins for paths under it.
102
+ .PARAMETER Path
103
+ The path to place.
104
+ .PARAMETER Roots
105
+ The candidate worktree roots.
106
+ #>
107
+ [CmdletBinding()]
108
+ [OutputType([string])]
109
+ param(
110
+ [AllowNull()] [AllowEmptyString()] [string] $Path,
111
+ [AllowNull()] [AllowEmptyCollection()] [string[]] $Roots
112
+ )
113
+
114
+ $best = $null
115
+ foreach ($root in @($Roots)) {
116
+ $normalizedRoot = ConvertTo-HookTargetWorktreeNormalizedPath -Path $root
117
+ if ([string]::IsNullOrEmpty($normalizedRoot)) { continue }
118
+ $relative = Get-HookTargetWorktreeRelativePath -Path $Path -WorktreeRoot $normalizedRoot
119
+ if ($null -ne $relative -and ($null -eq $best -or $normalizedRoot.Length -gt $best.Length)) { $best = $normalizedRoot }
120
+ }
121
+ return $best
122
+ }
123
+
124
+ function Get-HookTargetWorktreeKey {
125
+ <#
126
+ .SYNOPSIS
127
+ Return the stable key '<sanitized leaf>-<8 hex>' that names a worktree root.
128
+ .DESCRIPTION
129
+ The hex part is the first four bytes of the SHA-256 of the normalized root with no
130
+ trailing slash, and the leaf is reduced to file-name-safe characters. The algorithm
131
+ equals the worktree-derived batch-budget session id, which prefixes 'worktree-'.
132
+ .PARAMETER WorktreeRoot
133
+ The worktree root to key.
134
+ #>
135
+ [CmdletBinding()]
136
+ [OutputType([string])]
137
+ param(
138
+ [Parameter(Mandatory)] [string] $WorktreeRoot
139
+ )
140
+
141
+ $normalizedRoot = (ConvertTo-HookTargetWorktreeNormalizedPath -Path $WorktreeRoot).TrimEnd('/')
142
+ $leaf = $normalizedRoot.Substring($normalizedRoot.LastIndexOf('/') + 1) -replace '[^A-Za-z0-9._-]', '_'
143
+ $hashBytes = [System.Security.Cryptography.SHA256]::HashData([System.Text.Encoding]::UTF8.GetBytes($normalizedRoot))
144
+ $shortHash = -join (@($hashBytes[0..3]) | ForEach-Object { $_.ToString('x2') })
145
+ return "$leaf-$shortHash"
146
+ }
147
+
148
+ function ConvertFrom-HookTargetWorktreePorcelain {
149
+ <#
150
+ .SYNOPSIS
151
+ Parse `git worktree list --porcelain` text into worktree records.
152
+ .DESCRIPTION
153
+ Emits one record per non-bare stanza with Root (normalized), Branch (the short
154
+ branch name, or $null when detached), Bare, and Prunable. A bare stanza is skipped.
155
+ Blank lines separate stanzas, and CRLF line endings are tolerated.
156
+ .PARAMETER Text
157
+ The porcelain text.
158
+ #>
159
+ [CmdletBinding()]
160
+ [OutputType([pscustomobject])]
161
+ param(
162
+ [AllowNull()] [AllowEmptyString()] [string] $Text
163
+ )
164
+
165
+ $records = [System.Collections.Generic.List[object]]::new()
166
+ $stanza = $null
167
+ foreach ($rawLine in @(([string] $Text -split "`r?`n") + '')) {
168
+ $line = $rawLine.Trim()
169
+ if ($line -eq '' -or $line.StartsWith('worktree ')) {
170
+ if ($null -ne $stanza -and -not $stanza.Bare) {
171
+ $records.Add([pscustomobject]@{ Root = $stanza.Root; Branch = $stanza.Branch; Bare = $false; Prunable = $stanza.Prunable })
172
+ }
173
+ $stanza = $null
174
+ if ($line -ne '') {
175
+ $stanza = @{ Root = (ConvertTo-HookTargetWorktreeNormalizedPath -Path $line.Substring(9)); Branch = $null; Bare = $false; Prunable = $false }
176
+ }
177
+ continue
178
+ }
179
+ if ($null -eq $stanza) { continue }
180
+ if ($line -eq 'bare') {
181
+ $stanza.Bare = $true
182
+ } elseif ($line.StartsWith('branch ')) {
183
+ $reference = $line.Substring(7).Trim()
184
+ $stanza.Branch = if ($reference.StartsWith('refs/heads/')) { $reference.Substring(11) } else { $reference }
185
+ } elseif ($line -eq 'detached') {
186
+ $stanza.Branch = $null
187
+ } elseif ($line -eq 'prunable' -or $line.StartsWith('prunable ')) {
188
+ $stanza.Prunable = $true
189
+ }
190
+ }
191
+ return $records.ToArray()
192
+ }
193
+
194
+ function Resolve-HookTargetWorktree {
195
+ <#
196
+ .SYNOPSIS
197
+ Resolve the registered worktree that a hook call acts on.
198
+ .DESCRIPTION
199
+ The first present source decides, in the order FilePath, CommandOperand (paths
200
+ and/or branch), DelegationIdentity, SessionRoot; a later source is never consulted.
201
+ Relative paths map to the session root without enumerating worktrees. An absolute
202
+ path maps to its nearest enclosing registered worktree, and a path inside none, or
203
+ an invoker that throws or returns no worktree, falls back to the session root.
204
+ Disagreeing components yield TARGET_WORKTREE_AMBIGUOUS; a present branch or
205
+ identity that names no worktree yields TARGET_WORKTREE_NOT_DERIVABLE and never the
206
+ session root. The result carries WorktreeRoot, Source, Ambiguous, ReasonCode,
207
+ Detail, and Candidates. The invoker runs at most once.
208
+ .PARAMETER SessionRoot
209
+ The absolute session root (the hook's root); the fallback root.
210
+ .PARAMETER FilePath
211
+ File-tool target paths.
212
+ .PARAMETER CommandOperandPath
213
+ Command operand paths parsed by the caller.
214
+ .PARAMETER CommandBranch
215
+ A command branch operand parsed by the caller.
216
+ .PARAMETER DelegationPrompt
217
+ Agent delegation prompt text; its 'branch:' label and canonical issue line are read.
218
+ .PARAMETER CheckpointIssueReader
219
+ Optional { param($WorktreeRoot) <issue number or $null> } used only for an issue identity.
220
+ .PARAMETER WorktreeListInvoker
221
+ { param($SessionRoot) <porcelain text> }; the default runs git worktree list --porcelain.
222
+ #>
223
+ [CmdletBinding()]
224
+ [OutputType([pscustomobject])]
225
+ param(
226
+ [Parameter(Mandatory)] [string] $SessionRoot,
227
+ [AllowNull()] [AllowEmptyCollection()] [string[]] $FilePath,
228
+ [AllowNull()] [AllowEmptyCollection()] [string[]] $CommandOperandPath,
229
+ [AllowNull()] [AllowEmptyString()] [string] $CommandBranch,
230
+ [AllowNull()] [AllowEmptyString()] [string] $DelegationPrompt,
231
+ [scriptblock] $CheckpointIssueReader,
232
+ [scriptblock] $WorktreeListInvoker = { param($SessionRoot) $porcelain = & git -C $SessionRoot worktree list --porcelain 2>$null; if ($LASTEXITCODE -ne 0) { return '' }; return ($porcelain -join "`n") }
233
+ )
234
+
235
+ $ambiguousCode = 'TARGET_WORKTREE_AMBIGUOUS'
236
+ $notDerivableCode = 'TARGET_WORKTREE_NOT_DERIVABLE'
237
+ $ignoreCase = [System.StringComparison]::OrdinalIgnoreCase
238
+ $absolutePattern = '^(/|[A-Za-z]:/)'
239
+ $session = ConvertTo-HookTargetWorktreeNormalizedPath -Path $SessionRoot
240
+
241
+ # Nested helpers are called by constant name and read this function's variables.
242
+ function Get-HookTargetResult {
243
+ param($Root, [string] $Source, $Code, [string] $Detail = '', [string[]] $Candidates = @())
244
+ [pscustomobject]@{
245
+ WorktreeRoot = $Root
246
+ Source = $Source
247
+ Ambiguous = ($Code -eq $ambiguousCode)
248
+ ReasonCode = $Code
249
+ Detail = $Detail
250
+ Candidates = [string[]] @($Candidates)
251
+ }
252
+ }
253
+ function Add-HookTargetDistinctValue {
254
+ param([System.Collections.Generic.List[string]] $List, [string] $Value)
255
+ foreach ($existing in $List) { if ([string]::Equals($existing, $Value, $ignoreCase)) { return } }
256
+ $List.Add($Value)
257
+ }
258
+ function Get-HookTargetRecordedIssue {
259
+ param([string] $Root)
260
+ try { $value = ([string] (& $CheckpointIssueReader $Root)).Trim().TrimStart('#') } catch { return $null }
261
+ if ($value -notmatch '^\d+$') { return $null }
262
+ $digits = $value.TrimStart('0')
263
+ return $(if ($digits -eq '') { '0' } else { $digits })
264
+ }
265
+ function Resolve-HookTargetPathSource {
266
+ param([string[]] $Paths, [string] $Source)
267
+ $resolved = [System.Collections.Generic.List[string]]::new()
268
+ $details = [System.Collections.Generic.List[string]]::new()
269
+ $fellBack = $false
270
+ foreach ($candidate in $Paths) {
271
+ $normalized = ConvertTo-HookTargetWorktreeNormalizedPath -Path $candidate
272
+ $root = $session
273
+ if ($normalized -notmatch $absolutePattern) {
274
+ $fellBack = $true
275
+ $details.Add("relative path '$normalized' maps to the session root")
276
+ } else {
277
+ $enclosing = Get-HookTargetWorktreeEnclosingRoot -Path $normalized -Roots $roots
278
+ if ($null -eq $enclosing) {
279
+ $fellBack = $true
280
+ $details.Add($(if ($listDetail) { $listDetail } else { "path '$normalized' is inside no registered worktree" }))
281
+ } else {
282
+ $root = $enclosing
283
+ }
284
+ }
285
+ Add-HookTargetDistinctValue -List $resolved -Value $root
286
+ }
287
+ if ($resolved.Count -gt 1) {
288
+ return (Get-HookTargetResult -Root $null -Source $Source -Code $ambiguousCode -Detail ('the paths resolve to {0} different worktrees' -f $resolved.Count) -Candidates $resolved.ToArray())
289
+ }
290
+ $decidedBy = if ($fellBack) { 'SessionRoot' } else { $Source }
291
+ return (Get-HookTargetResult -Root $resolved[0] -Source $decidedBy -Code $null -Detail ($details -join '; '))
292
+ }
293
+ function Resolve-HookTargetBranchSource {
294
+ param([string] $Branch, [string] $Source)
295
+ $matched = @($records | Where-Object { $null -ne $_.Branch -and [string]::Equals($_.Branch, $Branch, $ignoreCase) } | ForEach-Object { $_.Root })
296
+ if ($matched.Count -eq 0) { return (Get-HookTargetResult -Root $null -Source $Source -Code $notDerivableCode -Detail "branch '$Branch' is checked out in no registered worktree") }
297
+ if ($matched.Count -gt 1) { return (Get-HookTargetResult -Root $null -Source $Source -Code $ambiguousCode -Detail "branch '$Branch' is checked out in $($matched.Count) registered worktrees" -Candidates $matched) }
298
+ return (Get-HookTargetResult -Root $matched[0] -Source $Source -Code $null)
299
+ }
300
+
301
+ # Select the deciding source: the first present one in precedence order.
302
+ $filePaths = @($FilePath | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
303
+ $operandPaths = @($CommandOperandPath | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
304
+ $branchOperand = if ([string]::IsNullOrWhiteSpace($CommandBranch)) { '' } else { $CommandBranch.Trim() }
305
+ $branches = [System.Collections.Generic.List[string]]::new()
306
+ $issues = [System.Collections.Generic.List[string]]::new()
307
+ if ($filePaths.Count -gt 0) {
308
+ $source = 'FilePath'
309
+ } elseif ($operandPaths.Count -gt 0 -or $branchOperand) {
310
+ $source = 'CommandOperand'
311
+ } elseif (-not [string]::IsNullOrWhiteSpace($DelegationPrompt)) {
312
+ $source = 'DelegationIdentity'
313
+ foreach ($match in [regex]::Matches($DelegationPrompt, '(?i)\bbranch:\s*(?<name>[A-Za-z0-9][A-Za-z0-9._/-]*)')) {
314
+ Add-HookTargetDistinctValue -List $branches -Value $match.Groups['name'].Value.TrimEnd('.', '/')
315
+ }
316
+ foreach ($match in [regex]::Matches($DelegationPrompt, 'Canonical issue number for this feature is #?(?<number>\d+)')) {
317
+ $digits = $match.Groups['number'].Value.TrimStart('0')
318
+ Add-HookTargetDistinctValue -List $issues -Value $(if ($digits -eq '') { '0' } else { $digits })
319
+ }
320
+ } else {
321
+ return (Get-HookTargetResult -Root $session -Source 'SessionRoot' -Code $null -Detail 'no target source is present; the session root applies')
322
+ }
323
+
324
+ # Enumerate registered worktrees at most once, and only when the deciding source needs them.
325
+ $needsList = switch ($source) {
326
+ 'FilePath' { @($filePaths | Where-Object { (ConvertTo-HookTargetWorktreeNormalizedPath -Path $_) -match $absolutePattern }).Count -gt 0 }
327
+ 'CommandOperand' { [bool] $branchOperand -or @($operandPaths | Where-Object { (ConvertTo-HookTargetWorktreeNormalizedPath -Path $_) -match $absolutePattern }).Count -gt 0 }
328
+ default { ($branches.Count -eq 1 -and $issues.Count -le 1) -or ($branches.Count -eq 0 -and $issues.Count -eq 1 -and $null -ne $CheckpointIssueReader) }
329
+ }
330
+ $records = @()
331
+ $listDetail = ''
332
+ if ($needsList) {
333
+ try {
334
+ $records = @(ConvertFrom-HookTargetWorktreePorcelain -Text (@(& $WorktreeListInvoker $SessionRoot) -join "`n"))
335
+ if ($records.Count -eq 0) { $listDetail = 'the worktree list returned no registered worktree' }
336
+ } catch {
337
+ $records = @()
338
+ $listDetail = "the worktree list failed: $($_.Exception.Message)"
339
+ }
340
+ }
341
+ $roots = @($records | ForEach-Object { $_.Root })
342
+
343
+ if ($source -eq 'FilePath') { return (Resolve-HookTargetPathSource -Paths $filePaths -Source 'FilePath') }
344
+
345
+ if ($source -eq 'CommandOperand') {
346
+ $pathResult = if ($operandPaths.Count -gt 0) { Resolve-HookTargetPathSource -Paths $operandPaths -Source 'CommandOperand' } else { $null }
347
+ if (-not $branchOperand) { return $pathResult }
348
+ $branchResult = Resolve-HookTargetBranchSource -Branch $branchOperand -Source 'CommandOperand'
349
+ if ($null -eq $pathResult) { return $branchResult }
350
+ if ($pathResult.Ambiguous) { return $pathResult }
351
+ if ($null -ne $branchResult.ReasonCode) { return $branchResult }
352
+ if (-not [string]::Equals($pathResult.WorktreeRoot, $branchResult.WorktreeRoot, $ignoreCase)) {
353
+ return (Get-HookTargetResult -Root $null -Source 'CommandOperand' -Code $ambiguousCode -Detail "the operand paths and branch '$branchOperand' resolve to different worktrees" -Candidates @($pathResult.WorktreeRoot, $branchResult.WorktreeRoot))
354
+ }
355
+ return $branchResult
356
+ }
357
+
358
+ if ($branches.Count -gt 1) { return (Get-HookTargetResult -Root $null -Source $source -Code $ambiguousCode -Detail ('the prompt names {0} different branches: {1}' -f $branches.Count, ($branches -join ', '))) }
359
+ if ($issues.Count -gt 1) { return (Get-HookTargetResult -Root $null -Source $source -Code $ambiguousCode -Detail ('the prompt names {0} different issue numbers: {1}' -f $issues.Count, ($issues -join ', '))) }
360
+ if ($branches.Count -eq 1) {
361
+ $branchResult = Resolve-HookTargetBranchSource -Branch $branches[0] -Source $source
362
+ if ($null -ne $branchResult.ReasonCode -or $issues.Count -eq 0 -or $null -eq $CheckpointIssueReader) { return $branchResult }
363
+ $target = $branchResult.WorktreeRoot
364
+ $recorded = Get-HookTargetRecordedIssue -Root $target
365
+ if ($null -ne $recorded) {
366
+ if ($recorded -eq $issues[0]) { return $branchResult }
367
+ return (Get-HookTargetResult -Root $null -Source $source -Code $ambiguousCode -Detail "branch '$($branches[0])' places the call in a worktree whose checkpoint records issue $recorded, not issue $($issues[0])" -Candidates @($target))
368
+ }
369
+ $elsewhere = @($records | Where-Object { -not [string]::Equals($_.Root, $target, $ignoreCase) -and (Get-HookTargetRecordedIssue -Root $_.Root) -eq $issues[0] } | ForEach-Object { $_.Root })
370
+ if ($elsewhere.Count -gt 0) {
371
+ return (Get-HookTargetResult -Root $null -Source $source -Code $ambiguousCode -Detail "branch '$($branches[0])' and issue $($issues[0]) place the call in different worktrees" -Candidates (@($target) + $elsewhere))
372
+ }
373
+ return $branchResult
374
+ }
375
+ if ($issues.Count -eq 1) {
376
+ if ($null -eq $CheckpointIssueReader) { return (Get-HookTargetResult -Root $null -Source $source -Code $notDerivableCode -Detail "issue $($issues[0]) cannot be placed without a checkpoint issue reader") }
377
+ $matched = @($records | Where-Object { (Get-HookTargetRecordedIssue -Root $_.Root) -eq $issues[0] } | ForEach-Object { $_.Root })
378
+ if ($matched.Count -eq 0) { return (Get-HookTargetResult -Root $null -Source $source -Code $notDerivableCode -Detail "issue $($issues[0]) is recorded by no registered worktree") }
379
+ if ($matched.Count -gt 1) { return (Get-HookTargetResult -Root $null -Source $source -Code $ambiguousCode -Detail "issue $($issues[0]) is recorded by $($matched.Count) registered worktrees" -Candidates $matched) }
380
+ return (Get-HookTargetResult -Root $matched[0] -Source $source -Code $null)
381
+ }
382
+ return (Get-HookTargetResult -Root $null -Source $source -Code $notDerivableCode -Detail 'the prompt carries no branch: label and no canonical issue line')
383
+ }
384
+
385
+ Export-ModuleMember -Function `
386
+ Resolve-HookTargetWorktree, `
387
+ ConvertFrom-HookTargetWorktreePorcelain, `
388
+ Get-HookTargetWorktreeEnclosingRoot, `
389
+ ConvertTo-HookTargetWorktreeNormalizedPath, `
390
+ Get-HookTargetWorktreeRelativePath, `
391
+ Get-HookTargetWorktreeKey
@@ -0,0 +1,185 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Shared comparison of a recorded worktree_path with a removal target (issue #995).
4
+
5
+ .DESCRIPTION
6
+ A run checkpoint records items[].worktree_path or features[].worktree_path either as
7
+ an absolute path or relative to the main checkout. Every site that compares such a
8
+ record to the absolute path a command names calls Test-WorktreeRunRecordedPathMatchesTarget,
9
+ so a relative record is joined to the same base roots everywhere and no site carries
10
+ its own normalise-and-compare.
11
+
12
+ A rooted record is compared to the target with Test-WorktreeRunPathEqual. A relative
13
+ record is joined to each non-blank base root and compared the same way. A relative
14
+ record that is blank, is . or .., has a . or .. segment, or contains : matches
15
+ nothing, so it can never name the main checkout or a path outside it.
16
+
17
+ .NOTES
18
+ PowerShell 7+. Exports Test-WorktreeRunPathEqual, Test-WorktreeRunRecordedPathMatchesTarget,
19
+ Get-WorktreeRunRecordBaseRoot, and Get-WorktreeRunMainCheckoutRoot. The two comparison
20
+ functions and Get-WorktreeRunRecordBaseRoot are pure. Get-WorktreeRunMainCheckoutRoot
21
+ reads the filesystem only through its siblings' seams (Get-WorktreeItemLiveRoot and
22
+ Get-WorktreeResolutionGitEntryKind). Mirrored byte-identically.
23
+ CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
24
+ #>
25
+
26
+ Set-StrictMode -Version Latest
27
+ $ErrorActionPreference = 'Stop'
28
+
29
+ Import-Module (Join-Path $PSScriptRoot 'WorktreeResolution.psm1') -ErrorAction Stop
30
+ Import-Module (Join-Path $PSScriptRoot 'WorktreeItemResolution.psm1') -ErrorAction Stop
31
+
32
+ # A drive-rooted, slash-rooted, or // (UNC) path; anything else is relative to some directory.
33
+ # The same pattern as $script:AbsolutePathPattern in WorktreeRunResolution.psm1.
34
+ $script:AbsolutePathPattern = '^([A-Za-z]:(/|$)|/)'
35
+
36
+ # The administrative entry that is a directory only in the main checkout; a linked
37
+ # worktree carries a .git file instead.
38
+ $script:GitEntryName = '.git'
39
+
40
+ # Private (moved from WorktreeRunResolution.psm1 for issue #995): compare two worktree
41
+ # paths, insensitive to separator style and trailing slashes, and case-insensitive for
42
+ # drive-letter and UNC (//) paths.
43
+ function Test-WorktreeRunPathEqual {
44
+ <#
45
+ .SYNOPSIS
46
+ Compare two worktree paths for equality.
47
+ .DESCRIPTION
48
+ Pure. Insensitive to separator style and trailing slashes; ordinal for single-slash
49
+ POSIX roots and ordinal-ignore-case for drive-letter and UNC (//) roots. A blank
50
+ side never matches.
51
+ .PARAMETER Left
52
+ One path.
53
+ .PARAMETER Right
54
+ The other path.
55
+ #>
56
+ [CmdletBinding()]
57
+ [OutputType([bool])]
58
+ param(
59
+ [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Left,
60
+ [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Right
61
+ )
62
+
63
+ if ([string]::IsNullOrWhiteSpace($Left) -or [string]::IsNullOrWhiteSpace($Right)) { return $false }
64
+ $a = ($Left.Trim() -replace '\\', '/').TrimEnd('/')
65
+ $b = ($Right.Trim() -replace '\\', '/').TrimEnd('/')
66
+ $isCaseInsensitivePath = ($a -match '^([A-Za-z]:|//)') -or ($b -match '^([A-Za-z]:|//)')
67
+ $comparison = if ($isCaseInsensitivePath) { [System.StringComparison]::OrdinalIgnoreCase } else { [System.StringComparison]::Ordinal }
68
+ return [string]::Equals($a, $b, $comparison)
69
+ }
70
+
71
+ # Private: whether a relative recorded path (already slash-normalised) is safe to join.
72
+ function Test-WorktreeRunRelativeRecordSafe {
73
+ [CmdletBinding()]
74
+ [OutputType([bool])]
75
+ param(
76
+ [Parameter(Mandatory = $true)] [AllowEmptyString()] [string] $Path
77
+ )
78
+
79
+ if ([string]::IsNullOrWhiteSpace($Path) -or $Path.Contains(':')) { return $false }
80
+ foreach ($segment in $Path.TrimEnd('/').Split('/')) {
81
+ if ($segment -eq '.' -or $segment -eq '..') { return $false }
82
+ }
83
+ return ($Path.TrimEnd('/').Length -gt 0)
84
+ }
85
+
86
+ function Test-WorktreeRunRecordedPathMatchesTarget {
87
+ <#
88
+ .SYNOPSIS
89
+ Report whether a recorded worktree_path names the removal target.
90
+ .DESCRIPTION
91
+ Pure. A rooted record (drive-letter, single-slash, or // after backslash-to-slash
92
+ normalisation) matches when Test-WorktreeRunPathEqual holds against the target. A
93
+ relative record matches when it is safe and Test-WorktreeRunPathEqual holds for
94
+ its join to at least one non-blank base root. A relative record that is blank, is
95
+ . or .., has a . or .. segment, or contains : matches nothing, as does a blank target.
96
+ .PARAMETER RecordedPath
97
+ The worktree_path a checkpoint records, absolute or relative.
98
+ .PARAMETER TargetPath
99
+ The absolute path the command names.
100
+ .PARAMETER BaseRoot
101
+ Optional. The roots a relative record is joined to: the main checkout root and the
102
+ root that holds the checkpoint. With none, a relative record matches nothing.
103
+ #>
104
+ [CmdletBinding()]
105
+ [OutputType([bool])]
106
+ param(
107
+ [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $RecordedPath,
108
+ [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $TargetPath,
109
+ [AllowNull()] [AllowEmptyCollection()] [string[]] $BaseRoot = @()
110
+ )
111
+
112
+ if ([string]::IsNullOrWhiteSpace($RecordedPath) -or [string]::IsNullOrWhiteSpace($TargetPath)) { return $false }
113
+ $recorded = $RecordedPath.Trim() -replace '\\', '/'
114
+ if ($recorded -match $script:AbsolutePathPattern) {
115
+ return (Test-WorktreeRunPathEqual -Left $recorded -Right $TargetPath)
116
+ }
117
+ if (-not (Test-WorktreeRunRelativeRecordSafe -Path $recorded)) { return $false }
118
+ foreach ($root in @($BaseRoot)) {
119
+ if ([string]::IsNullOrWhiteSpace($root)) { continue }
120
+ $joined = (($root.Trim() -replace '\\', '/').TrimEnd('/')) + '/' + $recorded.TrimEnd('/')
121
+ if (Test-WorktreeRunPathEqual -Left $joined -Right $TargetPath) { return $true }
122
+ }
123
+ return $false
124
+ }
125
+
126
+ function Get-WorktreeRunRecordBaseRoot {
127
+ <#
128
+ .SYNOPSIS
129
+ Return the base roots a relative recorded worktree_path is joined to.
130
+ .DESCRIPTION
131
+ Pure. The main checkout root and the root that holds the checkpoint a read
132
+ resolved (Read.Target.WorktreeRoot), with blank entries dropped. Every gate
133
+ passes the result to Test-WorktreeRunRecordedPathMatchesTarget as -BaseRoot.
134
+ .PARAMETER MainRoot
135
+ The main checkout root, or $null when none was derived.
136
+ .PARAMETER Read
137
+ A run-checkpoint read result carrying Target.WorktreeRoot, or $null.
138
+ .OUTPUTS
139
+ System.String[]
140
+ #>
141
+ [CmdletBinding()]
142
+ [OutputType([string[]], [object[]])]
143
+ param(
144
+ [AllowNull()][AllowEmptyString()][string] $MainRoot,
145
+ [AllowNull()] $Read
146
+ )
147
+
148
+ $readRoot = $null
149
+ if ($null -ne $Read -and @($Read.PSObject.Properties.Name) -contains 'Target' -and $null -ne $Read.Target -and @($Read.Target.PSObject.Properties.Name) -contains 'WorktreeRoot') {
150
+ $readRoot = [string] $Read.Target.WorktreeRoot
151
+ }
152
+ return , [string[]] @(@($MainRoot, $readRoot) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
153
+ }
154
+
155
+ function Get-WorktreeRunMainCheckoutRoot {
156
+ <#
157
+ .SYNOPSIS
158
+ Return the main checkout root reachable from a session path, or $null.
159
+ .DESCRIPTION
160
+ The first live root whose .git entry is a directory; a linked worktree's .git entry
161
+ is a file. Reads the filesystem only through Get-WorktreeItemLiveRoot and
162
+ Get-WorktreeResolutionGitEntryKind, the seams a test mocks.
163
+ .PARAMETER SessionRoot
164
+ The calling process's path, used only to find the repository.
165
+ #>
166
+ [CmdletBinding()]
167
+ [OutputType([string])]
168
+ param(
169
+ [Parameter(Mandatory = $true)] [AllowEmptyString()] [string] $SessionRoot
170
+ )
171
+
172
+ $liveRoots = Get-WorktreeItemLiveRoot -SessionRoot $SessionRoot
173
+ foreach ($root in $liveRoots) {
174
+ if ([string]::IsNullOrWhiteSpace($root)) { continue }
175
+ $gitEntry = (($root -replace '\\', '/').TrimEnd('/')) + '/' + $script:GitEntryName
176
+ if ((Get-WorktreeResolutionGitEntryKind -Path $gitEntry) -eq 'Directory') { return $root }
177
+ }
178
+ return $null
179
+ }
180
+
181
+ Export-ModuleMember -Function `
182
+ Test-WorktreeRunPathEqual, `
183
+ Test-WorktreeRunRecordedPathMatchesTarget, `
184
+ Get-WorktreeRunRecordBaseRoot, `
185
+ Get-WorktreeRunMainCheckoutRoot
@@ -31,6 +31,7 @@ $ErrorActionPreference = 'Stop'
31
31
  Import-Module (Join-Path $PSScriptRoot 'WorktreeResolution.psm1') -ErrorAction Stop
32
32
  Import-Module (Join-Path $PSScriptRoot 'WorktreeTargetResolution.psm1') -ErrorAction Stop
33
33
  Import-Module (Join-Path $PSScriptRoot 'WorktreeItemResolution.psm1') -ErrorAction Stop
34
+ Import-Module (Join-Path $PSScriptRoot 'WorktreeRunRecordPath.psm1') -ErrorAction Stop
34
35
 
35
36
  # The run checkpoint locations relative to a worktree root.
36
37
  $script:EpicCheckpointRelativePath = 'artifacts/orchestration/epic-orchestrator-state.json'
@@ -339,24 +340,6 @@ function Resolve-WorktreeParallelTarget {
339
340
  return (Resolve-WorktreeRunMatch -Matched $matched.ToArray() -SessionRoot $SessionRoot -Description ("parallel_slug '{0}'" -f $ParallelSlug))
340
341
  }
341
342
 
342
- # Private: compare two worktree paths, insensitive to separator style and trailing
343
- # slashes, and case-insensitive for drive-letter and UNC (//) paths.
344
- function Test-WorktreeRunPathEqual {
345
- [CmdletBinding()]
346
- [OutputType([bool])]
347
- param(
348
- [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Left,
349
- [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Right
350
- )
351
-
352
- if ([string]::IsNullOrWhiteSpace($Left) -or [string]::IsNullOrWhiteSpace($Right)) { return $false }
353
- $a = ($Left.Trim() -replace '\\', '/').TrimEnd('/')
354
- $b = ($Right.Trim() -replace '\\', '/').TrimEnd('/')
355
- $isCaseInsensitivePath = ($a -match '^([A-Za-z]:|//)') -or ($b -match '^([A-Za-z]:|//)')
356
- $comparison = if ($isCaseInsensitivePath) { [System.StringComparison]::OrdinalIgnoreCase } else { [System.StringComparison]::Ordinal }
357
- return [string]::Equals($a, $b, $comparison)
358
- }
359
-
360
343
  # Private: compare two pull request numbers as integers.
361
344
  function Test-WorktreeRunPrNumberEqual {
362
345
  [CmdletBinding()]
@@ -373,6 +356,8 @@ function Test-WorktreeRunPrNumberEqual {
373
356
  }
374
357
 
375
358
  # Private: whether a parsed run checkpoint records a pull request number or worktree path.
359
+ # A worktree_path record is compared through the shared matcher, so a relative record is
360
+ # joined to each BaseRoot (issue #995); with no BaseRoot a relative record matches nothing.
376
361
  function Test-WorktreeRunCheckpointRecord {
377
362
  [CmdletBinding()]
378
363
  [OutputType([bool])]
@@ -380,7 +365,8 @@ function Test-WorktreeRunCheckpointRecord {
380
365
  [Parameter(Mandatory = $true)] [pscustomobject] $Checkpoint,
381
366
  [Parameter(Mandatory = $true)] [ValidateSet('epic', 'parallel')] [string] $Kind,
382
367
  [Parameter(Mandatory = $true)] [ValidateSet('pr_number', 'worktree_path')] [string] $RecordField,
383
- [Parameter(Mandatory = $true)] [string] $Value
368
+ [Parameter(Mandatory = $true)] [string] $Value,
369
+ [AllowNull()] [AllowEmptyCollection()] [string[]] $BaseRoot = @()
384
370
  )
385
371
 
386
372
  $values = [System.Collections.Generic.List[object]]::new()
@@ -397,7 +383,7 @@ function Test-WorktreeRunCheckpointRecord {
397
383
  if ($RecordField -eq 'pr_number') {
398
384
  if (Test-WorktreeRunPrNumberEqual -Recorded $recorded -Expected ([long] $Value)) { return $true }
399
385
  }
400
- elseif (Test-WorktreeRunPathEqual -Left ([string] $recorded) -Right $Value) { return $true }
386
+ elseif (Test-WorktreeRunRecordedPathMatchesTarget -RecordedPath ([string] $recorded) -TargetPath $Value -BaseRoot $BaseRoot) { return $true }
401
387
  }
402
388
  return $false
403
389
  }
@@ -438,11 +424,15 @@ function Resolve-WorktreeRunTargetByRecord {
438
424
  'the command names no usable {0}, so the {1} run it belongs to cannot be identified' -f $RecordField, $Kind))
439
425
  }
440
426
  $needle = $Value.Trim()
427
+ # A relative worktree_path record is joined to the main checkout root and to the root
428
+ # holding the checkpoint (issue #995); the main root is derived once, and only for paths.
429
+ $mainRoot = if ($RecordField -eq 'worktree_path') { Get-WorktreeRunMainCheckoutRoot -SessionRoot $SessionRoot } else { $null }
441
430
  $matched = [System.Collections.Generic.List[string]]::new()
442
431
  foreach ($root in (Get-WorktreeRunLiveRoot -SessionRoot $SessionRoot)) {
443
432
  $checkpoint = Get-WorktreeRunCheckpoint -Kind $Kind -WorktreeRoot $root
444
433
  if ($null -eq $checkpoint) { continue }
445
- if (Test-WorktreeRunCheckpointRecord -Checkpoint $checkpoint -Kind $Kind -RecordField $RecordField -Value $needle) { $matched.Add($root) }
434
+ $baseRoot = [string[]] @(@($mainRoot, $root) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
435
+ if (Test-WorktreeRunCheckpointRecord -Checkpoint $checkpoint -Kind $Kind -RecordField $RecordField -Value $needle -BaseRoot $baseRoot) { $matched.Add($root) }
446
436
  }
447
437
  return (Resolve-WorktreeRunMatch -Matched $matched.ToArray() -SessionRoot $SessionRoot -Description $description)
448
438
  }
@@ -46,7 +46,10 @@ Mutation testing and golden tests run in pre-merge or nightly pipelines, not the
46
46
 
47
47
  ## File Size Limit
48
48
 
49
- - No production code, test code, or reusable script file may exceed **500 lines**.
49
+ - File size follows three tiers for production code, test code, and reusable script files, counted in logical lines (a trailing line terminator does not add a line):
50
+ - **500 lines (guidance):** look for a logical way to divide the file into multiple files, provided the parts can be separated logically. No gate fails at this tier.
51
+ - **700 lines (advisory):** look for a split more aggressively. Reviewers and `feature-review` report a non-blocking Warning for a file at or above 700 lines. No gate fails at this tier.
52
+ - **Above 800 lines (hard block):** split the file regardless of logical separability. The repository CI check and the PreToolUse hook block a file of 801 lines or more; a file of exactly 800 lines passes. Legacy files above 800 lines are listed in `config/file-size-baseline.json` and may shrink (with the entry tightened) or stay the same size, but may not grow.
50
53
  - Exceptions: temporary throwaway scripts created and deleted within an agent session; raw text fixtures for language-processing test data; Markdown documentation files.
51
54
 
52
55
  ## Error Handling and Logging