Skip to content

chore: steer the no-mistakes Test step to diff-focused local validation - #2

Merged
brentsec merged 2 commits into
mainfrom
fm/firstmate-targeted-validation-policy
Sep 16, 2026
Merged

brentsec merged 2 commits into
mainfrom
fm/firstmate-targeted-validation-policy

Conversation

@brentsec

Copy link
Copy Markdown
Owner

Intent

Approve configuring Firstmate's local validation to run only the relevant tests, leaving the complete test suite to GitHub, so the already-coded customization fixes do not repeatedly hit the 30-minute limit. Run the later customization validation without another heavy local validation competing for this machine.

What Changed

  • .no-mistakes.yaml now carries a test.instructions policy that directs the Test step to judge and run only the tests exercising the branch's changed behavior - preferring the narrowest selection such as the single subject script the diff touches, treating bin/fm-test-run.sh --changed as an optional upper bound rather than a required floor, and never walking the whole tests/ suite locally. commands.test stays absent; full deterministic regression coverage remains with GitHub CI.
  • tests/fm-nm-test-contract.test.sh adds a contract asserting the parsed config exposes a non-empty test.instructions string, and the file now skips both contracts with a message when ruby is unavailable instead of failing on the missing parser.
  • docs/configuration.md and the firstmate-coding-guidelines skill document the new field, including that test.instructions steers an agent that executes code and so is honored only from the default-branch copy of .no-mistakes.yaml - the same trust property commands.test already has.

Risk Assessment

✅ Low: The change is a well-bounded config, documentation, and contract-test edit whose central claims (that test.instructions is a consumed, default-branch-trusted field, and that CI owns full regression coverage) I verified directly against the no-mistakes binary and the CI workflow, with no correctness defect found in the only executable logic it adds.

Testing

I drove this change through firstmate's own test runner rather than reading it. The narrowest selection the new policy prescribes (tests/fm-nm-test-contract.test.sh plus tests/fm-documentation-audiences.test.sh) completes in 0.45s against a 206-script inventory, with --changed resolving to 54 scripts as a genuine upper bound to narrow from - which is exactly the local time-budget relief the intent asks for. I reproduced the ruby regression end-to-end: the pre-fix version of the contract test hard-fails on this ruby-less host while the shipped version emits skip: and the runner records gate_skip=true, exit 0. Because ruby is absent here, I provisioned a Debian container with ruby 3.3.8 and drove the ruby-present path adversarially - removing, emptying, and mistyping test.instructions each fail with the intended message, and re-pinning commands.test to a full-suite walk fails the pre-existing guard, so the new assertion is not vacuous. The documentation-audiences test passes over the changed docs prose, and --check-coverage confirms the CI lanes equal the full inventory, so leaving broad regression to GitHub is real. The change has no rendered UI surface - it is a YAML config field, a bash test, and prose - so the reviewer-visible evidence is CLI transcripts of the runner's actual output rather than screenshots. One scenario stayed untested: the no-mistakes Test step rendering the new field as its trusted live-validation runbook, which needs a default branch that already carries it. While driving the coverage proof I hit a pre-existing locale-dependent failure in bin/fm-test-run.sh --check-coverage under en_US.UTF-8; I reported it instead of fixing it, since the fix is in a file this change does not touch.

  • Live validation: ✅ go - 7 of 8 scenarios driven live against the product
