This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit b7b5406561bf322359b3c254d8ae7d26ebd34ca4
Author: Stephen Mallette <[email protected]>
AuthorDate: Fri Oct 9 13:38:51 2026 -0400

    Record merged PRs when recording a root's landing
    
    Assisted-by: Claude Code:claude-opus-5-5
---
 .beads/PRIME.md      |  14 ++++---
 bin/beads-commits.py | 105 ++++++++++++++++++++++++++++++++++++++++-----------
 2 files changed, 92 insertions(+), 27 deletions(-)

diff --git a/.beads/PRIME.md b/.beads/PRIME.md
index 9c4acca371..0e35f6369b 100644
--- a/.beads/PRIME.md
+++ b/.beads/PRIME.md
@@ -201,7 +201,8 @@ unchanged into 3.8-dev and master, so what is recorded then 
stays correct.
 
 **Then suggest, confirm, record.** `--suggest` finds the commits from the 
root's own JIRA and PR
 records, lists what else landed in the same window, and writes nothing. Show 
what it returns, take the operator's corrections, and only then
-run the recording form. It refuses any sha not reachable from 
`origin/<branch>`, so a refusal
+run the recording form. It also writes a record for each merged PR GitHub ties 
to those
+commits, or relates the root to one another root already holds. It refuses any 
sha not reachable from `origin/<branch>`, so a refusal
 means the work has not actually landed — say so and leave the root open.
 
 **An `epic` root needs `bd close <root> --force`.** The commit record just 
written is a
@@ -257,10 +258,13 @@ a list, and `bd ready` cannot tell you anything useful 
about it.
   the **root** (`--parent <root>`), not of any other bead, and pin it. 
`bin/beads-commits.py`
   finds a root's JIRA and PR records among its children, so a record attached 
any other way
   is invisible to `--suggest`.
-- **Search first — duplicates are the risk.** `bd search <ref> --status all` 
matches
-  external refs; `bd query` has no field for them. When a second root needs an 
artifact that
-  already has a record, add `bd dep add <root> <record> -t related` rather 
than a duplicate.
-  `--suggest` on that root will not see it, so supply those commits by hand at 
merge.
+- **Search first — duplicates are the risk.** `bd query` has no field for 
external refs,
+  and `bd search <ref> --status all` finds a ticket id but not a URL. For any 
ref,
+  `bd search tp --external-contains <ref> --status all` works — `tp` matches 
every bead id.
+  The PR record is written at merge (section 4), not by hand. When a second 
root needs an
+  artifact that already has a record, add `bd dep add <root> <record> -t 
related` rather than
+  a duplicate. `--suggest` on that root will not see it, so supply those 
commits by hand at
+  merge.
 - Records are the only link between beads and code; commit messages carry no 
bead ID. The
   commit record written at merge (section 4) is what makes that link work in 
reverse —
   `bd query 'notes="<sha>"' --all` returns the record and its `parent`, for 
any commit in the
diff --git a/bin/beads-commits.py b/bin/beads-commits.py
index 229a875d90..b226480b3d 100755
--- a/bin/beads-commits.py
+++ b/bin/beads-commits.py
@@ -32,12 +32,19 @@ Creates one `record` bead per landing, under the root:
                                                     apache/tinkerpop#3578
     notes                  every sha, full length
 
+and, alongside it, a record for each merged pull request GitHub associates with
+those commits -- apache/tinkerpop#3578 -- unless one already exists. A PR is
+usually opened after the root, so nothing earlier in the workflow is reliably
+there to write its record. Recording the landing is the one step every root
+passes through, and by then GitHub knows which PR carried the commits. A PR
+recorded under another root is related to this one rather than duplicated.
+
 The shas go in `notes` because that is the one free-text field bd can query.
-`bd search` indexes title and id only, and `bd query` has no field for comments
-or design, so a sha recorded anywhere else is unreachable except by grepping an
-export. `bd query 'notes=...'` is a substring match, which is why the full
-40-character sha is stored: a short sha pasted from `git log --oneline` is a
-prefix of it, so both forms of the query find the bead. Quote the sha inside 
the
+`bd search` matches title, id and some external refs but never notes, and
+`bd query` has no field for comments or design, so a sha recorded anywhere else
+is unreachable except by grepping an export. `bd query 'notes=...'` is a
+substring match, which is why the full 40-character sha is stored: a short sha
+pasted from `git log --oneline` is a prefix of it, so both forms of the query 
find the bead. Quote the sha inside the
 query: most shas begin with a digit, and an unquoted one is lexed as a number 
and
 fails to parse, the same way an unquoted version label does.
 
@@ -66,7 +73,7 @@ Once the operator confirms, the same command records them:
 
 What this script guarantees is the part that is easy to get wrong: every sha is
 verified reachable from origin/<branch> before anything is written, and a 
