Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion .agents/skills/bearings/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ It never tears down a task, merges a PR, dispatches new work, or mutates any tas
This is the required artifact; it lives in gitignored `data/`.
If today's file already exists, delete it first, then create a new file from scratch.
- The chat response is the concise four-section digest defined by the contract below: materially shorter than the report file, complete as a current snapshot, internally consistent with the file, and linked to that file for the full picture.
- For a richer review surface, optionally offer a Lavish board with `lavish-axi` when the report has enough structure to deserve one, but the markdown file is the required artifact and the four-section chat digest is the required minimum.
- For a richer review surface, optionally offer a Lavish board with `bin/fm-lavish.sh` when the report has enough structure to deserve one, but the markdown file is the required artifact and the four-section chat digest is the required minimum.

## Chat-response contract

Expand Down
7 changes: 6 additions & 1 deletion .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 section prints an actionable diagnostic line - MISSING, MISSING_MANUAL, BACKEND_INVALID, ROLE_INVALID, ROLE_OVERLAY_MISSING, NEEDS_GH_AUTH, TANGLE, SELF_DRIFT, CREW_DISPATCH invalid, CURRENCY_BASE, BACKLOG_STALE, BACKLOG_UNREADABLE, FLEET_SYNC, PR_CHECK_MIGRATION, SECONDMATE_SYNC, SECONDMATE_LIVENESS, NUDGE_SECONDMATES, AXI_SUITE_UPDATED, AXI_SUITE_REVIEW, AXI_SUITE_STUCK, FIRSTMATE_UPDATE_AVAILABLE, FIRSTMATE_UPDATE_STUCK, FORK_SYNC, FORK_SYNC_STUCK, WATCHER_UNIT, FREQUENCY_MONITOR_UNIT, or FMX - or when a standalone bin/fm-bootstrap.sh run prints one of those lines.
Use whenever the session-start digest's bootstrap section prints an actionable diagnostic line - MISSING, MISSING_MANUAL, BACKEND_INVALID, ROLE_INVALID, ROLE_OVERLAY_MISSING, NEEDS_GH_AUTH, TANGLE, SELF_DRIFT, CREW_DISPATCH invalid, CURRENCY_BASE, LAVISH_ACCESS, BACKLOG_STALE, BACKLOG_UNREADABLE, FLEET_SYNC, PR_CHECK_MIGRATION, SECONDMATE_SYNC, SECONDMATE_LIVENESS, NUDGE_SECONDMATES, AXI_SUITE_UPDATED, AXI_SUITE_REVIEW, AXI_SUITE_STUCK, FIRSTMATE_UPDATE_AVAILABLE, FIRSTMATE_UPDATE_STUCK, FORK_SYNC, FORK_SYNC_STUCK, WATCHER_UNIT, FREQUENCY_MONITOR_UNIT, or FMX - or when a standalone bin/fm-bootstrap.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 Down Expand Up @@ -71,6 +71,11 @@ When any diagnostic needs captain attention, report the plain consequence and re
- `CURRENCY_BASE: config/<file> is unusable - <reason>; <remediation>` - the home configured a comparison base for one of the two upstream checks, but the file cannot be used, so that check refuses rather than silently comparing against the wrong source.
Fix the named file to one non-empty git URL or absolute path, or remove it to fall back to the canonical upstream template; `docs/configuration.md` "Upstream firstmate and curated-fork checks" owns which check reads which file and why they are separate.
Until it is fixed, treat that check's currency signal as absent rather than as evidence the deployment is current.
- `LAVISH_ACCESS: <N> open review board link(s) still point at this machine only ...` - this vessel has a tailnet, so those boards could be reachable, but the links already handed over open nothing on the captain's own devices.
Nothing is broken and nothing is at risk; the boards work locally, and the failure is silent precisely because they look correct here.
Reopen each affected board with `bin/fm-lavish.sh <html-file>`, which moves it onto this vessel's tailnet address and prints the new link, then give the captain that new link if he was already sent the old one.
Do not hand-set the `LAVISH_AXI_*` variables to silence this: a server that is already running keeps emitting the hostname it was started with, so only reopening through the wrapper actually changes the link.
The check is detect-only, stays silent on a host with no tailnet, and repeats each session start until the affected sessions are reopened or ended.
- `BACKLOG_STALE: task <id> has <fault>; fix: <command>` - a current durable backlog record has a mechanically stale dependency edge.
Inspect the named record and apply the exact printed fix, and add the intended existing blocker afterward only when the dangling id was a typo.
The fix is a `tasks-axi unblock` command only when that command would actually run; when the clause instead names a hand edit to `data/backlog.md` - because `config/backlog-backend` is `manual`, or because the line says no tasks-axi fix is available since tasks-axi cannot resolve that record - delete the quoted `blocked-by:` token from the named record by hand and do not substitute a `tasks-axi` command for it.
Expand Down
4 changes: 4 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/bin/fm-cd-pretool-check.sh --claude"
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/bin/fm-lavish-pretool-check.sh --claude"
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/bin/fm-continuity-pretool-check.sh"
Expand Down
5 changes: 5 additions & 0 deletions .codex/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
"command": "bash -lc 'payload=$(cat 2>/dev/null || true); [ -n \"$payload\" ] || exit 0; command -v jq >/dev/null 2>&1 || exit 0; root=$(pwd -P) || exit 0; [ -x \"$root/bin/fm-cd-pretool-check.sh\" ] || exit 0; [ -f \"$root/AGENTS.md\" ] || exit 0; [ -f \"$root/.codex/hooks.json\" ] || exit 0; jq -e \"any(.hooks.PreToolUse[]?.hooks[]?.command?; type == \\\"string\\\" and contains(\\\"fm-cd-pretool-check.sh\\\"))\" \"$root/.codex/hooks.json\" >/dev/null 2>&1 || exit 0; printf \"%s\" \"$payload\" | \"$root/bin/fm-cd-pretool-check.sh\"'",
"timeout": 10
},
{
"type": "command",
"command": "bash -lc 'payload=$(cat 2>/dev/null || true); [ -n \"$payload\" ] || exit 0; command -v jq >/dev/null 2>&1 || exit 0; root=$(pwd -P) || exit 0; [ -x \"$root/bin/fm-lavish-pretool-check.sh\" ] || exit 0; [ -f \"$root/AGENTS.md\" ] || exit 0; [ -f \"$root/.codex/hooks.json\" ] || exit 0; jq -e \"any(.hooks.PreToolUse[]?.hooks[]?.command?; type == \\\"string\\\" and contains(\\\"fm-lavish-pretool-check.sh\\\"))\" \"$root/.codex/hooks.json\" >/dev/null 2>&1 || exit 0; printf \"%s\" \"$payload\" | \"$root/bin/fm-lavish-pretool-check.sh\"'",
"timeout": 10
},
{
"type": "command",
"command": "bash -lc 'command -v graphify >/dev/null 2>&1 || exit 0; graphify hook-check'",
Expand Down
16 changes: 16 additions & 0 deletions .grok/hooks/fm-primary-lavish-check.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -lc '[ -n \"${GROK_WORKSPACE_ROOT:-}\" ] || exit 0; exec \"${GROK_WORKSPACE_ROOT:-}/bin/fm-lavish-pretool-check.sh\"'",
"timeout": 10
}
]
}
]
}
}
65 changes: 65 additions & 0 deletions .opencode/plugins/fm-primary-lavish-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { realpathSync } from "node:fs";
import { resolve } from "node:path";
import { spawn } from "node:child_process";