Scenario Result Live Evidence
A contributor validating this branch locally runs only the tests that exercise its changed behavior and finishes in under a second instead of walking the 206-script suite ✅ pass live time bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh tests/fm-documentation-audiences.test.sh -> FM_TEST_SUMMARY total=2 failed=0, 0.448s wall, vs --list --all = 206 scripts; evidence file 06…
The changed contract test gate-skips instead of failing the local Test step on a host with no ruby installed ✅ pass live bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh on this ruby-less host -> skip: ruby not installed..., gate_skip=true, exit 0; evidence file 01-targeted-run-ruby-absent.txt
Regression reproduction: the pre-fix version of that same test hard-fails the run on this host, proving the skip conversion fixed a real break ✅ pass live Commit 26d647c's version of the script run through the same runner in a scratch copy -> not ok - ruby is required to parse .no-mistakes.yaml for this contract, exit=1; shipped version -> exit=0; evi…
Adversarial: with ruby present, deleting, emptying, or mistyping test.instructions is caught rather than silently accepted ✅ pass live Debian container with ruby 3.3.8, three mutations of .no-mistakes.yaml driven through bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh -> each `not ok - test.instructions must be a non-empty str…
Adversarial: re-pinning commands.test to the 30-minute full suite walk this change exists to prevent is rejected ✅ pass live Same container run with commands.test: 'bin/fm-test-run.sh --all' injected -> not ok - commands.test must be absent or empty so Test stays intent-targeted; got: "bin/fm-test-run.sh --all", exit=1;…
The new docs/configuration.md prose keeps the documentation audience inventory, owner pointers, and local links valid ✅ pass live bin/fm-test-run.sh tests/fm-documentation-audiences.test.sh -> 4 ok assertions including local link resolution, exit 0; evidence file 05-docs-audience-behavior.txt
GitHub CI still owns the complete deterministic suite the local Test step now declines to walk ✅ pass live LC_ALL=C bin/fm-test-run.sh --check-coverage (the exact command .github/workflows/ci.yml:61 runs) -> FM_TEST_COVERAGE ok total=206 across the 9 CI lanes; evidence file 04-ci-owns-full-coverage.txt
A no-mistakes Test step run on a repository whose default branch carries this config receives test.instructions as its trusted live-validation runbook ⏸️ untested no The field is honored only from the default-branch copy of .no-mistakes.yaml, so observing it live requires a no-mistakes pipeline running against a default branch that already carries it. This gate st…
Evidence: Targeted run of the changed contract test on this ruby-less host (gate skip, exit 0, 17ms)

Source: Targeted run of the changed contract test on this ruby-less host (gate skip, exit 0, 17ms)

$ bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh --json /tmp/nm-contract-timing.json
FM_TEST_BEGIN 2026-09-16T01:46:10Z tests/fm-nm-test-contract.test.sh family=unclassified expected_gate_skip=none
skip: ruby not installed; cannot parse .no-mistakes.yaml for these contracts
fm-test-run: gate skip: tests/fm-nm-test-contract.test.sh: ruby not installed; cannot parse .no-mistakes.yaml for these contracts
FM_TEST_END 2026-09-16T01:46:10Z tests/fm-nm-test-contract.test.sh exit=0 duration_ms=17 gate_skip=true
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=1 duration_ms=40
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=17 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-nm-test-contract.test.sh duration_ms=17
fm-test-run: wrote timing artifact: /tmp/nm-contract-timing.json
exit=0
Evidence: Regression reproduction: pre-fix version hard-fails, shipped version gate-skips

Source: Regression reproduction: pre-fix version hard-fails, shipped version gate-skips

\### BEFORE the fix (commit 26d647c version of tests/fm-nm-test-contract.test.sh), ruby absent:
$ bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh
FM_TEST_BEGIN 2026-09-16T01:46:24Z tests/fm-nm-test-contract.test.sh family=unclassified expected_gate_skip=none
not ok - ruby is required to parse .no-mistakes.yaml for this contract
FM_TEST_END 2026-09-16T01:46:24Z tests/fm-nm-test-contract.test.sh exit=1 duration_ms=17 gate_skip=false
FM_TEST_SUMMARY total=1 failed=1 skipped_gate=0 duration_ms=39
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=17 failed=1
FM_TEST_SLOWEST rank=1 script=tests/fm-nm-test-contract.test.sh duration_ms=17
exit=1

\### AFTER the fix (HEAD version of tests/fm-nm-test-contract.test.sh), ruby absent:
$ bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh
FM_TEST_BEGIN 2026-09-16T01:46:30Z tests/fm-nm-test-contract.test.sh family=unclassified expected_gate_skip=none
skip: ruby not installed; cannot parse .no-mistakes.yaml for these contracts
fm-test-run: gate skip: tests/fm-nm-test-contract.test.sh: ruby not installed; cannot parse .no-mistakes.yaml for these contracts
FM_TEST_END 2026-09-16T01:46:30Z tests/fm-nm-test-contract.test.sh exit=0 duration_ms=17 gate_skip=true
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=1 duration_ms=41
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=17 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-nm-test-contract.test.sh duration_ms=17
exit=0
Evidence: Adversarial contract enforcement with ruby 3.3.8 present (6 config states)

Source: Adversarial contract enforcement with ruby 3.3.8 present (6 config states)

ruby: ruby 3.3.8 (2025-04-09 revision b200bad6cd) [x86_64-linux-gnu]

==================================================================
SCENARIO: A. tracked .no-mistakes.yaml as shipped on this branch (test.instructions present)
==================================================================
$ bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh
FM_TEST_BEGIN 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh family=unclassified expected_gate_skip=none
ok - no-mistakes does not configure commands.test
ok - no-mistakes sets a non-empty test.instructions policy for the Test step
FM_TEST_END 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh exit=0 duration_ms=0 gate_skip=false
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=0
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=0 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-nm-test-contract.test.sh duration_ms=0
exit=0

--- mutated config (test: section) ---
---
test:
  evidence:
    store_in_repo: true

==================================================================
SCENARIO: B. adversarial: test.instructions removed entirely
==================================================================
$ bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh
FM_TEST_BEGIN 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh family=unclassified expected_gate_skip=none
ok - no-mistakes does not configure commands.test
not ok - test.instructions must be a non-empty string so the Test step stays diff-focused locally
FM_TEST_END 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh exit=1 duration_ms=0 gate_skip=false
FM_TEST_SUMMARY total=1 failed=1 skipped_gate=0 duration_ms=0
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=0 failed=1
FM_TEST_SLOWEST rank=1 script=tests/fm-nm-test-contract.test.sh duration_ms=0
exit=1

==================================================================
SCENARIO: C. adversarial: test.instructions present but empty
==================================================================
$ bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh
FM_TEST_BEGIN 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh family=unclassified expected_gate_skip=none
ok - no-mistakes does not configure commands.test
not ok - test.instructions must be a non-empty string so the Test step stays diff-focused locally
FM_TEST_END 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh exit=1 duration_ms=0 gate_skip=false
FM_TEST_SUMMARY total=1 failed=1 skipped_gate=0 duration_ms=0
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=0 failed=1
FM_TEST_SLOWEST rank=1 script=tests/fm-nm-test-contract.test.sh duration_ms=0
exit=1

==================================================================
SCENARIO: D. adversarial: test.instructions set to a non-string (list)
==================================================================
$ bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh
FM_TEST_BEGIN 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh family=unclassified expected_gate_skip=none
ok - no-mistakes does not configure commands.test
not ok - test.instructions must be a non-empty string so the Test step stays diff-focused locally
FM_TEST_END 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh exit=1 duration_ms=0 gate_skip=false
FM_TEST_SUMMARY total=1 failed=1 skipped_gate=0 duration_ms=0
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=0 failed=1
FM_TEST_SLOWEST rank=1 script=tests/fm-nm-test-contract.test.sh duration_ms=0
exit=1

==================================================================
SCENARIO: E. adversarial: commands.test re-pinned to the 30-minute full suite walk
==================================================================
$ bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh
FM_TEST_BEGIN 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh family=unclassified expected_gate_skip=none
not ok - commands.test must be absent or empty so Test stays intent-targeted; got: "bin/fm-test-run.sh --all"
FM_TEST_END 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh exit=1 duration_ms=0 gate_skip=false
FM_TEST_SUMMARY total=1 failed=1 skipped_gate=0 duration_ms=0
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=0 failed=1
FM_TEST_SLOWEST rank=1 script=tests/fm-nm-test-contract.test.sh duration_ms=0
exit=1

==================================================================
SCENARIO: F. restored tracked config (confirms the guard is not sticky)
==================================================================
$ bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh
FM_TEST_BEGIN 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh family=unclassified expected_gate_skip=none
ok - no-mistakes does not configure commands.test
ok - no-mistakes sets a non-empty test.instructions policy for the Test step
FM_TEST_END 2026-09-16T01:47:12Z tests/fm-nm-test-contract.test.sh exit=0 duration_ms=0 gate_skip=false
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=0
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=0 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-nm-test-contract.test.sh duration_ms=0
exit=0
Evidence: CI owns the full 206-script inventory, plus the pre-existing en_US.UTF-8 coverage-guard failure

Source: CI owns the full 206-script inventory, plus the pre-existing en_US.UTF-8 coverage-guard failure

Does GitHub CI really own the complete suite the local Test step now declines to walk?
bin/fm-test-run.sh --check-coverage is the runner's own proof that the CI lanes
(.github/workflows/ci.yml line 61 runs exactly this) equal the full inventory.

$ LC_ALL=C bin/fm-test-run.sh --check-coverage      # the locale GitHub runners use (C.UTF-8)
FM_TEST_COVERAGE ok total=206 parallel=24 parallel_max_ms=417163 parallel_imbalance_ms=2894 parallel_unhinted=0 serial=166 serial_shards=5 serial_unhinted=18 herdr=16
exit=0

CI lanes that make up that total:
  portable-parallel-1
  portable-parallel-2
  portable-serial
  portable-serial-1of5
  portable-serial-2of5
  portable-serial-3of5
  portable-serial-4of5
  portable-serial-5of5
  real-herdr-gated

--- SEPARATE, PRE-EXISTING ISSUE FOUND WHILE DRIVING THIS (not caused by this change) ---
The same guard fails on a host whose locale is en_US.UTF-8, because the comm(1)
calls in bin/fm-test-run.sh are not LC_ALL=C-prefixed the way every adjacent sort is:

$ LANG=en_US.UTF-8 bin/fm-test-run.sh --check-coverage
comm: file 2 is not in sorted order
comm: input is not in sorted order
exit=1
Evidence: Documentation audiences behavior test over the changed docs/configuration.md prose

Source: Documentation audiences behavior test over the changed docs/configuration.md prose

$ bin/fm-test-run.sh tests/fm-documentation-audiences.test.sh
FM_TEST_BEGIN 2026-09-16T01:48:58Z tests/fm-documentation-audiences.test.sh family=pure-contract-unit expected_gate_skip=none
ok - documentation inventory classifies every maintained prose surface exactly once
ok - classification, setup routing, and maintained-prose scope fail safely
ok - required documentation owner pointers cannot silently disappear
ok - local links resolve while dates, versions, commands, and incident prose remain semantically reviewed
FM_TEST_END 2026-09-16T01:48:58Z tests/fm-documentation-audiences.test.sh exit=0 duration_ms=403 gate_skip=false
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=427
FM_TEST_SUMMARY_FAMILY family=pure-contract-unit count=1 duration_ms=403 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-documentation-audiences.test.sh duration_ms=403
Evidence: Selection narrowing and wall clock: 206 full / 54 changed-upper-bound / 2 scripts in 0.448s

Source: Selection narrowing and wall clock: 206 full / 54 changed-upper-bound / 2 scripts in 0.448s

Local Test-step selection narrowing for this branch (base 8ff3a80 -> HEAD 19c09db)
Changed files:
  .agents/skills/firstmate-coding-guidelines/SKILL.md
  .no-mistakes.yaml
  docs/configuration.md
  tests/fm-nm-test-contract.test.sh

$ bin/fm-test-run.sh --list --all | wc -l              # complete deterministic suite (CI owns this)
206

$ bin/fm-test-run.sh --list --changed --base 8ff3a80 | wc -l   # optional upper bound to narrow FROM
54

Narrowest selection that actually exercises the changed behavior (what the new
test.instructions policy directs the Test step to run):
  tests/fm-nm-test-contract.test.sh      <- the only executable test of the changed .no-mistakes.yaml contract
  tests/fm-documentation-audiences.test.sh <- covers the changed docs/ prose classification and link resolution

Wall clock of that narrowest selection:
$ time bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh tests/fm-documentation-audiences.test.sh
FM_TEST_BEGIN 2026-09-16T01:49:14Z tests/fm-nm-test-contract.test.sh family=unclassified expected_gate_skip=none
skip: ruby not installed; cannot parse .no-mistakes.yaml for these contracts
fm-test-run: gate skip: tests/fm-nm-test-contract.test.sh: ruby not installed; cannot parse .no-mistakes.yaml for these contracts
FM_TEST_END 2026-09-16T01:49:14Z tests/fm-nm-test-contract.test.sh exit=0 duration_ms=17 gate_skip=true
FM_TEST_BEGIN 2026-09-16T01:49:14Z tests/fm-documentation-audiences.test.sh family=pure-contract-unit expected_gate_skip=none
ok - documentation inventory classifies every maintained prose surface exactly once
ok - classification, setup routing, and maintained-prose scope fail safely
ok - required documentation owner pointers cannot silently disappear
ok - local links resolve while dates, versions, commands, and incident prose remain semantically reviewed
FM_TEST_END 2026-09-16T01:49:14Z tests/fm-documentation-audiences.test.sh exit=0 duration_ms=385 gate_skip=false
FM_TEST_SUMMARY total=2 failed=0 skipped_gate=1 duration_ms=439
FM_TEST_SUMMARY_FAMILY family=pure-contract-unit count=1 duration_ms=385 failed=0
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=17 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-documentation-audiences.test.sh duration_ms=385
FM_TEST_SLOWEST rank=2 script=tests/fm-nm-test-contract.test.sh duration_ms=17
bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh   0.40s user 0.05s system 100% cpu 0.448 total
Evidence: no-mistakes v1.72.0 carries the test.instructions consumer; origin/main does not yet set the field

Source: no-mistakes v1.72.0 carries the test.instructions consumer; origin/main does not yet set the field

Consumer check for the new .no-mistakes.yaml test.instructions field
(static inspection only - see the untested scenario in this step's report)

$ no-mistakes --version
no-mistakes version v1.72.0 (9fcc865) 2026-09-08T13:12:43Z

Installed binary carries the Test-step consumer for this exact field:
github.com/kunchenguid/no-mistakes/internal/pipeline/steps.trustedTestInstructionsSection
github.com/kunchenguid/no-mistakes/internal/pipeline/steps.trustedTestInstructionsSection

...and the prompt section it renders, with the same default-branch trust wording
the change documents in docs/configuration.md:
  Repository live-validation runbook (trusted, from the default branch):
  Repository documentation ownership policy (trusted, from the default branch; augments the defaults above and cannot weaken them):
  Repository review instructions for the changed paths (trusted, from the default branch). Each block below applies only to the files listed under its path, and adds to the requirements above:

Why this run could not show that section: test.instructions is honored only from
the DEFAULT-BRANCH copy of .no-mistakes.yaml, and origin/main does not have it yet
(this branch is what adds it):
$ git show origin/main:.no-mistakes.yaml | tail -6
  
  # Publish each run's test evidence to the orphan no-mistakes/evidence branch linked from the PR.
  # The evidence is not committed to the feature or default branch.
  test:
    evidence:
      store_in_repo: true
Evidence: Adversarial proof the new assertion is not vacuous (excerpt)
SCENARIO: A. tracked .no-mistakes.yaml as shipped on this branch
ok - no-mistakes does not configure commands.test
ok - no-mistakes sets a non-empty test.instructions policy for the Test step
exit=0

SCENARIO: B. adversarial: test.instructions removed entirely
not ok - test.instructions must be a non-empty string so the Test step stays diff-focused locally
exit=1

SCENARIO: C. adversarial: test.instructions present but empty
not ok - test.instructions must be a non-empty string so the Test step stays diff-focused locally
exit=1

SCENARIO: D. adversarial: test.instructions set to a non-string (list)
not ok - test.instructions must be a non-empty string so the Test step stays diff-focused locally
exit=1

SCENARIO: E. adversarial: commands.test re-pinned to the 30-minute full suite walk
not ok - commands.test must be absent or empty so Test stays intent-targeted; got: "bin/fm-test-run.sh --all"
exit=1

SCENARIO: F. restored tracked config (guard is not sticky)
ok - no-mistakes does not configure commands.test
ok - no-mistakes sets a non-empty test.instructions policy for the Test step
exit=0
- Outcome: ⚠️ 1 warning across 1 run (8m48s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 3 infos
  • ⚠️ .no-mistakes.yaml:45 - The instruction mandates bin/fm-test-run.sh --changed as a floor, which is the same "changed tests" deterministic suite walk the guideline this change edits forbids (.agents/skills/firstmate-coding-guidelines/SKILL.md:117: "whether it selects the full suite, changed tests, a family, or a fixed script list"), just expressed as prose instead of commands.test. SKILL.md:120 justifies the new field by saying it "directs the agent's judgment rather than pinning a command", but the text pins one. Concrete over-selection on this very branch: .no-mistakes.yaml maps to pure-contract-unit + real-herdr-gated (bin/fm-test-run.sh:1540-1542), so bin/fm-test-run.sh --list --changed selects 54 of 206 scripts, including all 15 real-lab Herdr e2e scripts - a serial lane CI measures at ~7 minutes, with fm-backend-herdr-focus-flash-e2e alone at ~2 minutes locally (docs/fm-test-portable-shards.md:113) - even though the behavior this branch changes is covered by tests/fm-nm-test-contract.test.sh alone. Because the text also forbids running less, the agent cannot narrow to the relevant script, which is the 3.6-minute posture SKILL.md:119 credits. This works against the intent's "run only the relevant tests" and "without another heavy local validation competing for this machine". Narrower form that satisfies the intent: direct judgment ("select the tests that exercise the changed behavior; bin/fm-test-run.sh --changed is available as an upper bound when the mapping is unclear") instead of commanding the walk.
  • ⚠️ tests/fm-nm-test-contract.test.sh:39 - The two new case blocks (lines 39-46) assert that a natural-language instruction string contains particular phrases. The repo test-quality rule names this exactly: parsing the YAML into a semantic model is right, but "a natural-language prompt or instruction is not proven effective because its source contains a sentence." The guard gives a wrong PASS on a config that mandates the opposite: instructions reading "Run bin/fm-test-run.sh --all; it covers every changed area. Walking the entire tests/ suite locally is acceptable." match *fm-test-run.sh*changed* (via "every changed area") and *&#34;entire tests/ suite&#34;*, so the test goes green while the config now pins the forbidden full walk. Symmetrically, a behavior-preserving reword ("stay scoped to the branch diff") fails the test with the policy unchanged. Smallest honest remedy is to remove the two phrase-matching blocks; the only semantically checkable property left is the typed "test.instructions is present and a non-empty string" assertion at lines 37-38, matching the sibling commands.test check that asserts a typed value rather than prose.
  • ⚠️ tests/fm-nm-test-contract.test.sh:44 - The *&#34;complete deterministic regression&#34;* alternative never matches the configured value: .no-mistakes.yaml:51 says "full deterministic regression coverage", and "complete deterministic regression" appears only in the YAML comment at line 37, which is not part of the parsed instructions string. It is a second acceptance spelling that matches nothing today and only widens what the guard would accept tomorrow. No intent requirement needs two spellings. It lives inside the phrase-matching component flagged above, so removing that component removes this too; if the phrase matching is kept, the narrower form is a single exact requirement rather than an alternation.
  • ⚠️ .no-mistakes.yaml:44 - CONTRIBUTING.md:72 declares docs/configuration.md §"Gate defaults (.no-mistakes.yaml)" the owner of the tracked gate defaults, and that section (docs/configuration.md:217-222) enumerates them: test.evidence.store_in_repo: true and the commands.lint pin. This change adds a third tracked default, test.instructions, and a new trust property (honored only from the default-branch copy, like commands.test at docs/configuration.md:221), but the owning section is not updated. A reader following the declared owner pointer gets an incomplete list of what the tracked config sets.
  • ⚠️ tests/fm-nm-test-contract.test.sh:28 - The new function hard-fails when ruby is missing. On this machine command -v ruby exits 1, so the whole suite exits 1 with "not ok - ruby is required to parse .no-mistakes.yaml for this contract" before any contract is evaluated - and this suite is in pure-contract-unit, which the change's own mandated --changed selection pulls in for a .no-mistakes.yaml diff. The repo already owns the right convention for a host-missing prerequisite: emit skip: &lt;reason&gt; as the first output line (tests/lib.sh:249-251), which bin/fm-test-run.sh:1669-1693 records as a successful gate-skip rather than a failure. Flagging as ask-user because the honest remedy is not local to the new function - the pre-existing sibling at line 12 fails identically, as do tests/fm-ci-workflow.test.sh:21 and tests/fm-test-run.test.sh:1667 - so fixing it means either converting that whole pattern to skip: or declaring ruby a required local toolchain, both of which extend past this change's stated scope.
  • ⚠️ .no-mistakes.yaml:48 - "Record the exact command run and its pass/fail output as evidence before reporting the step done." is a parallel copy of a rule no-mistakes already owns: the Test step captures step evidence itself, and this same file already configures its destination at lines 52-53 (evidence.store_in_repo: true). The intent is about test scope ("run only the relevant tests, leaving the complete test suite to GitHub") and requires nothing about evidence handling. Recommend removing the sentence rather than keeping a second, drift-prone statement of the evidence contract.

🔧 Fix applied.
3 infos still open:

  • ℹ️ tests/fm-nm-test-contract.test.sh:11 - The ruby guard is now file-level, so on any host without ruby the whole file exits 0 with a gate skip - including the pre-existing commands.test contract, which previously hard-failed. command -v ruby exits 1 on this machine, so this change ships with zero local evidence for its own new contract, and a local regression that re-pinned commands.test would not be caught locally either. This is not a defect and needs no change: the sibling test_nm_has_no_deterministic_test_command is invoked first at line 44, so a function-scoped skip inside the new case could never have been reached, making the file-level guard the only coherent form of the requested skip. Enforcement is intact where it counts - GitHub's runner image ships ruby, so both contracts still execute in CI, and tests/fm-ci-workflow.test.sh:21 and tests/fm-test-run.test.sh:1667 still hard-fail if ruby ever disappears from that image, so the skip cannot silently become permanent.
  • ℹ️ .no-mistakes.yaml:44 - Verified in the consumer: internal/pipeline/steps.trustedTestInstructionsSection renders this field under the header "Repository live-validation runbook (trusted, from the default branch):", confirming the docs' default-branch-only trust claim. The practical consequence worth knowing is that this run's own Test step still reads main's copy, which has no test.instructions - so the new steering takes effect for validations that start after this merges, which matches the intent's framing about "the already-coded customization fixes" and the "later customization validation". Nothing to change.
  • ℹ️ .no-mistakes.yaml:53 - The no-mistakes Test step already ships the rule "Do NOT run the complete repository test suite. Local Test is targeted validation of the requested intent; remote CI owns broad regression and remains mandatory before a PR is ready" whenever commands.test is absent, which is firstmate's configured state. The new "Never run --all or otherwise walk the entire tests/ suite locally" sentence therefore restates a rule the tool already enforces by default; the config's incremental value is the firstmate-specific guidance (the bin/fm-test-run.sh tests/&lt;subject&gt;.test.sh narrowest-selection form and the optional --changed upper bound). Noting the overlap only, not recommending removal: the intent explicitly approves configuring this policy, and an explicit trusted statement is a deliberate belt-and-braces choice after the PR perf: accelerate local validation with bounded concurrency kunchenguid/firstmate#3644 32.7-minute regression.
⚠️ **Test** - 1 warning
  • ⚠️ bin/fm-test-run.sh:978 - Pre-existing and unrelated to this change: bin/fm-test-run.sh --check-coverage - the guard .github/workflows/ci.yml:61 runs to prove the CI lanes equal the full 206-script inventory - fails on any host whose locale is en_US.UTF-8, printing comm: file 2 is not in sorted order and exiting 1. Every sort in that guard is prefixed LC_ALL=C (lines 965-1053), but the comm calls that consume those files are not, so comm validates C-sorted input against en_US collation. It passes under LC_ALL=C, which is why GitHub runners (C.UTF-8) are green. Fix is mechanical - prefix the comm calls the same way - but it touches bin/fm-test-run.sh, which this change does not, so widening this already-reviewed diff is your scope call rather than mine.
  • Live validation: ✅ go - 7 of 8 scenarios driven live against the product
Scenario Result Live Evidence
A contributor validating this branch locally runs only the tests that exercise its changed behavior and finishes in under a second instead of walking the 206-script suite ✅ pass live time bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh tests/fm-documentation-audiences.test.sh -> FM_TEST_SUMMARY total=2 failed=0, 0.448s wall, vs --list --all = 206 scripts; evidence file 06…
The changed contract test gate-skips instead of failing the local Test step on a host with no ruby installed ✅ pass live bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh on this ruby-less host -> skip: ruby not installed..., gate_skip=true, exit 0; evidence file 01-targeted-run-ruby-absent.txt
Regression reproduction: the pre-fix version of that same test hard-fails the run on this host, proving the skip conversion fixed a real break ✅ pass live Commit 26d647c's version of the script run through the same runner in a scratch copy -> not ok - ruby is required to parse .no-mistakes.yaml for this contract, exit=1; shipped version -> exit=0; evi…
Adversarial: with ruby present, deleting, emptying, or mistyping test.instructions is caught rather than silently accepted ✅ pass live Debian container with ruby 3.3.8, three mutations of .no-mistakes.yaml driven through bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh -> each `not ok - test.instructions must be a non-empty str…
Adversarial: re-pinning commands.test to the 30-minute full suite walk this change exists to prevent is rejected ✅ pass live Same container run with commands.test: &#39;bin/fm-test-run.sh --all&#39; injected -> not ok - commands.test must be absent or empty so Test stays intent-targeted; got: &#34;bin/fm-test-run.sh --all&#34;, exit=1;…
The new docs/configuration.md prose keeps the documentation audience inventory, owner pointers, and local links valid ✅ pass live bin/fm-test-run.sh tests/fm-documentation-audiences.test.sh -> 4 ok assertions including local link resolution, exit 0; evidence file 05-docs-audience-behavior.txt
GitHub CI still owns the complete deterministic suite the local Test step now declines to walk ✅ pass live LC_ALL=C bin/fm-test-run.sh --check-coverage (the exact command .github/workflows/ci.yml:61 runs) -> FM_TEST_COVERAGE ok total=206 across the 9 CI lanes; evidence file 04-ci-owns-full-coverage.txt
A no-mistakes Test step run on a repository whose default branch carries this config receives test.instructions as its trusted live-validation runbook ⏸️ untested no The field is honored only from the default-branch copy of .no-mistakes.yaml, so observing it live requires a no-mistakes pipeline running against a default branch that already carries it. This gate st…
  • bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh --json /tmp/nm-contract-timing.json - targeted run on this ruby-less host; gate_skip=true, exit 0, 17ms
  • bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh against the pre-fix (commit 26d647c) version of the script in a scratch copy - reproduced not ok - ruby is required to parse .no-mistakes.yaml for this contract, exit 1
  • podman run --rm -v &lt;repo-copy&gt;:/repo debian:trixie-slim with ruby 3.3.8 installed, driving bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh across 6 config states: shipped, test.instructions removed, emptied, set to a list, commands.test re-pinned to bin/fm-test-run.sh --all, and restored
  • bin/fm-test-run.sh tests/fm-documentation-audiences.test.sh - docs classification, owner pointers, and local link resolution for the changed docs/configuration.md
  • bin/fm-test-run.sh tests/fm-nm-test-contract.test.sh tests/fm-documentation-audiences.test.sh under time - narrowest correct selection, 0.448s wall
  • bin/fm-test-run.sh --list --all | wc -l (206) vs bin/fm-test-run.sh --list --changed --base 8ff3a80 | wc -l (54) - selection narrowing
  • LC_ALL=C bin/fm-test-run.sh --check-coverage - CI lane coverage proof, FM_TEST_COVERAGE ok total=206
  • LANG=en_US.UTF-8 bin/fm-test-run.sh --check-coverage - reproduced the pre-existing locale-dependent failure
  • no-mistakes --version and symbol/string inspection of ~/.no-mistakes/bin/no-mistakes for trustedTestInstructionsSection and its rendered prompt section header
