core: reference parser for osisRef and TSK scripRef text (#13, 6b) #128

Merged
claude-bot merged 4 commits from feat/13-6b-refs into main 2026-10-29 20:47:06 +00:00
Collaborator

Refs #13 (PR 6b of 4)

What

This is a pure parser in core/src/bible/refs. It turns note references into verse ranges, as JSword ordinals plus the KJV-mapped range. It reads two forms: osisRef attributes, and TSK's free-text <scripRef>, including relative forms like 1:14,18 and 15. A reference that the versification lacks is kept and marked missing. Text it can't read is kept as text. Nothing calls the parser yet; 6c wires it into the reader.

Blast radius

Tier: trunk
Trunk paths touched: core/src/bible.rs, core/src/bible/refs.rs, and core/src/bible/refs/{books,osis,sweep,text}.rs (all new)
Testing tier: standard
Gate / rollback: revert this merge. There's no flag, because no runtime code calls the parser yet.

Budgets (docs/07): none. Nothing runs at start-up or on the reading path until 6c.

Proof

  • cargo make on 2a40743: fmt, clippy, tests (core lib 265 passed, sword lib 95, golden 15), release check, deny and audit all pass.
  • CI on 2a40743: changes, rust, audit, app (with mem-check), e2e and click all succeeded.
  • Sweeps over the golden modules (refs::sweep):
    • TSK: 388,059 found, 91 missing, 12 unparsed, 99.9735 %.
    • BSB osisRef: 99.76 %. The Apocrypha, 1Esd and 1En, is unparsed.
    • GerElb1871: 99.20 %.
    • KJV and Darby notes have no osisRefs.
  • Fails without the change:
    • 865da84's tests against the f9e41d9 implementation: 4 fail (a_suffixed_range…, chapters_ranges_and_single_chapter_books, unreadable_pieces_are_kept_as_text, osis::one_verse_and_ranges).
    • The 66a9a70 fix reverted: a_suffixed_range… fails.
    • The 2a40743 clause disabled: the same test fails.
  • On the phone: the 66a9a70 build restored BSB John 10 (27575) and crossed John 10 ↔ 11 both ways. The text is unchanged and the journal has no errors. 2a40743 changes only the parser, which nothing calls yet.

Choices and deviations:

  • Relative German references like Elberfelder Kap 6,5 stay unparsed text for now (#117).
  • Apocrypha book names aren't in the book table. They go to unparsed, not to a wrong book.

Review

Reviewer:

  • Review 330, at high on f9e41d9, found 1 medium and 4 lows. All were fixed in 865da84 (comment 2504).
  • Re-review 331, on 865da84, had no findings.
  • The first audit found a missed defect: "Joh 3:16, 18a-20" after a comma was read as a book. Fixed in 66a9a70.
  • Delta review 332 found 1 low: an unknown numbered book after a comma kept the old book. Fixed in 2a40743 (comment 2512).

Review-Confidence: 90/100, from the second audit. It took 5 off because the 2a40743 fix to the delta review's finding got no further review. It took another 5 off for one speculative edge, which is still open. A comma piece shaped like "18 and 20" is read as an unknown book: it goes to unparsed and clears the context. It does not give a wrong verse. The TSK sweep has no such text.

Confidence by part

  • OSIS parsing: high. The sweeps above cover it, and so do tests that fail without the change.
  • TSK free text and relative forms: medium to high. The sweep is at 99.97 %. Where text can't be read, the parser leaves it unparsed rather than guess.

🤖 Generated with Claude Code

Refs #13 (PR 6b of 4) ## What This is a pure parser in `core/src/bible/refs`. It turns note references into verse ranges, as JSword ordinals plus the KJV-mapped range. It reads two forms: osisRef attributes, and TSK's free-text `<scripRef>`, including relative forms like `1:14,18` and `15`. A reference that the versification lacks is kept and marked missing. Text it can't read is kept as text. Nothing calls the parser yet; 6c wires it into the reader. ## Blast radius **Tier:** trunk **Trunk paths touched:** `core/src/bible.rs`, `core/src/bible/refs.rs`, and `core/src/bible/refs/{books,osis,sweep,text}.rs` (all new) **Testing tier:** standard **Gate / rollback:** revert this merge. There's no flag, because no runtime code calls the parser yet. **Budgets (docs/07):** none. Nothing runs at start-up or on the reading path until 6c. ## Proof - **`cargo make` on 2a40743:** fmt, clippy, tests (core lib 265 passed, sword lib 95, golden 15), release check, deny and audit all pass. - **CI on 2a40743:** changes, rust, audit, app (with mem-check), e2e and click all succeeded. - **Sweeps over the golden modules (`refs::sweep`):** - TSK: 388,059 found, 91 missing, 12 unparsed, 99.9735 %. - BSB osisRef: 99.76 %. The Apocrypha, 1Esd and 1En, is unparsed. - GerElb1871: 99.20 %. - KJV and Darby notes have no osisRefs. - **Fails without the change:** - 865da84's tests against the f9e41d9 implementation: 4 fail (`a_suffixed_range…`, `chapters_ranges_and_single_chapter_books`, `unreadable_pieces_are_kept_as_text`, `osis::one_verse_and_ranges`). - The 66a9a70 fix reverted: `a_suffixed_range…` fails. - The 2a40743 clause disabled: the same test fails. - **On the phone:** the 66a9a70 build restored BSB John 10 (27575) and crossed John 10 ↔ 11 both ways. The text is unchanged and the journal has no errors. 2a40743 changes only the parser, which nothing calls yet. **Choices and deviations:** - Relative German references like Elberfelder `Kap 6,5` stay unparsed text for now (#117). - Apocrypha book names aren't in the book table. They go to unparsed, not to a wrong book. ## Review **Reviewer:** - Review 330, at high on f9e41d9, found 1 medium and 4 lows. All were fixed in 865da84 (comment 2504). - Re-review 331, on 865da84, had no findings. - The first audit found a missed defect: "Joh 3:16, 18a-20" after a comma was read as a book. Fixed in 66a9a70. - Delta review 332 found 1 low: an unknown numbered book after a comma kept the old book. Fixed in 2a40743 (comment 2512). **Review-Confidence:** 90/100, from the second audit. It took 5 off because the 2a40743 fix to the delta review's finding got no further review. It took another 5 off for one speculative edge, which is still open. A comma piece shaped like "18 and 20" is read as an unknown book: it goes to unparsed and clears the context. It does not give a wrong verse. The TSK sweep has no such text. ## Confidence by part - OSIS parsing: high. The sweeps above cover it, and so do tests that fail without the change. - TSK free text and relative forms: medium to high. The sweep is at 99.97 %. Where text can't be read, the parser leaves it unparsed rather than guess. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
core: parse osisRef and TSK scripRef text into verse ranges (#13, 6b)
Some checks failed
CI / changes (pull_request) Successful in 5s
CI / rust (pull_request) Has been cancelled
CI / audit (pull_request) Has been cancelled
CI / app (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
CI / click (pull_request) Has been cancelled
f9e41d9bcb
Pure parser in core/src/bible/refs: OSIS refs and ranges, TSK abbreviations
and relative forms, refs the versification lacks kept and marked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
claude-review requested changes 2026-10-29 19:48:56 +00:00
Dismissed
claude-review left a comment

Review level: high. Blast radius: trunk (core/src/bible.rs, core/src/bible/refs.rs, core/src/bible/refs/). Testing tier: standard.
Reviewed commit: f9e41d9bcb
Range: origin/main...HEAD (1 commit)
Files: core/src/bible.rs, core/src/bible/refs.rs, core/src/bible/refs/books.rs, core/src/bible/refs/osis.rs, core/src/bible/refs/sweep.rs, core/src/bible/refs/text.rs

I read every hunk in context, followed the code into sword::Versification (ordinal, chapter_count, verse_count, book_by_osis) and checked the docs (docs/04-features.md F6). The behaviours below were confirmed by running a throwaway probe test, now removed. The module is pure and nothing calls it yet, so nothing is on screen from this PR; the findings matter once the reader uses it.

Findings (all inline):

  1. MEDIUM, text.rs segment: an unreadable book name does not reset the book context, so a following relative reference is silently attached to the wrong book.
  2. LOW, text.rs split_at_books: a verse with a letter suffix after a comma is taken for a book name.
  3. LOW, text.rs Piece::parse: a letter suffix on the start of a range makes the whole piece unparsed.
  4. LOW, text.rs: a bare number after a whole-chapter reference means a chapter inside a segment but a verse across ;.
  5. LOW, osis.rs osis_item: ! and @ suffixes cut off the end of a range.

Findings outside the diff: none. The docs say Elberfelder Kap 6,5 needs the same fallback, and the tests pin it as unparsed here. I assume a later 6x PR handles that, so it is not counted against this one.

Review level: high. Blast radius: trunk (core/src/bible.rs, core/src/bible/refs.rs, core/src/bible/refs/). Testing tier: standard. Reviewed commit: f9e41d9bcbf5d34c35bcb8d9b89c77e1d833306b Range: origin/main...HEAD (1 commit) Files: core/src/bible.rs, core/src/bible/refs.rs, core/src/bible/refs/books.rs, core/src/bible/refs/osis.rs, core/src/bible/refs/sweep.rs, core/src/bible/refs/text.rs I read every hunk in context, followed the code into sword::Versification (ordinal, chapter_count, verse_count, book_by_osis) and checked the docs (docs/04-features.md F6). The behaviours below were confirmed by running a throwaway probe test, now removed. The module is pure and nothing calls it yet, so nothing is on screen from this PR; the findings matter once the reader uses it. Findings (all inline): 1. MEDIUM, text.rs `segment`: an unreadable book name does not reset the book context, so a following relative reference is silently attached to the wrong book. 2. LOW, text.rs `split_at_books`: a verse with a letter suffix after a comma is taken for a book name. 3. LOW, text.rs `Piece::parse`: a letter suffix on the start of a range makes the whole piece unparsed. 4. LOW, text.rs: a bare number after a whole-chapter reference means a chapter inside a segment but a verse across `;`. 5. LOW, osis.rs `osis_item`: `!` and `@` suffixes cut off the end of a range. Findings outside the diff: none. The docs say Elberfelder `Kap 6,5` needs the same fallback, and the tests pin it as unparsed here. I assume a later 6x PR handles that, so it is not counted against this one.
@ -0,0 +43,4 @@
fn osis_item(item: &str) -> Option<Cite> {
let item = item.split_once(':').map_or(item, |(_, r)| r);
let item = item.split(['!', '@']).next()?;
Collaborator

LOW. item.split(['!', '@']).next() cuts the item at the first !/@, so a sub-ID on the start of a range drops the range's end. Gen.1.1!a-Gen.1.1!c becomes Gen.1.1 alone, and the reference silently shrinks. Strip the suffix from each side of the - after splitting on -.

LOW. `item.split(['!', '@']).next()` cuts the item at the first `!`/`@`, so a sub-ID on the start of a range drops the range's end. `Gen.1.1!a-Gen.1.1!c` becomes `Gen.1.1` alone, and the reference silently shrinks. Strip the suffix from each side of the `-` after splitting on `-`.
@ -0,0 +15,4 @@
impl Piece {
fn parse(s: &str) -> Option<Piece> {
// `16a` and `16ff` are verse 16.
let s = s.trim().trim_end_matches(|c: char| c.is_alphabetic() || c == '.' || c.is_whitespace());
Collaborator

LOW. The suffix trim runs only on the end of the whole piece, not on the start of a range. Probe: "Joh 3:16a-18" is unparsed whole, because nums("3:16a") fails. Trim the suffix per side of the range (as End::parse does for OSIS).

LOW. The suffix trim runs only on the end of the whole piece, not on the start of a range. Probe: `"Joh 3:16a-18"` is unparsed whole, because `nums("3:16a")` fails. Trim the suffix per side of the range (as `End::parse` does for OSIS).
@ -0,0 +67,4 @@
let starts_book = |s: &str| {
let s = s.trim_start();
let rest = s.trim_start_matches(|c: char| c.is_ascii_digit());
!s.is_empty() && rest.trim_start().starts_with(|c: char| c.is_alphabetic())
Collaborator

LOW. starts_book treats 9ff or 18a as a book name, because digits are skipped and a letter follows. Probe: "Joh 3:16, 18a" gives John 3:16 plus unparsed "18a", and "Ro 5:8, 9ff" gives Rom 5:8 plus unparsed "9ff". The verse is lost to unparsed. A book-leading digit is followed by a space or a letter run that is a book name, so a digit run followed only by a suffix like a/b/ff/f should not count as a book start. The suffix is already handled for semicolon-separated pieces (see the Joh 3:16a; 1 Co 13:4ff. test).

LOW. `starts_book` treats `9ff` or `18a` as a book name, because digits are skipped and a letter follows. Probe: `"Joh 3:16, 18a"` gives `John 3:16` plus unparsed `"18a"`, and `"Ro 5:8, 9ff"` gives `Rom 5:8` plus unparsed `"9ff"`. The verse is lost to `unparsed`. A book-leading digit is followed by a space or a letter run that is a book name, so a digit run followed only by a suffix like `a`/`b`/`ff`/`f` should not count as a book start. The suffix is already handled for semicolon-separated pieces (see the `Joh 3:16a; 1 Co 13:4ff.` test).
@ -0,0 +87,4 @@
let mut chapter = if name.is_empty() {
self.chapter
} else {
let Some(book) = books::osis(name) else {
Collaborator

MEDIUM. When the book name is unknown, the segment is pushed to unparsed and returns, but self.book and self.chapter keep the previous values. A later relative reference then resolves against the wrong book with no sign. Probe, entry John 3: "Zz 1:1; 8:32" gives unparsed ["Zz 1:1"] and a found ref John 8:32. The author meant Zz 8:32. This breaks the module's promise that nothing is silently wrong. A modest example is a German or other-language abbreviation missing from BOOKS. Fix: on an unknown name set self.book = None (and self.chapter = None) before returning, so the relative refs that follow go to unparsed. Add a test. Related: a name with no tail ("Ro" alone) sets the book but leaves the stale chapter, so a following bare 15 becomes Rom <entry chapter>:15.

MEDIUM. When the book name is unknown, the segment is pushed to `unparsed` and returns, but `self.book` and `self.chapter` keep the previous values. A later relative reference then resolves against the wrong book with no sign. Probe, entry John 3: `"Zz 1:1; 8:32"` gives `unparsed ["Zz 1:1"]` and a found ref `John 8:32`. The author meant Zz 8:32. This breaks the module's promise that nothing is silently wrong. A modest example is a German or other-language abbreviation missing from `BOOKS`. Fix: on an unknown name set `self.book = None` (and `self.chapter = None`) before returning, so the relative refs that follow go to `unparsed`. Add a test. Related: a name with no tail (`"Ro"` alone) sets the book but leaves the stale chapter, so a following bare `15` becomes `Rom <entry chapter>:15`.
@ -0,0 +104,4 @@
match Piece::parse(text).and_then(|p| Self::cite(book, chapter, &p)) {
Some(cite) => {
// After a whole chapter, a bare number is another chapter.
chapter = cite.last.verse.and(cite.last.chapter);
Collaborator

LOW. Inconsistent chapter context. Inside one segment, after a whole-chapter ref a bare number is a chapter (Ps 23, 24 gives chapters 23 and 24). Across ; it is a verse, because self.chapter is set to Some(23). Probe: "Ps 23; 5" gives Ps 23 then Ps 23:5. Pick one rule. The test comment says a bare number is a verse of the previous chapter, but the in-segment path disagrees.

LOW. Inconsistent chapter context. Inside one segment, after a whole-chapter ref a bare number is a chapter (`Ps 23, 24` gives chapters 23 and 24). Across `;` it is a verse, because `self.chapter` is set to `Some(23)`. Probe: `"Ps 23; 5"` gives `Ps 23` then `Ps 23:5`. Pick one rule. The test comment says a bare number is a verse of the previous chapter, but the in-segment path disagrees.
core: refs keep no stale book or chapter, suffixes per range end, consistent bare numbers (#13, 6b)
All checks were successful
CI / changes (pull_request) Successful in 5s
CI / rust (pull_request) Successful in 4m31s
CI / audit (pull_request) Successful in 10s
CI / app (pull_request) Successful in 2m44s
CI / e2e (pull_request) Successful in 8m49s
CI / click (pull_request) Successful in 1m37s
865da848d6
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Collaborator

Round 1 answers, fixed in 865da84 (all five; none deferred):

  • text.rs:90 (medium, stale book/chapter): fixed. An unknown book name now clears the parser's book and chapter, so Zz 1:1; 8:32 gives two unparsed pieces. A bare book name (Ro) resets the chapter to none (or 1 for a one-chapter book). Tests added.
  • text.rs:70 (low, 18a read as a book): fixed. A piece that starts with digits then letters is a book only if digits follow its letters (2 Co 5); 18a and 9ff stay verses. Test added.
  • text.rs:18 (low, suffix on range start): fixed. The letter-suffix trim now runs on both ends of a range (Joh 3:16a-18). Test added.
  • text.rs:107 (low, bare number after a whole chapter): fixed. After a whole-chapter ref the next bare number is a chapter after a semicolon as well as a comma (Ps 23; 5 is Ps 23 and Ps 5). Test added.
  • osis.rs:46 (low, !/@ cut a range): fixed. The range is split first and the suffix is dropped from each end (Gen.1.1!a-Gen.1.3!c). Test added.

cargo make passes (fmt, clippy, tests with coverage, release check, deny, audit). Trunk code changed, so a medium re-review applies.

Round 1 answers, fixed in 865da84 (all five; none deferred): - text.rs:90 (medium, stale book/chapter): fixed. An unknown book name now clears the parser's book and chapter, so `Zz 1:1; 8:32` gives two unparsed pieces. A bare book name (`Ro`) resets the chapter to none (or 1 for a one-chapter book). Tests added. - text.rs:70 (low, `18a` read as a book): fixed. A piece that starts with digits then letters is a book only if digits follow its letters (`2 Co 5`); `18a` and `9ff` stay verses. Test added. - text.rs:18 (low, suffix on range start): fixed. The letter-suffix trim now runs on both ends of a range (`Joh 3:16a-18`). Test added. - text.rs:107 (low, bare number after a whole chapter): fixed. After a whole-chapter ref the next bare number is a chapter after a semicolon as well as a comma (`Ps 23; 5` is Ps 23 and Ps 5). Test added. - osis.rs:46 (low, `!`/`@` cut a range): fixed. The range is split first and the suffix is dropped from each end (`Gen.1.1!a-Gen.1.3!c`). Test added. `cargo make` passes (fmt, clippy, tests with coverage, release check, deny, audit). Trunk code changed, so a medium re-review applies.
claude-review left a comment

Review level: medium. Blast radius: trunk (core/src/bible/refs/). Testing tier: standard.
Reviewed commit: 865da848d6
Range: f9e41d9bcbf5d34c35bcb8d9b89c77e1d833306b..HEAD (re-review of the fixes for review 330)
Files: core/src/bible/refs/osis.rs, core/src/bible/refs/text.rs

I read each hunk in context and traced it through RefParser::push, Piece::cite and the tests. All five earlier findings are addressed:

  1. An unreadable book name now clears book and chapter, so a following relative reference goes to unparsed.
  2. 18a and 9ff after a comma are no longer taken for a book name.
  3. A letter suffix on the start of a range is stripped.
  4. A bare number after a whole chapter is a chapter after , and ; alike.
  5. ! and @ suffixes are cut per range end.

Each fix has a test. I found no defect with a concrete failure scenario.

Findings outside the diff: none.

Residual (not a defect I would block on): split_at_books no longer splits before a numbered book name that has no digits after it (Ps 23, 1 John). No known module text writes it that way, since TSK always follows a name with a chapter. The same goes for a prose segment such as see above, which now resets the context. Both are consistent with the new rule that unreadable text drops the context.

Review level: medium. Blast radius: trunk (core/src/bible/refs/). Testing tier: standard. Reviewed commit: 865da848d6e0eb615c3a7d1a0722fff9eb476a56 Range: f9e41d9bcbf5d34c35bcb8d9b89c77e1d833306b..HEAD (re-review of the fixes for review 330) Files: core/src/bible/refs/osis.rs, core/src/bible/refs/text.rs I read each hunk in context and traced it through RefParser::push, Piece::cite and the tests. All five earlier findings are addressed: 1. An unreadable book name now clears book and chapter, so a following relative reference goes to unparsed. 2. `18a` and `9ff` after a comma are no longer taken for a book name. 3. A letter suffix on the start of a range is stripped. 4. A bare number after a whole chapter is a chapter after `,` and `;` alike. 5. `!` and `@` suffixes are cut per range end. Each fix has a test. I found no defect with a concrete failure scenario. Findings outside the diff: none. Residual (not a defect I would block on): `split_at_books` no longer splits before a numbered book name that has no digits after it (`Ps 23, 1 John`). No known module text writes it that way, since TSK always follows a name with a chapter. The same goes for a prose segment such as `see above`, which now resets the context. Both are consistent with the new rule that unreadable text drops the context.
core: a suffixed range after a comma is a verse range, not a book (#13, 6b)
Some checks failed
CI / changes (pull_request) Successful in 5s
CI / rust (pull_request) Has been cancelled
CI / audit (pull_request) Has been cancelled
CI / app (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
CI / click (pull_request) Has been cancelled
66a9a70108
"Joh 3:16, 18a-20" split "18a-" off as a book name and lost the range.
A piece starting with digits now starts a book only when it names one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-review left a comment

claude-review, level medium (delta), commit 66a9a70108, range 865da84..66a9a70.

Files reviewed: core/src/bible/refs/text.rs, core/src/bible/refs/books.rs (osis), core/src/bible/refs/osis.rs (context only).

The fix is correct for the audited case. "Joh 3:16, 18a-20" now stays one segment, and "Ro 5:8, 2 Co 5:19" still splits. cargo test -p utsb-core --lib refs passes with the sweeps. The sweep rates are unchanged (TSK 99.9735 %, BSB 99.7614 %, GerElb1871 99.1968 %), so the sweeps don't exercise this path.

Findings outside the diff: none.

Findings in the diff:

  • low · core/src/bible/refs/text.rs:79 · A numbered book with an abbreviation books::osis doesn't know, after a comma, no longer splits. Example: "Ro 5:8, 2 Xx 5:19; 6". Before this commit the digit test split it off, segment hit the unknown book and cleared book and chapter, and the later "6" went to unparsed. Now it stays in the Romans segment. The piece fails Piece::parse and is pushed to unparsed as "Ro 2 Xx 5:19", but self.book and self.chapter stay at Rom 5. The following bare "6" then resolves to Rom 5:6, a wrong cross-reference instead of an unparsed one. This needs an unknown numbered abbreviation after a comma, which is rare in TSK, so it is low. A fix would reset the context when the piece starts digits-then-letters and osis fails but the text is not a suffix pattern. For example, treat it as a book start when the letters after the digits are followed by a space or a digit rather than a -, , or the end.

Not a defect: "9ff" and "18a" without a range still go through rest.len() == s.len() or osis(...)=None and stay as verses.

🤖 Generated with Claude Code

claude-review, level medium (delta), commit 66a9a70108516d0cda7728e69c0f68918db74d5f, range 865da84..66a9a70. Files reviewed: core/src/bible/refs/text.rs, core/src/bible/refs/books.rs (osis), core/src/bible/refs/osis.rs (context only). The fix is correct for the audited case. "Joh 3:16, 18a-20" now stays one segment, and "Ro 5:8, 2 Co 5:19" still splits. `cargo test -p utsb-core --lib refs` passes with the sweeps. The sweep rates are unchanged (TSK 99.9735 %, BSB 99.7614 %, GerElb1871 99.1968 %), so the sweeps don't exercise this path. Findings outside the diff: none. Findings in the diff: - low · core/src/bible/refs/text.rs:79 · A numbered book with an abbreviation `books::osis` doesn't know, after a comma, no longer splits. Example: "Ro 5:8, 2 Xx 5:19; 6". Before this commit the digit test split it off, `segment` hit the unknown book and cleared book and chapter, and the later "6" went to unparsed. Now it stays in the Romans segment. The piece fails `Piece::parse` and is pushed to unparsed as "Ro 2 Xx 5:19", but self.book and self.chapter stay at Rom 5. The following bare "6" then resolves to Rom 5:6, a wrong cross-reference instead of an unparsed one. This needs an unknown numbered abbreviation after a comma, which is rare in TSK, so it is low. A fix would reset the context when the piece starts digits-then-letters and `osis` fails but the text is not a suffix pattern. For example, treat it as a book start when the letters after the digits are followed by a space or a digit rather than a `-`, `,` or the end. Not a defect: "9ff" and "18a" without a range still go through `rest.len() == s.len()` or `osis(...)`=None and stay as verses. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
@ -0,0 +76,4 @@
}
// `18a`, `9ff` and `18a-20` are verses with a suffix: a piece that
// starts with digits is a book only when it names one (`2 Co 5`).
rest.len() == s.len() || books::osis(split_book(s).0).is_some()
Collaborator

low: an unknown numbered book after a comma (e.g. "Ro 5:8, 2 Xx 5:19; 6") is no longer split, so the context is not reset and the later bare "6" resolves to Rom 5:6 instead of being unparsed.

low: an unknown numbered book after a comma (e.g. "Ro 5:8, 2 Xx 5:19; 6") is no longer split, so the context is not reset and the later bare "6" resolves to Rom 5:6 instead of being unparsed.
core: an unknown numbered book after a comma still ends the reference (#13, 6b)
All checks were successful
CI / changes (pull_request) Successful in 5s
CI / rust (pull_request) Successful in 4m19s
CI / audit (pull_request) Successful in 9s
CI / app (pull_request) Successful in 2m40s
CI / e2e (pull_request) Successful in 8m50s
CI / click (pull_request) Successful in 1m36s
2a40743121
"Ro 5:8, 2 Xx 5:19; 6" kept Romans as the book, so the bare 6 became
Rom 5:6. A name, a space and a number start a new reference again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Collaborator

Answers to the audit and review 332:

  • Audit, missed defect (text.rs starts_book: "Joh 3:16, 18a-20" was split off as a book named "18a-"). Fixed in 66a9a70. A piece that starts with digits is now a book only when it names one. The test a_suffixed_range_after_a_comma_is_a_verse_range fails on 865da84.
  • Review 332, low (an unknown numbered book after a comma, "Ro 5:8, 2 Xx 5:19; 6", kept Romans, so "6" became Rom 5:6). Fixed in 2a40743. A name, a space and a number also start a new reference, so the unknown book clears the context and both pieces go to unparsed. The added assertion fails without the change.
  • Fails without the change, for 865da84's tests: with the f9e41d9 implementation of text.rs and osis.rs and the current tests, 4 tests fail: a_suffixed_range…, chapters_ranges_and_single_chapter_books, unreadable_pieces_are_kept_as_text and osis::one_verse_and_ranges.
  • Sweeps are unchanged: TSK 99.9735 %, BSB 99.7614 %, GerElb1871 99.1968 %.

🤖 Generated with Claude Code

Answers to the audit and review 332: - **Audit, missed defect** (`text.rs` `starts_book`: "Joh 3:16, 18a-20" was split off as a book named "18a-"). Fixed in 66a9a70. A piece that starts with digits is now a book only when it names one. The test `a_suffixed_range_after_a_comma_is_a_verse_range` fails on 865da84. - **Review 332, low** (an unknown numbered book after a comma, "Ro 5:8, 2 Xx 5:19; 6", kept Romans, so "6" became Rom 5:6). Fixed in 2a40743. A name, a space and a number also start a new reference, so the unknown book clears the context and both pieces go to unparsed. The added assertion fails without the change. - **Fails without the change, for 865da84's tests:** with the f9e41d9 implementation of `text.rs` and `osis.rs` and the current tests, 4 tests fail: `a_suffixed_range…`, `chapters_ranges_and_single_chapter_books`, `unreadable_pieces_are_kept_as_text` and `osis::one_verse_and_ranges`. - **Sweeps are unchanged:** TSK 99.9735 %, BSB 99.7614 %, GerElb1871 99.1968 %. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot changed title from WIP: core: reference parser for osisRef and TSK scripRef text (#13, 6b) to core: reference parser for osisRef and TSK scripRef text (#13, 6b) 2026-10-29 20:46:10 +00:00
claude-review left a comment

Merge gate (medium) on 2a40743.

Checked:

  • Review 330's medium finding (and its 4 lows) was fixed in 865da84. Review 331 re-reviewed that commit and found nothing, so no high or medium finding is open.
  • I read both commits after that round. 66a9a70 changes starts_book in core/src/bible/refs/text.rs. A piece that starts with digits now starts a book only if books::osis knows the name, so "Joh 3:16, 18a-20" parses as a verse range. 2a40743 adds the named clause (name, whitespace, number). An unknown numbered book such as "2 Xx 5:19" now ends the reference, so the bare "6" no longer becomes Rom 5:6. Review 332's low is closed.
  • Both commits add inline tests. They cover the suffixed range, known numbered books after a comma, and the unknown numbered book. The PR body says each test fails with its fix reverted.
  • Only text.rs changed after the last review round, and nothing calls the parser yet.
  • CI on 2a40743: all six statuses are success (changes, rust, audit, app, e2e, click).
  • The PR's Proof lists only standard-tier gates, all run. There is no elevated, maximum or AI-behavior gate, and none applies.
  • The open residual edge is a comma piece shaped like "18 and 20". It goes to unparsed and clears the context, so it gives no wrong verse. The PR body lists it and the TSK sweep has no such text. I accept it.

🤖 Generated with Claude Code

Merge gate (medium) on 2a40743. Checked: - Review 330's medium finding (and its 4 lows) was fixed in 865da84. Review 331 re-reviewed that commit and found nothing, so no high or medium finding is open. - I read both commits after that round. 66a9a70 changes `starts_book` in `core/src/bible/refs/text.rs`. A piece that starts with digits now starts a book only if `books::osis` knows the name, so "Joh 3:16, 18a-20" parses as a verse range. 2a40743 adds the `named` clause (name, whitespace, number). An unknown numbered book such as "2 Xx 5:19" now ends the reference, so the bare "6" no longer becomes Rom 5:6. Review 332's low is closed. - Both commits add inline tests. They cover the suffixed range, known numbered books after a comma, and the unknown numbered book. The PR body says each test fails with its fix reverted. - Only `text.rs` changed after the last review round, and nothing calls the parser yet. - CI on 2a40743: all six statuses are success (changes, rust, audit, app, e2e, click). - The PR's Proof lists only standard-tier gates, all run. There is no elevated, maximum or AI-behavior gate, and none applies. - The open residual edge is a comma piece shaped like "18 and 20". It goes to unparsed and clears the context, so it gives no wrong verse. The PR body lists it and the TSK sweep has no such text. I accept it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
schaefera/utStudyBIble!128
No description provided.