re-run
-finds the existing record rather than creating a second one.
+finds the existing records rather than creating second ones.
 """
 
 import argparse
@@ -141,6 +148,56 @@ def pr_merge_commit(number):
     return ((json.loads(proc.stdout).get("mergeCommit") or {}).get("oid")) or 
None
 
 
+def merged_prs(shas, slug):
+    """Merged pull requests GitHub associates with these commits, by number.
+
+    A failed lookup is reported rather than fatal: the commit record is the 
part
+    that has to be right, and a missing PR record can be added by hand.
+    """
+    numbers = set()
+    for sha in shas:
+        proc = subprocess.run(
+            ["gh", "api", f"repos/{slug}/commits/{sha}/pulls",
+             "--jq", ".[] | select(.merged_at != null) | .number"],
+            capture_output=True, text=True)
+        if proc.returncode != 0:
+            print(f"gh could not list pull requests for {sha[:10]}: 
{proc.stderr.strip()}\n"
+                  "add any PR record by hand", file=sys.stderr)
+            continue
+        numbers.update(int(n) for n in proc.stdout.split())
+    return sorted(numbers)
+
+
+def record_pr(root, ref, dry_run):
+    """Make sure the PR record exists, under this root or related to it."""
+    # bd search does not match a ref of this shape reliably, so filter 
client-side.
+    existing = [bead for bead in as_list(bd("list", "--all", "--limit", "0"))
+                if bead.get("issue_type") == "record" and 
bead.get("external_ref") == ref]
+    for bead in existing:
+        if bead.get("parent") == root:
+            print(f"{bead['id']} already records {ref} under {root}")
+            return
+    if existing:
+        # PRIME.md section 6: a second root relates to the existing record.
+        bead = existing[0]
+        related = (bd_show(root) or {}).get("dependencies") or []
+        if any(dep.get("id") == bead["id"] for dep in related):
+            print(f"{bead['id']} records {ref} under {bead.get('parent')}; 
already related to {root}")
+        elif dry_run:
+            print(f"--dry-run: would relate {root} to {bead['id']} ({ref})")
+        else:
+            bd("dep", "add", root, bead["id"], "-t", "related")
+            print(f"related {root} to {bead['id']}, which records {ref} under 
{bead.get('parent')}")
+        return
+    if dry_run:
+        print(f"--dry-run: would create a record for {ref} under {root} and 
pin it")
+        return
+    created = as_list(bd("create", "--type=record", f"--parent={root}",
+                         f"--title={ref}", f"--external-ref={ref}"))
+    bd("update", created[0]["id"], "-s", "pinned")
+    print(f"created {created[0]['id']} for {ref} under {root}, pinned")
+
+
 def suggest(root, branch):
     """Print the commits that look like this root's, for the operator to 
confirm.
 
@@ -305,7 +362,7 @@ def main():
     source.add_argument("--suggest", action="store_true",
                         help="print the commits that look like this root's; 
writes nothing")
     parser.add_argument("--fetch", action="store_true",
-                        help="refresh origin/<branch> first; otherwise nothing 
touches the network")
+                        help="refresh origin/<branch> first; otherwise only gh 
touches the network")
     parser.add_argument("--dry-run", action="store_true", help="print the 
record, write nothing")
     args = parser.parse_args()
 
@@ -326,23 +383,27 @@ def main():
     lines += [f"  {sha}  {subject(sha)}" for sha in shas]
     notes = "\n".join(lines)
 
+    # --pr already names the pull request; otherwise ask GitHub which carried 
these.
+    prs = [args.pr] if args.pr else merged_prs(shas, slug)
+
     found = existing_record(args.root, tip)
     if found:
-        print(f"{found['id']} already records this landing under {args.root}; 
nothing to do")
-        return
-
-    print(f"{ref}\n{notes}\n")
-    if args.dry_run:
-        print(f"--dry-run: would create a record under {args.root} and pin it")
-        return
-
-    created = as_list(bd("create", "--type=record", f"--parent={args.root}",
-                         f"--title={ref}", f"--external-ref={ref}", 
f"--notes={notes}"))
-    record = created[0]["id"]
-    # Records are created pinned (PRIME.md section 6), but `bd create` has no
-    # status flag, so pinning is a second call.
-    bd("update", record, "-s", "pinned")
-    print(f"created {record} under {args.root}, pinned, {len(shas)} commit(s)")
+        print(f"{found['id']} already records this landing under {args.root}")
+    else:
+        print(f"{ref}\n{notes}\n")
+        if args.dry_run:
+            print(f"--dry-run: would create a record under {args.root} and pin 
it")
+        else:
+            created = as_list(bd("create", "--type=record", 
f"--parent={args.root}",
+                                 f"--title={ref}", f"--external-ref={ref}", 
f"--notes={notes}"))
+            record = created[0]["id"]
+            # Records are created pinned (PRIME.md section 6), but `bd create` 
has no
+            # status flag, so pinning is a second call.
+            bd("update", record, "-s", "pinned")
+            print(f"created {record} under {args.root}, pinned, {len(shas)} 
commit(s)")
+
+    for number in prs:
+        record_pr(args.root, f"{slug}#{number}", args.dry_run)
 
 
 if __name__ == "__main__":

Reply via email to