Skip to content

feat(bin): make ship-branch prefix configurable per project - #2648

Open
wesleymatosdev wants to merge 3 commits into
kunchenguid:mainfrom
wesleymatosdev:fm/branch-prefix-config
Open

feat(bin): make ship-branch prefix configurable per project#2648
wesleymatosdev wants to merge 3 commits into
kunchenguid:mainfrom
wesleymatosdev:fm/branch-prefix-config

Conversation

@wesleymatosdev

Copy link
Copy Markdown

Intent

Make the ship-branch prefix configurable per project instead of a global switch, because bin/fm-brief.sh hardcodes every generated crewmate branch to fm/ across ~5 sites (the branch-creation command, the never-push rule text, the definition-of-done text, and the status-message text), which leaks that firstmate (an agent-orchestration tool) produced the branch/PR - fine for firstmate own repo but unwanted noise for a third-party public OSS repo that does not use this tooling (e.g. screenpipe/screenpipe, guilhermerodz/input-otp), where the captain has had to manually rename a branch after the fact.

Design requirements:

  • The natural home for the override is the project registry entry in data/projects.md, alongside the existing [no-mistakes]/[direct-PR]/[local-only] delivery-posture annotations. bin/fm-project-mode.sh is the single owner of that registry line format and its parser, so extend it there rather than duplicating parsing elsewhere.
  • Default behavior (no override configured) must remain byte-identical to today: fm/, so every existing firstmate installation is unaffected - this is additive, not a breaking change.
  • An empty/absent prefix override must still produce a valid, sensible branch name from the task id alone (a bare , never a leading slash or an empty branch name).
  • Update every one of the ~5 hardcoded fm/$ID sites in bin/fm-brief.sh to render a single resolved branch name consistently, so the branch-creation command, the never-push rule text, the definition-of-done text, and the status-message text can never disagree with each other or with the branch actually created.
  • Update bin/fm-brief.sh header/help text to document the new flag/behavior, and bin/fm-project-mode.sh header to document the new registry field, in each case as the sole owner of its own contract.
  • fm-brief.sh itself must not read data/projects.md directly for this value, mirroring the existing --mode architecture (a dedicated test already pins that fm-brief.sh no longer reads the registry for delivery mode): firstmate resolves the per-project override at task intake, the same way it resolves delivery mode, and passes it to fm-brief.sh as an explicit --branch-prefix flag; bin/fm-project-mode.sh exposes a --branch-prefix query for that resolution.
  • Add or extend tests colocated with the existing pattern proving: (a) an unregistered/default project still gets fm/, (b) a project with the override gets the configured prefix instead, (c) the resolved branch name is consistent across every generated section of the brief.

