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 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" diff --git a/tests/fm-brief.test.sh b/tests/fm-brief.test.sh new file mode 100755 index 00000000..1282fa0e --- /dev/null +++ b/tests/fm-brief.test.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Regression tests for bin/fm-brief.sh parse hygiene and scaffolding. +# +# fm-brief.sh builds each mode-specific Definition-of-done block inside a +# 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 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" \ + "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" \ + "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