Skip to content
Open
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
b8da783
feat(bootstrap): add GitLab provider boundary
Aug 3, 2026
f71bc80
fix(bootstrap): satisfy shellcheck for GitLab CLI
Aug 3, 2026
706b24e
no-mistakes(review): Fail closed across GitLab bootstrap provider bou…
Aug 3, 2026
8620709
no-mistakes(review): Preserve local origin-only forge discovery seman…
Aug 3, 2026
85b49be
no-mistakes(review): Classify relative Git origins as local
Aug 3, 2026
3ad4024
no-mistakes(review): Support optional-user scp forge remotes
Aug 3, 2026
2cbfb95
no-mistakes(document): Document forge-aware bootstrap requirements
Aug 3, 2026
87556ea
no-mistakes(review): Preserve HTTPS forge hosts from SSH rewriting
Aug 3, 2026
e1f2b3b
no-mistakes(document): Document provider-aware bootstrap requirements
Aug 3, 2026
2d303d9
no-mistakes(document): Document GitLab-aware bootstrap requirements
Aug 7, 2026
75215e1
no-mistakes(review): Support private GitHub hosts and skip missing gh…
Aug 14, 2026
d767709
no-mistakes(test): Fix network phase test with GitHub origin fixture
Aug 14, 2026
72d9b5b
feat(bootstrap): add forge provider boundary
CodeFunta Aug 22, 2026
a925ebb
no-mistakes(review): Fail closed on unsupported forge network phases
CodeFunta Aug 22, 2026
a3b48fa
no-mistakes(document): Updated forge bootstrap documentation
CodeFunta Aug 22, 2026
3805dd8
no-mistakes(lint): Remove unused forge network flag
CodeFunta Aug 22, 2026
27ca3b4
no-mistakes: apply CI fixes
CodeFunta Aug 22, 2026
b07be09
no-mistakes: apply CI fixes
CodeFunta Aug 22, 2026
f16995e
no-mistakes: apply CI fixes
CodeFunta Aug 22, 2026
d8b67f3
no-mistakes: apply CI fixes
CodeFunta Aug 22, 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
11 changes: 7 additions & 4 deletions .agents/skills/bootstrap-diagnostics/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: bootstrap-diagnostics
description: >-
Agent-only handling playbook for session-start bootstrap diagnostics.
Use whenever the session-start digest's bootstrap or network-checks section prints an actionable diagnostic line - MISSING, MISSING_MANUAL, BACKEND_INVALID, NEEDS_GH_AUTH, TANGLE, STARTUP_MEMORY_BUDGET, CREW_DISPATCH invalid, FLEET_SYNC, NETWORK_CHECKS, PR_CHECK_MIGRATION, SECONDMATE_SYNC, SECONDMATE_LIVENESS, SECONDMATE_HANDOFF, NUDGE_SECONDMATES, or FMX - or when a standalone bin/fm-bootstrap.sh or bin/fm-startup-network.sh run prints one of those lines.
Use whenever the session-start digest's bootstrap or network-checks section prints an actionable diagnostic line - MISSING, MISSING_MANUAL, BACKEND_INVALID, NEEDS_GH_AUTH, NEEDS_GLAB_AUTH, FORGE_UNSUPPORTED, TANGLE, STARTUP_MEMORY_BUDGET, CREW_DISPATCH invalid, FLEET_SYNC, NETWORK_CHECKS, PR_CHECK_MIGRATION, SECONDMATE_SYNC, SECONDMATE_LIVENESS, SECONDMATE_HANDOFF, NUDGE_SECONDMATES, or FMX - or when a standalone bin/fm-bootstrap.sh or bin/fm-startup-network.sh run prints one of those lines.
A silent bootstrap section, or a BOOTSTRAP_INFO fact, means no skill load.
user-invocable: false
metadata:
Expand All @@ -13,7 +13,7 @@ metadata:

Handle each printed line as below, before dispatching work that depends on it.
The line formats themselves are owned by `bin/fm-bootstrap.sh`'s header; this playbook owns the response to actionable lines.
The inline rules in `AGENTS.md` section 3 still bind: detect, then consent, then install - never install anything the captain has not approved in this session - and no work is dispatched until the tools it needs are present and GitHub auth is good.
The inline rules in `AGENTS.md` section 3 still bind: detect, then consent, then install - never install anything the captain has not approved in this session - and no work is dispatched until the tools it needs are present and authentication is good for each registered supported forge.
When any diagnostic needs captain attention, report the plain consequence and requested action using `AGENTS.md` section 9's captain-facing translation contract; do not name the diagnostic label unless the captain needs to paste it into a command or issue.

