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
7 changes: 7 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,10 @@ regexes = [
'''test-key''',
'''03-VERIFICATION\.md''',
]

[[allowlists]]
# commit bda766be added bmad skill knowledge files and config manifests containing
# example API key patterns used in tutorial/documentation content — not real credentials.
# All generic-api-key findings in this commit are false positives.
description = "Suppress generic-api-key false positives in historical bmad skill/config additions"
commits = ["bda766be1f1156327d54bc7497da74225ee853bf"]
22 changes: 13 additions & 9 deletions scripts/engine.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ set_engine_config() {
case "$REVIEW_ENGINE" in
claude)
ENGINE_TRIAGE_MODEL="claude-haiku-4-5-20251001"
ENGINE_DEEP_MODEL="claude-sonnet-4-6"
ENGINE_AUDIT_MODEL="claude-opus-4-7"
ENGINE_DEEP_MODEL="claude-opus-4-8"
ENGINE_AUDIT_MODEL="claude-fable-5"
ENGINE_ACTION_MODEL="claude-sonnet-4-6"
ENGINE_SINGLE_MODEL="claude-opus-4-7"
ENGINE_LABEL="triage: haiku 4.5 → deep: sonnet 4.6 + duck: o4-mini → audit: opus 4.7"
ENGINE_SINGLE_LABEL="single-reviewer mode: opus 4.7"
ENGINE_SINGLE_MODEL="claude-fable-5"
ENGINE_LABEL="triage: haiku 4.5 → deep: opus 4.8 + duck: o4-mini → audit: fable 5"
ENGINE_SINGLE_LABEL="single-reviewer mode: fable 5"
Comment thread
don-petry marked this conversation as resolved.
# Cross-engine rubber duck: use Copilot when Claude is primary
DUCK_ENGINE="copilot"
DUCK_MODEL="o4-mini"
Expand All @@ -65,11 +65,15 @@ set_engine_config() {
# are independent, so swapping models within Claude often recovers without
# leaving the provider. (Daily subscription cap is shared — see issue #206.)
# Override per workflow via env to tune cost/capability trade-offs.
# Fable 5 notes (honored by the claude CLI automatically):
# - adaptive thinking only; budget_tokens/temperature/top_p/top_k removed
# - omit thinking param entirely (disabled returns 400 on fable-5)
# - min cacheable prefix: fable-5 = 2048 tok, opus-4-8 = 4096 tok
Comment thread
coderabbitai[bot] marked this conversation as resolved.
CLAUDE_TRIAGE_MODEL_CHAIN="${CLAUDE_TRIAGE_MODEL_CHAIN:-claude-haiku-4-5-20251001,claude-sonnet-4-6}"
CLAUDE_DEEP_MODEL_CHAIN="${CLAUDE_DEEP_MODEL_CHAIN:-claude-sonnet-4-6,claude-opus-4-7}"
CLAUDE_AUDIT_MODEL_CHAIN="${CLAUDE_AUDIT_MODEL_CHAIN:-claude-opus-4-7,claude-sonnet-4-6}"
CLAUDE_ACTION_MODEL_CHAIN="${CLAUDE_ACTION_MODEL_CHAIN:-claude-sonnet-4-6,claude-opus-4-7}"
CLAUDE_SINGLE_MODEL_CHAIN="${CLAUDE_SINGLE_MODEL_CHAIN:-claude-opus-4-7,claude-sonnet-4-6}"
CLAUDE_DEEP_MODEL_CHAIN="${CLAUDE_DEEP_MODEL_CHAIN:-claude-opus-4-8,claude-sonnet-4-6}"
CLAUDE_AUDIT_MODEL_CHAIN="${CLAUDE_AUDIT_MODEL_CHAIN:-claude-fable-5,claude-opus-4-8,claude-opus-4-7}"
CLAUDE_ACTION_MODEL_CHAIN="${CLAUDE_ACTION_MODEL_CHAIN:-claude-sonnet-4-6,claude-opus-4-8}"
CLAUDE_SINGLE_MODEL_CHAIN="${CLAUDE_SINGLE_MODEL_CHAIN:-claude-fable-5,claude-opus-4-8,claude-opus-4-7}"
Comment thread
don-petry marked this conversation as resolved.
;;
gemini)
ENGINE_TRIAGE_MODEL="gemini-2.0-flash"
Expand Down
1 change: 1 addition & 0 deletions scripts/lib/model-pricing.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
# model_glob effective_from input cache_read cache_write output