Explicitly out of scope: do not touch .agents/skills/project-management/SKILL.md (a separate, already-open PR #2510 covers an unrelated change there, and these must ship as two fully independent PRs); do not retroactively rename branches on already-open PRs such as screenpipe-5622 (a separate, later decision).

This is a contribution to kunchenguid/firstmate, a repo the captain does not own/cannot push to directly; the gate is configured with fork https://github.com/wesleymatosdev/firstmate so branches push through the fork while the PR opens against origin.

What Changed

  • bin/fm-project-mode.sh gains a --branch-prefix <project-name> query and a new order-independent branch=<prefix> token in the data/projects.md registry bracket annotation, defaulting to fm/ when unset, unregistered, or the registry is absent.
  • bin/fm-brief.sh accepts a new --branch-prefix flag (rejected on --scout/--secondmate, validated against spaces and a leading -) and resolves a single BRANCH value used consistently across the branch-creation command, the never-push rule text, the definition-of-done text, and the status-message text for both direct-PR and local-only modes; it does not read data/projects.md itself.
  • bin/fm-merge-local.sh resolves the project's registered branch prefix via fm-project-mode.sh --branch-prefix (falling back to fm/ on failure) instead of hardcoding fm/$ID when locating the crewmate's branch to fast-forward.
  • AGENTS.md and docs/architecture.md document the new intake step and the registry/flag contract; tests in tests/fm-brief.test.sh and tests/fm-task-delivery.test.sh cover the default fm/<task-id> behavior, a configured prefix override, and consistency of the resolved branch name across all generated brief sections.

Risk Assessment

✅ Low: Both round-1 findings are correctly resolved: AGENTS.md section 7 now instructs firstmate to resolve the registered branch prefix via fm-project-mode.sh --branch-prefix <project> and pass it explicitly to fm-brief.sh, and fm-merge-local.sh now resolves the same value (via the identical PROJ_NAME=basename(PROJ) pattern already used in fm-spawn.sh) instead of hardcoding fm/$ID, with a safe fm/ fallback; traced the empty-prefix, unregistered-project, and missing-registry paths through fm-project-mode.sh and confirmed no wrong-branch or crash case remains reachable.

Testing

Both targeted test suites covering the new --branch-prefix flag and registry field pass in full, and a manual end-to-end run confirms bin/fm-merge-local.sh's new registry-driven prefix resolution (untested by any automated suite) works correctly with a non-default override; no regressions or missing behavior found, and no findings to report.

Evidence: fm-brief.sh branch-prefix tests
ok - fm-brief.sh: --branch-prefix omitted defaults every ship mode to fm/<task-id>
ok - fm-brief.sh: a --branch-prefix override renders identically across every generated section
ok - fm-brief.sh: an empty --branch-prefix override resolves to a bare <task-id> branch
ok - fm-brief.sh: --branch-prefix is refused on scout and secondmate scaffolds
ok - fm-brief.sh: --branch-prefix value is validated against embedded spaces and a leading dash
Evidence: fm-project-mode.sh --branch-prefix resolution test
ok - fm-project-mode: --branch-prefix resolves order-independently and defaults to the legacy fm/ prefix
# all fm-task-delivery tests passed
Evidence: manual end-to-end fm-merge-local.sh with a configured branch=fix/ override
merged fix/task123 into local main (bad5aca -> 9669e66) in <tmp>/projects/mythirdparty

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • 🚨 AGENTS.md:281 - Required design constraint from the intent: "firstmate resolves the per-project override at task intake, the same way it resolves delivery mode, and passes it to fm-brief.sh as an explicit --branch-prefix flag." AGENTS.md section 7 is the actual intake contract that instructs firstmate to do this for mode/yolo today (line 281: "Resolve every ship task's concrete delivery mode and yolo posture at intake, and pass both explicitly to the brief..."), but this diff does not touch AGENTS.md at all (confirmed: only bin/fm-brief.sh, bin/fm-project-mode.sh, and the two test files changed). Nothing in the codebase actually calls fm-project-mode.sh --branch-prefix &lt;project&gt; and forwards the result into fm-brief.sh ... --branch-prefix ... as part of normal task intake (grepped every bin/*.sh caller of fm-brief.sh; none pass --branch-prefix). As delivered, the new flag is fully mechanical and tested at the CLI layer, but nothing in the actual firstmate intake flow ever invokes it, so real ship tasks will keep getting the default fm/ prefix even for a project that registers a branch= override — the feature is inert in practice. Note .agents/skills/project-management/SKILL.md is explicitly out of scope per the intent, but AGENTS.md (which the intent's own language points to via "the same way it resolves delivery mode") is not excluded and appears to be the missing piece.
  • 🚨 bin/fm-merge-local.sh:44 - Concrete reachable bug: bin/fm-brief.sh's local-only mode now renders the crewmate's branch as $BRANCH ($BRANCH_PREFIX$ID), which can be any configured prefix (tests explicitly cover local-only + a custom prefix, e.g. contrib/$id). But bin/fm-merge-local.sh — the only script that performs the local-only merge, gated to mode=local-only at line 24 — still hardcodes BRANCH=&#34;fm/$ID&#34; at line 44 and does git rev-parse --verify refs/heads/$BRANCH immediately after. For a project with a registered branch= override running a local-only task, the crewmate correctly creates and commits to e.g. contrib/&lt;id&gt; (per the updated brief), but fm-merge-local.sh will look for fm/&lt;id&gt;, find nothing, and hard-error with "branch fm/<id> does not exist in <PROJ>", blocking the task's only merge path. state/<id>.meta records mode= but has no field for the resolved branch/prefix, so fm-merge-local.sh has no way to recover the actual branch name (unlike bin/fm-review-diff.sh, which falls back to the worktree's current branch via symbolic-ref when fm/$ID doesn't exist — fm-merge-local.sh has no equivalent fallback). This is reachable today via a direct fm-brief.sh --branch-prefix ... --mode local-only invocation, independent of whether AGENTS.md intake wiring exists. Recommend resolving this at the same shared boundary fm-brief.sh already uses (bin/fm-project-mode.sh --branch-prefix &lt;project&gt;) rather than re-deriving it ad hoc, or recording the resolved branch into state/<id>.meta at spawn/brief time so every consumer (fm-merge-local.sh included) reads one source of truth instead of assuming fm/$ID.

🔧 Fix: Wire branch-prefix intake in AGENTS.md; fix fm-merge-local.sh hardcoded fm/ prefix
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-brief.test.sh
  • bash tests/fm-task-delivery.test.sh
  • manual: registered a project with [local-only branch=fix/] in a scratch data/projects.md, created a fix/task123 branch ahead of main in a scratch project repo, wrote a matching state/task123.meta, and ran bin/fm-merge-local.sh task123 to confirm it resolves the registry's branch prefix (not the hardcoded fm/) and fast-forwards correctly
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

fm-brief.sh hardcoded every generated ship branch to fm/<task-id>, which
leaks that firstmate produced the branch/PR - unwanted for a third-party
public repo that does not use this tooling.

Add an optional --branch-prefix flag to fm-brief.sh (default "fm/", so
existing installs are unaffected) and teach fm-project-mode.sh - the
registry's single-owner parser - to resolve a project's optional
"branch=<prefix>" data/projects.md annotation via a new --branch-prefix
query, order-independent with the existing mode/+yolo tokens. Firstmate
resolves the override at task intake and passes it explicitly, mirroring
how --mode already works; fm-brief.sh itself never reads the registry.

An empty override resolves to a bare "<task-id>" branch rather than a
leading slash. All five previously hardcoded fm/$ID sites (branch
creation, never-push rule text, definition-of-done text, and the status
message) now render the resolved prefix consistently.
Copilot AI lite review requested due to automatic review settings August 19, 2026 21:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Captain, this PR makes the “ship branch” naming prefix configurable per project (via the data/projects.md registry parsed by bin/fm-project-mode.sh), so generated briefs and local-only merge tooling can use a non-fm/ prefix when desired while keeping the default behavior byte-identical (fm/<task-id>).

Changes:

  • Adds branch=<prefix> as an order-independent registry token and exposes bin/fm-project-mode.sh --branch-prefix <project> for mechanical resolution (defaulting to fm/, with branch= meaning “no prefix”).
  • Adds --branch-prefix to bin/fm-brief.sh and uses one resolved $BRANCH consistently across all brief sections that mention the branch.
  • Updates bin/fm-merge-local.sh to resolve the project’s branch prefix via fm-project-mode.sh --branch-prefix instead of hardcoding fm/$ID, and extends tests/docs accordingly.

Reviewed changes

Copilot reviewed 4 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
bin/fm-project-mode.sh Extends registry parsing to support branch=<prefix> and a --branch-prefix query while preserving legacy <mode> <yolo> output.
bin/fm-brief.sh Adds --branch-prefix and centralizes branch name rendering via a single $BRANCH variable.
bin/fm-merge-local.sh Resolves branch prefix per project before locating the crewmate branch to fast-forward into the default branch.
tests/fm-task-delivery.test.sh Adds coverage for fm-project-mode.sh --branch-prefix defaults, ordering, and empty override behavior.
tests/fm-brief.test.sh Adds coverage that the resolved branch name is consistent across all generated brief sections and modes.
docs/architecture.md Documents the registry/flag contract and intake behavior for branch-prefix resolution.
AGENTS.md Updates the intake contract to resolve and pass --branch-prefix explicitly (default fm/ needs no flag).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

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.

2 participants