Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions .claude/agents/parallel-orchestrator.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ tools:
- "Bash(gh *)"
- "Bash(poetry run python -c *)"
- "Bash(poetry run python -m *)"
- "Bash(bash .claude/lib/bash/compute-cohorts.sh*)"
- "Bash(bash .claude/lib/bash/compute-concurrency-batches.sh*)"
- "Bash(bash .claude/lib/bash/validate-parallel-manifest.sh*)"
- "mcp__drm-copilot__collect_pr_context"
- "mcp__drm-copilot__validate_orchestration_artifacts"
skills:
Expand Down Expand Up @@ -66,16 +69,32 @@ frames the *who* and *when*; the skill documents the *how* in full. The manifest
checkpoint schema, and the parallel enums are defined once in
`.claude/rules/parallel-orchestration.md` and are consumed here, never redefined.

Two of that procedure's steps are reached through a Python interpreter rather than through a
dedicated command, so the `tools` allowlist grants exactly two invocation prefixes for them.
`scripts/dev_tools/parallel_manifest_contract.py` is an import-only library with no CLI entry point,
so the manifest gate's `validate_parallel_manifest_text` check is invoked as
`poetry run python -c`; the checkpoint-validator CLI fallback the skill names in its
`## Parallel-Level Checkpoint` section is invoked as `poetry run python -m`. Both grants are scoped
The manifest gate is reached through the destination-runtime bash entry point, which needs no
Python interpreter and is published by push-down alongside `.claude`, so the `tools` allowlist
grants one entry per command-line entry point —
`"Bash(bash .claude/lib/bash/compute-cohorts.sh*)"`,
`"Bash(bash .claude/lib/bash/compute-concurrency-batches.sh*)"`, and
`"Bash(bash .claude/lib/bash/validate-parallel-manifest.sh*)"` — the last of which covers it:

```bash
bash .claude/lib/bash/validate-parallel-manifest.sh docs/features/parallel/<slug>/parallel.md
```

Exit 0 accepts the manifest, exit 1 rejects it with one error per line on stdout, and exit 2 means
the file is unreadable or uses a YAML construct outside the supported subset. The same entry point's
`--print-mode` and `--print-max-concurrency` subcommands supply `mode` and `max_concurrency` with
their documented defaults. `validate_parallel_manifest_text` in
`scripts/dev_tools/parallel_manifest_contract.py` remains the repository authority and the parity
reference; it is not invoked on the destination-runtime path. Cohort recoloring and concurrency
batching use `compute-cohorts.sh` and `compute-concurrency-batches.sh` under the same allowlist
entry.

The two `poetry run` grants remain for the repository-local paths that still need an interpreter:
the checkpoint-validator CLI fallback the skill names in its `## Parallel-Level Checkpoint` section
is invoked as `poetry run python -m`, and the drift-detection CLI likewise. Both grants stay scoped
to those two invocation forms only — not to `poetry run` as a whole — so `pytest`, `black`, `ruff`,
and every other `poetry run` subcommand remain outside the allowlist. The sibling persona
`.claude/agents/parallel-planner.md` records the same rationale for the same class of import-only
upstream library.
`.claude/agents/parallel-planner.md` records the same destination-runtime posture.

## Startup Protocol

Expand Down
54 changes: 44 additions & 10 deletions .claude/agents/parallel-planner.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ tools:
- "Bash(git *)"
- "Bash(gh *)"
- "Bash(poetry run *)"
- "Bash(bash .claude/lib/bash/compute-cohorts.sh*)"
- "Bash(bash .claude/lib/bash/compute-concurrency-batches.sh*)"
- "Bash(bash .claude/lib/bash/validate-parallel-manifest.sh*)"
- "mcp__drm-copilot__validate_orchestration_artifacts"
skills:
- policy-compliance-order
Expand Down Expand Up @@ -133,17 +136,48 @@ Do not report completion until:

## Upstream Library Invocation

