v2 chunk 1: scaffold note formatter (Path B parallel artifact)

Begin v2 (in-text citation note form) as a parallel artifact to the
v1 bibliography formatter, per the Path B architectural decision:
no shared mutable state, no shared prompt content, no v1 changes.

New artifacts:

- src/cmos/parser.py: add find_notes() / NoteDefinition /
  NotesParseResult as siblings to split_bibliography(). Targets
  pandoc-style markdown footnote definitions [^marker]: text.
  Single-line only; multi-line continuation deferred.

- src/cmos/note_formatter.py: new module mirroring formatter.py.
  17-rule SYSTEM_PROMPT_NOTES for CMOS 18 first-occurrence note
  form. Reuses cmos.runtime_validator.validate() unchanged — its
  structural checks all apply to note form too. Caller injection,
  retry loop, and model selection mirror v1.

- tests/test_parser.py: 7 new tests for find_notes().

- tests/test_note_formatter.py: 11 new tests mirroring v1 test
  discipline (no API calls, fake-caller injection, retry semantics,
  prompt smoke checks).

- exemplars/notes/: new subdirectory with 3 hand-synthesized
  first-occurrence note exemplars (book, journal article, chapter
  in edited book). Uses expected_note as the field name (not v1's
  expected_bibliography). harness/score.py:load_exemplars uses a
  non-recursive glob, so the v1 canary loader does not see these —
  Path B isolation is automatic.

v1 untouched. v1 canary still loads exactly 17 exemplars. Full
test suite: 96 v1 + 18 new v2 = 114 passing.

Smoke-tested all 3 v2 exemplars against real GPT-5 (not fakes),
2 runs each. book_first_yu and journal_first_kwon: 4/4 byte-perfect
on the first try. chapter_first_doyle: 0/2, surfacing two known
prompt gaps for the next iteration:

  1. Publisher abbreviation not expanded ("U of Chicago Press"
     preserved instead of "University of Chicago Press"). v1's
     formatter.py rule 14 is missing from the v2 prompt.

  2. "ed." pluralized to "eds." for multiple editors. CMOS NB
     uses "ed." invariantly regardless of editor count.

Both gaps are addressable prompt edits, not architectural problems —
exactly the kind of finding the dev loop is designed to surface.

