Repository navigation
fix(cobol): sentence-anchored paragraph headers (#3197) + census identifier lexicon (#3198) - #3226
Merged
Merged
Conversation
…tifier lexicon (#3198) #3197: a COBOL paragraph or section header begins a SENTENCE, so the previous code line must end with a period. The last line of a multi-line statement or data description is not a paragraph, however it is indented. Engine units per the answer key: zopeneditor P 58/67 -> 58/58, CBSA P 680/706 -> 680/680, both now exact against the hand-verified key and equal to the forge. The fix is not #2538's deferred fixed/free-format detection, and not an Area-A column anchor. Measured over language-crucible v1.3.0, keyword-rosetta and the three pinned mainframe corpora: every Area-A header already follows a completed sentence (0 exceptions), and real paragraphs DO sit in Area B in accepted source, so a column anchor would drop them. The deciding context is the previous line, which no lookbehind can span, so this is a registry-declared scope filter -- honoured by BOTH func_start consumers, the count in coding_analysis and the unit list in _slice_by_labels (#2753's trap). #3198: the census judges an occurrence by the language's own identifier rules. Two optional top-level registry keys, defaulting to today's reading: - identifier_case: "insensitive" -- `perform a-para` names `A-PARA` - identifier_extra_chars -- `-` is a COBOL name character, so `B-PARA-EXIT` is no longer a mention of `B-PARA` cobol is the only language that declares either; #3225 audits the rest. The entry paragraph stays flagged: nothing names it, and corollary 3 says the census never meant "dead". #3120's dead-code switch is re-scoped accordingly -- the engine supplies the (now exact) unit inventory, the forge keeps reachability, which the answer key scores 62/62 on CBSA. Golden masters re-blessed: 4196 raw differences, 2333 of them 3D-coordinate ripple, all 1863 real ones inside the cobol group plus the corpus-wide aggregates that follow. Verified by hand on BANKDATA (36 -> 28 units, every dropped one an OCCURS/MOVE/REMAINDER operand). Closes #3197, closes #3198. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3197, closes #3198. Part of #3122. Engine PR, with a golden-master bless.
#3197 — a paragraph header begins a sentence
func_startaccepted any loneNAME.indented into Area B, so the last line of a multi-line statement or data description became a paragraph, and split the real paragraph's span while it was at it.The proposed fix was file-level fixed/free-format detection (#2538's deferred item). The measurement says it isn't needed, and that an Area-A column anchor would be wrong. Over language-crucible v1.3.0, keyword-rosetta and all three pinned corpora:
CBL0601v01InOutLineLoop.cblputs every paragraph in column 13So the rule is the language's own: a header can only start where the previous sentence ended. It is format-independent, and it keeps the Area-B paragraphs that accepted source contains (a compiler warns and carries on).
The deciding context is the previous line, which a Python lookbehind cannot span, so this is a registry-declared scope filter (
_scope_filters: {"func_start": "cobol_sentence_start"}), honoured by bothfunc_startconsumers: the count incoding_analysisand the unit list in_slice_by_labels. A filter only one of them honoured would make the same file report two different paragraph counts (#2753's unfiltered-consumer trap); a test asserts they agree.One extra phantom shape came with it: an IDENTIFICATION/ENVIRONMENT operand on its own line (
PROGRAM-ID.thenCOACTUPC.,OBJECT-COMPUTER.thenXXXXX083.). The period ends a sentence, so the operand needs its own keyword list.#3198 — the census judges occurrences by the language's lexicon
docs/unreferenced_by_name_contract.mdsettles most of this issue. Corollary 3 already says the census means "nothing else in this file names this unit", and explicitly not "dead". So the issue's "fuller" option (a reachability pass) would change what the signal means, not fix it. What IS broken is the name test:perform a-paradid not nameA-PARA.-is a COBOL name character:B-PARA-EXITcounted as a mention ofB-PARA, so a paragraph read as referenced because a different paragraph's name began with its name (13 units across the corpora).Two optional top-level keys carry it, both defaulting to today's reading, so every language that declares neither is unchanged by construction:
cobol is the only language that declares either. The other case-insensitive languages have the same defect latent; declaring a key moves that language's census and needs its own evidence, so they are audited per language in #3225. A closed-set test pins the family, the same way
invocation_modelis pinned.The issue's micro-repro now reads:
perform a-parais a referenceB-PARA-EXIT#3120's dead-code switch is re-scoped, not pending
The entry paragraph stays flagged, because nothing names it.
usage_statustherefore cannot be COBOL dead code under its own contract. The engine supplies the unit inventory (now exact) and the forge keeps reachability, which the answer key scores 62/62 · 10/10 non-trivial on CBSA. A future engine-side reachability signal would be a new field besideusage_status, with its own issue and evidence — not a redefinition. Recorded indocs/refraction_engine_differential.md.Measured (answer key, engine column)
python tests/tools/mainframe_corpus.py score(#3213), before → after:usage_statusclaimsRecall was already complete on both, so the phantoms were pure precision loss.
The bless
crucible_check.py --update --yes, after scoping the uncapped diff (the tool's own output is capped twice):cobollanguage group, plus the corpus-wide aggregate rows that necessarily follow (ecosystem summary, archetype mix, health averages).Method Declarationsdown in all 183 moved files;Unreferenced By Name122 down / 44 up (the hyphen fix flags, the case fix and the phantom removal clear).BANKDATA.cbl, 36 → 28 units: every dropped one is anOCCURS … DEPENDING ONoperand, aMOVE … TOtarget or aDIVIDE … REMAINDERoperand. No real paragraph is lost.Verification
pytest tests/crucible_check.py(both fixtures, after bless)rosetta_audit.pytri_comparison_chart.py --all --citree_sitter_accuracy_audit.py --ci --allaudit_check.pyrefraction_snapshot.py check(+ all 3 full corpora)New tests:
test_cobol_sentence_paragraphs_3197.py(free format, fixed format with a column-7 continuation, real Area-B paragraphs, four statement-tail shapes, count/unit-list agreement, and the span no longer truncating).test_unreferenced_by_name_contract_2806.pygains corollary 7's rows.test_orphan_hyphen_fast_path_3182.pyis updated: its parity mechanism targeted the old admission guard and would have passed vacuously, and its fixture's census is now asserted outright.🤖 Generated with Claude Code