Skip to content

family.py: the dry runs' member-duplicate-id remedies and init's family-holder-already-here say the branch their lines run on, and that add line is _cited, so each runs as printed from the tracking branch (#1228) - #1264

Draft
brettheap wants to merge 2 commits into
fix/1127-family-followups-stackedfrom
fix/1228-remedy-branch-lines

Conversation

@brettheap

@brettheap brettheap commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

Goal

#1228: every add, bump and remove line that a family.py remedy prints says the branch it runs on, whichever route reached the refusal. The dry runs of add and bump that end in member-duplicate-id, and the family-holder-already-here that init ends in, now say it as #1201 makes member-unknown and member-already-present say it, and that add line is _cited. rv1201's one surviving mutant of #1201's rows, R5, is killed.

Stacked on #1201 at eac177af (itself on #1117 at 219e57c2). The PR's base is #1201's branch, fix/1127-family-followups-stacked, so it shows two commits: b54520cea (5 files, 442 insertions and 57 deletions) and the review round's 43d2e8b26, 5 files, 503 insertions and 64 deletions in all (git diff --stat eac177af9 43d2e8b26). It answers lane 5's review of #1201 (issuecomment-6372152028): SHOULD-1's optional code fix, and NIT-1 to NIT-3.

What held on #1201's head

At eac177af9, as rv1201 measured it (probes p6, p7, p9) and as this PR's rows show failing there (below), three printed lines were reached with no branch read, named none, and were refused family-on-tracking-branch when typed where a fresh clone stands:

  • (a) add --dry-run's member-duplicate-id. Under --dry-run, cmd_add skips _checked_branch and still asks _checked_member_id. Its remedy printed a remove line and a bump line and no branch.
  • (b) bump --dry-run's member-duplicate-id. _bump_dry_run asks _checked_bumped_id before anything reads the branch. Its remedy printed a remove line and no branch.
  • (c) init's family-holder-already-here. Its remedy printed _add_command's line between plain backticks: no branch clause, and not through _cited, so under a folder named with a backtick the first span ended at the path's backtick (RV1117's N6 shape).

What changed

  • scripts/family.py, (a) and (b). _checked_member_id and _checked_bumped_id take a keyword-only dry_run, false by default. cmd_add passes args.dry_run, and _bump_dry_run passes true. The real runs' calls (_mount_and_pin, and cmd_bump's) are unchanged, and so are their words. Set, the remedy puts _off_the_tracking_branch before its line or lines and ends with BRANCH_LEFT, as member-already-present does. The clause names "bump and remove refuse" for (a), whose remedy names both lines, and "remove refuses" for (b). Its switch -c names family/remove-<id>, the branch the remove line's own refusal names, so the clause and the refusal after it agree.
  • scripts/family.py, (c). The remedy now says the clause with init's own git -C <holder> switch -c family/add-<Project>, then the add through _cited, then BRANCH_LEFT. The issue left the branch word to the owner. This PR takes family/add-<Project>, bare, as the NEXT STEPS block prints it: quoted, it would read as a value.
    • Two helpers carry it. _add_branch_switch spells that switch -c line, and NEXT STEPS now calls it too, with the same bytes as before. _branch_clause holds the clause's words around a switch -c line already spelled, and _off_the_tracking_branch now calls it.
    • The code comments at the two member-duplicate-id asks and _off_the_tracking_branch's docstring now say that a dry run states the refusal before the branch is read, and a real run after it.
  • tests/test_readme_windows_quoting_sentence.py. NAMED moves one host-quoter call from _print_next_steps to _add_branch_switch, and FAMILY_LINES moves the switch -c kind with it. README's sentence already lists switch -c, so README.md and docs/handbook.html are unchanged. The changed remedies' words appear in no doc. The release notes name member-duplicate-id as a code (v1.0.0) and the family/add-<Project> spelling (v1.1.0). AGENTS.md and templates/family-root/ show git -C <path> switch -c family/add-<Project> as NEXT STEPS print it. None of that changes.
  • tests/test_family_remedy_lines.py. The new rows type each line as printed from the tracking branch. Typed alone, each line is refused family-on-tracking-branch. Typed after the clause's switch -c, it exits 0, and the holder is clean and its gate green:
    • (a): test_add_dry_runs_duplicate_id_lines_run_from_the_tracking_branch, [remove] and [bump];
    • (b): test_bump_dry_runs_duplicate_id_remove_runs_from_the_tracking_branch;
    • (c): test_init_holder_already_heres_add_runs_as_printed_after_its_switch, [plain] and [a`b].
    • NIT-1: test_a_rows_name_with_a_space_is_one_word_in_both_lines now asserts the switch -c line, family/bump-irrs, which is built from the row's id:. That kills R5, which builds it from the name and gets family/bump-member.
    • NIT-2: typed()'s docstring says that it asserts the interpreter's name and does not look it up on PATH.
  • tests/test_family_bump_to_checks.py. Its two member-duplicate-id rows used to assert that both runs print the same words. They now pin the real run's words as at eac177af9 and the dry run's with the clause (both_refused, duplicate_id_refusal(dry_run=True)).
  • tests/test_family_dry_run.py. duplicate-id leaves the parametrized "same under --dry-run" row and gets a row of its own, test_add_dry_runs_duplicate_id_names_the_branch_the_real_run_has_read. It pins the same code, detail, stdout and exit for both runs, and the dry run's remedy as the real one with the clause added and BRANCH_LEFT at the end.
  • NIT-3 is left. member-already-present still suggests family/bump-<id> for both of its lines. (a)'s clause likewise names one branch, family/remove-<id>, for both its remove and its bump. Either branch works for both verbs, since anything but the tracking branch does.

Review round (rv1264: two NITs)

rv1264 (opus) found this PR READY at b54520cea with two NITs (issuecomment-6724775227). The round's commit, 43d2e8b26, is the PR's second: 2 files, 67 insertions and 13 deletions (git diff --stat b54520cea 43d2e8b26), scripts/family.py and tests/test_family_remedy_lines.py. The PR now shows both commits, 5 files, 503 insertions and 64 deletions (git diff --stat eac177af9 43d2e8b26). The Goal, What held on #1201's head, What changed and Rows that fail on #1201's head describe b54520cea, the first commit. Mutants, Runs, Merge-trees and one bullet of Limits are read again at 43d2e8b26.

Rows that fail on #1201's head

The commit's new and changed rows (73 ids: the 5 new test_family_remedy_lines.py ids and the NIT-1 row, the 2 test_family_bump_to_checks.py rows, test_family_dry_run.py's new row and the 18 ids of its parametrized row, and the 46 ids of test_readme_windows_quoting_sentence.py) were run with eac177af9's scripts/family.py (sha256 b8199379c23f...) swapped in under this commit's tests, in a second worktree. The file was restored after the run and its sha256 checked: 88ad8766f4e2..., matching, with git status clean. Result: 9 failed, 64 passed, every failure an AssertionError on the remedy text:

row on eac177af9's family.py
(a) [remove], [bump] fail: the remedy ends "...not in the family." with no clause and no BRANCH_LEFT
(b) fail, the same way
(c) [plain], [a`b] fail: the remedy ends "There is no --force." with no clause
test_family_dry_run.py's new row fail, as (a)
the 2 test_family_bump_to_checks.py rows fail on the dry run's assert; the real run's assert just before it passes, so the real run's words are unchanged
NAMED[scripts/family.py] fail: there is no _add_branch_switch
the NIT-1 row, the 18 parametrized ids, the other 45 sentence ids pass, as they should: #1201 already builds the branch from the id: (the assert kills R5, below), and the rest pin unchanged behaviour

