From cd1813a79e7280f920acada0f2d0ca3259ae02d0 Mon Sep 17 00:00:00 2001 From: Search Agent Date: Wed, 29 Jul 2026 08:45:30 -0500 Subject: [PATCH 1/3] ci: cover fm-brief with stock macOS Bash --- .agents/skills/firstmate-coding-guidelines/SKILL.md | 2 ++ .github/workflows/ci.yml | 8 ++++++++ CONTRIBUTING.md | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.agents/skills/firstmate-coding-guidelines/SKILL.md b/.agents/skills/firstmate-coding-guidelines/SKILL.md index ac1da34dd84..033bf78e310 100644 --- a/.agents/skills/firstmate-coding-guidelines/SKILL.md +++ b/.agents/skills/firstmate-coding-guidelines/SKILL.md @@ -95,6 +95,8 @@ Run `bin/fm-doc-audience-check.sh`; it enforces classification, README setup rou - Plain dash `-`, never an em dash. - Never add an agent name as a commit co-author. - `bin/*.sh` and `bin/backends/*.sh` must pass `shellcheck`. +- `bin/*.sh` and `bin/backends/*.sh` must also parse under Bash 3.2, which is what `#!/usr/bin/env bash` resolves to on stock macOS; CI's `macos-stock-bash` job runs that parse sweep over the `bin/fm-lint.sh --list-files` inventory because Bash 4+ accepts constructs that Bash 3.2 rejects. +- Never nest a heredoc inside `$(...)` in a script whose heredoc bodies carry free-form English prose, because Bash 3.2 scans the command substitution for its closing `)` without understanding heredoc bodies, so one ordinary possessive can break parsing of the whole file; `tests/fm-brief.test.sh` enforces this structure with a Perl scanner, and `bin/fm-brief.sh` assigns its prose blocks with `IFS= read -r -d '' ... <.test.sh`, and extend an existing script rather than inventing a new runner. - Tests must exercise behavior through an executable or public interface and must never assert implementation-source bytes, including through parsers, regexes, snapshots, or indirect wrappers. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 064f1c16131..3b7b9cd0790 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -363,6 +363,14 @@ jobs: exit 1 } + brief_output=$(/bin/bash tests/fm-brief.test.sh) + printf '%s\n' "$brief_output" + brief_count=$(printf '%s\n' "$brief_output" | grep -c '^ok - ') + [ "$brief_count" -eq 16 ] || { + echo "::error::expected 16 fm-brief tests, got $brief_count" + exit 1 + } + invariants: name: Repo invariants runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6633ffc920..495e18c52a2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,7 +94,7 @@ Its header and `--help` own the flags, family labels, lanes, and changed-file ma Portable shard balance evidence lives in `docs/fm-test-portable-shards.md`. Local no-mistakes Test stays intent-targeted and must not wire `commands.test` to `--all` or a `tests/*.test.sh` walk. Family selection is the ordinary local path; `--all` is deliberate full regression only. -CI owns broad regression across required portable parallel shards, the portable serial lane's separate-runner shards, the Herdr lane, lint, invariants, the coverage guard, and stock macOS Bash compatibility in [`.github/workflows/ci.yml`](.github/workflows/ci.yml). +CI owns broad regression across required portable parallel shards, the portable serial lane's separate-runner shards, the Herdr lane, lint, invariants, the coverage guard, and the stock macOS Bash 3.2 compatibility lane in [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Use `bin/fm-test-run.sh --list-lanes` for exact lane names and `--help` for `--jobs` rules and required gate-skip flags when reproducing a lane locally. Discover tests by listing `tests/*.test.sh`: each is a self-contained bash script named `.test.sh`, and its header comment describes what it covers, so pass one to `bin/fm-test-run.sh` to focus on a subject with canonical timing output. Tests that need a real optional backend or an explicit opt-in (real herdr/zellij/cmux smoke tests, the live Pi regression) skip themselves and print the tool or environment gate needed to enable them, so the portable suite remains safe on machines without those tools. From 3ba44afc471a59f05862ba7b3f2fb0d27573abbb Mon Sep 17 00:00:00 2001 From: Search Agent Date: Wed, 29 Jul 2026 08:57:57 -0500 Subject: [PATCH 2/3] no-mistakes(review): Clarify Bash 3.2 scanner and CI authority --- .agents/skills/firstmate-coding-guidelines/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.agents/skills/firstmate-coding-guidelines/SKILL.md b/.agents/skills/firstmate-coding-guidelines/SKILL.md index 033bf78e310..723bcc08a74 100644 --- a/.agents/skills/firstmate-coding-guidelines/SKILL.md +++ b/.agents/skills/firstmate-coding-guidelines/SKILL.md @@ -96,7 +96,7 @@ Run `bin/fm-doc-audience-check.sh`; it enforces classification, README setup rou - Never add an agent name as a commit co-author. - `bin/*.sh` and `bin/backends/*.sh` must pass `shellcheck`. - `bin/*.sh` and `bin/backends/*.sh` must also parse under Bash 3.2, which is what `#!/usr/bin/env bash` resolves to on stock macOS; CI's `macos-stock-bash` job runs that parse sweep over the `bin/fm-lint.sh --list-files` inventory because Bash 4+ accepts constructs that Bash 3.2 rejects. -- Never nest a heredoc inside `$(...)` in a script whose heredoc bodies carry free-form English prose, because Bash 3.2 scans the command substitution for its closing `)` without understanding heredoc bodies, so one ordinary possessive can break parsing of the whole file; `tests/fm-brief.test.sh` enforces this structure with a Perl scanner, and `bin/fm-brief.sh` assigns its prose blocks with `IFS= read -r -d '' ... <.test.sh`, and extend an existing script rather than inventing a new runner. - Tests must exercise behavior through an executable or public interface and must never assert implementation-source bytes, including through parsers, regexes, snapshots, or indirect wrappers. From 9ae70c4ed81dc7edc21131b3316b797842c14672 Mon Sep 17 00:00:00 2001 From: Search Agent Date: Wed, 29 Jul 2026 09:08:05 -0500 Subject: [PATCH 3/3] no-mistakes(document): Document complete Bash 3.2 compatibility scope --- .agents/skills/firstmate-coding-guidelines/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.agents/skills/firstmate-coding-guidelines/SKILL.md b/.agents/skills/firstmate-coding-guidelines/SKILL.md index 723bcc08a74..ca43720d57f 100644 --- a/.agents/skills/firstmate-coding-guidelines/SKILL.md +++ b/.agents/skills/firstmate-coding-guidelines/SKILL.md @@ -95,8 +95,8 @@ Run `bin/fm-doc-audience-check.sh`; it enforces classification, README setup rou - Plain dash `-`, never an em dash. - Never add an agent name as a commit co-author. - `bin/*.sh` and `bin/backends/*.sh` must pass `shellcheck`. -- `bin/*.sh` and `bin/backends/*.sh` must also parse under Bash 3.2, which is what `#!/usr/bin/env bash` resolves to on stock macOS; CI's `macos-stock-bash` job runs that parse sweep over the `bin/fm-lint.sh --list-files` inventory because Bash 4+ accepts constructs that Bash 3.2 rejects. -- Never nest a heredoc inside `$(...)` in a script whose heredoc bodies carry free-form English prose, because Bash 3.2 scans the command substitution for its closing `)` without understanding heredoc bodies, so one ordinary possessive can break parsing of the whole file; `tests/fm-brief.test.sh` provides a partial local structural guard, `bin/fm-brief.sh` assigns its prose blocks with `IFS= read -r -d '' ... <.test.sh`, and extend an existing script rather than inventing a new runner. - Tests must exercise behavior through an executable or public interface and must never assert implementation-source bytes, including through parsers, regexes, snapshots, or indirect wrappers.