// PreToolUse seatbelt for OpenCode: deny a bare `lavish-axi` before the agent's
// bash tool runs it and hands the captain a http://127.0.0.1 link that opens
// nothing on his own devices (see bin/fm-lavish-pretool-check.sh and
// docs/lavish-access.md). This mirrors fm-primary-cd-check.js, calling the
// lavish-guard owner instead of the cd-guard one. tool.execute.before can block
// by throwing (verified 2026-07-09 against OpenCode 1.17.15 for the watcher-arm
// plugin; the same mechanism carries this guard). Unlike the cd-guard, the
// owner script is deliberately NOT primary-checkout-only: it fires wherever
// bin/fm-lavish.sh exists, because boards get opened from crew worktrees too.

function runProcess(command, args) {
return new Promise((resolvePromise) => {
const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => {
stdout += chunk.toString();
});
child.stderr.on("data", (chunk) => {
stderr += chunk.toString();
});
child.on("error", () => resolvePromise({ code: 0, stdout: "", stderr: "" }));
child.on("close", (code) => resolvePromise({ code: code ?? 0, stdout, stderr }));
});
}

async function resolveRoot(anchor) {
if (!anchor) return "";
const result = await runProcess("git", ["-C", anchor, "rev-parse", "--show-toplevel"]);
const root = result.stdout.trim();
if (result.code === 0 && root) return root;
try {
return realpathSync(anchor);
} catch {
return resolve(anchor);
}
}

