baldart 4.53.5 → 4.53.7
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/CHANGELOG.md +18 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/prd-card-writer.md +11 -1
- package/framework/.claude/skills/new/references/team-mode.md +4 -2
- package/framework/.claude/skills/worktree-manager/scripts/setup-worktree.sh +51 -19
- package/framework/.claude/workflows/new-card-review.js +22 -2
- package/framework/.claude/workflows/new-final-review.js +21 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ All notable changes to BALDART will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.53.7] - 2026-06-19
|
|
9
|
+
|
|
10
|
+
**`setup-worktree.sh` no longer turns an empty `toolchain.commands.build` into a spurious `baseline: fail`.** Observed on a real `/new FEAT-0037` run in a consumer (`mayo`) whose `baldart.config.yml` declares `build: ''` (single-quoted empty scalar — the project's build is not a separate toolchain gate; per `toolchain-protocol.md` an empty command must fall back to the default `npm run build`). The deterministic SSOT worktree script (v4.53.0) read that value with a sed that stripped only DOUBLE quotes (`\"?([^\"#]*)\"?`), so `build: ''` came through as the **literal 2-char string `''`** — non-empty — so the `[ -n "$TC_BUILD" ]` fallback never fired and the script ran `bash -c "''"`, an empty *quoted* command → `bash: : command not found` (exit 127) → `baseline: fail`. tsc + biome had actually passed and the worktree was green on disk; the failure was entirely the unresolved empty scalar. Fix: (1) `_tc()` now resolves a YAML scalar to its true value — `build:`, `build: ''`, `build: ""`, `build: ~`, `build: null` all collapse to empty so the default fallback fires (strips one layer of matching single OR double quotes + YAML null markers + inline `# comment`); (2) the script now tracks build provenance and implements the protocol's **SKIP tier** — the *fallback* `npm run build` on a project with no `build` npm script is a no-build library → SKIP (baseline stays pass), never a spurious fail, while a *configured* (non-empty) build command is always run as a real gate. Verified by executing the resolver against every empty-scalar form + real values, the fallback/SKIP decision on a has-build vs no-build `package.json`, and reproducing the exact `bash: : command not found` exit-127 from the old sed. The prose inline fallbacks (`setup.md`, `worktree-manager/SKILL.md`, `new2.js`) are model-driven and never had the sed bug — no parallel fix needed. **PATCH** (deterministic-script bugfix; correctly-configured consumers see no behaviour change; no new agent/skill/command/config key, no install change).
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`framework/.claude/skills/worktree-manager/scripts/setup-worktree.sh`** — `_tc()` rewritten to dequote single/double quotes + treat `~`/`null` as empty (was stripping double quotes only, so `build: ''` resolved to the literal `''` and skipped the fallback → `bash -c "''"` → `bash: : command not found` → spurious `baseline: fail`). Build resolution now records `BUILD_FROM_CONFIG` and the baseline build step adds the toolchain-protocol SKIP tier: the fallback `npm run build` on a no-`build`-script project is SKIPPED (baseline pass), not failed; a configured command is always a real gate.
|
|
15
|
+
|
|
16
|
+
## [4.53.6] - 2026-06-18
|
|
17
|
+
|
|
18
|
+
**`/new` now caps concurrency at 3 agents per wave — the API rate-limit guardrail, end to end.** Follow-up to the v4.53.5 root-cause finding (parallel coders killed by org-level rate-limiting): the org rate limit is SHARED across every terminal/session on the account, so a wide fan-out saturates the shared pool and the server throttles, killing agents mid-work and forcing full re-spawns (paying twice). Fix puts a hard ceiling of **3 concurrent agents** at every fan-out point in a `/new` epic: (1) `prd-card-writer` caps each `execution_strategy.groups[]` at ≤3 cards — a wider topological layer is split into sequential sub-groups of ≤3 (always safe: same-layer cards are independent by construction, so serializing them is correctness-neutral; the per-card `parallel_group` keeps its true logical layer, only the execution schedule is capped), so the **plan/cards reflect the rule**; (2) team-mode runtime never spawns more than 3 coders per wave and sub-batches a wider group sequentially (protects legacy epics + manual runs — the **guarantee**); (3) the per-wave and final review workflows cap their finder/verify fan-out at 3 via a rolling worker pool (not chunked barriers — those would block fast finders behind the slow background Codex poll). The cap is **3** (the user's choice — prefer never clogging over re-paying), a fixed operational guardrail like the existing retry caps (NOT a `baldart.config.yml` key). Honest limits: BALDART cannot see agents in OTHER terminals — the cap bounds one run; total cross-terminal parallelism is still the user's to manage. `new2` inherits the prd-card-writer group cap (same cards) and the final-review cap (shared workflow); its own per-card scheduling is not separately capped here (follow-up if it sees use). **PATCH** (operational guardrail on existing orchestration; no new agent/skill/command/config key, no install change).
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **`framework/.claude/agents/prd-card-writer.md`** — Parallel Group Computation step 7: cap each execution group at ≤3 cards (split wider independent layers into sequential sub-groups); example + `max_concurrent_agents: 3` added; per-card `parallel_group` unchanged (logical layer).
|
|
23
|
+
- **`framework/.claude/skills/new/references/team-mode.md`** — Step B gains a MANDATORY "Concurrency cap — MAX 3 coders concurrently" (sub-batch a wider group sequentially; cross-terminal caveat); Per-Group Execution header notes the cap.
|
|
24
|
+
- **`framework/.claude/workflows/new-card-review.js`** + **`new-final-review.js`** — new `MAX_PARALLEL=3` + `parallelCapped()` rolling worker pool; the Discovery/Review finder fan-out and the Verify fan-out run through it (≤3 concurrent agents, order-preserving, null-on-throw, no head-of-line blocking behind the Codex poll).
|
|
25
|
+
|
|
8
26
|
## [4.53.5] - 2026-06-18
|
|
9
27
|
|
|
10
28
|
**The team-mode empty-result gate now distinguishes a rate-limit death from a genuine fabrication — and re-spawns transient failures STAGGERED, not in the same parallel burst.** Root-cause finding (from reading the actual subagent transcripts of the v4.53.4 incident — `~/.claude/projects/<mayo>/<session>/subagents/agent-*.jsonl`): the two `/new FEAT-0035` L2 coders that "came to rest" with no work were **killed mid-flight by API rate-limiting** — both transcripts end on the identical `API Error: Server is temporarily limiting requests (not your usage limit) · Rate limited`, after the agent had done real work (file reads, a written plan) but before any edit. It was NOT model fabrication or a "decided nothing to do" stall (the v4.53.4 framing): a wide parallel wave (3 coders + orchestrator + other agents) saturated the API, the background teammate runtime does not auto-resume a rate-limited teammate, so it rested with no report + no diff, and the full re-spawn re-did the lost reads (the "paid twice" cost). Fix: the gate now reads the rested agent's LAST event — a transient API/rate-limit/overload error ⇒ a **transient infra failure** (re-spawn STAGGERED with backoff; never re-fire several transient-failed agents in the same parallel burst — it re-saturates the API; narrow the wave's fan-out width if rate limits recur; does not consume the genuine-failure Step-B budget), vs a CLEAN rest with no error ⇒ the genuine empty-result/fabrication path (one Step-B re-spawn → `AskUserQuestion`). Honest boundary: the cheaper real fix — *resuming* a rate-limited teammate instead of killing+re-spawning it — is Claude Code **runtime** behaviour, not BALDART's to fix in prose; BALDART mitigates (correct classification + staggered backoff + fan-out narrowing). **PATCH** (refines the v4.53.4 team-mode gate; no code/CLI/workflow change, no `baldart.config.yml` key).
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.53.
|
|
1
|
+
4.53.7
|
|
@@ -595,6 +595,7 @@ After all cards and matrices, compute parallelization metadata:
|
|
|
595
595
|
- Total cards <= 3: `sequential`
|
|
596
596
|
- Max cards in any single layer <= 1: `sequential`
|
|
597
597
|
- Otherwise: `team`
|
|
598
|
+
7. **Concurrency cap — MAX 3 cards per execution group (API rate-limit guardrail, since v4.53.6).** A parallel wave wider than 3 risks API throttling: the rate limit is enforced at the **organization level** and is SHARED across every Claude Code session/terminal on the account, so a wide coder fan-out saturates the shared pool (observed in a real run — `API Error: ... Rate limited (not your usage limit)` killed coders mid-work, forcing a full re-spawn = paying twice). When a topological layer has **more than 3 cards**, split it into multiple `execution_strategy.groups[]` entries of **≤3 cards each**. This is always safe: cards in the same layer are mutually independent by construction (no `depends_on` among them — step 4 — and no `(MODIFY)` file conflict — step 3), so the sub-groups run sequentially with **zero correctness impact**, only less parallelism. Keep each card's per-card `parallel_group` at its TRUE logical layer (unchanged); only the execution `groups[]` are capped — they are an execution schedule, not the logical DAG. (The `/new` team-mode runtime independently enforces the same ≤3 cap by sub-batching, so this is the plan-side reflection of a guarantee enforced at execution.)
|
|
598
599
|
|
|
599
600
|
**Add to epic parent card** an `execution_strategy` block:
|
|
600
601
|
|
|
@@ -610,8 +611,17 @@ execution_strategy:
|
|
|
610
611
|
- level: 1
|
|
611
612
|
cards: [FEAT-XXXX-02, FEAT-XXXX-03]
|
|
612
613
|
description: "Independent modules (no file overlap)"
|
|
614
|
+
# Concurrency cap (≤3/group): a logical layer of 5 independent cards is emitted as TWO
|
|
615
|
+
# sequential groups of ≤3 — same logical layer, split only for the execution schedule.
|
|
616
|
+
- level: 2
|
|
617
|
+
cards: [FEAT-XXXX-04, FEAT-XXXX-05, FEAT-XXXX-06]
|
|
618
|
+
description: "Layer 2 (independent) — batch 1 of 2 (concurrency cap ≤3)"
|
|
619
|
+
- level: 3
|
|
620
|
+
cards: [FEAT-XXXX-07, FEAT-XXXX-08]
|
|
621
|
+
description: "Layer 2 (independent) — batch 2 of 2 (concurrency cap ≤3)"
|
|
622
|
+
max_concurrent_agents: 3 # API rate-limit guardrail — no group exceeds this; runtime sub-batches too
|
|
613
623
|
file_conflicts:
|
|
614
|
-
- "FEAT-XXXX-
|
|
624
|
+
- "FEAT-XXXX-09 and FEAT-XXXX-10 both modify <shared-types-file> — forced sequential"
|
|
615
625
|
```
|
|
616
626
|
|
|
617
627
|
The consumer (`new/SKILL.md`) reads `execution_strategy.groups[].level` and
|
|
@@ -33,7 +33,7 @@ The standard pre-flight (setup.md Phase 0 + steps 1-6) runs unchanged; add:
|
|
|
33
33
|
|
|
34
34
|
### Per-Group Execution
|
|
35
35
|
|
|
36
|
-
Process groups in order (0, 1, 2, ...). Within each group, spawn coder agents IN PARALLEL — one per card.
|
|
36
|
+
Process groups in order (0, 1, 2, ...). Within each group, spawn coder agents IN PARALLEL — one per card, **capped at 3 concurrent** (sub-batch a wider group — see Step B § "Concurrency cap").
|
|
37
37
|
|
|
38
38
|
#### Step A: Pre-compute shared context (ONCE per group)
|
|
39
39
|
|
|
@@ -59,7 +59,9 @@ its `arch_baseline_path` at this file, so the review reuses the group baseline i
|
|
|
59
59
|
|
|
60
60
|
(No visibility emission on wave change — the internal tracker is the only state surface; see SKILL.md § "State surface — the tracker only". Record the wave start in the tracker only.)
|
|
61
61
|
|
|
62
|
-
For each card in the current group, spawn a coder agent using the Agent tool.
|
|
62
|
+
For each card in the current group, spawn a coder agent using the Agent tool. The group's cards MUST be spawned in a **SINGLE message** (multiple Agent tool calls) to run truly in parallel.
|
|
63
|
+
|
|
64
|
+
> **Concurrency cap — MAX 3 coders concurrently (MANDATORY — API rate-limit guardrail, since v4.53.6).** Never fire more than **3** coder agents in one parallel burst. The API rate limit is **organization-level and SHARED across every terminal/session on the account** — a wide coder fan-out saturates the shared pool and the server starts throttling (`API Error: ... Rate limited (not your usage limit)`), which **kills coders mid-work** and forces a full re-spawn (paying for the same work twice). `prd-card-writer` already caps `execution_strategy.groups[]` at ≤3, but if a group's `.cards` exceeds 3 (a legacy epic authored before this rule, or a manual run), **sub-batch it into chunks of ≤3 run SEQUENTIALLY** — spawn the first ≤3, wait for the wave to complete its Step-D pipeline (or at least the coders to finish), then the next ≤3. Do NOT fire them all at once. ⚠️ **BALDART cannot see agents spawned in OTHER terminals** — this cap bounds THIS run only; if you routinely run multiple `/new` epics across terminals, keep the total modest, because they all draw from the same shared pool. (Same guardrail the v4.53.5 transient-failure handling references when it says "narrow the fan-out".)
|
|
63
65
|
|
|
64
66
|
Each coder agent receives a **SELF-CONTAINED** mission briefing that includes EVERYTHING it needs — it will NOT call codebase-architect or plan-auditor itself:
|
|
65
67
|
|
|
@@ -193,16 +193,36 @@ ENV_FILES=("${_clean[@]:-}")
|
|
|
193
193
|
[ -n "$BACKLOG_DIR" ] || { BACKLOG_DIR="$(config_scalar paths backlog_dir)"; [ -n "$BACKLOG_DIR" ] || BACKLOG_DIR="backlog"; }
|
|
194
194
|
[ -n "$INSTALL_CMD" ] || INSTALL_CMD="npm install"
|
|
195
195
|
# toolchain.commands.* only when features.has_toolchain: true; else the defaults.
|
|
196
|
+
# Resolves a YAML scalar to its TRUE value: an empty scalar written any of the
|
|
197
|
+
# legal ways — `build:`, `build: ''`, `build: ""`, `build: ~`, `build: null` —
|
|
198
|
+
# MUST collapse to the empty string so the caller falls back to the default.
|
|
199
|
+
# (The old single-line sed stripped only DOUBLE quotes, so `build: ''` came
|
|
200
|
+
# through as the literal 2-char string `''` → non-empty → no fallback →
|
|
201
|
+
# `bash -c "''"` → `bash: : command not found` → spurious `baseline: fail`.)
|
|
196
202
|
_tc() {
|
|
197
|
-
local cfg="$MAIN/baldart.config.yml"
|
|
203
|
+
local cfg="$MAIN/baldart.config.yml" raw
|
|
198
204
|
grep -E '^[[:space:]]*has_toolchain:[[:space:]]*true' "$cfg" >/dev/null 2>&1 || return 0
|
|
199
|
-
grep -A20 '^toolchain:' "$cfg" 2>/dev/null | grep -A15 '^[[:space:]]*commands:' \
|
|
205
|
+
raw="$(grep -A20 '^toolchain:' "$cfg" 2>/dev/null | grep -A15 '^[[:space:]]*commands:' \
|
|
200
206
|
| grep -E "^[[:space:]]+$1:" | head -1 \
|
|
201
|
-
| sed -E "s
|
|
207
|
+
| sed -E "s/^[[:space:]]*$1:[[:space:]]*//" \
|
|
208
|
+
| sed -E 's/[[:space:]]+#.*$//; s/[[:space:]]+$//')" # drop YAML inline comment (space + #) + trailing ws
|
|
209
|
+
# strip ONE layer of matching surrounding quotes (single or double) → '' / "" become empty
|
|
210
|
+
case "$raw" in
|
|
211
|
+
\"*\") raw="${raw#\"}"; raw="${raw%\"}" ;;
|
|
212
|
+
\'*\') raw="${raw#\'}"; raw="${raw%\'}" ;;
|
|
213
|
+
esac
|
|
214
|
+
# explicit YAML null markers → empty
|
|
215
|
+
case "$raw" in '~'|null|Null|NULL) raw="" ;; esac
|
|
216
|
+
printf '%s' "$raw"
|
|
202
217
|
}
|
|
203
218
|
[ -n "$TC_TC" ] || TC_TC="$(_tc typecheck)"; [ -n "$TC_TC" ] || TC_TC="npx tsc --noEmit"
|
|
204
219
|
[ -n "$TC_LINT" ] || TC_LINT="$(_tc lint)"; [ -n "$TC_LINT" ] || TC_LINT="npx eslint --max-warnings=0 src/"
|
|
205
|
-
|
|
220
|
+
# Build provenance matters for the SKIP tier (toolchain-protocol.md): a CONFIGURED
|
|
221
|
+
# build that fails is a real gate failure; the FALLBACK default `npm run build` on
|
|
222
|
+
# a project with no `build` script is a no-build library → SKIP, never fail.
|
|
223
|
+
BUILD_FROM_CONFIG=1
|
|
224
|
+
[ -n "$TC_BUILD" ] || TC_BUILD="$(_tc build)"
|
|
225
|
+
[ -n "$TC_BUILD" ] || { TC_BUILD="npm run build"; BUILD_FROM_CONFIG=0; }
|
|
206
226
|
|
|
207
227
|
# --- log sink --------------------------------------------------------------
|
|
208
228
|
[ -n "$LOG" ] || LOG="/tmp/wt-setup-${SLUG}.log"
|
|
@@ -427,23 +447,35 @@ TIMEOUT_BIN=""
|
|
|
427
447
|
command -v timeout >/dev/null 2>&1 && TIMEOUT_BIN="timeout"
|
|
428
448
|
[ -z "$TIMEOUT_BIN" ] && command -v gtimeout >/dev/null 2>&1 && TIMEOUT_BIN="gtimeout"
|
|
429
449
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
450
|
+
# SKIP tier (toolchain-protocol.md): the FALLBACK default `npm run build` is only
|
|
451
|
+
# a real gate when the project actually defines a `build` script. A library with
|
|
452
|
+
# none → no-build project → SKIP (baseline stays pass), never a spurious fail. A
|
|
453
|
+
# CONFIGURED build command is always run as-is (its failure IS a real failure).
|
|
454
|
+
RUN_BUILD=1
|
|
455
|
+
if [ "$BUILD_FROM_CONFIG" = 0 ] && [ -f package.json ]; then
|
|
456
|
+
node -e 'const s=(require("./package.json").scripts)||{};process.exit(s.build?0:1)' 2>/dev/null \
|
|
457
|
+
|| { RUN_BUILD=0; err "… baseline build SKIPPED (no toolchain build command + no 'build' npm script → no-build project, per toolchain-protocol tier 3)"; }
|
|
438
458
|
fi
|
|
439
459
|
|
|
440
|
-
if [ "$
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
460
|
+
if [ "$RUN_BUILD" = 1 ]; then
|
|
461
|
+
err "… baseline build ($TC_BUILD, timeout ${BUILD_TIMEOUT}s)"
|
|
462
|
+
if [ -n "$TIMEOUT_BIN" ]; then
|
|
463
|
+
CI=1 "$TIMEOUT_BIN" "$BUILD_TIMEOUT" bash -c "$TC_BUILD" </dev/null >>"$LOG" 2>&1
|
|
464
|
+
BUILD_RC=$?
|
|
465
|
+
else
|
|
466
|
+
err "WARN: no 'timeout'/'gtimeout' binary — running build WITHOUT a hard timeout."
|
|
467
|
+
CI=1 bash -c "$TC_BUILD" </dev/null >>"$LOG" 2>&1
|
|
468
|
+
BUILD_RC=$?
|
|
469
|
+
fi
|
|
470
|
+
|
|
471
|
+
if [ "$BUILD_RC" -eq 124 ] && [ -n "$TIMEOUT_BIN" ]; then
|
|
472
|
+
M_BASELINE="timeout"; M_BLOG="$LOG"
|
|
473
|
+
# leave buildVerified:false in the registry (already provisional)
|
|
474
|
+
fail "baseline build TIMED OUT after ${BUILD_TIMEOUT}s — partial log at $LOG" 7
|
|
475
|
+
elif [ "$BUILD_RC" -ne 0 ]; then
|
|
476
|
+
M_BASELINE="fail"; M_BLOG="$LOG"
|
|
477
|
+
fail "baseline build FAILED (do NOT fix here — the coder repairs it) — see $LOG" 7
|
|
478
|
+
fi
|
|
447
479
|
fi
|
|
448
480
|
|
|
449
481
|
# 10. Build passed → flip buildVerified:true in the registry and finish clean.
|
|
@@ -39,6 +39,26 @@ const cfg = a.config || {}
|
|
|
39
39
|
const highRisk = (cfg.paths && cfg.paths.high_risk_modules) || [] // security-domain hint
|
|
40
40
|
const protocolRef = '.claude/skills/new/references/review-cycle.md'
|
|
41
41
|
|
|
42
|
+
// Concurrency cap (v4.53.6 — API rate-limit guardrail). The org-level rate limit is SHARED across
|
|
43
|
+
// every terminal/session on the account; a wide agent fan-out saturates it and the server throttles
|
|
44
|
+
// (kills agents mid-work). Cap concurrent agents at 3 — a rolling worker pool (NOT chunked barriers,
|
|
45
|
+
// which would block fast finders behind the slow background Codex poll). Mirrors the team-mode coder
|
|
46
|
+
// cap so review + implementation obey the same ceiling.
|
|
47
|
+
const MAX_PARALLEL = 3
|
|
48
|
+
async function parallelCapped(thunks, cap) {
|
|
49
|
+
if (!Array.isArray(thunks) || !thunks.length) return []
|
|
50
|
+
const results = new Array(thunks.length)
|
|
51
|
+
let next = 0
|
|
52
|
+
const worker = async () => {
|
|
53
|
+
while (next < thunks.length) {
|
|
54
|
+
const i = next++
|
|
55
|
+
try { results[i] = await thunks[i]() } catch (_) { results[i] = null }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
await Promise.all(Array.from({ length: Math.min(cap, thunks.length) }, worker))
|
|
59
|
+
return results
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
// Curated toolchain (since v4.41.0): when features.has_toolchain is on, the
|
|
43
63
|
// consumer records LITERAL gate commands in toolchain.commands.* — agents run
|
|
44
64
|
// THOSE instead of guessing (e.g. `npx biome check .` not `npm run lint`). Empty
|
|
@@ -270,7 +290,7 @@ if (maxQaTier === 'full') {
|
|
|
270
290
|
log('Discovery: qa-sentinel SKIPPED (wave max tier ≤ light — full suite deferred to the Final FULL gate).')
|
|
271
291
|
}
|
|
272
292
|
|
|
273
|
-
const findResults = (await
|
|
293
|
+
const findResults = (await parallelCapped(findThunks, MAX_PARALLEL)).filter(Boolean)
|
|
274
294
|
|
|
275
295
|
// ---- Fan-in: collect findings + Codex fallback branch -----------------------
|
|
276
296
|
let raw = []
|
|
@@ -314,7 +334,7 @@ raw.forEach((f, i) => { f.finding_id = `${f.source || 'src'}#${i}:${f.finding_id
|
|
|
314
334
|
// Phase Verify — specialist-owned validation (parity with new-final-review.js F.4)
|
|
315
335
|
// ───────────────────────────────────────────────────────────────────────────
|
|
316
336
|
phase('Verify')
|
|
317
|
-
const classified = (await
|
|
337
|
+
const classified = (await parallelCapped(raw.map((f) => () => verifyFinding(f)), MAX_PARALLEL)).filter(Boolean)
|
|
318
338
|
|
|
319
339
|
function domainVerifier(domain) {
|
|
320
340
|
const d = String(domain || 'code').toLowerCase()
|
|
@@ -36,6 +36,25 @@ const cfg = a.config || {}
|
|
|
36
36
|
const highRisk = (cfg.paths && cfg.paths.high_risk_modules) || [] // security-domain hint
|
|
37
37
|
const protocolRef = '.claude/skills/new/references/final-review.md'
|
|
38
38
|
|
|
39
|
+
// Concurrency cap (v4.53.6 — API rate-limit guardrail; mirrors new-card-review.js + the team-mode
|
|
40
|
+
// coder cap). The org-level rate limit is SHARED across every terminal/session; a wide agent fan-out
|
|
41
|
+
// saturates it and the server throttles. Cap concurrent agents at 3 via a rolling worker pool (NOT
|
|
42
|
+
// chunked barriers — those would block fast finders behind the slow background Codex poll).
|
|
43
|
+
const MAX_PARALLEL = 3
|
|
44
|
+
async function parallelCapped(thunks, cap) {
|
|
45
|
+
if (!Array.isArray(thunks) || !thunks.length) return []
|
|
46
|
+
const results = new Array(thunks.length)
|
|
47
|
+
let next = 0
|
|
48
|
+
const worker = async () => {
|
|
49
|
+
while (next < thunks.length) {
|
|
50
|
+
const i = next++
|
|
51
|
+
try { results[i] = await thunks[i]() } catch (_) { results[i] = null }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
await Promise.all(Array.from({ length: Math.min(cap, thunks.length) }, worker))
|
|
55
|
+
return results
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
if (!scope.length) {
|
|
40
59
|
log('new-final-review: empty review scope — nothing to review.')
|
|
41
60
|
return { codexEngine: 'none', findings: [], gateTable: [], summary: emptySummary() }
|
|
@@ -232,7 +251,7 @@ if (!slimApi && a.hasApiDataFiles !== false) {
|
|
|
232
251
|
: 'Review: api-perf-cost-auditor skipped (no API/data files in scope).')
|
|
233
252
|
}
|
|
234
253
|
|
|
235
|
-
const reviewResults = (await
|
|
254
|
+
const reviewResults = (await parallelCapped(reviewThunks, MAX_PARALLEL)).filter(Boolean)
|
|
236
255
|
|
|
237
256
|
// ---- Fan-in (F.4 step 9): collect findings + Codex fallback branch ----------
|
|
238
257
|
let raw = []
|
|
@@ -291,7 +310,7 @@ raw.forEach((f, i) => { f.finding_id = `${f.source || 'src'}#${i}:${f.finding_id
|
|
|
291
310
|
// self-judge, never a silent drop).
|
|
292
311
|
// ───────────────────────────────────────────────────────────────────────────
|
|
293
312
|
phase('Verify')
|
|
294
|
-
const classified = (await
|
|
313
|
+
const classified = (await parallelCapped(raw.map((f) => () => verifyFinding(f)), MAX_PARALLEL)).filter(Boolean)
|
|
295
314
|
|
|
296
315
|
// Route a finding to the specialist that OWNS its domain (never a generic code-reviewer for
|
|
297
316
|
// doc/api/security). Kept in sync with new2-resolve.js normDomain() routing buckets.
|