From e5fd563c1a6b486433fff3e6efb2f12f233436c2 Mon Sep 17 00:00:00 2001 From: Ballestar Date: Thu, 2 Jul 2026 18:04:07 -0700 Subject: [PATCH 1/5] fix(brief): repair fm-brief.sh parse error from lone apostrophe in DOD heredoc A lone single quote inside the no-mistakes Definition-of-done block, built with DOD=$(cat <&1); rc=$? + expect_code 0 "$rc" "bash -n bin/fm-brief.sh must parse cleanly (got: $out)" + [ -z "$out" ] || fail "bash -n bin/fm-brief.sh emitted unexpected output: $out" + pass "fm-brief: bash -n parses with no syntax errors" +} + +# --- ship (no-mistakes) DOD renders the guidance line with literal backticks -- + +test_ship_brief_no_mistakes_dod() { + local brief + FM_HOME="$BRIEF_HOME" "$ROOT/bin/fm-brief.sh" brief-ship-q6 alpha >/dev/null 2>&1 \ + || fail "fm-brief.sh ship scaffold exited non-zero" + brief="$BRIEF_HOME/data/brief-ship-q6/brief.md" + assert_present "$brief" "ship brief was not scaffolded" + assert_grep "Follow the guidance from no-mistakes itself for the mechanics" "$brief" \ + "no-mistakes DOD lost its guidance line" + assert_grep '`no-mistakes axi run --help`' "$brief" \ + "no-mistakes DOD must render literal backticks around the help command" + assert_grep '`help`' "$brief" \ + "no-mistakes DOD must render literal backticks around help" + assert_no_grep "no-mistakes' own guidance" "$brief" \ + "no-mistakes DOD must not carry the apostrophe that broke parsing" + pass "fm-brief: ship (no-mistakes) DOD renders guidance with literal backticks" +} + +# --- scout and secondmate paths still scaffold -------------------------------- + +test_scout_and_secondmate_scaffold() { + local brief + FM_HOME="$BRIEF_HOME" "$ROOT/bin/fm-brief.sh" brief-scout-q6 alpha --scout >/dev/null 2>&1 \ + || fail "fm-brief.sh scout scaffold exited non-zero" + brief="$BRIEF_HOME/data/brief-scout-q6/brief.md" + assert_present "$brief" "scout brief was not scaffolded" + assert_grep "SCOUT task" "$brief" "scout brief must declare itself a scout task" + assert_grep "report.md" "$brief" "scout brief must point at the report deliverable" + + FM_SECONDMATE_CHARTER='Supervise the alpha domain.' \ + FM_HOME="$BRIEF_HOME" "$ROOT/bin/fm-brief.sh" brief-sm-q6 --secondmate alpha >/dev/null 2>&1 \ + || fail "fm-brief.sh secondmate scaffold exited non-zero" + brief="$BRIEF_HOME/data/brief-sm-q6/brief.md" + assert_present "$brief" "secondmate charter was not scaffolded" + assert_grep "persistent domain supervisor" "$brief" \ + "secondmate charter must declare its role" + pass "fm-brief: scout and secondmate code paths still scaffold well-formed briefs" +} + +test_brief_parses_cleanly +test_ship_brief_no_mistakes_dod +test_scout_and_secondmate_scaffold From dd60341d72da27ee29832b8e22cf48c3611c25e1 Mon Sep 17 00:00:00 2001 From: Ballestar Date: Thu, 2 Jul 2026 18:04:12 -0700 Subject: [PATCH 2/5] fix(bin): use set -u-safe empty-array expansion in pr-merge and spawn Expanding "${arr[@]}" on an empty array under `set -u` throws "unbound variable" on bash < 4.4 (notably macOS system bash 3.2), while it is a no-op on bash 4.4+. Two sites expanded arrays that can legitimately be empty: - bin/fm-pr-merge.sh: merge_args is empty when the caller passes an explicit merge method, so the extra-args path failed on bash 3.2. - bin/fm-spawn.sh: shared_args is empty when no --harness/--model/ --effort/--backend flags are passed, so batch dispatch failed on bash 3.2. Both block the no-mistakes test step (which runs `bash tests/*.test.sh` locally) on macOS, and are pre-existing failures on main. Use the portable "${arr[@]+"${arr[@]}"}" idiom, which expands to nothing when the array is empty or unset and to the elements otherwise, under set -u on all bash versions. Behavior on bash 4.4+ is unchanged. Found while running the suite for the fm-brief parse fix; the two affected tests (fm-pr-merge extra-args, fm-spawn-batch dispatch) now pass on bash 3.2 as well as on CI's bash 5.x. --- bin/fm-pr-merge.sh | 2 +- bin/fm-spawn.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/fm-pr-merge.sh b/bin/fm-pr-merge.sh index 8ffb32cc..5c988cab 100755 --- a/bin/fm-pr-merge.sh +++ b/bin/fm-pr-merge.sh @@ -87,4 +87,4 @@ if ! caller_has_merge_method "$@"; then merge_args=(--squash) fi -gh-axi pr merge "$PR_NUMBER" --repo "$PR_OWNER/$PR_REPO" "${merge_args[@]}" "$@" +gh-axi pr merge "$PR_NUMBER" --repo "$PR_OWNER/$PR_REPO" "${merge_args[@]+"${merge_args[@]}"}" "$@" diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index 130ad852..e212b8fc 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -180,9 +180,9 @@ if [ "${#POS[@]}" -gt 0 ] && [ "${POS[0]}" != "$idpart" ] && case "$idpart" in * rc=2 continue elif [ "$KIND" = scout ]; then - if FM_SPAWN_NO_GUARD=1 "$FM_ROOT/bin/fm-spawn.sh" "${pair%%=*}" "${pair#*=}" "${shared_args[@]}" --scout; then :; else echo "batch: FAILED to spawn ${pair%%=*} (${pair#*=})" >&2; rc=1; fi + if FM_SPAWN_NO_GUARD=1 "$FM_ROOT/bin/fm-spawn.sh" "${pair%%=*}" "${pair#*=}" "${shared_args[@]+"${shared_args[@]}"}" --scout; then :; else echo "batch: FAILED to spawn ${pair%%=*} (${pair#*=})" >&2; rc=1; fi else - if FM_SPAWN_NO_GUARD=1 "$FM_ROOT/bin/fm-spawn.sh" "${pair%%=*}" "${pair#*=}" "${shared_args[@]}"; then :; else echo "batch: FAILED to spawn ${pair%%=*} (${pair#*=})" >&2; rc=1; fi + if FM_SPAWN_NO_GUARD=1 "$FM_ROOT/bin/fm-spawn.sh" "${pair%%=*}" "${pair#*=}" "${shared_args[@]+"${shared_args[@]}"}"; then :; else echo "batch: FAILED to spawn ${pair%%=*} (${pair#*=})" >&2; rc=1; fi fi done exit "$rc" From ace739fee13826ac8bd0cef98198bbe6363716cc Mon Sep 17 00:00:00 2001 From: Ballestar Date: Thu, 2 Jul 2026 18:21:05 -0700 Subject: [PATCH 3/5] no-mistakes(lint): silence intentional-single-quote SC2016 in fm-brief test --- tests/fm-brief.test.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/fm-brief.test.sh b/tests/fm-brief.test.sh index 4d3f7244..45b3a331 100755 --- a/tests/fm-brief.test.sh +++ b/tests/fm-brief.test.sh @@ -37,8 +37,10 @@ test_ship_brief_no_mistakes_dod() { assert_present "$brief" "ship brief was not scaffolded" assert_grep "Follow the guidance from no-mistakes itself for the mechanics" "$brief" \ "no-mistakes DOD lost its guidance line" + # shellcheck disable=SC2016 # single quotes are deliberate: the backticks must stay literal assert_grep '`no-mistakes axi run --help`' "$brief" \ "no-mistakes DOD must render literal backticks around the help command" + # shellcheck disable=SC2016 # single quotes are deliberate: the backticks must stay literal assert_grep '`help`' "$brief" \ "no-mistakes DOD must render literal backticks around help" assert_no_grep "no-mistakes' own guidance" "$brief" \ From 6c0418fead5f5284c0be895946f248663789232a Mon Sep 17 00:00:00 2001 From: Ballestar Date: Thu, 2 Jul 2026 18:37:55 -0700 Subject: [PATCH 4/5] no-mistakes(review): align fm-brief test assertion with main DOD wording --- tests/fm-brief.test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fm-brief.test.sh b/tests/fm-brief.test.sh index 45b3a331..1282fa0e 100755 --- a/tests/fm-brief.test.sh +++ b/tests/fm-brief.test.sh @@ -35,7 +35,7 @@ test_ship_brief_no_mistakes_dod() { || fail "fm-brief.sh ship scaffold exited non-zero" brief="$BRIEF_HOME/data/brief-ship-q6/brief.md" assert_present "$brief" "ship brief was not scaffolded" - assert_grep "Follow the guidance from no-mistakes itself for the mechanics" "$brief" \ + assert_grep "Follow the no-mistakes guidance for the mechanics" "$brief" \ "no-mistakes DOD lost its guidance line" # shellcheck disable=SC2016 # single quotes are deliberate: the backticks must stay literal assert_grep '`no-mistakes axi run --help`' "$brief" \ From 2e77a4aa1ec5efb7c18cf9dbe8045bf553c28570 Mon Sep 17 00:00:00 2001 From: Ballestar Date: Thu, 2 Jul 2026 18:47:46 -0700 Subject: [PATCH 5/5] no-mistakes(document): docs: register fm-brief test in CONTRIBUTING registry --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d81c9263..e4909848 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,6 +78,7 @@ tests/fm-grok-harness.test.sh # grok adapter spawn hook, token guard tests/fm-fleet-sync.test.sh # project clone refresh: safe detached recovery, STUCK drift reports, benign skips, and bootstrap relay tests/fm-x-mode.test.sh # X-mode poll, inbox context round-trip, reply threading, dismiss, dry-run preview, and .env-presence activation tests tests/fm-tangle-guard.test.sh # primary-checkout tangle detection, read-only remediation suppression, and spawn/brief isolation tests +tests/fm-brief.test.sh # fm-brief.sh parse hygiene (bash -n), no-mistakes DOD guidance line with literal backticks and no parse-breaking apostrophe, and intact scout/secondmate scaffolding tests/fm-spawn-batch.test.sh # batch dispatch and FM_HOME project-path scoping tests tests/fm-spawn-dispatch-profile.test.sh # concrete dispatch profile flags: active-profile backstop, harness/model/effort meta, launch templates, batch forwarding, and secondmate exemption tests/fm-update.test.sh # fast-forward-only self-update, reread, nudge, dedup, and skip-safety tests