eyecite-ts 0.29.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { E as ShortFormCitation, n as Citation } from "./citation-Dy_8OOzV.mjs";
1
+ import { D as ShortFormCitation, n as Citation } from "./citation-Dx71V5wD.mjs";
2
2
 
3
3
  //#region src/footnotes/types.d.ts
4
4
  /**
@@ -133,4 +133,4 @@ type ResolvedCitation<C extends Citation = Citation> = C extends ShortFormCitati
133
133
  };
134
134
  //#endregion
135
135
  export { FootnoteMap as a, ScopeStrategy as i, ResolutionResult as n, FootnoteZone as o, ResolvedCitation as r, ResolutionOptions as t };
136
- //# sourceMappingURL=types-BS607EPW.d.mts.map
136
+ //# sourceMappingURL=types-BKg6EJIz.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-BS607EPW.d.mts","names":[],"sources":["../src/footnotes/types.ts","../src/resolve/types.ts"],"mappings":";;;;;;AAIA;UAAiB,YAAA;;EAEf,KAAA;;EAEA,GAAA;;EAEA,cAAA;AAAA;AAMF;;;AAAA,KAAY,WAAA,GAAc,YAAA;;;;;;;KCFd,aAAA;;;;UAKK,iBAAA;;;;AALjB;;;;EAaE,aAAA,GAAgB,aAAA;EARlB;;;;EAcE,oBAAA;;;;;EAMA,wBAAA,GAA2B,MAAA;;;;;EAM3B,kBAAA;;;;;;EAOA,mBAAA;EA6BF;;;;;;;;EAnBE,iBAAA;;;AAqEF;;EA/DE,gBAAA;;;;;;EAOA,WAAA,GAAc,WAAA;AAAA;;;;UAMC,gBAAA;;;;;EAKf,UAAA;;;;;;;;;;;;;;;;;;EAmBA,eAAA;;;;EAKA,aAAA;;;;EAKA,QAAA;;;;;EAMA,UAAA;AAAA;;;;;;;;KAUU,gBAAA,WAA2B,QAAA,GAAW,QAAA,IAAY,CAAA,SAAU,iBAAA,GACpE,CAAA;EAAM,UAAA,EAAY,gBAAA;AAAA,IAClB,CAAA;EAAM,UAAA;AAAA"}
1
+ {"version":3,"file":"types-BKg6EJIz.d.mts","names":[],"sources":["../src/footnotes/types.ts","../src/resolve/types.ts"],"mappings":";;;;;;AAIA;UAAiB,YAAA;;EAEf,KAAA;;EAEA,GAAA;;EAEA,cAAA;AAAA;AAMF;;;AAAA,KAAY,WAAA,GAAc,YAAA;;;;;;;KCFd,aAAA;;;;UAKK,iBAAA;;;;AALjB;;;;EAaE,aAAA,GAAgB,aAAA;EARlB;;;;EAcE,oBAAA;;;;;EAMA,wBAAA,GAA2B,MAAA;;;;;EAM3B,kBAAA;;;;;;EAOA,mBAAA;EA6BF;;;;;;;;EAnBE,iBAAA;;;AAqEF;;EA/DE,gBAAA;;;;;;EAOA,WAAA,GAAc,WAAA;AAAA;;;;UAMC,gBAAA;;;;;EAKf,UAAA;;;;;;;;;;;;;;;;;;EAmBA,eAAA;;;;EAKA,aAAA;;;;EAKA,QAAA;;;;;EAMA,UAAA;AAAA;;;;;;;;KAUU,gBAAA,WAA2B,QAAA,GAAW,QAAA,IAAY,CAAA,SAAU,iBAAA,GACpE,CAAA;EAAM,UAAA,EAAY,gBAAA;AAAA,IAClB,CAAA;EAAM,UAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { E as ShortFormCitation, n as Citation } from "./citation-CsWsvkxB.cjs";
1
+ import { D as ShortFormCitation, n as Citation } from "./citation-t5DPIzyE.cjs";
2
2
 
3
3
  //#region src/footnotes/types.d.ts
4
4
  /**
@@ -133,4 +133,4 @@ type ResolvedCitation<C extends Citation = Citation> = C extends ShortFormCitati
133
133
  };
134
134
  //#endregion
135
135
  export { FootnoteMap as a, ScopeStrategy as i, ResolutionResult as n, FootnoteZone as o, ResolvedCitation as r, ResolutionOptions as t };
136
- //# sourceMappingURL=types-DXnfQIbk.d.cts.map
136
+ //# sourceMappingURL=types-D961u5ik.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-DXnfQIbk.d.cts","names":[],"sources":["../src/footnotes/types.ts","../src/resolve/types.ts"],"mappings":";;;;;;AAIA;UAAiB,YAAA;;EAEf,KAAA;;EAEA,GAAA;;EAEA,cAAA;AAAA;AAMF;;;AAAA,KAAY,WAAA,GAAc,YAAA;;;;;;;KCFd,aAAA;;;;UAKK,iBAAA;;;;AALjB;;;;EAaE,aAAA,GAAgB,aAAA;EARlB;;;;EAcE,oBAAA;;;;;EAMA,wBAAA,GAA2B,MAAA;;;;;EAM3B,kBAAA;;;;;;EAOA,mBAAA;EA6BF;;;;;;;;EAnBE,iBAAA;;;AAqEF;;EA/DE,gBAAA;;;;;;EAOA,WAAA,GAAc,WAAA;AAAA;;;;UAMC,gBAAA;;;;;EAKf,UAAA;;;;;;;;;;;;;;;;;;EAmBA,eAAA;;;;EAKA,aAAA;;;;EAKA,QAAA;;;;;EAMA,UAAA;AAAA;;;;;;;;KAUU,gBAAA,WAA2B,QAAA,GAAW,QAAA,IAAY,CAAA,SAAU,iBAAA,GACpE,CAAA;EAAM,UAAA,EAAY,gBAAA;AAAA,IAClB,CAAA;EAAM,UAAA;AAAA"}
1
+ {"version":3,"file":"types-D961u5ik.d.cts","names":[],"sources":["../src/footnotes/types.ts","../src/resolve/types.ts"],"mappings":";;;;;;AAIA;UAAiB,YAAA;;EAEf,KAAA;;EAEA,GAAA;;EAEA,cAAA;AAAA;AAMF;;;AAAA,KAAY,WAAA,GAAc,YAAA;;;;;;;KCFd,aAAA;;;;UAKK,iBAAA;;;;AALjB;;;;EAaE,aAAA,GAAgB,aAAA;EARlB;;;;EAcE,oBAAA;;;;;EAMA,wBAAA,GAA2B,MAAA;;;;;EAM3B,kBAAA;;;;;;EAOA,mBAAA;EA6BF;;;;;;;;EAnBE,iBAAA;;;AAqEF;;EA/DE,gBAAA;;;;;;EAOA,WAAA,GAAc,WAAA;AAAA;;;;UAMC,gBAAA;;;;;EAKf,UAAA;;;;;;;;;;;;;;;;;;EAmBA,eAAA;;;;EAKA,aAAA;;;;EAKA,QAAA;;;;;EAMA,UAAA;AAAA;;;;;;;;KAUU,gBAAA,WAA2B,QAAA,GAAW,QAAA,IAAY,CAAA,SAAU,iBAAA,GACpE,CAAA;EAAM,UAAA,EAAY,gBAAA;AAAA,IAClB,CAAA;EAAM,UAAA;AAAA"}
@@ -3,5 +3,5 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e,
3
3
 
4
4
  `,t.start);a=n===-1?0:n+2;let r=e.indexOf(`
5
5
 
6
- `,t.end);o=r===-1?e.length:r}else a=d(e,t.start),o=f(e,t.end);let s=e.slice(a,o),c=s.trim(),l=a+(s.length-s.trimStart().length),u=o-(s.length-s.trimEnd().length);if(i&&c.length>i){let e=c.slice(0,i);return{text:e,span:{start:l,end:l+e.length}}}return{text:c,span:{start:l,end:u}}}exports.getSurroundingContext=p,exports.groupByCase=c,exports.toBluebook=i,exports.toReporterKey=t,exports.toReporterKeys=n;
6
+ `,t.end);o=r===-1?e.length:r}else a=d(e,t.start),o=f(e,t.end);let s=e.slice(a,o),c=s.trim(),l=a+(s.length-s.trimStart().length),u=o-(s.length-s.trimEnd().length);if(i&&c.length>i){let e=c.slice(0,i);return{text:e,span:{start:l,end:l+e.length}}}return{text:c,span:{start:l,end:u}}}function m(e,t=``,n=``){let r=`${e}\u0000${t}\u0000${n}`.normalize(`NFC`),i=14695981039346656037n;for(let e=0;e<r.length;e++)i^=BigInt(r.charCodeAt(e)),i=BigInt.asUintN(64,i*1099511628211n);return i.toString(16).padStart(16,`0`)}function h(e){return e!==void 0&&/\w/.test(e)}function g(e,t,n){if(!n)return!1;let r=!h(n[0])||t===0||!h(e[t-1]),i=t+n.length,a=!h(n[n.length-1])||i>=e.length||!h(e[i]);return r&&a}function _(e,t){let n=[];if(!t)return n;let r=0;for(;;){let i=e.indexOf(t,r);if(i===-1)break;g(e,i,t)&&n.push(i),r=i+Math.max(1,t.length)}return n}function v(e,t,n={}){let r=n.space??`original`,i=n.contextLength??32,a=e.span,o=!1,s=`fullSpan`in e?e.fullSpan:void 0;n.fullSpan===!0&&s!==void 0&&(a=s,o=!0);let c=r===`clean`?a.cleanStart:a.originalStart,l=r===`clean`?a.cleanEnd:a.originalEnd;if(c<0||l>t.length||c>l)throw Error(`toDurableLocator: span [${c}, ${l}) is out of range for source of length ${t.length} — wrong source text or space?`);let u=t.slice(c,l);if(u.length===0)throw Error(`toDurableLocator: empty exact quote — nothing to anchor`);if(r===`original`&&!o&&u!==e.matchedText)throw Error(`toDurableLocator: sliced text "${u}" does not equal citation.matchedText "${e.matchedText}" — wrong source text or space?`);let d=p(t,{start:c,end:l}),f=d.span.start,h=d.span.end,g=t.slice(Math.max(f,c-i),c),v=t.slice(l,Math.min(h,l+i)),y=_(t,u).indexOf(c);return{v:1,space:r,quote:{exact:u,...g.length>0?{prefix:g}:{},...v.length>0?{suffix:v}:{}},position:{start:c,end:l},...y>=0?{occurrence:y}:{},contentHash:m(u,g,v)}}function y(e,t,n={}){return e.map(e=>v(e,t,n))}exports.getSurroundingContext=p,exports.groupByCase=c,exports.toBluebook=i,exports.toDurableLocator=v,exports.toDurableLocators=y,exports.toReporterKey=t,exports.toReporterKeys=n;
7
7
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/utils/reporterKey.ts","../../src/utils/bluebook.ts","../../src/utils/groupByCase.ts","../../src/utils/context.ts"],"sourcesContent":["import type { FullCaseCitation } from \"../types/citation\"\n\n/**\n * Format a volume-reporter-page key from citation fields.\n */\nfunction formatKey(volume: number | string, reporter: string, page: number | undefined): string {\n if (page === undefined) {\n return `${volume} ${reporter}`\n }\n return `${volume} ${reporter} ${page}`\n}\n\n/**\n * Extract the volume-reporter-page lookup key from a case citation.\n *\n * Strips case name, pincite, year, and parenthetical.\n * Uses `normalizedReporter` when available, falls back to `reporter`.\n * Omits the page for blank-page citations.\n *\n * @example\n * ```typescript\n * toReporterKey(citation) // \"550 U.S. 544\"\n * ```\n */\nexport function toReporterKey(citation: FullCaseCitation): string {\n const reporter = citation.normalizedReporter ?? citation.reporter\n const page = citation.hasBlankPage ? undefined : citation.page\n return formatKey(citation.volume, reporter, page)\n}\n\n/**\n * Extract all volume-reporter-page lookup keys from a case citation,\n * including parallel citations.\n *\n * Returns the primary key first, followed by any parallel citation keys.\n *\n * @example\n * ```typescript\n * toReporterKeys(citation) // [\"410 U.S. 113\", \"93 S. Ct. 705\"]\n * ```\n */\nexport function toReporterKeys(citation: FullCaseCitation): string[] {\n const keys = [toReporterKey(citation)]\n\n if (citation.parallelCitations?.length) {\n for (const p of citation.parallelCitations) {\n keys.push(formatKey(p.volume, p.reporter, p.page))\n }\n }\n\n return keys\n}\n","import type { Citation } from \"../types/citation\"\n\n/** Convert an integer to a Roman numeral (1-27 covers all amendments + articles). */\nfunction toRoman(n: number): string {\n const numerals: Array<[number, string]> = [\n [10, \"X\"],\n [9, \"IX\"],\n [5, \"V\"],\n [4, \"IV\"],\n [1, \"I\"],\n ]\n let result = \"\"\n let remaining = n\n for (const [value, numeral] of numerals) {\n while (remaining >= value) {\n result += numeral\n remaining -= value\n }\n }\n return result\n}\n\n/**\n * Reconstruct a canonical Bluebook-style citation string from structured fields.\n *\n * Works across all 11 citation types via the discriminated union.\n * Best-effort: uses whatever fields are available on the citation object.\n *\n * @example\n * ```typescript\n * toBluebook(caseCitation) // \"Bell Atl. Corp. v. Twombly, 550 U.S. 544 (2007)\"\n * toBluebook(statuteCite) // \"42 U.S.C. § 1983\"\n * toBluebook(idCite) // \"Id. at 570\"\n * ```\n */\nexport function toBluebook(citation: Citation): string {\n switch (citation.type) {\n case \"case\": {\n const reporter = citation.normalizedReporter ?? citation.reporter\n let pageStr: string\n if (citation.hasBlankPage) {\n pageStr = \" ___\"\n } else if (citation.page !== undefined) {\n pageStr = ` ${citation.page}`\n } else {\n pageStr = \"\"\n }\n\n const core = `${citation.volume} ${reporter}${pageStr}`\n const pincite = citation.pincite !== undefined ? `, ${citation.pincite}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n const caseName = citation.caseName ? `${citation.caseName}, ` : \"\"\n\n return `${caseName}${core}${pincite}${year}`\n }\n\n case \"statute\":\n case \"regulation\": {\n // Statutes and regulations share the same Bluebook rendering shape\n // (title + code + chapter? + \\u00A7 section + subsection + et seq.).\n // Regulations only differ in `type` discriminator; rendering is\n // identical. #637\n const title = citation.title !== undefined ? `${citation.title} ` : \"\"\n // section may be absent (e.g. Massachusetts chapter-only like\n // `G.L. c. 93A` \\u2014 #569). Render the chapter form when present and\n // omit the trailing `\\u00A7 <section>` if there is no section.\n const code = citation.code ? `${citation.code} ` : \"\"\n const chapter = citation.chapter ? `c. ${citation.chapter}` : \"\"\n const subsection = citation.subsection ?? \"\"\n const etSeq = citation.hasEtSeq ? \" et seq.\" : \"\"\n if (citation.section !== undefined && citation.section !== \"\") {\n const section = `\\u00A7 ${citation.section}`\n const sep = chapter ? \", \" : \"\"\n return `${title}${code}${chapter}${sep}${section}${subsection}${etSeq}`\n }\n // chapter-only OR section absent: emit `code c. chapter` (Mass)\n // or `code \\u00A7` placeholder when neither field is present.\n // (`code` may be absent when an untagged bare-section cite was emitted\n // \\u2014 #531/#565 \\u2014 handled by the `citation.code ? ... : \"\"` ternary above.)\n if (chapter) return `${title}${code}${chapter}${etSeq}`\n return `${title}${code}\\u00A7${etSeq}`\n }\n\n case \"constitutional\": {\n const jurisdiction = citation.jurisdiction === \"US\" ? \"U.S.\" : (citation.jurisdiction ?? \"\")\n const prefix = `${jurisdiction} Const.`\n\n let body = \"\"\n if (citation.article !== undefined) {\n body += ` art. ${toRoman(citation.article)}`\n }\n if (citation.amendment !== undefined) {\n body += ` amend. ${toRoman(citation.amendment)}`\n }\n if (citation.section !== undefined) {\n body += `, \\u00A7 ${citation.section}`\n }\n if (citation.clause !== undefined) {\n body += `, cl. ${citation.clause}`\n }\n // #789 — append the post-reform location for `former … (now …)` cites.\n if (citation.currentLocation) {\n const now = citation.currentLocation\n let nowBody = \"\"\n if (now.article !== undefined) nowBody += `art. ${toRoman(now.article)}`\n if (now.amendment !== undefined) nowBody += `amend. ${toRoman(now.amendment)}`\n if (now.section !== undefined) nowBody += `, § ${now.section}`\n if (now.clause !== undefined) nowBody += `, cl. ${now.clause}`\n if (nowBody) body += ` (now ${nowBody})`\n }\n return `${prefix}${body}`\n }\n\n case \"journal\": {\n const vol = citation.volume !== undefined ? `${citation.volume} ` : \"\"\n const page = citation.page !== undefined ? ` ${citation.page}` : \"\"\n const pincite = citation.pincite !== undefined ? `, ${citation.pincite}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${vol}${citation.abbreviation}${page}${pincite}${year}`\n }\n\n case \"docket\": {\n const caseName = citation.caseName ? `${citation.caseName}, ` : \"\"\n const courtAndYear =\n citation.court && citation.year\n ? ` (${citation.court} ${citation.year})`\n : citation.court\n ? ` (${citation.court})`\n : citation.year\n ? ` (${citation.year})`\n : \"\"\n return `${caseName}No. ${citation.docketNumber}${courtAndYear}`\n }\n\n case \"neutral\":\n return `${citation.year} ${citation.court} ${citation.documentNumber}`\n\n case \"publicLaw\":\n return `Pub. L. No. ${citation.congress}-${citation.lawNumber}`\n\n case \"federalRegister\": {\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${citation.volume} Fed. Reg. ${citation.page}${year}`\n }\n\n case \"statutesAtLarge\":\n return `${citation.volume} Stat. ${citation.page}`\n\n case \"sessionLaw\": {\n // California leads with the compilation (\"Stats. 1992, ch. 726\"); Nevada\n // leads with the year (\"2003 Nev. Stat., ch. 427\").\n const head =\n citation.jurisdiction === \"NV\"\n ? `${citation.year} ${citation.code}`\n : `${citation.code} ${citation.year}`\n let sectionText = \"\"\n if (citation.sections) {\n sectionText = `, §§ ${citation.sections.join(\", \")}`\n } else if (citation.sectionRange) {\n sectionText = `, §§ ${citation.sectionRange.start}-${citation.sectionRange.end}`\n } else if (citation.section) {\n sectionText = `, § ${citation.section}`\n }\n let pageText = \"\"\n const pageLabel = citation.jurisdiction === \"NV\" ? \"at \" : \"pp. \"\n if (citation.pageRange) {\n pageText = `, ${pageLabel}${citation.pageRange.start}-${citation.pageRange.end}`\n } else if (citation.page) {\n pageText = `, ${citation.jurisdiction === \"NV\" ? \"at \" : \"p. \"}${citation.page}`\n }\n return `${head}, ch. ${citation.chapter}${sectionText}${pageText}`\n }\n\n case \"treaty\": {\n // Volume-series-page (1155 U.N.T.S. 331) vs \"No.\"-style series (T.I.A.S. No. 1502).\n if (citation.volume !== undefined && citation.page !== undefined) {\n return `${citation.volume} ${citation.series} ${citation.page}`\n }\n return `${citation.series} No. ${citation.seriesNumber}`\n }\n\n case \"legislativeMaterial\": {\n if (citation.kind === \"congressionalRecord\") {\n return `${citation.volume} Cong. Rec. ${citation.page}`\n }\n const abbrev = citation.chamber === \"House\" ? \"H.R.\" : \"S.\"\n const cong = citation.congress !== undefined ? `, ${citation.congress}th Cong.` : \"\"\n const sess = citation.session ? `, ${citation.session} Sess.` : \"\"\n const page = citation.page !== undefined ? `, p. ${citation.page}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${abbrev} Rep. No. ${citation.reportNumber}${cong}${sess}${page}${year}`\n }\n\n case \"localOrdinance\":\n return `${citation.code} § ${citation.section}`\n\n case \"canon\":\n return `Canon ${citation.canon}${citation.subsection ?? \"\"}`\n\n case \"id\":\n return citation.pincite !== undefined ? `Id. at ${citation.pincite}` : \"Id.\"\n\n case \"supra\":\n return citation.pincite !== undefined\n ? `${citation.partyName}, supra, at ${citation.pincite}`\n : `${citation.partyName}, supra`\n\n case \"shortFormCase\": {\n const reporter = citation.reporter\n if (citation.pincite !== undefined) {\n return `${citation.volume} ${reporter} at ${citation.pincite}`\n }\n const page = citation.page !== undefined ? ` ${citation.page}` : \"\"\n return `${citation.volume} ${reporter}${page}`\n }\n\n case \"federalRule\": {\n // Bluebook canonical abbreviated form.\n const ruleSetAbbrev: Record<typeof citation.ruleSet, string> = {\n civil: \"Civ. P.\",\n criminal: \"Crim. P.\",\n evidence: \"Evid.\",\n appellate: \"App. P.\",\n bankruptcy: \"Bankr. P.\",\n }\n const subsection = citation.subsection ?? \"\"\n return `Fed. R. ${ruleSetAbbrev[citation.ruleSet]} ${citation.rule}${subsection}`\n }\n\n case \"stateRule\": {\n // State court rules render as the jurisdiction-rule-set anchor used\n // in the input. The extractor canonicalizes to a single\n // `<jurisdiction> R. <ruleSet>. <rule>(<subsection>)?` form. #636\n const ruleSetAbbrev: Record<typeof citation.ruleSet, string> = {\n civil: \"Civ. P.\",\n criminal: \"Crim. P.\",\n evidence: \"Evid.\",\n appellate: \"App. P.\",\n bankruptcy: \"Bankr. P.\",\n other: \"\",\n }\n const subsection = citation.subsection ?? \"\"\n const setPart = ruleSetAbbrev[citation.ruleSet]\n const tail = setPart ? ` R. ${setPart}` : \"\"\n return `${citation.jurisdiction}${tail} ${citation.rule}${subsection}`\n }\n\n case \"restatement\": {\n const subsection = citation.subsection ?? \"\"\n return `Restatement (${citation.edition}) of ${citation.subject} § ${citation.section}${subsection}`\n }\n\n case \"treatise\": {\n const section = `§ ${citation.section}`\n return `${citation.volume} ${citation.title} ${section}`\n }\n\n case \"annotation\": {\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${citation.volume} ${citation.series} ${citation.page}${year}`\n }\n }\n}\n","import type { Citation, FullCaseCitation } from \"../types/citation\"\nimport type { ResolvedCitation, ResolutionResult } from \"../resolve/types\"\nimport type { CaseGroup } from \"./types\"\nimport { toReporterKeys } from \"./reporterKey\"\n\n/**\n * Build a lookup key for a full case citation: \"volume-reporter-page\".\n * Used to group duplicate full citations that lack a parallel groupId.\n */\nfunction citeKey(c: FullCaseCitation): string {\n return `${c.volume}-${c.reporter}-${c.page ?? \"blank\"}`\n}\n\n/** Type guard: narrow a Citation to FullCaseCitation after checking type === \"case\". */\nfunction isFullCase(cite: Citation): cite is FullCaseCitation {\n return cite.type === \"case\"\n}\n\n/** Extract resolution from a resolved short-form citation. */\nfunction getResolution(cite: ResolvedCitation): ResolutionResult | undefined {\n return (cite as ResolvedCitation & { resolution?: ResolutionResult }).resolution\n}\n\n/**\n * Group resolved citations by underlying case.\n *\n * Composes parallel linking, resolution, and volume/reporter/page identity\n * into `CaseGroup` objects. Non-case citations are ignored. Unresolved\n * short-form citations are excluded.\n *\n * @example\n * ```typescript\n * const citations = extractCitations(text)\n * const resolved = resolveCitations(citations, text)\n * const groups = groupByCase(resolved)\n * ```\n */\nexport function groupByCase(citations: ResolvedCitation[]): CaseGroup[] {\n // Map from citation index -> group index (for short-form resolution lookup)\n const indexToGroup = new Map<number, number>()\n // Map from groupId or citeKey -> group index (for dedup)\n const keyToGroup = new Map<string, number>()\n const groups: CaseGroup[] = []\n\n // First pass: assign full case citations to groups\n for (let i = 0; i < citations.length; i++) {\n const cite = citations[i]\n if (!isFullCase(cite)) continue\n\n // Check if this citation belongs to an existing group\n const gid = cite.groupId\n const key = citeKey(cite)\n const existingIdx = (gid ? keyToGroup.get(gid) : undefined) ?? keyToGroup.get(key)\n\n if (existingIdx !== undefined) {\n // Add to existing group\n groups[existingIdx].mentions.push(cite)\n indexToGroup.set(i, existingIdx)\n } else {\n // Create new group\n const groupIdx = groups.length\n const group: CaseGroup = {\n primaryCitation: cite,\n mentions: [cite],\n parallelCitations: toReporterKeys(cite),\n }\n groups.push(group)\n indexToGroup.set(i, groupIdx)\n keyToGroup.set(key, groupIdx)\n if (gid) {\n keyToGroup.set(gid, groupIdx)\n }\n }\n }\n\n // Second pass: assign short-form citations to their resolved group\n for (let i = 0; i < citations.length; i++) {\n const cite = citations[i]\n if (cite.type !== \"id\" && cite.type !== \"supra\" && cite.type !== \"shortFormCase\") continue\n\n const resolution = getResolution(cite)\n if (resolution?.resolvedTo === undefined) continue\n\n const groupIdx = indexToGroup.get(resolution.resolvedTo)\n if (groupIdx === undefined) continue\n\n groups[groupIdx].mentions.push(cite)\n indexToGroup.set(i, groupIdx)\n }\n\n // Sort mentions within each group by document position\n for (const group of groups) {\n group.mentions.sort((a, b) => a.span.originalStart - b.span.originalStart)\n }\n\n return groups\n}\n","import type { ContextOptions, SurroundingContext } from \"./types\"\n\n/**\n * Legal abbreviations that contain periods but are NOT sentence boundaries.\n * Kept as a static set in this file — does NOT import from src/data/\n * to preserve tree-shaking of the utils entry point.\n */\nconst LEGAL_ABBREVIATIONS = new Set([\n // Court and case abbreviations\n \"v\",\n \"vs\",\n // Reporter abbreviations (common ones)\n \"U.S\",\n \"S.Ct\",\n \"S. Ct\",\n \"L.Ed\",\n \"L. Ed\",\n \"F\",\n \"F.2d\",\n \"F.3d\",\n \"F.4th\",\n \"F.Supp\",\n \"F. Supp\",\n \"A.2d\",\n \"A.3d\",\n \"N.E\",\n \"N.E.2d\",\n \"N.W\",\n \"N.W.2d\",\n \"S.E\",\n \"S.E.2d\",\n \"S.W\",\n \"S.W.2d\",\n \"S.W.3d\",\n \"So\",\n \"So.2d\",\n \"So.3d\",\n \"P\",\n \"P.2d\",\n \"P.3d\",\n // Titles and procedural terms\n \"No\",\n \"Nos\",\n \"Inc\",\n \"Corp\",\n \"Ltd\",\n \"Co\",\n \"Ass'n\",\n \"Dept\",\n \"Dist\",\n \"Cir\",\n \"App\",\n \"Supp\",\n \"Rev\",\n \"Stat\",\n \"Const\",\n // General legal abbreviations\n \"Mr\",\n \"Mrs\",\n \"Ms\",\n \"Dr\",\n \"Jr\",\n \"Sr\",\n \"St\",\n \"Ct\",\n \"Atl\",\n \"Cal\",\n \"Fla\",\n \"Ill\",\n \"Tex\",\n \"Pa\",\n \"Md\",\n \"Va\",\n \"Wis\",\n \"Minn\",\n \"Mich\",\n \"Mass\",\n \"Conn\",\n \"Colo\",\n \"Ariz\",\n \"Ark\",\n \"Ga\",\n \"La\",\n \"Ind\",\n \"Kan\",\n \"Ky\",\n \"Miss\",\n \"Mo\",\n \"Neb\",\n \"Nev\",\n \"Okla\",\n \"Or\",\n \"Tenn\",\n \"Vt\",\n \"Wash\",\n \"Wyo\",\n \"Del\",\n \"Haw\",\n \"Ida\",\n \"Me\",\n \"Mont\",\n \"R.I\",\n \"S.C\",\n \"S.D\",\n \"N.C\",\n \"N.D\",\n \"N.J\",\n \"N.M\",\n \"N.Y\",\n \"W.Va\",\n // Federal abbreviations\n \"U.S.C\",\n \"C.F.R\",\n \"Fed\",\n \"Reg\",\n \"Pub\",\n \"Amend\",\n \"Sec\",\n \"Art\",\n \"Cl\",\n \"Ch\",\n \"Pt\",\n \"Vol\",\n \"Ed\",\n \"Harv\",\n \"Yale\",\n \"Stan\",\n \"Colum\",\n \"Geo\",\n])\n\n/**\n * Check if a period at the given position is likely an abbreviation,\n * not a sentence boundary.\n */\nfunction isAbbreviationPeriod(text: string, dotIndex: number): boolean {\n // Look backwards from the dot to find the word\n let wordStart = dotIndex\n while (wordStart > 0 && text[wordStart - 1] !== \" \" && text[wordStart - 1] !== \"\\n\") {\n wordStart--\n }\n\n const word = text.slice(wordStart, dotIndex)\n\n // Single letter followed by period (e.g., \"U.\", \"S.\", \"F.\")\n // Only treat as abbreviation if part of a dotted sequence:\n // - preceded by a period (like the \"S\" in \"U.S.\")\n // - followed by a letter/digit (like the \"F\" in \"F.2d\")\n if (word.length === 1 && /[A-Z]/.test(word)) {\n const charAfterDot = dotIndex + 1 < text.length ? text[dotIndex + 1] : \"\"\n const charBeforeWord = wordStart > 0 ? text[wordStart - 1] : \"\"\n if (charBeforeWord === \".\" || /[A-Za-z0-9]/.test(charAfterDot)) return true\n }\n\n // Check multi-character abbreviations (strip any trailing dots for lookup)\n const stripped = word.replace(/\\.$/g, \"\")\n if (LEGAL_ABBREVIATIONS.has(stripped)) return true\n\n // Check if the word itself (with internal dots) is known: \"U.S\", \"F.2d\", etc.\n if (LEGAL_ABBREVIATIONS.has(word)) return true\n\n // Number followed by period (ordinals like \"1.\" in list context — not sentence end if no space+uppercase follows)\n // This is handled by the caller's space+uppercase check\n\n return false\n}\n\n/**\n * Find the start of the sentence containing the given position.\n */\nfunction findSentenceStart(text: string, pos: number): number {\n for (let i = pos - 1; i >= 0; i--) {\n const ch = text[i]\n if (ch === \".\" || ch === \"?\" || ch === \"!\") {\n if (ch === \".\" && isAbbreviationPeriod(text, i)) continue\n\n // Check if followed by whitespace (the char after this terminator)\n const next = i + 1\n if (next < text.length && /\\s/.test(text[next])) {\n // Skip whitespace to find the start of the next sentence\n let start = next\n while (start < pos && /\\s/.test(text[start])) start++\n return start\n }\n }\n }\n return 0\n}\n\n/**\n * Find the end of the sentence containing the given position.\n */\nfunction findSentenceEnd(text: string, pos: number): number {\n for (let i = pos; i < text.length; i++) {\n const ch = text[i]\n if (ch === \".\" || ch === \"?\" || ch === \"!\") {\n if (ch === \".\" && isAbbreviationPeriod(text, i)) continue\n return i + 1\n }\n }\n return text.length\n}\n\n/**\n * Find the enclosing sentence or paragraph around a citation span.\n *\n * Legal-text-aware: periods in reporter abbreviations, court names,\n * and procedural terms (Corp., U.S., F.3d, No., v.) are not treated\n * as sentence boundaries.\n *\n * @example\n * ```typescript\n * const ctx = getSurroundingContext(text, { start: 33, end: 52 })\n * // ctx.text: \"In Smith v. Doe, 500 F.2d 123 (2020), the Court held X.\"\n * // ctx.span: { start: 16, end: 71 }\n * ```\n */\nexport function getSurroundingContext(\n text: string,\n span: { start: number; end: number },\n options?: ContextOptions,\n): SurroundingContext {\n const type = options?.type ?? \"sentence\"\n const maxLength = options?.maxLength\n\n let start: number\n let end: number\n\n if (type === \"paragraph\") {\n // Find paragraph boundaries (double newline)\n const beforeSpan = text.lastIndexOf(\"\\n\\n\", span.start)\n start = beforeSpan === -1 ? 0 : beforeSpan + 2\n const afterSpan = text.indexOf(\"\\n\\n\", span.end)\n end = afterSpan === -1 ? text.length : afterSpan\n } else {\n start = findSentenceStart(text, span.start)\n end = findSentenceEnd(text, span.end)\n }\n\n const raw = text.slice(start, end)\n const resultText = raw.trim()\n const trimmedStart = start + (raw.length - raw.trimStart().length)\n const trimmedEnd = end - (raw.length - raw.trimEnd().length)\n\n if (maxLength && resultText.length > maxLength) {\n const truncated = resultText.slice(0, maxLength)\n return {\n text: truncated,\n span: { start: trimmedStart, end: trimmedStart + truncated.length },\n }\n }\n\n return {\n text: resultText,\n span: { start: trimmedStart, end: trimmedEnd },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/utils/reporterKey.ts","../../src/utils/bluebook.ts","../../src/utils/groupByCase.ts","../../src/utils/context.ts","../../src/utils/contentHash.ts","../../src/utils/tokenBounded.ts","../../src/utils/durableLocator.ts"],"sourcesContent":["import type { FullCaseCitation } from \"../types/citation\"\n\n/**\n * Format a volume-reporter-page key from citation fields.\n */\nfunction formatKey(volume: number | string, reporter: string, page: number | undefined): string {\n if (page === undefined) {\n return `${volume} ${reporter}`\n }\n return `${volume} ${reporter} ${page}`\n}\n\n/**\n * Extract the volume-reporter-page lookup key from a case citation.\n *\n * Strips case name, pincite, year, and parenthetical.\n * Uses `normalizedReporter` when available, falls back to `reporter`.\n * Omits the page for blank-page citations.\n *\n * @example\n * ```typescript\n * toReporterKey(citation) // \"550 U.S. 544\"\n * ```\n */\nexport function toReporterKey(citation: FullCaseCitation): string {\n const reporter = citation.normalizedReporter ?? citation.reporter\n const page = citation.hasBlankPage ? undefined : citation.page\n return formatKey(citation.volume, reporter, page)\n}\n\n/**\n * Extract all volume-reporter-page lookup keys from a case citation,\n * including parallel citations.\n *\n * Returns the primary key first, followed by any parallel citation keys.\n *\n * @example\n * ```typescript\n * toReporterKeys(citation) // [\"410 U.S. 113\", \"93 S. Ct. 705\"]\n * ```\n */\nexport function toReporterKeys(citation: FullCaseCitation): string[] {\n const keys = [toReporterKey(citation)]\n\n if (citation.parallelCitations?.length) {\n for (const p of citation.parallelCitations) {\n keys.push(formatKey(p.volume, p.reporter, p.page))\n }\n }\n\n return keys\n}\n","import type { Citation } from \"../types/citation\"\n\n/** Convert an integer to a Roman numeral (1-27 covers all amendments + articles). */\nfunction toRoman(n: number): string {\n const numerals: Array<[number, string]> = [\n [10, \"X\"],\n [9, \"IX\"],\n [5, \"V\"],\n [4, \"IV\"],\n [1, \"I\"],\n ]\n let result = \"\"\n let remaining = n\n for (const [value, numeral] of numerals) {\n while (remaining >= value) {\n result += numeral\n remaining -= value\n }\n }\n return result\n}\n\n/**\n * Reconstruct a canonical Bluebook-style citation string from structured fields.\n *\n * Works across all 11 citation types via the discriminated union.\n * Best-effort: uses whatever fields are available on the citation object.\n *\n * @example\n * ```typescript\n * toBluebook(caseCitation) // \"Bell Atl. Corp. v. Twombly, 550 U.S. 544 (2007)\"\n * toBluebook(statuteCite) // \"42 U.S.C. § 1983\"\n * toBluebook(idCite) // \"Id. at 570\"\n * ```\n */\nexport function toBluebook(citation: Citation): string {\n switch (citation.type) {\n case \"case\": {\n const reporter = citation.normalizedReporter ?? citation.reporter\n let pageStr: string\n if (citation.hasBlankPage) {\n pageStr = \" ___\"\n } else if (citation.page !== undefined) {\n pageStr = ` ${citation.page}`\n } else {\n pageStr = \"\"\n }\n\n const core = `${citation.volume} ${reporter}${pageStr}`\n const pincite = citation.pincite !== undefined ? `, ${citation.pincite}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n const caseName = citation.caseName ? `${citation.caseName}, ` : \"\"\n\n return `${caseName}${core}${pincite}${year}`\n }\n\n case \"statute\":\n case \"regulation\": {\n // Statutes and regulations share the same Bluebook rendering shape\n // (title + code + chapter? + \\u00A7 section + subsection + et seq.).\n // Regulations only differ in `type` discriminator; rendering is\n // identical. #637\n const title = citation.title !== undefined ? `${citation.title} ` : \"\"\n // section may be absent (e.g. Massachusetts chapter-only like\n // `G.L. c. 93A` \\u2014 #569). Render the chapter form when present and\n // omit the trailing `\\u00A7 <section>` if there is no section.\n const code = citation.code ? `${citation.code} ` : \"\"\n const chapter = citation.chapter ? `c. ${citation.chapter}` : \"\"\n const subsection = citation.subsection ?? \"\"\n const etSeq = citation.hasEtSeq ? \" et seq.\" : \"\"\n if (citation.section !== undefined && citation.section !== \"\") {\n const section = `\\u00A7 ${citation.section}`\n const sep = chapter ? \", \" : \"\"\n return `${title}${code}${chapter}${sep}${section}${subsection}${etSeq}`\n }\n // chapter-only OR section absent: emit `code c. chapter` (Mass)\n // or `code \\u00A7` placeholder when neither field is present.\n // (`code` may be absent when an untagged bare-section cite was emitted\n // \\u2014 #531/#565 \\u2014 handled by the `citation.code ? ... : \"\"` ternary above.)\n if (chapter) return `${title}${code}${chapter}${etSeq}`\n return `${title}${code}\\u00A7${etSeq}`\n }\n\n case \"constitutional\": {\n const jurisdiction = citation.jurisdiction === \"US\" ? \"U.S.\" : (citation.jurisdiction ?? \"\")\n const prefix = `${jurisdiction} Const.`\n\n let body = \"\"\n if (citation.article !== undefined) {\n body += ` art. ${toRoman(citation.article)}`\n }\n if (citation.amendment !== undefined) {\n body += ` amend. ${toRoman(citation.amendment)}`\n }\n if (citation.section !== undefined) {\n body += `, \\u00A7 ${citation.section}`\n }\n if (citation.clause !== undefined) {\n body += `, cl. ${citation.clause}`\n }\n // #789 — append the post-reform location for `former … (now …)` cites.\n if (citation.currentLocation) {\n const now = citation.currentLocation\n let nowBody = \"\"\n if (now.article !== undefined) nowBody += `art. ${toRoman(now.article)}`\n if (now.amendment !== undefined) nowBody += `amend. ${toRoman(now.amendment)}`\n if (now.section !== undefined) nowBody += `, § ${now.section}`\n if (now.clause !== undefined) nowBody += `, cl. ${now.clause}`\n if (nowBody) body += ` (now ${nowBody})`\n }\n return `${prefix}${body}`\n }\n\n case \"journal\": {\n const vol = citation.volume !== undefined ? `${citation.volume} ` : \"\"\n const page = citation.page !== undefined ? ` ${citation.page}` : \"\"\n const pincite = citation.pincite !== undefined ? `, ${citation.pincite}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${vol}${citation.abbreviation}${page}${pincite}${year}`\n }\n\n case \"docket\": {\n const caseName = citation.caseName ? `${citation.caseName}, ` : \"\"\n const courtAndYear =\n citation.court && citation.year\n ? ` (${citation.court} ${citation.year})`\n : citation.court\n ? ` (${citation.court})`\n : citation.year\n ? ` (${citation.year})`\n : \"\"\n return `${caseName}No. ${citation.docketNumber}${courtAndYear}`\n }\n\n case \"neutral\":\n return `${citation.year} ${citation.court} ${citation.documentNumber}`\n\n case \"publicLaw\":\n return `Pub. L. No. ${citation.congress}-${citation.lawNumber}`\n\n case \"federalRegister\": {\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${citation.volume} Fed. Reg. ${citation.page}${year}`\n }\n\n case \"statutesAtLarge\":\n return `${citation.volume} Stat. ${citation.page}`\n\n case \"sessionLaw\": {\n // California leads with the compilation (\"Stats. 1992, ch. 726\"); Nevada\n // leads with the year (\"2003 Nev. Stat., ch. 427\").\n const head =\n citation.jurisdiction === \"NV\"\n ? `${citation.year} ${citation.code}`\n : `${citation.code} ${citation.year}`\n let sectionText = \"\"\n if (citation.sections) {\n sectionText = `, §§ ${citation.sections.join(\", \")}`\n } else if (citation.sectionRange) {\n sectionText = `, §§ ${citation.sectionRange.start}-${citation.sectionRange.end}`\n } else if (citation.section) {\n sectionText = `, § ${citation.section}`\n }\n let pageText = \"\"\n const pageLabel = citation.jurisdiction === \"NV\" ? \"at \" : \"pp. \"\n if (citation.pageRange) {\n pageText = `, ${pageLabel}${citation.pageRange.start}-${citation.pageRange.end}`\n } else if (citation.page) {\n pageText = `, ${citation.jurisdiction === \"NV\" ? \"at \" : \"p. \"}${citation.page}`\n }\n return `${head}, ch. ${citation.chapter}${sectionText}${pageText}`\n }\n\n case \"treaty\": {\n // Volume-series-page (1155 U.N.T.S. 331) vs \"No.\"-style series (T.I.A.S. No. 1502).\n if (citation.volume !== undefined && citation.page !== undefined) {\n return `${citation.volume} ${citation.series} ${citation.page}`\n }\n return `${citation.series} No. ${citation.seriesNumber}`\n }\n\n case \"legislativeMaterial\": {\n if (citation.kind === \"congressionalRecord\") {\n return `${citation.volume} Cong. Rec. ${citation.page}`\n }\n const abbrev = citation.chamber === \"House\" ? \"H.R.\" : \"S.\"\n const cong = citation.congress !== undefined ? `, ${citation.congress}th Cong.` : \"\"\n const sess = citation.session ? `, ${citation.session} Sess.` : \"\"\n const page = citation.page !== undefined ? `, p. ${citation.page}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${abbrev} Rep. No. ${citation.reportNumber}${cong}${sess}${page}${year}`\n }\n\n case \"localOrdinance\":\n return `${citation.code} § ${citation.section}`\n\n case \"canon\":\n return `Canon ${citation.canon}${citation.subsection ?? \"\"}`\n\n case \"id\":\n return citation.pincite !== undefined ? `Id. at ${citation.pincite}` : \"Id.\"\n\n case \"supra\":\n return citation.pincite !== undefined\n ? `${citation.partyName}, supra, at ${citation.pincite}`\n : `${citation.partyName}, supra`\n\n case \"shortFormCase\": {\n const reporter = citation.reporter\n if (citation.pincite !== undefined) {\n return `${citation.volume} ${reporter} at ${citation.pincite}`\n }\n const page = citation.page !== undefined ? ` ${citation.page}` : \"\"\n return `${citation.volume} ${reporter}${page}`\n }\n\n case \"federalRule\": {\n // Bluebook canonical abbreviated form.\n const ruleSetAbbrev: Record<typeof citation.ruleSet, string> = {\n civil: \"Civ. P.\",\n criminal: \"Crim. P.\",\n evidence: \"Evid.\",\n appellate: \"App. P.\",\n bankruptcy: \"Bankr. P.\",\n }\n const subsection = citation.subsection ?? \"\"\n return `Fed. R. ${ruleSetAbbrev[citation.ruleSet]} ${citation.rule}${subsection}`\n }\n\n case \"stateRule\": {\n // State court rules render as the jurisdiction-rule-set anchor used\n // in the input. The extractor canonicalizes to a single\n // `<jurisdiction> R. <ruleSet>. <rule>(<subsection>)?` form. #636\n const ruleSetAbbrev: Record<typeof citation.ruleSet, string> = {\n civil: \"Civ. P.\",\n criminal: \"Crim. P.\",\n evidence: \"Evid.\",\n appellate: \"App. P.\",\n bankruptcy: \"Bankr. P.\",\n other: \"\",\n }\n const subsection = citation.subsection ?? \"\"\n const setPart = ruleSetAbbrev[citation.ruleSet]\n const tail = setPart ? ` R. ${setPart}` : \"\"\n return `${citation.jurisdiction}${tail} ${citation.rule}${subsection}`\n }\n\n case \"restatement\": {\n const subsection = citation.subsection ?? \"\"\n return `Restatement (${citation.edition}) of ${citation.subject} § ${citation.section}${subsection}`\n }\n\n case \"treatise\": {\n const section = `§ ${citation.section}`\n return `${citation.volume} ${citation.title} ${section}`\n }\n\n case \"annotation\": {\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${citation.volume} ${citation.series} ${citation.page}${year}`\n }\n }\n}\n","import type { Citation, FullCaseCitation } from \"../types/citation\"\nimport type { ResolvedCitation, ResolutionResult } from \"../resolve/types\"\nimport type { CaseGroup } from \"./types\"\nimport { toReporterKeys } from \"./reporterKey\"\n\n/**\n * Build a lookup key for a full case citation: \"volume-reporter-page\".\n * Used to group duplicate full citations that lack a parallel groupId.\n */\nfunction citeKey(c: FullCaseCitation): string {\n return `${c.volume}-${c.reporter}-${c.page ?? \"blank\"}`\n}\n\n/** Type guard: narrow a Citation to FullCaseCitation after checking type === \"case\". */\nfunction isFullCase(cite: Citation): cite is FullCaseCitation {\n return cite.type === \"case\"\n}\n\n/** Extract resolution from a resolved short-form citation. */\nfunction getResolution(cite: ResolvedCitation): ResolutionResult | undefined {\n return (cite as ResolvedCitation & { resolution?: ResolutionResult }).resolution\n}\n\n/**\n * Group resolved citations by underlying case.\n *\n * Composes parallel linking, resolution, and volume/reporter/page identity\n * into `CaseGroup` objects. Non-case citations are ignored. Unresolved\n * short-form citations are excluded.\n *\n * @example\n * ```typescript\n * const citations = extractCitations(text)\n * const resolved = resolveCitations(citations, text)\n * const groups = groupByCase(resolved)\n * ```\n */\nexport function groupByCase(citations: ResolvedCitation[]): CaseGroup[] {\n // Map from citation index -> group index (for short-form resolution lookup)\n const indexToGroup = new Map<number, number>()\n // Map from groupId or citeKey -> group index (for dedup)\n const keyToGroup = new Map<string, number>()\n const groups: CaseGroup[] = []\n\n // First pass: assign full case citations to groups\n for (let i = 0; i < citations.length; i++) {\n const cite = citations[i]\n if (!isFullCase(cite)) continue\n\n // Check if this citation belongs to an existing group\n const gid = cite.groupId\n const key = citeKey(cite)\n const existingIdx = (gid ? keyToGroup.get(gid) : undefined) ?? keyToGroup.get(key)\n\n if (existingIdx !== undefined) {\n // Add to existing group\n groups[existingIdx].mentions.push(cite)\n indexToGroup.set(i, existingIdx)\n } else {\n // Create new group\n const groupIdx = groups.length\n const group: CaseGroup = {\n primaryCitation: cite,\n mentions: [cite],\n parallelCitations: toReporterKeys(cite),\n }\n groups.push(group)\n indexToGroup.set(i, groupIdx)\n keyToGroup.set(key, groupIdx)\n if (gid) {\n keyToGroup.set(gid, groupIdx)\n }\n }\n }\n\n // Second pass: assign short-form citations to their resolved group\n for (let i = 0; i < citations.length; i++) {\n const cite = citations[i]\n if (cite.type !== \"id\" && cite.type !== \"supra\" && cite.type !== \"shortFormCase\") continue\n\n const resolution = getResolution(cite)\n if (resolution?.resolvedTo === undefined) continue\n\n const groupIdx = indexToGroup.get(resolution.resolvedTo)\n if (groupIdx === undefined) continue\n\n groups[groupIdx].mentions.push(cite)\n indexToGroup.set(i, groupIdx)\n }\n\n // Sort mentions within each group by document position\n for (const group of groups) {\n group.mentions.sort((a, b) => a.span.originalStart - b.span.originalStart)\n }\n\n return groups\n}\n","import type { ContextOptions, SurroundingContext } from \"./types\"\n\n/**\n * Legal abbreviations that contain periods but are NOT sentence boundaries.\n * Kept as a static set in this file — does NOT import from src/data/\n * to preserve tree-shaking of the utils entry point.\n */\nconst LEGAL_ABBREVIATIONS = new Set([\n // Court and case abbreviations\n \"v\",\n \"vs\",\n // Reporter abbreviations (common ones)\n \"U.S\",\n \"S.Ct\",\n \"S. Ct\",\n \"L.Ed\",\n \"L. Ed\",\n \"F\",\n \"F.2d\",\n \"F.3d\",\n \"F.4th\",\n \"F.Supp\",\n \"F. Supp\",\n \"A.2d\",\n \"A.3d\",\n \"N.E\",\n \"N.E.2d\",\n \"N.W\",\n \"N.W.2d\",\n \"S.E\",\n \"S.E.2d\",\n \"S.W\",\n \"S.W.2d\",\n \"S.W.3d\",\n \"So\",\n \"So.2d\",\n \"So.3d\",\n \"P\",\n \"P.2d\",\n \"P.3d\",\n // Titles and procedural terms\n \"No\",\n \"Nos\",\n \"Inc\",\n \"Corp\",\n \"Ltd\",\n \"Co\",\n \"Ass'n\",\n \"Dept\",\n \"Dist\",\n \"Cir\",\n \"App\",\n \"Supp\",\n \"Rev\",\n \"Stat\",\n \"Const\",\n // General legal abbreviations\n \"Mr\",\n \"Mrs\",\n \"Ms\",\n \"Dr\",\n \"Jr\",\n \"Sr\",\n \"St\",\n \"Ct\",\n \"Atl\",\n \"Cal\",\n \"Fla\",\n \"Ill\",\n \"Tex\",\n \"Pa\",\n \"Md\",\n \"Va\",\n \"Wis\",\n \"Minn\",\n \"Mich\",\n \"Mass\",\n \"Conn\",\n \"Colo\",\n \"Ariz\",\n \"Ark\",\n \"Ga\",\n \"La\",\n \"Ind\",\n \"Kan\",\n \"Ky\",\n \"Miss\",\n \"Mo\",\n \"Neb\",\n \"Nev\",\n \"Okla\",\n \"Or\",\n \"Tenn\",\n \"Vt\",\n \"Wash\",\n \"Wyo\",\n \"Del\",\n \"Haw\",\n \"Ida\",\n \"Me\",\n \"Mont\",\n \"R.I\",\n \"S.C\",\n \"S.D\",\n \"N.C\",\n \"N.D\",\n \"N.J\",\n \"N.M\",\n \"N.Y\",\n \"W.Va\",\n // Federal abbreviations\n \"U.S.C\",\n \"C.F.R\",\n \"Fed\",\n \"Reg\",\n \"Pub\",\n \"Amend\",\n \"Sec\",\n \"Art\",\n \"Cl\",\n \"Ch\",\n \"Pt\",\n \"Vol\",\n \"Ed\",\n \"Harv\",\n \"Yale\",\n \"Stan\",\n \"Colum\",\n \"Geo\",\n])\n\n/**\n * Check if a period at the given position is likely an abbreviation,\n * not a sentence boundary.\n */\nfunction isAbbreviationPeriod(text: string, dotIndex: number): boolean {\n // Look backwards from the dot to find the word\n let wordStart = dotIndex\n while (wordStart > 0 && text[wordStart - 1] !== \" \" && text[wordStart - 1] !== \"\\n\") {\n wordStart--\n }\n\n const word = text.slice(wordStart, dotIndex)\n\n // Single letter followed by period (e.g., \"U.\", \"S.\", \"F.\")\n // Only treat as abbreviation if part of a dotted sequence:\n // - preceded by a period (like the \"S\" in \"U.S.\")\n // - followed by a letter/digit (like the \"F\" in \"F.2d\")\n if (word.length === 1 && /[A-Z]/.test(word)) {\n const charAfterDot = dotIndex + 1 < text.length ? text[dotIndex + 1] : \"\"\n const charBeforeWord = wordStart > 0 ? text[wordStart - 1] : \"\"\n if (charBeforeWord === \".\" || /[A-Za-z0-9]/.test(charAfterDot)) return true\n }\n\n // Check multi-character abbreviations (strip any trailing dots for lookup)\n const stripped = word.replace(/\\.$/g, \"\")\n if (LEGAL_ABBREVIATIONS.has(stripped)) return true\n\n // Check if the word itself (with internal dots) is known: \"U.S\", \"F.2d\", etc.\n if (LEGAL_ABBREVIATIONS.has(word)) return true\n\n // Number followed by period (ordinals like \"1.\" in list context — not sentence end if no space+uppercase follows)\n // This is handled by the caller's space+uppercase check\n\n return false\n}\n\n/**\n * Find the start of the sentence containing the given position.\n */\nfunction findSentenceStart(text: string, pos: number): number {\n for (let i = pos - 1; i >= 0; i--) {\n const ch = text[i]\n if (ch === \".\" || ch === \"?\" || ch === \"!\") {\n if (ch === \".\" && isAbbreviationPeriod(text, i)) continue\n\n // Check if followed by whitespace (the char after this terminator)\n const next = i + 1\n if (next < text.length && /\\s/.test(text[next])) {\n // Skip whitespace to find the start of the next sentence\n let start = next\n while (start < pos && /\\s/.test(text[start])) start++\n return start\n }\n }\n }\n return 0\n}\n\n/**\n * Find the end of the sentence containing the given position.\n */\nfunction findSentenceEnd(text: string, pos: number): number {\n for (let i = pos; i < text.length; i++) {\n const ch = text[i]\n if (ch === \".\" || ch === \"?\" || ch === \"!\") {\n if (ch === \".\" && isAbbreviationPeriod(text, i)) continue\n return i + 1\n }\n }\n return text.length\n}\n\n/**\n * Find the enclosing sentence or paragraph around a citation span.\n *\n * Legal-text-aware: periods in reporter abbreviations, court names,\n * and procedural terms (Corp., U.S., F.3d, No., v.) are not treated\n * as sentence boundaries.\n *\n * @example\n * ```typescript\n * const ctx = getSurroundingContext(text, { start: 33, end: 52 })\n * // ctx.text: \"In Smith v. Doe, 500 F.2d 123 (2020), the Court held X.\"\n * // ctx.span: { start: 16, end: 71 }\n * ```\n */\nexport function getSurroundingContext(\n text: string,\n span: { start: number; end: number },\n options?: ContextOptions,\n): SurroundingContext {\n const type = options?.type ?? \"sentence\"\n const maxLength = options?.maxLength\n\n let start: number\n let end: number\n\n if (type === \"paragraph\") {\n // Find paragraph boundaries (double newline)\n const beforeSpan = text.lastIndexOf(\"\\n\\n\", span.start)\n start = beforeSpan === -1 ? 0 : beforeSpan + 2\n const afterSpan = text.indexOf(\"\\n\\n\", span.end)\n end = afterSpan === -1 ? text.length : afterSpan\n } else {\n start = findSentenceStart(text, span.start)\n end = findSentenceEnd(text, span.end)\n }\n\n const raw = text.slice(start, end)\n const resultText = raw.trim()\n const trimmedStart = start + (raw.length - raw.trimStart().length)\n const trimmedEnd = end - (raw.length - raw.trimEnd().length)\n\n if (maxLength && resultText.length > maxLength) {\n const truncated = resultText.slice(0, maxLength)\n return {\n text: truncated,\n span: { start: trimmedStart, end: trimmedStart + truncated.length },\n }\n }\n\n return {\n text: resultText,\n span: { start: trimmedStart, end: trimmedEnd },\n }\n}\n","/**\n * Stable FNV-1a-64 hex of the NFC-normalized, NUL-joined fields. A cheap,\n * synchronous, dependency-free identity for dedup/equality. Fields are joined on\n * a NUL byte (which cannot appear in citation text) so that, e.g., {exact:\"a b\"}\n * and {exact:\"a\", prefix:\"b\"} do not collide. Iterates UTF-16 code units so any\n * consumer reproduces it with the identical loop. Returns 16-char lowercase hex.\n */\nexport function contentHash(exact: string, prefix = \"\", suffix = \"\"): string {\n const s = `${exact}\\u0000${prefix}\\u0000${suffix}`.normalize(\"NFC\")\n let h = 0xcbf29ce484222325n\n const prime = 0x100000001b3n\n for (let i = 0; i < s.length; i++) {\n h ^= BigInt(s.charCodeAt(i))\n h = BigInt.asUintN(64, h * prime)\n }\n return h.toString(16).padStart(16, \"0\")\n}\n","/** True when the character is a word character (\\w: [A-Za-z0-9_]). */\nfunction isWord(c: string | undefined): boolean {\n return c !== undefined && /\\w/.test(c)\n}\n\n/**\n * True when `needle` placed at `at` in `haystack` is not glued to a surrounding\n * word character. A non-word edge of the needle (e.g. the trailing \".\" of \"Id.\")\n * never requires a boundary on that side.\n */\nexport function tokenBounded(haystack: string, at: number, needle: string): boolean {\n if (!needle) return false\n const leftOk = !isWord(needle[0]) || at === 0 || !isWord(haystack[at - 1])\n const end = at + needle.length\n const rightOk =\n !isWord(needle[needle.length - 1]) || end >= haystack.length || !isWord(haystack[end])\n return leftOk && rightOk\n}\n\n/** Every token-bounded start index of `needle` in `haystack`, in document order. */\nexport function tokenBoundedIndexes(haystack: string, needle: string): number[] {\n const out: number[] = []\n if (!needle) return out\n let from = 0\n for (;;) {\n const at = haystack.indexOf(needle, from)\n if (at === -1) break\n if (tokenBounded(haystack, at, needle)) out.push(at)\n from = at + Math.max(1, needle.length)\n }\n return out\n}\n","import type { Citation } from \"../types/citation\"\nimport type { Span } from \"../types/span\"\nimport { contentHash } from \"./contentHash\"\nimport { getSurroundingContext } from \"./context\"\nimport { tokenBoundedIndexes } from \"./tokenBounded\"\nimport type { DurableLocator, DurableLocatorOptions } from \"./types\"\n\n/**\n * Build a durable locator for one citation against `source`.\n *\n * `source` MUST be the text matching `options.space` (default \"original\" — the\n * text passed to extractCitations). See {@link DurableLocatorOptions}. Throws on\n * out-of-range offsets, an empty span, or (on the original-space core-span path)\n * a slice that does not equal `citation.matchedText` — all of which indicate the\n * wrong `source` or `space`.\n */\nexport function toDurableLocator(\n citation: Citation,\n source: string,\n options: DurableLocatorOptions = {},\n): DurableLocator {\n const space = options.space ?? \"original\"\n const contextLength = options.contextLength ?? 32\n\n // Choose the span: fullSpan (when requested AND present) else the core span.\n // `fullSpan` lives only on some union members, so guard with `in`.\n let span: Span = citation.span\n let useFull = false\n const full = \"fullSpan\" in citation ? citation.fullSpan : undefined\n if (options.fullSpan === true && full !== undefined) {\n span = full\n useFull = true\n }\n\n const start = space === \"clean\" ? span.cleanStart : span.originalStart\n const end = space === \"clean\" ? span.cleanEnd : span.originalEnd\n\n if (start < 0 || end > source.length || start > end) {\n throw new Error(\n `toDurableLocator: span [${start}, ${end}) is out of range for source of length ${source.length} — wrong source text or space?`,\n )\n }\n\n const exact = source.slice(start, end)\n if (exact.length === 0) {\n throw new Error(\"toDurableLocator: empty exact quote — nothing to anchor\")\n }\n\n // matchedText is the original-text substring, so it only equals the slice on\n // the original-space core-span path. The clean path and the fullSpan path have\n // no stored equivalent to cross-check against.\n if (space === \"original\" && !useFull && exact !== citation.matchedText) {\n throw new Error(\n `toDurableLocator: sliced text \"${exact}\" does not equal citation.matchedText \"${citation.matchedText}\" — wrong source text or space?`,\n )\n }\n\n // Sentence-bounded, then clamped to contextLength. getSurroundingContext gives\n // the enclosing legal sentence (it knows \"F.3d\"/\"U.S.\" periods are not\n // boundaries); we slice raw windows from `source` within those bounds.\n const sentence = getSurroundingContext(source, { start, end })\n const sentStart = sentence.span.start\n const sentEnd = sentence.span.end\n const prefix = source.slice(Math.max(sentStart, start - contextLength), start)\n const suffix = source.slice(end, Math.min(sentEnd, end + contextLength))\n\n const occurrence = tokenBoundedIndexes(source, exact).indexOf(start)\n\n return {\n v: 1,\n space,\n quote: {\n exact,\n ...(prefix.length > 0 ? { prefix } : {}),\n ...(suffix.length > 0 ? { suffix } : {}),\n },\n position: { start, end },\n ...(occurrence >= 0 ? { occurrence } : {}),\n contentHash: contentHash(exact, prefix, suffix),\n }\n}\n\n/** Build durable locators for many citations sharing one `source` + options. */\nexport function toDurableLocators(\n citations: Citation[],\n source: string,\n options: DurableLocatorOptions = {},\n): DurableLocator[] {\n return citations.map((citation) => toDurableLocator(citation, source, options))\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { n as Citation, p as FullCaseCitation } from "../citation-CsWsvkxB.cjs";
2
- import { r as ResolvedCitation } from "../types-DXnfQIbk.cjs";
1
+ import { m as FullCaseCitation, n as Citation } from "../citation-t5DPIzyE.cjs";
2
+ import { r as ResolvedCitation } from "../types-D961u5ik.cjs";
3
3
 
4
4
  //#region src/utils/types.d.ts
5
5
  /**
@@ -37,6 +37,53 @@ interface CaseGroup {
37
37
  /** Distinct reporter strings: ["550 U.S. 544", "127 S. Ct. 1955"] */
38
38
  parallelCitations: string[];
39
39
  }