The `"Bash(poetry run *)"` allowlist entry is retained deliberately, and its justification is
recorded here rather than left implicit. The blast-radius library
(`scripts/dev_tools/compute_blast_radius.py`) and the cohort-computation library
(`scripts/dev_tools/parallel_cohort_computation.py`) landed as import-only Python libraries with
no CLI entry point, matching the repository's `scripts/dev_tools/epic_wave_computation.py`
precedent. Radius derivation, V1-V3 validation, the contention relation, and cohort seeding are
therefore reached through a `poetry run` Python invocation, for example:
Every upstream library this planner needs is reachable from the published customization payload
alone, with no Python interpreter and no repository checkout. That is the point of the
destination-portability work in issue #462: a workspace that received `.claude` and `config` can
plan a parallel run.

**Blast radius — PowerShell port.** Radius derivation, V1-V3 validation, and the contention
relation come from `.claude/lib/blast-radius/BlastRadius.psm1`:

```powershell
Import-Module .claude/lib/blast-radius/BlastRadius.psm1 -Force
```

The facade exports `Get-PlanPaths`, `Get-BlastRadius`, `Get-BlastRadiusFromObservedPaths`,
`Test-BlastRadius`, and `Test-BlastRadiusConflict`. Its truth table is
`config/blast-radius.json`, which push-down publishes alongside `.claude`.

**Cohort seeding and concurrency batching — bash entry points.** The bash library is granted as
three entry-point-specific allowlist entries — `"Bash(bash .claude/lib/bash/compute-cohorts.sh*)"`,
`"Bash(bash .claude/lib/bash/compute-concurrency-batches.sh*)"`, and
`"Bash(bash .claude/lib/bash/validate-parallel-manifest.sh*)"` — one per command-line entry point.
The six sourceable libraries carry no grant because they are never invoked directly. The two
commands below require the first two of those entries:

```bash
poetry run python -c "from scripts.dev_tools.compute_blast_radius import derive_blast_radius"
bash .claude/lib/bash/compute-cohorts.sh --keys "<k1> <k2> ..." --edges "<a>:<b> ..."
bash .claude/lib/bash/compute-concurrency-batches.sh --keys "<k1> ..." --max-concurrency <n>
```

That invocation form requires exactly this allowlist entry. Without it the planner cannot obtain a
declared radius or a cohort partition, and planning cannot reach a ready state.
**Manifest validation — bash entry point.** The
`"Bash(bash .claude/lib/bash/validate-parallel-manifest.sh*)"` allowlist entry covers:

```bash
bash .claude/lib/bash/validate-parallel-manifest.sh <manifest-path>
bash .claude/lib/bash/validate-parallel-manifest.sh --print-mode <manifest-path>
bash .claude/lib/bash/validate-parallel-manifest.sh --print-max-concurrency <manifest-path>
```

**Python modules are the repository authority, not the runtime path.**
`scripts/dev_tools/compute_blast_radius.py`, `scripts/dev_tools/parallel_cohort_computation.py`,
and `scripts/dev_tools/parallel_manifest_contract.py` remain the reference implementations that the
ported libraries are asserted against by shared fixture corpora. Do not invoke them on the
destination-runtime path; cite them for their contract.

The `"Bash(poetry run *)"` allowlist entry is retained for the repository-local paths that still
need it — it is not required by any step above.
143 changes: 143 additions & 0 deletions .claude/lib/bash/compute-cohorts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#!/usr/bin/env bash
# compute-cohorts.sh: destination-portable command-line entry point for the
# parallel surface's cohort computation. It exists so a workspace that received
# the Claude customization payload can compute cohorts with nothing but bash --
# no Python, no Poetry, no repository checkout.
#
# Usage:
# bash .claude/lib/bash/compute-cohorts.sh --keys "<k1> <k2> ..." \
# [--edges "<a>:<b> <a>:<b> ..."]
#
# `--edges` is optional; omitting it, or passing an empty string, means the
# conflict graph has no edges. Item keys and edge endpoints are decimal
# integers matching `-?(0|[1-9][0-9]*)`; a token with a leading zero is
# rejected fail-closed with a lexical error, because the Python authority would
# read such a token differently and a silent disagreement is worse than a
# refusal.
#
# Output contract:
# stdout compact JSON array of arrays, identical to Python
# json.dumps(..., separators=(",", ":"))
# stderr on invalid input, the exact message the Python reference
# implementation raises
# exit 0 success
# exit 1 invalid input (duplicate key, self-loop, unknown endpoint)
# exit 2 usage error or a token outside the accepted integer lexis
set -euo pipefail

