Skip to content
Closed
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
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ README.md public overview and development notes
.claude/mods/ Claude Code mods (function-hooks plugins), committed; Calm's module may load through CLAUDE_CODE_ENABLE_FUNCTION_HOOKS or tengu_plugin_hooks_modules, but activates only when CLAUDE_CODE_ENABLE_FUNCTION_HOOKS is exactly "1" and is otherwise a complete no-op (docs/calm.md)
skills/ standalone public installer-facing skills, committed; not loaded by firstmate
bin/ helper scripts, committed; read each script's header before first use
.env optional Relay pairing token (presence-gates section 14), mail-plane credentials (schema: docs/configuration.md "Mail plane"), and typed dispatch resolution key TYPESAFE_API_KEY (presence-gates bin/fm-dispatch-resolve.sh, the watcher's routine-wake triage, and the Claude worker stop hook's finished check; docs/configuration.md "Typed dispatch resolution", "Routine-wake triage", and "Finished check"); LOCAL, gitignored
.env optional Relay pairing token (presence-gates section 14), mail-plane credentials (schema: docs/configuration.md "Mail plane"), and typed dispatch resolution key TYPESAFE_API_KEY (presence-gates bin/fm-dispatch-resolve.sh, the watcher's routine-wake triage, the Claude worker stop hook's finished check, and the pull request risk level; docs/configuration.md "Typed dispatch resolution", "Routine-wake triage", "Finished check", and "Pull request risk level"); LOCAL, gitignored
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/claude-permission-mode optional one-token permission posture for every Claude worker launch: absent or "bypass" keeps --dangerously-skip-permissions, "auto" launches with --permission-mode auto; LOCAL, gitignored; inherited by secondmate homes; see docs/configuration.md "Claude permission mode"
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 @@ -87,6 +87,7 @@ config/turnend-churn-absorb optional presence flag opting this home into the de
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/watched-tools.json optional list of the tools this home depends on, read by the update check armed with bin/fm-tool-update-check.sh; LOCAL, gitignored, firstmate-maintained but human-editable, and NOT inherited by secondmate homes; see docs/configuration.md "Watched tool updates"
config/jev-code-projects optional list of project names, one per line, whose pull request title, description, file list, and diff excerpt may be sent to Jev for the risk level; LOCAL, gitignored, absent = no project, and not inherited; see docs/configuration.md "Pull request risk level"
config/x-mode.env generated Relay watcher cadence; LOCAL, gitignored; source before arming watcher when present
data/ personal fleet records; LOCAL, gitignored as a whole
backlog.md task queue, dependencies, history
Expand Down Expand Up @@ -398,6 +399,7 @@ The worker reports the PR when CI first becomes green rather than waiting for me
For PR-based ship tasks, the ready signal depends on mode: `no-mistakes` reports `done: PR <url> checks green` after CI is green, while `direct-PR` reports `done: PR <url>` after opening the PR.
Run `bin/fm-pr-check.sh <id> <PR url>` with the URL copied from that ready signal - it records `pr=` and the forge's `pr_head=` when available in the task's meta and arms the watcher's merge poll.
Tell the captain the PR's full `https://...` URL copied from the worker's ready line or the task's `pr=` metadata, a concise outcome summary, and the no-mistakes risk level when applicable.
When `bin/fm-pr-check.sh` prints a `risk:` line, relay that advisory level and its reasons with the PR; it never gates or authorizes a merge (`docs/configuration.md` "Pull request risk level").
A captain instruction to merge is explicit authority; `yolo` is the only standing routine merge authority.
For any custom `state/<id>.check.sh` you write yourself, keep it an ordinary single-link mode-`0700` file, print one line only when firstmate should wake, print nothing otherwise, finish before `FM_CHECK_TIMEOUT`, then bind its current bytes with `bin/fm-check-register.sh <id>` before the watcher may execute it.
Retire a custom check only through `bin/fm-check-unregister.sh <id>` (or `bin/fm-teardown.sh` for a spawned task); never hand-compose an `rm` with `$STATE`/`$ID`.
Expand Down
17 changes: 15 additions & 2 deletions bin/fm-jev-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
# option, and a status; every decision
# made from that answer (a confidence floor, an approval gate, an absorb) stays
# in the caller's own code. bin/fm-dispatch-resolve.sh, the watcher's
# routine-wake triage (bin/fm-watch.sh), bin/fm-house-rules-check.sh, and the
# worker stop hook's finished check (bin/fm-finished-check.sh) are the callers.
# routine-wake triage (bin/fm-watch.sh), bin/fm-house-rules-check.sh, the
# worker stop hook's finished check (bin/fm-finished-check.sh), and the pull
# request risk level (bin/fm-pr-risk-lib.sh) are the callers.
#
# Key handling: sourcing this file copies an environment-provided
# TYPESAFE_API_KEY into one non-exported shell variable and unsets the exported
Expand All @@ -22,6 +23,13 @@
# fm_jev_key_load <home>
# 0 when a key is available, 1 when it is absent from both sources.
#
# fm_jev_code_allowed <project>
# 0 only when <project> is a line of $FM_HOME/config/jev-code-projects (one
# project name per line; blank lines and lines starting with # are ignored).
# The key alone lets a caller send routing text and status lines; a caller
# that would send a project's code, file list, or pull request text asks this
# first. An absent or empty file allows no project.
#
# fm_jev_choice <question-key> <instructions> <state-json-file> <criteria-json-file>
# One POST to $FM_JEV_BASE/v1/systemone. <state-json-file> holds the JSON
# value sent as `state`; <criteria-json-file> holds one JSON object mapping
Expand Down Expand Up @@ -82,6 +90,11 @@ fm_jev_key_load() { # <home>
[ -n "$TYPESAFE_API_KEY_PRIVATE" ]
}