40
+ /**
41
+ * A portable, host-agnostic locator for a citation, in the style of the W3C Web
42
+ * Annotation selectors. Stores the citation as a quote plus surrounding context
43
+ * (TextQuoteSelector) and an offset hint (TextPositionSelector), so it survives
44
+ * edits to the document. Produced by `toDurableLocator`; resolution back to a
45
+ * concrete range is a consumer concern.
46
+ */
47
+ interface DurableLocator {
48
+ /** Schema version. */
49
+ v: 1;
50
+ /** Which text the offsets + quote were taken from. */
51
+ space: "original" | "clean";
52
+ /** W3C TextQuoteSelector — the anchor of record. */
53
+ quote: {
54
+ exact: string;
55
+ prefix?: string;
56
+ suffix?: string;
57
+ };
58
+ /** W3C TextPositionSelector — offsets in `space`. Hint/audit; may drift. */
59
+ position: {
60
+ start: number;
61
+ end: number;
62
+ };
63
+ /**
64
+ * Document-order ordinal among token-bounded hits of `exact`. Omitted when the
65
+ * span is not a token-bounded hit (e.g. glued inside a longer word).
66
+ */
67
+ occurrence?: number;
68
+ /** Stable FNV-1a-64 hex of exact+prefix+suffix — locator identity. */
69
+ contentHash: string;
70
+ }
71
+ /** Options for `toDurableLocator` / `toDurableLocators`. */
72
+ interface DurableLocatorOptions {
73
+ /**
74
+ * Coordinate space. Default "original": `source` must be the text passed to
75
+ * extractCitations. "clean": `source` must be eyecite's cleaned text
76
+ * (e.g. cleanText(input).text).
77
+ */
78
+ space?: "original" | "clean";
79
+ /**
80
+ * Use fullSpan (case name through final parenthetical) when present, else the
81
+ * core span. Default false.
82
+ */
83
+ fullSpan?: boolean;
84
+ /** Max characters per context side after sentence-bounding. Default 32. */
85
+ contextLength?: number;
86
+ }
40
87
  //#endregion