# Resolve this script's own directory so the library sources regardless of cwd.
CC_SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=.claude/lib/bash/parallel-cohorts.sh
# shellcheck disable=SC1091
source "$CC_SCRIPT_DIR/parallel-cohorts.sh"

pc_enforce_c_locale

cc_usage() {
# Print the entry point's usage text.
cat <<'EOF'
Usage: compute-cohorts.sh --keys "<k1> <k2> ..." [--edges "<a>:<b> ..."]

Computes parallel execution cohorts from an undirected conflict graph by
deterministic greedy graph coloring in Welsh-Powell order.

Options:
--keys Space-separated item keys (required; may be an empty string).
--edges Space-separated conflict edges as <a>:<b> (optional).

Prints a compact JSON array of arrays on stdout. On invalid input, prints the
reference implementation's exact message on stderr and exits 1.
EOF
}

cc_require_integer() {
# Validate one token against the accepted decimal-integer lexis.
#
# Args: $1 = the token, $2 = a label naming where the token came from.
# Exits 2 with a lexical error when the token is outside the lexis.
local token="$1" label="$2"
if [[ ! $token =~ ^-?(0|[1-9][0-9]*)$ ]]; then
printf 'compute-cohorts.sh: %s must be a decimal integer matching -?(0|[1-9][0-9]*); found: %s\n' \
"$label" "$token" >&2
exit 2
fi
}

cc_validate_tokens() {
# Validate every key token and every edge endpoint.
#
# Args: $1 = space-separated keys, $2 = space-separated `a:b` edges.
local keys="$1" edges="$2" token
pcoh_split_words "$keys"
local -a key_tokens=("${PCOH_WORDS[@]}")
for token in "${key_tokens[@]}"; do
cc_require_integer "$token" "item key"
done

pcoh_split_words "$edges"
local -a edge_tokens=("${PCOH_WORDS[@]}")
# Each edge must be exactly two integer endpoints joined by a single colon;
# anything else is a malformed edge token rather than a graph error.
for token in "${edge_tokens[@]}"; do
if [[ $token != *:* || $token == *:*:* ]]; then
printf 'compute-cohorts.sh: edge must be <a>:<b>; found: %s\n' "$token" >&2
exit 2
fi
cc_require_integer "${token%%:*}" "edge endpoint"
cc_require_integer "${token#*:}" "edge endpoint"
done
}

cc_main() {
# Parse arguments, compute the cohorts, and print the result.
local keys="" edges="" keys_seen=0
while (($# > 0)); do
case "$1" in
--keys)
(($# >= 2)) || {
cc_usage >&2
return 2
}
keys="$2"
keys_seen=1
shift 2
;;
--edges)
(($# >= 2)) || {
cc_usage >&2
return 2
}
edges="$2"
shift 2
;;
--help | -h)
cc_usage
return 0
;;
*)
cc_usage >&2
return 2
;;
esac
done
((keys_seen == 1)) || {
cc_usage >&2
return 2
}

cc_validate_tokens "$keys" "$edges"
if ! pcoh_compute_cohorts "$keys" "$edges"; then
printf '%s\n' "$PCOH_ERROR" >&2
return 1
fi
printf '%s\n' "$PCOH_RESULT"
return 0
}

