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
31 changes: 30 additions & 1 deletion .github/workflows/no-mistakes-required.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ on:

permissions:
contents: read
# Needed to re-read the live PR body; the event payload only carries a
# snapshot taken when the event fired (see the verify step below).
pull-requests: read

# GitHub concurrency groups retain at most one pending run, replacing older
# pending runs even when cancel-in-progress is false. Give body-bearing events
Expand All @@ -31,13 +34,38 @@ jobs:
PR_BODY: ${{ github.event.pull_request.body }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_REPO: ${{ github.repository }}
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
has_signature() {
printf '%s' "${1:-}" | grep -qF -- "$marker"
}
if has_signature "${PR_BODY:-}"; then
echo "Found no-mistakes signature in PR #${PR_NUMBER} body."
exit 0
fi
# The event payload carries the body as it stood when the event fired,
# and no-mistakes opens the PR before it writes the deterministic
# '## Pipeline' section into the body. An 'opened' run therefore
# observes an unsigned snapshot of a PR that is compliant moments
# later, and by design (see the concurrency note above) no later event
# can replace that run's verdict. Re-read the live body before failing
# so this check reflects the PR's current state, not a stale snapshot.
attempts=10
attempt=1
while [ "$attempt" -le "$attempts" ]; do
live_body=$(gh api "repos/${PR_REPO}/pulls/${PR_NUMBER}" --jq '.body // ""' 2>/dev/null) || live_body=''
if has_signature "$live_body"; then
echo "Found no-mistakes signature in the live PR #${PR_NUMBER} body (attempt ${attempt})."
exit 0
fi
if [ "$attempt" -lt "$attempts" ]; then
sleep 6
fi
attempt=$((attempt + 1))
done
{
echo "::error::This PR was not raised through no-mistakes."
echo
Expand All @@ -50,5 +78,6 @@ jobs:
echo "See CONTRIBUTING.md for setup and the full workflow."
echo
echo "PR author: ${PR_AUTHOR}"
echo "Live PR body re-read ${attempts} times; the signature never appeared."
} >&2
exit 1
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Pushing through it runs an AI-driven review/test/lint pipeline in an isolated wo

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.
Because no-mistakes opens the PR before it writes that signature into the body, the check re-reads the live PR body before failing, so an opening event never leaves a permanently red check on a PR that is signed moments later.
GitHub Actions and Dependabot are exempt so their automation keeps working, but regular contributor PRs without the signature will not be reviewed or merged.

## Workflow
Expand Down
27 changes: 23 additions & 4 deletions bin/fm-bearings-snapshot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ set -u

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FLEET="$SCRIPT_DIR/fm-fleet-snapshot.sh"
# Only needed for the shared FM_DOING_CHAR_CAP constant (see that file); this
# wrapper does JSON projection in jq, not the classifier logic the lib mostly
# holds.
# shellcheck source=bin/fm-classify-lib.sh
. "$SCRIPT_DIR/fm-classify-lib.sh"

# Bounds (overridable for tests / large fleets).
FM_BEARINGS_LANDED=${FM_BEARINGS_LANDED:-6}
Expand Down Expand Up @@ -301,9 +306,23 @@ MODEL=$(printf '%s' "$SNAP" | jq \
--argjson pr_repos_shown "$PR_REPOS_SHOWN" \
--argjson pr_rows_capped "$PR_ROWS_CAPPED" \
--argjson pr_rows_min_total "$PR_ROWS_MIN_TOTAL" \
--argjson candidate_prs "$CANDIDATE_PRS" '
--argjson candidate_prs "$CANDIDATE_PRS" \
--argjson doing_cap "$FM_DOING_CHAR_CAP" '
def trunc($n): if . == null then null else
(tostring | gsub("\\s+"; " ") | if (length > $n) then (.[:$n] + "…") else . end) end;
# Word-boundary-aware cousin of trunc(), for "doing" fields only: a
# published status text should already fit a rendering surface without a
# mid-word cut. See FM_DOING_CHAR_CAP in bin/fm-classify-lib.sh
# for the sourced cap and the bash twin (fm_doing_truncate) this mirrors.
def doing_trunc($n):
tostring | gsub("\\s+"; " ") as $s
| if ($s | length) <= $n then $s
else ($s[0:$n]) as $cut
| ($cut | split(" ")) as $words
| (if ($words | length) > 1 then ($words[0:-1] | join(" ")) else "" end) as $boundary
| (if ($boundary | length) >= (($n * 3) / 5 | floor) and ($boundary | length) > 0
then $boundary else $cut end) + "…"
end;
def round_robin_landed($n):
. as $groups
| [range(0; (($groups | map(length) | max) // 0)) as $i
Expand Down Expand Up @@ -365,7 +384,7 @@ MODEL=$(printf '%s' "$SNAP" | jq \
elif .bearings_state == "externally_held" then
([.bearings_holds[] | .id + ": " + (.reason // "held")] | join("; "))
elif .bearings_state == "no_active_work" then "No active child work"
else (.current.reason // "Current home state unavailable") end) | trunc(120)),
else (.current.reason // "Current home state unavailable") end) | doing_trunc($doing_cap)),
provenance:.provenance.selected,freshness:.freshness.status,
age_seconds:.freshness.age_seconds,contradiction:(.contradiction // false),
reason:(.current.reason // "-")} ]) as $secondmates_all
Expand All @@ -376,12 +395,12 @@ MODEL=$(printf '%s' "$SNAP" | jq \
| {id, kind,
state: .current_state.state,
doing: ((.current_state.detail // "") as $d
| (if $d != "" then $d else (.hints.last_event_text // "") end) | trunc(90))
| (if $d != "" then $d else (.hints.last_event_text // "") end) | doing_trunc($doing_cap))
} ]
+ [ $secondmate_views[]
| select(.bearings_state == "active_child_work")
| {id,kind:"secondmate",state:.bearings_state,
doing:([.active_children[] | .id + ": " + (.doing // .state)] | join("; ") | trunc(90))} ]) as $in_flight_all
doing:([.active_children[] | .id + ": " + (.doing // .state)] | join("; ") | doing_trunc($doing_cap))} ]) as $in_flight_all
| ([ .backlog.records[]
| select(.structured and .captain_actionable == true)
| {id,key:.id,verb:"captain-hold",
Expand Down
38 changes: 38 additions & 0 deletions bin/fm-classify-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,41 @@ scan_captain_relevant_statuses() { # <state>
done
return 0
}

# The character budget a published "doing"/status-detail string must fit
# without needing a renderer to elide it. Sourced from
# data/herdr-card-iteration-2/report.md's measured fit ladder: at 11.5px
# Medium 500 (the report's recommended card type), all ten real `doing`
# strings sampled from a live fleet fit their card column with zero elision,
# and the longest of those ten is 59 characters (the binding case: a 59-char
# title uses 299px of a 315px depth-2 column). Below 11.5px, or at a lighter
# weight, some of those same strings elide. Overridable for tests or a
# different rendering surface; any override should stay grounded in a
# measured fit, not a guess.
FM_DOING_CHAR_CAP=${FM_DOING_CHAR_CAP:-59}

# fm_doing_truncate <text> [cap]: collapse whitespace and, only if <text>
# exceeds the char cap, shorten it to fit - preferring a cut at the last word
# boundary within the cap over a mid-word hard cut, but falling back to a hard
# cut when the word-boundary cut would throw away more than 40% of the
# budget (e.g. one long token with no early space). Mirrors the jq
# `doing_trunc($n)` helper duplicated in bin/fm-fleet-snapshot.sh and
# bin/fm-bearings-snapshot.sh for the same fields in JSON output; keep the
# three in sync.
fm_doing_truncate() { # <text> [cap]
local text=$1 cap=${2:-$FM_DOING_CHAR_CAP} collapsed cut boundary floor
local LC_ALL=C.UTF-8
collapsed=$(printf '%s' "$text" | tr -s '[:space:]' ' ')
[ "${#collapsed}" -gt "$cap" ] || { printf '%s' "$collapsed"; return 0; }
cut=${collapsed:0:cap}
case "$cut" in
*' '*) boundary=${cut% *} ;;
*) boundary=$cut ;;
esac
floor=$((cap * 3 / 5))
if [ "${#boundary}" -ge "$floor" ] && [ "${#boundary}" -gt 0 ]; then
printf '%s…' "$boundary"
else
printf '%s…' "$cut"
fi
}
20 changes: 18 additions & 2 deletions bin/fm-fleet-snapshot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ crew_state_json() { # <id>
esac
;;
esac
detail=$(fm_doing_truncate "$detail")
jq -n --arg raw "$raw" --arg state "$state" --arg source "$source" --arg detail "$detail" \
'{state:$state,source:$source,detail:$detail,raw:$raw}'
}
Expand Down Expand Up @@ -604,10 +605,25 @@ secondmate_home_summary_json() { # <backlog-json> <tasks-json>
--argjson decisions_n "$FM_SNAPSHOT_SECONDMATE_DECISIONS" \
--argjson landed_n "$FM_SNAPSHOT_SECONDMATE_LANDED_PER_HOME" \
--argjson backlog "$1" \
--argjson tasks "$2" '
--argjson tasks "$2" \
--argjson doing_cap "$FM_DOING_CHAR_CAP" '
def trunc($n):
tostring | gsub("\\s+"; " ")
| if length > $n then .[:$n] + "…" else . end;
# Word-boundary-aware cousin of trunc(), for the "doing" field only: a
# published status text should already fit a rendering surface without a
# mid-word cut. See FM_DOING_CHAR_CAP in
# bin/fm-classify-lib.sh for the sourced cap and the bash twin
# (fm_doing_truncate) this mirrors.
def doing_trunc($n):
tostring | gsub("\\s+"; " ") as $s
| if ($s | length) <= $n then $s
else ($s[0:$n]) as $cut
| ($cut | split(" ")) as $words
| (if ($words | length) > 1 then ($words[0:-1] | join(" ")) else "" end) as $boundary
| (if ($boundary | length) >= (($n * 3) / 5 | floor) and ($boundary | length) > 0
then $boundary else $cut end) + "…"
end;
([ $backlog.records[]?
| select((.state == "in_flight" or .state == "queued") and (.structured | not)) ]) as $unstructured_current
| ([ $backlog.records[]? | select(.state == "in_flight" and .structured) ]) as $owned_in_flight
Expand Down Expand Up @@ -663,7 +679,7 @@ secondmate_home_summary_json() { # <backlog-json> <tasks-json>
| $tasks[]
| select(.id == $work.id and .current_state.state == "working")
| {id,kind,state:.current_state.state,source:.current_state.source,
doing:((.current_state.detail // "") | trunc(120))} ]) as $active_all
doing:((.current_state.detail // "") | doing_trunc($doing_cap))} ]) as $active_all
| ($captain_holds_all
+ ([ $tasks[] as $t | ($t.hints.open_decisions // [])[]
| {id:$t.id,key,verb,summary:(.summary | trunc(160)),reason:null,source:"status"} ])) as $decisions_all
Expand Down
Loading
Loading