@danmoisan/drm-copilot-mcp 1.1.23 → 1.1.25

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
@@ -0,0 +1,149 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Codex PreToolUse hook that gates planner and orchestrator launches on current configured discovery authority.
4
+
5
+ .DESCRIPTION
6
+ Invoked by the Codex PreToolUse hook. The hook reads the Codex envelope from
7
+ stdin, recognizes an atomic-planner or orchestrator launch, and takes its
8
+ decision from the shared DiscoveryPlanning module (published under
9
+ .codex/lib/discovery-validation, or read from .claude/lib when this repository
10
+ hosts itself):
11
+
12
+ - a call that is not a planner or orchestrator launch, and a workspace without
13
+ a protected discovery activation, allow silently (no stdout) so the existing
14
+ Codex gates keep their behavior;
15
+ - an active workspace must still hold the activated locator and domain profile
16
+ and the protected session must hold a current, passing, gateway-written
17
+ planning-mode receipt for this host and session; otherwise the launch is
18
+ denied with a PreToolUse deny decision at exit 0.
19
+
20
+ An envelope the hook cannot read is a hard failure: the reason goes to stderr
21
+ and the hook exits 2. Dependency failures deny with the
22
+ DISCOVERY_PLANNING_GATE_BLOCKED: prefix. The hook never runs Python (issue #980).
23
+
24
+ .NOTES
25
+ Requires PowerShell 7+. Invokes NO external process.
26
+ #>
27
+ [CmdletBinding()]
28
+ param()
29
+ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot 'hook-dependency-guard.ps1') } catch { $script:HookDependencyGuardLoadFailed = $true }
30
+
31
+ function Get-DiscoveryPlanningHelperModulePath {
32
+ <#
33
+ .SYNOPSIS
34
+ Returns the published (.codex/lib) or self-hosting (.claude/lib) path of one helper module.
35
+ #>
36
+ [CmdletBinding()]
37
+ [OutputType([string])]
38
+ param(
39
+ [Parameter(Mandatory = $true)][string]$ModuleName,
40
+ [Parameter(Mandatory = $false)][string]$ScriptRoot = $PSScriptRoot
41
+ )
42
+
43
+ $published = Join-Path $ScriptRoot "../lib/discovery-validation/$ModuleName"
44
+ $selfHosted = Join-Path $ScriptRoot "../../.claude/lib/discovery-validation/$ModuleName"
45
+ foreach ($candidate in @($published, $selfHosted)) {
46
+ if (Test-Path -LiteralPath $candidate -PathType Leaf) { return $candidate }
47
+ }
48
+ return $published
49
+ }
50
+
51
+ function Import-DiscoveryPlanningSelfHostedModule {
52
+ <#
53
+ .SYNOPSIS
54
+ Imports the shared helpers from this repository's .claude/lib layout when no published .codex/lib exists (exemption D4).
55
+ #>
56
+ [CmdletBinding()]
57
+ param()
58
+
59
+ $ScriptRoot = $PSScriptRoot
60
+ try { Import-Module (Join-Path $ScriptRoot '../../.claude/lib/discovery-validation/DiscoveryEncoding.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryEncoding.psm1' -ErrorRecord $_ }
61
+ try { Import-Module (Join-Path $ScriptRoot '../../.claude/lib/discovery-validation/DiscoveryAuthority.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryAuthority.psm1' -ErrorRecord $_ }
62
+ try { Import-Module (Join-Path $ScriptRoot '../../.claude/lib/discovery-validation/DiscoveryManifest.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryManifest.psm1' -ErrorRecord $_ }
63
+ try { Import-Module (Join-Path $ScriptRoot '../../.claude/lib/discovery-validation/DiscoveryPayload.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryPayload.psm1' -ErrorRecord $_ }
64
+ try { Import-Module (Join-Path $ScriptRoot '../../.claude/lib/discovery-validation/DiscoveryToolRouting.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryToolRouting.psm1' -ErrorRecord $_ }
65
+ try { Import-Module (Join-Path $ScriptRoot '../../.claude/lib/discovery-validation/DiscoveryAuthorization.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryAuthorization.psm1' -ErrorRecord $_ }
66
+ try { Import-Module (Join-Path $ScriptRoot '../../.claude/lib/discovery-validation/DiscoveryStartRecord.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryStartRecord.psm1' -ErrorRecord $_ }
67
+ try { Import-Module (Join-Path $ScriptRoot '../../.claude/lib/discovery-validation/DiscoveryPlanning.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryPlanning.psm1' -ErrorRecord $_ }
68
+ }
69
+
70
+ # The published destination keeps the helpers beside .codex/hooks; this repository hosts them under .claude/lib.
71
+ if (Test-Path -LiteralPath (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryEncoding.psm1') -PathType Leaf) {
72
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryEncoding.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryEncoding.psm1' -ErrorRecord $_ }
73
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryAuthority.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryAuthority.psm1' -ErrorRecord $_ }
74
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryManifest.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryManifest.psm1' -ErrorRecord $_ }
75
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryPayload.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryPayload.psm1' -ErrorRecord $_ }
76
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryToolRouting.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryToolRouting.psm1' -ErrorRecord $_ }
77
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryAuthorization.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryAuthorization.psm1' -ErrorRecord $_ }
78
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryStartRecord.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryStartRecord.psm1' -ErrorRecord $_ }
79
+ try { Import-Module (Join-Path $PSScriptRoot '../lib/discovery-validation/DiscoveryPlanning.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'DiscoveryPlanning.psm1' -ErrorRecord $_ }
80
+ } else {
81
+ Import-DiscoveryPlanningSelfHostedModule
82
+ }
83
+
84
+ $script:DiscoveryPlanningReasonPrefix = 'DISCOVERY_PLANNING_GATE_BLOCKED:'
85
+
86
+ function Invoke-DiscoveryPlanningGateDecision {
87
+ <#
88
+ .SYNOPSIS
89
+ Parses the Codex PreToolUse envelope and returns the planning outcome.
90
+ .DESCRIPTION
91
+ Returns the shared outcome object (Decision, Code, Reason, Receipt,
92
+ Prerequisite). Every seam defaults to the real host and is injectable. A
93
+ recorded dependency failure returns a deny outcome before the envelope is read.
94
+ #>
95
+ [CmdletBinding()]
96
+ [OutputType([pscustomobject])]
97
+ param(
98
+ [Parameter(Mandatory = $false)][AllowNull()][AllowEmptyString()][string]$ToolInputRaw,
99
+ [Parameter(Mandatory = $false)][AllowNull()][hashtable]$HostEnvironment = $null,
100
+ [Parameter(Mandatory = $false)][AllowNull()][hashtable]$Store = $null,
101
+ [Parameter(Mandatory = $false)][AllowNull()][scriptblock]$ReadBytes = $null
102
+ )
103
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix $script:DiscoveryPlanningReasonPrefix
104
+ if ($null -ne $dependencyDecision) { return $dependencyDecision }
105
+
106
+ if ($null -eq $HostEnvironment) { $HostEnvironment = Get-DiscoveryTrustedHostEnvironment }
107
+ if ($null -eq $Store) { $Store = Get-DiscoveryStartStore }
108
+
109
+ $normalized = ConvertFrom-DiscoveryHostPayloadText -HostName 'codex' -Text $ToolInputRaw
110
+ $parameters = @{ Normalized = $normalized; HostEnvironment = $HostEnvironment; Store = $Store }
111
+ if ($null -ne $ReadBytes) { $parameters['ReadBytes'] = $ReadBytes }
112
+ return Get-DiscoveryPlanningDecision @parameters
113
+ }
114
+
115
+ function Invoke-DiscoveryPlanningGateEntryPoint {
116
+ <#
117
+ .SYNOPSIS
118
+ Reads one Codex PreToolUse payload, writes the decision and returns the process exit code.
119
+ .DESCRIPTION
120
+ An unreadable envelope writes its reason to stderr and returns 2. A deny
121
+ writes the PreToolUse decision JSON to stdout and returns 0. An allow writes
122
+ nothing and returns 0, as the Codex gates do.
123
+ #>
124
+ [CmdletBinding()]
125
+ [OutputType([int])]
126
+ param([Parameter(Mandatory = $false)][AllowNull()][AllowEmptyString()][string]$PayloadRaw)
127
+
128
+ $outcome = Invoke-DiscoveryPlanningGateDecision -ToolInputRaw $PayloadRaw
129
+ # A dependency failure is already the PreToolUse deny decision.
130
+ if ($outcome -is [System.Collections.IDictionary]) { [Console]::Out.WriteLine(($outcome | ConvertTo-Json -Compress -Depth 5)); return 0 }
131
+ if ($outcome.Decision -eq 'allow') { return 0 }
132
+ if ($outcome.Code -eq 'ENVELOPE') {
133
+ [Console]::Error.WriteLine("$($script:DiscoveryPlanningReasonPrefix) $($outcome.Reason)")
134
+ return 2
135
+ }
136
+ $decision = ConvertTo-DiscoveryPlanningHookOutput -Outcome $outcome -ReasonPrefix $script:DiscoveryPlanningReasonPrefix
137
+ [Console]::Out.WriteLine(($decision | ConvertTo-Json -Compress -Depth 5))
138
+ return 0
139
+ }
140
+
141
+ # Guard allows dot-sourcing in tests without executing the entrypoint.
142
+ if ($MyInvocation.InvocationName -eq '.') {
143
+ return
144
+ }
145
+ if ($script:HookDependencyGuardLoadFailed) { [Console]::Error.WriteLine('DISCOVERY_PLANNING_GATE_BLOCKED: hook-dependency-guard.ps1 failed to load; the gate fails closed.'); exit 2 }
146
+ $dependencyDecision = Get-HookDependencyFailureDecision -HookEvent PreToolUse -ReasonPrefix 'DISCOVERY_PLANNING_GATE_BLOCKED:'
147
+ if ($null -ne $dependencyDecision) { $dependencyDecision | ConvertTo-Json -Compress -Depth 5 | Write-Output; exit 0 }
148
+
149
+ exit (Invoke-DiscoveryPlanningGateEntryPoint -PayloadRaw ([Console]::In.ReadToEnd()))
@@ -4,12 +4,20 @@
4
4
 
5
5
  .DESCRIPTION
6
6
  Dot-sourced by enforce-orchestration-preimplementation-gate.ps1. The Codex counterpart
7
- of the issue #663 Claude sibling, holding two groups of functions:
7
+ of the issue #663 Claude sibling, holding three groups of functions:
8
8
 
9
9
  - The two per-mode read seams of issue #554 (Get-EpicCheckpointContent and
10
10
  Get-ParallelCheckpointContent), relocated verbatim from the gate file so the gate
11
11
  stays inside the 500-line cap. Their names and behaviour are unchanged, so tests
12
12
  that mock or shadow them by name are unaffected.
13
+ - The target-resolution building blocks of issue #1010, byte-identical to the Claude
14
+ sibling: the worktree-list wrapper Get-OrchestrationGateWorktreeList (the only
15
+ listing seam), the recording invoker Invoke-OrchestrationGateWorktreeList, and the
16
+ command-operand parser Get-OrchestrationGateCommandOperandPath, plus the reason-code
17
+ pair restated from WorktreeResolution.psm1. They feed Resolve-OrchestrationGateTarget
18
+ and Read-OrchestrationGateCheckpoint, which read the single-feature checkpoint through
19
+ the Get-CheckpointContent seam (mandatory absolute Path, moved here from the gate)
20
+ beneath the worktree the file path, apply_patch markers, or command operands name.
13
21
  - The epic-scope decision for the command, apply_patch, and path legs. A call is epic
14
22
  scope when the effective worktree's HEAD (the -C selector worktree when present,
15
23
  otherwise the session root) equals the integration_branch of
@@ -29,13 +37,23 @@
29
37
  from the resolution sibling dot-sourced below. The per-segment target resolver comes
30
38
  from the targets file dot-sourced below (issue #738), which calls
31
39
  Read-CommandLineSegment, Get-CommandLineGlobalOption, and Get-CommandLineInvocation,
32
- loaded by the gate's dot-source of hook-command-invocation.ps1.
40
+ loaded by the gate's dot-source of hook-command-invocation.ps1. Resolve-HookTargetWorktree
41
+ and ConvertFrom-HookTargetWorktreePorcelain come from the guarded dot-source of the
42
+ resolver twin hook-target-worktree.ps1 (issue #1009).
33
43
  Mirrored byte-identically under extensions/drm-copilot/resources/codex-and-agents-customizations/.
34
44
  AUTHORITY: PowerShell-authoritative. The epic readiness predicate has no reference implementation in another language, and this hook starts no interpreter process (issue #707, D15).
35
45
  #>
36
46
 
37
47
  . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-epic-resolution.ps1')
38
48
  . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-targets.ps1')
49
+ try { . (Join-Path $PSScriptRoot 'hook-target-worktree.ps1') } catch { Add-HookDependencyFailure -Name 'hook-target-worktree.ps1' -ErrorRecord $_ }
50
+
51
+ # The target-resolution reason codes (issue #1010). Their source is
52
+ # Get-WorktreeResolutionAmbiguityReasonCode and Get-WorktreeResolutionNoTargetReasonCode
53
+ # in .claude/lib/worktree-resolution/WorktreeResolution.psm1; Codex hooks cannot import
54
+ # .claude/lib, so the two literals are restated once here and pinned equal by a test.
55
+ $script:OrchestrationGateAmbiguousReasonCode = 'TARGET_WORKTREE_AMBIGUOUS'
56
+ $script:OrchestrationGateNotDerivableReasonCode = 'TARGET_WORKTREE_NOT_DERIVABLE'
39
57
 
40
58
  # An epic manifest lives under the epics tree; the pattern is tested after backslash
41
59
  # normalisation so either separator spelling is accepted.
@@ -68,6 +86,225 @@ function Get-ParallelCheckpointContent {
68
86
  return Get-Content -Raw -LiteralPath $path
69
87
  }
70
88
 
89
+ # The single-feature read seam (issue #1010, moved from the gate file). It takes the
90
+ # absolute path composed beneath the resolved worktree root, so no read binds to the
91
+ # calling process's directory; an absent file returns an empty string.
92
+ function Get-CheckpointContent {
93
+ [CmdletBinding()]
94
+ [OutputType([string])]
95
+ param([Parameter(Mandatory)][ValidatePattern('^([A-Za-z]:[\\/]|/)')][string] $Path)
96
+
97
+ if (-not (Test-Path -LiteralPath $Path)) {
98
+ return ''
99
+ }
100
+ return Get-Content -Raw -LiteralPath $Path
101
+ }
102
+
103
+ function Get-OrchestrationGateWorktreeList {
104
+ <#
105
+ .SYNOPSIS
106
+ Returns the porcelain worktree listing for the session root (issue #1010 seam).
107
+ .DESCRIPTION
108
+ Runs the listing exactly as the default WorktreeListInvoker of
109
+ Resolve-HookTargetWorktree does and returns an empty string on a non-zero exit. It
110
+ is the only test seam for the listing.
111
+ .OUTPUTS
112
+ System.String
113
+ #>
114
+ [CmdletBinding()]
115
+ [OutputType([string])]
116
+ param([Parameter(Mandatory)][string] $SessionRoot)
117
+
118
+ $porcelain = & git -C $SessionRoot worktree list --porcelain 2>$null
119
+ if ($LASTEXITCODE -ne 0) { return '' }
120
+ return ($porcelain -join "`n")
121
+ }
122
+
123
+ function Invoke-OrchestrationGateWorktreeList {
124
+ <#
125
+ .SYNOPSIS
126
+ Recording WorktreeListInvoker: returns the listing and records a listing failure.
127
+ .DESCRIPTION
128
+ Sets $script:OrchestrationGateWorktreeListFailed when the wrapper throws (and then
129
+ rethrows) or when its text holds no worktree entry, so the gate denies instead of
130
+ accepting the resolver's session-root fallback (issue #1010, spec E1).
131
+ .OUTPUTS
132
+ System.String
133
+ #>
134
+ [CmdletBinding()]
135
+ [OutputType([string])]
136
+ param([Parameter(Mandatory)][string] $SessionRoot)
137
+
138
+ try {
139
+ $text = [string](Get-OrchestrationGateWorktreeList -SessionRoot $SessionRoot)
140
+ } catch {
141
+ $script:OrchestrationGateWorktreeListFailed = $true
142
+ throw
143
+ }
144
+ if (@(ConvertFrom-HookTargetWorktreePorcelain -Text $text).Count -eq 0) {
145
+ $script:OrchestrationGateWorktreeListFailed = $true
146
+ }
147
+ return $text
148
+ }
149
+
150
+ function Get-OrchestrationGateCommandOperandPath {
151
+ <#
152
+ .SYNOPSIS
153
+ Returns the worktree-selecting paths of every git add and git commit invocation.
154
+ .DESCRIPTION
155
+ Scans every Structural match of git add and git commit (every command segment and
156
+ wrapper payload). Each -C value in any git global-option position and each absolute
157
+ operand is returned, distinct and in first-seen order. Relative operands, a -C on a
158
+ git segment invoking neither add nor commit, and Indeterminate matches are ignored.
159
+ For a match whose record is not argument-injected, a subcommand-aware walk (issue
160
+ #1010, F2) also reads the tokens after the subcommand with a per-subcommand option
161
+ model, compared case-sensitively: a value option consumes the next token unless its
162
+ value is inline (--name=value) or attached (-mvalue); a standalone flag consumes
163
+ itself; '--' makes every later token an operand; a redirection and its target are
164
+ skipped; any other dash token, such as the cluster -am, stops the walk. Its literal
165
+ absolute tokens are returned, and a token the walk reads as an option value is not
166
+ returned as an operand.
167
+ .PARAMETER Command
168
+ The full command line.
169
+ .OUTPUTS
170
+ System.String[]
171
+ #>
172
+ [CmdletBinding()]
173
+ [OutputType([string[]])]
174
+ param([Parameter(Mandatory)][AllowEmptyString()][string] $Command)
175
+
176
+ $valueOptions = @{
177
+ commit = @('-m', '--message', '-F', '--file', '--author', '--date', '-C', '--reuse-message', '-c', '--reedit-message', '-t', '--template', '--fixup', '--squash', '--trailer', '--pathspec-from-file')
178
+ add = @('--pathspec-from-file', '--chmod')
179
+ }
180
+ $flagOptions = @{
181
+ commit = @('-a', '--all', '--amend', '--no-edit', '--no-verify', '-n', '-s', '--signoff', '-q', '--quiet', '-v', '--verbose', '--allow-empty', '-o', '--only', '-i', '--include', '--pathspec-file-nul')
182
+ add = @('-A', '--all', '-u', '--update', '-f', '--force', '-N', '--intent-to-add', '-n', '--dry-run', '-v', '--verbose', '--pathspec-file-nul')
183
+ }
184
+ $absolutePattern = '^([A-Za-z]:[\\/]|/)'
185
+ $paths = [System.Collections.Generic.List[string]]::new()
186
+ foreach ($subcommand in @('add', 'commit')) {
187
+ foreach ($match in @(Get-CommandLineInvocation -CommandText $Command -CommandWord 'git' -SubcommandPath @($subcommand))) {
188
+ if ($match.Status -ne 'Structural') { continue }
189
+ $walked = [System.Collections.Generic.List[string]]::new()
190
+ $optionValues = [System.Collections.Generic.List[string]]::new()
191
+ $tokens = if ($match.Segment.ArgumentInjected) { [string[]]@() } else { [string[]]@($match.Segment.Tokens) }
192
+ $afterSeparator = $false
193
+ for ($index = [int]$match.OperandIndex; $index -lt $tokens.Count; $index++) {
194
+ $token = $tokens[$index]
195
+ if (-not $afterSeparator -and $token -match '^(<|>|\d*>|&>)') {
196
+ if ($token -match '^(\d*[<>]{1,2}&?|&>{1,2})$') { $index++ }
197
+ continue
198
+ }
199
+ if ($afterSeparator -or -not $token.StartsWith('-')) { $walked.Add($token); continue }
200
+ if ($token -ceq '--') { $afterSeparator = $true; continue }
201
+ $name = if ($token.IndexOf('=') -gt 0) { $token.Substring(0, $token.IndexOf('=')) } else { $token }
202
+ if ($valueOptions[$subcommand] -ccontains $name) {
203
+ if ($name -ceq $token -and $index + 1 -lt $tokens.Count) { $index++; $optionValues.Add($tokens[$index]) }
204
+ continue
205
+ }
206
+ if ($token -match '^-[^-].' -and $valueOptions[$subcommand] -ccontains $token.Substring(0, 2)) { continue }
207
+ if ($flagOptions[$subcommand] -ccontains $token) { continue }
208
+ break
209
+ }
210
+ $candidates = @(@($match.GlobalOptions) | Where-Object { $_.Name -ceq '-C' -and $_.Value } | ForEach-Object { [string]$_.Value })
211
+ $candidates += @(@($match.Operands) | Where-Object { $_ -match $absolutePattern -and $optionValues -cnotcontains $_ })
212
+ $candidates += @($walked | Where-Object { $_ -match $absolutePattern -and (Test-CommandLineTokenLiteral -Token $_) })
213
+ foreach ($candidate in $candidates) {
214
+ if ($paths -notcontains $candidate) { $paths.Add($candidate) }
215
+ }
216
+ }
217
+ }
218
+ return [string[]]$paths.ToArray()
219
+ }
220
+
221
+ function Resolve-OrchestrationGateTarget {
222
+ <#
223
+ .SYNOPSIS
224
+ Resolves the worktree whose checkpoint governs a single-feature path or command leg (issue #1010).
225
+ .DESCRIPTION
226
+ Calls Resolve-HookTargetWorktree with the recording invoker: a file path is passed as
227
+ -FilePath, the marker paths of an apply_patch body as -FilePath (markers in two
228
+ worktrees resolve as ambiguous), and any other command's
229
+ Get-OrchestrationGateCommandOperandPath result as -CommandOperandPath. A failed or
230
+ empty listing gives NoTarget instead of the session-root fallback; Ambiguous gives
231
+ Ambiguous; a null root gives NoTarget; otherwise SessionRoot or OtherWorktree.
232
+ .OUTPUTS
233
+ System.Management.Automation.PSCustomObject with Status, WorktreeRoot, ReasonCode, and Detail.
234
+ #>
235
+ [CmdletBinding()]
236
+ [OutputType([pscustomobject])]
237
+ param(
238
+ [AllowNull()][AllowEmptyString()][string] $FilePath = '',
239
+ [AllowNull()][AllowEmptyString()][string] $Command = ''
240
+ )
241
+
242
+ $sessionRoot = (Get-Location).Path
243
+ $script:OrchestrationGateWorktreeListFailed = $false
244
+ $invoker = { param($SessionRoot) Invoke-OrchestrationGateWorktreeList -SessionRoot $SessionRoot }
245
+ $markerPaths = if ($FilePath) { @() } else { @(Get-OrchestrationPatchMarkerPath -PatchText ([string]$Command)) }
246
+ $resolved = if ($FilePath) {
247
+ Resolve-HookTargetWorktree -SessionRoot $sessionRoot -FilePath @($FilePath) -WorktreeListInvoker $invoker
248
+ } elseif ($markerPaths.Count -gt 0) {
249
+ Resolve-HookTargetWorktree -SessionRoot $sessionRoot -FilePath $markerPaths -WorktreeListInvoker $invoker
250
+ } else {
251
+ Resolve-HookTargetWorktree -SessionRoot $sessionRoot -CommandOperandPath @(Get-OrchestrationGateCommandOperandPath -Command ([string]$Command)) -WorktreeListInvoker $invoker
252
+ }
253
+ $detail = "resolver source $($resolved.Source): $($resolved.Detail)"
254
+ if ($script:OrchestrationGateWorktreeListFailed) {
255
+ return [pscustomobject]@{ Status = 'NoTarget'; WorktreeRoot = $null; ReasonCode = $script:OrchestrationGateNotDerivableReasonCode; Detail = "the worktree listing failed or named no worktree; $detail" }
256
+ }
257
+ if ($resolved.Ambiguous) {
258
+ return [pscustomobject]@{ Status = 'Ambiguous'; WorktreeRoot = $null; ReasonCode = $script:OrchestrationGateAmbiguousReasonCode; Detail = $detail }
259
+ }
260
+ if ([string]::IsNullOrEmpty([string]$resolved.WorktreeRoot)) {
261
+ return [pscustomobject]@{ Status = 'NoTarget'; WorktreeRoot = $null; ReasonCode = $script:OrchestrationGateNotDerivableReasonCode; Detail = $detail }
262
+ }
263
+ $isSession = [string]::Equals([string]$resolved.WorktreeRoot, (ConvertTo-HookTargetWorktreeNormalizedPath -Path $sessionRoot), [System.StringComparison]::OrdinalIgnoreCase)
264
+ $status = if ($isSession) { 'SessionRoot' } else { 'OtherWorktree' }
265
+ return [pscustomobject]@{ Status = $status; WorktreeRoot = [string]$resolved.WorktreeRoot; ReasonCode = $null; Detail = $detail }
266
+ }
267
+
268
+ function Read-OrchestrationGateCheckpoint {
269
+ <#
270
+ .SYNOPSIS
271
+ Reads the checkpoint beneath the resolved target worktree, or returns the deny reason.
272
+ .DESCRIPTION
273
+ Resolved is $false, with DenyReason set, when the target is NoTarget or Ambiguous.
274
+ Otherwise Raw holds the checkpoint text (empty when absent) and Path its absolute
275
+ path, composed beneath the resolved root with a forward slash.
276
+ .OUTPUTS
277
+ System.Management.Automation.PSCustomObject
278
+ #>
279
+ [CmdletBinding()]
280
+ [OutputType([pscustomobject])]
281
+ param(
282
+ [Parameter(Mandatory)][string] $Mode,
283
+ [AllowNull()][AllowEmptyString()][string] $FilePath = '',
284
+ [AllowNull()][AllowEmptyString()][string] $Command = ''
285
+ )
286
+
287
+ $target = Resolve-OrchestrationGateTarget -FilePath $FilePath -Command $Command
288
+ if ($target.Status -eq 'NoTarget' -or $target.Status -eq 'Ambiguous') {
289
+ return [pscustomobject]@{
290
+ Resolved = $false
291
+ Raw = ''
292
+ Path = $null
293
+ DenyReason = ("PREIMPLEMENTATION_GATE_BLOCKED: $($target.ReasonCode): the target worktree of this $Mode call " +
294
+ "could not be resolved: $($target.Detail). Implementation operations require an identifiable target " +
295
+ 'worktree whose checkpoint is ready.')
296
+ }
297
+ }
298
+
299
+ $path = ([string]$target.WorktreeRoot).TrimEnd('/') + '/artifacts/orchestration/orchestrator-state.json'
300
+ return [pscustomobject]@{
301
+ Resolved = $true
302
+ Raw = [string](Get-CheckpointContent -Path $path)
303
+ Path = $path
304
+ DenyReason = $null
305
+ }
306
+ }
307
+
71
308
  function Get-OrchestrationEpicScopeSelector {
72
309
  <#
73
310
  .SYNOPSIS
@@ -33,8 +33,6 @@ try { . (Join-Path $PSScriptRoot 'enforce-orchestration-preimplementation-gate-e
33
33
  # Shared command-line parser (issue #545): per-segment scan text and structural matching.
34
34
  try { . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-scanner.ps1' -ErrorRecord $_ }
35
35
  try { . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-invocation.ps1' -ErrorRecord $_ }
36
- # The readiness checkpoint this gate reads and names in its block message.
37
- $script:CheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
38
36
 
39
37
  # Every orchestration checkpoint a planner or orchestrator surface writes. Writing one
40
38
  # of these is orchestration bookkeeping, not implementation, so the gate must not
@@ -270,7 +268,7 @@ function Test-OrchestrationReady {
270
268
  }
271
269
  $lifecycleReady = $false
272
270
  if ($Payload.PSObject.Properties.Name -contains 'lifecycle_ready') {
273
- $lifecycleReady = [bool]$Payload.lifecycle_ready
271
+ $lifecycleReady = ($Payload.lifecycle_ready -is [bool]) -and $Payload.lifecycle_ready
274
272
  }
275
273
 
276
274
  if (-not $issueNum -or -not $featureFolder -or -not $routeId -or -not $lifecycleReady) {
@@ -284,17 +282,6 @@ function Test-OrchestrationReady {
284
282
  )
285
283
  }
286
284
 
287
- function Get-CheckpointContent {
288
- [CmdletBinding()]
289
- [OutputType([string])]
290
- param()
291
-
292
- if (-not (Test-Path -LiteralPath $script:CheckpointPath)) {
293
- return ''
294
- }
295
- return Get-Content -Raw -LiteralPath $script:CheckpointPath
296
- }
297
-
298
285
  function Get-OrchestrationPreimplementationGateAllowDecision {
299
286
  [CmdletBinding()]
300
287
  [OutputType([System.Collections.Specialized.OrderedDictionary])]
@@ -445,8 +432,12 @@ function Invoke-OrchestrationPreimplementationGateDecision {
445
432
  Get-OrchestrationModeDenyReason -Mode $mode -Failure $failure)
446
433
  }
447
434
 
435
+ # Truthiness fall-through is kept: an empty -CheckpointRaw resolves like an unbound one.
436
+ # The checkpoint is read beneath the worktree the call targets (issue #1010).
448
437
  if (-not $CheckpointRaw) {
449
- $CheckpointRaw = Get-CheckpointContent
438
+ $read = Read-OrchestrationGateCheckpoint -Mode 'single-feature' -FilePath $filePath -Command $command
439
+ if (-not $read.Resolved) { return Get-OrchestrationPreimplementationGateBlockDecision -Reason $read.DenyReason }
440
+ $CheckpointRaw = $read.Raw
450
441
  }
451
442
  try {
452
443
  $checkpoint = ConvertFrom-CheckpointJson -Json $CheckpointRaw
@@ -8,14 +8,26 @@
8
8
  operation. When the target file is a PowerShell source file (.ps1, .psm1, .psd1),
9
9
  it decides whether the change may proceed in direct mode.
10
10
 
11
- Direct mode counts distinct production PowerShell paths per Codex session. The
12
- running set is persisted under .codex/state/powershell-batch-budget.<session_id>.json,
13
- and repeated edits to the same file are counted once. The 4th distinct production
14
- path is denied with a POWERSHELL_LARGE_PATH_REQUIRED reason that instructs the
15
- caller to route the change through .codex/prompts/orchestrate-work.md.
16
-
17
- The orchestrated large path is detected from
18
- <root>/artifacts/orchestration/orchestrator-state.json. The selected route is the
11
+ Direct mode counts distinct production PowerShell paths per Codex session and per
12
+ resolved target worktree, and repeated edits to the same file are counted once. The
13
+ 4th distinct production path in one worktree is denied with a
14
+ POWERSHELL_LARGE_PATH_REQUIRED reason that instructs the caller to route the change
15
+ through .codex/prompts/orchestrate-work.md.
16
+
17
+ Each evaluated path is placed with Resolve-HookTargetWorktree from the dot-sourced
18
+ hook-target-worktree.ps1. An absolute path resolves to its nearest enclosing
19
+ registered git worktree; a relative path, a path inside no registered worktree, and
20
+ an ambiguous result use the session root. 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
+ .codex/state/powershell-batch-budget.<session_id>.json when the resolved target worktree
24
+ is the session root and in .codex/state/powershell-batch-budget.<session_id>.<worktree key>.json
25
+ otherwise; no state is written under another worktree. A path under the root-level
26
+ artifacts/ directory of the resolved target worktree is never counted and writes no
27
+ state. No containment filter applies: a path outside every registered worktree is
28
+ counted against the session root, as before.
29
+
30
+ The selected route is the
19
31
  route_id value when that key is present, otherwise the path_selected value, and it
20
32
  is usable only as a non-blank string. When the selected route is large,
21
33
  remediation, or preparation and the checkpoint is not terminal (next_step is not
@@ -36,9 +48,12 @@
36
48
  hookSpecificOutput.permissionDecision = 'deny' and exits 0. Files already counted
37
49
  are always allowed through.
38
50
 
39
- Known limitation: a stale non-terminal large-path checkpoint left at the root
40
- exempts a later direct-mode session at that root. Orchestrator checkpoint hygiene
41
- (issue #673) moves foreign checkpoints aside before a new run.
51
+ Known limitations: an 8.3 short-name path, or a path reached through a junction,
52
+ that differs textually from the long path git reports matches no registered
53
+ worktree and falls back to the session root. A stale non-terminal large-path
54
+ checkpoint in a worktree exempts a later direct-mode session editing that worktree;
55
+ orchestrator checkpoint hygiene (issue #673) moves foreign checkpoints aside before
56
+ a new run.
42
57
 
43
58
  .NOTES
44
59
  Compatible with PowerShell 7+.
@@ -51,6 +66,9 @@ $script:HookDependencyGuardLoadFailed = $false; try { . (Join-Path $PSScriptRoot
51
66
  # mapping for every tool name the ^(apply_patch|Edit|Write)$ matcher admits.
52
67
  try { . (Join-Path $PSScriptRoot 'codex-pretooluse-file-mapping.ps1') } catch { Add-HookDependencyFailure -Name 'codex-pretooluse-file-mapping.ps1' -ErrorRecord $_ }
53
68
 
69
+ # Shared target-worktree resolver; the Codex twin of HookTargetWorktree.psm1.
70
+ try { . (Join-Path $PSScriptRoot 'hook-target-worktree.ps1') } catch { Add-HookDependencyFailure -Name 'hook-target-worktree.ps1' -ErrorRecord $_ }
71
+
54
72
  # Shared route helpers; this file is byte-identical to the Claude runtime copy.
55
73
  $script:PowerShellBatchBudgetRouteImportFailure = $null; try { . (Join-Path $PSScriptRoot 'enforce-batch-budget-route.ps1') } catch { $script:PowerShellBatchBudgetRouteImportFailure = 'enforce-batch-budget-route.ps1' }
56
74
 
@@ -194,7 +212,8 @@ function Invoke-PowerShellBatchBudgetHook {
194
212
  [scriptblock] $WriteState = {
195
213
  param([string] $Path, [System.Collections.IDictionary] $State)
196
214
  $State | ConvertTo-Json -Depth 5 | Set-Content -Path $Path -Encoding UTF8
197
- }
215
+ },
216
+ [scriptblock] $WorktreeListInvoker
198
217
  )
199
218
 
200
219
  if (-not $ToolInputRaw) {
@@ -218,12 +237,31 @@ function Invoke-PowerShellBatchBudgetHook {
218
237
  }
219
238
  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." }
220
239
 
240
+ # The path is evaluated against the registered worktree that holds it; the session root
241
+ # is the fallback for a relative path, a path in no registered worktree, and an ambiguous result.
242
+ $resolverArguments = @{ SessionRoot = $Root; FilePath = @($filePath) }
243
+ if ($PSBoundParameters.ContainsKey('WorktreeListInvoker')) { $resolverArguments['WorktreeListInvoker'] = $WorktreeListInvoker }
244
+ $resolution = Resolve-HookTargetWorktree @resolverArguments
245
+ $targetRoot = $Root
246
+ $isSessionRoot = $true
247
+ if (-not $resolution.Ambiguous -and $null -ne $resolution.WorktreeRoot -and -not [string]::Equals($resolution.WorktreeRoot, (ConvertTo-HookTargetWorktreeNormalizedPath -Path $Root), [System.StringComparison]::OrdinalIgnoreCase)) {
248
+ $targetRoot = $resolution.WorktreeRoot
249
+ $isSessionRoot = $false
250
+ }
251
+
252
+ # The root-level artifacts/ directory is gitignored scratch, never a production file.
253
+ $relativePath = if ($normalized -match '^(/|[A-Za-z]:/)') { Get-HookTargetWorktreeRelativePath -Path $normalized -WorktreeRoot $targetRoot } else { $normalized }
254
+ if ($null -ne $relativePath -and $relativePath.StartsWith('artifacts/', [System.StringComparison]::OrdinalIgnoreCase)) {
255
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
256
+ }
257
+
221
258
  $stateDir = Join-Path -Path $Root -ChildPath '.codex/state'
222
- $stateFile = Join-Path -Path $stateDir -ChildPath ("powershell-batch-budget.$SessionId.json")
259
+ $stateLeaf = if ($isSessionRoot) { "powershell-batch-budget.$SessionId.json" } else { "powershell-batch-budget.$SessionId.$(Get-HookTargetWorktreeKey -WorktreeRoot $targetRoot).json" }
260
+ $stateFile = Join-Path -Path $stateDir -ChildPath $stateLeaf
223
261
 
224
262
  # The checkpoint is read before any state operation, so the large path neither
225
263
  # creates the state directory nor reads or writes the state file.
226
- $checkpointPath = Join-Path -Path $Root -ChildPath 'artifacts/orchestration/orchestrator-state.json'
264
+ $checkpointPath = Join-Path -Path $targetRoot -ChildPath 'artifacts/orchestration/orchestrator-state.json'
227
265
  $checkpointText = ''
228
266
  try {
229
267
  $checkpointText = [string](& $ReadCheckpoint $checkpointPath)
@@ -279,7 +317,8 @@ function Invoke-PowerShellBatchBudgetCodexEntryPoint {
279
317
  .PARAMETER PayloadRaw
280
318
  Raw PreToolUse payload text.
281
319
  .PARAMETER RepositoryRoot
282
- Root used to locate the Codex state directory and the orchestrator checkpoint.
320
+ Session root: it locates the Codex state directory and is the fallback root. The
321
+ orchestrator checkpoint comes from the resolved target worktree of each path.
283
322
  .PARAMETER HookSeams
284
323
  Optional seam overrides splatted into Invoke-PowerShellBatchBudgetHook.
285
324
  #>