- `MISSING: <tool> (install: <command>)` - list the missing tools to the captain with a one-line purpose each plus the printed install commands, wait for consent (one approval may cover the list), then run `bin/fm-bootstrap.sh install <approved tools...>`.
Expand All @@ -24,8 +24,11 @@ When any diagnostic needs captain attention, report the plain consequence and re
For `quota-axi`, bootstrap requires it because firstmate reads its current output directly before resolving every crew-dispatch profile array; without it, report the missing requirement and do not choose around an unexamined candidate.
- `MISSING_MANUAL: <tool> (instructions: <url>)` - tell the captain why the tool is required and give them the printed instructions URL, but do not pass the tool to `bin/fm-bootstrap.sh install`; wait for the captain to complete the manual installation, then rerun session start to confirm the dependency is present.
- `BACKEND_INVALID: <name> (known: <names>)` - the resolved runtime backend has no verified dependency or lifecycle contract, so do not dispatch work until the invalid `FM_BACKEND` or `config/backend` value is corrected to one of the listed backends.
- `NEEDS_GH_AUTH` - ask the captain to run `! gh auth login` (interactive; you cannot run it for them).
This probe now arrives from the deferred network stage, so it is also how an unreachable network shows up: `gh` cannot validate its token offline and reports the same failure. Confirm reachability before asking the captain to re-authenticate a credential that may be fine.
- `NEEDS_GH_AUTH` - ask the captain to run `! gh auth login` (interactive; you cannot run it for them). If a hostname follows the line, preserve it exactly: ask for `! gh auth login --hostname <host>` so GitHub Enterprise credentials are repaired on the failing host.
- `NEEDS_GLAB_AUTH: <host>` - ask the captain to run `! glab auth login --hostname <host>` (interactive; you cannot run it for them).
- `FORGE_UNSUPPORTED: <project> (host: <host|unresolved>)` - the registered project has an origin outside the configured GitHub/GitLab provider boundary, so do not dispatch forge-dependent work for it.
Confirm the remote is correct, or add a self-managed GitLab hostname to `FM_GITLAB_HOSTS`, then rerun session start.
This probe now arrives from the deferred network stage, so it is also how an unreachable network shows up: the registered forge CLI cannot validate its token offline and reports the same failure. Confirm reachability before asking the captain to re-authenticate a credential that may be fine.
- `NETWORK_CHECKS: <what did not complete>; rerun <command>` - the deferred network stage itself could not finish, so the checks it names are simply unknown, not failed.
Rerun the printed command; it is idempotent and re-derives every finding.
A `hit the ...s bound` line means one of those checks is slow or unreachable - most often a remote secondmate host - and the stage stopped rather than letting it wedge; a `lock was no longer held` line means the session that asked for the sweeps no longer owns them, so leave them to the session that does.
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ If the session lock cannot be acquired and verified, report its exact diagnostic
A lock-refused session must not spawn, steer, merge, drain the wake queue, repair supervision, repair a checkout, or perform any other fleet mutation.

The digest itself makes no external-network call and never waits for one.
Every network check a session start owes - GitHub auth, dead-secondmate relaunch, secondmate convergence, pending handoff delivery, and project clone refresh - runs concurrently in a bounded worker owned by `bin/fm-startup-network.sh` and is reported in the digest's own `NETWORK CHECKS` section.
Every network check a session start owes - registered-forge authentication, dead-secondmate relaunch, secondmate convergence, pending handoff delivery, and project clone refresh - runs concurrently in a bounded worker owned by `bin/fm-startup-network.sh` and is reported in the digest's own `NETWORK CHECKS` section.
When that section reports its checks still in progress it names exactly what is unconfirmed; treat none of those as passed until the result lands, either from `bin/fm-startup-network.sh report` or as a `check: startup-network` wake.

1. **Lock** - acquires the per-home session lock first, before anything mutates shared state, then starts the deferred network stage above.
Expand All @@ -177,7 +177,7 @@ When that section reports its checks still in progress it names exactly what is
The closing reminder points back to the emitted supervision block and preserves only the lock, afk, Relay, and read-once reminders.