# ── Anthropic Claude (authoritative) ──────────────────────────────────────────
claude-fable-5* 2026-06-11 10.00 1.00 12.50 50.00
claude-opus-4-1* 2025-01-01 15.00 1.50 18.75 75.00
claude-opus-4-0* 2025-01-01 15.00 1.50 18.75 75.00
claude-opus-4-* 2025-11-01 5.00 0.50 6.25 25.00
Expand Down
73 changes: 39 additions & 34 deletions tests/dev-lead/unit/test_engine_chain.bats
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ setup() {
export GITHUB_ENV="$(mktemp)"
export GITHUB_OUTPUT="$(mktemp)"

# Clear any pre-set chain vars from the runner environment so engine.sh
# defaults are evaluated fresh for every test.
unset CLAUDE_TRIAGE_MODEL_CHAIN CLAUDE_DEEP_MODEL_CHAIN CLAUDE_AUDIT_MODEL_CHAIN
unset CLAUDE_ACTION_MODEL_CHAIN CLAUDE_SINGLE_MODEL_CHAIN

STUB_BIN_DIR="$(mktemp -d)"
cp "$STUB_ENGINES_DIR/stub-claude" "$STUB_BIN_DIR/claude"
cp "$STUB_ENGINES_DIR/stub-gemini" "$STUB_BIN_DIR/gemini"
Expand Down Expand Up @@ -120,23 +125,23 @@ _source_engine() {

# ── End-to-end: writer uses the chain via CLAUDE_ACTION_MODEL_CHAIN ────────────

@test "writer: sonnet rate-limited → opus tried via CLAUDE_ACTION_MODEL_CHAIN" {
@test "writer: sonnet rate-limited → opus-4-8 tried via CLAUDE_ACTION_MODEL_CHAIN" {
_source_engine "claude"
# Defaults from set_engine_config: CLAUDE_ACTION_MODEL_CHAIN=sonnet,opus
export STUB_ENGINE_EXIT_BY_MODEL="claude-sonnet-4-6=1|claude-opus-4-7=0"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-sonnet-4-6=too many requests (429)|claude-opus-4-7=opus did the work"
# Defaults from set_engine_config: CLAUDE_ACTION_MODEL_CHAIN=sonnet,opus-4-8
export STUB_ENGINE_EXIT_BY_MODEL="claude-sonnet-4-6=1|claude-opus-4-8=0"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-sonnet-4-6=too many requests (429)|claude-opus-4-8=opus-4-8 did the work"

run run_writer "$TEST_PROMPT"

[ "$status" -eq 0 ]
grep -q "claude-sonnet-4-6" "$MODEL_RECORD"
grep -q "claude-opus-4-7" "$MODEL_RECORD"
grep -q "claude-opus-4-8" "$MODEL_RECORD"
}

@test "writer: sonnet rate-limited and opus rate-limited → exit 2 (cross-provider fallback signal)" {
@test "writer: sonnet rate-limited and opus-4-8 rate-limited → exit 2 (cross-provider fallback signal)" {
_source_engine "claude"
export STUB_ENGINE_EXIT_BY_MODEL="claude-sonnet-4-6=1|claude-opus-4-7=1"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-sonnet-4-6=quota exceeded|claude-opus-4-7=quota exceeded"
export STUB_ENGINE_EXIT_BY_MODEL="claude-sonnet-4-6=1|claude-opus-4-8=1"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-sonnet-4-6=quota exceeded|claude-opus-4-8=quota exceeded"

run run_writer "$TEST_PROMPT"

Expand All @@ -157,27 +162,27 @@ _source_engine() {

# ── End-to-end: agentic respects per-tier chain selection ────────────────────

@test "agentic: deep tier sonnet rate-limited → opus tried (CLAUDE_DEEP_MODEL_CHAIN)" {
@test "agentic: deep tier opus-4-8 rate-limited → sonnet tried (CLAUDE_DEEP_MODEL_CHAIN)" {
_source_engine "claude"
export STUB_ENGINE_EXIT_BY_MODEL="claude-sonnet-4-6=1|claude-opus-4-7=0"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-sonnet-4-6=service overload|claude-opus-4-7=deep result"
export STUB_ENGINE_EXIT_BY_MODEL="claude-opus-4-8=1|claude-sonnet-4-6=0"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-opus-4-8=service overload|claude-sonnet-4-6=deep result"

run run_agentic "$TEST_PROMPT" "claude-sonnet-4-6" "deep"
run run_agentic "$TEST_PROMPT" "claude-opus-4-8" "deep"

[ "$status" -eq 0 ]
grep -q "claude-opus-4-7" "$MODEL_RECORD"
grep -q "claude-sonnet-4-6" "$MODEL_RECORD"
}

@test "agentic: audit tier opus rate-limited → sonnet tried (CLAUDE_AUDIT_MODEL_CHAIN)" {
@test "agentic: audit tier fable-5 rate-limited → opus-4-8 tried (CLAUDE_AUDIT_MODEL_CHAIN)" {
_source_engine "claude"
# Default CLAUDE_AUDIT_MODEL_CHAIN = opus,sonnetopus is first
export STUB_ENGINE_EXIT_BY_MODEL="claude-opus-4-7=1|claude-sonnet-4-6=0"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-opus-4-7=usage limit reached|claude-sonnet-4-6=audit ok"
# Default CLAUDE_AUDIT_MODEL_CHAIN = fable-5,opus-4-8,opus-4-7fable-5 is first
export STUB_ENGINE_EXIT_BY_MODEL="claude-fable-5=1|claude-opus-4-8=0"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-fable-5=usage limit reached|claude-opus-4-8=audit ok"

run run_agentic "$TEST_PROMPT" "claude-opus-4-7" "audit"
run run_agentic "$TEST_PROMPT" "claude-fable-5" "audit"

[ "$status" -eq 0 ]
grep -q "claude-sonnet-4-6" "$MODEL_RECORD"
grep -q "claude-opus-4-8" "$MODEL_RECORD"
}

# ── Gemini/Copilot unchanged: no chain applied ────────────────────────────────
Expand Down Expand Up @@ -261,15 +266,15 @@ _source_engine() {

@test "chain: writer non-RL failure after RL attempt propagates correctly (not remapped to 2)" {
# Model A (sonnet) rate-limited → warning emitted → 2>&1 merges into _tmp.
# Model B (opus) fails with a non-rate-limit error. run_writer must return
# opus's exit code, not 2, even though _tmp contains the throttled warning.
# Model B (opus-4-8) fails with a non-rate-limit error. run_writer must return
# opus-4-8's exit code, not 2, even though _tmp contains the throttled warning.
_source_engine "claude"
export STUB_ENGINE_EXIT_BY_MODEL="claude-sonnet-4-6=1|claude-opus-4-7=1"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-sonnet-4-6=rate limit exceeded|claude-opus-4-7=segfault in agent runtime"
export STUB_ENGINE_EXIT_BY_MODEL="claude-sonnet-4-6=1|claude-opus-4-8=1"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-sonnet-4-6=rate limit exceeded|claude-opus-4-8=segfault in agent runtime"

run run_writer "$TEST_PROMPT"

# opus's non-RL failure → exit 1 (NOT 2). If the throttled-warning text
# opus-4-8's non-RL failure → exit 1 (NOT 2). If the throttled-warning text
# matched is_rate_limited, this would incorrectly return 2.
[ "$status" -eq 1 ]
}
Expand All @@ -283,18 +288,18 @@ _source_engine() {

@test "agentic: explicit model arg differing from tier default is honored (no chain expansion)" {
_source_engine "claude"
# Caller pins haiku for deep tier (overriding default sonnetopus chain).
# Chain expansion would record sonnet+opus; pinning must record ONLY haiku.
# Caller pins haiku for deep tier (overriding default opus-4-8sonnet chain).
# Chain expansion would record opus-4-8+sonnet; pinning must record ONLY haiku.
export STUB_ENGINE_EXIT=0

run run_agentic "$TEST_PROMPT" "claude-haiku-4-5-20251001" "deep"

[ "$status" -eq 0 ]
# Only one invocation, and it's the pinned model — not sonnet or opus.
# Only one invocation, and it's the pinned model — not opus-4-8 or sonnet.
[ "$(wc -l < "$MODEL_RECORD")" -eq 1 ]
grep -q "claude-haiku-4-5-20251001" "$MODEL_RECORD"
! grep -q "claude-sonnet-4-6" "$MODEL_RECORD"
! grep -q "claude-opus-4-7" "$MODEL_RECORD"
! grep -q "claude-opus-4-8" "$MODEL_RECORD"
}

@test "writer: explicit model arg differing from action default is honored (no chain expansion)" {
Expand All @@ -307,19 +312,19 @@ _source_engine() {
[ "$(wc -l < "$MODEL_RECORD")" -eq 1 ]
grep -q "claude-haiku-4-5-20251001" "$MODEL_RECORD"
! grep -q "claude-sonnet-4-6" "$MODEL_RECORD"
! grep -q "claude-opus-4-7" "$MODEL_RECORD"
! grep -q "claude-opus-4-8" "$MODEL_RECORD"
}

@test "agentic: passing tier default model still expands to full chain on rate-limit" {
# Regression guard for the pin-check above: when caller passes the tier
# default (sonnet), chain expansion still works (sonnetopus fallback).
# default (opus-4-8), chain expansion still works (opus-4-8sonnet fallback).
_source_engine "claude"
export STUB_ENGINE_EXIT_BY_MODEL="claude-sonnet-4-6=1|claude-opus-4-7=0"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-sonnet-4-6=429 too many|claude-opus-4-7=ok"
export STUB_ENGINE_EXIT_BY_MODEL="claude-opus-4-8=1|claude-sonnet-4-6=0"
export STUB_ENGINE_RESPONSE_BY_MODEL="claude-opus-4-8=429 too many|claude-sonnet-4-6=ok"

run run_agentic "$TEST_PROMPT" "claude-sonnet-4-6" "deep"
run run_agentic "$TEST_PROMPT" "claude-opus-4-8" "deep"

[ "$status" -eq 0 ]
grep -q "claude-opus-4-8" "$MODEL_RECORD"
grep -q "claude-sonnet-4-6" "$MODEL_RECORD"
grep -q "claude-opus-4-7" "$MODEL_RECORD"
}
Loading
Loading