# Guard so the file can be sourced without executing main. main's return code
# is captured and re-exited explicitly as the final statement.
if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
cc_rc=0
cc_main "$@" || cc_rc=$?
exit "$cc_rc"
fi
122 changes: 122 additions & 0 deletions .claude/lib/bash/compute-concurrency-batches.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
# compute-concurrency-batches.sh: destination-portable command-line entry point
# for the parallel surface's concurrency batching. It exists so a workspace that
# received the Claude customization payload can cap fan-out with nothing but
# bash -- no Python, no Poetry, no repository checkout.
#
# Usage:
# bash .claude/lib/bash/compute-concurrency-batches.sh \
# --keys "<k1> <k2> ..." --max-concurrency <n>
#
# Item keys are decimal integers matching `-?(0|[1-9][0-9]*)`; a token with a
# leading zero is rejected fail-closed with a lexical error, because the Python
# authority would read such a token differently and a silent disagreement is
# worse than a refusal.
#
# Output contract:
# stdout compact JSON array of arrays, identical to Python
# json.dumps(..., separators=(",", ":"))
# stderr on invalid input, the exact message the Python reference
# implementation raises
# exit 0 success
# exit 1 invalid input (max_concurrency below 1)
# exit 2 usage error or a token outside the accepted integer lexis
set -euo pipefail

# Resolve this script's own directory so the library sources regardless of cwd.
CB_SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=.claude/lib/bash/parallel-cohorts.sh
# shellcheck disable=SC1091
source "$CB_SCRIPT_DIR/parallel-cohorts.sh"

pc_enforce_c_locale

cb_usage() {
# Print the entry point's usage text.
cat <<'EOF'
Usage: compute-concurrency-batches.sh --keys "<k1> <k2> ..." --max-concurrency <n>

Chunks one cohort's item keys into concurrency-capped batches in ascending key
order. Every batch holds exactly <n> keys except a possibly smaller final batch.

Options:
--keys Space-separated cohort item keys (required; may be empty).
--max-concurrency The fan-out cap (required).

Prints a compact JSON array of arrays on stdout. When the cap is below 1, prints
the reference implementation's exact message on stderr and exits 1.
EOF
}

cb_require_integer() {
# Validate one token against the accepted decimal-integer lexis.
#
# Args: $1 = the token, $2 = a label naming where the token came from.
# Exits 2 with a lexical error when the token is outside the lexis.
local token="$1" label="$2"
if [[ ! $token =~ ^-?(0|[1-9][0-9]*)$ ]]; then
printf 'compute-concurrency-batches.sh: %s must be a decimal integer matching -?(0|[1-9][0-9]*); found: %s\n' \
"$label" "$token" >&2
exit 2
fi
}

cb_main() {
# Parse arguments, compute the batches, and print the result.
local keys="" cap="" keys_seen=0 token
while (($# > 0)); do
case "$1" in
--keys)
(($# >= 2)) || {
cb_usage >&2
return 2
}
keys="$2"
keys_seen=1
shift 2
;;
--max-concurrency)
(($# >= 2)) || {
cb_usage >&2
return 2
}
cap="$2"
shift 2
;;
--help | -h)
cb_usage
return 0
;;
*)
cb_usage >&2
return 2
;;
esac
done
if ((keys_seen == 0)) || [[ -z $cap ]]; then
cb_usage >&2
return 2
fi

cb_require_integer "$cap" "max_concurrency"
pcoh_split_words "$keys"
local -a key_tokens=("${PCOH_WORDS[@]}")
for token in "${key_tokens[@]}"; do
cb_require_integer "$token" "item key"
done

if ! pcoh_compute_concurrency_batches "$keys" "$cap"; then
printf '%s\n' "$PCOH_ERROR" >&2
return 1
fi
printf '%s\n' "$PCOH_RESULT"
return 0
}

# Guard so the file can be sourced without executing main. main's return code
# is captured and re-exited explicitly as the final statement.
if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
cb_rc=0
cb_main "$@" || cb_rc=$?
exit "$cb_rc"
fi
Loading
Loading