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: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ If a problem surfaces during the run, the orchestrator's reply is: "I saw X. Wan

Workers (`super-build`, `super-qa`, `super-review`) share the dispatcher's `gh` token bucket. They MUST:

- Required GitHub reads use `.claude/bin/super-board-github-read.py`; exit 79 stops the run.
- Check its `--check` before GitHub writes, migrations, merges, and new dispatch; preserve work when halted.
- Source `.claude/bin/super-board-gh-guard.sh` (`scripts/` in this repo) at worker start.
- Call `sb_gh_guard_check 200` before any burst of `gh` calls.
- Prefer local `git blame` / `git log` over `gh api graphql` for any sub-agent that doesn't need fresh state.
Expand Down
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ copy_file() {
fi
}

for script in super-board-run.sh super-board-gh-guard.sh super-board-status.py super-board-wave-plan.sh super-board-deps.sh super-board-preflight.sh super-board-merge-gate.sh super-board-merge-policy.py super-board-approval.py super-board-env-check.sh super-board-agents-md.py super-board-settings.py super-board-setup.py super-board-usage.sh super-board-pr-body.sh super-review-file-refactor.sh super-qa-file-bug.sh super-board-stop.sh; do
for script in super-board-github-read.py super-board-run.sh super-board-gh-guard.sh super-board-status.py super-board-wave-plan.sh super-board-deps.sh super-board-preflight.sh super-board-merge-gate.sh super-board-merge-policy.py super-board-approval.py super-board-env-check.sh super-board-agents-md.py super-board-settings.py super-board-setup.py super-board-usage.sh super-board-pr-body.sh super-review-file-refactor.sh super-qa-file-bug.sh super-board-stop.sh; do
if [ -f "$REPO_ROOT/scripts/$script" ]; then
copy_file "$REPO_ROOT/scripts/$script" "$TARGET/.claude/bin/$script"
chmod +x "$TARGET/.claude/bin/$script"
Expand Down
19 changes: 14 additions & 5 deletions scripts/super-board-approval.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@
from __future__ import annotations

import argparse
import importlib.util
from pathlib import Path
from datetime import datetime
import hashlib
import json
import re
import subprocess
import sys

_reader_spec = importlib.util.spec_from_file_location("github_read", Path(__file__).with_name("super-board-github-read.py"))
github_read = importlib.util.module_from_spec(_reader_spec)
_reader_spec.loader.exec_module(github_read)

PREFIX = "approval-request: "
SHA = re.compile(r"[0-9a-f]{40}\Z")
SCOPE = re.compile(r"[0-9a-f]{64}\Z")
Expand Down Expand Up @@ -129,10 +135,9 @@ def __init__(self, repo):

@staticmethod
def read(*args):
result = subprocess.run(["gh", *args], capture_output=True, text=True, timeout=30)
if result.returncode:
raise ValueError("GitHub evidence could not be read")
return json.loads(result.stdout)
kind = ("approval" if "graphql" in args else "comments" if "--paginate" in args
else "permission" if args[-1].endswith("/permission") else "json")
return github_read.read(list(args), kind)[1]

def permission(self, login):
if not re.fullmatch(r"[A-Za-z0-9_-]+(?:\[bot\])?", login):
Expand Down Expand Up @@ -245,4 +250,8 @@ def main():


if __name__ == "__main__":
main()
try:
main()
except github_read.ReadHalted as error:
print(f"GitHub read halt: {error}", file=sys.stderr)
sys.exit(github_read.HALTED)
8 changes: 4 additions & 4 deletions scripts/super-board-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
# The sweep must never free one of those; only a person can.
set -euo pipefail

HERE=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO=""; LIMIT="200"; ONLY=""; FROM=""
while [ $# -gt 0 ]; do
case "$1" in
Expand All @@ -80,17 +81,16 @@ else
# lives in a COMMENT and the REST list cannot return comments. One query gets
# bodies and comments together; `gh issue view` per issue would be one REST
# call each, and a 35-card board burns that budget for no reason.
ISSUES=$(gh api graphql --paginate --slurp -f query='
PAGES=$(python3 "$HERE/super-board-github-read.py" --kind issues -- api graphql --paginate --slurp -f query='
query($owner:String!,$repo:String!,$endCursor:String){
repository(owner:$owner,name:$repo){
issues(states:OPEN,first:100,after:$endCursor){
pageInfo{ hasNextPage endCursor }
nodes{ number title body state labels(first:30){ nodes{ name } } comments(last:8){ nodes{ body } } }
}
}
}' -F owner="${REPO%%/*}" -F repo="${REPO##*/}" 2>/dev/null \
| jq '[ .[].data.repository.issues.nodes[] ]') || {
echo "could not read issues for $REPO" >&2; exit 69; }
}' -F owner="${REPO%%/*}" -F repo="${REPO##*/}") || exit $?
ISSUES=$(printf '%s' "$PAGES" | jq '[ .[].data.repository.issues.nodes[] ]')
fi

APPROVAL_REPO="$REPO"
Expand Down
6 changes: 5 additions & 1 deletion scripts/super-board-gh-guard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ SB_GH_GUARD_BUDGET_DEFAULT=150 # per-worker soft cap on gh calls
SB_GH_GUARD_SUBAGENT_BUDGET=50 # per adversarial-mode sub-agent cap
SB_GH_GUARD_STATE_FILE="${SB_GH_GUARD_STATE_FILE:-${TMPDIR:-/tmp}/super-board-gh-budget-$$}"

SB_GITHUB_READ="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/super-board-github-read.py"
sb_gh_required_read() { local kind="$1"; shift; python3 "$SB_GITHUB_READ" --kind "$kind" -- "$@"; }
sb_gh_halt_check() { python3 "$SB_GITHUB_READ" --check; }

sb_gh_guard_check() {
# Sleep until GraphQL quota recovers. Also checks REST.
# Arg 1: optional minimum-remaining threshold (default 200).
local min="${1:-$SB_GH_GUARD_MIN_REMAINING_DEFAULT}"
local payload graphql_remaining graphql_reset rest_remaining now wait
payload=$(gh api rate_limit 2>/dev/null || echo '{"resources":{"graphql":{"remaining":5000,"reset":0},"core":{"remaining":5000,"reset":0}}}')
payload=$(sb_gh_required_read quota api rate_limit) || return $?
graphql_remaining=$(echo "$payload" | jq -r '.resources.graphql.remaining // 5000')
rest_remaining=$(echo "$payload" | jq -r '.resources.core.remaining // 5000')

Expand Down
Loading
Loading