41
88
  //#region src/utils/reporterKey.d.ts
42
89
  /**
@@ -118,5 +165,19 @@ declare function getSurroundingContext(text: string, span: {
118
165
  end: number;
119
166
  }, options?: ContextOptions): SurroundingContext;
120
167
  //#endregion
121
- export { type CaseGroup, type ContextOptions, type SurroundingContext, getSurroundingContext, groupByCase, toBluebook, toReporterKey, toReporterKeys };
168
+ //#region src/utils/durableLocator.d.ts
169
+ /**
170
+ * Build a durable locator for one citation against `source`.
171
+ *
172
+ * `source` MUST be the text matching `options.space` (default "original" — the
173
+ * text passed to extractCitations). See {@link DurableLocatorOptions}. Throws on
174
+ * out-of-range offsets, an empty span, or (on the original-space core-span path)
175
+ * a slice that does not equal `citation.matchedText` — all of which indicate the
176
+ * wrong `source` or `space`.
177
+ */
178
+ declare function toDurableLocator(citation: Citation, source: string, options?: DurableLocatorOptions): DurableLocator;
179
+ /** Build durable locators for many citations sharing one `source` + options. */
180
+ declare function toDurableLocators(citations: Citation[], source: string, options?: DurableLocatorOptions): DurableLocator[];
181
+ //#endregion
182
+ export { type CaseGroup, type ContextOptions, type DurableLocator, type DurableLocatorOptions, type SurroundingContext, getSurroundingContext, groupByCase, toBluebook, toDurableLocator, toDurableLocators, toReporterKey, toReporterKeys };
122
183
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/utils/types.ts","../../src/utils/reporterKey.ts","../../src/utils/bluebook.ts","../../src/utils/groupByCase.ts","../../src/utils/context.ts"],"mappings":";;;;;;AAMA;UAAiB,cAAA;;EAEf,IAAA;EAEA;EAAA,SAAA;AAAA;;;;UAMe,kBAAA;;EAEf,IAAA;;EAEA,IAAA;IAAQ,KAAA;IAAe,GAAA;EAAA;AAAA;;;;;;;UASR,SAAA;;EAEf,eAAA,EAAiB,gBAAA;;EAEjB,QAAA,EAAU,gBAAA;ECTI;EDWd,iBAAA;AAAA;;;;;;AA7BF;;;;;AAUA;;;;iBCQgB,aAAA,CAAc,QAAA,EAAU,gBAAA;;;;;;ADKxC;;;;;;iBCYgB,cAAA,CAAe,QAAA,EAAU,gBAAA;;;;;;ADnCzC;;;;;AAUA;;;;;iBEmBgB,UAAA,CAAW,QAAA,EAAU,QAAA;;;;;AF7BrC;;;;;AAUA;;;;;;;iBGqBgB,WAAA,CAAY,SAAA,EAAW,gBAAA,KAAqB,SAAA;;;;;;AH/B5D;;;;;AAUA;;;;;;iBIyMgB,qBAAA,CACd,IAAA,UACA,IAAA;EAAQ,KAAA;EAAe,GAAA;AAAA,GACvB,OAAA,GAAU,cAAA,GACT,kBAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/utils/types.ts","../../src/utils/reporterKey.ts","../../src/utils/bluebook.ts","../../src/utils/groupByCase.ts","../../src/utils/context.ts","../../src/utils/durableLocator.ts"],"mappings":";;;;;;AAMA;UAAiB,cAAA;;EAEf,IAAA;EAEA;EAAA,SAAA;AAAA;;;;UAMe,kBAAA;;EAEf,IAAA;;EAEA,IAAA;IAAQ,KAAA;IAAe,GAAA;EAAA;AAAA;;;;;;;UASR,SAAA;EAgBjB;EAdE,eAAA,EAAiB,gBAAA;;EAEjB,QAAA,EAAU,gBAAA;;EAEV,iBAAA;AAAA;;;;;;;;UAUe,cAAA;;EAEf,CAAA;EAqBF;EAnBE,KAAA;;EAEA,KAAA;IACE,KAAA;IACA,MAAA;IACA,MAAA;EAAA;EA2BF;EAxBA,QAAA;IAAY,KAAA;IAAe,GAAA;EAAA;ECjCb;;;;EDsCd,UAAA;ECrBc;EDuBd,WAAA;AAAA;;UAIe,qBAAA;;;AEjCjB;;;EFuCE,KAAA;EEvCmC;;;;EF4CnC,QAAA;EG1Cc;EH4Cd,aAAA;AAAA;;;;;;AA3EF;;;;;AAUA;;;;iBCQgB,aAAA,CAAc,QAAA,EAAU,gBAAA;;;;;;ADKxC;;;;;;iBCYgB,cAAA,CAAe,QAAA,EAAU,gBAAA;;;;;;ADnCzC;;;;;AAUA;;;;;iBEmBgB,UAAA,CAAW,QAAA,EAAU,QAAA;;;;;AF7BrC;;;;;AAUA;;;;;;;iBGqBgB,WAAA,CAAY,SAAA,EAAW,gBAAA,KAAqB,SAAA;;;;;;AH/B5D;;;;;AAUA;;;;;;iBIyMgB,qBAAA,CACd,IAAA,UACA,IAAA;EAAQ,KAAA;EAAe,GAAA;AAAA,GACvB,OAAA,GAAU,cAAA,GACT,kBAAA;;;;;AJvNH;;;;;AAUA;;iBKAgB,gBAAA,CACd,QAAA,EAAU,QAAA,EACV,MAAA,UACA,OAAA,GAAS,qBAAA,GACR,cAAA;;iBA+Da,iBAAA,CACd,SAAA,EAAW,QAAA,IACX,MAAA,UACA,OAAA,GAAS,qBAAA,GACR,cAAA"}
@@ -1,5 +1,5 @@
1
- import { n as Citation, p as FullCaseCitation } from "../citation-Dy_8OOzV.mjs";
2
- import { r as ResolvedCitation } from "../types-BS607EPW.mjs";
1
+ import { m as FullCaseCitation, n as Citation } from "../citation-Dx71V5wD.mjs";
2
+ import { r as ResolvedCitation } from "../types-BKg6EJIz.mjs";
3
3
 
