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
4 changes: 2 additions & 2 deletions scripts/super-board-merge-policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
PR. Every env in allowed_envs with a command runs (a command of "-" means the
deploy pipeline applies it: nothing to run). target_env not in allowed_envs, or
allowed with no command → needs you. migrations.human_steps (any migration PR)
and `needs-you: <command>` lines in the PR body (any PR) → needs you.
and `needs-you: <command>` lines in the PR body, bare or inside the status alert (any PR) → needs you.
Globs: `**/` any directories (incl. none), `**` anything, `*` within one path
segment, `?` one character.
"""
Expand Down Expand Up @@ -188,7 +188,7 @@ def main() -> int:
needs.append(f"{cmd or '<your ' + target + ' migrate command>'} # {target}: the base deploys here and it is not in allowed_envs")
needs += [str(s) for s in mig.get("human_steps") or []]
for line in body.splitlines():
m = re.match(r"^\s*(?:[-*]\s*)?needs-you:\s*(.+?)\s*$", line, re.I)
m = re.match(r"^\s*(?:>\s*)?(?:[-*]\s*)?needs-you:\s*(.+?)\s*$", line, re.I)
if m and m.group(1).lower() != "done":
needs.append(m.group(1).strip("`"))

Expand Down
20 changes: 15 additions & 5 deletions scripts/super-board-pr-body.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
#
# super-board-pr-body.sh --pr <N> --block <name> --expect-head <sha> \
# (--body-file <md> | --append-file <md>) [--repo o/r] [--dry-run]
# super-board-pr-body.sh --skeleton # empty body with every marker, in order
# super-board-pr-body.sh --skeleton # empty body with every marker, in order (history folded)
# super-board-pr-body.sh --time [--config <cfg.json>] # "Oct 2, 10:02 EDT" in config timezone
#
# Blocks, in body order: redcheck status problem solution ac history visual risk.
# A block that is missing is inserted at its place in that order.
#
# --body-file replaces the block's content.
# --append-file appends lines inside the block (history rows).
# --append-file appends lines inside the block (history rows), above a closing </details>.
# --expect-head the PR head you based this edit on. The script refuses (exit 6) when
# the PR head is no longer that commit: the block would describe code
# that is not the code on the PR. Required unless --dry-run.
Expand Down Expand Up @@ -49,6 +49,10 @@ done
if [ "$MODE" = "skeleton" ]; then
for b in $BLOCKS; do
[ "$b" = redcheck ] && continue # only when a check was red
if [ "$b" = history ]; then # folded; --append-file keeps rows inside
printf '<!-- sb:history -->\n<details><summary>Iteration history</summary>\n\n| Lane | Done | Time | Details |\n|---|---|---|---|\n\n</details>\n<!-- /sb:history -->\n\n'
continue
fi
printf '<!-- sb:%s -->\n<!-- /sb:%s -->\n\n' "$b" "$b"
done
exit 0
Expand Down Expand Up @@ -113,13 +117,19 @@ if span:
inner = body[span[0] + len(open_m):span[1] - len(close_m)].strip("\n")
if mode == "append":
lines = inner.split("\n") if inner else []
new_lines = [l for l in content.split("\n")]
# A folded block keeps its rows inside the fold, above </details>
# (and above the blank line GitHub needs before it).
tail = []
if lines and lines[-1].strip() == "</details>":
tail = [lines.pop()]
while lines and not lines[-1].strip():
tail.insert(0, lines.pop())
# Idempotent append: a row already present is not added twice.
for l in new_lines:
for l in content.split("\n"):
if l.strip() and l in lines:
continue
lines.append(l)
inner = "\n".join(lines).strip("\n")
inner = "\n".join(lines + tail).strip("\n")
else:
inner = content
out = body[:span[0]] + f"{open_m}\n{inner}\n{close_m}" + body[span[1]:]
Expand Down
5 changes: 4 additions & 1 deletion skills/super-board/references/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,13 @@ read it — re-read, then redo the edit. Builder opens the PR with every block f
<!-- /sb:ac -->

<!-- sb:history -->
## Iteration history
<details><summary>Iteration history</summary>

| Lane | Done | Time | Details |
|---|---|---|---|
| 🔨 builder | ✅ | <super-board-pr-body.sh --time> | draft · `<sha>` |

</details>
<!-- /sb:history -->

<!-- sb:risk -->
Expand Down
23 changes: 18 additions & 5 deletions skills/super-board/references/writing-standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,14 @@ Each block closes with `<!-- /sb:<name> -->`.
| merged | `> [!TIP]` | `✅ Merged · \`9c41e07\`` |
| blocked, red check | `> [!WARNING]` | `🛑 Blocked · needs Eric` |

A partial PR writes `Refs #N`, never `Closes #N`.
A partial PR writes `Refs #N`, never `Closes #N`. A human-only step can ride inside the
alert, one `> needs-you: <command>` line each; the merge gate reads it there or under Risk:

```
> [!WARNING]
> 🛑 Blocked · needs you · ✅ 2/3 AC · Closes #790 · head `3f9c2a1`
> needs-you: `npm run db:migrate:live`
```

**Problem** — lettered steps under Where. GitHub has no lettered-list syntax, so type the letter:

Expand Down Expand Up @@ -156,17 +163,22 @@ goes in Before | After.
↳ not verified: needs prod deploy · Eric
```

**Iteration history** — local time from config `timezone` (onboard sets it to the machine's
zone). Get the stamp with `super-board-pr-body.sh --time --config <cfg>`.
**Iteration history** — folded in `<details>`, so the reader sees Problem to Risk first.
Local time from config `timezone` (onboard sets it to the machine's zone). Get the stamp with
`super-board-pr-body.sh --time --config <cfg>`. `--append-file` keeps a new row inside the
fold, above the blank line and `</details>`; an older unfolded history still appends at its end.

```
## Iteration history
<details><summary>Iteration history</summary>

| Lane | Done | Time | Details |
|---|---|---|---|
| 🔨 builder | ✅ | Oct 2, 10:02 EDT | draft · `3f9c2a1` |
| 🔍 qa | ❌ v1 | Oct 2, 10:40 EDT | mobile hidden · `runs/issue-790-qa-v1/` |
| 🔍 qa | ✅ v2 | Oct 2, 11:55 EDT | `runs/issue-790-qa-v2/` |
| 🧐 reviewer | ✅ merged | Oct 2, 12:20 EDT | squash `9c41e07` |

</details>
```

**Before | After** — two columns, one row per viewport, both shas in the header. Images are
Expand All @@ -193,7 +205,8 @@ here too.
Levels: 🟢 Low · 🟡 Medium · 🔴 High. **Door** is two-way (a revert undoes it) or one-way
(hard to undo: DROP, rename, data rewrite, public API, sent email); a one-way door is never 🟢.
**Blast radius** is a short label (`none`, `page`, `billing`, `all users`) and one line on who
breaks. A `needs-you: <command>` line (merge gate reads it) goes under Risk.
breaks. A `needs-you: <command>` line (merge gate reads it) goes under Risk or inside the
status alert.

## 3 · Ticket

Expand Down
15 changes: 15 additions & 0 deletions tests/test-merge-gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,21 @@ needs-you: `npm run backfill -- --env live`')" gate --expect-head "$SHA" 2>/dev/
echo "$OUT" | grep -q "^needs-you: npm run backfill -- --env live" || fail "the declared step must be printed, got: $OUT"
teardown

# 22b — the status block carries needs-you inside its alert (writing-standard.md § 2).
head_setup
RC=0; OUT=$(STUB_OID="$SHA" STUB_META="$(meta "" src/x.ts '<!-- sb:status -->
> [!WARNING]
> 🛑 Blocked · needs you · Closes #7 · head `3f9c2a1`
> needs-you: `npm run backfill -- --env live`
<!-- /sb:status -->

<!-- sb:problem -->
## Problem
<!-- /sb:problem -->')" gate --expect-head "$SHA" 2>/dev/null) || RC=$?
[ "$RC" -eq 8 ] || fail "a needs-you line in the status alert should exit 8, got $RC"
echo "$OUT" | grep -q "^needs-you: npm run backfill -- --env live" || fail "the status-block step must be printed, got: $OUT"
teardown

# 23 — metadata the gate cannot read fails safe to a human (7), never to merge.
head_setup
RC=0; STUB_OID="$SHA" STUB_META='not json' gate --expect-head "$SHA" >/dev/null 2>&1 || RC=$?
Expand Down
26 changes: 26 additions & 0 deletions tests/test-pr-body.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,31 @@ has "row appended" "$(cat "$BODY")" "runs/issue-7-qa-v1/"
is "re-append is a no-op" "unchanged" "$(run --pr 7 --block history --expect-head 3f9c2a1 --append-file "$WORK/row.md")"
is "row appears once" "1" "$(grep -c 'runs/issue-7-qa-v1/' "$BODY")"

echo "── a folded history keeps new rows inside the fold"
cp "$BODY" "$WORK/unfolded.md"
cat > "$BODY" <<'FOLD'
<!-- sb:status -->
> [!NOTE]
> ⏳ In QA · Closes #7 · head `3f9c2a1`
<!-- /sb:status -->

<!-- sb:history -->
<details><summary>Iteration history</summary>

| Lane | Done | Time | Details |
|---|---|---|---|
| 🔨 builder | ✅ | Oct 2, 10:02 EDT | draft |

</details>
<!-- /sb:history -->
FOLD
run --pr 7 --block history --expect-head 3f9c2a1 --append-file "$WORK/row.md" >/dev/null
ROW=$(grep -n 'runs/issue-7-qa-v1/' "$BODY" | cut -d: -f1); END=$(grep -n '^</details>$' "$BODY" | cut -d: -f1)
[ -n "$ROW" ] && [ "$ROW" -lt "$END" ] && ok "row lands above </details>" || bad "row lands above </details>" "row < $END" "${ROW:-none}"
is "blank line kept before </details>" "" "$(sed -n "$((END - 1))p" "$BODY")"
is "folded re-append is a no-op" "unchanged" "$(run --pr 7 --block history --expect-head 3f9c2a1 --append-file "$WORK/row.md")"
cp "$WORK/unfolded.md" "$BODY"

echo "── refuses when the head moved"
cp "$BODY" "$WORK/before.md"
RC=0; run --pr 7 --block status --expect-head deadbee --body-file "$WORK/risk.md" >/dev/null || RC=$?
Expand All @@ -101,6 +126,7 @@ hasnt "dry run edits nothing" "$(cat "$GH_LOG")" "PATCH"
echo "── skeleton"
SK=$("$SCRIPT" --skeleton)
for b in status problem solution ac history visual risk; do has "skeleton has $b" "$SK" "<!-- sb:$b -->"; done
has "skeleton folds the history" "$SK" "<details><summary>Iteration history</summary>"

echo "── time in config timezone"
echo '{"timezone":"UTC"}' > "$WORK/cfg.json"
Expand Down
11 changes: 11 additions & 0 deletions tests/test_writing_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,17 @@ def test_pr_body_markers():
assert "## Not verified" not in run_md and "**Status:**" not in run_md


def test_history_is_folded_and_needs_you_sits_in_the_status_alert():
"""From the pr-skill template: history folds in <details>; a human step can ride the status alert."""
run_md = (REF / "run.md").read_text(encoding="utf-8")
for name, text in (("writing-standard.md", STANDARD), ("run.md", run_md)):
hist = text[text.index("<!-- sb:history -->"):text.index("<!-- /sb:history -->")] if "<!-- sb:history -->\n" in text else \
next(b for b in fenced(text) if "Iteration history" in b)
assert "<details><summary>Iteration history</summary>" in hist, f"{name} history is not folded"
assert hist.rstrip().endswith("</details>"), f"{name} history fold is not closed"
assert "> needs-you: " in STANDARD, "writing-standard.md shows no needs-you line in the status alert"


def test_risk_names_door_and_blast_radius():
"""Risk carries the merge danger: a two-way or one-way door and a blast radius."""
run_md = (REF / "run.md").read_text(encoding="utf-8")
Expand Down
Loading