Skip to content
Open
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
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion bin/fm-pr-merge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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[@]}"}" "$@"
4 changes: 2 additions & 2 deletions bin/fm-spawn.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
74 changes: 74 additions & 0 deletions tests/fm-brief.test.sh
Original file line number Diff line number Diff line change
@@ -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 <<EOF ... EOF) command substitution. A lone single quote inside
# such a block breaks bash's $() scanner: it hunts past the closing EOF) for
# the matching quote, so `bash -n` fails and no brief can be scaffolded at
# all. The fix removed an apostrophe from the no-mistakes DOD; these cases pin
# the parse, the no-mistakes DOD render, and the untouched scout/secondmate
# paths so the quoting class cannot silently regress again.
set -u

# shellcheck source=tests/lib.sh
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"

TMP_ROOT=$(fm_test_tmproot fm-brief)
BRIEF_HOME="$TMP_ROOT/home"
mkdir -p "$BRIEF_HOME/data"

# --- parse hygiene -----------------------------------------------------------

test_brief_parses_cleanly() {
local out rc
out=$(bash -n "$ROOT/bin/fm-brief.sh" 2>&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