4
4
  //#region src/utils/types.d.ts
5
5
  /**
@@ -37,6 +37,53 @@ interface CaseGroup {
37
37
  /** Distinct reporter strings: ["550 U.S. 544", "127 S. Ct. 1955"] */
38
38
  parallelCitations: string[];
39
39
  }
40
+ /**
41
+ * A portable, host-agnostic locator for a citation, in the style of the W3C Web
42
+ * Annotation selectors. Stores the citation as a quote plus surrounding context
43
+ * (TextQuoteSelector) and an offset hint (TextPositionSelector), so it survives
44
+ * edits to the document. Produced by `toDurableLocator`; resolution back to a
45
+ * concrete range is a consumer concern.
46
+ */
47
+ interface DurableLocator {
48
+ /** Schema version. */
49
+ v: 1;
50
+ /** Which text the offsets + quote were taken from. */
51
+ space: "original" | "clean";
52
+ /** W3C TextQuoteSelector — the anchor of record. */
53
+ quote: {
54
+ exact: string;
55
+ prefix?: string;
56
+ suffix?: string;
57
+ };
58
+ /** W3C TextPositionSelector — offsets in `space`. Hint/audit; may drift. */
59
+ position: {
60
+ start: number;
61
+ end: number;
62
+ };
63
+ /**
64
+ * Document-order ordinal among token-bounded hits of `exact`. Omitted when the
65
+ * span is not a token-bounded hit (e.g. glued inside a longer word).
66
+ */
67
+ occurrence?: number;
68
+ /** Stable FNV-1a-64 hex of exact+prefix+suffix — locator identity. */
69
+ contentHash: string;
70
+ }
71
+ /** Options for `toDurableLocator` / `toDurableLocators`. */
72
+ interface DurableLocatorOptions {
73
+ /**
74
+ * Coordinate space. Default "original": `source` must be the text passed to
75
+ * extractCitations. "clean": `source` must be eyecite's cleaned text
76
+ * (e.g. cleanText(input).text).
77
+ */
78
+ space?: "original" | "clean";
79
+ /**
80
+ * Use fullSpan (case name through final parenthetical) when present, else the
81
+ * core span. Default false.
82
+ */
83
+ fullSpan?: boolean;
84
+ /** Max characters per context side after sentence-bounding. Default 32. */
85
+ contextLength?: number;
86
+ }
40
87
  //#endregion