Mutants

Each was applied alone in the second worktree at b54520cea (clean), run through lane 5's runner, and restored, with the sha256 of every touched file checked and git status clean after each. The #1228 mutants ran against all 73 ids above (84 with the rest of test_family_remedy_lines.py); M8 also ran against tests/test_git_bash_dialect.py and tests/test_family_switch_branch_exists.py, which read NEXT STEPS.

id mutation result
M1 (a)'s clause dropped killed, 3: (a) [remove], [bump], test_family_dry_run.py's new row
M2 (b)'s clause dropped killed, 3: (b), the 2 test_family_bump_to_checks.py rows
M3 (c)'s clause dropped killed, 2: (c) [plain], [a`b]
M4a dry_run ignored in add (always false) killed, 3, as M1
M4b dry_run ignored in bump (always false) killed, 3, as M2
M5 (c) without _cited: the add between plain backticks killed, 1: (c) [a`b]
M6 (a)'s branch is bump's, not remove's killed, 3, as M1
M7 (a) without BRANCH_LEFT killed, 3, as M1
M8 (c)'s placeholder branch quoted for the shell killed, 2: (c) both; the two NEXT STEPS files pass (the shell reads the quoted word the same)
M9 (c) without BRANCH_LEFT killed, 2: (c) both
M10a (a)'s branch from the row's name, not its id: killed, 1: test_add_dry_runs_clause_branch_is_made_from_the_rows_id (it survived at b54520cea, 84 passed)
M10b (b)'s branch from the row's name, not its id: killed, 1: test_bump_dry_runs_clause_branch_is_made_from_the_other_rows_id (it survived at b54520cea, 84 passed)
M11 (a)'s dry_run inverted: the real run gets the clause killed, 3, as M1
M12 (c)'s switch -c names --into, not the holder killed, 2: (c) both
M14 (a)'s clause names one verb, "remove refuses" killed, 3, as M1
M15 _branch_clause prints its switch -c bare killed, 11, across every clause row
M3b NIT-1's f"Remediation: {clause}" without its clause killed, 2: (c) [plain], [a`b]
R1 rv1201's: _cited always prints the command bare killed, 15 of the file's 17
R2 rv1201's: the typed owner is the first path segment killed, 6
R3 rv1201's: _family_line quotes for sh on every host killed, NAMED[scripts/family.py]
R4 rv1201's: member-already-present loses its clause killed, 4
R5 rv1201's: member-already-present's branch from the name killed by NIT-1's assert, test_a_rows_name_with_a_space_is_one_word_in_both_lines (it survived at eac177af9)
R6 rv1201's: member-already-present names the typed project killed, 4 (3 in test_family.py)
R7 rv1201's: README and the handbook drop cd from the sentence killed, test_the_kinds_of_family_lines_are_the_ones_the_sentence_lists

