@danmoisan/drm-copilot-mcp 1.1.11 → 1.1.14

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 (66) hide show
  1. package/out/mcp-server.js +368 -209
  2. package/package.json +2 -2
  3. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +1 -1
  4. package/resources/claude-customizations/.claude/agents/epic-planner.md +4 -1
  5. package/resources/claude-customizations/.claude/agents/parallel-planner.md +18 -1
  6. package/resources/claude-customizations/.claude/agents/pr-author.md +15 -0
  7. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +16 -8
  8. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-authorization.ps1 +443 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +33 -37
  10. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +120 -5
  11. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +127 -0
  12. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +172 -24
  13. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +14 -27
  14. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +66 -5
  15. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +134 -10
  16. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +37 -8
  17. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +3 -1
  18. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +332 -0
  19. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +211 -182
  20. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +43 -6
  21. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +486 -0
  22. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +5 -2
  23. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +446 -0
  24. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +1 -0
  25. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeReadiness.psm1 +176 -0
  26. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +369 -0
  27. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +392 -0
  28. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeResolution.psm1 +500 -0
  29. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeTargetResolution.psm1 +347 -0
  30. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +27 -0
  31. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +150 -2
  32. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +5 -5
  33. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +12 -0
  34. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +14 -1
  35. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +15 -0
  36. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +4 -1
  37. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +32 -17
  38. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +39 -6
  39. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +39 -10
  40. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +15 -0
  41. package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +221 -0
  42. package/resources/claude-customizations/config/blast-radius.json +14 -1
  43. package/resources/claude-customizations/pack-manifests/core.json +11 -0
  44. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +13 -14
  45. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  46. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +193 -0
  47. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +15 -5
  48. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-resolution.ps1 +493 -0
  49. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +189 -0
  50. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +172 -24
  51. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +14 -27
  52. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexDeployment.ps1 +87 -0
  53. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +96 -0
  54. package/resources/codex-and-agents-customizations/.codex/scripts/codex-routing-cli-common.ps1 +370 -0
  55. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-contract.ps1 +475 -0
  56. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-runtime.ps1 +445 -0
  57. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-persistence-runtime.ps1 +118 -0
  58. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-sandbox-preflight.ps1 +69 -0
  59. package/resources/codex-and-agents-customizations/.codex/scripts/launch-epic-child-wave.ps1 +471 -0
  60. package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +280 -0
  61. package/resources/codex-and-agents-customizations/.codex/scripts/resume-epic-child.ps1 +263 -0
  62. package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +2 -1
  63. package/resources/codex-and-agents-customizations/pack-manifests/core.json +20 -1
  64. package/resources/lib/codex-routing/CodexDeployment.psm1 +315 -0
  65. package/resources/lib/codex-routing/CodexTopology.psm1 +394 -0
  66. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +37 -0
