@danmoisan/drm-copilot-mcp 1.0.21 → 1.0.23

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 (43) hide show
  1. package/out/mcp-server.js +1730 -139
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/MEMORY.md +5 -1
  4. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_commit_push_memory_before_pr.md +48 -2
  5. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_no_sendmessage_tool.md +35 -0
  6. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_worktree_isolation_branches_from_main.md +45 -0
  7. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +257 -0
  8. package/resources/claude-customizations/.claude/agents/parallel-planner.md +183 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +23 -11
  10. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +259 -0
  11. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +499 -0
  12. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate-helpers.ps1 +302 -0
  13. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +359 -0
  14. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +244 -0
  15. package/resources/claude-customizations/.claude/lib/bash/compute-cohorts.sh +143 -0
  16. package/resources/claude-customizations/.claude/lib/bash/compute-concurrency-batches.sh +122 -0
  17. package/resources/claude-customizations/.claude/lib/bash/parallel-cohorts.sh +330 -0
  18. package/resources/claude-customizations/.claude/lib/bash/parallel-common.sh +238 -0
  19. package/resources/claude-customizations/.claude/lib/bash/parallel-items-validate.sh +244 -0
  20. package/resources/claude-customizations/.claude/lib/bash/parallel-manifest-validate.sh +187 -0
  21. package/resources/claude-customizations/.claude/lib/bash/parallel-yaml-emit.sh +340 -0
  22. package/resources/claude-customizations/.claude/lib/bash/parallel-yaml-scan.sh +335 -0
  23. package/resources/claude-customizations/.claude/lib/bash/validate-parallel-manifest.sh +134 -0
  24. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +379 -0
  25. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +491 -0
  26. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +490 -0
  27. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +429 -0
  28. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +366 -0
  29. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +184 -0
  30. package/resources/claude-customizations/.claude/rules/shell.md +7 -2
  31. package/resources/claude-customizations/.claude/settings.json +28 -0
  32. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +152 -0
  33. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +93 -0
  34. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +971 -0
  35. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +461 -0
  36. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +176 -0
  37. package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +56 -0
  38. package/resources/claude-customizations/config/blast-radius.json +16 -0
  39. package/resources/claude-customizations/config/orchestration-routing.json +355 -0
  40. package/resources/claude-customizations/pack-manifests/core.json +32 -1
  41. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  42. package/resources/config/orchestration-routing.json +22 -0
  43. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +29 -0
