diff --git a/scripts/ask-minimax-run b/scripts/ask-minimax-run index 0cbabb6..f18ed03 100755 --- a/scripts/ask-minimax-run +++ b/scripts/ask-minimax-run @@ -26,4 +26,15 @@ export CLAUDE_CONFIG_DIR="$MINIMAX_DIR" export CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1 export COLORTERM=truecolor +# MINIMAX_EFFORT (provider.env) applies to Flash models only, unless the caller passed --effort. +model="${ANTHROPIC_MODEL:-}"; has_effort=0; prev="" +for a in "$@"; do + [[ "$prev" == "--model" ]] && model="$a" + [[ "$a" == "--effort" ]] && has_effort=1 + prev="$a" +done +if [[ -n "${MINIMAX_EFFORT:-}" && $has_effort -eq 0 && "$model" == *Flash* ]]; then + set -- --effort "$MINIMAX_EFFORT" "$@" +fi + exec claude "$@" diff --git a/scripts/ci-triage-minimax-run b/scripts/ci-triage-minimax-run index baff2ca..e4bb71c 100755 --- a/scripts/ci-triage-minimax-run +++ b/scripts/ci-triage-minimax-run @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Triage a failing Gitea Actions run in a MiniMax M3 sub-session. +# Triage a failing Gitea Actions run in a MiniMax sub-session. # Fetches logs via gitea-agent-companion (token stays inside gac), redacts # credential-shaped content, and has MiniMax write a structured triage file. # Spec: specs/ci-triage-minimax-run.spec.md. Used by /ci-triage-minimax. @@ -7,7 +7,7 @@ # Usage: # ci-triage-minimax-run --repo --run [--job ] # [--out ] [--tail N] [--max-turns N] [--timeout SECS] -# [--dryrun|-n] +# [--dryrun|-n] [--m3] [--effort low|medium|high|xhigh|max] # # Exit codes: # 0 triage file written @@ -25,6 +25,8 @@ OUT_FILE="" TAIL_LINES=2000 MAX_TURNS=25 TIMEOUT_SECS=300 +USE_M3=0 +EFFORT_ARG="" DRYRUN=0 CRED_PATTERN='ghp_[A-Za-z0-9]|glpat-|x-access-token:|Authorization:|BEGIN [A-Z ]*PRIVATE KEY|AKIA[0-9A-Z]{16}' @@ -37,11 +39,15 @@ while [[ $# -gt 0 ]]; do --tail) TAIL_LINES="$2"; shift 2 ;; --max-turns) MAX_TURNS="$2"; shift 2 ;; --timeout) TIMEOUT_SECS="$2"; shift 2 ;; + --m3) USE_M3=1; shift ;; + --effort) EFFORT_ARG="$2"; shift 2 ;; --dryrun|-n) DRYRUN=1; shift ;; -h|--help) usage; exit 0 ;; *) echo "error: unknown argument '$1'" >&2; usage >&2; exit 1 ;; esac done +if [[ $USE_M3 -eq 1 ]]; then MODEL="MiniMax-M3[1m]"; EFFORT="$EFFORT_ARG"; else MODEL="MiniMax-M3.1-Flash-Preview"; EFFORT="${EFFORT_ARG:-low}"; fi +[[ -z "$EFFORT" || "$EFFORT" =~ ^(low|medium|high|xhigh|max)$ ]] || { echo "error: --effort must be low|medium|high|xhigh|max" >&2; exit 1; } # --- CT-1 preflight ----------------------------------------------------------- [[ "$REPO" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || { echo "error: --repo must be owner/repo" >&2; exit 1; } @@ -82,7 +88,7 @@ if [[ "$DRYRUN" -eq 1 ]]; then echo "-- failing step --" echo "" echo "-- sub-session command --" - echo "cd && timeout $TIMEOUT_SECS ask-minimax-run --model 'MiniMax-M3[1m]'" \ + echo "cd && timeout $TIMEOUT_SECS ask-minimax-run --model '$MODEL'${EFFORT:+ --effort $EFFORT}" \ "--permission-mode default --allowedTools 'Read,Glob,Grep,Write'" \ "--disallowedTools 'Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch'" \ "--max-turns $MAX_TURNS --output-format json --add-dir $OUT_DIR -p < prompt.txt" @@ -163,7 +169,7 @@ run_once() { return 0 fi ( cd "$WORK_DIR" && timeout "$TIMEOUT_SECS" ask-minimax-run \ - --model "MiniMax-M3[1m]" \ + --model "$MODEL" ${EFFORT:+--effort "$EFFORT"} \ --permission-mode default \ --allowedTools "Read,Glob,Grep,Write" \ --disallowedTools "Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch" \ diff --git a/scripts/explore-minimax-run b/scripts/explore-minimax-run index ae0f2d6..2c98b56 100755 --- a/scripts/explore-minimax-run +++ b/scripts/explore-minimax-run @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Run a read-only codebase exploration sweep in a MiniMax M3 sub-session. +# Run a read-only codebase exploration sweep in a MiniMax sub-session. # Wraps ask-minimax-run with the hardening the exploration case needs: # - --add-dir per search root (paths outside cwd are otherwise denied) # - secrets-root refusal + skip-list in the prompt preamble @@ -11,7 +11,8 @@ # Usage: # explore-minimax-run --root [--root ...] \ # --prompt-file --out \ -# [--breadth medium|very-thorough] [--max-turns N] [--timeout SECS] +# [--breadth medium|very-thorough] [--max-turns N] [--timeout SECS] \ +# [--m3] [--effort low|medium|high|xhigh|max] # # Exit codes: # 0 answer written to --out @@ -20,7 +21,7 @@ # 3 ran but answer is empty or carries no file:line citations — fall back to an Explore agent set -euo pipefail -usage() { grep '^#' "$0" | sed 's/^# \{0,1\}//' | sed -n '2,20p'; } +usage() { grep '^#' "$0" | sed 's/^# \{0,1\}//' | sed -n '2,21p'; } ROOTS=() PROMPT_FILE="" @@ -28,6 +29,8 @@ OUT_FILE="" BREADTH="medium" MAX_TURNS=40 TIMEOUT_SECS=540 +USE_M3=0 +EFFORT_ARG="" while [[ $# -gt 0 ]]; do case "$1" in @@ -37,10 +40,14 @@ while [[ $# -gt 0 ]]; do --breadth) BREADTH="$2"; shift 2 ;; --max-turns) MAX_TURNS="$2"; shift 2 ;; --timeout) TIMEOUT_SECS="$2"; shift 2 ;; + --m3) USE_M3=1; shift ;; + --effort) EFFORT_ARG="$2"; shift 2 ;; -h|--help) usage; exit 0 ;; *) echo "error: unknown argument '$1'" >&2; usage >&2; exit 1 ;; esac done +if [[ $USE_M3 -eq 1 ]]; then MODEL="MiniMax-M3"; EFFORT="$EFFORT_ARG"; else MODEL="MiniMax-M3.1-Flash-Preview"; EFFORT="${EFFORT_ARG:-medium}"; fi +[[ -z "$EFFORT" || "$EFFORT" =~ ^(low|medium|high|xhigh|max)$ ]] || { echo "error: --effort must be low|medium|high|xhigh|max" >&2; exit 1; } [[ ${#ROOTS[@]} -gt 0 ]] || { echo "error: at least one --root required" >&2; exit 1; } [[ -n "$PROMPT_FILE" && -f "$PROMPT_FILE" ]] || { echo "error: --prompt-file missing or not a file" >&2; exit 1; } @@ -109,7 +116,7 @@ run_once() { # --disallowedTools: the minimax profile defaults to acceptEdits and allows # Edit(/tmp/**)/WebFetch at user level; deny beats allow. timeout "$TIMEOUT_SECS" ask-minimax-run \ - --model MiniMax-M3 \ + --model "$MODEL" ${EFFORT:+--effort "$EFFORT"} \ --permission-mode default \ --allowedTools "Read,Glob,Grep" \ --disallowedTools "Write,Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch" \ diff --git a/scripts/mm-worktree-task b/scripts/mm-worktree-task index 28ed31d..0017b21 100755 --- a/scripts/mm-worktree-task +++ b/scripts/mm-worktree-task @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Launch a MiniMax M3 implementation sub-session in an isolated git worktree, +# Launch a MiniMax implementation sub-session in an isolated git worktree, # with the prompt assembled from a planner work item. See specs/mm-worktree-task.spec.md. set -euo pipefail @@ -15,7 +15,7 @@ usage() { cat <<'EOF' Usage: mm-worktree-task [OPTIONS] -Launch a MiniMax M3 implementation sub-session (via ask-minimax-run) in an +Launch a MiniMax implementation sub-session (via ask-minimax-run) in an isolated git worktree of the current repo, with the prompt assembled from a planner work item. @@ -33,6 +33,9 @@ Options: --docs all|recent|none Which item documents to bundle (default: recent) --tools "" Extra --allowedTools entries (comma-separated) --timeout Sub-session timeout (default: 1800) + --m3 Use MiniMax-M3[1m] instead of the default MiniMax-M3.1-Flash-Preview + --effort Thinking depth low|medium|high|xhigh|max (default: max; + not sent with --m3 unless given) --gate "" Re-run this command in the worktree yourself, after the sub-session exits, against its LAST commit. Appended as a "Supervisor gate" section in the @@ -68,6 +71,8 @@ BASE="main" DOCS="recent" EXTRA_TOOLS="" TIMEOUT=1800 +USE_M3=0 +EFFORT_ARG="" GATE="" GATE_TIMEOUT=900 UPLOAD=0 @@ -87,6 +92,8 @@ while [[ $# -gt 0 ]]; do --gate) GATE="$2"; shift 2 ;; --gate-timeout) GATE_TIMEOUT="$2"; shift 2 ;; --context) CP_CONTEXT="$2"; shift 2 ;; + --m3) USE_M3=1; shift ;; + --effort) EFFORT_ARG="$2"; shift 2 ;; --upload) UPLOAD=1; shift ;; --collect) COLLECT=1; shift ;; --dryrun|-n) DRYRUN=1; shift ;; @@ -98,6 +105,9 @@ while [[ $# -gt 0 ]]; do esac done +if [[ $USE_M3 -eq 1 ]]; then MODEL="MiniMax-M3[1m]"; EFFORT="$EFFORT_ARG"; else MODEL="MiniMax-M3.1-Flash-Preview"; EFFORT="${EFFORT_ARG:-max}"; fi +[[ -z "$EFFORT" || "$EFFORT" =~ ^(low|medium|high|xhigh|max)$ ]] || { err "--effort must be low|medium|high|xhigh|max"; exit 1; } + case "$DOCS" in all|recent|none) ;; *) err "--docs must be all|recent|none"; exit 1 ;; esac if [[ -z "$UUID" ]]; then @@ -374,7 +384,7 @@ EOF PROMPT_CONTENT="$(build_prompt)" -CMD="cd ${WORKTREE} && setsid nohup timeout ${TIMEOUT} ask-minimax-run --model \"MiniMax-M3[1m]\" --allowedTools \"${ALLOWLIST}\" --permission-mode acceptEdits --add-dir ${REPORT_DIR} -p \"\$(cat ${PROMPT_FILE})\" > ${STDOUT_FILE} 2>&1" +CMD="cd ${WORKTREE} && setsid nohup timeout ${TIMEOUT} ask-minimax-run --model \"${MODEL}\"${EFFORT:+ --effort ${EFFORT}} --allowedTools \"${ALLOWLIST}\" --permission-mode acceptEdits --add-dir ${REPORT_DIR} -p \"\$(cat ${PROMPT_FILE})\" > ${STDOUT_FILE} 2>&1" if [[ $DRYRUN -eq 1 ]]; then echo "[dryrun] mm-worktree-task — no changes will be made" @@ -510,7 +520,7 @@ info "launching ask-minimax-run (detached, timeout ${TIMEOUT}s)" cd "$WORKTREE" start=$(date +%s) set +e - setsid nohup timeout "$TIMEOUT" ask-minimax-run --model "MiniMax-M3[1m]" \ + setsid nohup timeout "$TIMEOUT" ask-minimax-run --model "$MODEL" ${EFFORT:+--effort "$EFFORT"} \ --allowedTools "$ALLOWLIST" --permission-mode acceptEdits \ --add-dir "$REPORT_DIR" -p "$(cat "$PROMPT_FILE")" \ > "$STDOUT_FILE" 2>&1 diff --git a/scripts/review-loop-run b/scripts/review-loop-run index 61c3aab..1bf4086 100755 --- a/scripts/review-loop-run +++ b/scripts/review-loop-run @@ -17,6 +17,7 @@ Usage: review-loop-run launch --kind --name [--round N] --file [--file ...] [--context ""] [--prev ] [--tools ""] [--timeout ] + [--m3] [--effort low|medium|high|xhigh|max] [--project ] [--dryrun|-n] review-loop-run status [--name ] [--round N] [--project ] review-loop-run counts @@ -238,7 +239,7 @@ kind_tools() { } cmd_launch() { - local kind="" name="" round=1 context="" prev="" tools="" timeout=1500 project="" dryrun=0 + local kind="" name="" round=1 context="" prev="" tools="" timeout=1500 project="" dryrun=0 use_m3=0 effort_arg="" local -a files=() while [[ $# -gt 0 ]]; do case "$1" in @@ -251,11 +252,15 @@ cmd_launch() { --tools) tools="$2"; shift 2 ;; --timeout) timeout="$2"; shift 2 ;; --project) project="$2"; shift 2 ;; + --m3) use_m3=1; shift ;; + --effort) effort_arg="$2"; shift 2 ;; --dryrun|-n) dryrun=1; shift ;; -h|--help) usage; exit 0 ;; *) die "unknown option: $1" 2 ;; esac done + if [[ $use_m3 -eq 1 ]]; then model="MiniMax-M3"; effort="$effort_arg"; else model="MiniMax-M3.1-Flash-Preview"; effort="${effort_arg:-max}"; fi + [[ -z "$effort" || "$effort" =~ ^(low|medium|high|xhigh|max)$ ]] || die "--effort must be low|medium|high|xhigh|max" 2 [[ " $KINDS " == *" $kind "* ]] || die "--kind must be one of: $KINDS" 2 [[ "$name" =~ ^[A-Za-z0-9._-]+$ ]] || die "--name must match ^[A-Za-z0-9._-]+\$" 2 [[ "$round" =~ ^[0-9]+$ ]] || die "--round must be an integer" 2 @@ -323,7 +328,7 @@ EOF launch_body=$(cat < "$stdout" 2>&1 +timeout ${timeout} ask-minimax-run --model ${model}${effort:+ --effort ${effort}} --allowedTools "$allow" --permission-mode acceptEdits -p "\$(cat "$prompt")" > "$stdout" 2>&1 echo "exit=\$?" >> "$stdout" EOF ) diff --git a/scripts/review-minimax-run b/scripts/review-minimax-run index fe99177..841d57c 100755 --- a/scripts/review-minimax-run +++ b/scripts/review-minimax-run @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# First-pass code review of a git branch in a MiniMax M3 sub-session. +# First-pass code review of a git branch in a MiniMax sub-session. # The wrapper materialises the diff/log itself (the sub-session gets no Bash), # runs MiniMax read-only except for one findings file, and gates the result. # Spec: specs/review-minimax-run.spec.md. Used by /review-minimax; safe directly. @@ -7,7 +7,7 @@ # Usage: # review-minimax-run --worktree [--base ] [--out ] # [--profile agent-runtimes] [--max-diff-lines N] [--max-turns N] -# [--timeout SECS] [--dryrun|-n] +# [--timeout SECS] [--dryrun|-n] [--m3] [--effort low|medium|high|xhigh|max] # # Exit codes: # 0 findings file written (may contain "NO FINDINGS") @@ -25,6 +25,8 @@ PROFILE="" MAX_DIFF_LINES=6000 MAX_TURNS=40 TIMEOUT_SECS=540 +USE_M3=0 +EFFORT_ARG="" DRYRUN=0 # RM-3: credential-shaped content in the diff aborts the review outright. CRED_PATTERN='ghp_[A-Za-z0-9]|glpat-|x-access-token:|Authorization:|BEGIN [A-Z ]*PRIVATE KEY|AKIA[0-9A-Z]{16}' @@ -38,11 +40,15 @@ while [[ $# -gt 0 ]]; do --max-diff-lines) MAX_DIFF_LINES="$2"; shift 2 ;; --max-turns) MAX_TURNS="$2"; shift 2 ;; --timeout) TIMEOUT_SECS="$2"; shift 2 ;; + --m3) USE_M3=1; shift ;; + --effort) EFFORT_ARG="$2"; shift 2 ;; --dryrun|-n) DRYRUN=1; shift ;; -h|--help) usage; exit 0 ;; *) echo "error: unknown argument '$1'" >&2; usage >&2; exit 1 ;; esac done +if [[ $USE_M3 -eq 1 ]]; then MODEL="MiniMax-M3[1m]"; EFFORT="$EFFORT_ARG"; else MODEL="MiniMax-M3.1-Flash-Preview"; EFFORT="${EFFORT_ARG:-max}"; fi +[[ -z "$EFFORT" || "$EFFORT" =~ ^(low|medium|high|xhigh|max)$ ]] || { echo "error: --effort must be low|medium|high|xhigh|max" >&2; exit 1; } # --- RM-1 preflight ----------------------------------------------------------- [[ -n "$WORKTREE" ]] || { echo "error: --worktree required" >&2; exit 1; } @@ -172,7 +178,7 @@ if [[ "$DRYRUN" -eq 1 ]]; then echo "-- prompt --" cat "$FULL_PROMPT" echo "-- command --" - echo "cd $WORK_DIR && timeout $TIMEOUT_SECS ask-minimax-run --model 'MiniMax-M3[1m]'" \ + echo "cd $WORK_DIR && timeout $TIMEOUT_SECS ask-minimax-run --model '$MODEL'${EFFORT:+ --effort $EFFORT}" \ "--permission-mode default --allowedTools 'Read,Glob,Grep,Write'" \ "--disallowedTools 'Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch'" \ "--max-turns $MAX_TURNS --output-format json --add-dir $WORKTREE --add-dir $OUT_DIR -p < prompt.txt" @@ -189,7 +195,7 @@ run_once() { return 0 fi ( cd "$WORK_DIR" && timeout "$TIMEOUT_SECS" ask-minimax-run \ - --model "MiniMax-M3[1m]" \ + --model "$MODEL" ${EFFORT:+--effort "$EFFORT"} \ --permission-mode default \ --allowedTools "Read,Glob,Grep,Write" \ --disallowedTools "Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch" \ diff --git a/specs/ci-triage-minimax-run.spec.md b/specs/ci-triage-minimax-run.spec.md index d968c64..373ea20 100644 --- a/specs/ci-triage-minimax-run.spec.md +++ b/specs/ci-triage-minimax-run.spec.md @@ -8,7 +8,7 @@ Triage a failing Gitea Actions run in a MiniMax M3 sub-session: fetch the run's ``` ci-triage-minimax-run --repo --run [--job ] \ - [--out ] [--tail N] [--max-turns N] [--timeout SECS] [--dryrun|-n] + [--out ] [--tail N] [--max-turns N] [--timeout SECS] [--dryrun|-n] [--m3] [--effort LEVEL] ``` | Flag | Default | Description | @@ -20,6 +20,8 @@ ci-triage-minimax-run --repo --run [--job ] \ | `--tail` | 2000 | Log lines fetched (gac `--tail`) | | `--max-turns` | 25 | Sub-session turn cap | | `--timeout` | 300 | Internal timeout (logs-only task; well under the harness Bash 600s cap) | +| `--m3` | off | Use `MiniMax-M3[1m]` instead of the default `MiniMax-M3.1-Flash-Preview` (1M window natively, always thinks). No effort is sent unless `--effort` is given. | +| `--effort ` | `low` | Thinking depth `low\|medium\|high\|xhigh\|max`, passed to `ask-minimax-run --effort`. Any other value exits 1. | | `--dryrun`, `-n` | off | Print resolved config, the gac command, the full prompt, and the sub-session command line; invoke neither gac's log fetch nor the sub-session | ## Requirements @@ -30,7 +32,7 @@ ci-triage-minimax-run --repo --run [--job ] \ - **CT-4 Redaction.** Before prompt assembly, credential-shaped content in `run-logs.ndjson` (`ghp_…|glpat-|x-access-token:|Authorization:|BEGIN … PRIVATE KEY|AKIA…` — same pattern set as review-minimax-run) is replaced in place with `[REDACTED]`. Redact-and-continue, not abort: CI logs routinely carry unmasked clone URLs and the log is the whole payload. Residual risk: best-effort patterns; novel credential formats can pass. - **CT-5 Envelope parse.** `failed_step`, `sliced`, and `note` are extracted from the NDJSON envelope with jq. If `failed_step` is null (cancelled/crashed run, green multi-job), the prompt states this and the output contract requires the literal `UNKNOWN` under `## Failing step` — no fabricated step names. - **CT-6 Prompt contract.** Fixed preamble: read-only triage of the two files in cwd; explicit error-signature patterns to search (`error:|FAILED|Traceback|exit code|denied|timeout|not found|no such|ECONN|refused`); do not suggest running tests or builds; if the log carries the truncation marker or `sliced` is partial, say so — do not improvise. Output sections, exactly: `## Failing step` / `## Error excerpt` (≤10 verbatim lines) / `## Probable cause` (1–3 sentences, non-empty) / `## Suggested next action` (one command or one edit description — advisory only) / `## Confidence`. -- **CT-7 Invocation.** From the scratch dir: `timeout ask-minimax-run --model "MiniMax-M3[1m]" --permission-mode default --allowedTools "Read,Glob,Grep,Write" --disallowedTools "Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch" --max-turns N --output-format json --add-dir -p`. Same rationale set as review-minimax-run RM-6; no worktree access needed. +- **CT-7 Invocation.** From the scratch dir: `timeout ask-minimax-run --model "$MODEL" --effort "$EFFORT" --permission-mode default --allowedTools "Read,Glob,Grep,Write" --disallowedTools "Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch" --max-turns N --output-format json --add-dir -p`. Same rationale set as review-minimax-run RM-6; no worktree access needed. - **CT-8 Result gate.** Triage file exists, contains `## Failing step`, and has a non-blank line under `## Probable cause`. - **CT-9 Retry.** On MiniMax error or gate failure: remove the out-file, retry once; never retry timeout (rc 124); then exit 2 (transport) or 3 (gate). The gac fetch is NOT retried by the script (single fetch, reused across both attempts). - **CT-10 Test seams.** `CI_TRIAGE_FAKE_LOGS=` bypasses the gac fetch (file used as `run-logs.ndjson`; `jobs.txt` gets a placeholder); `CI_TRIAGE_FAKE_JSON=` substitutes the sub-session envelope; `CI_TRIAGE_FAKE_TRIAGE=` (companion, optional) plays the sub-session's Write of the triage file. Gates and retry logic still run, so exits 0/2/3 and the redaction step are assertable offline. diff --git a/specs/explore-minimax-run.spec.md b/specs/explore-minimax-run.spec.md index 97b5808..e31449c 100644 --- a/specs/explore-minimax-run.spec.md +++ b/specs/explore-minimax-run.spec.md @@ -9,7 +9,7 @@ Run a broad, read-only "where does X live" codebase exploration in a MiniMax M3 ``` explore-minimax-run --root [--root ...] \ --prompt-file --out \ - [--breadth medium|very-thorough] [--max-turns N] [--timeout SECS] + [--breadth medium|very-thorough] [--max-turns N] [--timeout SECS] [--m3] [--effort LEVEL] ``` | Flag | Default | Description | @@ -20,6 +20,8 @@ explore-minimax-run --root [--root ...] \ | `--breadth` | `medium` | `medium` (stop when confidently answered) or `very-thorough` (full sweep, alternate naming conventions) | | `--max-turns` | 40 | Sub-session turn cap | | `--timeout` | 540 | Internal timeout in seconds (keeps callers under the harness Bash 600s cap) | +| `--m3` | off | Use `MiniMax-M3` instead of the default `MiniMax-M3.1-Flash-Preview` (1M window natively, always thinks). No effort is sent unless `--effort` is given. | +| `--effort ` | `medium` | Thinking depth `low\|medium\|high\|xhigh\|max`, passed to `ask-minimax-run --effort`. Any other value exits 1. | ## Behaviour / requirements diff --git a/specs/mm-worktree-task.spec.md b/specs/mm-worktree-task.spec.md index 03d9b1e..af79ef9 100644 --- a/specs/mm-worktree-task.spec.md +++ b/specs/mm-worktree-task.spec.md @@ -26,6 +26,8 @@ mm-worktree-task [OPTIONS] | `--docs all\|recent\|none` | `recent` | Which item documents to bundle. `recent` = documents updated in the last 14 days; falls back to `all` if the CLI does not expose update dates. The full list of document names is always included in the prompt regardless of which are bundled. | | `--tools ""` | — | Extra `--allowedTools` entries merged into the default allowlist (comma-separated). | | `--timeout ` | `1800` | Max seconds the sub-session may run (`timeout ` wrapper). | +| `--m3` | off | Use `MiniMax-M3[1m]` instead of the default `MiniMax-M3.1-Flash-Preview` (1M window natively, always thinks). No effort is sent unless `--effort` is given. | +| `--effort ` | `max` | Thinking depth `low\|medium\|high\|xhigh\|max`, passed to `ask-minimax-run --effort`. Any other value exits 1. | | `--gate ""` | — | Supervisor gate: after the sub-session exits, `mm-worktree-task` itself re-runs this command in the worktree against the sub-session's LAST commit and appends a `## Supervisor gate` section to the result file (see Behaviour §8). Never trust the agent's own "tests pass" claim — this re-run is the check. | | `--gate-timeout ` | `900` | Max seconds `--gate`'s command may run before it is killed and recorded as a timed-out (failed) gate. | | `--context ` | resolved (see §0) | `cp-cli --context` used for every planner call. | @@ -76,7 +78,7 @@ mm-worktree-task [OPTIONS] - If `--brief ` is given, its content is appended (or used standalone as the task body when no uuid was given). 8. Launch detached (skipped under `--dryrun` and `--collect`): ``` - cd && setsid nohup timeout ask-minimax-run --model "MiniMax-M3[1m]" \ + cd && setsid nohup timeout ask-minimax-run --model "$MODEL" --effort "$EFFORT" \ --allowedTools "[,<--tools>]" --permission-mode acceptEdits \ --add-dir -p "$(cat )" \ > /-stdout.txt 2>&1 diff --git a/specs/review-loop-run.spec.md b/specs/review-loop-run.spec.md index c947ff1..7e0549f 100644 --- a/specs/review-loop-run.spec.md +++ b/specs/review-loop-run.spec.md @@ -9,7 +9,7 @@ Launch a detached MiniMax M3 review of a review-loop artefact (plan, spec diff, ``` review-loop-run launch --kind --name [--round N] \ --file [--file ...] [--context ""] [--prev ] \ - [--tools ""] [--timeout ] [--dryrun|-n] + [--tools ""] [--timeout ] [--m3] [--effort ] [--dryrun|-n] review-loop-run status [--name ] [--round N] review-loop-run counts review-loop-run --help @@ -35,6 +35,8 @@ review-loop-run --help | `--prev` | auto | Explicit previous-round result file (overrides the auto lookup). | | `--tools` | kind default | Extra `--allowedTools` entries (comma-separated). | | `--timeout` | 1500 | Seconds before the sub-session is killed. | +| `--m3` | off | Use `MiniMax-M3` instead of the default `MiniMax-M3.1-Flash-Preview` (1M window natively, always thinks). No effort is sent unless `--effort` is given. | +| `--effort ` | `max` | Thinking depth `low\|medium\|high\|xhigh\|max`, passed to `ask-minimax-run --effort`. Any other value exits 1. | | `--project` | current repo root | Repo to `cd` into before launching. | | `--dryrun`, `-n` | off | Print the assembled prompt and the exact launch command; write nothing; launch nothing. | @@ -51,7 +53,7 @@ review-loop-run --help 2. Validate: kind in the closed set, name matches `^[A-Za-z0-9._-]+$`, every `--file` exists, `ask-minimax-run` on PATH, MiniMax profile present. 3. Assemble the prompt from the kind template (§Templates): header (non-interactive, write to the output file), task text, `--context`, the "Files to read" list = `--file` entries + the kind's default best-practices/gotcha files that exist + the previous-round result (rounds ≥2), the output path, constraints. 4. Write `/-r-prompt.txt`. -5. Write `/-r-launch.sh` (a tiny bash script that `cd`s to the repo, runs `timeout ask-minimax-run --model MiniMax-M3 --allowedTools "" --permission-mode acceptEdits -p "$(cat prompt)" > stdout 2>&1`, then appends `exit=$?` to the stdout file) and starts it with `setsid nohup >/dev/null 2>&1 &`. +5. Write `/-r-launch.sh` (a tiny bash script that `cd`s to the repo, runs `timeout ask-minimax-run --model "$MODEL" --effort "$EFFORT" --allowedTools "" --permission-mode acceptEdits -p "$(cat prompt)" > stdout 2>&1`, then appends `exit=$?` to the stdout file) and starts it with `setsid nohup >/dev/null 2>&1 &`. 6. Print the three paths and `launched`. `status` scans `/*-r*-stdout.txt`; a file whose last line matches `^exit=` is `done`, else `running`; counts come from `counts`. diff --git a/specs/review-minimax-run.spec.md b/specs/review-minimax-run.spec.md index 5dd26c7..7cf6083 100644 --- a/specs/review-minimax-run.spec.md +++ b/specs/review-minimax-run.spec.md @@ -9,7 +9,7 @@ Run a first-pass code review of a git branch (typically an agent-produced worktr ``` review-minimax-run --worktree [--base ] [--out ] \ [--profile agent-runtimes] [--max-diff-lines N] [--max-turns N] \ - [--timeout SECS] [--dryrun|-n] + [--timeout SECS] [--dryrun|-n] [--m3] [--effort LEVEL] ``` | Flag | Default | Description | @@ -21,6 +21,8 @@ review-minimax-run --worktree [--base ] [--out ] \ | `--max-diff-lines` | 6000 | Diff budget; larger diffs exit 1 with the count | | `--max-turns` | 40 | Sub-session turn cap | | `--timeout` | 540 | Internal timeout (keeps callers under the harness Bash 600s cap). Why: a longer run invoked synchronously from a skill is killed by the harness with no findings file. | +| `--m3` | off | Use `MiniMax-M3[1m]` instead of the default `MiniMax-M3.1-Flash-Preview` (1M window natively, always thinks). No effort is sent unless `--effort` is given. | +| `--effort ` | `max` | Thinking depth `low\|medium\|high\|xhigh\|max`, passed to `ask-minimax-run --effort`. Any other value exits 1. | | `--dryrun`, `-n` | off | Print resolved config, materialised-input plan, full prompt, and the exact sub-session command line; create/invoke nothing beyond the scratch dir | ## Requirements @@ -30,7 +32,7 @@ review-minimax-run --worktree [--base ] [--out ] \ - **RM-3 Redaction gate.** If `diff.patch` matches `ghp_[A-Za-z0-9]|glpat-|x-access-token:|Authorization:|BEGIN [A-Z ]*PRIVATE KEY|AKIA[0-9A-Z]{16}`, exit 1 with "diff contains credential-shaped content; review in-session". No redact-and-continue for diffs: a credential in a diff means the branch itself needs attention. - **RM-4 Empty/oversize diff.** Empty `diff.patch` → exit 1 "nothing to review". More than `--max-diff-lines` lines → exit 1 with the count. - **RM-5 Prompt contract.** Fixed preamble: first-pass reviewer, non-interactive; inputs are the three files in cwd; the worktree is readable for surrounding context only; write ONLY the findings file at the given path; never modify any other file; do not run anything, fix anything, or improvise if an input is missing/truncated — say so and stop. Checklist = unconditional core (distilled from llm-code-security §10/§12: injection, hardcoded credentials, unquoted shell vars, verify=False, weak hashing/random, missing input validation, chmod 777, curl|bash, unpinned images/deps, missing idempotency, unconditional health checks, missing locking) as explicit grep-able patterns; `--profile agent-runtimes` appends the 5 recurring MiniMax test-file bugs. Findings format: `### [BLOCKER|SHOULD-FIX|NIT] ` blocks with `file:line`, ≤10-line excerpt, category, `Confidence: confirmed|suspected`; clean review → literal `NO FINDINGS`. -- **RM-6 Invocation.** From the scratch dir: `timeout <secs> ask-minimax-run --model "MiniMax-M3[1m]" --permission-mode default --allowedTools "Read,Glob,Grep,Write" --disallowedTools "Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch" --max-turns N --output-format json --add-dir <worktree> --add-dir <out-dir> -p < prompt > response.json`. Why each: `[1m]` pins the 1M window (unknown model ids get 200k + early auto-compact); `Write` must be allowlisted or MiniMax exits 0 without creating the file; `default` not `acceptEdits` — new-file Write needs no edit approval and nothing else is auto-approved; deny-list beats the profile's permissive defaults. +- **RM-6 Invocation.** From the scratch dir: `timeout <secs> ask-minimax-run --model "$MODEL" --effort "$EFFORT" --permission-mode default --allowedTools "Read,Glob,Grep,Write" --disallowedTools "Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch" --max-turns N --output-format json --add-dir <worktree> --add-dir <out-dir> -p < prompt > response.json`. Why each: `$MODEL` is Flash (1M natively) by default; with `--m3`, `[1m]` pins the 1M window (unknown model ids get 200k + early auto-compact); `Write` must be allowlisted or MiniMax exits 0 without creating the file; `default` not `acceptEdits` — new-file Write needs no edit approval and nothing else is auto-approved; deny-list beats the profile's permissive defaults. - **RM-7 Clean-tree assertion.** After each attempt, `git -C <worktree> status --porcelain` must be empty; a dirtied tree is exit 2 (and the dirt is reported, not reverted). - **RM-8 Result gate.** Findings file exists AND (contains `NO FINDINGS` OR (≥1 line matching `^### \[(BLOCKER|SHOULD-FIX|NIT)\]` AND ≥1 finding cites a path that exists relative to the worktree)). A bare `:NNN` match is NOT sufficient — timestamps and counters satisfy that. - **RM-9 Retry.** On MiniMax error OR gate failure: remove the out-file and retry once (covers the "exit 0, file never written" failure and prevents a stale attempt-1 file passing the gate). Never retry a timeout (rc 124). Then exit 2 (transport/timeout/dirty-tree) or 3 (gate). diff --git a/tests/fixtures/mm-worktree-task-docs/list.json b/tests/fixtures/mm-worktree-task-docs/list.json index 33b57ae..241a862 100644 --- a/tests/fixtures/mm-worktree-task-docs/list.json +++ b/tests/fixtures/mm-worktree-task-docs/list.json @@ -1 +1 @@ -{"documents": [{"name": "notes.md", "updated_at": "2026-09-01T00:00:00Z"}, {"name": "old-notes.md", "updated_at": "2020-01-01T00:00:00Z"}]} +{"documents": [{"name": "notes.md", "updated_at": "2026-10-10T00:00:00Z"}, {"name": "old-notes.md", "updated_at": "2020-01-01T00:00:00Z"}]} diff --git a/tests/test-ci-triage-minimax-run.sh b/tests/test-ci-triage-minimax-run.sh index 14362f5..123a5fc 100755 --- a/tests/test-ci-triage-minimax-run.sh +++ b/tests/test-ci-triage-minimax-run.sh @@ -27,6 +27,16 @@ assert_contains() { fi } +assert_not_contains() { + local label=$1 needle=$2 haystack=$3 + if [[ "$haystack" != *"$needle"* ]]; then + printf " ${GREEN}PASS${NC} %s\n" "$label"; PASS=$((PASS + 1)) + else + printf " ${RED}FAIL${NC} %s\n" "$label" + printf " expected NOT to contain: %s\n" "$needle"; FAIL=$((FAIL + 1)) + fi +} + assert_eq() { local label=$1 expected=$2 actual=$3 if [[ "$expected" == "$actual" ]]; then @@ -70,10 +80,20 @@ echo "== dryrun ==" DRY_OUT="$("$SCRIPT" --repo skynet/gac-scratch --run 42 --dryrun 2>&1)" assert_eq "dryrun exits 0" 0 $? assert_contains "dryrun shows gac command" "inspect skynet/gac-scratch --run 42" "$DRY_OUT" -assert_contains "dryrun shows model pin" "MiniMax-M3[1m]" "$DRY_OUT" +assert_contains "dryrun defaults to Flash" "MiniMax-M3.1-Flash-Preview" "$DRY_OUT" +assert_contains "dryrun default effort low" "--effort low" "$DRY_OUT" assert_contains "dryrun defers fetch" "not fetched in dryrun" "$DRY_OUT" assert_contains "dryrun shows job passthrough default" "job: auto" "$DRY_OUT" +echo "== dryrun --m3/--effort ==" +M3_OUT="$("$SCRIPT" --repo skynet/gac-scratch --run 42 --m3 --dryrun 2>&1)" +assert_eq "m3 dryrun exits 0" 0 $? +assert_contains "m3 dryrun shows M3 model" "MiniMax-M3[1m]" "$M3_OUT" +assert_not_contains "m3 dryrun sends no effort" "--effort" "$M3_OUT" +EF_OUT="$("$SCRIPT" --repo skynet/gac-scratch --run 42 --effort high --dryrun 2>&1)" +assert_contains "explicit effort overrides default" "--effort high" "$EF_OUT" +"$SCRIPT" --repo o/r --run 1 --effort bogus --dryrun >/dev/null 2>&1; assert_eq "bad effort exits 1" 1 $? + echo "== fake logs: redaction + failing step in prompt, exit 0 ==" OUT0="$TMP/out0.md" RUN_OUT="$(CI_TRIAGE_FAKE_LOGS="$FAKE_LOGS" CI_TRIAGE_FAKE_JSON="$FAKE_OK_JSON" CI_TRIAGE_FAKE_TRIAGE="$FAKE_TRIAGE" \ diff --git a/tests/test-mm-worktree-task.sh b/tests/test-mm-worktree-task.sh index 1d3fbb6..c47fbb5 100755 --- a/tests/test-mm-worktree-task.sh +++ b/tests/test-mm-worktree-task.sh @@ -83,7 +83,15 @@ assert_contains "dryrun shows worktree path" ".claude/worktrees/mm-testslug" "$D assert_contains "dryrun shows branch name" "mm/testslug" "$DRY_OUT" assert_contains "dryrun shows bundle path" "testslug-bundle.md" "$DRY_OUT" assert_contains "dryrun shows the ask-minimax-run command" "ask-minimax-run" "$DRY_OUT" -assert_contains "dryrun command uses MiniMax-M3[1m] model" 'MiniMax-M3[1m]' "$DRY_OUT" +assert_contains "dryrun command defaults to Flash model" 'MiniMax-M3.1-Flash-Preview' "$DRY_OUT" +assert_contains "dryrun default effort max" "--effort max" "$DRY_OUT" +M3_OUT="$("$SCRIPT" "$TEST_UUID" --slug testslug --m3 --dryrun 2>&1)" +assert_eq "m3 dryrun exits 0" "0" "$?" +assert_contains "m3 dryrun uses M3[1m] model" 'MiniMax-M3[1m]' "$M3_OUT" +assert_not_contains "m3 dryrun sends no effort" "--effort" "$M3_OUT" +EF_OUT="$("$SCRIPT" "$TEST_UUID" --slug testslug --effort low --dryrun 2>&1)" +assert_contains "explicit effort overrides default" "--effort low" "$EF_OUT" +"$SCRIPT" "$TEST_UUID" --effort bogus --dryrun >/dev/null 2>&1; assert_eq "bad effort exits 1" "1" "$?" assert_contains "dryrun bundles recent doc notes.md" "notes.md (bundled below)" "$DRY_OUT" assert_contains "dryrun lists old doc as available only" "old-notes.md (available only)" "$DRY_OUT" assert_not_contains "dryrun does not bundle stale doc body" "Stale notes, should not be bundled" "$DRY_OUT" diff --git a/tests/test-review-loop-run.sh b/tests/test-review-loop-run.sh index 70db268..576054a 100755 --- a/tests/test-review-loop-run.sh +++ b/tests/test-review-loop-run.sh @@ -19,6 +19,9 @@ assert_contains "dryrun banner" "[dryrun]" "$out" assert_contains "nothing launched" "nothing launched" "$out" assert_contains "task text is the spec template" "OUT OF SCOPE: reporting that a symbol" "$out" assert_contains "context included" "Context: closes bug 1234" "$out" +assert_contains "defaults to Flash" "--model MiniMax-M3.1-Flash-Preview --effort max" "$out" +out_m3=$("$SCRIPT" launch --kind spec --name spec-Y --file "$TMP/spec.diff" --m3 -n 2>&1) +assert_contains "--m3 uses M3 with no effort" "--model MiniMax-M3 --allowedTools" "$out_m3" assert_contains "output path uses tag" "spec-X-r1-result.md" "$out" assert_contains "present default listed" "$TMP/bp/spec-driven-development.md" "$out" assert_contains "absent default skipped" "default file absent, skipped" "$out" diff --git a/tests/test-review-minimax-run.sh b/tests/test-review-minimax-run.sh index 11fd21f..c1b0aeb 100755 --- a/tests/test-review-minimax-run.sh +++ b/tests/test-review-minimax-run.sh @@ -27,6 +27,16 @@ assert_contains() { fi } +assert_not_contains() { + local label=$1 needle=$2 haystack=$3 + if [[ "$haystack" != *"$needle"* ]]; then + printf " ${GREEN}PASS${NC} %s\n" "$label"; PASS=$((PASS + 1)) + else + printf " ${RED}FAIL${NC} %s\n" "$label" + printf " expected NOT to contain: %s\n" "$needle"; FAIL=$((FAIL + 1)) + fi +} + assert_eq() { local label=$1 expected=$2 actual=$3 if [[ "$expected" == "$actual" ]]; then @@ -68,7 +78,15 @@ echo "== dryrun ==" DRY_OUT="$("$SCRIPT" --worktree "$REPO" --dryrun 2>&1)" assert_eq "dryrun exits 0" 0 $? assert_contains "dryrun shows branch" "branch: feature" "$DRY_OUT" -assert_contains "dryrun shows model pin" "MiniMax-M3[1m]" "$DRY_OUT" +assert_contains "dryrun defaults to Flash" "MiniMax-M3.1-Flash-Preview" "$DRY_OUT" +assert_contains "dryrun default effort max" "--effort max" "$DRY_OUT" +M3_OUT="$("$SCRIPT" --worktree "$REPO" --m3 --dryrun 2>&1)" +assert_eq "m3 dryrun exits 0" 0 $? +assert_contains "m3 dryrun shows M3 model" "MiniMax-M3[1m]" "$M3_OUT" +assert_not_contains "m3 dryrun sends no effort" "--effort" "$M3_OUT" +EF_OUT="$("$SCRIPT" --worktree "$REPO" --effort high --dryrun 2>&1)" +assert_contains "explicit effort overrides default" "--effort high" "$EF_OUT" +"$SCRIPT" --worktree "$REPO" --effort bogus --dryrun >/dev/null 2>&1; assert_eq "bad effort exits 1" 1 $? assert_contains "dryrun shows deny list" "Bash,WebFetch,WebSearch" "$DRY_OUT" assert_contains "dryrun prompt has findings format" "NO FINDINGS" "$DRY_OUT" assert_contains "dryrun shows checklist" "shell=True" "$DRY_OUT"