41
88
  //#region src/utils/reporterKey.d.ts
42
89
  /**
@@ -118,5 +165,19 @@ declare function getSurroundingContext(text: string, span: {
118
165
  end: number;
119
166
  }, options?: ContextOptions): SurroundingContext;
120
167
  //#endregion
121
- export { type CaseGroup, type ContextOptions, type SurroundingContext, getSurroundingContext, groupByCase, toBluebook, toReporterKey, toReporterKeys };
168
+ //#region src/utils/durableLocator.d.ts
169
+ /**
170
+ * Build a durable locator for one citation against `source`.
171
+ *
172
+ * `source` MUST be the text matching `options.space` (default "original" — the
173
+ * text passed to extractCitations). See {@link DurableLocatorOptions}. Throws on
174
+ * out-of-range offsets, an empty span, or (on the original-space core-span path)
175
+ * a slice that does not equal `citation.matchedText` — all of which indicate the
176
+ * wrong `source` or `space`.
177
+ */
178
+ declare function toDurableLocator(citation: Citation, source: string, options?: DurableLocatorOptions): DurableLocator;
179
+ /** Build durable locators for many citations sharing one `source` + options. */
180
+ declare function toDurableLocators(citations: Citation[], source: string, options?: DurableLocatorOptions): DurableLocator[];
181
+ //#endregion
182
+ export { type CaseGroup, type ContextOptions, type DurableLocator, type DurableLocatorOptions, type SurroundingContext, getSurroundingContext, groupByCase, toBluebook, toDurableLocator, toDurableLocators, toReporterKey, toReporterKeys };
122
183
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/utils/types.ts","../../src/utils/reporterKey.ts","../../src/utils/bluebook.ts","../../src/utils/groupByCase.ts","../../src/utils/context.ts"],"mappings":";;;;;;AAMA;UAAiB,cAAA;;EAEf,IAAA;EAEA;EAAA,SAAA;AAAA;;;;UAMe,kBAAA;;EAEf,IAAA;;EAEA,IAAA;IAAQ,KAAA;IAAe,GAAA;EAAA;AAAA;;;;;;;UASR,SAAA;;EAEf,eAAA,EAAiB,gBAAA;;EAEjB,QAAA,EAAU,gBAAA;ECTI;EDWd,iBAAA;AAAA;;;;;;AA7BF;;;;;AAUA;;;;iBCQgB,aAAA,CAAc,QAAA,EAAU,gBAAA;;;;;;ADKxC;;;;;;iBCYgB,cAAA,CAAe,QAAA,EAAU,gBAAA;;;;;;ADnCzC;;;;;AAUA;;;;;iBEmBgB,UAAA,CAAW,QAAA,EAAU,QAAA;;;;;AF7BrC;;;;;AAUA;;;;;;;iBGqBgB,WAAA,CAAY,SAAA,EAAW,gBAAA,KAAqB,SAAA;;;;;;AH/B5D;;;;;AAUA;;;;;;iBIyMgB,qBAAA,CACd,IAAA,UACA,IAAA;EAAQ,KAAA;EAAe,GAAA;AAAA,GACvB,OAAA,GAAU,cAAA,GACT,kBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/utils/types.ts","../../src/utils/reporterKey.ts","../../src/utils/bluebook.ts","../../src/utils/groupByCase.ts","../../src/utils/context.ts","../../src/utils/durableLocator.ts"],"mappings":";;;;;;AAMA;UAAiB,cAAA;;EAEf,IAAA;EAEA;EAAA,SAAA;AAAA;;;;UAMe,kBAAA;;EAEf,IAAA;;EAEA,IAAA;IAAQ,KAAA;IAAe,GAAA;EAAA;AAAA;;;;;;;UASR,SAAA;EAgBjB;EAdE,eAAA,EAAiB,gBAAA;;EAEjB,QAAA,EAAU,gBAAA;;EAEV,iBAAA;AAAA;;;;;;;;UAUe,cAAA;;EAEf,CAAA;EAqBF;EAnBE,KAAA;;EAEA,KAAA;IACE,KAAA;IACA,MAAA;IACA,MAAA;EAAA;EA2BF;EAxBA,QAAA;IAAY,KAAA;IAAe,GAAA;EAAA;ECjCb;;;;EDsCd,UAAA;ECrBc;EDuBd,WAAA;AAAA;;UAIe,qBAAA;;;AEjCjB;;;EFuCE,KAAA;EEvCmC;;;;EF4CnC,QAAA;EG1Cc;EH4Cd,aAAA;AAAA;;;;;;AA3EF;;;;;AAUA;;;;iBCQgB,aAAA,CAAc,QAAA,EAAU,gBAAA;;;;;;ADKxC;;;;;;iBCYgB,cAAA,CAAe,QAAA,EAAU,gBAAA;;;;;;ADnCzC;;;;;AAUA;;;;;iBEmBgB,UAAA,CAAW,QAAA,EAAU,QAAA;;;;;AF7BrC;;;;;AAUA;;;;;;;iBGqBgB,WAAA,CAAY,SAAA,EAAW,gBAAA,KAAqB,SAAA;;;;;;AH/B5D;;;;;AAUA;;;;;;iBIyMgB,qBAAA,CACd,IAAA,UACA,IAAA;EAAQ,KAAA;EAAe,GAAA;AAAA,GACvB,OAAA,GAAU,cAAA,GACT,kBAAA;;;;;AJvNH;;;;;AAUA;;iBKAgB,gBAAA,CACd,QAAA,EAAU,QAAA,EACV,MAAA,UACA,OAAA,GAAS,qBAAA,GACR,cAAA;;iBA+Da,iBAAA,CACd,SAAA,EAAW,QAAA,IACX,MAAA,UACA,OAAA,GAAS,qBAAA,GACR,cAAA"}
@@ -3,5 +3,5 @@ function e(e,t,n){return n===void 0?`${e} ${t}`:`${e} ${t} ${n}`}function t(t){l
3
3
 
4
4
  `,t.start);a=n===-1?0:n+2;let r=e.indexOf(`
5
5
 
6
- `,t.end);o=r===-1?e.length:r}else a=d(e,t.start),o=f(e,t.end);let s=e.slice(a,o),c=s.trim(),l=a+(s.length-s.trimStart().length),u=o-(s.length-s.trimEnd().length);if(i&&c.length>i){let e=c.slice(0,i);return{text:e,span:{start:l,end:l+e.length}}}return{text:c,span:{start:l,end:u}}}export{p as getSurroundingContext,c as groupByCase,i as toBluebook,t as toReporterKey,n as toReporterKeys};
6
+ `,t.end);o=r===-1?e.length:r}else a=d(e,t.start),o=f(e,t.end);let s=e.slice(a,o),c=s.trim(),l=a+(s.length-s.trimStart().length),u=o-(s.length-s.trimEnd().length);if(i&&c.length>i){let e=c.slice(0,i);return{text:e,span:{start:l,end:l+e.length}}}return{text:c,span:{start:l,end:u}}}function m(e,t=``,n=``){let r=`${e}\u0000${t}\u0000${n}`.normalize(`NFC`),i=14695981039346656037n;for(let e=0;e<r.length;e++)i^=BigInt(r.charCodeAt(e)),i=BigInt.asUintN(64,i*1099511628211n);return i.toString(16).padStart(16,`0`)}function h(e){return e!==void 0&&/\w/.test(e)}function g(e,t,n){if(!n)return!1;let r=!h(n[0])||t===0||!h(e[t-1]),i=t+n.length,a=!h(n[n.length-1])||i>=e.length||!h(e[i]);return r&&a}function _(e,t){let n=[];if(!t)return n;let r=0;for(;;){let i=e.indexOf(t,r);if(i===-1)break;g(e,i,t)&&n.push(i),r=i+Math.max(1,t.length)}return n}function v(e,t,n={}){let r=n.space??`original`,i=n.contextLength??32,a=e.span,o=!1,s=`fullSpan`in e?e.fullSpan:void 0;n.fullSpan===!0&&s!==void 0&&(a=s,o=!0);let c=r===`clean`?a.cleanStart:a.originalStart,l=r===`clean`?a.cleanEnd:a.originalEnd;if(c<0||l>t.length||c>l)throw Error(`toDurableLocator: span [${c}, ${l}) is out of range for source of length ${t.length} — wrong source text or space?`);let u=t.slice(c,l);if(u.length===0)throw Error(`toDurableLocator: empty exact quote — nothing to anchor`);if(r===`original`&&!o&&u!==e.matchedText)throw Error(`toDurableLocator: sliced text "${u}" does not equal citation.matchedText "${e.matchedText}" — wrong source text or space?`);let d=p(t,{start:c,end:l}),f=d.span.start,h=d.span.end,g=t.slice(Math.max(f,c-i),c),v=t.slice(l,Math.min(h,l+i)),y=_(t,u).indexOf(c);return{v:1,space:r,quote:{exact:u,...g.length>0?{prefix:g}:{},...v.length>0?{suffix:v}:{}},position:{start:c,end:l},...y>=0?{occurrence:y}:{},contentHash:m(u,g,v)}}function y(e,t,n={}){return e.map(e=>v(e,t,n))}export{p as getSurroundingContext,c as groupByCase,i as toBluebook,v as toDurableLocator,y as toDurableLocators,t as toReporterKey,n as toReporterKeys};
7
7
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/utils/reporterKey.ts","../../src/utils/bluebook.ts","../../src/utils/groupByCase.ts","../../src/utils/context.ts"],"sourcesContent":["import type { FullCaseCitation } from \"../types/citation\"\n\n/**\n * Format a volume-reporter-page key from citation fields.\n */\nfunction formatKey(volume: number | string, reporter: string, page: number | undefined): string {\n if (page === undefined) {\n return `${volume} ${reporter}`\n }\n return `${volume} ${reporter} ${page}`\n}\n\n/**\n * Extract the volume-reporter-page lookup key from a case citation.\n *\n * Strips case name, pincite, year, and parenthetical.\n * Uses `normalizedReporter` when available, falls back to `reporter`.\n * Omits the page for blank-page citations.\n *\n * @example\n * ```typescript\n * toReporterKey(citation) // \"550 U.S. 544\"\n * ```\n */\nexport function toReporterKey(citation: FullCaseCitation): string {\n const reporter = citation.normalizedReporter ?? citation.reporter\n const page = citation.hasBlankPage ? undefined : citation.page\n return formatKey(citation.volume, reporter, page)\n}\n\n/**\n * Extract all volume-reporter-page lookup keys from a case citation,\n * including parallel citations.\n *\n * Returns the primary key first, followed by any parallel citation keys.\n *\n * @example\n * ```typescript\n * toReporterKeys(citation) // [\"410 U.S. 113\", \"93 S. Ct. 705\"]\n * ```\n */\nexport function toReporterKeys(citation: FullCaseCitation): string[] {\n const keys = [toReporterKey(citation)]\n\n if (citation.parallelCitations?.length) {\n for (const p of citation.parallelCitations) {\n keys.push(formatKey(p.volume, p.reporter, p.page))\n }\n }\n\n return keys\n}\n","import type { Citation } from \"../types/citation\"\n\n/** Convert an integer to a Roman numeral (1-27 covers all amendments + articles). */\nfunction toRoman(n: number): string {\n const numerals: Array<[number, string]> = [\n [10, \"X\"],\n [9, \"IX\"],\n [5, \"V\"],\n [4, \"IV\"],\n [1, \"I\"],\n ]\n let result = \"\"\n let remaining = n\n for (const [value, numeral] of numerals) {\n while (remaining >= value) {\n result += numeral\n remaining -= value\n }\n }\n return result\n}\n\n/**\n * Reconstruct a canonical Bluebook-style citation string from structured fields.\n *\n * Works across all 11 citation types via the discriminated union.\n * Best-effort: uses whatever fields are available on the citation object.\n *\n * @example\n * ```typescript\n * toBluebook(caseCitation) // \"Bell Atl. Corp. v. Twombly, 550 U.S. 544 (2007)\"\n * toBluebook(statuteCite) // \"42 U.S.C. § 1983\"\n * toBluebook(idCite) // \"Id. at 570\"\n * ```\n */\nexport function toBluebook(citation: Citation): string {\n switch (citation.type) {\n case \"case\": {\n const reporter = citation.normalizedReporter ?? citation.reporter\n let pageStr: string\n if (citation.hasBlankPage) {\n pageStr = \" ___\"\n } else if (citation.page !== undefined) {\n pageStr = ` ${citation.page}`\n } else {\n pageStr = \"\"\n }\n\n const core = `${citation.volume} ${reporter}${pageStr}`\n const pincite = citation.pincite !== undefined ? `, ${citation.pincite}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n const caseName = citation.caseName ? `${citation.caseName}, ` : \"\"\n\n return `${caseName}${core}${pincite}${year}`\n }\n\n case \"statute\":\n case \"regulation\": {\n // Statutes and regulations share the same Bluebook rendering shape\n // (title + code + chapter? + \\u00A7 section + subsection + et seq.).\n // Regulations only differ in `type` discriminator; rendering is\n // identical. #637\n const title = citation.title !== undefined ? `${citation.title} ` : \"\"\n // section may be absent (e.g. Massachusetts chapter-only like\n // `G.L. c. 93A` \\u2014 #569). Render the chapter form when present and\n // omit the trailing `\\u00A7 <section>` if there is no section.\n const code = citation.code ? `${citation.code} ` : \"\"\n const chapter = citation.chapter ? `c. ${citation.chapter}` : \"\"\n const subsection = citation.subsection ?? \"\"\n const etSeq = citation.hasEtSeq ? \" et seq.\" : \"\"\n if (citation.section !== undefined && citation.section !== \"\") {\n const section = `\\u00A7 ${citation.section}`\n const sep = chapter ? \", \" : \"\"\n return `${title}${code}${chapter}${sep}${section}${subsection}${etSeq}`\n }\n // chapter-only OR section absent: emit `code c. chapter` (Mass)\n // or `code \\u00A7` placeholder when neither field is present.\n // (`code` may be absent when an untagged bare-section cite was emitted\n // \\u2014 #531/#565 \\u2014 handled by the `citation.code ? ... : \"\"` ternary above.)\n if (chapter) return `${title}${code}${chapter}${etSeq}`\n return `${title}${code}\\u00A7${etSeq}`\n }\n\n case \"constitutional\": {\n const jurisdiction = citation.jurisdiction === \"US\" ? \"U.S.\" : (citation.jurisdiction ?? \"\")\n const prefix = `${jurisdiction} Const.`\n\n let body = \"\"\n if (citation.article !== undefined) {\n body += ` art. ${toRoman(citation.article)}`\n }\n if (citation.amendment !== undefined) {\n body += ` amend. ${toRoman(citation.amendment)}`\n }\n if (citation.section !== undefined) {\n body += `, \\u00A7 ${citation.section}`\n }\n if (citation.clause !== undefined) {\n body += `, cl. ${citation.clause}`\n }\n // #789 — append the post-reform location for `former … (now …)` cites.\n if (citation.currentLocation) {\n const now = citation.currentLocation\n let nowBody = \"\"\n if (now.article !== undefined) nowBody += `art. ${toRoman(now.article)}`\n if (now.amendment !== undefined) nowBody += `amend. ${toRoman(now.amendment)}`\n if (now.section !== undefined) nowBody += `, § ${now.section}`\n if (now.clause !== undefined) nowBody += `, cl. ${now.clause}`\n if (nowBody) body += ` (now ${nowBody})`\n }\n return `${prefix}${body}`\n }\n\n case \"journal\": {\n const vol = citation.volume !== undefined ? `${citation.volume} ` : \"\"\n const page = citation.page !== undefined ? ` ${citation.page}` : \"\"\n const pincite = citation.pincite !== undefined ? `, ${citation.pincite}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${vol}${citation.abbreviation}${page}${pincite}${year}`\n }\n\n case \"docket\": {\n const caseName = citation.caseName ? `${citation.caseName}, ` : \"\"\n const courtAndYear =\n citation.court && citation.year\n ? ` (${citation.court} ${citation.year})`\n : citation.court\n ? ` (${citation.court})`\n : citation.year\n ? ` (${citation.year})`\n : \"\"\n return `${caseName}No. ${citation.docketNumber}${courtAndYear}`\n }\n\n case \"neutral\":\n return `${citation.year} ${citation.court} ${citation.documentNumber}`\n\n case \"publicLaw\":\n return `Pub. L. No. ${citation.congress}-${citation.lawNumber}`\n\n case \"federalRegister\": {\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${citation.volume} Fed. Reg. ${citation.page}${year}`\n }\n\n case \"statutesAtLarge\":\n return `${citation.volume} Stat. ${citation.page}`\n\n case \"sessionLaw\": {\n // California leads with the compilation (\"Stats. 1992, ch. 726\"); Nevada\n // leads with the year (\"2003 Nev. Stat., ch. 427\").\n const head =\n citation.jurisdiction === \"NV\"\n ? `${citation.year} ${citation.code}`\n : `${citation.code} ${citation.year}`\n let sectionText = \"\"\n if (citation.sections) {\n sectionText = `, §§ ${citation.sections.join(\", \")}`\n } else if (citation.sectionRange) {\n sectionText = `, §§ ${citation.sectionRange.start}-${citation.sectionRange.end}`\n } else if (citation.section) {\n sectionText = `, § ${citation.section}`\n }\n let pageText = \"\"\n const pageLabel = citation.jurisdiction === \"NV\" ? \"at \" : \"pp. \"\n if (citation.pageRange) {\n pageText = `, ${pageLabel}${citation.pageRange.start}-${citation.pageRange.end}`\n } else if (citation.page) {\n pageText = `, ${citation.jurisdiction === \"NV\" ? \"at \" : \"p. \"}${citation.page}`\n }\n return `${head}, ch. ${citation.chapter}${sectionText}${pageText}`\n }\n\n case \"treaty\": {\n // Volume-series-page (1155 U.N.T.S. 331) vs \"No.\"-style series (T.I.A.S. No. 1502).\n if (citation.volume !== undefined && citation.page !== undefined) {\n return `${citation.volume} ${citation.series} ${citation.page}`\n }\n return `${citation.series} No. ${citation.seriesNumber}`\n }\n\n case \"legislativeMaterial\": {\n if (citation.kind === \"congressionalRecord\") {\n return `${citation.volume} Cong. Rec. ${citation.page}`\n }\n const abbrev = citation.chamber === \"House\" ? \"H.R.\" : \"S.\"\n const cong = citation.congress !== undefined ? `, ${citation.congress}th Cong.` : \"\"\n const sess = citation.session ? `, ${citation.session} Sess.` : \"\"\n const page = citation.page !== undefined ? `, p. ${citation.page}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${abbrev} Rep. No. ${citation.reportNumber}${cong}${sess}${page}${year}`\n }\n\n case \"localOrdinance\":\n return `${citation.code} § ${citation.section}`\n\n case \"canon\":\n return `Canon ${citation.canon}${citation.subsection ?? \"\"}`\n\n case \"id\":\n return citation.pincite !== undefined ? `Id. at ${citation.pincite}` : \"Id.\"\n\n case \"supra\":\n return citation.pincite !== undefined\n ? `${citation.partyName}, supra, at ${citation.pincite}`\n : `${citation.partyName}, supra`\n\n case \"shortFormCase\": {\n const reporter = citation.reporter\n if (citation.pincite !== undefined) {\n return `${citation.volume} ${reporter} at ${citation.pincite}`\n }\n const page = citation.page !== undefined ? ` ${citation.page}` : \"\"\n return `${citation.volume} ${reporter}${page}`\n }\n\n case \"federalRule\": {\n // Bluebook canonical abbreviated form.\n const ruleSetAbbrev: Record<typeof citation.ruleSet, string> = {\n civil: \"Civ. P.\",\n criminal: \"Crim. P.\",\n evidence: \"Evid.\",\n appellate: \"App. P.\",\n bankruptcy: \"Bankr. P.\",\n }\n const subsection = citation.subsection ?? \"\"\n return `Fed. R. ${ruleSetAbbrev[citation.ruleSet]} ${citation.rule}${subsection}`\n }\n\n case \"stateRule\": {\n // State court rules render as the jurisdiction-rule-set anchor used\n // in the input. The extractor canonicalizes to a single\n // `<jurisdiction> R. <ruleSet>. <rule>(<subsection>)?` form. #636\n const ruleSetAbbrev: Record<typeof citation.ruleSet, string> = {\n civil: \"Civ. P.\",\n criminal: \"Crim. P.\",\n evidence: \"Evid.\",\n appellate: \"App. P.\",\n bankruptcy: \"Bankr. P.\",\n other: \"\",\n }\n const subsection = citation.subsection ?? \"\"\n const setPart = ruleSetAbbrev[citation.ruleSet]\n const tail = setPart ? ` R. ${setPart}` : \"\"\n return `${citation.jurisdiction}${tail} ${citation.rule}${subsection}`\n }\n\n case \"restatement\": {\n const subsection = citation.subsection ?? \"\"\n return `Restatement (${citation.edition}) of ${citation.subject} § ${citation.section}${subsection}`\n }\n\n case \"treatise\": {\n const section = `§ ${citation.section}`\n return `${citation.volume} ${citation.title} ${section}`\n }\n\n case \"annotation\": {\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${citation.volume} ${citation.series} ${citation.page}${year}`\n }\n }\n}\n","import type { Citation, FullCaseCitation } from \"../types/citation\"\nimport type { ResolvedCitation, ResolutionResult } from \"../resolve/types\"\nimport type { CaseGroup } from \"./types\"\nimport { toReporterKeys } from \"./reporterKey\"\n\n/**\n * Build a lookup key for a full case citation: \"volume-reporter-page\".\n * Used to group duplicate full citations that lack a parallel groupId.\n */\nfunction citeKey(c: FullCaseCitation): string {\n return `${c.volume}-${c.reporter}-${c.page ?? \"blank\"}`\n}\n\n/** Type guard: narrow a Citation to FullCaseCitation after checking type === \"case\". */\nfunction isFullCase(cite: Citation): cite is FullCaseCitation {\n return cite.type === \"case\"\n}\n\n/** Extract resolution from a resolved short-form citation. */\nfunction getResolution(cite: ResolvedCitation): ResolutionResult | undefined {\n return (cite as ResolvedCitation & { resolution?: ResolutionResult }).resolution\n}\n\n/**\n * Group resolved citations by underlying case.\n *\n * Composes parallel linking, resolution, and volume/reporter/page identity\n * into `CaseGroup` objects. Non-case citations are ignored. Unresolved\n * short-form citations are excluded.\n *\n * @example\n * ```typescript\n * const citations = extractCitations(text)\n * const resolved = resolveCitations(citations, text)\n * const groups = groupByCase(resolved)\n * ```\n */\nexport function groupByCase(citations: ResolvedCitation[]): CaseGroup[] {\n // Map from citation index -> group index (for short-form resolution lookup)\n const indexToGroup = new Map<number, number>()\n // Map from groupId or citeKey -> group index (for dedup)\n const keyToGroup = new Map<string, number>()\n const groups: CaseGroup[] = []\n\n // First pass: assign full case citations to groups\n for (let i = 0; i < citations.length; i++) {\n const cite = citations[i]\n if (!isFullCase(cite)) continue\n\n // Check if this citation belongs to an existing group\n const gid = cite.groupId\n const key = citeKey(cite)\n const existingIdx = (gid ? keyToGroup.get(gid) : undefined) ?? keyToGroup.get(key)\n\n if (existingIdx !== undefined) {\n // Add to existing group\n groups[existingIdx].mentions.push(cite)\n indexToGroup.set(i, existingIdx)\n } else {\n // Create new group\n const groupIdx = groups.length\n const group: CaseGroup = {\n primaryCitation: cite,\n mentions: [cite],\n parallelCitations: toReporterKeys(cite),\n }\n groups.push(group)\n indexToGroup.set(i, groupIdx)\n keyToGroup.set(key, groupIdx)\n if (gid) {\n keyToGroup.set(gid, groupIdx)\n }\n }\n }\n\n // Second pass: assign short-form citations to their resolved group\n for (let i = 0; i < citations.length; i++) {\n const cite = citations[i]\n if (cite.type !== \"id\" && cite.type !== \"supra\" && cite.type !== \"shortFormCase\") continue\n\n const resolution = getResolution(cite)\n if (resolution?.resolvedTo === undefined) continue\n\n const groupIdx = indexToGroup.get(resolution.resolvedTo)\n if (groupIdx === undefined) continue\n\n groups[groupIdx].mentions.push(cite)\n indexToGroup.set(i, groupIdx)\n }\n\n // Sort mentions within each group by document position\n for (const group of groups) {\n group.mentions.sort((a, b) => a.span.originalStart - b.span.originalStart)\n }\n\n return groups\n}\n","import type { ContextOptions, SurroundingContext } from \"./types\"\n\n/**\n * Legal abbreviations that contain periods but are NOT sentence boundaries.\n * Kept as a static set in this file — does NOT import from src/data/\n * to preserve tree-shaking of the utils entry point.\n */\nconst LEGAL_ABBREVIATIONS = new Set([\n // Court and case abbreviations\n \"v\",\n \"vs\",\n // Reporter abbreviations (common ones)\n \"U.S\",\n \"S.Ct\",\n \"S. Ct\",\n \"L.Ed\",\n \"L. Ed\",\n \"F\",\n \"F.2d\",\n \"F.3d\",\n \"F.4th\",\n \"F.Supp\",\n \"F. Supp\",\n \"A.2d\",\n \"A.3d\",\n \"N.E\",\n \"N.E.2d\",\n \"N.W\",\n \"N.W.2d\",\n \"S.E\",\n \"S.E.2d\",\n \"S.W\",\n \"S.W.2d\",\n \"S.W.3d\",\n \"So\",\n \"So.2d\",\n \"So.3d\",\n \"P\",\n \"P.2d\",\n \"P.3d\",\n // Titles and procedural terms\n \"No\",\n \"Nos\",\n \"Inc\",\n \"Corp\",\n \"Ltd\",\n \"Co\",\n \"Ass'n\",\n \"Dept\",\n \"Dist\",\n \"Cir\",\n \"App\",\n \"Supp\",\n \"Rev\",\n \"Stat\",\n \"Const\",\n // General legal abbreviations\n \"Mr\",\n \"Mrs\",\n \"Ms\",\n \"Dr\",\n \"Jr\",\n \"Sr\",\n \"St\",\n \"Ct\",\n \"Atl\",\n \"Cal\",\n \"Fla\",\n \"Ill\",\n \"Tex\",\n \"Pa\",\n \"Md\",\n \"Va\",\n \"Wis\",\n \"Minn\",\n \"Mich\",\n \"Mass\",\n \"Conn\",\n \"Colo\",\n \"Ariz\",\n \"Ark\",\n \"Ga\",\n \"La\",\n \"Ind\",\n \"Kan\",\n \"Ky\",\n \"Miss\",\n \"Mo\",\n \"Neb\",\n \"Nev\",\n \"Okla\",\n \"Or\",\n \"Tenn\",\n \"Vt\",\n \"Wash\",\n \"Wyo\",\n \"Del\",\n \"Haw\",\n \"Ida\",\n \"Me\",\n \"Mont\",\n \"R.I\",\n \"S.C\",\n \"S.D\",\n \"N.C\",\n \"N.D\",\n \"N.J\",\n \"N.M\",\n \"N.Y\",\n \"W.Va\",\n // Federal abbreviations\n \"U.S.C\",\n \"C.F.R\",\n \"Fed\",\n \"Reg\",\n \"Pub\",\n \"Amend\",\n \"Sec\",\n \"Art\",\n \"Cl\",\n \"Ch\",\n \"Pt\",\n \"Vol\",\n \"Ed\",\n \"Harv\",\n \"Yale\",\n \"Stan\",\n \"Colum\",\n \"Geo\",\n])\n\n/**\n * Check if a period at the given position is likely an abbreviation,\n * not a sentence boundary.\n */\nfunction isAbbreviationPeriod(text: string, dotIndex: number): boolean {\n // Look backwards from the dot to find the word\n let wordStart = dotIndex\n while (wordStart > 0 && text[wordStart - 1] !== \" \" && text[wordStart - 1] !== \"\\n\") {\n wordStart--\n }\n\n const word = text.slice(wordStart, dotIndex)\n\n // Single letter followed by period (e.g., \"U.\", \"S.\", \"F.\")\n // Only treat as abbreviation if part of a dotted sequence:\n // - preceded by a period (like the \"S\" in \"U.S.\")\n // - followed by a letter/digit (like the \"F\" in \"F.2d\")\n if (word.length === 1 && /[A-Z]/.test(word)) {\n const charAfterDot = dotIndex + 1 < text.length ? text[dotIndex + 1] : \"\"\n const charBeforeWord = wordStart > 0 ? text[wordStart - 1] : \"\"\n if (charBeforeWord === \".\" || /[A-Za-z0-9]/.test(charAfterDot)) return true\n }\n\n // Check multi-character abbreviations (strip any trailing dots for lookup)\n const stripped = word.replace(/\\.$/g, \"\")\n if (LEGAL_ABBREVIATIONS.has(stripped)) return true\n\n // Check if the word itself (with internal dots) is known: \"U.S\", \"F.2d\", etc.\n if (LEGAL_ABBREVIATIONS.has(word)) return true\n\n // Number followed by period (ordinals like \"1.\" in list context — not sentence end if no space+uppercase follows)\n // This is handled by the caller's space+uppercase check\n\n return false\n}\n\n/**\n * Find the start of the sentence containing the given position.\n */\nfunction findSentenceStart(text: string, pos: number): number {\n for (let i = pos - 1; i >= 0; i--) {\n const ch = text[i]\n if (ch === \".\" || ch === \"?\" || ch === \"!\") {\n if (ch === \".\" && isAbbreviationPeriod(text, i)) continue\n\n // Check if followed by whitespace (the char after this terminator)\n const next = i + 1\n if (next < text.length && /\\s/.test(text[next])) {\n // Skip whitespace to find the start of the next sentence\n let start = next\n while (start < pos && /\\s/.test(text[start])) start++\n return start\n }\n }\n }\n return 0\n}\n\n/**\n * Find the end of the sentence containing the given position.\n */\nfunction findSentenceEnd(text: string, pos: number): number {\n for (let i = pos; i < text.length; i++) {\n const ch = text[i]\n if (ch === \".\" || ch === \"?\" || ch === \"!\") {\n if (ch === \".\" && isAbbreviationPeriod(text, i)) continue\n return i + 1\n }\n }\n return text.length\n}\n\n/**\n * Find the enclosing sentence or paragraph around a citation span.\n *\n * Legal-text-aware: periods in reporter abbreviations, court names,\n * and procedural terms (Corp., U.S., F.3d, No., v.) are not treated\n * as sentence boundaries.\n *\n * @example\n * ```typescript\n * const ctx = getSurroundingContext(text, { start: 33, end: 52 })\n * // ctx.text: \"In Smith v. Doe, 500 F.2d 123 (2020), the Court held X.\"\n * // ctx.span: { start: 16, end: 71 }\n * ```\n */\nexport function getSurroundingContext(\n text: string,\n span: { start: number; end: number },\n options?: ContextOptions,\n): SurroundingContext {\n const type = options?.type ?? \"sentence\"\n const maxLength = options?.maxLength\n\n let start: number\n let end: number\n\n if (type === \"paragraph\") {\n // Find paragraph boundaries (double newline)\n const beforeSpan = text.lastIndexOf(\"\\n\\n\", span.start)\n start = beforeSpan === -1 ? 0 : beforeSpan + 2\n const afterSpan = text.indexOf(\"\\n\\n\", span.end)\n end = afterSpan === -1 ? text.length : afterSpan\n } else {\n start = findSentenceStart(text, span.start)\n end = findSentenceEnd(text, span.end)\n }\n\n const raw = text.slice(start, end)\n const resultText = raw.trim()\n const trimmedStart = start + (raw.length - raw.trimStart().length)\n const trimmedEnd = end - (raw.length - raw.trimEnd().length)\n\n if (maxLength && resultText.length > maxLength) {\n const truncated = resultText.slice(0, maxLength)\n return {\n text: truncated,\n span: { start: trimmedStart, end: trimmedStart + truncated.length },\n }\n }\n\n return {\n text: resultText,\n span: { start: trimmedStart, end: trimmedEnd },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/utils/reporterKey.ts","../../src/utils/bluebook.ts","../../src/utils/groupByCase.ts","../../src/utils/context.ts","../../src/utils/contentHash.ts","../../src/utils/tokenBounded.ts","../../src/utils/durableLocator.ts"],"sourcesContent":["import type { FullCaseCitation } from \"../types/citation\"\n\n/**\n * Format a volume-reporter-page key from citation fields.\n */\nfunction formatKey(volume: number | string, reporter: string, page: number | undefined): string {\n if (page === undefined) {\n return `${volume} ${reporter}`\n }\n return `${volume} ${reporter} ${page}`\n}\n\n/**\n * Extract the volume-reporter-page lookup key from a case citation.\n *\n * Strips case name, pincite, year, and parenthetical.\n * Uses `normalizedReporter` when available, falls back to `reporter`.\n * Omits the page for blank-page citations.\n *\n * @example\n * ```typescript\n * toReporterKey(citation) // \"550 U.S. 544\"\n * ```\n */\nexport function toReporterKey(citation: FullCaseCitation): string {\n const reporter = citation.normalizedReporter ?? citation.reporter\n const page = citation.hasBlankPage ? undefined : citation.page\n return formatKey(citation.volume, reporter, page)\n}\n\n/**\n * Extract all volume-reporter-page lookup keys from a case citation,\n * including parallel citations.\n *\n * Returns the primary key first, followed by any parallel citation keys.\n *\n * @example\n * ```typescript\n * toReporterKeys(citation) // [\"410 U.S. 113\", \"93 S. Ct. 705\"]\n * ```\n */\nexport function toReporterKeys(citation: FullCaseCitation): string[] {\n const keys = [toReporterKey(citation)]\n\n if (citation.parallelCitations?.length) {\n for (const p of citation.parallelCitations) {\n keys.push(formatKey(p.volume, p.reporter, p.page))\n }\n }\n\n return keys\n}\n","import type { Citation } from \"../types/citation\"\n\n/** Convert an integer to a Roman numeral (1-27 covers all amendments + articles). */\nfunction toRoman(n: number): string {\n const numerals: Array<[number, string]> = [\n [10, \"X\"],\n [9, \"IX\"],\n [5, \"V\"],\n [4, \"IV\"],\n [1, \"I\"],\n ]\n let result = \"\"\n let remaining = n\n for (const [value, numeral] of numerals) {\n while (remaining >= value) {\n result += numeral\n remaining -= value\n }\n }\n return result\n}\n\n/**\n * Reconstruct a canonical Bluebook-style citation string from structured fields.\n *\n * Works across all 11 citation types via the discriminated union.\n * Best-effort: uses whatever fields are available on the citation object.\n *\n * @example\n * ```typescript\n * toBluebook(caseCitation) // \"Bell Atl. Corp. v. Twombly, 550 U.S. 544 (2007)\"\n * toBluebook(statuteCite) // \"42 U.S.C. § 1983\"\n * toBluebook(idCite) // \"Id. at 570\"\n * ```\n */\nexport function toBluebook(citation: Citation): string {\n switch (citation.type) {\n case \"case\": {\n const reporter = citation.normalizedReporter ?? citation.reporter\n let pageStr: string\n if (citation.hasBlankPage) {\n pageStr = \" ___\"\n } else if (citation.page !== undefined) {\n pageStr = ` ${citation.page}`\n } else {\n pageStr = \"\"\n }\n\n const core = `${citation.volume} ${reporter}${pageStr}`\n const pincite = citation.pincite !== undefined ? `, ${citation.pincite}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n const caseName = citation.caseName ? `${citation.caseName}, ` : \"\"\n\n return `${caseName}${core}${pincite}${year}`\n }\n\n case \"statute\":\n case \"regulation\": {\n // Statutes and regulations share the same Bluebook rendering shape\n // (title + code + chapter? + \\u00A7 section + subsection + et seq.).\n // Regulations only differ in `type` discriminator; rendering is\n // identical. #637\n const title = citation.title !== undefined ? `${citation.title} ` : \"\"\n // section may be absent (e.g. Massachusetts chapter-only like\n // `G.L. c. 93A` \\u2014 #569). Render the chapter form when present and\n // omit the trailing `\\u00A7 <section>` if there is no section.\n const code = citation.code ? `${citation.code} ` : \"\"\n const chapter = citation.chapter ? `c. ${citation.chapter}` : \"\"\n const subsection = citation.subsection ?? \"\"\n const etSeq = citation.hasEtSeq ? \" et seq.\" : \"\"\n if (citation.section !== undefined && citation.section !== \"\") {\n const section = `\\u00A7 ${citation.section}`\n const sep = chapter ? \", \" : \"\"\n return `${title}${code}${chapter}${sep}${section}${subsection}${etSeq}`\n }\n // chapter-only OR section absent: emit `code c. chapter` (Mass)\n // or `code \\u00A7` placeholder when neither field is present.\n // (`code` may be absent when an untagged bare-section cite was emitted\n // \\u2014 #531/#565 \\u2014 handled by the `citation.code ? ... : \"\"` ternary above.)\n if (chapter) return `${title}${code}${chapter}${etSeq}`\n return `${title}${code}\\u00A7${etSeq}`\n }\n\n case \"constitutional\": {\n const jurisdiction = citation.jurisdiction === \"US\" ? \"U.S.\" : (citation.jurisdiction ?? \"\")\n const prefix = `${jurisdiction} Const.`\n\n let body = \"\"\n if (citation.article !== undefined) {\n body += ` art. ${toRoman(citation.article)}`\n }\n if (citation.amendment !== undefined) {\n body += ` amend. ${toRoman(citation.amendment)}`\n }\n if (citation.section !== undefined) {\n body += `, \\u00A7 ${citation.section}`\n }\n if (citation.clause !== undefined) {\n body += `, cl. ${citation.clause}`\n }\n // #789 — append the post-reform location for `former … (now …)` cites.\n if (citation.currentLocation) {\n const now = citation.currentLocation\n let nowBody = \"\"\n if (now.article !== undefined) nowBody += `art. ${toRoman(now.article)}`\n if (now.amendment !== undefined) nowBody += `amend. ${toRoman(now.amendment)}`\n if (now.section !== undefined) nowBody += `, § ${now.section}`\n if (now.clause !== undefined) nowBody += `, cl. ${now.clause}`\n if (nowBody) body += ` (now ${nowBody})`\n }\n return `${prefix}${body}`\n }\n\n case \"journal\": {\n const vol = citation.volume !== undefined ? `${citation.volume} ` : \"\"\n const page = citation.page !== undefined ? ` ${citation.page}` : \"\"\n const pincite = citation.pincite !== undefined ? `, ${citation.pincite}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${vol}${citation.abbreviation}${page}${pincite}${year}`\n }\n\n case \"docket\": {\n const caseName = citation.caseName ? `${citation.caseName}, ` : \"\"\n const courtAndYear =\n citation.court && citation.year\n ? ` (${citation.court} ${citation.year})`\n : citation.court\n ? ` (${citation.court})`\n : citation.year\n ? ` (${citation.year})`\n : \"\"\n return `${caseName}No. ${citation.docketNumber}${courtAndYear}`\n }\n\n case \"neutral\":\n return `${citation.year} ${citation.court} ${citation.documentNumber}`\n\n case \"publicLaw\":\n return `Pub. L. No. ${citation.congress}-${citation.lawNumber}`\n\n case \"federalRegister\": {\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${citation.volume} Fed. Reg. ${citation.page}${year}`\n }\n\n case \"statutesAtLarge\":\n return `${citation.volume} Stat. ${citation.page}`\n\n case \"sessionLaw\": {\n // California leads with the compilation (\"Stats. 1992, ch. 726\"); Nevada\n // leads with the year (\"2003 Nev. Stat., ch. 427\").\n const head =\n citation.jurisdiction === \"NV\"\n ? `${citation.year} ${citation.code}`\n : `${citation.code} ${citation.year}`\n let sectionText = \"\"\n if (citation.sections) {\n sectionText = `, §§ ${citation.sections.join(\", \")}`\n } else if (citation.sectionRange) {\n sectionText = `, §§ ${citation.sectionRange.start}-${citation.sectionRange.end}`\n } else if (citation.section) {\n sectionText = `, § ${citation.section}`\n }\n let pageText = \"\"\n const pageLabel = citation.jurisdiction === \"NV\" ? \"at \" : \"pp. \"\n if (citation.pageRange) {\n pageText = `, ${pageLabel}${citation.pageRange.start}-${citation.pageRange.end}`\n } else if (citation.page) {\n pageText = `, ${citation.jurisdiction === \"NV\" ? \"at \" : \"p. \"}${citation.page}`\n }\n return `${head}, ch. ${citation.chapter}${sectionText}${pageText}`\n }\n\n case \"treaty\": {\n // Volume-series-page (1155 U.N.T.S. 331) vs \"No.\"-style series (T.I.A.S. No. 1502).\n if (citation.volume !== undefined && citation.page !== undefined) {\n return `${citation.volume} ${citation.series} ${citation.page}`\n }\n return `${citation.series} No. ${citation.seriesNumber}`\n }\n\n case \"legislativeMaterial\": {\n if (citation.kind === \"congressionalRecord\") {\n return `${citation.volume} Cong. Rec. ${citation.page}`\n }\n const abbrev = citation.chamber === \"House\" ? \"H.R.\" : \"S.\"\n const cong = citation.congress !== undefined ? `, ${citation.congress}th Cong.` : \"\"\n const sess = citation.session ? `, ${citation.session} Sess.` : \"\"\n const page = citation.page !== undefined ? `, p. ${citation.page}` : \"\"\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${abbrev} Rep. No. ${citation.reportNumber}${cong}${sess}${page}${year}`\n }\n\n case \"localOrdinance\":\n return `${citation.code} § ${citation.section}`\n\n case \"canon\":\n return `Canon ${citation.canon}${citation.subsection ?? \"\"}`\n\n case \"id\":\n return citation.pincite !== undefined ? `Id. at ${citation.pincite}` : \"Id.\"\n\n case \"supra\":\n return citation.pincite !== undefined\n ? `${citation.partyName}, supra, at ${citation.pincite}`\n : `${citation.partyName}, supra`\n\n case \"shortFormCase\": {\n const reporter = citation.reporter\n if (citation.pincite !== undefined) {\n return `${citation.volume} ${reporter} at ${citation.pincite}`\n }\n const page = citation.page !== undefined ? ` ${citation.page}` : \"\"\n return `${citation.volume} ${reporter}${page}`\n }\n\n case \"federalRule\": {\n // Bluebook canonical abbreviated form.\n const ruleSetAbbrev: Record<typeof citation.ruleSet, string> = {\n civil: \"Civ. P.\",\n criminal: \"Crim. P.\",\n evidence: \"Evid.\",\n appellate: \"App. P.\",\n bankruptcy: \"Bankr. P.\",\n }\n const subsection = citation.subsection ?? \"\"\n return `Fed. R. ${ruleSetAbbrev[citation.ruleSet]} ${citation.rule}${subsection}`\n }\n\n case \"stateRule\": {\n // State court rules render as the jurisdiction-rule-set anchor used\n // in the input. The extractor canonicalizes to a single\n // `<jurisdiction> R. <ruleSet>. <rule>(<subsection>)?` form. #636\n const ruleSetAbbrev: Record<typeof citation.ruleSet, string> = {\n civil: \"Civ. P.\",\n criminal: \"Crim. P.\",\n evidence: \"Evid.\",\n appellate: \"App. P.\",\n bankruptcy: \"Bankr. P.\",\n other: \"\",\n }\n const subsection = citation.subsection ?? \"\"\n const setPart = ruleSetAbbrev[citation.ruleSet]\n const tail = setPart ? ` R. ${setPart}` : \"\"\n return `${citation.jurisdiction}${tail} ${citation.rule}${subsection}`\n }\n\n case \"restatement\": {\n const subsection = citation.subsection ?? \"\"\n return `Restatement (${citation.edition}) of ${citation.subject} § ${citation.section}${subsection}`\n }\n\n case \"treatise\": {\n const section = `§ ${citation.section}`\n return `${citation.volume} ${citation.title} ${section}`\n }\n\n case \"annotation\": {\n const year = citation.year !== undefined ? ` (${citation.year})` : \"\"\n return `${citation.volume} ${citation.series} ${citation.page}${year}`\n }\n }\n}\n","import type { Citation, FullCaseCitation } from \"../types/citation\"\nimport type { ResolvedCitation, ResolutionResult } from \"../resolve/types\"\nimport type { CaseGroup } from \"./types\"\nimport { toReporterKeys } from \"./reporterKey\"\n\n/**\n * Build a lookup key for a full case citation: \"volume-reporter-page\".\n * Used to group duplicate full citations that lack a parallel groupId.\n */\nfunction citeKey(c: FullCaseCitation): string {\n return `${c.volume}-${c.reporter}-${c.page ?? \"blank\"}`\n}\n\n/** Type guard: narrow a Citation to FullCaseCitation after checking type === \"case\". */\nfunction isFullCase(cite: Citation): cite is FullCaseCitation {\n return cite.type === \"case\"\n}\n\n/** Extract resolution from a resolved short-form citation. */\nfunction getResolution(cite: ResolvedCitation): ResolutionResult | undefined {\n return (cite as ResolvedCitation & { resolution?: ResolutionResult }).resolution\n}\n\n/**\n * Group resolved citations by underlying case.\n *\n * Composes parallel linking, resolution, and volume/reporter/page identity\n * into `CaseGroup` objects. Non-case citations are ignored. Unresolved\n * short-form citations are excluded.\n *\n * @example\n * ```typescript\n * const citations = extractCitations(text)\n * const resolved = resolveCitations(citations, text)\n * const groups = groupByCase(resolved)\n * ```\n */\nexport function groupByCase(citations: ResolvedCitation[]): CaseGroup[] {\n // Map from citation index -> group index (for short-form resolution lookup)\n const indexToGroup = new Map<number, number>()\n // Map from groupId or citeKey -> group index (for dedup)\n const keyToGroup = new Map<string, number>()\n const groups: CaseGroup[] = []\n\n // First pass: assign full case citations to groups\n for (let i = 0; i < citations.length; i++) {\n const cite = citations[i]\n if (!isFullCase(cite)) continue\n\n // Check if this citation belongs to an existing group\n const gid = cite.groupId\n const key = citeKey(cite)\n const existingIdx = (gid ? keyToGroup.get(gid) : undefined) ?? keyToGroup.get(key)\n\n if (existingIdx !== undefined) {\n // Add to existing group\n groups[existingIdx].mentions.push(cite)\n indexToGroup.set(i, existingIdx)\n } else {\n // Create new group\n const groupIdx = groups.length\n const group: CaseGroup = {\n primaryCitation: cite,\n mentions: [cite],\n parallelCitations: toReporterKeys(cite),\n }\n groups.push(group)\n indexToGroup.set(i, groupIdx)\n keyToGroup.set(key, groupIdx)\n if (gid) {\n keyToGroup.set(gid, groupIdx)\n }\n }\n }\n\n // Second pass: assign short-form citations to their resolved group\n for (let i = 0; i < citations.length; i++) {\n const cite = citations[i]\n if (cite.type !== \"id\" && cite.type !== \"supra\" && cite.type !== \"shortFormCase\") continue\n\n const resolution = getResolution(cite)\n if (resolution?.resolvedTo === undefined) continue\n\n const groupIdx = indexToGroup.get(resolution.resolvedTo)\n if (groupIdx === undefined) continue\n\n groups[groupIdx].mentions.push(cite)\n indexToGroup.set(i, groupIdx)\n }\n\n // Sort mentions within each group by document position\n for (const group of groups) {\n group.mentions.sort((a, b) => a.span.originalStart - b.span.originalStart)\n }\n\n return groups\n}\n","import type { ContextOptions, SurroundingContext } from \"./types\"\n\n/**\n * Legal abbreviations that contain periods but are NOT sentence boundaries.\n * Kept as a static set in this file — does NOT import from src/data/\n * to preserve tree-shaking of the utils entry point.\n */\nconst LEGAL_ABBREVIATIONS = new Set([\n // Court and case abbreviations\n \"v\",\n \"vs\",\n // Reporter abbreviations (common ones)\n \"U.S\",\n \"S.Ct\",\n \"S. Ct\",\n \"L.Ed\",\n \"L. Ed\",\n \"F\",\n \"F.2d\",\n \"F.3d\",\n \"F.4th\",\n \"F.Supp\",\n \"F. Supp\",\n \"A.2d\",\n \"A.3d\",\n \"N.E\",\n \"N.E.2d\",\n \"N.W\",\n \"N.W.2d\",\n \"S.E\",\n \"S.E.2d\",\n \"S.W\",\n \"S.W.2d\",\n \"S.W.3d\",\n \"So\",\n \"So.2d\",\n \"So.3d\",\n \"P\",\n \"P.2d\",\n \"P.3d\",\n // Titles and procedural terms\n \"No\",\n \"Nos\",\n \"Inc\",\n \"Corp\",\n \"Ltd\",\n \"Co\",\n \"Ass'n\",\n \"Dept\",\n \"Dist\",\n \"Cir\",\n \"App\",\n \"Supp\",\n \"Rev\",\n \"Stat\",\n \"Const\",\n // General legal abbreviations\n \"Mr\",\n \"Mrs\",\n \"Ms\",\n \"Dr\",\n \"Jr\",\n \"Sr\",\n \"St\",\n \"Ct\",\n \"Atl\",\n \"Cal\",\n \"Fla\",\n \"Ill\",\n \"Tex\",\n \"Pa\",\n \"Md\",\n \"Va\",\n \"Wis\",\n \"Minn\",\n \"Mich\",\n \"Mass\",\n \"Conn\",\n \"Colo\",\n \"Ariz\",\n \"Ark\",\n \"Ga\",\n \"La\",\n \"Ind\",\n \"Kan\",\n \"Ky\",\n \"Miss\",\n \"Mo\",\n \"Neb\",\n \"Nev\",\n \"Okla\",\n \"Or\",\n \"Tenn\",\n \"Vt\",\n \"Wash\",\n \"Wyo\",\n \"Del\",\n \"Haw\",\n \"Ida\",\n \"Me\",\n \"Mont\",\n \"R.I\",\n \"S.C\",\n \"S.D\",\n \"N.C\",\n \"N.D\",\n \"N.J\",\n \"N.M\",\n \"N.Y\",\n \"W.Va\",\n // Federal abbreviations\n \"U.S.C\",\n \"C.F.R\",\n \"Fed\",\n \"Reg\",\n \"Pub\",\n \"Amend\",\n \"Sec\",\n \"Art\",\n \"Cl\",\n \"Ch\",\n \"Pt\",\n \"Vol\",\n \"Ed\",\n \"Harv\",\n \"Yale\",\n \"Stan\",\n \"Colum\",\n \"Geo\",\n])\n\n/**\n * Check if a period at the given position is likely an abbreviation,\n * not a sentence boundary.\n */\nfunction isAbbreviationPeriod(text: string, dotIndex: number): boolean {\n // Look backwards from the dot to find the word\n let wordStart = dotIndex\n while (wordStart > 0 && text[wordStart - 1] !== \" \" && text[wordStart - 1] !== \"\\n\") {\n wordStart--\n }\n\n const word = text.slice(wordStart, dotIndex)\n\n // Single letter followed by period (e.g., \"U.\", \"S.\", \"F.\")\n // Only treat as abbreviation if part of a dotted sequence:\n // - preceded by a period (like the \"S\" in \"U.S.\")\n // - followed by a letter/digit (like the \"F\" in \"F.2d\")\n if (word.length === 1 && /[A-Z]/.test(word)) {\n const charAfterDot = dotIndex + 1 < text.length ? text[dotIndex + 1] : \"\"\n const charBeforeWord = wordStart > 0 ? text[wordStart - 1] : \"\"\n if (charBeforeWord === \".\" || /[A-Za-z0-9]/.test(charAfterDot)) return true\n }\n\n // Check multi-character abbreviations (strip any trailing dots for lookup)\n const stripped = word.replace(/\\.$/g, \"\")\n if (LEGAL_ABBREVIATIONS.has(stripped)) return true\n\n // Check if the word itself (with internal dots) is known: \"U.S\", \"F.2d\", etc.\n if (LEGAL_ABBREVIATIONS.has(word)) return true\n\n // Number followed by period (ordinals like \"1.\" in list context — not sentence end if no space+uppercase follows)\n // This is handled by the caller's space+uppercase check\n\n return false\n}\n\n/**\n * Find the start of the sentence containing the given position.\n */\nfunction findSentenceStart(text: string, pos: number): number {\n for (let i = pos - 1; i >= 0; i--) {\n const ch = text[i]\n if (ch === \".\" || ch === \"?\" || ch === \"!\") {\n if (ch === \".\" && isAbbreviationPeriod(text, i)) continue\n\n // Check if followed by whitespace (the char after this terminator)\n const next = i + 1\n if (next < text.length && /\\s/.test(text[next])) {\n // Skip whitespace to find the start of the next sentence\n let start = next\n while (start < pos && /\\s/.test(text[start])) start++\n return start\n }\n }\n }\n return 0\n}\n\n/**\n * Find the end of the sentence containing the given position.\n */\nfunction findSentenceEnd(text: string, pos: number): number {\n for (let i = pos; i < text.length; i++) {\n const ch = text[i]\n if (ch === \".\" || ch === \"?\" || ch === \"!\") {\n if (ch === \".\" && isAbbreviationPeriod(text, i)) continue\n return i + 1\n }\n }\n return text.length\n}\n\n/**\n * Find the enclosing sentence or paragraph around a citation span.\n *\n * Legal-text-aware: periods in reporter abbreviations, court names,\n * and procedural terms (Corp., U.S., F.3d, No., v.) are not treated\n * as sentence boundaries.\n *\n * @example\n * ```typescript\n * const ctx = getSurroundingContext(text, { start: 33, end: 52 })\n * // ctx.text: \"In Smith v. Doe, 500 F.2d 123 (2020), the Court held X.\"\n * // ctx.span: { start: 16, end: 71 }\n * ```\n */\nexport function getSurroundingContext(\n text: string,\n span: { start: number; end: number },\n options?: ContextOptions,\n): SurroundingContext {\n const type = options?.type ?? \"sentence\"\n const maxLength = options?.maxLength\n\n let start: number\n let end: number\n\n if (type === \"paragraph\") {\n // Find paragraph boundaries (double newline)\n const beforeSpan = text.lastIndexOf(\"\\n\\n\", span.start)\n start = beforeSpan === -1 ? 0 : beforeSpan + 2\n const afterSpan = text.indexOf(\"\\n\\n\", span.end)\n end = afterSpan === -1 ? text.length : afterSpan\n } else {\n start = findSentenceStart(text, span.start)\n end = findSentenceEnd(text, span.end)\n }\n\n const raw = text.slice(start, end)\n const resultText = raw.trim()\n const trimmedStart = start + (raw.length - raw.trimStart().length)\n const trimmedEnd = end - (raw.length - raw.trimEnd().length)\n\n if (maxLength && resultText.length > maxLength) {\n const truncated = resultText.slice(0, maxLength)\n return {\n text: truncated,\n span: { start: trimmedStart, end: trimmedStart + truncated.length },\n }\n }\n\n return {\n text: resultText,\n span: { start: trimmedStart, end: trimmedEnd },\n }\n}\n","/**\n * Stable FNV-1a-64 hex of the NFC-normalized, NUL-joined fields. A cheap,\n * synchronous, dependency-free identity for dedup/equality. Fields are joined on\n * a NUL byte (which cannot appear in citation text) so that, e.g., {exact:\"a b\"}\n * and {exact:\"a\", prefix:\"b\"} do not collide. Iterates UTF-16 code units so any\n * consumer reproduces it with the identical loop. Returns 16-char lowercase hex.\n */\nexport function contentHash(exact: string, prefix = \"\", suffix = \"\"): string {\n const s = `${exact}\\u0000${prefix}\\u0000${suffix}`.normalize(\"NFC\")\n let h = 0xcbf29ce484222325n\n const prime = 0x100000001b3n\n for (let i = 0; i < s.length; i++) {\n h ^= BigInt(s.charCodeAt(i))\n h = BigInt.asUintN(64, h * prime)\n }\n return h.toString(16).padStart(16, \"0\")\n}\n","/** True when the character is a word character (\\w: [A-Za-z0-9_]). */\nfunction isWord(c: string | undefined): boolean {\n return c !== undefined && /\\w/.test(c)\n}\n\n/**\n * True when `needle` placed at `at` in `haystack` is not glued to a surrounding\n * word character. A non-word edge of the needle (e.g. the trailing \".\" of \"Id.\")\n * never requires a boundary on that side.\n */\nexport function tokenBounded(haystack: string, at: number, needle: string): boolean {\n if (!needle) return false\n const leftOk = !isWord(needle[0]) || at === 0 || !isWord(haystack[at - 1])\n const end = at + needle.length\n const rightOk =\n !isWord(needle[needle.length - 1]) || end >= haystack.length || !isWord(haystack[end])\n return leftOk && rightOk\n}\n\n/** Every token-bounded start index of `needle` in `haystack`, in document order. */\nexport function tokenBoundedIndexes(haystack: string, needle: string): number[] {\n const out: number[] = []\n if (!needle) return out\n let from = 0\n for (;;) {\n const at = haystack.indexOf(needle, from)\n if (at === -1) break\n if (tokenBounded(haystack, at, needle)) out.push(at)\n from = at + Math.max(1, needle.length)\n }\n return out\n}\n","import type { Citation } from \"../types/citation\"\nimport type { Span } from \"../types/span\"\nimport { contentHash } from \"./contentHash\"\nimport { getSurroundingContext } from \"./context\"\nimport { tokenBoundedIndexes } from \"./tokenBounded\"\nimport type { DurableLocator, DurableLocatorOptions } from \"./types\"\n\n/**\n * Build a durable locator for one citation against `source`.\n *\n * `source` MUST be the text matching `options.space` (default \"original\" — the\n * text passed to extractCitations). See {@link DurableLocatorOptions}. Throws on\n * out-of-range offsets, an empty span, or (on the original-space core-span path)\n * a slice that does not equal `citation.matchedText` — all of which indicate the\n * wrong `source` or `space`.\n */\nexport function toDurableLocator(\n citation: Citation,\n source: string,\n options: DurableLocatorOptions = {},\n): DurableLocator {\n const space = options.space ?? \"original\"\n const contextLength = options.contextLength ?? 32\n\n // Choose the span: fullSpan (when requested AND present) else the core span.\n // `fullSpan` lives only on some union members, so guard with `in`.\n let span: Span = citation.span\n let useFull = false\n const full = \"fullSpan\" in citation ? citation.fullSpan : undefined\n if (options.fullSpan === true && full !== undefined) {\n span = full\n useFull = true\n }\n\n const start = space === \"clean\" ? span.cleanStart : span.originalStart\n const end = space === \"clean\" ? span.cleanEnd : span.originalEnd\n\n if (start < 0 || end > source.length || start > end) {\n throw new Error(\n `toDurableLocator: span [${start}, ${end}) is out of range for source of length ${source.length} — wrong source text or space?`,\n )\n }\n\n const exact = source.slice(start, end)\n if (exact.length === 0) {\n throw new Error(\"toDurableLocator: empty exact quote — nothing to anchor\")\n }\n\n // matchedText is the original-text substring, so it only equals the slice on\n // the original-space core-span path. The clean path and the fullSpan path have\n // no stored equivalent to cross-check against.\n if (space === \"original\" && !useFull && exact !== citation.matchedText) {\n throw new Error(\n `toDurableLocator: sliced text \"${exact}\" does not equal citation.matchedText \"${citation.matchedText}\" — wrong source text or space?`,\n )\n }\n\n // Sentence-bounded, then clamped to contextLength. getSurroundingContext gives\n // the enclosing legal sentence (it knows \"F.3d\"/\"U.S.\" periods are not\n // boundaries); we slice raw windows from `source` within those bounds.\n const sentence = getSurroundingContext(source, { start, end })\n const sentStart = sentence.span.start\n const sentEnd = sentence.span.end\n const prefix = source.slice(Math.max(sentStart, start - contextLength), start)\n const suffix = source.slice(end, Math.min(sentEnd, end + contextLength))\n\n const occurrence = tokenBoundedIndexes(source, exact).indexOf(start)\n\n return {\n v: 1,\n space,\n quote: {\n exact,\n ...(prefix.length > 0 ? { prefix } : {}),\n ...(suffix.length > 0 ? { suffix } : {}),\n },\n position: { start, end },\n ...(occurrence >= 0 ? { occurrence } : {}),\n contentHash: contentHash(exact, prefix, suffix),\n }\n}\n\n/** Build durable locators for many citations sharing one `source` + options. */\nexport function toDurableLocators(\n citations: Citation[],\n source: string,\n options: DurableLocatorOptions = {},\n): DurableLocator[] {\n return citations.map((citation) => toDurableLocator(citation, source, options))\n}\n"],"mappings":"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"}