@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.
- package/out/mcp-server.js +1730 -139
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/MEMORY.md +5 -1
- package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_commit_push_memory_before_pr.md +48 -2
- package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_no_sendmessage_tool.md +35 -0
- package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_worktree_isolation_branches_from_main.md +45 -0
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +257 -0
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +183 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +23 -11
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +259 -0
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +499 -0
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate-helpers.ps1 +302 -0
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +359 -0
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +244 -0
- package/resources/claude-customizations/.claude/lib/bash/compute-cohorts.sh +143 -0
- package/resources/claude-customizations/.claude/lib/bash/compute-concurrency-batches.sh +122 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-cohorts.sh +330 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-common.sh +238 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-items-validate.sh +244 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-manifest-validate.sh +187 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-yaml-emit.sh +340 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-yaml-scan.sh +335 -0
- package/resources/claude-customizations/.claude/lib/bash/validate-parallel-manifest.sh +134 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +379 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +491 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +490 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +429 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +366 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +184 -0
- package/resources/claude-customizations/.claude/rules/shell.md +7 -2
- package/resources/claude-customizations/.claude/settings.json +28 -0
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +152 -0
- package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +93 -0
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +971 -0
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +461 -0
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +176 -0
- package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +56 -0
- package/resources/claude-customizations/config/blast-radius.json +16 -0
- package/resources/claude-customizations/config/orchestration-routing.json +355 -0
- package/resources/claude-customizations/pack-manifests/core.json +32 -1
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/config/orchestration-routing.json +22 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +29 -0
package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1
ADDED
|
@@ -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
|