fm_jev_code_allowed() { # <project>
[ -n "$1" ] && [ "${1#\#}" = "$1" ] \
&& grep -qxF -- "$1" "${FM_HOME:-.}/config/jev-code-projects" 2>/dev/null
}

_fm_jev_fail() { # <reason>
FM_JEV_STATUS=error
FM_JEV_ERROR=$1
Expand Down
17 changes: 15 additions & 2 deletions bin/fm-pr-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
# live only in a private sidecar and are never interpolated into shell source.
# A GitHub pull request URL and a GitLab merge request URL are both accepted,
# including a merge request on a self-hosted GitLab instance.
# Usage: fm-pr-check.sh <task-id> <pr-url>
# A home that holds TYPESAFE_API_KEY also gets one advisory `risk:` line for the
# recorded pull request (bin/fm-pr-risk-lib.sh); it never affects the arming.
# bin/fm-pr-merge.sh re-registers through this script with --no-risk, which
# skips that line entirely: the level is rated once, at ready time, never in a
# merge.
# Usage: fm-pr-check.sh <task-id> <pr-url> [--no-risk]
set -eu

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Expand All @@ -19,8 +24,13 @@ STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
. "$SCRIPT_DIR/fm-wake-lib.sh"
# shellcheck source=bin/fm-parent-channel-lib.sh
. "$SCRIPT_DIR/fm-parent-channel-lib.sh"
# shellcheck source=bin/fm-pr-risk-lib.sh
. "$SCRIPT_DIR/fm-pr-risk-lib.sh"

if [ "$#" -ne 2 ]; then
RATE_RISK=1
if [ "$#" -eq 3 ] && [ "$3" = --no-risk ]; then
RATE_RISK=0
elif [ "$#" -ne 2 ]; then
echo "error: invalid PR check request" >&2
exit 2
fi
Expand Down Expand Up @@ -176,3 +186,6 @@ case "$READY_RC" in
*) printf 'actionable: PR %s is registered but its ready line did not reach the parent channel (rc=%s)\n' "$URL" "$READY_RC" >&2 ;;
esac
printf 'armed: state/%s.check.sh\n' "$ID"
[ "$RATE_RISK" = 1 ] || exit 0
PR_PROJECT=$(grep '^project=' "$META" | tail -1 | cut -d= -f2- || true)
fm_pr_risk "$ID" "$PROVIDER" "$URL" "$HOST" "$PROJECT_PATH" "$NUMBER" "${PR_PROJECT##*/}" 2>/dev/null || true
2 changes: 1 addition & 1 deletion bin/fm-pr-merge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ METHODS
}

record_pr_metadata() {
if ! "$SCRIPT_DIR/fm-pr-check.sh" "$ID" "$URL"; then
if ! "$SCRIPT_DIR/fm-pr-check.sh" "$ID" "$URL" --no-risk; then
return 1
fi
grep -qxF "pr=$URL" "$META" || {
Expand Down
Loading
Loading