From c11f892d09865121e1bfb3943f2df521e3547edd Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Tue, 8 Sep 2026 20:44:02 +0800 Subject: [PATCH 1/6] fix: tighten generated status protocol --- bin/fm-brief.sh | 24 ++++++++------ bin/fm-dod-lib.sh | 4 +-- tests/fm-brief.test.sh | 73 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/bin/fm-brief.sh b/bin/fm-brief.sh index 0956e5d6968..1e90dcb2886 100755 --- a/bin/fm-brief.sh +++ b/bin/fm-brief.sh @@ -215,6 +215,11 @@ shell_quote() { STATUS_FILE=$(shell_quote "$STATE/$ID.status") INBOX_DIR=$(shell_quote "$STATE/$ID.inbox") +IFS= read -r -d '' STATUS_WAKE_REMINDER <<'EOF' || true +Each status-file append wakes the supervisor and costs a full supervision turn. +Append only when this protocol requires it; never use status as a progress log. +EOF +STATUS_WAKE_REMINDER=${STATUS_WAKE_REMINDER%$'\n'} # The receive-and-ack half of the steering-inbox contract, included in every # scaffold kind. The record format, doorbell line, and re-ring ladder are @@ -226,6 +231,7 @@ IFS= read -r -d '' INBOX_SECTION <> $STATUS_FILE\` States: working, needs-decision, blocked, $PAUSED_VERB, done, failed. $SILENT_OPERATIONAL_INPUT_RULE +$STATUS_WAKE_REMINDER Use \`$PAUSED_VERB: {why}\` (distinct from \`blocked:\`) only when your domain is deliberately idling on a known external wait you expect to clear on its own; use \`blocked:\` when you are stuck and need firstmate to act. Use this only for material phase changes, a captain decision, a real blocker, a failure, work ready for review, or work you landed. Work you landed includes a merge you performed yourself under standing merge authority and one the captain merged on the forge: under that authority nothing is ever \"ready for review\", so a landed merge that goes unreported reaches the captain as silence. @@ -454,9 +461,9 @@ The report is the only thing that survives, so anything worth keeping must be in \`echo "{state}: {one short line}" >> $STATUS_FILE\` States: working, needs-decision, blocked, $PAUSED_VERB, done, failed. $SILENT_OPERATIONAL_INPUT_RULE - Each append wakes firstmate, so report sparingly: only phase changes a supervisor - would act on and the needs-decision/blocked/paused/done/failed states. No step-by-step - FYI progress lines; firstmate reads your pane for that. + $STATUS_WAKE_REMINDER + Append \`working:\` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened. + Never append \`working:\` for a sub-step, a verification pass, or the start of re-review. Whenever you mention a PR anywhere - a status line, your terminal, a summary - write its full https:// URL exactly as the forge printed it, never a bare number such as "PR 108"; firstmate copies that URL from your line rather than assembling one. @@ -480,7 +487,7 @@ Write your findings to \`$DATA/$ID/report.md\`. The report must stand alone: what you did, what you found, the evidence (commands run, output, file:line references), and what you recommend. If your deliverable is a visual artifact the captain will review and iterate on, you may host the Lavish review loop yourself (poll, revise, re-serve, staying alive) instead of handing it back to firstmate. Before reporting done, read and follow \`$FM_ROOT/.agents/skills/captain-hold-lifecycle/SKILL.md\` and pass its shared completion gate for the report and any visual review. -When the report is complete, append \`done: {one-line conclusion}\` to the status file and stop. +Only after the report exists and is complete, append \`done: {one-line conclusion}\` to the status file and stop. If your findings reveal work that should ship (e.g. you reproduced a bug and the fix is clear), say so in the report; firstmate may promote this task in place, and you would then receive mode-specific ship instructions as a follow-up message. EOF echo "scaffolded: $BRIEF (scout; replace {TASK} and {FIRSTMATE_SPEC})" @@ -538,14 +545,13 @@ $RULE1 \`echo "{state}: {one short line}" >> $STATUS_FILE\` States: working, needs-decision, blocked, $PAUSED_VERB, done, failed. $SILENT_OPERATIONAL_INPUT_RULE - Each append wakes firstmate, so report sparingly: only phase changes a supervisor - would act on (setup done, bug reproduced, fix implemented, validation passed) and the - needs-decision/blocked/paused/done/failed states. No step-by-step FYI progress lines; - firstmate reads your pane for that. + $STATUS_WAKE_REMINDER + Append \`working:\` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened. + Never append \`working:\` for a sub-step, a verification pass, or the start of re-review. Whenever you mention a PR anywhere - a status line, your terminal, a summary - write its full https:// URL exactly as the forge printed it, never a bare number such as "PR 108"; firstmate copies that URL from your line rather than assembling one. - A mid-task \`working:\` line (including setup complete) is nonterminal: do not end the + A mid-task \`working:\` line is nonterminal: do not end the turn after it; continue the same stage until a defined \`done:\` gate under Definition of done. Use \`$PAUSED_VERB: {why}\` - distinct from \`blocked:\` - ONLY when you are deliberately idling on a known external wait you expect to clear on its own (an upstream release, a rate-limit reset, diff --git a/bin/fm-dod-lib.sh b/bin/fm-dod-lib.sh index c5be1b1455c..61a14643e6f 100755 --- a/bin/fm-dod-lib.sh +++ b/bin/fm-dod-lib.sh @@ -203,8 +203,8 @@ EOF # Definition of done Delivery contract: mode=no-mistakes The task is complete only when committed on your branch. -When you believe it is complete, append \`done: {summary}\` to the status file and stop. -Firstmate will then instruct you to run /no-mistakes to validate and ship a PR. +After committing the implementation, append \`working: implementation committed and validation started\` only if that phase change has not already been reported, then run /no-mistakes to validate and ship a PR. +A local commit, local validation pass, or pipeline start is never a \`done:\` event. You drive no-mistakes by responding to its gates, not by implementing fixes. Follow the guidance no-mistakes itself provides for the mechanics: it loads when you invoke /no-mistakes, and \`no-mistakes axi run --help\` plus the \`help\` lines in each \`axi\` response are authoritative and version-matched to the installed binary. diff --git a/tests/fm-brief.test.sh b/tests/fm-brief.test.sh index b1495a1831a..5238ebe39aa 100755 --- a/tests/fm-brief.test.sh +++ b/tests/fm-brief.test.sh @@ -223,13 +223,79 @@ test_ship_modes_generate_clean_briefs() { assert_grep "## Captain's intent" "$brief" "$id: brief missing Captain's intent subsection" assert_grep "## Firstmate spec" "$brief" "$id: brief missing Firstmate spec subsection" assert_grep 'never a bare number such as "PR 108"' "$brief" "$id: brief missing the full-PR-URL rule" - assert_grep "mid-task \`working:\` line (including setup complete) is nonterminal" "$brief" \ - "$id: brief missing nonterminal working:/setup-complete gate protection" + assert_grep "mid-task \`working:\` line is nonterminal" "$brief" \ + "$id: brief missing nonterminal working: gate protection" + assert_no_grep "working:\` line (including setup complete)" "$brief" \ + "$id: brief still permits a setup-complete progress event" assert_no_grep "EOF" "$brief" "$id: brief leaked a heredoc EOF marker (unterminated heredoc)" done pass "fm-brief.sh: no-mistakes/direct-PR/local-only briefs generate cleanly" } +test_status_protocol_is_phase_only() { + local home kind id brief content reminder reminder_count + home="$TMP_ROOT/status-protocol-home" + mkdir -p "$home/data" + reminder='Each status-file append wakes the supervisor and costs a full supervision turn. +Append only when this protocol requires it; never use status as a progress log.' + + for kind in ship scout secondmate; do + id="status-protocol-$kind" + case "$kind" in + ship) + FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" firstmate --mode no-mistakes >/dev/null 2>&1 + ;; + scout) + FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" firstmate --scout >/dev/null 2>&1 + ;; + secondmate) + FM_HOME="$home" FM_SECONDMATE_CHARTER='Handle routed domain work.' \ + "$ROOT/bin/fm-brief.sh" "$id" --secondmate --no-projects >/dev/null 2>&1 + ;; + esac + brief="$home/data/$id/brief.md" + content=$(cat "$brief") + reminder_count=$(count_literal "$content" "$reminder") + [ "$reminder_count" = 1 ] \ + || fail "$kind scaffold must carry the two-line wake-cost reminder exactly once, found $reminder_count" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Never append a `resolved:` echo of a firstmate steer' "$brief" \ + "$kind scaffold permits a resolved echo of a firstmate steer" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'moving its message into `handled/` is the acknowledgement' "$brief" \ + "$kind scaffold lost inbox-move acknowledgement ownership" + done + + brief="$home/data/status-protocol-ship/brief.md" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' "$brief" \ + "ship scaffold lost the closed set of reportable working phases" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Never append `working:` for a sub-step, a verification pass, or the start of re-review.' "$brief" \ + "ship scaffold permits chatty working events" + assert_no_grep 'done: {summary}' "$brief" \ + "no-mistakes scaffold still permits a pre-pipeline done event" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'A local commit, local validation pass, or pipeline start is never a `done:` event.' "$brief" \ + "no-mistakes scaffold does not forbid local done events" + assert_grep 'done: PR {url} checks green' "$brief" \ + "no-mistakes scaffold lost its only terminal done form" + + id="status-protocol-direct" + FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" firstmate --mode direct-PR >/dev/null 2>&1 + brief="$home/data/$id/brief.md" + assert_grep 'done: PR {url}' "$brief" \ + "direct-PR scaffold lost its terminal done form" + assert_no_grep 'done: PR {url} checks green' "$brief" \ + "direct-PR scaffold inherited the no-mistakes terminal form" + + brief="$home/data/status-protocol-scout/brief.md" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Only after the report exists and is complete, append `done: {one-line conclusion}`' "$brief" \ + "scout scaffold permits done before its report exists" + pass "fm-brief.sh: generated status protocol wakes only for actionable phases and terminal outcomes" +} + # A blocked headless capture must route a Codex worker to the captain's signed-in # Chrome instead of letting the worker discard or downgrade the reference. test_ship_and_scout_briefs_render_browser_fallback_rule() { @@ -333,7 +399,7 @@ test_ship_mode_is_explicit_not_registry() { brief="$home/data/brief-explicit-a5/brief.md" grep -qx "Delivery contract: mode=no-mistakes" "$brief" \ || fail "registered direct-PR posture overrode the explicit --mode" - assert_grep "Firstmate will then instruct you to run /no-mistakes" "$brief" \ + assert_grep "then run /no-mistakes to validate and ship a PR" "$brief" \ "explicit no-mistakes brief did not render the pipeline definition of done" # An unregistered project is not a blocker either, because nothing is looked up. @@ -1024,6 +1090,7 @@ test_script_parses test_no_heredoc_in_command_substitution test_help_includes_entire_header test_ship_modes_generate_clean_briefs +test_status_protocol_is_phase_only test_ship_and_scout_briefs_render_browser_fallback_rule test_upstream_sync_template_renders_direct_pr_delivery test_ship_mode_is_required_and_closed_set From 4fc53e07fe685b2273e6e9332c8b0bd2d01984a4 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Tue, 8 Sep 2026 20:57:21 +0800 Subject: [PATCH 2/6] no-mistakes(review): Captain: scout working phases narrowed to research/report only --- bin/fm-brief.sh | 2 +- tests/fm-brief.test.sh | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/fm-brief.sh b/bin/fm-brief.sh index 1e90dcb2886..799c7edb145 100755 --- a/bin/fm-brief.sh +++ b/bin/fm-brief.sh @@ -462,7 +462,7 @@ The report is the only thing that survives, so anything worth keeping must be in States: working, needs-decision, blocked, $PAUSED_VERB, done, failed. $SILENT_OPERATIONAL_INPUT_RULE $STATUS_WAKE_REMINDER - Append \`working:\` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened. + Append \`working:\` only for a genuine phase change the supervisor would act on: starting the investigation, entering a distinct research phase, or beginning report writing. Never append \`working:\` for a sub-step, a verification pass, or the start of re-review. Whenever you mention a PR anywhere - a status line, your terminal, a summary - write its full https:// URL exactly as the forge printed it, never a bare number such as "PR 108"; firstmate diff --git a/tests/fm-brief.test.sh b/tests/fm-brief.test.sh index 5238ebe39aa..810672710db 100755 --- a/tests/fm-brief.test.sh +++ b/tests/fm-brief.test.sh @@ -291,6 +291,11 @@ Append only when this protocol requires it; never use status as a progress log.' brief="$home/data/status-protocol-scout/brief.md" # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: starting the investigation, entering a distinct research phase, or beginning report writing.' "$brief" \ + "scout scaffold lost its research/report phase-only working rule" + assert_no_grep 'implementation committed and validation started, or PR opened' "$brief" \ + "scout scaffold still names ship-only working phases" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. assert_grep 'Only after the report exists and is complete, append `done: {one-line conclusion}`' "$brief" \ "scout scaffold permits done before its report exists" pass "fm-brief.sh: generated status protocol wakes only for actionable phases and terminal outcomes" From 3fc18029ca142bab20b828e85501eaa7a84d5f36 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Tue, 8 Sep 2026 21:11:06 +0800 Subject: [PATCH 3/6] no-mistakes(review): Captain: mode-specific status handoffs fixed; delivery-contract tests pass --- bin/fm-brief.sh | 26 +++++++++++++++++--------- bin/fm-promote.sh | 10 +++++++++- bin/fm-status-lib.sh | 31 +++++++++++++++++++++++++++++++ tests/fm-brief.test.sh | 10 ++++++++++ tests/fm-task-delivery.test.sh | 16 +++++++++++++++- 5 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 bin/fm-status-lib.sh diff --git a/bin/fm-brief.sh b/bin/fm-brief.sh index 799c7edb145..e49f4dd0d1c 100755 --- a/bin/fm-brief.sh +++ b/bin/fm-brief.sh @@ -92,8 +92,12 @@ esac . "$SCRIPT_DIR/fm-classify-lib.sh" # shellcheck source=bin/fm-dod-lib.sh . "$SCRIPT_DIR/fm-dod-lib.sh" +# shellcheck source=bin/fm-status-lib.sh +. "$SCRIPT_DIR/fm-status-lib.sh" PAUSED_VERB=${FM_CLASSIFY_PAUSED_VERB:-$FM_CLASSIFY_PAUSED_VERB_DEFAULT} SILENT_OPERATIONAL_INPUT_RULE=$FM_OPERATIONAL_SILENT_REPLY_RULE +STATUS_WORKING_RULE= +STATUS_NO_PROGRESS_RULE= resolve_directory_input() { local name=$1 path=$2 resolved @@ -186,6 +190,14 @@ elif [ "$MERGE_AUTHORITY_SET" -eq 1 ]; then echo "error: --merge-authority applies only to ship briefs; secondmate charters resolve each project from data/projects.md" >&2 exit 1 fi +if [ "$KIND" = scout ]; then + STATUS_WORKING_RULE=$(fm_status_working_rule scout) +elif [ "$KIND" = ship ]; then + STATUS_WORKING_RULE=$(fm_status_working_rule "$MODE") +fi +if [ "$KIND" != secondmate ]; then + STATUS_NO_PROGRESS_RULE=$(fm_status_no_progress_rule) +fi ID=${POS[0]} if [ "$KIND" = secondmate ] && [ "$HERDR_LAB" -eq 1 ]; then @@ -215,11 +227,7 @@ shell_quote() { STATUS_FILE=$(shell_quote "$STATE/$ID.status") INBOX_DIR=$(shell_quote "$STATE/$ID.inbox") -IFS= read -r -d '' STATUS_WAKE_REMINDER <<'EOF' || true -Each status-file append wakes the supervisor and costs a full supervision turn. -Append only when this protocol requires it; never use status as a progress log. -EOF -STATUS_WAKE_REMINDER=${STATUS_WAKE_REMINDER%$'\n'} +STATUS_WAKE_REMINDER=$(fm_status_wake_reminder) # The receive-and-ack half of the steering-inbox contract, included in every # scaffold kind. The record format, doorbell line, and re-ring ladder are @@ -462,8 +470,8 @@ The report is the only thing that survives, so anything worth keeping must be in States: working, needs-decision, blocked, $PAUSED_VERB, done, failed. $SILENT_OPERATIONAL_INPUT_RULE $STATUS_WAKE_REMINDER - Append \`working:\` only for a genuine phase change the supervisor would act on: starting the investigation, entering a distinct research phase, or beginning report writing. - Never append \`working:\` for a sub-step, a verification pass, or the start of re-review. + $STATUS_WORKING_RULE + $STATUS_NO_PROGRESS_RULE Whenever you mention a PR anywhere - a status line, your terminal, a summary - write its full https:// URL exactly as the forge printed it, never a bare number such as "PR 108"; firstmate copies that URL from your line rather than assembling one. @@ -546,8 +554,8 @@ $RULE1 States: working, needs-decision, blocked, $PAUSED_VERB, done, failed. $SILENT_OPERATIONAL_INPUT_RULE $STATUS_WAKE_REMINDER - Append \`working:\` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened. - Never append \`working:\` for a sub-step, a verification pass, or the start of re-review. + $STATUS_WORKING_RULE + $STATUS_NO_PROGRESS_RULE Whenever you mention a PR anywhere - a status line, your terminal, a summary - write its full https:// URL exactly as the forge printed it, never a bare number such as "PR 108"; firstmate copies that URL from your line rather than assembling one. diff --git a/bin/fm-promote.sh b/bin/fm-promote.sh index 7ddf3951a5d..9170fc30da3 100755 --- a/bin/fm-promote.sh +++ b/bin/fm-promote.sh @@ -34,6 +34,8 @@ DATA="${FM_DATA_OVERRIDE:-$FM_HOME/data}" # shellcheck source=bin/fm-dod-lib.sh . "$SCRIPT_DIR/fm-dod-lib.sh" +# shellcheck source=bin/fm-status-lib.sh +. "$SCRIPT_DIR/fm-status-lib.sh" # shellcheck source=bin/fm-pr-lib.sh . "$SCRIPT_DIR/fm-pr-lib.sh" # shellcheck source=bin/fm-wake-lib.sh @@ -194,6 +196,9 @@ PROMOTION_ASK_USER_BLOCK= if [ "$MODE" = no-mistakes ]; then PROMOTION_ASK_USER_BLOCK=$(fm_ask_user_escalation_block "$DATA" "$ID") fi +PROMOTION_STATUS_WAKE_REMINDER=$(fm_status_wake_reminder) +PROMOTION_STATUS_WORKING_RULE=$(fm_status_working_rule "$MODE") +PROMOTION_STATUS_NO_PROGRESS_RULE=$(fm_status_no_progress_rule) mkdir -p "$DATA/$ID" [ ! -d "$INSTRUCTIONS" ] || { echo "error: ship instructions path is a directory: $INSTRUCTIONS" >&2; exit 1; } TMP="$DATA/$ID/.ship-instructions.md.${BASHPID:-$$}" @@ -213,7 +218,10 @@ EOF 3. Return to a clean default-branch base, then create your branch: \`git checkout -b fm/$ID\`. 4. Carry over only the intended fix changes. Leave scratch commits, debug edits, and experiment files behind. 5. If you reproduced a bug, turn that reproduction into a regression test. -6. These ship instructions supersede the scout delivery rules and report-based Definition of done. Everything else in your original instructions carries over unchanged: the status protocol; the instruction inbox and its acknowledgement; the escalation rules, including ask-user; and every safety rule. +6. These ship instructions supersede the scout delivery rules and report-based Definition of done. The shared status protocol rules carry over unchanged, but the mode-specific working-phase rule below supersedes the scout wording. The instruction inbox and its acknowledgement; the escalation rules, including ask-user; and every safety rule carry over unchanged. + $PROMOTION_STATUS_WAKE_REMINDER + $PROMOTION_STATUS_WORKING_RULE + $PROMOTION_STATUS_NO_PROGRESS_RULE $PROMOTION_ASK_USER_BLOCK 7. Treat the scout-time Firstmate spec and any unmarked legacy \`# Task\` text as investigation context, not captain intent or ship-time instructions. EOF diff --git a/bin/fm-status-lib.sh b/bin/fm-status-lib.sh new file mode 100644 index 00000000000..83019d7ecec --- /dev/null +++ b/bin/fm-status-lib.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Shared generated worker status-protocol wording. + +fm_status_wake_reminder() { + printf '%s\n' \ + 'Each status-file append wakes the supervisor and costs a full supervision turn.' \ + 'Append only when this protocol requires it; never use status as a progress log.' +} + +fm_status_working_rule() { # + local kind=$1 + case "$kind" in + scout) + printf '%s\n' 'Append `working:` only for a genuine phase change the supervisor would act on: starting the investigation, entering a distinct research phase, or beginning report writing.' + ;; + no-mistakes|direct-PR) + printf '%s\n' 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' + ;; + local-only) + printf '%s\n' 'Append `working:` only for a genuine phase change the supervisor would act on: work started or implementation committed and validation started.' + ;; + *) + echo "error: fm_status_working_rule: unknown kind '$kind'" >&2 + return 1 + ;; + esac +} + +fm_status_no_progress_rule() { + printf '%s\n' 'Never append `working:` for a sub-step, a verification pass, or the start of re-review.' +} diff --git a/tests/fm-brief.test.sh b/tests/fm-brief.test.sh index 810672710db..92747a4a6a7 100755 --- a/tests/fm-brief.test.sh +++ b/tests/fm-brief.test.sh @@ -284,11 +284,21 @@ Append only when this protocol requires it; never use status as a progress log.' id="status-protocol-direct" FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" firstmate --mode direct-PR >/dev/null 2>&1 brief="$home/data/$id/brief.md" + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' "$brief" \ + "direct-PR scaffold lost its mode-specific working rule" assert_grep 'done: PR {url}' "$brief" \ "direct-PR scaffold lost its terminal done form" assert_no_grep 'done: PR {url} checks green' "$brief" \ "direct-PR scaffold inherited the no-mistakes terminal form" + id="status-protocol-local-only" + FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" firstmate --mode local-only >/dev/null 2>&1 + brief="$home/data/$id/brief.md" + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started or implementation committed and validation started.' "$brief" \ + "local-only scaffold lost its mode-specific working rule" + assert_no_grep 'implementation committed and validation started, or PR opened' "$brief" \ + "local-only scaffold still permits a PR working phase" + brief="$home/data/status-protocol-scout/brief.md" # shellcheck disable=SC2016 # Backticks are literal generated brief prose. assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: starting the investigation, entering a distinct research phase, or beginning report writing.' "$brief" \ diff --git a/tests/fm-task-delivery.test.sh b/tests/fm-task-delivery.test.sh index f7060a9e3ce..9f02492de4a 100755 --- a/tests/fm-task-delivery.test.sh +++ b/tests/fm-task-delivery.test.sh @@ -429,6 +429,20 @@ STUB "$mode: promoted worker did not receive the Captain's intent subsection" assert_grep "## Firstmate spec" "$payload" \ "$mode: promoted worker did not receive the Firstmate spec subsection" + assert_grep "The shared status protocol rules carry over unchanged, but the mode-specific working-phase rule below supersedes the scout wording." "$payload" \ + "$mode: promoted worker did not receive the status protocol handoff" + assert_grep "Each status-file append wakes the supervisor and costs a full supervision turn." "$payload" \ + "$mode: promoted worker did not receive the status wake-cost reminder" + case "$mode" in + no-mistakes|direct-PR) + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' "$payload" \ + "$mode: promoted worker lost its allowed working phases" ;; + local-only) + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started or implementation committed and validation started.' "$payload" \ + "local-only: promoted worker lost its PR-free working phases" + assert_no_grep 'implementation committed and validation started, or PR opened' "$payload" \ + "local-only: promoted worker still permits a PR working phase" ;; + esac # Compare the public outputs of both real generation paths. The promoted # payload ends at its Definition of done, as does an ordinary generated @@ -459,7 +473,7 @@ STUB payload="$TMP_ROOT/promote-dod/payload-promote-dod-direct-pr" assert_grep "supersede the scout delivery rules and report-based Definition of done" "$payload" \ "promoted worker retained the scout delivery contract" - assert_grep "status protocol; the instruction inbox and its acknowledgement; the escalation rules, including ask-user; and every safety rule" "$payload" \ + assert_grep "The instruction inbox and its acknowledgement; the escalation rules, including ask-user; and every safety rule carry over unchanged" "$payload" \ "promoted worker lost the scout protocols and safety rules that still apply" # The faster paths keep their own contracts rather than inheriting the pipeline's. From 07816571e512ebb6c73c4c9ca050ae43d4f94949 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Tue, 8 Sep 2026 21:25:57 +0800 Subject: [PATCH 4/6] no-mistakes(review): Captain: promotion payload fix verified by delivery-contract tests --- bin/fm-brief.sh | 3 ++- bin/fm-promote.sh | 2 ++ bin/fm-status-lib.sh | 4 ++++ tests/fm-task-delivery.test.sh | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bin/fm-brief.sh b/bin/fm-brief.sh index e49f4dd0d1c..7a79a202628 100755 --- a/bin/fm-brief.sh +++ b/bin/fm-brief.sh @@ -228,6 +228,7 @@ shell_quote() { STATUS_FILE=$(shell_quote "$STATE/$ID.status") INBOX_DIR=$(shell_quote "$STATE/$ID.inbox") STATUS_WAKE_REMINDER=$(fm_status_wake_reminder) +STATUS_NO_RESOLVED_ECHO_RULE=$(fm_status_no_resolved_echo_rule) # The receive-and-ack half of the steering-inbox contract, included in every # scaffold kind. The record format, doorbell line, and re-ring ladder are @@ -239,7 +240,7 @@ IFS= read -r -d '' INBOX_SECTION <&2; exit 1; } TMP="$DATA/$ID/.ship-instructions.md.${BASHPID:-$$}" @@ -222,6 +223,7 @@ EOF $PROMOTION_STATUS_WAKE_REMINDER $PROMOTION_STATUS_WORKING_RULE $PROMOTION_STATUS_NO_PROGRESS_RULE + $PROMOTION_STATUS_NO_RESOLVED_ECHO_RULE $PROMOTION_ASK_USER_BLOCK 7. Treat the scout-time Firstmate spec and any unmarked legacy \`# Task\` text as investigation context, not captain intent or ship-time instructions. EOF diff --git a/bin/fm-status-lib.sh b/bin/fm-status-lib.sh index 83019d7ecec..c282224f955 100644 --- a/bin/fm-status-lib.sh +++ b/bin/fm-status-lib.sh @@ -29,3 +29,7 @@ fm_status_working_rule() { # fm_status_no_progress_rule() { printf '%s\n' 'Never append `working:` for a sub-step, a verification pass, or the start of re-review.' } + +fm_status_no_resolved_echo_rule() { + printf '%s\n' 'Never append a `resolved:` echo of a firstmate steer; moving its message into `handled/` is the acknowledgement.' +} diff --git a/tests/fm-task-delivery.test.sh b/tests/fm-task-delivery.test.sh index 9f02492de4a..c1b89fab194 100755 --- a/tests/fm-task-delivery.test.sh +++ b/tests/fm-task-delivery.test.sh @@ -433,6 +433,8 @@ STUB "$mode: promoted worker did not receive the status protocol handoff" assert_grep "Each status-file append wakes the supervisor and costs a full supervision turn." "$payload" \ "$mode: promoted worker did not receive the status wake-cost reminder" + assert_grep 'Never append a `resolved:` echo of a firstmate steer; moving its message into `handled/` is the acknowledgement.' "$payload" \ + "$mode: promoted worker did not receive the no-resolved-echo rule" case "$mode" in no-mistakes|direct-PR) assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' "$payload" \ From ece426978d4830387c8d811fc012b069c99ff0b0 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Tue, 8 Sep 2026 22:10:01 +0800 Subject: [PATCH 5/6] no-mistakes(document): Document shared status-protocol ownership --- AGENTS.md | 2 +- docs/scripts.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d5fc0e0dcb9..dc23f88044b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -527,7 +527,7 @@ Preserve durable structured identifiers, dependencies, and completion artifact l ## 11. Crewmate briefs -`bin/fm-brief.sh` and its help own scaffold syntax, generated variants, status protocol, delivery-mode definitions of done, and exact safety mechanics. +`bin/fm-brief.sh` and its help own scaffold syntax and generated variants; `bin/fm-status-lib.sh` owns the shared generated status-protocol wording; `bin/fm-dod-lib.sh` owns delivery-mode definitions of done; and the relevant script headers own exact safety mechanics. Use its scaffold as the contract, then fill `## Captain's intent` (`{TASK}`) with the captain's own ask plus the context needed to read it, including the substance of any report, decision, or PR the ask refers to, and fill `## Firstmate spec` (`{FIRSTMATE_SPEC}`) with Firstmate's build instructions. `bin/fm-dod-lib.sh` owns what a no-mistakes worker may pass as `--intent` and its rule that the string must be self-sufficient. Keep additions task-specific rather than repeating lifecycle instructions, and alter generated sections only when the task genuinely differs from the standard shape. diff --git a/docs/scripts.md b/docs/scripts.md index 4b0bbaebded..07bd3f2d2bf 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -34,7 +34,8 @@ The shared no-mistakes gate refusal for fleet lifecycle entrypoints is summarize | `fm-backlog-receive.sh` | Idempotently ingest one confined remote handoff outbox through tasks-axi | | `fm-captain-hold.sh` | Hold tasks for the captain, record the captain's answers, gate investigation completion, and report record divergence between the status log and the backlog | | `fm-decision-hold.sh` | One-release compatibility shim mapping the retired decision commands onto fm-captain-hold.sh | -| `fm-brief.sh` | Scaffold ship (explicit `--mode`), scout, secondmate-charter, and Herdr-lab briefs, with Captain's intent and Firstmate spec subsections on ship/scout | +| `fm-brief.sh` | Scaffold ship (explicit `--mode`), scout, secondmate-charter, and Herdr-lab briefs, with mode-specific status protocol and Captain's intent and Firstmate spec subsections on ship/scout | +| `fm-status-lib.sh` | Shared generated worker status-protocol wording for briefs and promotion handoffs | | `fm-dod-lib.sh` | One owner of the ship definition of done and of the no-mistakes `--intent` contract | | `fm-herdr-lab.sh` | Provision and guardedly operate an isolated, never-default Herdr lab session | | `fm-install-herdr.sh` | Install CI's exact-version Herdr pin with official asset URL, SHA-256, and protocol checks | @@ -135,7 +136,7 @@ The shared no-mistakes gate refusal for fleet lifecycle entrypoints is summarize | `fm-pr-merge.sh` | Record PR metadata, merge a task's canonical full GitHub or GitLab URL, then refuse an outcome it cannot prove landed or queued | | `fm-merge-outcome-lib.sh` | Publish a confirmed merge's durable, role-routed supervision outcome | | `fm-parent-channel-lib.sh` | Resolve a secondmate home's parent channel and append a captain-facing outcome line to it at most once | -| `fm-promote.sh` | Promote a scout task in place to a protected ship task with an explicit delivery mode, and write the ship instructions carrying that mode's definition of done | +| `fm-promote.sh` | Promote a scout task in place to a protected ship task with an explicit delivery mode, and write ship instructions carrying that mode's status protocol and definition of done | | `fm-teardown.sh` | Fail-closed teardown: return landed ship worktrees, require completed scout deliverables, retire secondmate homes | | `fm-harness.sh` | Detect the running harness and resolve crew or secondmate harness, model, and effort | | `fm-lock.sh` | Per-home firstmate session lock | From 5ddacbf73cd7fa2da849c7f80512136d1eb1797a Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Tue, 8 Sep 2026 22:13:55 +0800 Subject: [PATCH 6/6] no-mistakes(lint): Suppress intentional SC2016 literal-backtick warnings --- bin/fm-status-lib.sh | 5 +++++ tests/fm-brief.test.sh | 2 ++ tests/fm-task-delivery.test.sh | 3 +++ 3 files changed, 10 insertions(+) diff --git a/bin/fm-status-lib.sh b/bin/fm-status-lib.sh index c282224f955..84acdea1dbe 100644 --- a/bin/fm-status-lib.sh +++ b/bin/fm-status-lib.sh @@ -11,12 +11,15 @@ fm_status_working_rule() { # local kind=$1 case "$kind" in scout) + # shellcheck disable=SC2016 # Backticks are literal status-protocol text. printf '%s\n' 'Append `working:` only for a genuine phase change the supervisor would act on: starting the investigation, entering a distinct research phase, or beginning report writing.' ;; no-mistakes|direct-PR) + # shellcheck disable=SC2016 # Backticks are literal status-protocol text. printf '%s\n' 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' ;; local-only) + # shellcheck disable=SC2016 # Backticks are literal status-protocol text. printf '%s\n' 'Append `working:` only for a genuine phase change the supervisor would act on: work started or implementation committed and validation started.' ;; *) @@ -27,9 +30,11 @@ fm_status_working_rule() { # } fm_status_no_progress_rule() { + # shellcheck disable=SC2016 # Backticks are literal status-protocol text. printf '%s\n' 'Never append `working:` for a sub-step, a verification pass, or the start of re-review.' } fm_status_no_resolved_echo_rule() { + # shellcheck disable=SC2016 # Backticks are literal status-protocol text. printf '%s\n' 'Never append a `resolved:` echo of a firstmate steer; moving its message into `handled/` is the acknowledgement.' } diff --git a/tests/fm-brief.test.sh b/tests/fm-brief.test.sh index 92747a4a6a7..76bc933c2c8 100755 --- a/tests/fm-brief.test.sh +++ b/tests/fm-brief.test.sh @@ -284,6 +284,7 @@ Append only when this protocol requires it; never use status as a progress log.' id="status-protocol-direct" FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" firstmate --mode direct-PR >/dev/null 2>&1 brief="$home/data/$id/brief.md" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' "$brief" \ "direct-PR scaffold lost its mode-specific working rule" assert_grep 'done: PR {url}' "$brief" \ @@ -294,6 +295,7 @@ Append only when this protocol requires it; never use status as a progress log.' id="status-protocol-local-only" FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" firstmate --mode local-only >/dev/null 2>&1 brief="$home/data/$id/brief.md" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started or implementation committed and validation started.' "$brief" \ "local-only scaffold lost its mode-specific working rule" assert_no_grep 'implementation committed and validation started, or PR opened' "$brief" \ diff --git a/tests/fm-task-delivery.test.sh b/tests/fm-task-delivery.test.sh index c1b89fab194..5bcede71a2c 100755 --- a/tests/fm-task-delivery.test.sh +++ b/tests/fm-task-delivery.test.sh @@ -433,13 +433,16 @@ STUB "$mode: promoted worker did not receive the status protocol handoff" assert_grep "Each status-file append wakes the supervisor and costs a full supervision turn." "$payload" \ "$mode: promoted worker did not receive the status wake-cost reminder" + # shellcheck disable=SC2016 # Backticks are literal generated delivery prose. assert_grep 'Never append a `resolved:` echo of a firstmate steer; moving its message into `handled/` is the acknowledgement.' "$payload" \ "$mode: promoted worker did not receive the no-resolved-echo rule" case "$mode" in no-mistakes|direct-PR) + # shellcheck disable=SC2016 # Backticks are literal generated delivery prose. assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' "$payload" \ "$mode: promoted worker lost its allowed working phases" ;; local-only) + # shellcheck disable=SC2016 # Backticks are literal generated delivery prose. assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started or implementation committed and validation started.' "$payload" \ "local-only: promoted worker lost its PR-free working phases" assert_no_grep 'implementation committed and validation started, or PR opened' "$payload" \