M1 to M9 are n1228's, and they cover the issue's list: the clause dropped from each of (a), (b) and (c), dry_run ignored, and _cited dropped from (c). M10a to M15 are the first commit's own, and M3b the second's. R1 to R7 are rv1201's again: R1 to R6 against test_family_remedy_lines.py, with R6 also against test_family.py's spelling row, and R3 and R7 against the sentence file. scripts/family.py came back to sha256 88ad8766f4e2... after each run, README.md to 71d9fecafbd6... and docs/handbook.html to 1d01f0823e60... after R7, and git status was clean after the last one. At 43d2e8b26, M10a, M10b and M3b each ran alone in the same detached tree, clean at that commit, over tests/test_family_remedy_lines.py's 19 ids (1 failed and 18 passed, 1 failed and 18 passed, 2 failed and 17 passed), with scripts/family.py back to sha256 b7f0d8c5f2db... after each and git status clean after the last. The other rows' results are those of b54520cea, which rv1264 re-ran and matched.

Runs

All through lane 5's run10.sh: a gh stub first on PATH, GH_TOKEN bogus, an empty GH_CONFIG_DIR, a blank git identity, GIT_SSH_COMMAND=false, GIT_CONFIG_GLOBAL=/dev/null, EMAIL exported, and git-filter-repo 2.47.0 (CI's pin) on PATH. Every START line reads head=b54520cea716b44df1a95139a46901a478d616fd and dirty=0, with three exceptions. The swapped run and each mutant read dirty=1, the one file changed, except R7, which changes two and reads dirty=2. The predicted merge's run names its own head. Python 3.12. The second commit's runs, marked in the table, read head=43d2e8b26... and dirty=0, except the mutants' and the swapped runs, which read dirty=1, and the pre-commit run of the new rows, which read head=b54520cea... and dirty=2.

run git result log
the commit's new and changed rows (73 ids) 2.43.0 73 passed in 69.23s logs/rows.log
the same 73 ids, eac177af9's family.py swapped in 2.43.0 9 failed, 64 passed (above) logs/baserows.log
gate A 2.43.0 1 failed, 662 passed in 413.56s (the one failure is in Limits) logs/gateA243.log
gate A again 2.43.0 663 passed in 509.19s logs/gateA243b.log
gate A 2.47.1 663 passed in 421.81s logs/gateA247.log
gate B 2.43.0 389 passed in 261.02s logs/gateB243.log
gate B 2.47.1 389 passed in 340.68s logs/gateB247.log
gate A, gate B and test_path_guard_hygiene.py (23 files) on the predicted merge, scratch commit e22074def (below), head=e22074def... 2.43.0 1174 passed in 790.16s logs/merged243.log
the row that failed once, alone, 10 runs of its 2 ids 2.43.0 20 of 20 passed logs/rep-head243-*.log
The review round, 43d2e8b26: the two new rows and the IR RS row, before the commit (tree dirty) 2.43.0 3 passed, 16 deselected in 7.50s work/n1228e/logs/newrows-head.log
the two new rows, eac177af9's family.py swapped in 2.43.0 2 failed, 17 deselected work/n1228e/logs/newrows-on-eac177af9.log
M10a, M10b, M3b, each alone, over tests/test_family_remedy_lines.py 2.43.0 1 failed 18 passed; 1 failed 18 passed; 2 failed 17 passed work/n1228e/logs/mut-*.log
gate A (13 files), the head 2.43.0 665 passed in 379.55s work/n1228e/logs/gateA243.log
gate A (13 files), the head 2.47.1 665 passed in 372.06s work/n1228e/logs/gateA247.log
gate A and tests/test_path_guard_hygiene.py (14 files) on the predicted restack, scratch commit f168938c4, head=f168938c4... 2.43.0 787 passed in 401.58s work/n1228e/logs/restack243.log
tests/test_family_init_landing.py, the file of family-holder-already-here's detail 2.43.0 19 passed in 8.34s work/n1228e/logs/init-landing243.log

The 2.47.1 runs put lane 5's git 2.47.1 first on PATH, and each log's first line reads git version 2.47.1. No run had a skip. The full suite was not run. Gate A of the second commit is 665 ids, the first commit's 663 and the two new rows; git-filter-repo 2.47.0 (CI's pin) was on PATH in every run. tests/test_path_guard_hygiene.py is still not at this head (see Gate A above), so gate B (the first commit's 9 files) was not run on the second commit, which the brief did not ask for. It changes _landing's remedy and one test file only.

Merge-trees

Read on 2026-11-08 at about 17:55Z, after fetching main at 9729315f1, with #1201 at eac177af9, #1117 at 219e57c28 and this PR at 43d2e8b26 read back from gh pr view. Of the 50 open PRs, three others touch this PR's five files: #1117 and #1201 (scripts/family.py and test files) and #1164 (scripts/family.py). The results are the same on git 2.43.0 and 2.47.1 (work/n1228e/logs/merge-trees-2.log).

git merge-tree --write-tree <other> 43d2e8b26 exit result
#1201, eac177af9 0 clean, tree dca51cc9a756
#1117, 219e57c28 0 clean, tree 584c537c5aa4
#1164, 74096eb47 1 one conflict, docs/handbook.html
main, 9729315f1 1 one conflict, docs/handbook.html

Limits

  • The clause's branch is built from the row's id:, and two rows now tell it from the name (mutants M10a and M10b, killed since 43d2e8b26; see the review round). Every other fixture row's id is its project lowercased, so only a row renamed by hand tells them apart. The old gap was harmless in effect, as rv1201 found for R5: the clause's switch -c need only leave the tracking branch.
  • NIT-3 is left (above). It is the same question as (a)'s one branch for two verbs.
  • The interpreter is asserted, not looked up (NIT-2). The docstring says so. rv1201 typed the lines through bash, dash and zsh with python3 from PATH, 12 of 12.
  • The clause says "a branch other than the tracking branch". It is worded to be true on any branch, as family.py: the add, bump and remove lines its remedies name begin with the interpreter and the script's path, quote the row's name, fill in the family's org, say the branch they run on, and member-already-present prints its remove (#1127) #1201's is, since a dry run says the refusal before the branch is read. A person already off the tracking branch reads a clause that does not apply to them.
  • init's <Project> stays a placeholder in the clause's switch -c, as NEXT STEPS print it. add's own refusal names the branch lowercased; any spelling works.
  • One row failed once, in code this commit does not touch. In the first gate A on 2.43, tests/test_family_dry_run.py::test_bump_dry_run_of_a_repair_to_the_pinned_commit_is_a_move[digest] failed:
    • What it saw. The dry run said it would commit the repair. The real run then said nothing to do: InkRouter/IRRS is already pinned at ab0fea7e32a2 and every fact agrees. and exited 1.
    • Where that is decided. The real run's answer comes from _nothing_to_record, a git diff --quiet HEAD made after write_lf rewrites family.yaml in place. The row's hand edit keeps the file's size.
    • The commit leaves that path alone. It changes no code of _nothing_to_record, _rewrite_members, force_stage or tree_digest. Its only tree_digest lines are one sentence of _bump_dry_run's docstring, rewrapped. The row has no duplicate id, so the dry_run flag in _checked_bumped_id never shows.
    • Every other run passed it: the second gate A on 2.43, gate A on 2.47.1, the predicted merge's run, and 20 of 20 runs alone.
    • The cause is not found. A probe of the plain race (a same-size, in-place rewrite right after git add and git commit, then git diff --quiet HEAD) missed none of 200 rewrites on 2.43.0 and none of 200 on 2.47.1. The failure's log is logs/gateA243.log, and the probe is probe/racy.py.
  • tests/test_path_guard_hygiene.py ran on the predicted merge only. It is not at this head (see Runs). This commit adds no file-system guard: its family.py hunks add no is_file, is_dir, exists, is_symlink or os.path call.
  • The runs are on the bench's Python 3.12; CI runs 3.14. No test line this commit adds uses ast.dump, repr(, sys.version or !r. The 442 added lines are ASCII and at most 79 columns. None of the commit's new asserts joins conditions with and or or. The one such assert in test_family_dry_run.py (line 1431) is eac177af9's line 1394, moved down.
  • Not run: the full suite, a real Windows host, and macOS.
  • It lands after family.py: the remove that both member-duplicate-id remedies name and the add that bump's member-unknown names carry --family-root <holder>, quoted for the host, so each runs as printed (#1005) #1117 and family.py: the add, bump and remove lines its remedies name begin with the interpreter and the script's path, quote the row's name, fill in the family's org, say the branch they run on, and member-already-present prints its remove (#1127) #1201, by the restack plan above.

Closes #1228

Lane: openreposhape-5 (openRepoShape-5)

🤖 Generated with Claude Code

Summary by Sourcery

Make family remediation guidance executable from tracking-branch checkouts across dry-run duplicate-ID and init holder-already-present refusals.

New Features:

  • Ensure dry-run duplicate-ID remedies identify a non-tracking branch where their suggested commands can run.
  • Make the init holder-already-present remedy include a branch switch and a safely cited add command.

Bug Fixes:

  • Correct remediation output so affected add, bump, and init commands can be executed as printed from a tracking-branch checkout, including paths containing backticks.

Enhancements:

  • Share branch-clause and init branch-switch formatting across remediation and next-steps output.
  • Expand coverage for dry-run remedies, branch-name construction from row IDs, command quoting, and executable remediation lines.

Tests:

  • Add behavioral tests that execute the newly printed remediation commands before and after switching branches.
  • Update dry-run expectations to distinguish branch-aware remedies from real-run output.

…`family-holder-already-here` say the branch their lines run on, and that `add` line is `_cited`, so each runs as printed from the tracking branch (#1228)

Three lines a remedy printed were reached with no branch read, named
none, and were refused `family-on-tracking-branch` when typed where a
fresh clone stands (lane 5's review of #1201, SHOULD-1):

- `add --dry-run` skips `_checked_branch` and still asks
  `_checked_member_id`; `bump --dry-run` asks `_checked_bumped_id` before
  `_branch_said`. Both now take a keyword-only `dry_run` flag, false by
  default, so the real runs' asks (`_mount_and_pin`, `_checked_bump_target`,
  `_bump_and_pin`) and their words are unchanged. Set, the remedy puts
  `_off_the_tracking_branch` before its line or lines, naming the branch
  the `remove` line's own refusal names (`family/remove-<id>`), and ends
  with `BRANCH_LEFT`, as `member-already-present` does.
- `init`'s `family-holder-already-here` printed `_add_command`'s line
  between plain backticks. It now comes after `init`'s own branch,
  `git -C <holder> switch -c family/add-<Project>`, in the same clause,
  and through `_cited`, so a holder under a folder named with a backtick
  prints it whole. The switch line is `_add_branch_switch`, shared with
  the NEXT STEPS block (same bytes there), and the clause's words are
  `_branch_clause`, which `_off_the_tracking_branch` now calls. The
  quoting test's `NAMED` moves one host-quoter call from
  `_print_next_steps` to `_add_branch_switch`; README's sentence already
  lists `switch -c`.

Rows: each dry run's lines and `init`'s `add` are typed as printed from
the tracking branch: alone they are refused there, after the `switch -c`
they go through. The two `both_runs` rows of `member-duplicate-id` and
`add`'s dry-run parity case now pin the real run's words as before and
the dry run's with the clause. NIT-1: the row with a space in its name
asserts the `switch -c` line, which kills the branch-from-name mutant.
NIT-2: `typed()` says it asserts the interpreter and does not look it up.

Closes #1228

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Implementation

Update duplicate-ID refusal handling so dry runs include the required branch clause and trailing guidance while real-run wording remains unchanged.

  • scripts/family.py L6710-6719, L7651-7657, L8777-8787 (+10 small edits)

Factor branch-clause and init branch-switch formatting, then use the shared switch in NEXT STEPS and the holder-already-here remedy so its add command is cited and executable as printed.

  • scripts/family.py L2316-2350, L2519-2526, L2532-2536 (+2 small edits)

Tests

Add executable remedy checks for dry-run duplicate-ID and init refusals, including tracking-branch behavior, quoting, and branch names derived from row IDs.

  • tests/test_family_remedy_lines.py L41-59, L163-214, L471-668 (+7 small edits)

Separate real-run and dry-run expectations for bump duplicate-ID refusals and verify their remedies differ only by the dry-run branch guidance.

  • tests/test_family_bump_to_checks.py L91-103, L113-136, L153-156 (+5 small edits)

Add a focused add dry-run duplicate-ID test that pins parity of refusal details and the additional branch clause in its remedy.

  • tests/test_family_dry_run.py L1088-1093, L1177-1209 (+4 small edits)

Update quoting-call accounting to reflect that the shared init branch-switch helper now owns the switch command.

  • tests/test_readme_windows_quoting_sentence.py L74-77, L94-95

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-11-08T17:36Z) Independent review of #1264 (#1228) by rv1264, lane 5's opus reviewer, which wrote none of it: READY at b54520ce, with two NITs. The report follows as the reviewer wrote it.

Independent review of #1264 (#1228) for lane openRepoShape-5, by rv1264 (opus), at b54520c: READY

No MUST and no SHOULD. Two NITs and two observations, none of which needs the head to move. The head is still b54520cea716 (ls-remote at 17:35Z), as are #1201 (eac177af9) and #1117 (219e57c28). I am read-only on the PR. I made no commits, pushes or GitHub writes, and ran no subagents.

Findings

NIT-1: the commit adds a fourth bare "Remediation: " literal, which is Sonar's one new issue

NIT-2: M10a and M10b survive; harmless in effect, but one assert each would kill them

  • Both survive the 84 ids (logs/mut-M10a.log, logs/mut-M10b.log), as the writer says, because every fixture's id is its name lowercased.
  • The fallbacks they change, str(row.get("id") or name) and str(held.get("id") or held["project"]), can never be reached. _row_recording only returns a row whose id: is truthy. So the branch is always family/remove-<project_id>.
  • It is harmless in effect. Any branch but the tracking branch runs the lines.
  • One assert would pin the body's own claim that "the clause and the refusal after it agree". My probe probe/test_zz_rv1264_m10.py renames the row's project: to a valid name that is not its id (Orrs with id irrs; Osss with id irss) and asserts the clause's branch:
    • at the head: 2 passed;
    • M10a alone: the (a) row fails, 'family/remove-orrs' == 'family/remove-irrs';
    • M10b alone: the (b) row fails, 'family/remove-osss' == 'family/remove-irss'.
  • NIT-1's IR RS shape cannot serve here (see Observation 2).

Observation 1 (the writer's flake): this commit cannot cause it

The row is test_family_dry_run.py::test_bump_dry_run_of_a_repair_to_the_pinned_commit_is_a_move[digest].

  • The row never reaches the commit's new code. A bump with no duplicate id leaves _checked_bumped_id at if held is None: return (scripts/family.py:8771-8772), and the new code comes after it.
  • The real run's path is untouched. _bump_and_pin, _rewrite_members, force_stage, _nothing_to_record and write_lf have no hunk in the commit. I listed every @@ hunk.
  • The test-file changes run after the row. The new row is at :1177, and the removed ADD_REFUSALS case and the two new imports change nothing the row runs.
  • The failure's own output points at git. The real run printed ab0fea7e32a2 -> ab0fea7e32a2 tree 47697c117216, which is the correctly computed digest, and then nothing to do. So it wrote the right manifest, and git diff --quiet HEAD -- members/IRRS family.yaml still found no difference. write_lf rewrites in place (open("w"): same inode, and a 64-hex digest keeps the size the same). So git's stat data missed a real change.
  • That hazard is in _nothing_to_record, which predates this commit. It could be its own issue, if wanted: compare content, not stat. I did not reproduce it.

Observation 2 (not this PR's code)

#1201's NIT-1 row (IR RS) asserts the words of lines that cannot run as printed. Typed, remove --member 'IR RS' is REFUSED unsafe-value (logs/m10-head-debug.log). The row only claims "ONE word", so nothing in it is false.

Checked and true

The three routes, probed on git 2.43.0 and 2.47.1 (probe/test_zz_rv1264_probe.py; logs/probe243.log and logs/probe247.log: 13 passed on each)

  • Each holder is a real git clone, standing on main, under Dana's clone/ or Dana's work/.
    • (a) and (b) clone the family's holder, with its submodules initialised.
    • (c) clones init's own remote.
  • Every line typed was taken out of the printed remedy, never rebuilt, and typed through /bin/bash, /bin/dash and /usr/bin/zsh, with python3 taken from PATH.
  • (a) remove and bump, and (b) remove: typed alone, each is REFUSED family-on-tracking-branch. After the clause's switch -c, each exits 0 on family/remove-irrs (or -irss). The real add or bump then exits 0, the holder is clean, and its gate is green.
  • remove's own refusal suggests the clause's branch. bump's own refusal suggests family/bump-irrs-<sha12>; the body says this openly in its NIT-3 bullet.
  • (c): the add is _cited after the clause. With <Project> filled in, it is refused alone and exits 0 after the switch, on family/add-IRQS.

The rows

  • The 73 ids are collected (list-rows.txt).
  • With eac177af9's family.py swapped in, in rv1264-mut: 9 failed, 64 passed (logs/baserows.log). Every failure is an AssertionError on the remedy text or on NAMED[scripts/family.py].
  • The two test_family_bump_to_checks.py rows fail only at the dry-run assert (:156, :212), so the real run's words are unchanged.
  • Restored: sha256 88ad8766... OK, status clean.

Mutants (tools/mut.py, summary in logs/mut-summary.log)

Reading and the body's other claims

  • The diff is 5 files, +442/-57. All 442 added lines are ASCII and at most 79 columns.
  • No added line uses ast.dump, repr(, sys.version or !r.
  • No new assert joins conditions with and or or. The one such assert, test_family_dry_run.py:1431, is eac177af9's :1394.
  • Every _family_line and _add_command site either follows _checked_branch in its real run or carries the clause, so the Goal's "every" holds for family.py.
  • No changed remedy wording appears in README, docs/, AGENTS.md or the templates.
  • The body has one closing keyword (Closes #1228), and body-guard.py exits 0 on the live body. Sourcery appended its summary after the writer's footer.

Gates

  • All runs used run10.sh on Python 3.12.3, with git-filter-repo from L/tools/venv247 (2.47.0 by the briefs; its --version prints a40bce548d2c).
  • Every head run's START line reads dirty=0.
  • No run had a skip or a failure.
run git result log
gate A, 13 files: the issue's 8, test_family_dry_run.py, and the 4 hygiene files that exist (test_path_guard_hygiene.py does not) 2.43.0 663 passed logs/gateA243.log
gate A 2.47.1 663 passed logs/gateA247.log
gate B, the body's 9 related files 2.43.0 389 passed logs/gateB243.log
predicted restack C2 4091dda72 (tree 240aabff, main be21751c3), gate A + test_path_guard_hygiene.py 2.43.0 785 passed logs/merged243.log
predicted restack C3 068d2e4e0 (tree f4d393bf, main 46fe6ac19), the same 14 files 2.43.0 785 passed logs/merged243b.log

C1, C2 and C3 are unreferenced scratch commits made with commit-tree (C1 is 7eb84ddcd: head + #1117's head).

Merge-trees

Logs: logs/merge-trees.log, merge-trees-2.log, merge-trees-3.log.

git merge-tree --write-tree <other> b54520cea result
#1201, eac177af9 clean, tree f64b574e
#1117, 219e57c28 clean, tree 43171572
#1164, 74096eb47 exit 1, docs/handbook.html
main be21751c3, 46fe6ac19, 9ec0dc79c exit 1 on each, docs/handbook.html

Not done, and state

Lane: openreposhape-5 (openRepoShape-5)

…ready-here` builds its clause before it raises, so "Remediation: " is no fourth bare copy, and two rows pin that each dry run's clause names its branch from the row's id (#1228)

rv1264's two NITs on #1264, both at b54520c:

- NIT-1: the (c) hunk wrote `"Remediation: "` beside `_branch_clause`'s
  call, a fourth bare copy of that literal after #1201's three, which
  SonarCloud reports on #1264 as python:S1192. `_landing` now holds the
  clause in a local and says `f"Remediation: {clause}"`. The refusal's
  words are byte for byte what they were, for a plain folder and for one
  named with a backtick (probed before and after, same script path).
- NIT-2: mutants M10a and M10b, the dry runs' clause branch built from
  the row's name and not its `id:`, survived because every fixture row's
  id is its name lowercased. Two rows rename a row by hand (`IRRS` to
  `Orrs`, `IRSS` to `Osss`) and assert the clause's `switch -c` names
  `family/remove-irrs` and `family/remove-irss`. The edit those rows and
  the `IR RS` row share is now `renamed_by_hand`.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Nov 8, 2026

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-11-08T18:25Z) Delta check of #1264's review round, b54520ce..43d2e8b2, by rv1264b, lane 5's sonnet reviewer, which wrote none of it: READY. NIT-A is answered: the coordinator has updated the Goal paragraph to name both commits (5 files, 503 insertions and 64 deletions in all), and nothing else in the body changed. SonarCloud lists S1192 AaG7-FM2kL7yTWvgodeV as CLOSED at this head. The report follows as the reviewer wrote it.

Delta check of #1264 (#1228) for lane openRepoShape-5, by rv1264b (sonnet), b54520c..43d2e8b: READY

There is no MUST and no SHOULD. I found four observations, none of which needs the head to move. I was read-only on the PR and ran no subagents. I made no GitHub writes and no pushes.

Findings

NIT-A (Goal paragraph is stale). The ## Goal paragraph still says the PR "shows one commit, b54520cea: 5 files, 442 insertions and 57 deletions". The PR now shows two commits (git diff --shortstat eac177af9 43d2e8b26 gives 5 files, 503 insertions, 64 deletions), and the body's own "Review round" paragraph states those numbers. The brief told the writer to keep the Goal, so this is the coordinator's call. Nobody reading the body is misled.

NIT-B (a no-op renamed_by_hand is caught by one row only). I made the helper return at once (T1). Only the IR RS row failed (1 failed, 18 passed). The two new rows pass without the rename, because their only guard is the helper's own premise asserts. With the helper a no-op and M10a applied together (T3), the file still fails only the IR RS row. The realistic ways the helper breaks (the regex stops matching, the id changes) trip its own asserts, and a no-op helper is caught by the IR RS row. So this is an observation. If wanted, one assert name in rows_of(holder) in each new row would close it.

Observation C (main moved again). The body's merge-tree section names main 9729315f1. Main is now 801a7d6d0 (one commit: adopt-project.py and two adopt tests, none of them in the stack or in gate A). Every merge-tree result below is unchanged on it.

Observation D (CI). Per gh pr view: package, tests-git-bash and SonarCloud passed at 18:23Z. tests was still IN_PROGRESS (started 17:55:13Z). closingIssuesReferences is [], which is expected while the base is #1201's branch.

Evidence for each check

1. The delta is what the body says.

  • It is one commit, a child of b54520cea, by Brett Heap <service@opensoft.dev>.
  • git diff --numstat shows scripts/family.py +3/-3 and tests/test_family_remedy_lines.py +64/-10, so 2 files, +67/-13, as the body says.
  • All added lines are ASCII and at most 79 columns. There is no repr(, !r, ast.dump or sys.version, and no assert joins conditions with and or or.
  • The subject is 246 characters. The trailers are Lane: and Co-Authored-By: Claude Sonnet 5.5, and there is no closing keyword. Closes #1228 is only in the first commit.
  • The family.py hunk moves _branch_clause(...) into a local clause and the third Refusal argument becomes f"Remediation: {clause}". Both helpers are pure, so evaluation order changes nothing observable.

2. NIT-1.

  • grep -n '"Remediation: "' scripts/family.py gives three lines at the head: 1713, 6577 and 9497. At eac177af9 they were 1713, 6547 and 9432, the same three shifted. b54520cea had four (it added 2530).
  • Byte probe (work/rv1264b/probe/remedy.sh, my own, not the writer's): I ran it on a scratch git repo with two commits holding the b54520cea and head trees, both clean, from the same cwd and the same script path. init over a family folder that already holds its holder gave the same output both times.
    • Plain folder: 1120 bytes of stderr, sha256 900c7453..., cmp identical.
    • Folder named a`b: 1112 bytes, sha256 4e15fcc3..., cmp identical.
    • In both, stdout is empty and exit is 2 (the exit= files are identical). The a`b refusal prints the git -C '...' and python3 ... spans bare, as intended. The counts differ from the writer's 1156/1148 only because my scratch path is shorter.
    • My first export had no .git, so it refused git-failed and the comparison was vacuous. I discarded it and redid it on the git repo.
  • SonarCloud on the head: the check passes. The MCP search for PR 1264 returns one issue, python:S1192 AaG7-FM2kL7yTWvgodeV, status CLOSED. The quality gate is OK, with new_duplicated_lines_density 0.0.

3. NIT-2. Baseline: 86 ids (the 19-id file, the 2 test_family_bump_to_checks.py rows, the 2 test_family_dry_run.py rows, and the sentence file) at the head in rv1264-mut: 86 passed in 75.66s (logs/rowshead243.log).

id mutation result
M10a (a)'s branch from the row's name, not its id: killed, 1 failed, 85 passed: the (a) row test_add_dry_runs_clause_branch_is_made_from_the_rows_id only
M10b (b)'s branch from the row's name, not its id: killed, 1 failed, 85 passed: the (b) row test_bump_dry_runs_clause_branch_is_made_from_the_other_rows_id only
S1 eac177af9's family.py swapped in, the two new rows alone 2 failed ([] == ['git -C ... switch -c family/remove-irrs'] and ...irss)
S1file the same swap, the whole 19-id file 7 failed, 12 passed (the two new rows, (a) [remove] and [bump], (b), and (c) [plain] and [a`b])

The swap matches the body's "2 failed, each on [] == [the switch line]".

4. My own mutants. Each ran alone in rv1264-mut over the 86 ids unless noted. Logs are logs/mut-<ID>.log and the summary is logs/mut-summary.log.

id mutation result
M3b the f-string without its clause ("Remediation: ") killed, 2: (c) [plain] and [a`b]
P1 (c)'s switch names --into, not the holder killed, 2
P2 (c)'s clause names "bump and remove refuse" killed, 2
P3 {clause.rstrip()} killed, 2
P4 _cited dropped from (c)'s add killed, 1: [a`b]
P5 BRANCH_LEFT dropped from (c) killed, 2
P6 (c)'s placeholder branch is family/remove-<Project>, run over the remedy-lines file, test_family_switch_branch_exists.py and test_family_init_landing.py killed, 3: (c) both, and test_init_next_steps_spell_the_branch_as_its_member
T1 renamed_by_hand a no-op (19-id file) killed by the IR RS row only (NIT-B)
T2 renamed_by_hand edits but never commits (19-id file) killed, 3: IR RS and both new rows
T3 renamed_by_hand a no-op and M10a together (19-id file) the IR RS row only fails; the new (a) row passes (NIT-B)
R1 rv1201's: _cited always bare (19-id file) killed, 17 of 19. It was 15 of 17 at b54520cea, so the two new rows add 2.
R5 rv1201's: member-already-present's branch from the name (19-id file) killed by the IR RS row (1 failed). The refactor kept that row's discrimination.

Every swap and mutant was restored and checked by sha256. family.py came back to b7f0d8c5f2db0698a9a98a90d07cb9031675b06d47027d09d7d638b679c18870 and test_family_remedy_lines.py to 0783268cd5396db9e7f59c70f67c7a4affca8dc3048067479259a4bbab2e37cf. git status was empty after each, and rv1264-review and rv1264-mut are both clean and detached at 43d2e8b26.

5. The writer's four edits beyond the brief.

  • Limits bullet rewritten: needed and accurate. The old text said M10a and M10b survive, which is now false. The new text says they are killed since 43d2e8b26, and I confirmed that.
  • Goal left as written: follows the brief. It is NIT-A, and the Review round paragraph covers it.
  • Shared helper renamed_by_hand: sound. It removes a third copy of a ten-line block, and Sonar's duplication reads 0.0. The IR RS row's asserts are unchanged (the diff removes only the inline edit and moves the premise assert into the helper). R5 still kills through that row.
    • One minor point: name goes into the re.sub replacement template and project into the pattern unescaped. That is harmless for IR RS, Orrs, Osss, IRRS and IRSS.
  • Three new module-docstring lines (plus a blank line): accurate and in the file's style.

6. Gates at the head. Python 3.12.3. The runner puts git-filter-repo from L/tools/venv247 on PATH (the CI pin, 2.47.0 by the briefs; its --version prints a40bce548d2c). Each run was 14 files: gate A's 13 plus test_family_init_landing.py. Both START lines read head=43d2e8b2... and dirty=0. There was no skip and no failure in any log.

git result log
2.43.0 684 passed in 396.22s logs/gateA243.log
2.47.1 (first line of the log reads git version 2.47.1) 684 passed in 375.78s logs/gateA247.log

684 is the body's 665 (13 files) plus test_family_init_landing.py's 19, which matches n1228e/logs/init-landing243.log. The writer's other counts check against their logs: 665 on 2.43 and 2.47.1, 787 on the restack, 3 passed, 16 deselected, M10a and M10b at 1 failed 18 passed, M3b at 2 failed 17 passed, the swap at 2 failed. Their START lines read dirty 2, 1 or 0 as the body says. Only one run of more than 100 tests ran at a time, plus small mutant runs alongside. I did not run tests/test_path_guard_hygiene.py (not at this head), gate B, the full suite, Windows, macOS or Python 3.14.

7. Merge-trees (git merge-tree --write-tree <other> 43d2e8b26, run on git 2.43.0 and 2.47.1 with the same results; logs logs/merge-trees.log and logs/merge-trees-restack.log).

other exit result
#1201 eac177af9 0 clean, tree dca51cc9a756 (the body's)
#1117 219e57c28 0 clean, tree 584c537c5aa4 (the body's)
#1164 74096eb47 1 docs/handbook.html only (the body's)
main 9729315f1 at 18:09Z 1 docs/handbook.html only
main 801a7d6d0 (fresh at 18:23Z) 1 docs/handbook.html only, tree f2b7ca07c4a2

8. The body's new section and counts.

  • Body guard and keywords: body-guard.py exits 0 on the live body. The only closing keyword is Closes #1228, and the Lane line and footer are in place.
  • Diff against the pre-round body (n1228e/live-body.md): the new Review round section, the M10a and M10b rows (survived to killed), the M3b row, the Runs rows and their note, the Merge-trees section, the first Limits bullet and the Sourcery summary.
  • Counts that check out:
    • 2 files, +67/-13, and 5 files, +503/-64;
    • three bare literals at 1713, 6577 and 9497;
    • b7f0d8c5f2db for the head's family.py, 88ad8766f4e2 for b54520cea's, and b8199379c23f for eac177af9's (I recomputed it);
    • the merge-tree trees dca51cc9a756, 584c537c5aa4, dad4a74a1cde, c6ce0111f823 and 4ebd3c638f84.
  • Two statements I could not check: the scratch commit hashes (f168938c4 and 02f5c651c, which I could not recreate) and "13 files" in the main-moved list. I did not read the writer's restack-delta.log.

Not done

The full suite, gate B, tests/test_path_guard_hygiene.py on the head, a gate run on the 801a7d6d0 restack, a real Windows host, macOS and Python 3.14.

Notes and logs are in /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1264b/ (STATE.md, logs/, probe/, tools/mut.py, body.diff, pr-body.md).

Lane: openreposhape-5 (openRepoShape-5)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant