Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
58cb0cc
feat: add local extension points (config/hooks/ lifecycle hooks, bin-…
julius-retzer Jul 13, 2026
48ddb5a
no-mistakes(review): {"summary": "narrow post-merge hook docs; fix va…
julius-retzer Jul 13, 2026
b30f07c
no-mistakes(review): document post-spawn respawn firing; add post-spa…
julius-retzer Jul 13, 2026
31df338
no-mistakes(review): bound hooks with shell watchdog when timeout bin…
julius-retzer Jul 13, 2026
f9619ae
no-mistakes(review): kill hook process group in watchdog; guard sleep…
julius-retzer Jul 13, 2026
34e3a70
no-mistakes(test): symlink fm-hooks-lib.sh in fm-gotmp teardown test …
julius-retzer Jul 13, 2026
55611f8
no-mistakes(test): skip handoff tests when installed tasks-axi is inc…
julius-retzer Jul 13, 2026
605cc3d
no-mistakes(review): detach hook stdio so orphans cannot stall callers
julius-retzer Jul 14, 2026
44f3990
no-mistakes(review): fire hooks last; harden hook stderr tempfile; ho…
julius-retzer Jul 14, 2026
00366e4
no-mistakes(review): defer pr-ready past merge; cap hook stderr relay…
julius-retzer Jul 14, 2026
4f33587
no-mistakes(review): make pr-ready fire once via durable meta marker;…
julius-retzer Jul 14, 2026
a5c0f7d
no-mistakes(review): gate pr-ready marker on installed hook; cap hook…
julius-retzer Jul 14, 2026
2d0e61a
no-mistakes(review): make hooks lib bash 3.2 compatible; guard the floor
julius-retzer Jul 14, 2026
c42d869
no-mistakes(review): hand hook FIFO to cap writer by fd; guard opens
julius-retzer Jul 14, 2026
4ebccc5
no-mistakes(review): symlink hooks lib in old-bin; make pr-ready defe…
julius-retzer Jul 14, 2026
23b93eb
no-mistakes(document): document hook points in script headers and con…
julius-retzer Jul 14, 2026
49d8a77
no-mistakes(document): document extension points in README features a…
julius-retzer Jul 14, 2026
fd1bb12
no-mistakes(test): make fm-fleet-snapshot.sh parse under bash 3.2
julius-retzer Jul 14, 2026
6b3c787
no-mistakes(test): wait for herdr composer before afk e2e self-check
julius-retzer Jul 15, 2026
b8277d1
no-mistakes(document): document bin-local/ in updatefirstmate preserv…
julius-retzer Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .agents/skills/firstmate-coding-guidelines/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ Keep instructions as the authority and discovery layer, but make repeated execut
- 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`.
- Bash 3.2, the interpreter stock macOS ships as `/bin/bash`, is the floor every shipped `bin/` script must parse and run under; a bash 4+ only construct (`coproc`, `read -N`, `declare -A`, `mapfile`, `${var^^}`) is a parse error that kills the script before its first line runs on such a home, and CI and the local suite run bash 5, which parse them happily.
- `tests/fm-bash32.test.sh` guards that floor: it parses the whole shipped `bin/` surface under a real bash 3.2 when the host has one, and scans for those constructs everywhere else.
- Run `bin/fm-lint.sh` before treating a script change as done; it is the single owner of the lint definition (file set, config, and pinned shellcheck version) that CI and the no-mistakes pre-push gate both invoke, and it refuses to run under any other shellcheck version.
- Colocate tests with the existing pattern in `tests/`, name them `<subject>.test.sh`, and extend an existing script rather than inventing a new runner.
- A backend-verification doc (`docs/*-backend.md`) records empirical facts, not assumptions.
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/updatefirstmate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This skill performs that pull for the running main firstmate and every secondmat

The update is **fast-forward only** - the same sanctioned self-write as the fleet sync firstmate already runs.
It never forces, never creates a merge commit, never stashes, and advances a target only on a clean fast-forward; anything dirty, diverged, offline, or on the wrong branch is skipped and reported.
A tracked-files fast-forward leaves the gitignored operational dirs (data/, state/, config/, projects/, .no-mistakes/) untouched, so a secondmate's in-flight work is never disrupted.
A tracked-files fast-forward leaves the gitignored operational dirs (data/, state/, config/, projects/, bin-local/, .no-mistakes/) untouched, so a secondmate's in-flight work is never disrupted.
This touches only the firstmate repo and its own worktrees, never anything under `projects/`.

## What it does
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ config/backend
config/x-mode.env
config/cmux-socket-password
config/wedge-alarm
config/hooks/
bin-local/
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Hard rules, in priority order:
You may maintain this repo's private operational state directly.
Shared tracked material is `AGENTS.md`, `README.md`, `CONTRIBUTING.md`, `.tasks.toml`, `.github/workflows/`, `bin/`, `.agents/skills/`, and public `skills/`.
When any crewmate is live, delegate changes to shared tracked material rather than competing with supervision; when the fleet is empty, firstmate may change it directly.
This repo is a shared template, while `.env`, `data/`, `state/`, `config/`, `projects/`, and `.no-mistakes/` are captain-private and gitignored.
This repo is a shared template, while `.env`, `data/`, `state/`, `config/`, `projects/`, `bin-local/`, and `.no-mistakes/` are captain-private and gitignored.
Ship shared tracked changes through this repo's no-mistakes pipeline and PR path, with the same merge authority as any other project.
Never add an agent name as a commit co-author.

Expand All @@ -62,6 +62,7 @@ README.md public overview and development notes
.claude/skills symlink to .agents/skills for claude compatibility
skills/ standalone public installer-facing skills, committed; not loaded by firstmate
bin/ helper scripts, committed; read each script's header before first use
bin-local/ personal helper scripts; LOCAL, gitignored; never auto-executed - invoked only via config/hooks delegation or explicit instruction (docs/extension-points.md)
.env optional X-mode pairing token; LOCAL, gitignored; presence-gates section 14
config/crew-harness crewmate harness override; LOCAL, gitignored; absent or "default" = same as firstmate. Inherited as the literal file: a concrete primary adapter value also controls a secondmate home's own crewmates (section 4)
config/crew-dispatch.json optional crewmate dispatch profiles; LOCAL, gitignored; firstmate-maintained but human-editable natural-language rules that choose a per-task harness/model/effort profile (section 4). Inherited by secondmate homes
Expand All @@ -71,6 +72,7 @@ config/backend runtime session-provider backend override for new tasks; LOCAL,
config/cmux-socket-password optional cmux control-socket password; LOCAL, gitignored; read fresh on every cmux CLI call and passed through without ever overriding an operator's own ambient CMUX_SOCKET_PASSWORD when absent (docs/cmux-backend.md "Setup")
config/wedge-alarm optional away-mode wedge-alarm active-alert directives; LOCAL, gitignored; absent means auto (macOS Notification Center when available); see docs/wedge-alarm.md
config/x-mode.env generated X-mode watcher cadence; LOCAL, gitignored; source before arming watcher when present
config/hooks/ optional personal lifecycle hook executables; LOCAL, gitignored; run best-effort (absent = no-op, a failing hook never blocks the flow) at the documented lifecycle points; docs/extension-points.md owns the contract
data/ personal fleet records; LOCAL, gitignored as a whole
backlog.md task queue, dependencies, history
captain.md this home's domain-local captain preferences and working style; LOCAL, gitignored, canonical even if harness memory mirrors it, and updated with inspect-then-update
Expand All @@ -85,7 +87,7 @@ state/ volatile runtime signals; gitignored
<id>.status appended by crewmates: "<state>: <note>" wake-event lines, not current-state truth
<id>.turn-ended touched by turn-end hooks
<id>.grok-turnend-token firstmate-owned grok hook registry token for the task; removed by teardown
<id>.meta written by fm-spawn: window=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; kind=secondmate also records home= and projects=; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); fm-pr-check, including through fm-pr-merge, records one canonical pr= and GitHub's pr_head= when available; fm-x-link appends x_request=, x_request_ts=, x_followups=, and optional x_platform=/x_reply_max_chars= for an X-mode-originated task (section 14)
<id>.meta written by fm-spawn: window=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; kind=secondmate also records home= and projects=; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); fm-pr-check, including through fm-pr-merge, records one canonical pr= and GitHub's pr_head= when available, plus pr_ready_hook= only in a home that has a pr-ready hook installed (docs/extension-points.md); fm-x-link appends x_request=, x_request_ts=, x_followups=, and optional x_platform=/x_reply_max_chars= for an X-mode-originated task (section 14)
<id>.check.sh authenticated slow poll; the watcher dispatches validated PR data and the byte-identified X shim through trusted repository scripts, runs registered custom checks from hash-validated private snapshots, and rejects every other state check without execution
<id>.check-trust private content binding created by fm-check-register.sh for an intentional custom check
<id>.pr-poll private validated data sidecar for the byte-static PR merge poll
Expand Down
7 changes: 5 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@ See the [no-mistakes quick start](https://kunchenguid.github.io/no-mistakes/star
`AGENTS.md` is the agent's main job description and names when to load bundled firstmate skills; `CLAUDE.md` is a symlink to it, and `.claude/skills` is a symlink to `.agents/skills`.
- Only shared material is tracked: `AGENTS.md`, `README.md`, `CONTRIBUTING.md`, `.tasks.toml`, `.github/workflows/`, `bin/`, `.agents/skills/`, and `skills/`.
`.agents/skills/` holds agent-loaded skills that assume a live firstmate home and carry `metadata.internal: true` so installers such as [skills.sh](https://skills.sh) hide them from discovery; `skills/` holds standalone, installer-facing public skills with no firstmate dependency (see the README's "Two-tier skill layout").
Everything personal to one captain's fleet (`.env`, `data/`, `state/`, `config/`, `projects/`, `.no-mistakes/`) is gitignored; never commit it.
Everything personal to one captain's fleet (`.env`, `data/`, `state/`, `config/`, `projects/`, `bin-local/`, `.no-mistakes/`) is gitignored; never commit it.
`config/hooks/` lifecycle hooks and `bin-local/` personal scripts are the sanctioned local extension points for personal automation, so it never needs a fork of tracked code; [`docs/extension-points.md`](docs/extension-points.md) owns their contract.
The root `.tasks.toml` is tracked `tasks-axi` config for `data/backlog.md`; compatible `tasks-axi` is the default backend for routine backlog mutations, with the compatibility definition owned by [`docs/configuration.md`](docs/configuration.md) ("Backlog backend").
A local `config/backlog-backend=manual` opt-out forces firstmate's routine backlog updates to hand-editing and stays gitignored; validated secondmate handoffs still delegate through `tasks-axi mv`.
A local `config/backend` file explicitly overrides runtime auto-detection for new task endpoints and stays gitignored; spawn-supported values are `tmux` plus experimental `herdr`, `zellij`, `orca`, and `cmux`, while `codex-app` is documented only in `docs/codex-app-backend.md`.
It does not make `data/` tracked.
- Helper scripts in `bin/` are plain bash.
- Helper scripts in `bin/` are plain bash, and bash 3.2 - the interpreter stock macOS ships as `/bin/bash` - is the floor they must parse and run under.
A bash 4+ only construct (`coproc`, `read -N`, `declare -A`, `mapfile`, `${var^^}`) is not a style nit but a parse error that kills the script on a stock-macOS home before its first line runs, and both CI and the local suite run bash 5, which parses them happily.
`tests/fm-bash32.test.sh` is the guard: it parses the whole shipped `bin/` surface under a real bash 3.2 when the host has one, and scans for those constructs everywhere else.
Each starts with a usage header comment; keep it accurate when you change behavior.
Test scripts and helpers in `tests/` are plain bash too.
`bin/fm-lint.sh` must pass: it is the single owner of the lint definition (the shellcheck file set, config, and pinned shellcheck version), and both CI and the no-mistakes pre-push gate run it, so local and CI can never diverge.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Launching a supported harness inside it instantiates your first mate - and makes
- **Optional secondmates** - opt in to persistent second mates that run from isolated firstmate homes with their own `FM_HOME`, state, projects, and session lock, supervising project clones or a project-less firstmate-repo domain, kept on the primary firstmate version by guarded local fast-forwards and checked for live agent processes at session start.
- **Event-driven, zero-token supervision** - a bash watcher sleeps on the fleet and wakes the first mate only when something needs you; verified primary harnesses also get a turn-end backstop that blocks or follows up on a blind stop when work is under way and supervision is not live.
- **Optional X mode** - opt in with one local `.env` token so firstmate can answer your public `@myfirstmate` mentions, act on normal reversible mention requests through the same lifecycle as chat requests, acknowledge spawned work, and post up to three public-safe completion follow-ups within seven days for genuine milestones and the final outcome without changing non-X behavior; dry-run preview records would-be replies and dismissals locally before go-live.
- **Optional local extension points** - add personal automation without forking tracked code: drop an executable into the gitignored `config/hooks/` to run at a lifecycle moment (`post-spawn`, `pr-ready`, `post-merge`, `post-teardown`), with `bin-local/` for the personal scripts those hooks delegate to; firstmate ships no hooks, so an installation that uses neither sees zero behavior change.
- **Guarded by construction** - the first mate is read-only over your projects except for the guarded paths authorized by [hard rule 1](AGENTS.md#1-identity-and-prime-directives), with fleet sync's safe branch pruning remaining part of the fleet-sync exception; crewmates make every project change behind the configured merge authority.
- **Restart-proof** - all state lives on disk and in the active session backend (tmux by hard default, herdr or cmux when selected or auto-detected, zellij/orca when explicitly selected); kill the session anytime and the next one reconciles, including confirmed-dead secondmate agents, and carries on.

Expand Down Expand Up @@ -184,6 +185,7 @@ Firstmate's skills live in two separate places with different audiences:

- [docs/architecture.md](docs/architecture.md) - how the crew, supervision, worktrees, secondmates, and project modes work.
- [docs/configuration.md](docs/configuration.md) - environment variables, `FM_HOME`, runtime backend selection, optional X mode, the files you set, and harness support.
- [docs/extension-points.md](docs/extension-points.md) - local extension points: `config/hooks/` lifecycle hooks and `bin-local/` personal scripts, both gitignored and optional.
- [docs/wedge-alarm.md](docs/wedge-alarm.md) - configure the active alert for an away-mode escalation delivery that gets stuck.
- [docs/tmux-backend.md](docs/tmux-backend.md) - setup guide for the tmux reference backend: prerequisites, attaching, and watching crew windows.
- [docs/herdr-backend.md](docs/herdr-backend.md) - setup guide for the experimental herdr backend, plus its verification notes and known gaps.
Expand Down
14 changes: 8 additions & 6 deletions bin/fm-fleet-snapshot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -702,9 +702,10 @@ registry_secondmates_json() {
if [ "$bytes" -gt "$max_bytes" ]; then
byte_truncated=true
content=$(printf "%s" "$content" | LC_ALL=C head -c "$max_bytes")
complete=${content%$'\n'*}
if [ "$complete" != "$content" ]; then
content=$complete
# bash 3.2 cannot parse a case statement inside this $(cat <<'BASH') command
# substitution, so drop the partial trailing line with an if instead.
if [ "${content#*$'\n'}" != "$content" ]; then
content=${content%$'\n'*}
else
content=
fi
Expand Down Expand Up @@ -801,9 +802,10 @@ bounded_parent_activities_json() { # <status-file>
byte_truncated=false
if [ "$size" -gt "$max_bytes" ]; then
byte_truncated=true
complete=${content#*$'\n'}
if [ "$complete" != "$content" ]; then
content=$complete
# bash 3.2 cannot parse a case statement inside this $(cat <<'BASH') command
# substitution, so drop the partial leading line with an if instead.
if [ "${content#*$'\n'}" != "$content" ]; then
content=${content#*$'\n'}
else
content=
fi
Expand Down
Loading