@@ -0,0 +1,392 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Portable-identity resolution of the item a gated call acts on (issue #673).
4
+
5
+ .DESCRIPTION
6
+ Selects the worktree a call pertains to from portable identity alone: the
7
+ canonical issue number in the call text, matched against the issue-num of each
8
+ live worktree's orchestrator checkpoint, and a branch signal. A feature-folder
9
+ path and a file path are never read as worktree selectors, because a merged
10
+ feature folder exists in every checkout branched from main and so places a call
11
+ in many worktrees at once; it names what the work is, not where it lives.
12
+
13
+ A worktree is live when it is registered in the repository's worktree
14
+ administrative layout and its root still carries a worktree marker, so a deleted
15
+ directory is excluded. No identity, or one matching no live worktree, resolves
16
+ NoTarget; several live matches or disagreeing identities resolve Ambiguous. A
17
+ branch signal is the only tie-breaker, because git checks a branch out in at most
18
+ one worktree; no timestamp or lifecycle step is ever read to break a tie.
19
+
20
+ .NOTES
21
+ PowerShell 7+. Re-uses its siblings for enumeration, the marker test, the ascent,
22
+ the branch reader, normalisation, the join, the result constructor, and both
23
+ reason-code accessors, re-implementing none. Neither reason-code literal appears
24
+ here. Its only filesystem read is the checkpoint-text seam. Mirrored byte-identically.
25
+ CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
26
+ #>
27
+
28
+ Set-StrictMode -Version Latest
29
+ $ErrorActionPreference = 'Stop'
30
+
31
+ Import-Module (Join-Path $PSScriptRoot 'WorktreeResolution.psm1') -ErrorAction Stop
32
+ Import-Module (Join-Path $PSScriptRoot 'WorktreeTargetResolution.psm1') -ErrorAction Stop
33
+
34
+ # The one definition of the checkpoint location relative to a worktree root; every
35
+ # consumer composes an absolute path from it and a resolved root.
36
+ $script:CheckpointRelativePath = 'artifacts/orchestration/orchestrator-state.json'
37
+
38
+ # The canonical issue-number line an orchestrator writes into a delegation prompt.
39
+ # Case-sensitive on purpose: a lowercase near-miss is not the contract sentence.
40
+ $script:IssueSignalPattern = 'Canonical issue number for this feature is #?(?<number>\d+)'
41
+
42
+ # An issue number as a caller may spell it: optional hash, then digits only.
43
+ $script:IssueNumberPattern = '^#?(?<digits>\d+)$'
44
+
45
+ function Get-WorktreeItemCheckpointRelativePath {
46
+ <#
47
+ .SYNOPSIS
48
+ Return the repository-relative path of the orchestrator checkpoint.
49
+ .DESCRIPTION
50
+ The single definition of that literal; an absolute path comes from Get-WorktreeItemCheckpointPath.
51
+ #>
52
+ [CmdletBinding()]
53
+ [OutputType([string])]
54
+ param()
55
+
56
+ return $script:CheckpointRelativePath
57
+ }
58
+
59
+ function Get-WorktreeItemCheckpointPath {
60
+ <#
61
+ .SYNOPSIS
62
+ Compose the absolute checkpoint path under a worktree root.
63
+ .DESCRIPTION
64
+ Returns a forward-slash absolute path, and throws when the root is not absolute:
65
+ a relative root would reintroduce the process-directory binding this removes.
66
+ .PARAMETER WorktreeRoot
67
+ The absolute worktree root, in either separator style.
68
+ #>
69
+ [CmdletBinding()]
70
+ [OutputType([string])]
71
+ param(
72
+ [Parameter(Mandatory = $true)] [string] $WorktreeRoot
73
+ )
74
+
75
+ return (Join-WorktreeResolutionPath -WorktreeRoot $WorktreeRoot -RepoRelativePath (Get-WorktreeItemCheckpointRelativePath))
76
+ }
77
+
78
+ function ConvertTo-WorktreeItemIssueNumber {
79
+ <#
80
+ .SYNOPSIS
81
+ Normalise a value to a positive issue number as a digit string, or $null.
82
+ .DESCRIPTION
83
+ Pure. Accepts an integer, or a string of optional hash plus digits with
84
+ whitespace trimmed, and returns the number without leading zeros. Returns $null
85
+ for anything else, including zero, a negative value, none, and an empty value.
86
+ .PARAMETER Value
87
+ The candidate value, of any type.
88
+ #>
89
+ [CmdletBinding()]
90
+ [OutputType([string])]
91
+ param(
92
+ [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [object] $Value
93
+ )
94
+
95
+ if ($null -eq $Value) { return $null }
96
+ $match = [regex]::Match(([string] $Value).Trim(), $script:IssueNumberPattern)
97
+ if (-not $match.Success) { return $null }
98
+ # A digit run wider than the largest integer is rejected rather than wrapped.
99
+ $number = [long] 0
100
+ if (-not [long]::TryParse($match.Groups['digits'].Value, [ref] $number)) { return $null }
101
+ if ($number -le 0) { return $null }
102
+ return $number.ToString([System.Globalization.CultureInfo]::InvariantCulture)
103
+ }
104
+
105
+ function Find-WorktreeItemIssueSignal {
106
+ <#
107
+ .SYNOPSIS
108
+ Return the distinct issue numbers named by canonical issue lines in a text.
109
+ .DESCRIPTION
110
+ Scans case-sensitively for the canonical issue-number sentence, returning each
111
+ distinct number in first-appearance order as one object a caller assigns first.
112
+ .PARAMETER Text
113
+ The prompt or command text to scan.
114
+ #>
115
+ [CmdletBinding()]
116
+ [OutputType([string[]], [object[]])]
117
+ param(
118
+ [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Text
119
+ )
120
+
121
+ $found = [System.Collections.Generic.List[string]]::new()
122
+ # Collect each canonical line's number in first-appearance order, discarding a
123
+ # repeat. Order matters: the ambiguity detail lists them as the caller wrote them.
124
+ foreach ($match in [regex]::Matches([string] $Text, $script:IssueSignalPattern)) {
125
+ $number = ConvertTo-WorktreeItemIssueNumber -Value $match.Groups['number'].Value
126
+ if ($null -ne $number -and -not $found.Contains($number)) { $found.Add($number) }
127
+ }
128
+ return , [string[]] $found.ToArray()
129
+ }
130
+
131
+ function Get-WorktreeItemCheckpointText {
132
+ <#
133
+ .SYNOPSIS
134
+ Return the raw text of a file, or $null when it is absent or unreadable.
135
+ .DESCRIPTION
136
+ The module's only filesystem read, isolated as the seam a test mocks.
137
+ .PARAMETER Path
138
+ The absolute path to read.
139
+ #>
140
+ [CmdletBinding()]
141
+ [OutputType([string])]
142
+ param(
143
+ [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Path
144
+ )
145
+
146
+ if ([string]::IsNullOrWhiteSpace($Path)) { return $null }
147
+ if (-not (Test-Path -LiteralPath $Path -PathType Leaf -ErrorAction SilentlyContinue)) { return $null }
148
+ try { return [System.IO.File]::ReadAllText($Path) } catch { return $null }
149
+ }
150
+
151
+ function Get-WorktreeItemCheckpointIssue {
152
+ <#
153
+ .SYNOPSIS
154
+ Return the issue number recorded by a worktree's checkpoint, or $null.
155
+ .DESCRIPTION
156
+ Reads only the canonical checkpoint path beneath the given root, so an archived
157
+ checkpoint is never matched. Returns $null when the file is absent, empty,
158
+ unparseable, not an object, or carries no issue-num key.
159
+ .PARAMETER WorktreeRoot
160
+ The absolute worktree root whose checkpoint is read.
161
+ #>
162
+ [CmdletBinding()]
163
+ [OutputType([string])]
164
+ param(
165
+ [Parameter(Mandatory = $true)] [string] $WorktreeRoot
166
+ )
167
+
168
+ $text = Get-WorktreeItemCheckpointText -Path (Get-WorktreeItemCheckpointPath -WorktreeRoot $WorktreeRoot)
169
+ if ([string]::IsNullOrWhiteSpace($text)) { return $null }
170
+ $payload = $null
171
+ try { $payload = $text | ConvertFrom-Json } catch { return $null }
172
+ # A parse that yielded nothing, one that yielded a scalar or array, and a missing
173
+ # key all mean the same to a caller: this worktree records no identifiable issue.
174
+ if ($null -eq $payload -or $payload -isnot [pscustomobject]) { return $null }
175
+ if ($payload.PSObject.Properties.Name -notcontains 'issue-num') { return $null }
176
+ return (ConvertTo-WorktreeItemIssueNumber -Value $payload.'issue-num')
177
+ }
178
+
179
+ function Get-WorktreeItemLiveRoot {
180
+ <#
181
+ .SYNOPSIS
182
+ Return the live worktree roots reachable from a session path.
183
+ .DESCRIPTION
184
+ Seam. Ascends from the session path to its enclosing worktree root, enumerates
185
+ the registered roots, and keeps those still carrying a worktree marker. Empty
186
+ when the path lies inside no worktree, and returned as one object.
187
+ .PARAMETER SessionRoot
188
+ The calling process's path, used only to find the repository.
189
+ .PARAMETER Branch
190
+ Optional. Keep only the roots that have this branch checked out.
191
+ #>
192
+ [CmdletBinding()]
193
+ [OutputType([string[]], [object[]])]
194
+ param(
195
+ [Parameter(Mandatory = $true)] [AllowEmptyString()] [string] $SessionRoot,
196
+ [string] $Branch
197
+ )
198
+
199
+ $ascended = Find-WorktreeResolutionRoot -Path $SessionRoot
200
+ if ($null -eq $ascended) { return , [string[]] @() }
201
+ # The enumerator applies the branch filter, being the only code reading each HEAD.
202
+ $registered = if ([string]::IsNullOrWhiteSpace($Branch)) {
203
+ Get-WorktreeResolutionWorktreeRoot -SessionRoot $ascended
204
+ }
205
+ else {
206
+ Get-WorktreeResolutionWorktreeRoot -SessionRoot $ascended -Branch $Branch
207
+ }
208
+ # Drop a registration whose directory is gone; it can hold no checkpoint to match.
209
+ $live = [System.Collections.Generic.List[string]]::new()
210
+ foreach ($root in @($registered)) {
211
+ if (Test-WorktreeResolutionRootMarker -Path $root) { $live.Add($root) }
212
+ }
213
+ return , [string[]] $live.ToArray()
214
+ }
215
+
216
+ # Private: convert a resolved root and session path into a result, labelling it
217
+ # SessionRoot or OtherWorktree so that test is written once, as the sibling normalisation
218
+ # constructor does. The ConvertTo verb is deliberate: a New- verb is state-changing to
219
+ # PSScriptAnalyzer and this function changes nothing.
220
+ function ConvertTo-WorktreeItemResolvedResult {
221
+ [CmdletBinding()]
222
+ [OutputType([pscustomobject])]
223
+ param(
224
+ [Parameter(Mandatory = $true)] [string] $WorktreeRoot,
225
+ [Parameter(Mandatory = $true)] [string] $SessionRoot,
226
+ [Parameter(Mandatory = $true)] [string] $Detail,
227
+ [string] $Branch
228
+ )
229
+
230
+ $root = ConvertTo-WorktreeResolutionNormalizedPath -Path $WorktreeRoot
231
+ # The root is the session's own when it equals the session path or the worktree that
232
+ # path sits inside. The ascent runs only on failure, because it reaches the disk.
233
+ $isSession = ($null -ne $root -and $root -eq (ConvertTo-WorktreeResolutionNormalizedPath -Path $SessionRoot))
234
+ if (-not $isSession) {
235
+ $ascended = Find-WorktreeResolutionRoot -Path $SessionRoot
236
+ $isSession = ($null -ne $ascended -and $root -eq $ascended)
237
+ }
238
+ $status = if ($isSession) { 'SessionRoot' } else { 'OtherWorktree' }
239
+ $signal = if ([string]::IsNullOrWhiteSpace($Branch)) { '' } else { 'Branch' }
240
+ return (New-WorktreeResolutionTargetResult -Status $status -SessionRoot $SessionRoot `
241
+ -WorktreeRoot $WorktreeRoot -Signal $signal -SignalValue $Branch -Candidate @($WorktreeRoot) -Detail $Detail)
242
+ }
243
+
244
+ # Private: decide the target when the call carries a branch signal. A branch is checked
245
+ # out in at most one worktree, so two live matches is repository state, not caller error.
246
+ function Resolve-WorktreeItemTargetByBranch {
247
+ [CmdletBinding()]
248
+ [OutputType([pscustomobject])]
249
+ param(
250
+ [Parameter(Mandatory = $true)] [string] $Session,
251
+ [Parameter(Mandatory = $true)] [string] $Branch,
252
+ [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [string[]] $Issue
253
+ )
254
+
255
+ # Assigned before wrapping, because the seam returns its array as one object;
256
+ # wrapping the call would yield a one-element array holding the array.
257
+ $branchSignalRoots = Get-WorktreeItemLiveRoot -SessionRoot $Session -Branch $Branch
258
+ $branchRoots = @($branchSignalRoots)
259
+ # Zero live matches names no place to act on; more than one is a double checkout.
260
+ if ($branchRoots.Count -eq 0) {
261
+ return (New-WorktreeResolutionTargetResult -Status 'NoTarget' -SessionRoot $Session -Detail (
262
+ "branch '{0}' is checked out in no live worktree" -f $Branch))
263
+ }
264
+ if ($branchRoots.Count -gt 1) {
265
+ return (New-WorktreeResolutionTargetResult -Status 'Ambiguous' -SessionRoot $Session `
266
+ -Signal 'Branch' -SignalValue $Branch -Candidate $branchRoots -Detail (
267
+ "branch '{0}' is checked out in {1} live worktrees" -f $Branch, $branchRoots.Count))
268
+ }
269
+
270
+ $target = $branchRoots[0]
271
+ $branchOnly = "branch '{0}' resolves to the live worktree that has it checked out" -f $Branch
272
+ if ($Issue.Count -eq 0) {
273
+ return (ConvertTo-WorktreeItemResolvedResult -WorktreeRoot $target -SessionRoot $Session -Branch $Branch -Detail $branchOnly)
274
+ }
275
+
276
+ $number = $Issue[0]
277
+ $recorded = Get-WorktreeItemCheckpointIssue -WorktreeRoot $target
278
+ # The branch worktree agrees with the issue, disagrees, or records none. Agreement
279
+ # breaks a stale-attempt tie; disagreement must not be guessed past.
280
+ if ($null -ne $recorded) {
281
+ if ($recorded -eq $number) {
282
+ return (ConvertTo-WorktreeItemResolvedResult -WorktreeRoot $target -SessionRoot $Session -Branch $Branch -Detail (
283
+ "branch '{0}' and issue {1} resolve to the same live worktree" -f $Branch, $number))
284
+ }
285
+ return (New-WorktreeResolutionTargetResult -Status 'Ambiguous' -SessionRoot $Session `
286
+ -Signal 'Branch' -SignalValue $Branch -Candidate @($target) -Detail (
287
+ "branch '{0}' places the call in a worktree whose checkpoint records issue {1}, not issue {2}" -f $Branch, $recorded, $number))
288
+ }
289
+
290
+ # The branch worktree records no issue, so the number is checked against the other
291
+ # live worktrees; a match there means the two signals disagree about placement.
292
+ $elsewhere = [System.Collections.Generic.List[string]]::new()
293
+ $liveRoots = Get-WorktreeItemLiveRoot -SessionRoot $Session
294
+ foreach ($root in @($liveRoots)) {
295
+ if ($root -ne $target -and (Get-WorktreeItemCheckpointIssue -WorktreeRoot $root) -eq $number) { $elsewhere.Add($root) }
296
+ }
297
+ if ($elsewhere.Count -gt 0) {
298
+ return (New-WorktreeResolutionTargetResult -Status 'Ambiguous' -SessionRoot $Session `
299
+ -Signal 'Branch' -SignalValue $Branch -Candidate (@($target) + $elsewhere.ToArray()) -Detail (
300
+ "branch '{0}' and issue {1} place the call in different worktrees" -f $Branch, $number))
301
+ }
302
+ return (ConvertTo-WorktreeItemResolvedResult -WorktreeRoot $target -SessionRoot $Session -Branch $Branch -Detail $branchOnly)
303
+ }
304
+
305
+ # Private: decide the target when the call carries an issue number and no branch. Two
306
+ # live worktrees recording one issue is stale-attempt, and its detail names both remedies.
307
+ function Resolve-WorktreeItemTargetByIssue {
308
+ [CmdletBinding()]
309
+ [OutputType([pscustomobject])]
310
+ param(
311
+ [Parameter(Mandatory = $true)] [string] $Session,
312
+ [Parameter(Mandatory = $true)] [string] $Issue
313
+ )
314
+
315
+ # Keep every live worktree whose own checkpoint records this issue. An absent or
316
+ # unreadable checkpoint records nothing, which becomes a deny, not a silent pick.
317
+ $matched = [System.Collections.Generic.List[string]]::new()
318
+ $liveRoots = Get-WorktreeItemLiveRoot -SessionRoot $Session
319
+ foreach ($root in @($liveRoots)) {
320
+ if ((Get-WorktreeItemCheckpointIssue -WorktreeRoot $root) -eq $Issue) { $matched.Add($root) }
321
+ }
322
+
323
+ # Zero matches denies for want of a place, one resolves, more is stale-attempt.
324
+ if ($matched.Count -eq 0) {
325
+ return (New-WorktreeResolutionTargetResult -Status 'NoTarget' -SessionRoot $Session -Detail (
326
+ "issue {0} is recorded in the orchestrator checkpoint of no live worktree" -f $Issue))
327
+ }
328
+ if ($matched.Count -eq 1) {
329
+ return (ConvertTo-WorktreeItemResolvedResult -WorktreeRoot $matched[0] -SessionRoot $Session -Detail (
330
+ "issue {0} resolves to the live worktree recording it" -f $Issue))
331
+ }
332
+ $detail = ("issue {0} is recorded in the orchestrator checkpoints of {1} live worktrees; " -f $Issue, $matched.Count) +
333
+ 'move the stale checkpoint to artifacts/orchestration/handoff/ or add a branch: label naming the item''s branch'
334
+ return (New-WorktreeResolutionTargetResult -Status 'Ambiguous' -SessionRoot $Session -Candidate $matched.ToArray() -Detail $detail)
335
+ }
336
+
337
+ function Resolve-WorktreeItemTarget {
338
+ <#
339
+ .SYNOPSIS
340
+ Resolve the worktree a gated call acts on, from portable identity only.
341
+ .DESCRIPTION
342
+ Reads the canonical issue number and a branch signal from the call text and
343
+ decides one of the four target states. No path signal is read. The session path
344
+ only finds the repository and labels a resolved result SessionRoot.
345
+ .PARAMETER Text
346
+ The prompt or command text to read identity from.
347
+ .PARAMETER SessionRoot
348
+ Optional. The calling process's path. Defaults to the current location.
349
+ #>
350
+ [CmdletBinding()]
351
+ [OutputType([pscustomobject])]
352
+ param(
353
+ [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Text,
354
+ [string] $SessionRoot
355
+ )
356
+
357
+ $session = if ([string]::IsNullOrWhiteSpace($SessionRoot)) { (Get-Location).ProviderPath } else { $SessionRoot }
358
+ # Assigned before it is wrapped, for the reason recorded in the branch resolver.
359
+ $issueSignal = Find-WorktreeItemIssueSignal -Text $Text
360
+ $issues = @($issueSignal)
361
+ $branch = Find-WorktreeResolutionBranchSignal -Text $Text
362
+ $hasBranch = -not [string]::IsNullOrWhiteSpace($branch)
363
+
364
+ # Two identity-shape verdicts precede any enumeration or file read: several issue
365
+ # numbers cannot name one item, and no identity cannot name any.
366
+ if ($issues.Count -gt 1) {
367
+ return (New-WorktreeResolutionTargetResult -Status 'Ambiguous' -SessionRoot $session -Detail (
368
+ "the call names {0} different issue numbers ({1}), and an item has exactly one" -f $issues.Count, ($issues -join ', ')))
369
+ }
370
+ if ($issues.Count -eq 0 -and -not $hasBranch) {
371
+ return (New-WorktreeResolutionTargetResult -Status 'NoTarget' -SessionRoot $session -Detail (
372
+ 'the call carries neither a canonical issue number line nor a branch signal ' +
373
+ '(--head, --branch, or branch:), so the item it acts on cannot be identified'))
374
+ }
375
+
376
+ # A branch signal is authoritative about placement and can break a stale-attempt
377
+ # tie, so it selects the branch-present rows; otherwise the issue number does.
378
+ if ($hasBranch) {
379
+ return (Resolve-WorktreeItemTargetByBranch -Session $session -Branch $branch -Issue $issues)
380
+ }
381
+ return (Resolve-WorktreeItemTargetByIssue -Session $session -Issue $issues[0])
382
+ }
383
+
384
+ Export-ModuleMember -Function `
385
+ Get-WorktreeItemCheckpointRelativePath, `
386
+ Get-WorktreeItemCheckpointPath, `
387
+ Find-WorktreeItemIssueSignal, `
388
+ ConvertTo-WorktreeItemIssueNumber, `
389
+ Get-WorktreeItemCheckpointText, `
390
+ Get-WorktreeItemCheckpointIssue, `
391
+ Get-WorktreeItemLiveRoot, `
392
+ Resolve-WorktreeItemTarget