⚠️ **Document** - 1 info
  • ℹ️ docs/configuration.md:217 - Pre-existing and out of scope for this change. The "Gate defaults (.no-mistakes.yaml)" section - which CONTRIBUTING.md:72 names as the owner of the tracked gate defaults - now enumerates test.evidence.store_in_repo, test.instructions, and the commands.lint pin, but the tracked file also sets document.instructions (.no-mistakes.yaml:15-23, the trusted Document-step placement policy) and disable_project_settings: true (.no-mistakes.yaml:10). The latter is documented at docs/architecture.md:238 under the gate authority boundary, but the owner section carries no pointer to it, and document.instructions is documented nowhere. Both omissions predate this change, which only added test.instructions, so completing the inventory here would widen an already-reviewed diff. Proposed follow-up: extend that section with document.instructions and a one-line pointer to docs/architecture.md for disable_project_settings, so the declared owner is a complete inventory of what the tracked config sets.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Add a trusted test.instructions policy to .no-mistakes.yaml directing the
Test step to select and run only tests relevant to the branch diff via
bin/fm-test-run.sh --changed, and to never walk the entire tests/ suite
locally. GitHub CI already runs the complete deterministic regression
suite on every push, so a redundant local full-suite walk only burns the
local fix-round time budget. commands.test stays absent per the existing
firstmate-coding-guidelines policy.

Extend tests/fm-nm-test-contract.test.sh to parse the real config and
assert test.instructions is set and steers toward the changed-file
selector instead of a full suite walk, and cross-reference the sanctioned
test.instructions surface from firstmate-coding-guidelines.
@brentsec
brentsec merged commit be520ea into main Sep 16, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant