fix: make local semantic-line-breaks consume CI's NLB checker (closes #2085) - #2322
Conversation
WIP --- opened up front to claim the issue; implementing now. Co-authored-by: dem-extra1 <dem-extra1@users.noreply.github.com>
The local reformatter and gha's check-new-line-breaks were two implementations of one convention. A semicolon is not a sentence boundary, so --write joined clause pairs onto one line and CI rejected them. Load the checker at the SHA validate.yml pins, split sentences with its split_sentences, and split mid-line semicolons when its classify_line returns clause. A pin file records that SHA and the file's sha256 so a stale or hand-edited copy fails loudly. #2081 (comma-clause joins into 200-400 char lines) is unchanged: the gate does not flag commas. Co-authored-by: dem-extra1 <dem-extra1@users.noreply.github.com>
Self-review of the #2085 construction found leftover Don'ts and a local-run sentence that still described the pre-fix reformatter. Date those case records, point skill-builder and challenge-the-assignment at the new load path, and catalog the vendored checker in AGENTS.md's generated-output table. Co-authored-by: dem-extra1 <dem-extra1@users.noreply.github.com>
The ellipsis section still said the reformatter misses that boundary. After #2085 it does not. Co-authored-by: dem-extra1 <dem-extra1@users.noreply.github.com>
Past-tense leftover claims, an ambiguous "It" in skill-builder, and a masking test that only passed because the fixture was too short for classify_line to return clause. Co-authored-by: dem-extra1 <dem-extra1@users.noreply.github.com>
…on't `--write` already splits `[...] Text` the same way the checker does. The scoped `find` fallback now calls emit_gate_clean too, so a miss cannot write a line the gate would still flag. Co-authored-by: dem-extra1 <dem-extra1@users.noreply.github.com>
The continuation packed "see it. It shares" onto one added line. Co-authored-by: dem-extra1 <dem-extra1@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Session wrap 2026-08-26 12:31 PDT: the Ready on 271a6f4 still stands and mergeable_state was clean. The non-blocking Posted by Claude Code (AI agent) --- not written by a human. |
…o nlb_gate nlb_gate.py derived which checker script CI runs from validate.yml's `uses:` pin, but called classify_line/split_sentences at each call site's compiled-in default (clause_breaks=True, clause_min_length=80) rather than validate.yml's `with:` block. Today those match by coincidence; a future `with: clause-min-length: '10'` would make CI flag lines the local reformatter leaves alone, with nothing to catch the drift. Add parse_ci_nlb_with/resolve_nlb_config/load_nlb_config, which parse the NLB step's `with:` block and resolve clause-breaks/clause-min-length through the vendored checker's own _env_flag/_env_int (round-tripped through os.environ and restored), so a config value validate.yml sets or omits resolves exactly as CI's NLB_CLAUSE_BREAKS/NLB_CLAUSE_MIN_LENGTH would. emit_gate_clean now defaults to that resolution and threads it through every recursive classify_line call. Adds 7 tests to test_slb.py: the live-validate.yml resolution equals today's defaults, and a synthetic clause-min-length: '10' validate.yml changes both classify_line's verdict and emit_gate_clean's output for the reviewer's example line. 86 passed, 0 failed (was 79). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MyNR6ZrvN6M4Rr8eZbHts
|
Pushed 233eba9 addressing the adversarial finding raised against 271a6f4 (local/CI agreement was coincidental --- the gate pinned the checker's SHA but ignored the action's clause-config inputs): Addressed. The one earlier finding (sync-nlb-checker.py FileNotFoundError without gh) remains Deferred to #2338, as before. Posted by Claude Code (AI agent) --- not written by a human. Generated by Claude Code |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
The review round on 233eba9 reproduced a regression: the config- threading commit's module-scope yaml import made the previously dependency-free reformatter crash with a raw traceback (even --help) on machines without PyYAML. Move the import inside parse_ci_nlb_with, guarded with the repo's friendly-exit convention (matching validate-skills.py), so every yaml-free path works and a run that needs the config exits with the install instruction. Two regression tests block a shim yaml module and assert --help succeeds and the real run gets the message, not a traceback. 88/88 tests pass. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MyNR6ZrvN6M4Rr8eZbHts
|
Pushed 980922d addressing the round at 233eba9: Addressed --- the unguarded module-scope Posted by Claude Code (AI agent) --- not written by a human. Generated by Claude Code |
The CI checkout cannot resolve origin/main, so the previous test file hit the scope-diff error before the yaml path and failed on the message assertion. Probe with --all on a temp file outside the repo instead, which skips base-ref scoping and deterministically reaches the config resolution that needs PyYAML. Reproduced the CI failure shape locally before the fix; 88/88 after. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MyNR6ZrvN6M4Rr8eZbHts
The adversarial round measured a ~170x whole-file reformat regression: emit_gate_clean resolved the checker singleton before calling load_nlb_config, so the no-override cache condition never held and validate.yml was re-parsed once per sentence. Pass the caller's checker (None on the ordinary path) instead, and pin the behavior with a counting test (resolution runs at most once across repeated calls). Also cover the clause-breaks axis the round showed untested: a synthetic clause-breaks 'false' config resolves and changes classification, so silently dropping that axis now fails a test. 92/92 tests pass; whole-file preview timing back to sub-second. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MyNR6ZrvN6M4Rr8eZbHts
|
Pushed 112dd70 addressing the adversarial round at 233eba9:
Now 92/92 tests. The interim CI failure at 980922d was the new PyYAML test depending on a git base ref CI cannot resolve; fixed in cf65603 (probe via --all on a temp file). Posted by Claude Code (AI agent) --- not written by a human. Generated by Claude Code |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
The delta adversarial round showed the guard exit escaped the reformatter per-path except Exception loop (SystemExit is not an Exception), so a multi-path run died at the first path with no summary, and the comment claiming parity with the module-scope guard convention overclaimed. The guard now raises NLBPinError, which the per-path loop reports before continuing to the summary, the comment states the actual contract, and a multi-path regression test pins it. Also dropped the dead REPO_ROOT binding the same round flagged. 93/93 tests pass. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MyNR6ZrvN6M4Rr8eZbHts
|
Pushed 750e14c addressing the final adversarial delta round at 112dd70:
93/93 tests. That round also verified no cache pollution from synthetic-config tests, explicit-config operation with yaml fully absent, and the perf fix (0.136s vs 9.921s pre-fix, byte-identical output). Posted by Claude Code (AI agent) --- not written by a human. Generated by Claude Code |
2026-08-26 23:01 UTC |
|
Claude finished review — View run Correcting this: I ran My prior message's headline ("Ready for merge, pending CI completion") was self-contradictory — a PR with in-progress required checks and no verdict-bearing review comment at its current HEAD is not ready for merge, full stop, regardless of qualifying language attached afterward. Restating accurately: Code-level findings: unchanged from my analysis above — the new commit ( PR status: NOT fully clean. VerdictBlocked / not clean at time of writing — CI is still in progress on HEAD Reviewed commit: 750e14c Stopping Point: Clean stopping point reached — this review-only task is complete. My prior message's "Ready for merge" framing was inaccurate and is superseded by this correction; no further review-only action is queued. Reviewed commit: 750e14c |
Closes #2085.
The local reformatter (
scripts/semantic-line-breaks.py) and CI'scheck-new-line-breaksjob disagreed on mid-line semicolons (and on lowercase-follower sentence boundaries).--writecould therefore produce a CI failure. Measured on #2073 ate88f2eecand again on #2092.Construction
Agreement is by construction, not by two implementations happening to match:
.github/workflows/validate.ymlalready pinsMorrison-Lab/gha/check-new-line-breaks@fdaeccc47273fb8c542d05b00d488242b8563b15.scripts/vendor/gha-check-new-line-breaks.pyis that script at that SHA (byte-identical; do not hand-edit).scripts/vendor/gha-check-new-line-breaks.pinrecords the git SHA and the sha256 of those bytes.scripts/lib/nlb_gate.pyrefuses to load if the pin disagrees withvalidate.ymlor the file bytes.emit_gate_clean: sentence splits come from the gate'ssplit_sentences; clause splits run only whenclassify_linereturnsclause.Refresh after an action-pin bump:
python3 scripts/sync-nlb-checker.py.Predicates checked (not only the semicolon)
Against the vendored checker at that SHA, the local splitter also missed
_SENT_BREAK_LOWER_RE(a period after two lowercase letters, then a lowercase word). That path is now the gate's function, so--writesplits it and no longer rejoins a hand-break. Extra lowercase abbrevs (min/hr/hrs) ride along on that same branch.#2081 left alone
The gate does not flag comma clauses (gha#336). A long comma-clause sentence stays one line;
scripts/test_slb.pypins that. Closing the 200-400 character comma-join trap is a different issue.Tests
python3 scripts/test_slb.py: 79 passed. Covers the pin, the #2085 semicolon line, lowercase-follower splits, code-span masking on a line long enough to be aclause, ellipsis-before-capital, and that #2081 comma joins are unchanged.