@danmoisan/drm-copilot-mcp 1.1.23 → 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 (199) hide show
  1. package/out/mcp-server.js +13347 -7926
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/discovery-capability-sentinel.md +34 -0
  4. package/resources/claude-customizations/.claude/agents/legacy-parity-analyst.md +101 -43
  5. package/resources/claude-customizations/.claude/agents/migration-coverage-reviewer.md +107 -43
  6. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +14 -0
  7. package/resources/claude-customizations/.claude/agents/parallel-planner.md +21 -1
  8. package/resources/claude-customizations/.claude/agents/pr-author.md +17 -1
  9. package/resources/claude-customizations/.claude/agents/requirements-reconciler.md +117 -42
  10. package/resources/claude-customizations/.claude/agents/runtime-characterization-analyst.md +103 -43
  11. package/resources/claude-customizations/.claude/hooks/discovery-capability-probe.ps1 +102 -0
  12. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +69 -194
  13. package/resources/claude-customizations/.claude/hooks/enforce-discovery-planning-gate.ps1 +87 -0
  14. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +7 -1
  15. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +6 -3
  16. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +168 -48
  17. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +1 -1
  18. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +59 -20
  19. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +44 -3
  20. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +60 -4
  21. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +4 -2
  22. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +59 -20
  23. package/resources/claude-customizations/.claude/hooks/record-discovery-agent-start.ps1 +132 -0
  24. package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +56 -212
  25. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-rejection.ps1 +172 -0
  26. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +9 -1
  27. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +9 -3
  28. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusAggregate.psm1 +154 -0
  29. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusCombination.psm1 +228 -0
  30. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +1 -0
  31. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +4 -2
  32. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +63 -16
  33. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +56 -1
  34. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +4 -1
  35. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +5 -1
  36. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
  37. package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +25 -2
  38. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
  39. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
  40. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
  41. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
  42. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
  43. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
  44. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
  45. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
  46. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
  47. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
  48. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
  49. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
  50. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
  51. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
  52. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +188 -341
  53. package/resources/claude-customizations/.claude/lib/worktree-resolution/HookTargetWorktree.psm1 +391 -0
  54. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +1 -0
  55. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +150 -18
  56. package/resources/claude-customizations/.claude/settings.json +32 -3
  57. package/resources/claude-customizations/.claude/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  58. package/resources/claude-customizations/.claude/skills/characterize-runtime-behavior/SKILL.md +134 -0
  59. package/resources/claude-customizations/.claude/skills/discovery-behavior-reconciliation/SKILL.md +28 -47
  60. package/resources/claude-customizations/.claude/skills/discovery-coverage-ledger/SKILL.md +32 -48
  61. package/resources/claude-customizations/.claude/skills/discovery-parity-matrix/SKILL.md +28 -45
  62. package/resources/claude-customizations/.claude/skills/discovery-repo-inventory/SKILL.md +28 -62
  63. package/resources/claude-customizations/.claude/skills/discovery-runtime-characterization/SKILL.md +28 -45
  64. package/resources/claude-customizations/.claude/skills/discovery-validate-artifacts/SKILL.md +28 -61
  65. package/resources/claude-customizations/.claude/skills/discovery-workflow/SKILL.md +55 -139
  66. package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +24 -9
  67. package/resources/claude-customizations/.claude/skills/extract-feature-contract/SKILL.md +133 -0
  68. package/resources/claude-customizations/.claude/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  69. package/resources/claude-customizations/.claude/skills/initialize-discovery-workspace/SKILL.md +131 -0
  70. package/resources/claude-customizations/.claude/skills/inventory-legacy-system/SKILL.md +131 -0
  71. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +5 -4
  72. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +21 -1
  73. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +11 -1
  74. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +34 -0
  75. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +69 -6
  76. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +15 -0
  77. package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +6 -0
  78. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +21 -4
  79. package/resources/claude-customizations/.claude/skills/reconcile-parity-matrix/SKILL.md +131 -0
  80. package/resources/claude-customizations/.claude/skills/review-discovery-coverage/SKILL.md +129 -0
  81. package/resources/claude-customizations/.claude/skills/validate-discovery-artifacts/SKILL.md +127 -0
  82. package/resources/claude-customizations/config/blast-radius.json +6 -5
  83. package/resources/claude-customizations/config/orchestration-routing.json +61 -1
  84. package/resources/claude-customizations/pack-manifests/core.json +33 -1
  85. package/resources/codex-and-agents-customizations/.agents/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  86. package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +7 -0
  87. package/resources/codex-and-agents-customizations/.agents/skills/characterize-runtime-behavior/SKILL.md +134 -0
  88. package/resources/codex-and-agents-customizations/.agents/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
  89. package/resources/codex-and-agents-customizations/.agents/skills/discovery-coverage-ledger/SKILL.md +50 -0
  90. package/resources/codex-and-agents-customizations/.agents/skills/discovery-parity-matrix/SKILL.md +46 -0
  91. package/resources/codex-and-agents-customizations/.agents/skills/discovery-repo-inventory/SKILL.md +46 -0
  92. package/resources/codex-and-agents-customizations/.agents/skills/discovery-runtime-characterization/SKILL.md +46 -0
  93. package/resources/codex-and-agents-customizations/.agents/skills/discovery-validate-artifacts/SKILL.md +46 -0
  94. package/resources/codex-and-agents-customizations/.agents/skills/discovery-workflow/SKILL.md +62 -0
  95. package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +24 -9
  96. package/resources/codex-and-agents-customizations/.agents/skills/extract-feature-contract/SKILL.md +133 -0
  97. package/resources/codex-and-agents-customizations/.agents/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  98. package/resources/codex-and-agents-customizations/.agents/skills/initialize-discovery-workspace/SKILL.md +131 -0
  99. package/resources/codex-and-agents-customizations/.agents/skills/inventory-legacy-system/SKILL.md +131 -0
  100. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +8 -0
  101. package/resources/codex-and-agents-customizations/.agents/skills/reconcile-parity-matrix/SKILL.md +131 -0
  102. package/resources/codex-and-agents-customizations/.agents/skills/review-discovery-coverage/SKILL.md +129 -0
  103. package/resources/codex-and-agents-customizations/.agents/skills/validate-discovery-artifacts/SKILL.md +127 -0
  104. package/resources/codex-and-agents-customizations/.codex/agents/discovery-capability-sentinel.toml +51 -0
  105. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c1.toml +148 -0
  106. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c2.toml +148 -0
  107. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3-elevated.toml +148 -0
  108. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3.toml +148 -0
  109. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c4.toml +148 -0
  110. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst.toml +148 -0
  111. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c1.toml +154 -0
  112. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c2.toml +154 -0
  113. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3-elevated.toml +154 -0
  114. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3.toml +154 -0
  115. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c4.toml +154 -0
  116. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer.toml +154 -0
  117. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c1.toml +164 -0
  118. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c2.toml +164 -0
  119. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3-elevated.toml +164 -0
  120. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3.toml +164 -0
  121. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c4.toml +164 -0
  122. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler.toml +164 -0
  123. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c1.toml +150 -0
  124. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c2.toml +150 -0
  125. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3-elevated.toml +150 -0
  126. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3.toml +150 -0
  127. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c4.toml +150 -0
  128. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst.toml +150 -0
  129. package/resources/codex-and-agents-customizations/.codex/config.toml +33 -3
  130. package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +22 -0
  131. package/resources/codex-and-agents-customizations/.codex/hooks/discovery-capability-probe.ps1 +97 -0
  132. package/resources/codex-and-agents-customizations/.codex/hooks/discovery-start-attestation.ps1 +171 -0
  133. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +4 -1
  134. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-artifact-gate.ps1 +187 -0
  135. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-planning-gate.ps1 +149 -0
  136. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +239 -2
  137. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -15
  138. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +54 -15
  139. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +54 -15
  140. package/resources/codex-and-agents-customizations/.codex/hooks/hook-target-worktree.ps1 +369 -0
  141. package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +8 -0
  142. package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +22 -1
  143. package/resources/codex-and-agents-customizations/.codex/hooks/validate-discovery-agent-output.ps1 +178 -0
  144. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +11 -1
  145. package/resources/codex-and-agents-customizations/pack-manifests/core.json +63 -1
  146. package/resources/config/orchestration-routing.json +61 -1
  147. package/resources/customizations/.github/agents/discovery-capability-sentinel.agent.md +34 -0
  148. package/resources/customizations/.github/agents/legacy-parity-analyst.agent.md +122 -0
  149. package/resources/customizations/.github/agents/migration-coverage-reviewer.agent.md +128 -0
  150. package/resources/customizations/.github/agents/requirements-reconciler.agent.md +138 -0
  151. package/resources/customizations/.github/agents/runtime-characterization-analyst.agent.md +124 -0
  152. package/resources/customizations/.github/hooks/discovery-agent-start.ps1 +139 -0
  153. package/resources/customizations/.github/hooks/discovery-agent-stop.ps1 +123 -0
  154. package/resources/customizations/.github/hooks/discovery-capability-probe.ps1 +98 -0
  155. package/resources/customizations/.github/hooks/discovery-planning.ps1 +91 -0
  156. package/resources/customizations/.github/hooks/discovery-pre-tool.ps1 +123 -0
  157. package/resources/customizations/.github/hooks/discovery.json +35 -0
  158. package/resources/customizations/.github/instructions/mermaid.instructions.md +85 -0
  159. package/resources/customizations/.github/prompts/execute-hard-lock.prompt.md +105 -0
  160. package/resources/customizations/.github/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  161. package/resources/customizations/.github/skills/characterize-runtime-behavior/SKILL.md +134 -0
  162. package/resources/customizations/.github/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
  163. package/resources/customizations/.github/skills/discovery-coverage-ledger/SKILL.md +50 -0
  164. package/resources/customizations/.github/skills/discovery-parity-matrix/SKILL.md +46 -0
  165. package/resources/customizations/.github/skills/discovery-repo-inventory/SKILL.md +46 -0
  166. package/resources/customizations/.github/skills/discovery-runtime-characterization/SKILL.md +46 -0
  167. package/resources/customizations/.github/skills/discovery-validate-artifacts/SKILL.md +46 -0
  168. package/resources/customizations/.github/skills/discovery-workflow/SKILL.md +62 -0
  169. package/resources/customizations/.github/skills/execution-environment/SKILL.md +24 -9
  170. package/resources/customizations/.github/skills/extract-feature-contract/SKILL.md +133 -0
  171. package/resources/customizations/.github/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  172. package/resources/customizations/.github/skills/initialize-discovery-workspace/SKILL.md +131 -0
  173. package/resources/customizations/.github/skills/inventory-legacy-system/SKILL.md +131 -0
  174. package/resources/customizations/.github/skills/reconcile-parity-matrix/SKILL.md +131 -0
  175. package/resources/customizations/.github/skills/review-discovery-coverage/SKILL.md +129 -0
  176. package/resources/customizations/.github/skills/validate-discovery-artifacts/SKILL.md +127 -0
  177. package/resources/lib/codex-routing/CodexDeployment.psm1 +5 -1
  178. package/resources/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
  179. package/resources/lib/codex-routing/CodexTopology.psm1 +25 -2
  180. package/resources/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
  181. package/resources/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
  182. package/resources/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
  183. package/resources/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
  184. package/resources/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
  185. package/resources/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
  186. package/resources/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
  187. package/resources/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
  188. package/resources/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
  189. package/resources/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
  190. package/resources/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
  191. package/resources/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
  192. package/resources/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
  193. package/resources/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
  194. package/resources/powershell/PoshQC/PoshQC.Parallel.psm1 +452 -0
  195. package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +106 -8
  196. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +12 -0
  197. package/resources/powershell/PoshQC/PoshQC.psd1 +6 -1
  198. package/resources/powershell/PoshQC/PoshQC.psm1 +7 -1
  199. package/resources/powershell/PoshQC/README.md +20 -1
@@ -3,7 +3,7 @@
3
3
  Epic-scope legs, checkpoint read seams, and target resolution of the preimplementation gate.
4
4
 
5
5
  .DESCRIPTION
6
- Dot-sourced by enforce-orchestration-preimplementation-gate.ps1. Holds four groups of
6
+ Dot-sourced by enforce-orchestration-preimplementation-gate.ps1. Holds five groups of
7
7
  functions:
8
8
 
9
9
  - The import guards for the worktree-resolution modules (issue #690). A failed import
@@ -15,13 +15,20 @@
15
15
  read binds to the calling process's directory. Get-CheckpointContent and
16
16
  Get-OrchestrationModeDenyReason were relocated here from the gate file by issue #690
17
17
  to keep the gate inside the 500-line cap.
18
+ - The target-resolution building blocks of issue #1010: the worktree-list wrapper
19
+ Get-OrchestrationGateWorktreeList (the only listing seam), the recording invoker
20
+ Invoke-OrchestrationGateWorktreeList, which records a failed or empty listing in
21
+ $script:OrchestrationGateWorktreeListFailed, and the command-operand parser
22
+ Get-OrchestrationGateCommandOperandPath for git add and git commit.
18
23
  - The target resolution of issue #690. Resolve-OrchestrationGateTarget selects the
19
24
  worktree whose checkpoint governs the call from portable identity only: the
20
25
  integration_branch: value for an epic delegation, the parallel_slug: value for a
21
- parallel delegation, the file_path or git -C operand for the path and command legs,
22
- and the canonical issue-number line and branch: label for a single-feature
23
- delegation. Read-OrchestrationGateCheckpoint reads the checkpoint beneath the
24
- resolved root, or returns the deny for an unresolved target.
26
+ parallel delegation, and the canonical issue-number line and branch: label for a
27
+ single-feature delegation. The path and command legs resolve through
28
+ Resolve-HookTargetWorktree (issue #1010) from the file_path or from every git add
29
+ and git commit selector and absolute operand. Read-OrchestrationGateCheckpoint reads
30
+ the checkpoint beneath the resolved root, or returns the deny for an unresolved
31
+ target.
25
32
  - The epic-scope decision for the command and path legs (issue #663). A staging
26
33
  command or a Write/Edit call is epic scope when the effective worktree's HEAD equals
27
34
  the integration branch of the epic checkpoint that Resolve-EpicScopeCheckpoint
@@ -30,21 +37,24 @@
30
37
 
31
38
  .NOTES
32
39
  PowerShell 7+. Depends on functions the gate defines or dot-sources before any call:
33
- Split-OrchestrationCommandLine and ConvertTo-OrchestrationCommandToken (helpers file),
34
40
  Get-OrchestrationDelegationCheckpointPath (modes file), the gate's allow and block
35
- decision constructors, and, through the targets file this file dot-sources (issue #738),
36
- Read-CommandLineSegment, Get-CommandLineGlobalOption, and Get-CommandLineInvocation
37
- (loaded by the gate's dot-source of hook-command-invocation.ps1). Imports
38
- WorktreeItemResolution.psm1 and WorktreeRunResolution.psm1 before
39
- EpicScopeResolution.psm1, so the nested import inside EpicScopeResolution reuses the
40
- same module instance. Mirrored byte-identically under
41
+ decision constructors, and Read-CommandLineSegment, Get-CommandLineGlobalOption, and
42
+ Get-CommandLineInvocation (loaded by the gate's dot-source of
43
+ hook-command-invocation.ps1), which the targets file this file dot-sources (issue #738)
44
+ and Get-OrchestrationGateCommandOperandPath call. Imports
45
+ WorktreeResolution.psm1 (the reason-code accessors), then WorktreeItemResolution.psm1
46
+ and WorktreeRunResolution.psm1 before EpicScopeResolution.psm1, so the nested import
47
+ inside EpicScopeResolution reuses the same module instance, and HookTargetWorktree.psm1
48
+ (Resolve-HookTargetWorktree, issue #1009) last. Mirrored byte-identically under
41
49
  extensions/drm-copilot/resources/claude-customizations/.
42
50
  #>
43
51
 
52
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeResolution.psm1' -ErrorRecord $_ }
44
53
  try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeItemResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeItemResolution.psm1' -ErrorRecord $_ }
45
54
  try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunResolution.psm1' -ErrorRecord $_ }
46
55
  try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'EpicScopeResolution.psm1' -ErrorRecord $_ }
47
56
  try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeReadiness.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'EpicScopeReadiness.psm1' -ErrorRecord $_ }
57
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/HookTargetWorktree.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookTargetWorktree.psm1' -ErrorRecord $_ }
48
58
  # Per-segment target resolution for the epic-scope decision (issue #738).
49
59
  . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-targets.ps1')
50
60
 
@@ -102,15 +112,137 @@ function Get-OrchestrationModeDenyReason {
102
112
  'require that checkpoint to satisfy every readiness predicate before implementation begins.')
103
113
  }
104
114
 
115
+ function Get-OrchestrationGateWorktreeList {
116
+ <#
117
+ .SYNOPSIS
118
+ Returns the porcelain worktree listing for the session root (issue #1010 seam).
119
+ .DESCRIPTION
120
+ Runs the listing exactly as the default WorktreeListInvoker of
121
+ Resolve-HookTargetWorktree does and returns an empty string on a non-zero exit. It
122
+ is the only test seam for the listing.
123
+ .OUTPUTS
124
+ System.String
125
+ #>
126
+ [CmdletBinding()]
127
+ [OutputType([string])]
128
+ param([Parameter(Mandatory)][string] $SessionRoot)
129
+
130
+ $porcelain = & git -C $SessionRoot worktree list --porcelain 2>$null
131
+ if ($LASTEXITCODE -ne 0) { return '' }
132
+ return ($porcelain -join "`n")
133
+ }
134
+
135
+ function Invoke-OrchestrationGateWorktreeList {
136
+ <#
137
+ .SYNOPSIS
138
+ Recording WorktreeListInvoker: returns the listing and records a listing failure.
139
+ .DESCRIPTION
140
+ Sets $script:OrchestrationGateWorktreeListFailed when the wrapper throws (and then
141
+ rethrows) or when its text holds no worktree entry, so the gate denies instead of
142
+ accepting the resolver's session-root fallback (issue #1010, spec E1).
143
+ .OUTPUTS
144
+ System.String
145
+ #>
146
+ [CmdletBinding()]
147
+ [OutputType([string])]
148
+ param([Parameter(Mandatory)][string] $SessionRoot)
149
+
150
+ try {
151
+ $text = [string](Get-OrchestrationGateWorktreeList -SessionRoot $SessionRoot)
152
+ } catch {
153
+ $script:OrchestrationGateWorktreeListFailed = $true
154
+ throw
155
+ }
156
+ if (@(ConvertFrom-HookTargetWorktreePorcelain -Text $text).Count -eq 0) {
157
+ $script:OrchestrationGateWorktreeListFailed = $true
158
+ }
159
+ return $text
160
+ }
161
+
162
+ function Get-OrchestrationGateCommandOperandPath {
163
+ <#
164
+ .SYNOPSIS
165
+ Returns the worktree-selecting paths of every git add and git commit invocation.
166
+ .DESCRIPTION
167
+ Scans every Structural match of git add and git commit (every command segment and
168
+ wrapper payload). Each -C value in any git global-option position and each absolute
169
+ operand is returned, distinct and in first-seen order. Relative operands, a -C on a
170
+ git segment invoking neither add nor commit, and Indeterminate matches are ignored.
171
+ For a match whose record is not argument-injected, a subcommand-aware walk (issue
172
+ #1010, F2) also reads the tokens after the subcommand with a per-subcommand option
173
+ model, compared case-sensitively: a value option consumes the next token unless its
174
+ value is inline (--name=value) or attached (-mvalue); a standalone flag consumes
175
+ itself; '--' makes every later token an operand; a redirection and its target are
176
+ skipped; any other dash token, such as the cluster -am, stops the walk. Its literal
177
+ absolute tokens are returned, and a token the walk reads as an option value is not
178
+ returned as an operand.
179
+ .PARAMETER Command
180
+ The full command line.
181
+ .OUTPUTS
182
+ System.String[]
183
+ #>
184
+ [CmdletBinding()]
185
+ [OutputType([string[]])]
186
+ param([Parameter(Mandatory)][AllowEmptyString()][string] $Command)
187
+
188
+ $valueOptions = @{
189
+ commit = @('-m', '--message', '-F', '--file', '--author', '--date', '-C', '--reuse-message', '-c', '--reedit-message', '-t', '--template', '--fixup', '--squash', '--trailer', '--pathspec-from-file')
190
+ add = @('--pathspec-from-file', '--chmod')
191
+ }
192
+ $flagOptions = @{
193
+ commit = @('-a', '--all', '--amend', '--no-edit', '--no-verify', '-n', '-s', '--signoff', '-q', '--quiet', '-v', '--verbose', '--allow-empty', '-o', '--only', '-i', '--include', '--pathspec-file-nul')
194
+ add = @('-A', '--all', '-u', '--update', '-f', '--force', '-N', '--intent-to-add', '-n', '--dry-run', '-v', '--verbose', '--pathspec-file-nul')
195
+ }
196
+ $absolutePattern = '^([A-Za-z]:[\\/]|/)'
197
+ $paths = [System.Collections.Generic.List[string]]::new()
198
+ foreach ($subcommand in @('add', 'commit')) {
199
+ foreach ($match in @(Get-CommandLineInvocation -CommandText $Command -CommandWord 'git' -SubcommandPath @($subcommand))) {
200
+ if ($match.Status -ne 'Structural') { continue }
201
+ $walked = [System.Collections.Generic.List[string]]::new()
202
+ $optionValues = [System.Collections.Generic.List[string]]::new()
203
+ $tokens = if ($match.Segment.ArgumentInjected) { [string[]]@() } else { [string[]]@($match.Segment.Tokens) }
204
+ $afterSeparator = $false
205
+ for ($index = [int]$match.OperandIndex; $index -lt $tokens.Count; $index++) {
206
+ $token = $tokens[$index]
207
+ if (-not $afterSeparator -and $token -match '^(<|>|\d*>|&>)') {
208
+ if ($token -match '^(\d*[<>]{1,2}&?|&>{1,2})$') { $index++ }
209
+ continue
210
+ }
211
+ if ($afterSeparator -or -not $token.StartsWith('-')) { $walked.Add($token); continue }
212
+ if ($token -ceq '--') { $afterSeparator = $true; continue }
213
+ $name = if ($token.IndexOf('=') -gt 0) { $token.Substring(0, $token.IndexOf('=')) } else { $token }
214
+ if ($valueOptions[$subcommand] -ccontains $name) {
215
+ if ($name -ceq $token -and $index + 1 -lt $tokens.Count) { $index++; $optionValues.Add($tokens[$index]) }
216
+ continue
217
+ }
218
+ if ($token -match '^-[^-].' -and $valueOptions[$subcommand] -ccontains $token.Substring(0, 2)) { continue }
219
+ if ($flagOptions[$subcommand] -ccontains $token) { continue }
220
+ break
221
+ }
222
+ $candidates = @(@($match.GlobalOptions) | Where-Object { $_.Name -ceq '-C' -and $_.Value } | ForEach-Object { [string]$_.Value })
223
+ $candidates += @(@($match.Operands) | Where-Object { $_ -match $absolutePattern -and $optionValues -cnotcontains $_ })
224
+ $candidates += @($walked | Where-Object { $_ -match $absolutePattern -and (Test-CommandLineTokenLiteral -Token $_) })
225
+ foreach ($candidate in $candidates) {
226
+ if ($paths -notcontains $candidate) { $paths.Add($candidate) }
227
+ }
228
+ }
229
+ }
230
+ return [string[]]$paths.ToArray()
231
+ }
232
+
105
233
  function Resolve-OrchestrationGateTarget {
106
234
  <#
107
235
  .SYNOPSIS
108
236
  Resolves the worktree whose checkpoint governs a gated call (issue #690 seam).
109
237
  .DESCRIPTION
110
238
  An epic delegation is keyed on integration_branch: (with epic_feature_folder: as a
111
- cross-check), a parallel delegation on parallel_slug:, a path leg on its file_path
112
- operand, a command leg on its git -C selector (none means the session root), and a
113
- single-feature delegation on its canonical issue-number line and branch: label.
239
+ cross-check), a parallel delegation on parallel_slug:, and a single-feature
240
+ delegation on its canonical issue-number line and branch: label. The path and
241
+ command legs use Resolve-HookTargetWorktree (issue #1010): a path leg passes its
242
+ file_path, a command leg the Get-OrchestrationGateCommandOperandPath result, and the
243
+ recording invoker supplies the worktree listing. A failed or empty listing denies as
244
+ not derivable instead of falling back to the session root; an ambiguous result
245
+ denies as ambiguous; a null root denies as not derivable.
114
246
  .OUTPUTS
115
247
  System.Management.Automation.PSCustomObject (the worktree-resolution target result).
116
248
  #>
@@ -131,13 +263,29 @@ function Resolve-OrchestrationGateTarget {
131
263
  }
132
264
  return Resolve-WorktreeParallelTarget -ParallelSlug $signal.ParallelSlug -SessionRoot $sessionRoot
133
265
  }
134
- if ($FilePath) {
135
- return Resolve-WorktreeOperandTarget -Path $FilePath -SessionRoot $sessionRoot
266
+ if (-not $FilePath -and -not $Command) {
267
+ return Resolve-WorktreeItemTarget -Text $Prompt -SessionRoot $sessionRoot
268
+ }
269
+ $script:OrchestrationGateWorktreeListFailed = $false
270
+ $invoker = { param($SessionRoot) Invoke-OrchestrationGateWorktreeList -SessionRoot $SessionRoot }
271
+ $resolved = if ($FilePath) {
272
+ Resolve-HookTargetWorktree -SessionRoot $sessionRoot -FilePath @($FilePath) -WorktreeListInvoker $invoker
273
+ } else {
274
+ Resolve-HookTargetWorktree -SessionRoot $sessionRoot -CommandOperandPath @(Get-OrchestrationGateCommandOperandPath -Command $Command) -WorktreeListInvoker $invoker
275
+ }
276
+ $detail = "resolver source $($resolved.Source): $($resolved.Detail)"
277
+ if ($script:OrchestrationGateWorktreeListFailed) {
278
+ return [pscustomobject]@{ Status = 'NoTarget'; WorktreeRoot = $null; ReasonCode = (Get-WorktreeResolutionNoTargetReasonCode); Detail = "the worktree listing failed or named no worktree; $detail" }
136
279
  }
137
- if ($Command) {
138
- return Resolve-WorktreeOperandTarget -Path (Get-OrchestrationEpicScopeSelector -Command $Command) -SessionRoot $sessionRoot
280
+ if ($resolved.Ambiguous) {
281
+ return [pscustomobject]@{ Status = 'Ambiguous'; WorktreeRoot = $null; ReasonCode = (Get-WorktreeResolutionAmbiguityReasonCode); Detail = $detail }
139
282
  }
140
- return Resolve-WorktreeItemTarget -Text $Prompt -SessionRoot $sessionRoot
283
+ if ([string]::IsNullOrEmpty([string]$resolved.WorktreeRoot)) {
284
+ return [pscustomobject]@{ Status = 'NoTarget'; WorktreeRoot = $null; ReasonCode = (Get-WorktreeResolutionNoTargetReasonCode); Detail = $detail }
285
+ }
286
+ $isSession = [string]::Equals([string]$resolved.WorktreeRoot, (ConvertTo-HookTargetWorktreeNormalizedPath -Path $sessionRoot), [System.StringComparison]::OrdinalIgnoreCase)
287
+ $status = if ($isSession) { 'SessionRoot' } else { 'OtherWorktree' }
288
+ return [pscustomobject]@{ Status = $status; WorktreeRoot = [string]$resolved.WorktreeRoot; ReasonCode = $null; Detail = $detail }
141
289
  }
142
290
 
143
291
  function Read-OrchestrationGateCheckpoint {
@@ -187,34 +335,6 @@ function Read-OrchestrationGateCheckpoint {
187
335
  }
188
336
  }
189
337
 
190
- function Get-OrchestrationEpicScopeSelector {
191
- <#
192
- .SYNOPSIS
193
- Returns the value of a leading `git -C <value>` selector, or $null.
194
- .DESCRIPTION
195
- Pure. Reads only the first segment of the command line, split and tokenized with
196
- the helpers-file parser so quoting is handled exactly as the staging exemption
197
- handles it. An unbalanced command line has no decidable selector and returns $null.
198
- .PARAMETER Command
199
- The full command line.
200
- .OUTPUTS
201
- System.String
202
- #>
203
- [CmdletBinding()]
204
- [OutputType([string])]
205
- param([Parameter(Mandatory)][AllowEmptyString()][string] $Command)
206
-
207
- $split = Split-OrchestrationCommandLine -CommandText $Command
208
- if (-not $split.Balanced -or @($split.Segments).Count -eq 0) {
209
- return $null
210
- }
211
- $token = @(ConvertTo-OrchestrationCommandToken -Segment ([string]@($split.Segments)[0]).Trim())
212
- if ($token.Count -ge 3 -and $token[0] -ceq 'git' -and $token[1] -ceq '-C' -and $token[2]) {
213
- return $token[2]
214
- }
215
- return $null
216
- }
217
-
218
338
  function Get-OrchestrationEpicScopeDecision {
219
339
  <#
220
340
  .SYNOPSIS
@@ -239,7 +239,7 @@ function Test-OrchestrationReady {
239
239
  }
240
240
  $lifecycleReady = $false
241
241
  if ($Payload.PSObject.Properties.Name -contains 'lifecycle_ready') {
242
- $lifecycleReady = [bool]$Payload.lifecycle_ready
242
+ $lifecycleReady = ($Payload.lifecycle_ready -is [bool]) -and $Payload.lifecycle_ready
243
243
  }
244
244
 
245
245
  if (-not $issueNum -or -not $featureFolder -or -not $routeId -or -not $lifecycleReady) {
@@ -7,14 +7,28 @@
7
7
  operation. When the target file is a PowerShell source file (.ps1, .psm1, .psd1),
8
8
  it decides whether the change may proceed in direct mode.
9
9
 
10
- Direct mode counts distinct production PowerShell paths per Claude Code session.
11
- The running set is persisted under .claude/state/powershell-batch-budget.<session_id>.json,
12
- and repeated edits to the same file are counted once. The 4th distinct production
13
- path is denied with a POWERSHELL_LARGE_PATH_REQUIRED reason that instructs the
14
- caller to route the change through /orchestrate.
15
-
16
- The orchestrated large path is detected from
17
- <root>/artifacts/orchestration/orchestrator-state.json. The selected route is the
10
+ Direct mode counts distinct production PowerShell paths per Claude Code session and
11
+ per resolved target worktree, and repeated edits to the same file are counted once.
12
+ The 4th distinct production path in one worktree is denied with a
13
+ POWERSHELL_LARGE_PATH_REQUIRED reason that instructs the caller to route the change
14
+ through /orchestrate.
15
+
16
+ The edited file is placed with Resolve-HookTargetWorktree from
17
+ .claude/lib/worktree-resolution/HookTargetWorktree.psm1. An absolute path resolves to
18
+ its nearest enclosing registered git worktree; a relative path, a path inside no
19
+ registered worktree, and an ambiguous result use the session root (the checkout that
20
+ holds this hook). The route is read from
21
+ artifacts/orchestration/orchestrator-state.json in the resolved target worktree.
22
+ The running set is kept under the session root, in
23
+ .claude/state/powershell-batch-budget.<session_id>.json when the resolved target worktree
24
+ is the session root and in
25
+ .claude/state/powershell-batch-budget.<session_id>.<worktree key>.json otherwise,
26
+ where the key comes from Get-HookTargetWorktreeKey; no state is written under another
27
+ worktree. A path under the root-level artifacts/ directory of the resolved target worktree
28
+ is gitignored scratch: it is allowed, consumes no slot, and writes no state. A
29
+ nested directory named artifacts elsewhere in the tree still counts.
30
+
31
+ The selected route is the
18
32
  route_id value when that key is present, otherwise the path_selected value, and it
19
33
  is usable only as a non-blank string. When the selected route is large,
20
34
  remediation, or preparation and the checkpoint is not terminal (next_step is not
@@ -38,9 +52,9 @@
38
52
  is composed into a file name, so a hostile id cannot escape the state
39
53
  directory. Resolving the id never creates the state directory.
40
54
 
41
- Candidate paths are contained to the resolved root. A candidate that resolves
42
- outside it is discarded: the decision is 'allow', no slot is consumed, and no
43
- state is written. Persisted entries that fail the same containment test are
55
+ Candidate paths are contained to the resolved target worktree root. A candidate
56
+ that resolves outside it is discarded: the decision is 'allow', no slot is consumed,
57
+ and no state is written. Persisted entries that fail the same containment test are
44
58
  dropped when state is rehydrated, so a state file carried between worktrees
45
59
  cannot spend this worktree's budget.
46
60
 
@@ -48,9 +62,12 @@
48
62
  hookSpecificOutput.permissionDecision = 'deny' and exits 0. Files already counted
49
63
  are always allowed through.
50
64
 
51
- Known limitation: a stale non-terminal large-path checkpoint left at the root
52
- exempts a later direct-mode session at that root. Orchestrator checkpoint hygiene
53
- (issue #673) moves foreign checkpoints aside before a new run.
65
+ Known limitations: an 8.3 short-name path, or a path reached through a junction,
66
+ that differs textually from the long path git reports matches no registered
67
+ worktree and falls back to the session root. A stale non-terminal large-path
68
+ checkpoint in a worktree exempts a later direct-mode session editing that worktree;
69
+ orchestrator checkpoint hygiene (issue #673) moves foreign checkpoints aside before
70
+ a new run.
54
71
 
55
72
  .NOTES
56
73
  Compatible with PowerShell 7+.
@@ -61,6 +78,7 @@ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot
61
78
 
62
79
 
63
80
  try { Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookPayload.psm1' -ErrorRecord $_ }
81
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/HookTargetWorktree.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'HookTargetWorktree.psm1' -ErrorRecord $_ }
64
82
  $script:PowerShellBatchBudgetRouteImportFailure = $null; try { . (Join-Path $PSScriptRoot 'enforce-batch-budget-route.ps1') } catch { $script:PowerShellBatchBudgetRouteImportFailure = 'enforce-batch-budget-route.ps1' }
65
83
 
66
84
  function Test-PowerShellBatchBudgetPathInRoot {
@@ -351,7 +369,8 @@ function Invoke-PowerShellBatchBudgetHook {
351
369
  [scriptblock] $WriteState = {
352
370
  param([string] $Path, [System.Collections.IDictionary] $State)
353
371
  $State | ConvertTo-Json -Depth 5 | Set-Content -Path $Path -Encoding UTF8
354
- }
372
+ },
373
+ [scriptblock] $WorktreeListInvoker
355
374
  )
356
375
 
357
376
  $payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
@@ -373,6 +392,25 @@ function Invoke-PowerShellBatchBudgetHook {
373
392
  }
374
393
  if ($script:PowerShellBatchBudgetRouteImportFailure) { return Get-PowerShellBatchBudgetBlockDecision -Reason "POWERSHELL_LARGE_PATH_REQUIRED: the route helper '$($script:PowerShellBatchBudgetRouteImportFailure)' failed to load, so the batch budget for '$normalized' cannot be evaluated; the gate fails closed." }
375
394
 
395
+ # The file is evaluated against the registered worktree that holds it. The session root
396
+ # stays the fallback for a relative path, a path in no registered worktree, and an
397
+ # ambiguous result, which keeps today's checkpoint, containment, and state-file name.
398
+ $resolverArguments = @{ SessionRoot = $Root; FilePath = @($filePath) }
399
+ if ($PSBoundParameters.ContainsKey('WorktreeListInvoker')) { $resolverArguments['WorktreeListInvoker'] = $WorktreeListInvoker }
400
+ $resolution = Resolve-HookTargetWorktree @resolverArguments
401
+ $targetRoot = $Root
402
+ $isSessionRoot = $true
403
+ if (-not $resolution.Ambiguous -and $null -ne $resolution.WorktreeRoot -and -not [string]::Equals($resolution.WorktreeRoot, (ConvertTo-HookTargetWorktreeNormalizedPath -Path $Root), [System.StringComparison]::OrdinalIgnoreCase)) {
404
+ $targetRoot = $resolution.WorktreeRoot
405
+ $isSessionRoot = $false
406
+ }
407
+
408
+ # The root-level artifacts/ directory is gitignored scratch, never a production file.
409
+ $relativePath = if ($normalized -match '^(/|[A-Za-z]:/)') { Get-HookTargetWorktreeRelativePath -Path $normalized -WorktreeRoot $targetRoot } else { $normalized }
410
+ if ($null -ne $relativePath -and $relativePath.StartsWith('artifacts/', [System.StringComparison]::OrdinalIgnoreCase)) {
411
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
412
+ }
413
+
376
414
  $stateDir = Join-Path -Path $Root -ChildPath '.claude/state'
377
415
 
378
416
  # Resolved before the directory is ensured, because reading the session-id
@@ -383,11 +421,12 @@ function Invoke-PowerShellBatchBudgetHook {
383
421
  -SessionIdFilePath (Join-Path -Path $stateDir -ChildPath 'current-session-id') `
384
422
  -ReadSessionIdFile $ReadSessionIdFile
385
423
 
386
- $stateFile = Join-Path -Path $stateDir -ChildPath ("powershell-batch-budget.$resolvedSessionId.json")
424
+ $stateLeaf = if ($isSessionRoot) { "powershell-batch-budget.$resolvedSessionId.json" } else { "powershell-batch-budget.$resolvedSessionId.$(Get-HookTargetWorktreeKey -WorktreeRoot $targetRoot).json" }
425
+ $stateFile = Join-Path -Path $stateDir -ChildPath $stateLeaf
387
426
 
388
427
  # The checkpoint is read before any state operation, so the large path neither
389
428
  # creates the state directory nor reads or writes the state file.
390
- $checkpointPath = Join-Path -Path $Root -ChildPath 'artifacts/orchestration/orchestrator-state.json'
429
+ $checkpointPath = Join-Path -Path $targetRoot -ChildPath 'artifacts/orchestration/orchestrator-state.json'
391
430
  $checkpointText = ''
392
431
  try {
393
432
  $checkpointText = [string](& $ReadCheckpoint $checkpointPath)
@@ -399,7 +438,7 @@ function Invoke-PowerShellBatchBudgetHook {
399
438
  $isLargePath = Test-BatchBudgetLargePathRoute -CheckpointText $checkpointText
400
439
  $observedRoute = Get-BatchBudgetSelectedRoute -CheckpointText $checkpointText
401
440
  if ($isLargePath) {
402
- return Invoke-PowerShellBatchBudgetDecision -FilePath $filePath -State (Get-PowerShellBatchBudgetState -ProdCap $ProdCap) -StateFile $stateFile -Root $Root -LargePathRoute
441
+ return Invoke-PowerShellBatchBudgetDecision -FilePath $filePath -State (Get-PowerShellBatchBudgetState -ProdCap $ProdCap) -StateFile $stateFile -Root $targetRoot -LargePathRoute
403
442
  }
404
443
 
405
444
  if (-not (& $TestPathExists $stateDir)) {
@@ -411,13 +450,13 @@ function Invoke-PowerShellBatchBudgetHook {
411
450
  if (& $TestPathExists $stateFile) {
412
451
  try {
413
452
  $loaded = & $ReadState $stateFile | ConvertFrom-Json -ErrorAction Stop
414
- $state = ConvertTo-PowerShellBatchBudgetState -InputObject $loaded -ProdCap $ProdCap -Root $Root
453
+ $state = ConvertTo-PowerShellBatchBudgetState -InputObject $loaded -ProdCap $ProdCap -Root $targetRoot
415
454
  } catch {
416
455
  Write-Verbose "Ignoring unreadable PowerShell batch-budget state file '$stateFile': $($_.Exception.Message)"
417
456
  }
418
457
  }
419
458
 
420
- $decision = Invoke-PowerShellBatchBudgetDecision -FilePath $filePath -State $state -StateFile $stateFile -Root $Root -ObservedRoute $observedRoute
459
+ $decision = Invoke-PowerShellBatchBudgetDecision -FilePath $filePath -State $state -StateFile $stateFile -Root $targetRoot -ObservedRoute $observedRoute
421
460
  if ($decision.shouldWriteState) {
422
461
  try {
423
462
  & $WriteState $stateFile $decision.state
@@ -10,9 +10,12 @@
10
10
  in one of these forms, matched by token position:
11
11
 
12
12
  - git log ... and git rev-parse ... (no git global option)
13
+ - git worktree list --porcelain (exactly these four tokens, issue #1011)
13
14
  - gh pr create ... and gh pr edit ... (no gh global option)
14
15
  - gh pr view <N> --json closingIssuesReferences,body (exactly these six tokens, issue #989)
15
16
  - sha256sum artifacts/pr_body_<N>.md (only as the whole command)
17
+ - sha256sum <item worktree>/artifacts/pr_body_<N>.md (only as the whole command; an absolute
18
+ forward-slash path, issue #1011)
16
19
  - date -u [+<format>] (only as the whole command)
17
20
 
18
21
  Segments may be joined only by ';', '&&', '||', '|', or a newline. A record that is
@@ -42,7 +45,38 @@ $script:PrAuthorAllowlistWrapperWords = @(
42
45
  'sh', 'bash', 'zsh', 'dash', 'ksh', 'pwsh', 'powershell', 'eval', 'xargs',
43
46
  'env', 'timeout', 'time', 'command', 'nohup'
44
47
  )
45
- $script:PrAuthorAllowlistFormText = 'git log, git rev-parse, gh pr create, gh pr edit, gh pr view <N> --json closingIssuesReferences,body, sha256sum artifacts/pr_body_<N>.md, date -u [+<format>]'
48
+ $script:PrAuthorAllowlistFormText = 'git log, git rev-parse, git worktree list --porcelain, gh pr create, gh pr edit, gh pr view <N> --json closingIssuesReferences,body, sha256sum artifacts/pr_body_<N>.md, sha256sum <item worktree>/artifacts/pr_body_<N>.md, date -u [+<format>]'
49
+
50
+ function Test-PrAuthorAbsoluteBodyPathToken {
51
+ <#
52
+ .SYNOPSIS
53
+ Report whether a token is an absolute canonical body path beneath an item worktree (issue #1011).
54
+ .DESCRIPTION
55
+ The token must be rooted at '/' or at a drive letter followed by ':/', use forward slashes
56
+ only, hold at least one segment before 'artifacts', hold no empty, '.', or '..' segment,
57
+ and end in '/artifacts/pr_body_<digits>.md' with the exact case of the relative form.
58
+ Pure string logic: no process, file read, or module import.
59
+ .PARAMETER Value
60
+ The sha256sum argument token, quotes already stripped.
61
+ .OUTPUTS
62
+ System.Boolean
63
+ #>
64
+ [CmdletBinding()]
65
+ [OutputType([bool])]
66
+ param(
67
+ [Parameter(Mandatory)][AllowEmptyString()][string] $Value
68
+ )
69
+
70
+ if ($Value.Contains('\') -or $Value -cnotmatch '^(/|[A-Za-z]:/)(?<rest>.+)/artifacts/pr_body_\d+\.md$') {
71
+ return $false
72
+ }
73
+ foreach ($segment in $Matches['rest'].Split('/')) {
74
+ if ($segment -eq '' -or $segment -eq '.' -or $segment -eq '..') {
75
+ return $false
76
+ }
77
+ }
78
+ return $true
79
+ }
46
80
 
47
81
  function Test-PrAuthorAllowlistForm {
48
82
  <#
@@ -67,7 +101,11 @@ function Test-PrAuthorAllowlistForm {
67
101
  }
68
102
  switch -CaseSensitive ($Token[0]) {
69
103
  'git' {
70
- return ($Token.Count -ge 2 -and @('log', 'rev-parse') -ccontains $Token[1])
104
+ if ($Token.Count -ge 2 -and @('log', 'rev-parse') -ccontains $Token[1]) {
105
+ return $true
106
+ }
107
+ # The read-only worktree discovery form, matched exactly (issue #1011).
108
+ return ($Token.Count -eq 4 -and $Token[1] -ceq 'worktree' -and $Token[2] -ceq 'list' -and $Token[3] -ceq '--porcelain')
71
109
  }
72
110
  'gh' {
73
111
  if ($Token.Count -ge 3 -and $Token[1] -ceq 'pr' -and @('create', 'edit') -ccontains $Token[2]) {
@@ -78,7 +116,10 @@ function Test-PrAuthorAllowlistForm {
78
116
  $Token[4] -ceq '--json' -and $Token[5] -ceq 'closingIssuesReferences,body')
79
117
  }
80
118
  'sha256sum' {
81
- return ($IsWholeCommand -and $Token.Count -eq 2 -and $Token[1] -cmatch '^artifacts/pr_body_\d+\.md$')
119
+ if (-not $IsWholeCommand -or $Token.Count -ne 2) {
120
+ return $false
121
+ }
122
+ return ($Token[1] -cmatch '^artifacts/pr_body_\d+\.md$' -or (Test-PrAuthorAbsoluteBodyPathToken -Value $Token[1]))
82
123
  }
83
124
  'date' {
84
125
  if (-not $IsWholeCommand -or $Token.Count -lt 2 -or $Token[1] -cne '-u') {
@@ -50,6 +50,8 @@ Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeTarge
50
50
  # feature folder exists in every checkout branched from main, so it places a call in many at
51
51
  # once. The item is identified by its canonical issue number and its branch instead.
52
52
  Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeItemResolution.psm1') -Force -ErrorAction Stop
53
+ # Shared target-worktree resolver (issues #1009, #1011): resolves the branch a --head names.
54
+ Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/HookTargetWorktree.psm1') -Force -ErrorAction Stop
53
55
  # Epic scope (issue #663): the epic integration pull request is gated against the epic checkpoint.
54
56
  Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeResolution.psm1') -Force -ErrorAction Stop
55
57
  Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeReadiness.psm1') -Force -ErrorAction Stop
@@ -70,10 +72,15 @@ function Resolve-PrAuthorWorktreeTarget {
70
72
  directly would depend on the machine's live worktrees and would pass or fail
71
73
  according to what other sessions happen to have checked out, which the repository's
72
74
  determinism policy forbids. Mocking here lets a test state the resolution outcome it
73
- is exercising and nothing else. A NoTarget result for a command with no signal passes
74
- through Resolve-PrAuthorCurrentBranchTarget, which re-resolves the current branch (#989).
75
+ is exercising and nothing else. A branch the command names (--head, --branch, or
76
+ branch:) is resolved by the shared resolver Resolve-HookTargetWorktree, given only the
77
+ session root and that branch (issue #1011), unless the command also carries a canonical
78
+ issue number line, which Resolve-WorktreeItemTarget decides so the branch/issue agreement
79
+ check applies. Without a selection, Resolve-WorktreeItemTarget runs, and a NoTarget result
80
+ for a command with no signal passes through Resolve-PrAuthorCurrentBranchTarget, which
81
+ re-resolves the current branch (#989).
75
82
  .OUTPUTS
76
- The target result object from Resolve-WorktreeItemTarget.
83
+ A target result object built by New-WorktreeResolutionTargetResult.
77
84
  #>
78
85
  [CmdletBinding()]
79
86
  param(
@@ -81,10 +88,59 @@ function Resolve-PrAuthorWorktreeTarget {
81
88
  [string] $CommandText
82
89
  )
83
90
 
84
- $target = Resolve-WorktreeItemTarget -Text $CommandText -SessionRoot (Get-Location).Path
91
+ $sessionRoot = (Get-Location).Path
92
+ $branch = Find-WorktreeResolutionBranchSignal -Text $CommandText
93
+ $issueSignal = Find-WorktreeItemIssueSignal -Text $CommandText
94
+ if ($null -ne $branch -and @($issueSignal).Count -eq 0) {
95
+ $shared = Resolve-HookTargetWorktree -SessionRoot $sessionRoot -CommandBranch $branch
96
+ $mapped = ConvertFrom-PrAuthorHookTargetResult -Result $shared -CommandText $CommandText -SessionRoot $sessionRoot
97
+ if ($null -ne $mapped) { return $mapped }
98
+ }
99
+ $target = Resolve-WorktreeItemTarget -Text $CommandText -SessionRoot $sessionRoot
85
100
  return (Resolve-PrAuthorCurrentBranchTarget -CommandText $CommandText -Target $target)
86
101
  }
87
102
 
103
+ function ConvertFrom-PrAuthorHookTargetResult {
104
+ <#
105
+ .SYNOPSIS
106
+ Map a Resolve-HookTargetWorktree result onto a pr-author target result, or $null (issue #1011).
107
+ .DESCRIPTION
108
+ Returns $null (no selection) when Result is null, when CommandText carries no branch
109
+ signal, when Result.Source is not CommandOperand, when Result names no root, or when that
110
+ root fails Test-WorktreeResolutionRootMarker (a registration whose directory is gone).
111
+ Only a CommandOperand result selects, being branch-derived; a FilePath, SessionRoot, or
112
+ DelegationIdentity result never selects the worktree (issue #673). An ambiguous result
113
+ maps to Ambiguous. Labelling is delegated to ConvertTo-WorktreeItemResolvedResult, which
114
+ maps a root equal to SessionRoot, or to the worktree enclosing it, to SessionRoot and any
115
+ other root to OtherWorktree.
116
+ .PARAMETER Result
117
+ The Resolve-HookTargetWorktree result.
118
+ .PARAMETER CommandText
119
+ The Bash command text under evaluation.
120
+ .PARAMETER SessionRoot
121
+ The session root the result is compared with.
122
+ .OUTPUTS
123
+ System.Management.Automation.PSCustomObject or $null
124
+ #>
125
+ [CmdletBinding()]
126
+ [OutputType([pscustomobject])]
127
+ param(
128
+ [AllowNull()][object] $Result,
129
+ [Parameter(Mandatory)][AllowEmptyString()][string] $CommandText,
130
+ [Parameter(Mandatory)][string] $SessionRoot
131
+ )
132
+
133
+ $branch = Find-WorktreeResolutionBranchSignal -Text $CommandText
134
+ if ($null -eq $Result -or $null -eq $branch -or $Result.Source -cne 'CommandOperand') { return $null }
135
+ $detail = if ([string]::IsNullOrWhiteSpace([string]$Result.Detail)) { "branch '$branch' resolved by the shared resolver" } else { [string]$Result.Detail }
136
+ if ($Result.Ambiguous) {
137
+ return (New-WorktreeResolutionTargetResult -Status Ambiguous -SessionRoot $SessionRoot -Signal Branch -SignalValue $branch -Candidate @($Result.Candidates) -Detail $detail)
138
+ }
139
+ if ([string]::IsNullOrEmpty([string]$Result.WorktreeRoot)) { return $null }
140
+ if (-not (Test-WorktreeResolutionRootMarker -Path $Result.WorktreeRoot)) { return $null }
141
+ return (ConvertTo-WorktreeItemResolvedResult -WorktreeRoot $Result.WorktreeRoot -SessionRoot $SessionRoot -Branch $branch -Detail $detail)
142
+ }
143
+
88
144
  function Get-PrAuthorTargetCheckpointResolution {
89
145
  <#
90
146
  .SYNOPSIS
@@ -59,7 +59,9 @@ function Test-EpicBaseBranchOverride {
59
59
  with reason EPIC_BASE_BRANCH_MISMATCH. When epic_mode is absent or false, when the
60
60
  checkpoint is unreadable, or when the command is not gh pr create (the base branch is
61
61
  set at create time, not edit time), this check is a no-op and standalone behavior is
62
- unchanged.
62
+ unchanged. Only the JSON boolean true counts as epic mode: a string or number such as
63
+ "false", "true", or 1 is not epic mode, so the --base requirement does not apply
64
+ (issue #1001).
63
65
  .PARAMETER CommandText
64
66
  The Bash command text under evaluation.
65
67
  .PARAMETER CheckpointPath
@@ -116,7 +118,7 @@ function Test-EpicBaseBranchOverride {
116
118
  }
117
119
 
118
120
  $checkpointProps = @($checkpoint.PSObject.Properties.Name)
119
- if ($checkpointProps -notcontains 'epic_mode' -or -not [bool]$checkpoint.epic_mode) {
121
+ if ($checkpointProps -notcontains 'epic_mode' -or -not (($checkpoint.epic_mode -is [bool]) -and $checkpoint.epic_mode)) {
120
122
  return $null
121
123
  }
122
124