Skip to content

Commit 8887ab0

Browse files
clay-goodclaude
andcommitted
fix(extract): don't flag the singular of a defined y→ies plural term
STRUCT-006's undefined-term scan skips a singular phrase whose plural is a defined term, but the check only tried the bare "+s"/"+es" forms. For a term whose final word takes a "y"→"ies" plural — "Licensed Facilities" defined, "Licensed Facility" used — those forms produce "licensed facilitys" / "licensed facilityes", so the singular was not recognized and leaked in as a used-but-undefined term. (The regular "+s" case, "Licensed Products" → "Licensed Product", already worked.) Also test regularPlural(phrase), which produces the "ies" form; the bare "+s"/"+es" forms are kept for a final word already ending in "s" ("Asset Class" → "Asset Classes"), which regularPlural intentionally skips. Zero golden churn: no fixture in the 327-doc corpus uses the singular of a y→ies plural-defined term. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 3cb0121 commit 8887ab0

3 files changed

Lines changed: 29 additions & 4 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
**Vaulytica is the second pair of eyes you can cite.**
66

7-
`1,111 deterministic rules` · `20 cross-document checks` · `5 pre-disclosure checks` · `3 execution-readiness reconciliations` · `5 derived-deadline families` · `16 document sub-domains` · `88 state-law overlays (non-compete · security deposit · usury · will formalities)` · `10 export formats` · `0 servers` · `0 AI` · `5,860 passing tests` · `v9.41.0` · `MIT`
7+
`1,111 deterministic rules` · `20 cross-document checks` · `5 pre-disclosure checks` · `3 execution-readiness reconciliations` · `5 derived-deadline families` · `16 document sub-domains` · `88 state-law overlays (non-compete · security deposit · usury · will formalities)` · `10 export formats` · `0 servers` · `0 AI` · `5,861 passing tests` · `v9.41.0` · `MIT`
88

99
![Vaulytica landing page — "Drop legal docs. Get a report. Nothing leaves your browser."](docs/images/hero.png)
1010

@@ -1332,7 +1332,7 @@ npm run dev # open the printed URL
13321332
npm run build # static site → dist/
13331333
npm run typecheck # tsc --noEmit
13341334
npm run lint # eslint
1335-
npm run test # vitest — 5,860 tests, ~35s
1335+
npm run test # vitest — 5,861 tests, ~35s
13361336
npm run coverage # vitest + V8 coverage, enforces the regression floor
13371337
npm run accuracy # v5 Ground Truth harness → tools/accuracy/SCOREBOARD.md
13381338
npm run mutation # Stryker mutation score (scoped to extractors; slow, off the per-push path)

src/extract/definitions.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,21 @@ describe("plural compounds of defined terms", () => {
994994
);
995995
expect(map.undefined_capitalized.map((u) => u.term)).not.toContain("Licensed Products");
996996
});
997+
998+
it("does not flag the singular of a defined 'y'→'ies' plural term", () => {
999+
// "Licensed Facility" is the singular of the defined "Licensed
1000+
// Facilities"; the bare "+s"/"+es" check produced "licensed facilitys"/
1001+
// "licensed facilityes" and missed it, leaking a STRUCT-006 false positive.
1002+
const map = extractDefinitions(
1003+
buildTree([
1004+
"Agreement",
1005+
'"Licensed Facilities" means the plants operated by Seller.',
1006+
"Each Licensed Facility shall be maintained.",
1007+
"A Licensed Facility may be inspected without notice.",
1008+
]),
1009+
);
1010+
expect(map.undefined_capitalized.map((u) => u.term)).not.toContain("Licensed Facility");
1011+
});
9971012
});
9981013

9991014
describe("statute suffixes and office titles", () => {

src/extract/definitions.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,8 +709,18 @@ export function extractDefinitions(tree: DocumentTree): DefinitionMap {
709709
// A SINGULAR use of a defined PLURAL term — "each Licensed Patent" where
710710
// "Licensed Patents" is the defined term — is that term's use, not a new
711711
// undefined one. (The mirror, a plural use of a defined singular, is
712-
// handled by isCompoundOfDefined further down.)
713-
if (definedNames.has(`${phraseLower}s`) || definedNames.has(`${phraseLower}es`)) continue;
712+
// handled by isCompoundOfDefined further down.) The bare "+s"/"+es"
713+
// forms are kept for a final word already ending in "s" ("Asset Class" →
714+
// "Asset Classes"); regularPlural adds the "y" → "ies" case ("Licensed
715+
// Facility" → "Licensed Facilities") that neither bare form produces.
716+
const definedAsPlural = regularPlural(phraseLower);
717+
if (
718+
definedNames.has(`${phraseLower}s`) ||
719+
definedNames.has(`${phraseLower}es`) ||
720+
(definedAsPlural !== null && definedNames.has(definedAsPlural))
721+
) {
722+
continue;
723+
}
714724
// TITLE_CASE_PHRASE cannot cross an all-caps word, so a candidate is
715725
// often a truncation of a longer defined term — "Contractor Background"
716726
// cut from the defined "Contractor Background IP". A word-boundary

0 commit comments

Comments
 (0)