Skip to content

Commit e3819a0

Browse files
scripts+docs: R21 Task B — daily upstream-PR nudge runbook (#484)
Adds scripts/nudge-upstream-prs.sh + companion runbook so SBO3L's 4 open upstream PRs/issues get a daily check-in without manual overhead. Catches new comments within 24h + bumps politely once a PR has gone 7+ days quiet. What's watched ============== ensdomains/ensips#71 (ENSIP-26 spec PR) ensdomains/ensips/72 (ENSIP-26 design-Qs discussion issue) Uniswap/universal-router#477 (per-command policy-guarded swap) KeeperHub/cli#57 (IP-1 envelope protocol) The list is hardcoded near top of the script; new targets append in `repo:kind:number:label` form. What the script does ==================== Per-target: 1. gh api repos/$REPO/{pulls|issues}/$NUMBER for metadata 2. Compute "days since last update" from updated_at 3. Pull comments filtered to last 24h, surface new commenters + first 120 chars of each 4. (--bump mode) if quiet ≥ 7d AND no recent self-bump (cooldown), POST a polite "bumping for visibility" comment Safety rails on --bump: - won't bump closed PRs - won't bump a PR we already-bumped within the last 7d - won't auto-reply to comments (manual response only) - read-mostly: only write op is the 7d-cooldown-gated bump Cron suggestion =============== 0 9 * * * cd /path && ./scripts/nudge-upstream-prs.sh --bump > /tmp/sbo3l-nudge.log 2>&1 Exit codes for orchestration: 0 = new activity in last 24h 1 = no new activity (cron can skip downstream notify) 2 = arg parse error Why this matters ================ Per memory `competitor_intel_2026-05-03.md`: Luca (KH) said "going dark for engineering Qs." Bumping is the right tool when sync feedback isn't available — keeps the discussion thread alive without being pushy. For judges scoring sponsor-relationships: visible engagement hardens the "active community participant" claim vs a fire-and-forget upstream PR. Tested locally: dry run reports correctly across all 4 watched targets. Bash syntax clean (`bash -n`). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 86105df commit e3819a0

2 files changed

Lines changed: 375 additions & 0 deletions

File tree

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Upstream-PR daily engagement runbook
2+
3+
> **Audience:** Daniel.
4+
> **Outcome:** every open SBO3L upstream PR/issue gets a daily
5+
> automated check; new comments surface within 24h; quiet PRs get
6+
> a polite nudge once they cross 7 days.
7+
> **Cost:** $0. Pure `gh` CLI + cron.
8+
9+
## Why this exists
10+
11+
R19+R20 shipped 4 upstream PRs/issues — silence is the default
12+
state for community PRs at maintainer organisations like
13+
ENS/Uniswap/KeeperHub. Without proactive engagement:
14+
15+
1. New comments can sit unread for days, killing momentum.
16+
2. Long-quiet PRs trend toward "stale → ignored → closed."
17+
3. Judges scoring sponsor-relationships see a snapshot of the
18+
discussion thread; visible activity > silent waiting.
19+
20+
The script + cron close those gaps without adding manual work
21+
to Daniel's day.
22+
23+
## What's watched
24+
25+
| # | Repo | Number | Kind | Description |
26+
|---|---|---|---|---|
27+
| 1 | `ensdomains/ensips` | 71 | PR | ENSIP-26 — Agent Identity Records |
28+
| 2 | `ensdomains/ensips` | 72 | issue | ENSIP-26 design-Qs discussion (5 specific Qs) |
29+
| 3 | `Uniswap/universal-router` | 477 | PR | Per-command policy-guarded swap pattern |
30+
| 4 | `KeeperHub/cli` | 57 | PR | IP-1 envelope protocol proposal |
31+
32+
The list is hardcoded in `scripts/nudge-upstream-prs.sh` near the
33+
top — `WATCH_TARGETS=(...)`. Add new PRs/issues by appending a
34+
line in `repo:kind:number:label` form.
35+
36+
## What the script does
37+
38+
```bash
39+
./scripts/nudge-upstream-prs.sh # report only (read-only)
40+
./scripts/nudge-upstream-prs.sh --bump # report + bump-comment when idle ≥ 7d
41+
```
42+
43+
Per-target, the script:
44+
45+
1. Queries `gh api repos/$REPO/{pulls|issues}/$NUMBER` for
46+
metadata.
47+
2. Computes "days since last update" from the `updated_at`
48+
timestamp.
49+
3. Pulls comments via `gh api repos/$REPO/issues/$NUMBER/comments`
50+
filtered to the last 24h. Surfaces new commenters + first 120
51+
chars of each comment.
52+
4. **In `--bump` mode:** if quiet ≥ 7 days AND we haven't
53+
already-bumped within the last 7 days (cooldown check), posts
54+
a polite "bumping for visibility" comment.
55+
56+
The bump body is short + non-pushy:
57+
58+
```
59+
Bumping this for visibility -- happy to address any feedback or iterate
60+
on the design questions if the maintainers have time. The SBO3L reference
61+
implementation continues shipping (CI green, adapters publishing) so no
62+
rush on our end; this is just a nudge in case the thread got buried.
63+
64+
If there is a different forum (Discord, Telegram, weekly call) where this
65+
kind of proposal gets discussed, happy to redirect.
66+
```
67+
68+
Tone is "we're shipping, just want to know if there's a better
69+
channel" — not "review my PR pls." Keeps the maintainer
70+
relationship friendly even on PRs that ultimately don't merge.
71+
72+
## Cron setup (recommended)
73+
74+
Add to your crontab:
75+
76+
```bash
77+
# 9am daily, bump-comment if any PR has gone 7+ days quiet
78+
0 9 * * * cd /path/to/SBO3L-ethglobal-openagents-2026 && \
79+
./scripts/nudge-upstream-prs.sh --bump > /tmp/sbo3l-nudge.log 2>&1
80+
```
81+
82+
Or, if you prefer manual daily runs:
83+
84+
```bash
85+
# Add to your shell rc file (.bashrc / .zshrc)
86+
alias nudge='cd /path/to/SBO3L-ethglobal-openagents-2026 && \
87+
./scripts/nudge-upstream-prs.sh --bump'
88+
```
89+
90+
Then run `nudge` once a day. Output is one screen, takes ~5 sec.
91+
92+
## Sample output (clean state)
93+
94+
```
95+
===================================================================
96+
SBO3L upstream-PR daily nudge — 2026-05-03T08:51:01Z
97+
quiet threshold: 7 days
98+
bump mode: report-only
99+
===================================================================
100+
101+
── ENSIP-26 spec PR (ensdomains/ensips#71, kind=pr) ──────────────
102+
state: open
103+
last activity: 2026-05-03T05:51:44Z (0d ago)
104+
→ no comments in last 24h
105+
106+
── ENSIP-26 design-Qs discussion (ensdomains/ensips#72, kind=issue) ──────────────
107+
state: open
108+
last activity: 2026-05-03T07:38:43Z (0d ago)
109+
→ no comments in last 24h
110+
111+
── Universal Router policy-guarded swap (Uniswap/universal-router#477, kind=pr) ──────────────
112+
state: open
113+
last activity: 2026-05-03T06:10:38Z (0d ago)
114+
→ no comments in last 24h
115+
116+
── KH IP-1 envelope protocol (KeeperHub/cli#57, kind=pr) ──────────────
117+
state: open
118+
last activity: 2026-05-03T06:15:09Z (0d ago)
119+
→ no comments in last 24h
120+
121+
===================================================================
122+
done.
123+
new activity in last 24h: no
124+
===================================================================
125+
```
126+
127+
## Sample output (active state — comments arrived)
128+
129+
```
130+
── ENSIP-26 spec PR (ensdomains/ensips#71, kind=pr) ──────────────
131+
state: open
132+
last activity: 2026-05-04T15:22:11Z (0d ago)
133+
→ 🆕 2 new comment(s) in last 24h
134+
→ recent commenters:
135+
- dhaiwat: This is great — Q3 (Ed25519 vs polymorphic) is the bigge...
136+
- ses.eth: I'd push back on Q1 — namespace prefix avoids the colli...
137+
```
138+
139+
When this happens: stop, read the comments, decide on a response.
140+
Don't auto-reply via cron; that's pushy + risks bad-tone replies.
141+
142+
## Sample output (bump fires — 7+ days quiet, --bump enabled)
143+
144+
```
145+
── KH IP-1 envelope protocol (KeeperHub/cli#57, kind=pr) ──────────────
146+
state: open
147+
last activity: 2026-05-10T14:00:00Z (8d ago)
148+
→ no comments in last 24h
149+
→ idle 8d ≥ 7d, posting bump comment
150+
→ ✅ bump comment posted
151+
```
152+
153+
Post-bump, the cooldown kicks in: subsequent runs won't bump
154+
again for another 7 days even if the thread stays quiet. Prevents
155+
turning the bump into spam.
156+
157+
## What `--bump` will NOT do (safety rails)
158+
159+
- ❌ Bump on a PR that already has a bump from B2JK-Industry within the last 7 days.
160+
- ❌ Bump on a closed PR (state ≠ `open`).
161+
- ❌ Bump on an unfetchable PR (auth issue, repo deleted, etc.).
162+
- ❌ Auto-reply to comments.
163+
- ❌ Force-merge or close anything.
164+
165+
The script is **read-mostly**; the only write op is the periodic
166+
bump comment, gated behind 3 conditions (open + idle ≥ 7d + no
167+
recent self-bump).
168+
169+
## Exit codes (for cron orchestration)
170+
171+
- **0** — at least one PR had new activity in last 24h.
172+
- **1** — no new activity (cron can short-circuit a downstream
173+
notify step).
174+
- **2** — argument parse error.
175+
- **non-zero from gh api** — auth or rate-limit failure on
176+
individual targets; script logs + continues.
177+
178+
## Adding a new PR/issue to watch
179+
180+
Edit the `WATCH_TARGETS` array in
181+
`scripts/nudge-upstream-prs.sh`:
182+
183+
```bash
184+
WATCH_TARGETS=(
185+
"ensdomains/ensips:pr:71:ENSIP-26 spec PR"
186+
"ensdomains/ensips:issue:72:ENSIP-26 design-Qs discussion"
187+
"Uniswap/universal-router:pr:477:Universal Router policy-guarded swap"
188+
"KeeperHub/cli:pr:57:KH IP-1 envelope protocol"
189+
"<owner>/<repo>:pr:<number>:<short label>" # ← add here
190+
)
191+
```
192+
193+
Format: `repo:kind:number:label` separated by colons. Avoid
194+
colons in the label (use dashes / dots).
195+
196+
## Why this matters (judge-grade impact)
197+
198+
- **Sponsor relationships** — visible engagement on upstream PRs
199+
hardens "we're an active community participant" claim. Per
200+
competitor intel memory: Luca (KH) said "going dark for
201+
engineering Qs" — bumping is the right tool when sync feedback
202+
isn't available.
203+
- **Truthfulness** — every "we proposed X to upstream + we're
204+
driving the discussion" claim becomes resolvable: a judge can
205+
see the bump-comment timestamps in the upstream thread.
206+
- **Submission posture** — at submission time, the snapshot of
207+
each upstream PR shows recent SBO3L activity (either a real
208+
comment-and-respond or a documented bump), which reads as
209+
"engaged" rather than "fire-and-forget."
210+
211+
## Related artifacts
212+
213+
- [`docs/proof/ensip-upstream-pr.md`](../proof/ensip-upstream-pr.md) — judge evidence for ENSIP-26
214+
- [`docs/proof/ensip-followup-issue.md`](../proof/ensip-followup-issue.md) — judge evidence for the design-Qs issue
215+
- [`docs/proof/uniswap-universal-router-pr.md`](../proof/uniswap-universal-router-pr.md) — UR PR evidence
216+
- [`docs/proof/kh-protocol-pr.md`](../proof/kh-protocol-pr.md) — KH PR evidence

scripts/nudge-upstream-prs.sh

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#!/usr/bin/env bash
2+
#
3+
# nudge-upstream-prs.sh — daily check on SBO3L's open upstream PRs +
4+
# discussion issues. Run via cron; outputs a one-screen summary + (if
5+
# 7+ days idle) appends a polite "bumping for visibility" follow-up
6+
# comment.
7+
#
8+
# What it watches (R21 Task B, 2026-05-03):
9+
# - ensdomains/ensips#71 (ENSIP-26 — Agent Identity Records)
10+
# - ensdomains/ensips/issues/72 (ENSIP-26 — 5 design Qs discussion)
11+
# - Uniswap/universal-router#477 (per-command policy-guarded swap)
12+
# - KeeperHub/cli#57 (IP-1 envelope protocol proposal)
13+
#
14+
# Why a script (not a manual workflow): all four PRs/issues are
15+
# open-loop — reviewers may not respond for days. Running this
16+
# daily catches new comments early + nudges politely once the
17+
# 7-day quiet threshold trips, without requiring Daniel to remember
18+
# to check.
19+
#
20+
# Usage:
21+
# ./scripts/nudge-upstream-prs.sh # report only
22+
# ./scripts/nudge-upstream-prs.sh --bump # report + bump-comment if idle ≥ 7d
23+
# ./scripts/nudge-upstream-prs.sh --quiet-since=N # override the 7-day threshold to N days
24+
#
25+
# Output:
26+
# stdout = human-readable per-PR status
27+
# exit 0 = at least one PR has new activity since last 24h
28+
# exit 1 = no new activity (cron can short-circuit downstream notify if 1)
29+
#
30+
# Cron suggestion (Daniel-side):
31+
# 0 9 * * * /path/to/scripts/nudge-upstream-prs.sh --bump > /tmp/nudge.log 2>&1
32+
# (9am daily; bump-comment if any PR has gone 7+ days quiet)
33+
#
34+
# Requires:
35+
# - gh CLI (authenticated as B2JK-Industry / a maintainer)
36+
# - jq
37+
38+
set -euo pipefail
39+
40+
QUIET_DAYS=7
41+
BUMP=0
42+
43+
for arg in "$@"; do
44+
case "$arg" in
45+
--bump) BUMP=1 ;;
46+
--quiet-since=*) QUIET_DAYS="${arg#*=}" ;;
47+
*) echo "unknown arg: $arg" >&2; exit 2 ;;
48+
esac
49+
done
50+
51+
# (repo, kind, number, label) tuples for the 4 watch targets.
52+
# kind = "pr" or "issue".
53+
WATCH_TARGETS=(
54+
"ensdomains/ensips:pr:71:ENSIP-26 spec PR"
55+
"ensdomains/ensips:issue:72:ENSIP-26 design-Qs discussion"
56+
"Uniswap/universal-router:pr:477:Universal Router policy-guarded swap"
57+
"KeeperHub/cli:pr:57:KH IP-1 envelope protocol"
58+
)
59+
60+
NEW_ACTIVITY_FOUND=0
61+
TODAY_UNIX=$(date +%s)
62+
QUIET_THRESHOLD_UNIX=$((TODAY_UNIX - QUIET_DAYS * 86400))
63+
RECENT_THRESHOLD_UNIX=$((TODAY_UNIX - 86400)) # last 24h
64+
65+
# Polite follow-up template. Kept short + non-pushy. Built with
66+
# printf to avoid heredoc-vs-command-substitution quoting traps.
67+
BUMP_BODY="Bumping this for visibility -- happy to address any feedback or iterate on the design questions if the maintainers have time. The SBO3L reference implementation continues shipping (CI green, adapters publishing) so no rush on our end; this is just a nudge in case the thread got buried.
68+
69+
If there is a different forum (Discord, Telegram, weekly call) where this kind of proposal gets discussed, happy to redirect."
70+
71+
echo "==================================================================="
72+
echo " SBO3L upstream-PR daily nudge — $(date -u +%Y-%m-%dT%H:%M:%SZ)"
73+
echo " quiet threshold: ${QUIET_DAYS} days"
74+
echo " bump mode: $([ $BUMP = 1 ] && echo enabled || echo report-only)"
75+
echo "==================================================================="
76+
77+
for tuple in "${WATCH_TARGETS[@]}"; do
78+
IFS=":" read -r REPO KIND NUMBER LABEL <<<"$tuple"
79+
echo
80+
echo "── $LABEL ($REPO#$NUMBER, kind=$KIND) ──────────────"
81+
82+
if [ "$KIND" = "pr" ]; then
83+
ENDPOINT="repos/$REPO/pulls/$NUMBER"
84+
COMMENTS_ENDPOINT="repos/$REPO/issues/$NUMBER/comments"
85+
else
86+
ENDPOINT="repos/$REPO/issues/$NUMBER"
87+
COMMENTS_ENDPOINT="repos/$REPO/issues/$NUMBER/comments"
88+
fi
89+
90+
META=$(gh api "$ENDPOINT" 2>/dev/null) || {
91+
echo " ERROR: gh api failed for $ENDPOINT — auth or repo access issue"
92+
continue
93+
}
94+
95+
STATE=$(echo "$META" | jq -r '.state')
96+
UPDATED_AT=$(echo "$META" | jq -r '.updated_at')
97+
UPDATED_UNIX=$(date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$UPDATED_AT" +%s 2>/dev/null \
98+
|| date -u -d "$UPDATED_AT" +%s 2>/dev/null)
99+
DAYS_SINCE_UPDATE=$(( (TODAY_UNIX - UPDATED_UNIX) / 86400 ))
100+
HAS_REVIEWS=$(echo "$META" | jq -r '.review_comments // 0')
101+
102+
echo " state: $STATE"
103+
echo " last activity: $UPDATED_AT (${DAYS_SINCE_UPDATE}d ago)"
104+
105+
if [ "$STATE" != "open" ]; then
106+
echo " → not open, skipping"
107+
continue
108+
fi
109+
110+
# New comments in the last 24h?
111+
NEW_COMMENT_COUNT=$(gh api "$COMMENTS_ENDPOINT" --paginate \
112+
--jq '[.[] | select(.created_at > (now - 86400 | strftime("%Y-%m-%dT%H:%M:%SZ")))] | length' \
113+
2>/dev/null || echo "0")
114+
115+
if [ "$NEW_COMMENT_COUNT" -gt 0 ] 2>/dev/null; then
116+
echo " → 🆕 $NEW_COMMENT_COUNT new comment(s) in last 24h"
117+
echo " → recent commenters:"
118+
gh api "$COMMENTS_ENDPOINT" --paginate \
119+
--jq '.[] | select(.created_at > (now - 86400 | strftime("%Y-%m-%dT%H:%M:%SZ"))) | " - \(.user.login): \(.body | .[0:120])"' 2>/dev/null
120+
NEW_ACTIVITY_FOUND=1
121+
else
122+
echo " → no comments in last 24h"
123+
fi
124+
125+
# Bump if idle ≥ QUIET_DAYS AND --bump flag set.
126+
if [ $BUMP = 1 ] && [ "$DAYS_SINCE_UPDATE" -ge "$QUIET_DAYS" ]; then
127+
# Avoid bump-spam: only bump if the most recent comment is NOT
128+
# already a B2JK-Industry bump within the last QUIET_DAYS.
129+
LAST_BUMP_FROM_US=$(gh api "$COMMENTS_ENDPOINT" --paginate \
130+
--jq '[.[] | select(.user.login == "B2JK-Industry" and (.body | startswith("Bumping this for visibility")))] | last | .created_at // empty' \
131+
2>/dev/null || echo "")
132+
133+
if [ -n "$LAST_BUMP_FROM_US" ]; then
134+
LAST_BUMP_UNIX=$(date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$LAST_BUMP_FROM_US" +%s 2>/dev/null \
135+
|| date -u -d "$LAST_BUMP_FROM_US" +%s 2>/dev/null)
136+
DAYS_SINCE_BUMP=$(( (TODAY_UNIX - LAST_BUMP_UNIX) / 86400 ))
137+
if [ "$DAYS_SINCE_BUMP" -lt "$QUIET_DAYS" ]; then
138+
echo " → already bumped ${DAYS_SINCE_BUMP}d ago — skipping (bump cooldown)"
139+
continue
140+
fi
141+
fi
142+
143+
echo " → idle ${DAYS_SINCE_UPDATE}d ≥ ${QUIET_DAYS}d, posting bump comment"
144+
gh api -X POST "$COMMENTS_ENDPOINT" \
145+
-f body="$BUMP_BODY" >/dev/null \
146+
&& echo " → ✅ bump comment posted" \
147+
|| echo " → ❌ bump comment FAILED (auth or rate limit?)"
148+
elif [ $BUMP = 1 ]; then
149+
echo " → idle ${DAYS_SINCE_UPDATE}d < ${QUIET_DAYS}d, no bump needed"
150+
fi
151+
done
152+
153+
echo
154+
echo "==================================================================="
155+
echo " done."
156+
echo " new activity in last 24h: $([ $NEW_ACTIVITY_FOUND = 1 ] && echo yes || echo no)"
157+
echo "==================================================================="
158+
159+
[ $NEW_ACTIVITY_FOUND = 1 ] && exit 0 || exit 1

0 commit comments

Comments
 (0)