Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0ae14af
ci: gate GitHub workflows with pinned actionlint (#2517)
kunchenguid Aug 17, 2026
ac55d39
fix: install pinned lint tools across supported platforms (#2546)
kunchenguid Aug 17, 2026
312871d
docs: reconcile test-evidence docs with store_in_repo: true (#2548)
kunchenguid Aug 17, 2026
64d61ae
docs: clarify test evidence branch storage (#2549)
kunchenguid Aug 17, 2026
d023c45
docs: hint that live scouts may host their own Lavish review loop (#2…
kunchenguid Aug 18, 2026
d843712
fix(bin): report remote secondmate delivery and state truthfully (#2570)
kunchenguid Aug 18, 2026
d9ee8ea
feat: adopt spendPriority for quota dispatch (#2574)
kunchenguid Aug 18, 2026
862c532
docs: add GROK_BOT.md Grok Bot system prompt (#2590)
kunchenguid Aug 18, 2026
9d2ad81
docs: update GROK_BOT.md nautical terms and self-improvement (#2592)
kunchenguid Aug 18, 2026
f758e51
doc: Update language in GROK_BOT.md for clarity
kunchenguid Aug 18, 2026
ed66b85
fix(bin): preserve inactive reconciliation scan progress (#2595)
kunchenguid Aug 18, 2026
63362d2
doc: Revise Firstmate delegation and communication guidelines
kunchenguid Aug 18, 2026
03bb1d8
doc: Update work delegation and secret management instructions
kunchenguid Aug 18, 2026
c54c448
test(procevent): make the process-event suite's detached-runner asser…
kunchenguid Aug 19, 2026
7f5255a
fix: preserve pending replies and defer remote reposts (#2618)
kunchenguid Aug 19, 2026
b57c4d6
fix(bin): honor declared pauses in busy-pane wedge checks (#2619)
kunchenguid Aug 19, 2026
f242264
doc: Enhance communication guidelines for decision-making
kunchenguid Aug 19, 2026
7b38a2f
doc: Update task delegation and communication guidelines
kunchenguid Aug 19, 2026
87681a4
fix(bin): reliably confirm herdr steer submission (#2647)
kunchenguid Aug 19, 2026
1cb900c
feat(bearings): add interactive Lavish fleet board (#2659)
kunchenguid Aug 20, 2026
b96dba1
fix(bearings): restore decision options and add close controls (#2707)
kunchenguid Aug 21, 2026
a0cec26
ci: require no-mistakes pipeline step attestation (#2710)
kunchenguid Aug 21, 2026
f8d103e
Merge remote-tracking branch 'upstream/main' into fm/firstmate-upstre…
prandelicious Aug 21, 2026
90ea8ed
no-mistakes(review): Bind pipeline attestations to current PR head
prandelicious Aug 21, 2026
42c00c9
no-mistakes(review): Trust head statuses and privatize decision drops
prandelicious Aug 21, 2026
24fbf41
no-mistakes(review): Validate published no-mistakes evidence commits
prandelicious Aug 21, 2026
a5a685a
no-mistakes(document): Document authenticated no-mistakes evidence re…
prandelicious Aug 22, 2026
988b084
ci: allow manual workflow dispatch
prandelicious Aug 22, 2026
5e01b5b
no-mistakes(document): Refresh documentation audience verification co…
prandelicious Aug 22, 2026
f82aefe
ci: run checks for edited pull requests
prandelicious Aug 22, 2026
31ad6cb
no-mistakes(document): Document current pull request validation triggers
prandelicious 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
8 changes: 7 additions & 1 deletion .agents/skills/bearings/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ Compose the payload from the same snapshot with the same ranking judgment as the

- A Captain's Call decision key is the FULL hold identity from `decisions_open`; a merge card's key is `merge.<task-id>`; the Charted Next dispatch picker's key is `dispatch.charted`.
- Decision cards carry agent-authored copy: a short noun-phrase title, one-line `about` and `decide` context rows, and option labels with hints, with the recommended option marked.
- Every decision card must include at least one selectable option, and the board always renders freeform as a supplementary "something else" input, never the only control.
- Do not use `__drop__` as an option value: that reserved answer is the card's Close / drop control, recognized by the keyed-answer intake as a decline.
- Every Captain's Call item and every Underway, Recently Landed, and Charted Next row carries an explicit `repo` field. Fill it from the snapshot and task records wherever known; use null or an empty string only as the deliberate genuinely-no-repo marker, in which case the template may show the internal id. Ids otherwise stay in the payload only as the routing channel, and composed reasons name blockers in plain words.

Run `build` once after composing the payload.
Expand All @@ -87,7 +89,11 @@ Never run `lavish-axi poll` for the board yourself: the armed source's supervise
### Handling a board wake

A board answer arrives as an ordinary `procevent lavish <source-id> <sequence>` check wake. Identify it by comparing the wake source id with `bin/fm-procevent-lavish.sh source-id "$(bin/fm-bearings-board.sh path)"`, regardless of which answer kinds the result contains; then load `process-event-sources` and follow its contract for the result read, adapter classification, and the handled acknowledgement.
Decision answers need no routing from you: the runner feeds the board's any-origin binding into `bin/fm-decision-hold.sh`'s one keyed-answer intake, which closes each full-identity hold at answer time; reconcile any `skipped:` key yourself, using `resolve` when routed work exists.
Decision answers need no routing from you: the runner feeds the board's any-origin binding into `bin/fm-decision-hold.sh`'s one keyed-answer intake, which closes each full-identity hold at answer time.
A reserved `__drop__` answer is the captain closing or dropping that hold, not a substantive choice and not a merge.
The intake declines it through `bin/fm-decision-hold.sh` with a "dropped by captain" decision record, so the hold leaves Captain's Call on the next rebuild.
Existing work routed behind that hold remains independent queued work; dropping does not close those dependents.
Reconcile any other `skipped:` key yourself, using `resolve` when routed work exists.
Route the non-decision keys yourself:

- `merge.<task-id>` is the captain's explicit merge order; follow the merge ruling below.
Expand Down
46 changes: 36 additions & 10 deletions .agents/skills/bearings/assets/board-template.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@
.fm-btn--primary:hover { background: var(--rust-600); }
.fm-btn--gold { background: var(--gold-500); color: var(--navy-700); border-color: var(--ink-900); box-shadow: var(--shadow-hard-sm); }
.fm-btn--gold:hover { background: var(--gold-600); color: var(--white); }
.fm-btn--ghost { background: transparent; color: var(--text-muted); border-color: var(--border-default); box-shadow: none; }
.fm-btn--ghost:hover { background: var(--paper-100); color: var(--text-strong); border-color: var(--ink-300); }
.fm-btn[disabled] { opacity: 0.5; cursor: not-allowed; }

/* ---- fm-card ---- */
Expand Down Expand Up @@ -219,7 +221,9 @@
letter-spacing: 0.07em; color: var(--navy-700); background: var(--gold-300);
border: 1px solid var(--gold-600); border-radius: var(--radius-xs); padding: 3px 7px 2px; }
.bb-opt:has(input:checked) { border-color: var(--rust-500); background: var(--rust-050); box-shadow: inset 0 0 0 1px var(--rust-500); }
.bb-decision__foot { display: flex; align-items: center; gap: 10px; margin-top: auto; }
.bb-decision__foot { display: flex; align-items: center; gap: 10px; margin-top: auto; flex-wrap: wrap; }
.bb-drop { margin-left: auto; }
.is-queued .bb-drop { display: none; }
.bb-queued {
display: none; align-items: center; gap: 6px;
font-size: var(--fs-2xs); font-weight: 800; text-transform: uppercase; letter-spacing: 0.07em;
Expand Down Expand Up @@ -517,10 +521,14 @@
});
form.appendChild(opts);

if (item.allow_freeform) {
/* Decision cards always keep a supplementary "something else" box; merge
and credential cards keep the payload's allow_freeform flag. */
if (item.type === "decision" || item.allow_freeform) {
var ff = document.createElement("input");
ff.type = "text"; ff.name = "note"; ff.className = "bb-freeform";
ff.placeholder = item.freeform_hint || "or answer in your own words…";
ff.placeholder = item.freeform_hint || (item.type === "decision"
? "or something else…"
: "or answer in your own words…");
form.appendChild(ff);
}

Expand All @@ -534,16 +542,18 @@
var answerLimit = el("span", "bb-limit");
answerLimit.setAttribute("role", "alert");
foot.appendChild(answerLimit);
var dropBtn = null;
if (item.type === "decision") {
dropBtn = el("button", "fm-btn fm-btn--sm fm-btn--ghost bb-drop", "Close / drop");
dropBtn.type = "button";
dropBtn.setAttribute("aria-label", "Close or drop this decision");
foot.appendChild(dropBtn);
}
form.appendChild(foot);

form.addEventListener("submit", function (ev) {
ev.preventDefault();
function queueAnswer(answer) {
if (card.classList.contains("is-queued")) return;
answerLimit.classList.remove("is-visible");
var fd = new FormData(form);
var value = fd.get("answer");
var note = (fd.get("note") || "").trim();
/* picked option, optionally annotated; a bare note is itself the answer */
var answer = value ? (note ? value + " - " + note : value) : note;
if (!answer) return;
if (utf8ByteLength(answer) > 512) {
answerLimit.textContent = "Answer is too long to queue (512 bytes maximum).";
Expand All @@ -560,7 +570,23 @@
card.classList.add("is-queued");
/* deal the next card once this one is answered */
setTimeout(function () { showCard(active < cards.length - 1 ? active + 1 : active); }, 450);
}

form.addEventListener("submit", function (ev) {
ev.preventDefault();
var fd = new FormData(form);
var value = fd.get("answer");
var note = (fd.get("note") || "").trim();
/* picked option, optionally annotated; a bare note is itself the answer */
var answer = value ? (note ? value + " - " + note : value) : note;
queueAnswer(answer);
});
if (dropBtn) {
dropBtn.addEventListener("click", function () {
/* reserved close/drop encoding; the keyed-answer intake declines it */
queueAnswer("__drop__");
});
}

pad.appendChild(form);
card.appendChild(pad);
Expand Down
1 change: 1 addition & 0 deletions .agents/skills/decision-hold-lifecycle/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ When the captain's answer authorizes follow-up work, the hold remains the author
When the captain's answer routes no follow-up work at all, such as a declined proposal, `bin/fm-decision-hold.sh decline` records that answer and closes the hold; it never substitutes for routing work the captain did authorize.
When the captain simply answers a hold that has no follow-up work routed behind it yet, `bin/fm-decision-hold.sh answer` records that answer and closes the hold, so answering is closing rather than a separate later act that can be forgotten.
"A keyed answer closes its matching hold" is one capability with one owner, `bin/fm-decision-hold.sh answers`, and every channel that carries a captain answer feeds it the same `<decision-key>` and answer.
The exact answer `__drop__` is the reserved close/drop encoding owned by that script's header: the intake declines the hold with a dropped-by-captain record rather than recording a substantive answer, and closes only that hold while existing dependents remain independent queued work.
A channel never maps a key to a hold, records a decision, or closes anything itself, so no channel is special and a new one needs no new closing logic.
Chat already feeds it: `bin/fm-send.sh --resolve-key` answers a decision in whichever ledger still holds it open, including a decision already transferred to its durable hold.
A captured-answer source feeds it too once bound with `bin/fm-decision-hold.sh bind <source-id> <origin-id>`, or with `--any-origin` for a source that carries answers across origins, such as the bearings board; bind before arming the source, and key each structured question by the hold's own decision key, or by its full hold identity under an any-origin binding.
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
branches: [main]
pull_request:
branches: [main]
types: [opened, synchronize, reopened, edited]
workflow_dispatch:

permissions:
contents: read
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/no-mistakes-required.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,42 @@ jobs:
PR_BODY: ${{ github.event.pull_request.body }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
GH_TOKEN: ${{ github.token }}
run: |
set -eu
marker='Updates from [git push no-mistakes](https://github.com/kunchenguid/no-mistakes)'
if printf '%s' "${PR_BODY:-}" | grep -qF -- "$marker"; then
echo "Found no-mistakes signature in PR #${PR_NUMBER} body."
if ! command -v jq >/dev/null 2>&1; then
echo "::error::This check requires jq to inspect authenticated pipeline evidence, but jq was not found on the runner." >&2
exit 1
fi
evidence_path=$(printf '.no-mistakes/evidence/%s' "$PR_HEAD_REF" | jq -sRr @uri)
evidence=$(curl --fail-with-body --silent --show-error \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${PR_HEAD_REPOSITORY}/commits?sha=no-mistakes/evidence&path=${evidence_path}&per_page=1")
evidence_commit=$(printf '%s' "$evidence" | jq -r \
--arg branch "$PR_HEAD_REF" \
'first(.[] | select(.commit.message | startswith("no-mistakes: evidence for " + $branch + " (run "))) | .sha // empty')
if [ -z "$evidence_commit" ]; then
{
echo "::error::No authenticated no-mistakes evidence was published for this branch."
echo
echo "This repository requires the pipeline-produced evidence commit on the"
echo "orphan no-mistakes/evidence branch, not author-supplied PR-body claims."
echo
echo "Re-run the pipeline with 'git push no-mistakes' for the current commit."
echo "See CONTRIBUTING.md for setup and the full workflow."
echo
echo "PR author: ${PR_AUTHOR}"
} >&2
exit 1
fi
echo "Authenticated no-mistakes evidence commit found: ${evidence_commit}."
exit 0
fi
{
Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ We require this to reduce the maintainer's burden of reviewing and merging contr
`no-mistakes` puts a local git proxy in front of your real remote.
Pushing through it runs an AI-driven review/test/lint pipeline in an isolated worktree, forwards the push upstream only after every check passes, and opens a clean PR automatically.

A GitHub Actions check (`Require no-mistakes`) runs on PRs targeting `main` and fails if the body is missing the deterministic signature that no-mistakes writes.
It evaluates every PR opening and body edit independently, so a later edit cannot replace an earlier pending compliance check.
GitHub Actions and Dependabot are exempt so their automation keeps working, but regular contributor PRs without the signature will not be reviewed or merged.
A GitHub Actions check (`Require no-mistakes`) runs on PRs targeting `main` and requires both the deterministic signature that no-mistakes writes and authenticated pipeline evidence published for the PR branch on the orphan `no-mistakes/evidence` branch.
It evaluates every PR opening, synchronization, reopening, and edit independently, so a later event cannot replace an earlier pending compliance check.
GitHub Actions and Dependabot are exempt so their automation keeps working, but regular contributor PRs without both forms of evidence will not be reviewed or merged.

## Workflow

Expand Down
15 changes: 12 additions & 3 deletions bin/fm-bearings-board.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@
# Captain's Call item explicitly carries `repo`; the composer fills it from the
# snapshot and task records wherever known, and uses null or an empty string
# only as the deliberate genuinely-no-repo marker. In that exceptional case
# the template may display the routing id. Anything else refuses before the
# existing board is touched.
# the template may display the routing id. Decision cards must include at least
# one selectable option; every other Captain's Call item must either include an
# option or explicitly allow freeform input. Option values cannot
# be `__drop__`: that reserved answer is the board Close / drop encoding,
# recognized by fm-decision-hold.sh's keyed-answer intake as a decline rather
# than a substantive choice. Anything else refuses before the existing board
# is touched.
#
# The board path is stable - $FM_HOME/.lavish/bearings-board.html - so a
# re-invocation rebuilds the same file in place, which keeps the same Lavish
Expand Down Expand Up @@ -85,10 +90,14 @@ validate_payload() { # <data.json>
and repo_marker
and (.title | nonempty_string)
and (.options | type == "array")
and ((.options | length) > 0 or .allow_freeform == true)
and (if .type == "decision"
then (.options | length) > 0
else ((.options | length) > 0 or .allow_freeform == true)
end)
and ([.options[]
| type == "object"
and (.value | slug(128))
and .value != "__drop__"
and (.label | nonempty_string)
and optional_string("hint")] | all)
and (optional_string("about"))
Expand Down
Loading
Loading