@@ -0,0 +1,244 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Pre-tool-use hook that gates git worktree remove behind parallel checkpoint merge state.
4
+
5
+ .DESCRIPTION
6
+ Invoked by the Claude Code PreToolUse hook on the "Bash" matcher before any Bash
7
+ command runs. Regex-matches git worktree remove against CLAUDE_TOOL_INPUT.command,
8
+ extracts the target worktree path argument, reads
9
+ artifacts/orchestration/parallel-orchestrator-state.json, and finds the items[] record
10
+ whose worktree_path matches. Allows removal only when that record's merge_status is
11
+ merged or worktree_removed. Denies with reason PARALLEL_WORKTREE_REMOVAL_BLOCKED when
12
+ the checkpoint is unreadable, no matching record exists, or merge_status is anything
13
+ else - fail-closed, following the enforce-epic-worktree-removal-gate.ps1 precedent of
14
+ treating an unreadable/no-match checkpoint as deny.
15
+
16
+ Adapted from enforce-epic-worktree-removal-gate.ps1. The command interception regexes
17
+ and the path normalization are unchanged; the checkpoint path, the read seam name, and
18
+ the record collection differ, because the parallel surface records per-item state in
19
+ items[] rather than features[]. A parallel run has no integration branch: each item
20
+ opens its own pull request against main, so a removed worktree is unrecoverable work
21
+ unless that item's own merge has been durably confirmed.
22
+
23
+ .NOTES
24
+ Compatible with PowerShell 7+. No external module dependencies. Filesystem reads go
25
+ through an injectable wrapper function so tests can mock the boundary without writing
26
+ temporary files.
27
+ #>
28
+ [CmdletBinding()]
29
+ param()
30
+
31
+ $script:ParallelCheckpointPath = 'artifacts/orchestration/parallel-orchestrator-state.json'
32
+ $script:AllowedMergeStatuses = @('merged', 'worktree_removed')
33
+
34
+ function Get-ParallelWorktreeRemovalGateCheckpointContent {
35
+ <#
36
+ .SYNOPSIS
37
+ Read the raw JSON text of the parallel checkpoint. Tests mock this function
38
+ (read seam).
39
+ .OUTPUTS
40
+ System.String or $null
41
+ #>
42
+ [CmdletBinding()]
43
+ [OutputType([string])]
44
+ param()
45
+
46
+ if (-not (Test-Path -LiteralPath $script:ParallelCheckpointPath -PathType Leaf)) {
47
+ return $null
48
+ }
49
+ return (Get-Content -LiteralPath $script:ParallelCheckpointPath -Raw)
50
+ }
51
+
52
+ function Get-ParallelWorktreeRemovalCommandPath {
53
+ <#
54
+ .SYNOPSIS
55
+ Extract the target worktree path argument from a git worktree remove command.
56
+ .PARAMETER CommandText
57
+ The Bash command text under evaluation.
58
+ .OUTPUTS
59
+ System.String or $null
60
+ #>
61
+ [CmdletBinding()]
62
+ [OutputType([string])]
63
+ param(
64
+ [Parameter(Mandatory)]
65
+ [string] $CommandText
66
+ )
67
+
68
+ if ($CommandText -match '(?i)\bgit\s+worktree\s+remove\s+(?<path>\S+)') {
69
+ return $Matches['path'].Trim('"''')
70
+ }
71
+ return $null
72
+ }
73
+
74
+ function Find-ParallelWorktreeItemRecord {
75
+ <#
76
+ .SYNOPSIS
77
+ Locate the items[] record whose worktree_path matches the target path.
78
+ .PARAMETER Checkpoint
79
+ Parsed parallel checkpoint, or $null when absent/unreadable.
80
+ .PARAMETER WorktreePath
81
+ The target worktree path extracted from the command text.
82
+ .OUTPUTS
83
+ System.Object or $null
84
+ #>
85
+ [CmdletBinding()]
86
+ param(
87
+ [AllowNull()]
88
+ $Checkpoint,
89
+
90
+ [AllowNull()]
91
+ [string] $WorktreePath
92
+ )
93
+
94
+ if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
95
+ return $null
96
+ }
97
+ $checkpointProps = @($Checkpoint.PSObject.Properties.Name)
98
+ if ($checkpointProps -notcontains 'items') {
99
+ return $null
100
+ }
101
+
102
+ $normalizedTarget = ($WorktreePath -replace '\\', '/').TrimEnd('/')
103
+
104
+ # Scan every recorded item for a worktree_path that matches the removal target;
105
+ # path separators are normalized so Windows- and POSIX-style paths compare equal.
106
+ foreach ($item in @($Checkpoint.items)) {
107
+ $itemProps = @($item.PSObject.Properties.Name)
108
+ if ($itemProps -notcontains 'worktree_path') {
109
+ continue
110
+ }
111
+ $normalizedItemPath = (([string]$item.worktree_path) -replace '\\', '/').TrimEnd('/')
112
+ if ($normalizedItemPath -eq $normalizedTarget) {
113
+ return $item
114
+ }
115
+ }
116
+ return $null
117
+ }
118
+
119
+ function Test-ParallelWorktreeRemovalAllowed {
120
+ <#
121
+ .SYNOPSIS
122
+ Decision logic: allow only when the matching item record's merge_status is
123
+ merged or worktree_removed.
124
+ .PARAMETER ItemRecord
125
+ The matched items[] record, or $null when no match was found.
126
+ .OUTPUTS
127
+ System.Boolean
128
+ #>
129
+ [CmdletBinding()]
130
+ [OutputType([bool])]
131
+ param(
132
+ [AllowNull()]
133
+ $ItemRecord
134
+ )
135
+
136
+ if ($null -eq $ItemRecord) {
137
+ return $false
138
+ }
139
+ $props = @($ItemRecord.PSObject.Properties.Name)
140
+ if ($props -notcontains 'merge_status') {
141
+ return $false
142
+ }
143
+ return $script:AllowedMergeStatuses -contains ([string]$ItemRecord.merge_status)
144
+ }
145
+
146
+ function Get-ParallelWorktreeGateAllowDecision {
147
+ [CmdletBinding()]
148
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
149
+ param()
150
+
151
+ return [ordered]@{
152
+ hookSpecificOutput = [ordered]@{
153
+ hookEventName = 'PreToolUse'
154
+ permissionDecision = 'allow'
155
+ }
156
+ }
157
+ }
158
+
159
+ function Get-ParallelWorktreeGateBlockDecision {
160
+ [CmdletBinding()]
161
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
162
+ param(
163
+ [Parameter(Mandatory)]
164
+ [string] $Reason
165
+ )
166
+
167
+ return [ordered]@{
168
+ hookSpecificOutput = [ordered]@{
169
+ hookEventName = 'PreToolUse'
170
+ permissionDecision = 'deny'
171
+ permissionDecisionReason = $Reason
172
+ }
173
+ }
174
+ }
175
+
176
+ function Invoke-ParallelWorktreeRemovalGateDecision {
177
+ <#
178
+ .SYNOPSIS
179
+ Parses CLAUDE_TOOL_INPUT and returns an allow-or-block decision.
180
+ .PARAMETER ToolInputRaw
181
+ The raw JSON tool payload supplied by Claude Code.
182
+ .OUTPUTS
183
+ System.Collections.Specialized.OrderedDictionary
184
+ #>
185
+ [CmdletBinding()]
186
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
187
+ param(
188
+ [string] $ToolInputRaw
189
+ )
190
+
191
+ if (-not $ToolInputRaw) {
192
+ return Get-ParallelWorktreeGateAllowDecision
193
+ }
194
+
195
+ try {
196
+ $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
197
+ } catch {
198
+ throw "enforce-parallel-worktree-removal-gate hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
199
+ }
200
+
201
+ $commandText = $toolInput.command
202
+ if (-not $commandText) {
203
+ return Get-ParallelWorktreeGateAllowDecision
204
+ }
205
+
206
+ if ($commandText -notmatch '(?i)\bgit\s+worktree\s+remove\b') {
207
+ return Get-ParallelWorktreeGateAllowDecision
208
+ }
209
+
210
+ $worktreePath = Get-ParallelWorktreeRemovalCommandPath -CommandText $commandText
211
+
212
+ $checkpointRaw = Get-ParallelWorktreeRemovalGateCheckpointContent
213
+ $checkpoint = $null
214
+ if (-not [string]::IsNullOrWhiteSpace($checkpointRaw)) {
215
+ try {
216
+ $checkpoint = $checkpointRaw | ConvertFrom-Json -ErrorAction Stop
217
+ } catch {
218
+ $checkpoint = $null
219
+ }
220
+ }
221
+
222
+ $itemRecord = Find-ParallelWorktreeItemRecord -Checkpoint $checkpoint -WorktreePath $worktreePath
223
+ if (Test-ParallelWorktreeRemovalAllowed -ItemRecord $itemRecord) {
224
+ return Get-ParallelWorktreeGateAllowDecision
225
+ }
226
+
227
+ return Get-ParallelWorktreeGateBlockDecision -Reason "PARALLEL_WORKTREE_REMOVAL_BLOCKED: git worktree remove for '$worktreePath' requires a matching parallel checkpoint items[] record with merge_status in {merged, worktree_removed}. The checkpoint was unreadable, no matching record was found, or merge_status was not yet safe for removal."
228
+ }
229
+
230
+ # Guard allows dot-sourcing in tests without executing the entrypoint.
231
+ if ($MyInvocation.InvocationName -eq '.') {
232
+ return
233
+ }
234
+
235
+ try {
236
+ $decision = Invoke-ParallelWorktreeRemovalGateDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
237
+ } catch {
238
+ Write-Error $_
239
+ exit 1
240
+ }
241
+
242
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
243
+
244
+ exit 0
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env bash
2
+ # compute-cohorts.sh: destination-portable command-line entry point for the
3
+ # parallel surface's cohort computation. It exists so a workspace that received
4
+ # the Claude customization payload can compute cohorts with nothing but bash --
5
+ # no Python, no Poetry, no repository checkout.
6
+ #
7
+ # Usage:
8
+ # bash .claude/lib/bash/compute-cohorts.sh --keys "<k1> <k2> ..." \
9
+ # [--edges "<a>:<b> <a>:<b> ..."]
10
+ #
11
+ # `--edges` is optional; omitting it, or passing an empty string, means the
12
+ # conflict graph has no edges. Item keys and edge endpoints are decimal
13
+ # integers matching `-?(0|[1-9][0-9]*)`; a token with a leading zero is
14
+ # rejected fail-closed with a lexical error, because the Python authority would
15
+ # read such a token differently and a silent disagreement is worse than a
16
+ # refusal.
17
+ #
18
+ # Output contract:
19
+ # stdout compact JSON array of arrays, identical to Python
20
+ # json.dumps(..., separators=(",", ":"))
21
+ # stderr on invalid input, the exact message the Python reference
22
+ # implementation raises
23
+ # exit 0 success
24
+ # exit 1 invalid input (duplicate key, self-loop, unknown endpoint)
25
+ # exit 2 usage error or a token outside the accepted integer lexis
26
+ set -euo pipefail
27
+
28
+ # Resolve this script's own directory so the library sources regardless of cwd.
29
+ CC_SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
30
+ # shellcheck source=.claude/lib/bash/parallel-cohorts.sh
31
+ # shellcheck disable=SC1091
32
+ source "$CC_SCRIPT_DIR/parallel-cohorts.sh"
33
+
34
+ pc_enforce_c_locale
35
+
36
+ cc_usage() {
37
+ # Print the entry point's usage text.
38
+ cat <<'EOF'
39
+ Usage: compute-cohorts.sh --keys "<k1> <k2> ..." [--edges "<a>:<b> ..."]
40
+
41
+ Computes parallel execution cohorts from an undirected conflict graph by
42
+ deterministic greedy graph coloring in Welsh-Powell order.
43
+
44
+ Options:
45
+ --keys Space-separated item keys (required; may be an empty string).
46
+ --edges Space-separated conflict edges as <a>:<b> (optional).
47
+
48
+ Prints a compact JSON array of arrays on stdout. On invalid input, prints the
49
+ reference implementation's exact message on stderr and exits 1.
50
+ EOF
51
+ }
52
+
53
+ cc_require_integer() {
54
+ # Validate one token against the accepted decimal-integer lexis.
55
+ #
56
+ # Args: $1 = the token, $2 = a label naming where the token came from.
57
+ # Exits 2 with a lexical error when the token is outside the lexis.
58
+ local token="$1" label="$2"
59
+ if [[ ! $token =~ ^-?(0|[1-9][0-9]*)$ ]]; then
60
+ printf 'compute-cohorts.sh: %s must be a decimal integer matching -?(0|[1-9][0-9]*); found: %s\n' \
61
+ "$label" "$token" >&2
62
+ exit 2
63
+ fi
64
+ }
65
+
66
+ cc_validate_tokens() {
67
+ # Validate every key token and every edge endpoint.
68
+ #
69
+ # Args: $1 = space-separated keys, $2 = space-separated `a:b` edges.
70
+ local keys="$1" edges="$2" token
71
+ pcoh_split_words "$keys"
72
+ local -a key_tokens=("${PCOH_WORDS[@]}")
73
+ for token in "${key_tokens[@]}"; do
74
+ cc_require_integer "$token" "item key"
75
+ done
76
+
77
+ pcoh_split_words "$edges"
78
+ local -a edge_tokens=("${PCOH_WORDS[@]}")
79
+ # Each edge must be exactly two integer endpoints joined by a single colon;
80
+ # anything else is a malformed edge token rather than a graph error.
81
+ for token in "${edge_tokens[@]}"; do
82
+ if [[ $token != *:* || $token == *:*:* ]]; then
83
+ printf 'compute-cohorts.sh: edge must be <a>:<b>; found: %s\n' "$token" >&2
84
+ exit 2
85
+ fi
86
+ cc_require_integer "${token%%:*}" "edge endpoint"
87
+ cc_require_integer "${token#*:}" "edge endpoint"
88
+ done
89
+ }
90
+
91
+ cc_main() {
92
+ # Parse arguments, compute the cohorts, and print the result.
93
+ local keys="" edges="" keys_seen=0
94
+ while (($# > 0)); do
95
+ case "$1" in
96
+ --keys)
97
+ (($# >= 2)) || {
98
+ cc_usage >&2
99
+ return 2
100
+ }
101
+ keys="$2"
102
+ keys_seen=1
103
+ shift 2
104
+ ;;
105
+ --edges)
106
+ (($# >= 2)) || {
107
+ cc_usage >&2
108
+ return 2
109
+ }
110
+ edges="$2"
111
+ shift 2
112
+ ;;
113
+ --help | -h)
114
+ cc_usage
115
+ return 0
116
+ ;;
117
+ *)
118
+ cc_usage >&2
119
+ return 2
120
+ ;;
121
+ esac
122
+ done
123
+ ((keys_seen == 1)) || {
124
+ cc_usage >&2
125
+ return 2
126
+ }
127
+
128
+ cc_validate_tokens "$keys" "$edges"
129
+ if ! pcoh_compute_cohorts "$keys" "$edges"; then
130
+ printf '%s\n' "$PCOH_ERROR" >&2
131
+ return 1
132
+ fi
133
+ printf '%s\n' "$PCOH_RESULT"
134
+ return 0
135
+ }
136
+
137
+ # Guard so the file can be sourced without executing main. main's return code
138
+ # is captured and re-exited explicitly as the final statement.
139
+ if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
140
+ cc_rc=0
141
+ cc_main "$@" || cc_rc=$?
142
+ exit "$cc_rc"
143
+ fi
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env bash
2
+ # compute-concurrency-batches.sh: destination-portable command-line entry point
3
+ # for the parallel surface's concurrency batching. It exists so a workspace that
4
+ # received the Claude customization payload can cap fan-out with nothing but
5
+ # bash -- no Python, no Poetry, no repository checkout.
6
+ #
7
+ # Usage:
8
+ # bash .claude/lib/bash/compute-concurrency-batches.sh \
9
+ # --keys "<k1> <k2> ..." --max-concurrency <n>
10
+ #
11
+ # Item keys are decimal integers matching `-?(0|[1-9][0-9]*)`; a token with a
12
+ # leading zero is rejected fail-closed with a lexical error, because the Python
13
+ # authority would read such a token differently and a silent disagreement is
14
+ # worse than a refusal.
15
+ #
16
+ # Output contract:
17
+ # stdout compact JSON array of arrays, identical to Python
18
+ # json.dumps(..., separators=(",", ":"))
19
+ # stderr on invalid input, the exact message the Python reference
20
+ # implementation raises
21
+ # exit 0 success
22
+ # exit 1 invalid input (max_concurrency below 1)
23
+ # exit 2 usage error or a token outside the accepted integer lexis
24
+ set -euo pipefail
25
+
26
+ # Resolve this script's own directory so the library sources regardless of cwd.
27
+ CB_SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
28
+ # shellcheck source=.claude/lib/bash/parallel-cohorts.sh
29
+ # shellcheck disable=SC1091
30
+ source "$CB_SCRIPT_DIR/parallel-cohorts.sh"
31
+
32
+ pc_enforce_c_locale
33
+
34
+ cb_usage() {
35
+ # Print the entry point's usage text.
36
+ cat <<'EOF'
37
+ Usage: compute-concurrency-batches.sh --keys "<k1> <k2> ..." --max-concurrency <n>
38
+
39
+ Chunks one cohort's item keys into concurrency-capped batches in ascending key
40
+ order. Every batch holds exactly <n> keys except a possibly smaller final batch.
41
+
42
+ Options:
43
+ --keys Space-separated cohort item keys (required; may be empty).
44
+ --max-concurrency The fan-out cap (required).
45
+
46
+ Prints a compact JSON array of arrays on stdout. When the cap is below 1, prints
47
+ the reference implementation's exact message on stderr and exits 1.
48
+ EOF
49
+ }
50
+
51
+ cb_require_integer() {
52
+ # Validate one token against the accepted decimal-integer lexis.
53
+ #
54
+ # Args: $1 = the token, $2 = a label naming where the token came from.
55
+ # Exits 2 with a lexical error when the token is outside the lexis.
56
+ local token="$1" label="$2"
57
+ if [[ ! $token =~ ^-?(0|[1-9][0-9]*)$ ]]; then
58
+ printf 'compute-concurrency-batches.sh: %s must be a decimal integer matching -?(0|[1-9][0-9]*); found: %s\n' \
59
+ "$label" "$token" >&2
60
+ exit 2
61
+ fi
62
+ }
63
+
64
+ cb_main() {
65
+ # Parse arguments, compute the batches, and print the result.
66
+ local keys="" cap="" keys_seen=0 token
67
+ while (($# > 0)); do
68
+ case "$1" in
69
+ --keys)
70
+ (($# >= 2)) || {
71
+ cb_usage >&2
72
+ return 2
73
+ }
74
+ keys="$2"
75
+ keys_seen=1
76
+ shift 2
77
+ ;;
78
+ --max-concurrency)
79
+ (($# >= 2)) || {
80
+ cb_usage >&2
81
+ return 2
82
+ }
83
+ cap="$2"
84
+ shift 2
85
+ ;;
86
+ --help | -h)
87
+ cb_usage
88
+ return 0
89
+ ;;
90
+ *)
91
+ cb_usage >&2
92
+ return 2
93
+ ;;
94
+ esac
95
+ done
96
+ if ((keys_seen == 0)) || [[ -z $cap ]]; then
97
+ cb_usage >&2
98
+ return 2
99
+ fi
100
+
101
+ cb_require_integer "$cap" "max_concurrency"
102
+ pcoh_split_words "$keys"
103
+ local -a key_tokens=("${PCOH_WORDS[@]}")
104
+ for token in "${key_tokens[@]}"; do
105
+ cb_require_integer "$token" "item key"
106
+ done
107
+
108
+ if ! pcoh_compute_concurrency_batches "$keys" "$cap"; then
109
+ printf '%s\n' "$PCOH_ERROR" >&2
110
+ return 1
111
+ fi
112
+ printf '%s\n' "$PCOH_RESULT"
113
+ return 0
114
+ }
115
+
116
+ # Guard so the file can be sourced without executing main. main's return code
117
+ # is captured and re-exited explicitly as the final statement.
118
+ if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
119
+ cb_rc=0
120
+ cb_main "$@" || cb_rc=$?
121
+ exit "$cb_rc"
122
+ fi