Files
boshu2__agentops/scripts/session-pr-scope.sh
T
Bo 0dcbae450c fix(loop): re-enforce session-PR-scope hooklessly + fix stale hook refs (ag-o5xp #session-pr-scope) (#661)
## Problem

The session-PR-scope guardrail shipped as a PreToolUse hook
(`hooks/session-pr-counter.sh`, PR #362), but **PR #511 (the 3.0
hookless teardown) deleted all hooks**. Six workflow-discipline surfaces
were never updated — they still advertise the hook as a live, shipping
mechanism. That's a **stale active-surface promise + a lost reliability
guardrail** (surfaced by the ag-4hf7 Google-SRE audit:
"explainability/trust" turned inward).

## Hookless re-enforcement (the decision)

- **The mandatory layer already survives**: the `/evolve` post-mortem
checkpoint #6 (council-gated, cannot be bypassed) enforces session-scope
for autonomous loops — the actual failure mode. It only lacked a
canonical count source.
- **New `scripts/session-pr-scope.sh`** — the reusable hookless count +
verdict (`OK`/`WARN`/`BLOCK`), fail-open, robot-friendly
(`--json`/`--count`). It is now:
  - the checkpoint's `$session_pr_count` source, and
- the one-liner an **opt-in** hook wraps (re-author via the
hooks-authoring skill — AgentOps ships none).
- 8 bats cases (thresholds, block-mode exit code, fail-open); shellcheck
clean.

## Stale-doc truth (6 surfaces, all hedged)

CLAUDE.md · AGENTS-WORKFLOW.md · skills/ship-loop/SKILL.md · the evolve
postmortem-checkpoint (skills/, skills-codex/, packs overlay — kept in
parity; codex hashes regenerated for **evolve + ship-loop only**, the 7
pre-existing drifts left untouched).

## Ratchet (knowledge → a constraint)

Extend `check-hookless-cold-start.sh` to cover these workflow-discipline
surfaces — reversing the now-stale CLAUDE.md carve-out — so this exact
drift class **cannot recur**. Gate scope 5 → 10 surfaces; its bats test
stays green. (This is the "knowledge becomes a constraint" invariant the
whole ag-4hf7 thread identified as our edge — applied to the very gap
that thread surfaced.)

## Verification

- `bats tests/scripts/session-pr-scope.bats` → 8/8
- `bash scripts/check-hookless-cold-start.sh` → PASS (10 surfaces)
- `check-hookless-cold-start.bats` → 5/5 ·
`validate-codex-generated-artifacts.sh --scope head` → pass ·
wiring-closure → pass · shellcheck clean · overlay ≡ source

Closes-scenario: ag-o5xp#session-pr-scope
Bounded-context: BC3-Loop
Evidence: scripts/session-pr-scope.sh
2026-05-31 19:51:07 +00:00

95 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# session-pr-scope.sh — hookless session-PR-scope signal (ag-o5xp).
#
# Supersedes the removed PreToolUse hook `hooks/session-pr-counter.sh` (PR #362,
# soc-1aou), which was deleted in the 3.0 hookless teardown (#511). AgentOps 3.0
# ships no hooks; this is the hookless replacement for the session-scope count.
#
# Counts PRs the current GitHub user opened within a trailing window and emits a
# session-scope verdict (OK | WARN | BLOCK) against SESSION_PR_THRESHOLD. It is the
# canonical session-PR count source:
# - the /evolve post-mortem checkpoint reads it for $session_pr_count
# (skills/evolve/references/postmortem-checkpoint.md);
# - anyone wanting the old pre-creation signal can wrap it in an opt-in hook
# authored via the hooks-authoring skill (AgentOps ships none by default);
# - it can run manually or as a warn-only CI step.
#
# Fail-open: advisory tooling must never block on infrastructure failure. If `gh`
# or `jq` is unavailable, or the count cannot be parsed, it exits 0 with
# verdict=unknown.
#
# Usage:
# scripts/session-pr-scope.sh # human-readable verdict
# scripts/session-pr-scope.sh --json # {count,threshold,window_hours,verdict,over,block_mode}
# scripts/session-pr-scope.sh --count # just the integer count (for $session_pr_count)
#
# Env:
# SESSION_PR_THRESHOLD (default 5) — post-mortem checkpoint threshold
# AGENTOPS_SESSION_PR_WINDOW_HOURS (default 24) — "current session" window
# AGENTOPS_SESSION_PR_BLOCK (unset) — when =1, verdict BLOCK + exit 2 at count >= threshold
set -uo pipefail
THRESHOLD="${SESSION_PR_THRESHOLD:-5}"
WINDOW_HOURS="${AGENTOPS_SESSION_PR_WINDOW_HOURS:-24}"
BLOCK_MODE="${AGENTOPS_SESSION_PR_BLOCK:-}"
MODE="human"
case "${1:-}" in
--json) MODE="json" ;;
--count) MODE="count" ;;
""|--human) MODE="human" ;;
-h|--help) sed -n '2,33p' "$0"; exit 0 ;;
*) printf 'unknown arg: %s\n' "$1" >&2; exit 64 ;;
esac
emit_unknown() {
case "$MODE" in
json) printf '{"count":null,"threshold":%s,"window_hours":%s,"verdict":"unknown","over":false,"block_mode":%s}\n' \
"$THRESHOLD" "$WINDOW_HOURS" "$([ "$BLOCK_MODE" = 1 ] && echo true || echo false)" ;;
count) printf '0\n' ;;
human) printf 'session-pr-scope: verdict=unknown (gh/jq unavailable or unparseable; failing open)\n' ;;
esac
exit 0
}
command -v gh >/dev/null 2>&1 || emit_unknown
command -v jq >/dev/null 2>&1 || emit_unknown
SINCE_ISO="$(date -u -d "${WINDOW_HOURS} hours ago" +%FT%TZ 2>/dev/null || date -u +%FT%TZ)"
COUNT="$(gh pr list --search "author:@me created:>=${SINCE_ISO}" --state all --limit 100 --json number 2>/dev/null | jq -r 'length' 2>/dev/null)"
case "$COUNT" in
''|*[!0-9]*) emit_unknown ;;
esac
# Verdict: WARN once the count reaches threshold-1 (the next PR tips the session
# over); BLOCK only when over the threshold AND block mode is opted in.
WARN_AT=$((THRESHOLD - 1))
verdict="ok"
over=false
exit_code=0
if [ "$COUNT" -ge "$THRESHOLD" ]; then
over=true
if [ "$BLOCK_MODE" = "1" ]; then verdict="block"; exit_code=2; else verdict="warn"; fi
elif [ "$COUNT" -ge "$WARN_AT" ]; then
verdict="warn"
fi
reminder="Session-scope: ${COUNT} PR(s) in the last ${WINDOW_HOURS}h (threshold ${THRESHOLD}). Run a real post-mortem (/post-mortem --deep, council-gated) before the next PR — reactive-PR spirals are the dominant back-half failure mode (soc-waxr). The /evolve loop enforces this mechanically at checkpoint #6."
case "$MODE" in
count) printf '%s\n' "$COUNT"; exit 0 ;;
json)
printf '{"count":%s,"threshold":%s,"window_hours":%s,"verdict":"%s","over":%s,"block_mode":%s}\n' \
"$COUNT" "$THRESHOLD" "$WINDOW_HOURS" "$verdict" "$over" \
"$([ "$BLOCK_MODE" = 1 ] && echo true || echo false)"
exit "$exit_code" ;;
human)
case "$verdict" in
ok) printf 'session-pr-scope: OK — %s PR(s) in %sh (threshold %s)\n' "$COUNT" "$WINDOW_HOURS" "$THRESHOLD" ;;
warn) printf 'session-pr-scope: WARN — %s\n' "$reminder" ;;
block) printf 'session-pr-scope: BLOCK — %s\n' "$reminder" >&2 ;;
esac
exit "$exit_code" ;;
esac