Out of scope (deferred to chunk 2 and later): CLI extension,
shortened-form generation, document reassembly, harness scoring
loop integration, v2 linter rules, real-draft testing.
This commit is contained in:
Mark Eaton
2026-04-11 18:20:50 -04:00
parent 2a8c72ae22
commit 28ca3ac928
7 changed files with 614 additions and 3 deletions
+34
View File
@@ -0,0 +1,34 @@
# First-occurrence full note for a single-author book.
#
# Mirrors the v1 exemplar `book_single_author_yu.toml` (same source, same
# author, same publisher) but expects CMOS 18 NOTE form rather than
# bibliography form. Cited passage is on a fictional specific page (45)
# to exercise the rule that note form takes a specific page, not a
# range.
#
# Exercises:
# - Author in NORMAL order ("Charles Yu", not "Yu, Charles")
# - Comma separation between elements (vs periods in bibliography)
# - Publisher in PARENTHESES with year
# - No place of publication (CMOS 14.30, 18th ed.)
# - Specific page number
# - Italicized book title
# - Terminal period at end of note
#
# Field naming note: this exemplar uses `expected_note` (not v1's
# `expected_bibliography`). The v2 scoring harness loader is not yet
# wired up; chunk 1 of v2 only smoke-tests these via direct tomllib
# loading. The field name change is forward-compatible.
source = "Real published book; this is a fabricated specific page citation for testing note form"
type = "note_book"
tags = ["note", "book", "single-author", "first-occurrence"]
canary = true
messy_input = "yu, charles. interior chinatown. New York: Pantheon Books, 2020. p. 45."
expected_note = "Charles Yu, *Interior Chinatown* (Pantheon Books, 2020), 45."
[canonical]
author = "Charles Yu"
title = "Interior Chinatown"
publisher = "Pantheon Books"
year = 2020
page = 45
+32
View File
@@ -0,0 +1,32 @@
# First-occurrence full note for a chapter in an edited book.
#
# Mirrors the v1 exemplar `book_chapter_doyle.toml` (same chapter, same
# editors, same publisher, same year) but expects CMOS 18 NOTE form
# rather than bibliography form. The cited passage is on a fictional
# specific page (117) within the chapter's full range.
#
# Exercises:
# - Author in NORMAL order ("Kathleen Doyle")
# - Chapter title in straight quotes with closing comma INSIDE the
# quotes ("The Queen Mary Psalter,")
# - Lowercase "in" before the italicized book title (CMOS note form)
# - "ed." abbreviation, NOT "Edited by"
# - Editors in NORMAL order ("P. J. M. Marks and Stephen Parkin")
# - Publisher in PARENTHESES with year
# - Specific page citation
# - Terminal period
source = "Real published chapter; specific cited page is fabricated for note-form testing"
type = "note_chapter"
tags = ["note", "chapter", "edited-volume", "first-occurrence", "two-editors"]
canary = true
messy_input = "Kathleen Doyle, 'the queen mary psalter,' in The Book by Design: The Remarkable Story of the World's Greatest Invention, P. J. M. Marks and Stephen Parkin eds., Chicago: U of Chicago Press, 2023, pp. 114-120, cited p. 117."
expected_note = "Kathleen Doyle, \"The Queen Mary Psalter,\" in *The Book by Design: The Remarkable Story of the World's Greatest Invention*, ed. P. J. M. Marks and Stephen Parkin (University of Chicago Press, 2023), 117."
[canonical]
author = "Kathleen Doyle"
chapter_title = "The Queen Mary Psalter"
book_title = "The Book by Design: The Remarkable Story of the World's Greatest Invention"
editors = "ed. P. J. M. Marks and Stephen Parkin"
publisher = "University of Chicago Press"
year = 2023
page = 117
+34
View File
@@ -0,0 +1,34 @@
# First-occurrence full note for a journal article with DOI.
#
# Mirrors the v1 exemplar `journal_with_doi_kwon.toml` (same article,
# same author, same DOI) but expects CMOS 18 NOTE form rather than
# bibliography form. The cited passage is on a fictional specific
# page within the article's full range — the bibliography form has
# `181859`, the note form cites a single page like `1830`.
#
# Exercises:
# - Author in NORMAL order ("Hyeyoung Kwon")
# - Article title in straight quotes with closing comma INSIDE the
# quotes ("Inclusion Work: ...,")
# - Italicized journal name
# - Volume / issue / year format same as bibliography:
# *Journal* VOL, no. ISSUE (YEAR)
# - Specific page after the (YEAR): construct
# - DOI as full https URL
# - Terminal period
source = "Real published article; specific cited page is fabricated for note-form testing"
type = "note_journal"
tags = ["note", "journal", "single-author", "first-occurrence", "doi"]
canary = true
messy_input = "Kwon, Hyeyoung. \"inclusion work: children of immigrants claiming membership in everyday life.\" American Journal of Sociology, Vol. 127, Issue 6, 2022, pp. 1818-1859. DOI: 10.1086/720277. (cited p. 1830)"
expected_note = "Hyeyoung Kwon, \"Inclusion Work: Children of Immigrants Claiming Membership in Everyday Life,\" *American Journal of Sociology* 127, no. 6 (2022): 1830, https://doi.org/10.1086/720277."
[canonical]
author = "Hyeyoung Kwon"
article_title = "Inclusion Work: Children of Immigrants Claiming Membership in Everyday Life"
journal = "American Journal of Sociology"
volume = 127
issue = 6
year = 2022
page = 1830
doi = "https://doi.org/10.1086/720277"