diff --git a/.claude/skills/design-doc-creator/SKILL.md b/.claude/skills/design-doc-creator/SKILL.md index 3e5952dba..b27028279 100644 --- a/.claude/skills/design-doc-creator/SKILL.md +++ b/.claude/skills/design-doc-creator/SKILL.md @@ -441,7 +441,7 @@ The script creates a comprehensive template. Fill in: - Overview: High-level approach - Architecture: Technical design - Implementation plan: Break into phases with tasks -- Files to modify: List new/changed files with LOC estimates +- Files to modify: List new/changed files with LOC estimates. Use one repo-relative path per top-level bullet and make it the first backticked token; continuation lines and later backticks are prose. **Examples:** - Show before/after code or workflows diff --git a/.claude/skills/design-doc-creator/resources/design_doc_structure.md b/.claude/skills/design-doc-creator/resources/design_doc_structure.md index 8c41e3fdb..350ee3c18 100644 --- a/.claude/skills/design-doc-creator/resources/design_doc_structure.md +++ b/.claude/skills/design-doc-creator/resources/design_doc_structure.md @@ -14,6 +14,7 @@ Complete reference for AILANG design documents. **Priority**: P0 (High) | P1 (Medium) | P2 (Low) **Estimated**: 3 days **Dependencies**: None | Feature X, Feature Y +**Planner-Lane**: codex-ok | opus-required ``` **Purpose**: Quick metadata for understanding scope and priority. @@ -297,6 +298,8 @@ Create a central builtin registry where all builtins are registered once with: ```markdown ### Files to Modify/Create +Use one repo-relative path per top-level bullet, with that path as the bullet's first backticked token; put explanatory prose and any later backticks after it. + **New files:** - `internal/builtins/spec.go` (~300 LOC) - BuiltinSpec struct and registry - `internal/builtins/validator.go` (~150 LOC) - Validation logic diff --git a/.claude/skills/mission-control/SKILL.md b/.claude/skills/mission-control/SKILL.md index e1443633d..694f56f55 100644 --- a/.claude/skills/mission-control/SKILL.md +++ b/.claude/skills/mission-control/SKILL.md @@ -564,7 +564,7 @@ model from the driver-exported env (defaults track the charter table): |---|---|---| | Controller (this session: triage/pick/record/retro) | `$MODEL` (session) | **Opus** (opus-first since 2026-07-16, Mark: the long orchestration session is mechanical work — it must NOT ride Fable) | | Design-doc-creator | **ROTATION** (Mark 2026-07-17; `$MISSION_DESIGNER_MODEL` is the rotation SEED, not a fixed pin) | Rotate per new-doc iteration: `claude:claude-fable-5` → `codex:gpt-5.6-sol` → (gemini after G4) → repeat. State: `~/.ailang/state/mission-designer-rotation` holds the LAST-USED value; pick the next list entry (missing file = start at claude), write back after the designer run. Every design passes the quorum regardless of author — record `(designer, quorum outcome)` in the evidence row. A probe-failed designer falls to the NEXT in rotation (not to `$MODEL`), FLAGGED | -| Sprint-planner | `$MISSION_PLANNER_MODEL` | Opus (down-tier A/B = M3; keep Opus until evidence) | +| Sprint-planner | `$MISSION_PLANNER_MODEL` | `codex:gpt-5.6-sol` configured default; effective lane = `derive-planner-lane.sh` output, used VERBATIM; fail-closed to opus | | Sprint-executor | `$MISSION_EXECUTOR_MODEL` | Opus | | Sprint-evaluator | `$MISSION_EVALUATOR_MODEL` | **Sonnet** (default changed fable→sonnet 2026-07-16 iter 38, Mark directive #399: "default … gemini (if able to git clone the codebase etc)? otherwise sonnet-5"; gemini-managed_agents VERIFIED not-viable-today — server-side sandbox sees no worktree + backend timed out; sonnet ≠ opus executor → generator≠judge, and it's Agent-tool-PINNABLE unlike fable) | @@ -586,6 +586,17 @@ session is NOT Fable, a fable pin is unenforceable — apply the generator≠jud silently inherit. `provider:model` values (e.g. `codex:gpt-5.6-sol`) instead signal cross-provider routing via `provider_executor` (fleet Phase C), not the Agent tool. +**Step 1b — derive the effective planner lane (MANDATORY; before ANY planner probe or spawn).** +Run `tools/launchd/derive-planner-lane.sh ` with the driver-exported +environment intact. Its output is exactly one line, ` `; use that line +VERBATIM. If it begins `opus `, spawn the opus Agent path directly and do **not** perform a codex +probe or spawn for the planner role; copy the reason token VERBATIM into the Gate-4 +routing-evidence row. Only `codex declared:codex-ok` enters the codex planner recipe below. If the +script is missing on disk, fail closed to opus **LOUDLY** and record the missing-script reason in +the same evidence row. This rule is mission-independent and live wherever this shared skill is +resolved: the step-0 environment pin protects missions configured for opus, and the missing-script +rule protects missions whose checkout has no derivation script (including Ailang World). + **Cross-provider spawn recipe (`provider:model`, M1b — currently `codex` only).** When a role's env value matches `^([a-z_]+):(.+)$`, DO NOT use the Agent tool. Split it (`PROVIDER=${VAL%%:*}`, `MODEL=${VAL#*:}`) and route: @@ -691,6 +702,34 @@ value matches `^([a-z_]+):(.+)$`, DO NOT use the Agent tool. Split it (`PROVIDER (d) proves the reconstruction faithful by sha256-manifesting the executor's final tree BEFORE starting and `shasum -c` after the last commit — byte-identity or the reconstruction is wrong. Two milestones that touch the SAME file are exactly why snapshots beat file-lists here. + 2a. **Planner role — parameterize this executor recipe; do not fork it.** Apply every shared + probe, bounded-background-run, directive-delivery, stdin, sandbox, output-capture, hygiene, + timeout, and fallback guard above by reference (including the executor recipe's `exit 64`, + `< /dev/null`, and `run_in_background` guards). There are exactly four planner deltas: + - **Working directory:** first assert + `git status --porcelain -- ` is empty. From local `HEAD`, create an ephemeral + detached worktree with `git worktree add --detach`, then pass its path with `-C`. The path + MUST be a SIBLING OF THIS MISSION'S REPO — DERIVE it, never hardcode it + (`"$(cd "$REPO/.." && pwd)/.planner-wt-iter"`): this skill is shared by every mission on + the rig, so an absolute path baked in for one of them is wrong for the others. Worktrees + under `/tmp` are forbidden — CWD-relative path tests then fail for the LOCATION rather than + the code, and CI never reproduces that red. + Never use `-b` or base it on `origin/dev`: a committed-but-unpushed design doc must be + visible to the planner. + - **Directive:** use the per-iteration file + `/tmp/codex_planner_directive_iter.txt`, carrying the executor recipe's identical + ≥200-byte delivery assertion and closed-stdin behavior on both probe and run by reference. + - **Sandbox directories and evidence:** keep `--add-dir "$GOCACHE" --add-dir "$GOMODCACHE"`. + **In-sandbox gate verdicts are NOT evidence**: socket-touching checks + are `UNINFORMATIVE UNDER SANDBOX`, and the controller re-verifies load-bearing premises + outside the sandbox before handing the plan to the executor. + - **Post-run controller steps:** (1) assert both artifacts exist in the worktree and are + well-formed (`jq -e . sprint_.json`; plan non-empty and names the design doc); (2) reject + placeholder vacuous-passes (`MILESTONE_ID` or `auto-parse failed`); (3) copy both artifacts + to their main-checkout paths, refusing to overwrite unexpected existing files; (4) remove + the planner worktree; (5) run + `ailang messages import-github --labels bug,feature,ailang-message` outside the sandbox; + (6) commit with `Co-Authored-By: codex `. 3. **generator≠judge guard (HARD, constraint #3):** before spawning the evaluator, assert the evaluator's PROVIDER ≠ the executor's PROVIDER. If the executor ran on codex, the evaluator MUST NOT be a codex `provider:model` — if `$MISSION_EVALUATOR_MODEL` collides, re-route the evaluator diff --git a/CHANGELOG.md b/CHANGELOG.md index c608c9e20..a5e1d8627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,18 @@ For the latest version, see [changelogs/v0.18-current.md](changelogs/v0.18-curre unencodable chunk. See [#546](https://github.com/sunholo-data/ailang/issues/546) and [arniwesth/ailang#2](https://github.com/arniwesth/ailang/pull/2). +### Mission infrastructure (no user-facing language or CLI change) + +- The mission loop's sprint-planner now defaults to the ChatGPT-subscription codex + lane instead of opus, so opus stays controller-only. The configured default is not + the effective lane: a new `tools/launchd/derive-planner-lane.sh` reads the picked + design doc and fails **closed** to opus unless the doc declares + `**Planner-Lane**: codex-ok` and every path in its Files section is inside a narrow + infrastructure allowlist. Rollback is one commented line in + `~/.config/ailang/mission-.env`. Design docs gain an optional + `**Planner-Lane**` header field. See + `design_docs/implemented/v1_0_0/m-planner-codex-lane.md`. + ## Changelog Archives The full changelog has been split into themed files for searchability and readability: diff --git a/design_docs/planned/v1_0_0/m-planner-codex-lane-sprint-plan.md b/design_docs/implemented/v1_0_0/m-planner-codex-lane-sprint-plan.md similarity index 100% rename from design_docs/planned/v1_0_0/m-planner-codex-lane-sprint-plan.md rename to design_docs/implemented/v1_0_0/m-planner-codex-lane-sprint-plan.md diff --git a/design_docs/planned/v1_0_0/m-planner-codex-lane.md b/design_docs/implemented/v1_0_0/m-planner-codex-lane.md similarity index 93% rename from design_docs/planned/v1_0_0/m-planner-codex-lane.md rename to design_docs/implemented/v1_0_0/m-planner-codex-lane.md index b92d8c789..d6ccdabe5 100644 --- a/design_docs/planned/v1_0_0/m-planner-codex-lane.md +++ b/design_docs/implemented/v1_0_0/m-planner-codex-lane.md @@ -368,23 +368,65 @@ of forking them, so a future guard fix lands once. **Modified:** - `tools/launchd/mission-control.sh` — role-generic probe loop (Bash 3.2 form) + gated default flip (~35 LOC net) - `.claude/skills/mission-control/SKILL.md` — roles-table cell + derivation step + planner sub-bullet (~55 lines) -- `~/.claude/skills/mission-control/SKILL.md` — sync copy (L15) -- `~/dev/sunholo-data/ailang-world/tools/launchd/mission-control.sh` — sync copy (L15; see D6) - `.claude/skills/design-doc-creator/SKILL.md` — one-line template addition: the `**Planner-Lane**: codex-ok | opus-required` header field (D2 field supply) **New:** - `tools/launchd/derive-planner-lane.sh` — D2 stage-2 derivation (~60 LOC, Bash 3.2-compatible, pure text — contains no codex invocation, provable by AC-D2's grep) -- `tools/launchd/testdata/planner-lane/` — eight tiny fixture docs for AC-D2 (rev 3, per +- `tools/launchd/testdata/planner-lane/` — twelve tiny fixture docs for AC-D2 (rev 3, per gpt5-6-sol's R2 fixture list): (a) unlisted language path (declares `codex-ok`, - Files-to-Modify touches `internal/parser/…` — the lying-declaration case), (b) field-missing, - (c) clean infra `codex-ok`, (f) unknown FUTURE `internal/...` path (a dir that does not exist + Files-to-Modify touches internal/parser/… — the lying-declaration case), (b) field-missing, + (c) clean infra `codex-ok`, (f) unknown FUTURE internal/... path (a dir that does not exist today), (g) mixed infra + language paths, (h) malformed Files-to-Modify section, (i) duplicate - Files-to-Modify sections + Files-to-Modify sections, (j) prose-first bullet, (k) alternate `## Files to Modify` + heading, (l) invalid Planner-Lane value, (m) `opus-required`, (n) a Files bullet carrying no + backticked token at all — the sentinel arm (j) does not reach **No Go code. No AILANG code.** +### Landing Checklist + +Completed at mission iteration 136 (2026-08-04). Each row records what was actually done. + +- **`~/.claude` skill copy — NO-OP BY CONSTRUCTION, not skipped.** `~/.claude/skills/mission-control` + is a SYMLINK to this repo's `.claude/skills/mission-control` (`readlink` + same inode, measured). + The two "copies" are ONE FILE, so the plan's `diff -q` criterion is satisfied the moment the edit + reaches the MAIN checkout. It follows that a skill edit committed only from a worktree never + reaches the running skill — this sprint landed via PR and then fast-forwarded the main checkout. +- **World driver sync — DECLINED, deliberately, message sent.** The plan recommended syncing + `tools/launchd/mission-control.sh` into `~/dev/sunholo-data/ailang-world/` and pinning World's + planner to opus in `mission-world.env`, on the stated premise that World was kill-switched and so + "verifiable with zero live risk". THAT PREMISE NO LONGER HOLDS — World is armed and ran its + iteration 45 the same night. Declining is strictly lower risk and costs World nothing: its planner + resolves to opus either way, and now every layer agrees on opus rather than relying on a pin. + Cost: the World driver now LAGS this one (they were byte-identical; `diff -q` was the drift + detector and it is now tripped by design). Surfaced to Mark as a pending ops call. +- **`.agents/skills/mission-control/SKILL.md` — OUT OF SCOPE, stale by design**, tracked by `#544` + (44,067 B vs 92,077 B). Not synced; `.agents/` deliberately NOT added to the D2 allowlist. +- **`mission-v1.env` rollback line present and COMMENTED.** Created at + `~/.config/ailang/mission-v1.env`; proven inert while commented, and uncommenting was exercised + once (`planner=ROLLBACK-PROVEN`) then reverted. +- **`git worktree list | grep planner-wt-` — empty.** The ephemeral AC3a worktree was removed + (control: the sprint worktree is still listed, so the grep sees worktrees). + +### Engagement-rate reality (§0.5, measured at landing — do NOT oversell this lane) + +Of the planned design docs, **41 carry a `Files` heading** and **40 of them derive +`opus fail-closed:planner-lane-field-missing`**. Exactly **ONE** doc declares `**Planner-Lane**` +today: this sprint's own. So the codex planner lane engages only on NEWLY AUTHORED infra docs, and +the doc's success metric ("`planner=codex…` appears in the evidence row on infra iterations") +matures over weeks, not on day one. That is the fail-closed design working as intended. + +### The revert is three-tier — D6's "one env var" is true only for M4 + +| Landed | Revert | Covered by D6's env var? | +|---|---|---| +| M1 script/fixtures/template | `rm` the script + revert 2 markdown files | n/a — zero runtime effect | +| **M2 driver probe loop + rollback plumbing** | `git checkout -- tools/launchd/mission-control.sh` — a CODE revert. An env var cannot un-break a driver that fails to parse. | **NO** | +| M3 skill | revert the skill (one file; the `~/.claude` copy follows by symlink) | **NO** (mitigated: derivation fails closed to opus if the script is gone) | +| M4 default flip | uncomment `MISSION_PLANNER_MODEL` in `~/.config/ailang/mission-.env` | **YES** — and only because M2 first built the delivery mechanism D6 already claimed existed | + --- ## Acceptance Criteria (D5 — non-vacuous by construction) diff --git a/tools/launchd/derive-planner-lane.sh b/tools/launchd/derive-planner-lane.sh new file mode 100755 index 000000000..0e4d87b4e --- /dev/null +++ b/tools/launchd/derive-planner-lane.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# Derive the mission planner lane from a design document. Pure text only. + +emit() { + printf '%s\n' "$1" + exit 0 +} + +# Step 0: an explicit non-codex planner pin always wins. +case "${MISSION_PLANNER_MODEL:-}" in + codex:*) ;; + *) emit "opus fail-closed:env-pin" ;; +esac + +# Step 1: require one readable document argument. +doc=${1:-} +if [ -z "$doc" ] || [ ! -r "$doc" ]; then + printf '%s\n' "derive-planner-lane: design document is missing or unreadable" >&2 + emit "opus fail-closed:no-doc" +fi + +# Step 2: read and validate the declaration. +planner_line=$(grep -m1 -E '^\*\*Planner-Lane\*\*:' "$doc" 2>/dev/null) +if [ -z "$planner_line" ]; then + emit "opus fail-closed:planner-lane-field-missing" +fi +planner_value=${planner_line#\*\*Planner-Lane\*\*:} +planner_value=$(printf '%s\n' "$planner_value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') +case "$planner_value" in + codex-ok|opus-required) ;; + *) emit "opus fail-closed:planner-lane-field-invalid" ;; +esac + +# Step 3: an opus declaration needs no path analysis. +if [ "$planner_value" = "opus-required" ]; then + emit "opus declared:opus-required" +fi + +# Step 4: locate exactly one Files section and extract the first backticked +# token from every top-level bullet in it. Continuations and later tokens are +# prose. The awk matcher is the portable ERE equivalent of Files\b. +section_count=$(awk ' + BEGIN { count = 0 } + { + line = tolower($0) + if (line ~ /^#{2,4}[[:space:]]+files([^[:alnum:]_]|$)/) count++ + } + END { print count } +' "$doc") +if [ "$section_count" -ne 1 ]; then + emit "opus fail-closed:no-files-section" +fi + +paths=$(awk ' + BEGIN { in_files = 0 } + { + line = tolower($0) + if (!in_files && line ~ /^#{2,4}[[:space:]]+files([^[:alnum:]_]|$)/) { + in_files = 1 + next + } + if (in_files && ($0 ~ /^#{1,4}[[:space:]]/ || $0 ~ /^---$/)) exit + if (in_files && $0 ~ /^- /) { + if (match($0, /`[^`]+`/)) print substr($0, RSTART + 1, RLENGTH - 2) + else print "__UNPARSABLE_PATH_ENTRY__" + } + } +' "$doc") + +if [ -z "$paths" ]; then + printf '%s\n' "derive-planner-lane: Files section has no path bullets" >&2 + emit "opus fail-closed:unparsable-path-entry" +fi + +old_ifs=$IFS +IFS=' +' +for path in $paths; do + if [ -z "$path" ] || [ "$path" = "__UNPARSABLE_PATH_ENTRY__" ]; then + IFS=$old_ifs + emit "opus fail-closed:unparsable-path-entry" + fi + case "$path" in + */*|*.md|*.sh|*.go|*.yml) ;; + *) IFS=$old_ifs; emit "opus fail-closed:unparsable-path-entry" ;; + esac + case "$path" in + /*|~*|*..*) IFS=$old_ifs; emit "opus fail-closed:path-not-in-codex-allowlist" ;; + esac + case "$path" in + tools/launchd/*|.claude/skills/mission-control/SKILL.md|.claude/skills/design-doc-creator/*) ;; + *) IFS=$old_ifs; emit "opus fail-closed:path-not-in-codex-allowlist" ;; + esac +done +IFS=$old_ifs + +# Step 5: every declared path is approved infrastructure. +emit "codex declared:codex-ok" diff --git a/tools/launchd/mission-control.sh b/tools/launchd/mission-control.sh index 2e553bd3e..ec326282a 100755 --- a/tools/launchd/mission-control.sh +++ b/tools/launchd/mission-control.sh @@ -50,6 +50,12 @@ MISSION_NAME="${MISSION_NAME:-v1}" MISSION_REPO="${MISSION_REPO:-sunholo-data/ailang}" MISSION_DOC="${MISSION_DOC:-design_docs/v1-mission.md}" export MISSION_NAME MISSION_REPO MISSION_DOC +# D6 rollback pin: always sourced for the RESOLVED mission name, so the documented +# `MISSION_PLANNER_MODEL=opus` rollback works for V1 (whose plist sets no MISSION_PROFILE). +# Convention: entries use ${VAR:-value} so a command-line env pin still wins (the AC fixtures +# depend on that). Double-source for World is idempotent. +[ -f "$HOME/.config/ailang/mission-${MISSION_NAME}.env" ] \ + && . "$HOME/.config/ailang/mission-${MISSION_NAME}.env" STATE_DIR="$HOME/.ailang/state" if [ "$MISSION_NAME" = "v1" ]; then # LEGACY paths — bit-for-bit compat with the live V1 loop (no migration). @@ -277,10 +283,20 @@ export MISSION_DESIGNER_MODEL="${MISSION_DESIGNER_MODEL:-claude:claude-fable-5}" # this. Enforced by the skill's Gate-3 metered ledger; quota-bucket (subscription) spend is # NOT counted — this caps dollars, not tokens. export MISSION_METERED_BUDGET_USD="${MISSION_METERED_BUDGET_USD:-5}" -export MISSION_PLANNER_MODEL="${MISSION_PLANNER_MODEL:-opus}" +# THE FLIP (m-planner-codex-lane M4, mission iteration 136): the sprint-planner default +# moves to the ChatGPT-subscription codex bucket so opus stays controller-only (Mark +# quota-offload #1). The CONFIGURED default is not the EFFECTIVE lane: the skill's Gate-3 +# step 1b runs tools/launchd/derive-planner-lane.sh on the picked design doc and fails +# CLOSED to opus unless that doc declares **Planner-Lane**: codex-ok AND every path it +# declares is inside the D2 infra allowlist. Rollback = uncomment MISSION_PLANNER_MODEL +# in ~/.config/ailang/mission-.env (delivery mechanism added by M2 above). +export MISSION_PLANNER_MODEL="${MISSION_PLANNER_MODEL:-codex:gpt-5.6-sol}" export MISSION_EXECUTOR_MODEL="${MISSION_EXECUTOR_MODEL:-codex:gpt-5.6-sol}" -# Codex-lane pre-flight (2026-07-27): subscription quota is invisible until it errors, so probe -# once per fire. Any probe failure → fall back to opus THIS fire only (logged, never wedged). +# Codex-lane pre-flight, ROLE-GENERIC (m-planner-codex-lane): probe once per DISTINCT +# codex model, fall back per-role on ANY non-zero rc (#486: probe MUST carry --model; +# an unusable pin is exactly as fatal as spent quota). Export AFTER fallback so the +# EXPORTED env — what the routing-evidence row reports — stays honest. +# BASH 3.2 (L19): ':'-delimited string sets, NOT associative arrays; no ${var,,}. # # The probe MUST carry --model (#486, 2026-07-27): without it codex exercises its DEFAULT model, # so a pinned-but-unreachable model false-greens the lane. Live evidence that day: codex-cli @@ -292,20 +308,33 @@ export MISSION_EXECUTOR_MODEL="${MISSION_EXECUTOR_MODEL:-codex:gpt-5.6-sol}" # fatal to the lane as a spent quota, and the old quota-only gate is what let #486 through. The # skill's Gate-3 recipe re-probes and would fall back anyway; doing it here keeps the EXPORTED # env honest, which is what the routing-evidence row reports. -case "$MISSION_EXECUTOR_MODEL" in codex:*) - cx_model="${MISSION_EXECUTOR_MODEL#codex:}" - _mc_bounded "$PROBE_TIMEOUT" codex exec --skip-git-repo-check --model "$cx_model" 'reply with exactly: ok' - cx_rc=$?; cx_out="$MC_BOUNDED_OUT" - if [ $cx_rc -ne 0 ]; then - if [ $cx_rc -eq 124 ]; then cx_why="probe timed out after ${PROBE_TIMEOUT}s" - elif printf '%s' "$cx_out" | grep -qiE "$QUOTA_SIG"; then cx_why="quota-limited" - else cx_why="probe failed (rc=$cx_rc)"; fi - log "codex executor lane $cx_why for model '$cx_model' -> falling back to opus for this fire" - log "codex probe output: $(printf '%s' "$cx_out" | tail -3 | tr '\n' ' ')" - MISSION_EXECUTOR_MODEL="opus"; export MISSION_EXECUTOR_MODEL - fi - ;; -esac +_cx_probed=":" # models probed this fire (dedupe: planner+executor share the default model) +_cx_failed=":" # models whose probe failed +for role in PLANNER EXECUTOR; do + var="MISSION_${role}_MODEL"; val="${!var}" + case "$val" in codex:*) + cx_model="${val#codex:}" + case "$_cx_probed" in *":${cx_model}:"*) : ;; *) # not yet probed + _cx_probed="${_cx_probed}${cx_model}:" + _mc_bounded "$PROBE_TIMEOUT" codex exec --skip-git-repo-check --model "$cx_model" 'reply with exactly: ok' + cx_rc=$?; cx_out="$MC_BOUNDED_OUT" + if [ "$cx_rc" -ne 0 ]; then + _cx_failed="${_cx_failed}${cx_model}:" + # why-classification happens ONCE, at probe time (timeout / quota-sig / other) + if [ "$cx_rc" -eq 124 ]; then cx_why="probe timed out after ${PROBE_TIMEOUT}s" + elif printf '%s' "$cx_out" | grep -qiE "$QUOTA_SIG"; then cx_why="quota-limited" + else cx_why="probe failed (rc=$cx_rc)"; fi + log "codex model '$cx_model' unusable: $cx_why" + log "codex probe output: $(printf '%s' "$cx_out" | tail -3 | tr '\n' ' ')" + fi + ;; esac + case "$_cx_failed" in *":${cx_model}:"*) + role_lc=$(printf '%s' "$role" | tr 'A-Z' 'a-z') # ${role,,} is bash-4.0-only (L21) + log "codex ${role_lc} lane -> falling back to opus for this fire (model '$cx_model')" + printf -v "$var" 'opus'; export "$var" + ;; esac + ;; esac +done # evaluator default = sonnet (2026-07-16, Mark directive on #399: "default can be gemini (if able # to git clone the codebase etc)? otherwise sonnet-5"). gemini managed_agents is NOT viable as the # evaluator today — VERIFIED iteration 38: (1) architecturally the request body carries only diff --git a/tools/launchd/testdata/planner-lane/a-unlisted-language-path.md b/tools/launchd/testdata/planner-lane/a-unlisted-language-path.md new file mode 100644 index 000000000..ce1dc7df1 --- /dev/null +++ b/tools/launchd/testdata/planner-lane/a-unlisted-language-path.md @@ -0,0 +1,9 @@ +# Unlisted Language Path + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `internal/parser/parser.go` — language implementation + +--- diff --git a/tools/launchd/testdata/planner-lane/b-field-missing.md b/tools/launchd/testdata/planner-lane/b-field-missing.md new file mode 100644 index 000000000..f4c8bca74 --- /dev/null +++ b/tools/launchd/testdata/planner-lane/b-field-missing.md @@ -0,0 +1,9 @@ +# Missing Field + +**Status**: Planned + +### Files to Modify/Create + +- `tools/launchd/example.sh` — infrastructure + +--- diff --git a/tools/launchd/testdata/planner-lane/c-clean-infra.md b/tools/launchd/testdata/planner-lane/c-clean-infra.md new file mode 100644 index 000000000..1fe04670e --- /dev/null +++ b/tools/launchd/testdata/planner-lane/c-clean-infra.md @@ -0,0 +1,10 @@ +# Clean Infrastructure + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `tools/launchd/example.sh` — infrastructure +- `.claude/skills/design-doc-creator/SKILL.md` — documentation + +--- diff --git a/tools/launchd/testdata/planner-lane/f-future-internal-path.md b/tools/launchd/testdata/planner-lane/f-future-internal-path.md new file mode 100644 index 000000000..72465ebcc --- /dev/null +++ b/tools/launchd/testdata/planner-lane/f-future-internal-path.md @@ -0,0 +1,9 @@ +# Future Internal Path + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `internal/future/component.go` — not present today + +--- diff --git a/tools/launchd/testdata/planner-lane/g-mixed-infra-language.md b/tools/launchd/testdata/planner-lane/g-mixed-infra-language.md new file mode 100644 index 000000000..259252fcd --- /dev/null +++ b/tools/launchd/testdata/planner-lane/g-mixed-infra-language.md @@ -0,0 +1,10 @@ +# Mixed Paths + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `tools/launchd/example.sh` — infrastructure +- `internal/parser/parser.go` — language implementation + +--- diff --git a/tools/launchd/testdata/planner-lane/h-malformed-files-section.md b/tools/launchd/testdata/planner-lane/h-malformed-files-section.md new file mode 100644 index 000000000..4fbf041b8 --- /dev/null +++ b/tools/launchd/testdata/planner-lane/h-malformed-files-section.md @@ -0,0 +1,9 @@ +# Malformed Files Section + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `/tmp/generated.sh` — absolute paths are forbidden + +--- diff --git a/tools/launchd/testdata/planner-lane/i-duplicate-files-sections.md b/tools/launchd/testdata/planner-lane/i-duplicate-files-sections.md new file mode 100644 index 000000000..ea5f6ff04 --- /dev/null +++ b/tools/launchd/testdata/planner-lane/i-duplicate-files-sections.md @@ -0,0 +1,13 @@ +# Duplicate Files Sections + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `tools/launchd/one.sh` — first section + +### Files to Create + +- `tools/launchd/two.sh` — second section + +--- diff --git a/tools/launchd/testdata/planner-lane/j-prose-first-bullet.md b/tools/launchd/testdata/planner-lane/j-prose-first-bullet.md new file mode 100644 index 000000000..927ed77cd --- /dev/null +++ b/tools/launchd/testdata/planner-lane/j-prose-first-bullet.md @@ -0,0 +1,9 @@ +# Prose First Bullet + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- `codex-ok` is prose; `tools/launchd/example.sh` follows later + +--- diff --git a/tools/launchd/testdata/planner-lane/k-alternate-heading.md b/tools/launchd/testdata/planner-lane/k-alternate-heading.md new file mode 100644 index 000000000..f76fd586e --- /dev/null +++ b/tools/launchd/testdata/planner-lane/k-alternate-heading.md @@ -0,0 +1,9 @@ +# Alternate Heading + +**Planner-Lane**: codex-ok + +## Files to Modify + +- `tools/launchd/example.sh` — infrastructure + +--- diff --git a/tools/launchd/testdata/planner-lane/l-field-invalid.md b/tools/launchd/testdata/planner-lane/l-field-invalid.md new file mode 100644 index 000000000..714a1fefc --- /dev/null +++ b/tools/launchd/testdata/planner-lane/l-field-invalid.md @@ -0,0 +1,9 @@ +# Invalid Field + +**Planner-Lane**: maybe + +### Files to Modify/Create + +- `tools/launchd/example.sh` — infrastructure + +--- diff --git a/tools/launchd/testdata/planner-lane/m-opus-required.md b/tools/launchd/testdata/planner-lane/m-opus-required.md new file mode 100644 index 000000000..bf4d222bc --- /dev/null +++ b/tools/launchd/testdata/planner-lane/m-opus-required.md @@ -0,0 +1,9 @@ +# Opus Required + +**Planner-Lane**: opus-required + +### Files to Modify/Create + +- `internal/parser/parser.go` — declaration short-circuits path checks + +--- diff --git a/tools/launchd/testdata/planner-lane/n-no-backtick-bullet.md b/tools/launchd/testdata/planner-lane/n-no-backtick-bullet.md new file mode 100644 index 000000000..0d81da1c0 --- /dev/null +++ b/tools/launchd/testdata/planner-lane/n-no-backtick-bullet.md @@ -0,0 +1,12 @@ +# Fixture (n) — a Files bullet carrying NO backticked token at all + +Covers the `__UNPARSABLE_PATH_ENTRY__` sentinel arm of `derive-planner-lane.sh`, which fixture +(j) does NOT reach: (j)'s first backticked token exists but is not path-shaped, so (j) is caught +one arm later by the path-shape check. Without this fixture, neutering the sentinel is a +mutation that survives the whole matrix. + +**Planner-Lane**: codex-ok + +### Files to Modify/Create + +- tools/launchd/derive-planner-lane.sh — path written as bare prose, no backticks