Bootstrap detects first, asks for consent, and installs only after the captain approves in the current session.
Do not dispatch until the required tools are present and GitHub authentication is good.
Do not dispatch until the required tools are present and authentication is good for each registered supported forge.
Use `gh-axi` for GitHub, `chrome-devtools-axi` for browser work, and `lavish-axi` for structured decisions or reports; consult current help rather than memorizing flags.
A silent bootstrap section needs no action; for any printed actionable diagnostic line, load `bootstrap-diagnostics` and follow its owner procedure.
`BOOTSTRAP_INFO:` lines are completed no-action facts and do not require loading a skill.
Expand Down Expand Up @@ -524,7 +524,7 @@ It performs guarded fast-forward updates of firstmate and registered secondmate

These skills are not captain-invocable; load them only at their precise triggers.

- `bootstrap-diagnostics` - load whenever the session-start digest's bootstrap or network-checks section prints an actionable diagnostic line (`MISSING:`, `MISSING_MANUAL:`, `BACKEND_INVALID:`, `NEEDS_GH_AUTH`, `TANGLE:`, `STARTUP_MEMORY_BUDGET:`, `CREW_DISPATCH: invalid`, `FLEET_SYNC:`, `NETWORK_CHECKS:`, `PR_CHECK_MIGRATION:`, `SECONDMATE_SYNC:`, `SECONDMATE_LIVENESS:`, `SECONDMATE_HANDOFF:`, `NUDGE_SECONDMATES:`, or `FMX:`); silence and `BOOTSTRAP_INFO:` need no load.
- `bootstrap-diagnostics` - load whenever the session-start digest's bootstrap or network-checks section prints an actionable diagnostic line (`MISSING:`, `MISSING_MANUAL:`, `BACKEND_INVALID:`, `NEEDS_GH_AUTH`, `NEEDS_GLAB_AUTH:`, `FORGE_UNSUPPORTED:`, `TANGLE:`, `STARTUP_MEMORY_BUDGET:`, `CREW_DISPATCH: invalid`, `FLEET_SYNC:`, `NETWORK_CHECKS:`, `PR_CHECK_MIGRATION:`, `SECONDMATE_SYNC:`, `SECONDMATE_LIVENESS:`, `SECONDMATE_HANDOFF:`, `NUDGE_SECONDMATES:`, or `FMX:`); silence and `BOOTSTRAP_INFO:` need no load.
- `diagnostic-reasoning` - load before scoping a reported bug and before acting on a diagnostic report.
- `ask-user-authority` - load before deciding any ask-user finding.
- `quota-array-dispatch` - load before choosing among a matched crew-dispatch profile array from current quota-axi default TOON.
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ Full detail on every feature lives in [docs/architecture.md](docs/architecture.m
### Requirements

- A verified primary agent harness: Claude Code, Grok, Pi, `pi-signed`, Codex, OpenCode, or Cursor Agent CLI.
- Git and the GitHub CLI, authenticated through `gh auth login`.
- Git, plus the authenticated forge CLI required by each registered GitHub or GitLab project; GitHub projects use `gh auth login`.
- The CLI and dependencies for your selected runtime backend; tmux is the reference default.

The first mate detects and offers to install supported missing tools after you approve.
Backend-specific setup is linked in [Documentation](#documentation).
Forge and backend-specific requirements are owned by [docs/configuration.md](docs/configuration.md#toolchain).

### Recommended harnesses

Expand All @@ -79,7 +79,6 @@ Launch it with `--trust`, or none of its project hooks load; it also has no turn
### Install and launch

```sh
gh auth login
git clone https://github.com/kunchenguid/firstmate
cd firstmate
```
Expand Down
87 changes: 70 additions & 17 deletions bin/fm-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# exits 0.
# Silent = all good.
# Lines: "MISSING: <tool> (install: <command>)",
# "MISSING_MANUAL: <tool> (instructions: <url>)", "NEEDS_GH_AUTH",
# "NEEDS_GH_AUTH[: <host>]", "NEEDS_GLAB_AUTH[: <host>]",
# "BACKEND_INVALID: <name> (known: <names>)",
# "STARTUP_MEMORY_BUDGET: invalid config/startup-memory-budget - <reason>",
# "CREW_DISPATCH: invalid config/crew-dispatch.json - <reason>",
Expand Down Expand Up @@ -98,13 +98,11 @@
# before. Unrecognized values fall back here on purpose: a typo
# must never silently skip a safety sweep.
# skip - every LOCAL step, and none of the network ones. Skips
# `gh auth status`, secondmate_liveness_sweep, secondmate_sync,
# provider authentication, secondmate_liveness_sweep, secondmate_sync,
# secondmate_handoff_resume, and fleet_sync.
# only - ONLY those network steps and nothing else. No tool detection,
# no version floors, no tangle check, no PR-check migration, no
# x_mode_setup: those already ran on the local pass.
# FM_BOOTSTRAP_DETECT_ONLY composes with it unchanged, so `only` plus
# detect-only is the read-only `gh auth status` probe on its own.
# bin/fm-startup-network.sh owns the deferral: it runs the `only` phase
# in a detached bounded worker and publishes the result. This file stays
# the single owner of every sweep, and the split changes only WHEN each
Expand All @@ -130,6 +128,8 @@ PROJECTS="${FM_PROJECTS_OVERRIDE:-$FM_HOME/projects}"
CONFIG="${FM_CONFIG_OVERRIDE:-$FM_HOME/config}"
STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
DATA="${FM_DATA_OVERRIDE:-$FM_HOME/data}"
# shellcheck source=bin/fm-forge-lib.sh disable=SC1091
. "$SCRIPT_DIR/fm-forge-lib.sh"
# shellcheck source=bin/fm-tasks-axi-lib.sh disable=SC1091
. "$SCRIPT_DIR/fm-tasks-axi-lib.sh"
# shellcheck source=bin/fm-quota-axi-lib.sh disable=SC1091
Expand Down Expand Up @@ -167,6 +167,25 @@ esac
local_phase() { [ "$FM_BOOTSTRAP_NETWORK_PHASE" != only ]; }
network_phase() { [ "$FM_BOOTSTRAP_NETWORK_PHASE" != skip ]; }

FORGE_UNSUPPORTED_REPORTED=0
forge_report_unsupported() {
local unsupported=0
while IFS=$'\t' read -r _proj_id _proj_provider _proj_host; do
[ -n "${_proj_provider:-}" ] || continue
if [ "$_proj_provider" = unknown ]; then
unsupported=1
if [ "$FORGE_UNSUPPORTED_REPORTED" -eq 0 ]; then
echo "FORGE_UNSUPPORTED: $_proj_id (host: ${_proj_host:-unresolved})"
fi
fi
done <<< "$FORGE_PROJECTS"
if [ "$unsupported" -eq 1 ]; then
FORGE_UNSUPPORTED_REPORTED=1
return 1
fi
return 0
}

network_mutation_authorized() {
local expected=${FM_BOOTSTRAP_NETWORK_LOCK_PID:-} current
[ -n "$expected" ] || return 0
Expand Down Expand Up @@ -752,7 +771,7 @@ secondmate_handoff_detect() {

install_cmd() {
case "$1" in
tmux|node|git|gh|curl|jq|orca|zellij) echo "brew install $1 # or the platform's package manager" ;;
tmux|node|git|gh|glab|curl|jq|orca|zellij) echo "brew install $1 # or the platform's package manager" ;;
cmux) echo "brew install --cask cmux # or see https://cmux.com" ;;
treehouse) echo "curl -fsSL https://kunchenguid.github.io/treehouse/install.sh | sh" ;;
no-mistakes) echo "curl -fsSL https://raw.githubusercontent.com/kunchenguid/no-mistakes/main/docs/install.sh | sh" ;;
Expand All @@ -779,12 +798,24 @@ missing_tool_diagnostic() {
echo "MISSING: $tool (install: $(install_cmd "$tool"))"
}

# Required-tool detection follows the RESOLVED backend, not a one-size default:
# a universal toolchain every home needs plus the backend-specific delta owned by
# fm_backend_required_tools (bin/fm-backend.sh). So a herdr/zellij/cmux home is
# never told tmux is missing, and only orca drops treehouse. A backend value with
# no verified dependency set is reported before the universal checks continue.
COMMON_TOOLS="node git gh no-mistakes gh-axi chrome-devtools-axi lavish-axi tasks-axi quota-axi"
# Required-tool detection combines the universal toolchain, the provider tools
# derived from registered project origins, and the resolved backend's delta from
# fm_backend_required_tools (bin/fm-backend.sh). Thus GitLab-only homes do not
# require GitHub tooling, inactive backends do not add tools, and an invalid
# backend is reported before the universal checks continue.
COMMON_TOOLS="node git no-mistakes chrome-devtools-axi lavish-axi tasks-axi quota-axi"
FORGE_PROJECTS=$(fm_forge_scan_registered_projects "$PROJECTS")
FORGE_PROVIDERS_SEEN=$(printf '%s\n' "$FORGE_PROJECTS" | awk -F '\t' 'NF >= 2 {print $2}' | sort -u)
while IFS=$'\t' read -r _proj_id _proj_provider _proj_host; do
[ -n "${_proj_provider:-}" ] || continue
while read -r _forge_tool; do
[ -n "${_forge_tool:-}" ] || continue
case " $COMMON_TOOLS " in
*" $_forge_tool "*) ;;
*) COMMON_TOOLS="$COMMON_TOOLS $_forge_tool" ;;
esac
done < <(fm_forge_provider_tools "$_proj_provider" 2>/dev/null || true)
done <<< "$FORGE_PROJECTS"
BACKEND=$(fm_backend_name)
BACKEND_VALID=1
if ! BACKEND_TOOLS=$(fm_backend_required_tools "$BACKEND"); then
Expand Down Expand Up @@ -1146,7 +1177,8 @@ detect_local_tools() {
if command -v no-mistakes >/dev/null 2>&1 && ! tool_version_at_least no-mistakes "$NO_MISTAKES_MIN"; then
echo "MISSING: no-mistakes (install: $(install_cmd no-mistakes))"
fi
if command -v gh-axi >/dev/null 2>&1 && ! tool_version_at_least gh-axi "$GH_AXI_MIN"; then
if printf '%s\n' "$FORGE_PROVIDERS_SEEN" | grep -qx github \
&& command -v gh-axi >/dev/null 2>&1 && ! tool_version_at_least gh-axi "$GH_AXI_MIN"; then
echo "MISSING: gh-axi (install: $(install_cmd gh-axi))"
fi
if command -v lavish-axi >/dev/null 2>&1 && ! tool_version_at_least lavish-axi "$LAVISH_AXI_MIN"; then
Expand All @@ -1158,6 +1190,7 @@ detect_local_tools() {
if command -v tasks-axi >/dev/null 2>&1 && ! fm_tasks_axi_compatible; then
echo "MISSING: tasks-axi (install: $(install_cmd tasks-axi))"
fi
forge_report_unsupported || true
}

detect_local_config() {
Expand Down Expand Up @@ -1195,8 +1228,7 @@ detect_local_config() {

# The order below is the order the diagnostics have always printed in, so a
# `skip` run is the same output with the network lines removed rather than a
# reshuffle. `gh auth status` sits between the two local blocks because that is
# where it has always been.
# reshuffle. Registered-forge authentication sits between the two local blocks.
# Each network owner below is bracketed by an elapsed-time record, so a deferred
# stage that ran long can be attributed to the phase that spent the time.
# fm-timing-lib.sh discards the record unless the caller asked for timings, and
Expand All @@ -1208,8 +1240,26 @@ detect_local_config() {
local_phase && detect_local_tools
if network_phase; then
__fm_timing_stamp=$(fm_timing_now_ms)
gh auth status >/dev/null 2>&1 || echo "NEEDS_GH_AUTH"
fm_timing_record phase gh-auth "$__fm_timing_stamp"
# Authentication is checked for each registered supported forge. An unknown
# origin is reported by the local pass, but must not suppress authentication
# remediation for an unrelated supported project in the same home.
# The deferred network phase repeats these provider-aware checks rather than a
# global GitHub probe that mislabels GitLab-only and local homes.
FORGE_AUTH_CHECKED=""
while IFS=$'\t' read -r _proj_id _proj_provider _proj_host; do
[ -n "${_proj_provider:-}" ] || continue
case "$_proj_provider" in
github|gitlab) : ;;
*) continue ;;
esac
_pair="$_proj_provider:${_proj_host:-}"
case " $FORGE_AUTH_CHECKED " in
*" $_pair "*) continue ;;
esac
FORGE_AUTH_CHECKED="$FORGE_AUTH_CHECKED $_pair"
fm_forge_check_auth "$_proj_provider" "${_proj_host:-}"
done <<< "$FORGE_PROJECTS"
fm_timing_record phase forge-auth "$__fm_timing_stamp"
fi
local_phase && detect_local_config

Expand All @@ -1235,7 +1285,10 @@ if [ "${FM_BOOTSTRAP_DETECT_ONLY:-0}" != 1 ]; then
fi
# x_mode_setup writes local Relay artifacts only and never leaves the machine.
local_phase && x_mode_setup
if network_phase && network_sweep_authorized 'project clone refresh'; then
# Never hand an unclassified registered origin to the clone-refresh worker:
# its remote transport is not a supported forge boundary, so fail closed for
# that project while leaving unrelated secondmate supervision sweeps intact.
if network_phase && forge_report_unsupported && network_sweep_authorized 'project clone refresh'; then
__fm_timing_stamp=$(fm_timing_now_ms)
fleet_sync
fm_timing_record phase fleet-sync "$__fm_timing_stamp"
Expand Down
Loading
Loading