export const FmPrimaryLavishCheck = async ({ directory, worktree }) => {
const root = worktree ? (() => {
try {
return realpathSync(worktree);
} catch {
return resolve(worktree);
}
})() : await resolveRoot(directory);

return {
"tool.execute.before": async (input, output) => {
if (!root || input?.tool !== "bash") return;
const command = output?.args?.command;
if (!command || typeof command !== "string") return;

const result = await runProcess(`${root}/bin/fm-lavish-pretool-check.sh`, ["--command", command]);
if (result.code !== 2) return;

const reason = result.stderr.trim() || "denied by the lavish-guard PreToolUse seatbelt";
throw new Error(reason);
},
};
};
23 changes: 19 additions & 4 deletions .pi/extensions/fm-primary-turnend-guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,19 @@ function runGuard(): Promise<{ code: number; stderr: string }> {
}

// PreToolUse seatbelts (bin/fm-arm-pretool-check.sh, docs/arm-pretool-check.md;
// bin/fm-cd-pretool-check.sh, docs/cd-guard.md). Both piggyback on this same
// extension file rather than separate ones so no extra Pi -e flag is needed at
// launch - the primary already loads this file for the turn-end guard, and
// bin/fm-cd-pretool-check.sh, docs/cd-guard.md; bin/fm-lavish-pretool-check.sh,
// docs/lavish-access.md). All three piggyback on this same extension file
// rather than separate ones so no extra Pi -e flag is needed at launch - the
// primary already loads this file for the turn-end guard, and
// pi.on("tool_call", ...) can block (verified 2026-07-09 against pi 0.80.5:
// returning {block: true} prevents the bash command from running). Each owner
// script owns its own decision and is inert outside the real primary checkout.
// script owns its own decision and its own scope: the first two are inert
// outside the real primary checkout, while the lavish guard's own scope is any
// checkout carrying bin/fm-lavish.sh. What THIS file delivers is narrower than
// that scope: fm-spawn launches a Pi crewmate or scout with a generated
// per-task extension carrying only a turn_end handler, so this file is loaded
// by the primary session and by a Pi secondmate home, not by a Pi crew
// worktree (docs/lavish-access.md records that limit).
function runChecker(script: string, command: string): Promise<{ code: number; stderr: string }> {
return new Promise((resolveResult) => {
const child = spawn(`${root}/bin/${script}`, ["--command", command], {
Expand All @@ -106,6 +113,10 @@ function runCdCheck(command: string): Promise<{ code: number; stderr: string }>
return runChecker("fm-cd-pretool-check.sh", command);
}

function runLavishCheck(command: string): Promise<{ code: number; stderr: string }> {
return runChecker("fm-lavish-pretool-check.sh", command);
}

export default function (pi: ExtensionAPI) {
pi.on?.("session_start", (event) => {
const reason = String((event as { reason?: unknown }).reason ?? "");
Expand All @@ -126,6 +137,10 @@ export default function (pi: ExtensionAPI) {
if (cdResult.code === 2) {
return { block: true, reason: cdResult.stderr.trim() || "denied by the cd-guard PreToolUse seatbelt" };
}
const lavishResult = await runLavishCheck(command);
if (lavishResult.code === 2) {
return { block: true, reason: lavishResult.stderr.trim() || "denied by the lavish-guard PreToolUse seatbelt" };
}
const result = await runPretoolCheck(command);
if (result.code !== 2) return {};
return { block: true, reason: result.stderr.trim() || "denied by the watcher-arm PreToolUse seatbelt" };
Expand Down
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ state/ volatile runtime signals; gitignored
.pr-check-migration-scan-v1 private marker proving the non-executing scan disabled every unsafe legacy check; .pr-check-migration-v1 separately records completed private repairs
x-watch.check.sh generated X-mode relay poll shim; present only when opted in (section 14)
pending-replies/ parent-owned secondmate pending-reply records (correlation id, delivery vs reply, recovery, escalation); fm-pending-reply-lib.sh
service-port.<service> published record of the address and port one vessel-local service took; a record, NEVER a lock, so nothing may read it to decide a port is free (bin/fm-service-port.sh; docs/lavish-access.md)
lavish/ this home's private review-board server state, session store, and claim token; owned by bin/fm-lavish.sh
x-inbox/ generated X-mode pending mention payloads; fmx-respond drains it (section 14)
x-context/ generated X-mode durable per-request reply context and one-wake offer markers, keyed by request_id; survives inbox cleanup and expires within seven days (section 14; bin/fm-x-lib.sh)
x-outbox/ generated X-mode dry-run reply and dismiss previews; inspect it when FMX_DRY_RUN is set (section 14)
Expand Down Expand Up @@ -171,7 +173,7 @@ A lock-refused session must not spawn, steer, merge, drain the wake queue, repai

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.
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.
Use `gh-axi` for GitHub, `chrome-devtools-axi` for browser work, and `bin/fm-lavish.sh` - never bare `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.
`secondmate-provisioning` owns startup secondmate sync, liveness, and inherited local-material convergence.
Expand Down Expand Up @@ -442,7 +444,7 @@ Reach the captain immediately for:

Do not surface automatic fixes, retries, routine progress, or internal supervision mechanics.
Batch non-urgent updates into the next natural reply.
Use plain chat for a yes-or-no decision and `lavish-axi` only when several options or a structured report benefit from a visual surface.
Use plain chat for a yes-or-no decision and `bin/fm-lavish.sh` only when several options or a structured report benefit from a visual surface; it is the only sanctioned way to open a review board, because bare `lavish-axi` hands the captain a link that opens nowhere but this machine (docs/lavish-access.md).
Whenever a PR is mentioned, include its full `https://...` URL before any shorthand reference.
Generate a PDF deliverable only through `bin/fm-pdf-finish.sh`, which refuses to publish a file a real reader cannot read, because a browser-printed document looks correct on screen and fails at the recipient (docs/pdf-output.md).
Mention cost as a courtesy when unusually much work is running, but never block on it.
Expand Down Expand Up @@ -496,7 +498,7 @@ Bootstrap separately detects when the primary checkout's own default branch has

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

- `bootstrap-diagnostics` - load whenever the session-start digest's bootstrap section prints an actionable diagnostic line (`MISSING:`, `MISSING_MANUAL:`, `BACKEND_INVALID:`, `ROLE_INVALID:`, `ROLE_OVERLAY_MISSING:`, `NEEDS_GH_AUTH`, `TANGLE:`, `SELF_DRIFT:`, `CREW_DISPATCH: invalid`, `CURRENCY_BASE:`, `BACKLOG_STALE:`, `BACKLOG_UNREADABLE:`, `FLEET_SYNC:`, `PR_CHECK_MIGRATION:`, `SECONDMATE_SYNC:`, `SECONDMATE_LIVENESS:`, `NUDGE_SECONDMATES:`, `AXI_SUITE_UPDATED:`, `AXI_SUITE_REVIEW:`, `AXI_SUITE_STUCK:`, `FIRSTMATE_UPDATE_AVAILABLE:`, `FIRSTMATE_UPDATE_STUCK:`, `FORK_SYNC:`, `FORK_SYNC_STUCK:`, or `FMX:`); silence and `BOOTSTRAP_INFO:` need no load.
- `bootstrap-diagnostics` - load whenever the session-start digest's bootstrap section prints an actionable diagnostic line (`MISSING:`, `MISSING_MANUAL:`, `BACKEND_INVALID:`, `ROLE_INVALID:`, `ROLE_OVERLAY_MISSING:`, `NEEDS_GH_AUTH`, `TANGLE:`, `SELF_DRIFT:`, `CREW_DISPATCH: invalid`, `CURRENCY_BASE:`, `LAVISH_ACCESS:`, `BACKLOG_STALE:`, `BACKLOG_UNREADABLE:`, `FLEET_SYNC:`, `PR_CHECK_MIGRATION:`, `SECONDMATE_SYNC:`, `SECONDMATE_LIVENESS:`, `NUDGE_SECONDMATES:`, `AXI_SUITE_UPDATED:`, `AXI_SUITE_REVIEW:`, `AXI_SUITE_STUCK:`, `FIRSTMATE_UPDATE_AVAILABLE:`, `FIRSTMATE_UPDATE_STUCK:`, `FORK_SYNC:`, `FORK_SYNC_STUCK:`, or `FMX:`); silence and `BOOTSTRAP_INFO:` need no load.
- `diagnostic-reasoning` - load before scoping a reported bug and before acting on a diagnostic report.
- `harness-adapters` - load before spawning or recovering a crewmate or secondmate, handling a trust dialog, sending a harness-specific skill invocation, interrupting or exiting an agent, resuming an exited agent, or verifying a new harness adapter.
- `firstmate-orca` - load before switching to Orca, spawning or supervising Orca-backed work, smoke-testing Orca backend behavior, debugging Orca task state, or reconciling Orca-backed task metadata.
Expand Down
Loading
Loading