Install
$ agentstack add skill-jslandau-legal-tools-cite-checking ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Cite-Checking
Prerequisites
This skill uses the citation taxonomy, short-form resolution rules, proposition-extraction rules, parenthetical handling, structured component schemas, flag vocabulary, and CourtListener API patterns defined in the citation-toolkit skill. Read citation-toolkit first; the stages below refer to its definitions by name rather than restating them.
When dispatching subtask subagents, follow the Model Tiers for Subtasks section of citation-toolkit: Stage 2 extraction and Stage 3 parsing are Haiku-tier; Stage 5 pincite extraction and Stage 6 support analysis are Sonnet-tier; the Stage 7 critic is Opus-tier by design.
Overview
This skill takes a legal document and produces a standalone cite-check report. For every citation in the document the report shows:
- Whether the cited source was located
- A précis of what the source actually says at the pincite
- A qualitative assessment of how well the source supports the proposition it is cited for
- A critic subagent's independent assessment of that analysis
The source document is never modified.
Before Starting
Ask the user:
- What is the document? (file path or paste content)
- What format is it in? PDF, plain text/Markdown, DOCX, or Word with tracked changes
- Where should the output be saved? Three sibling files are produced: a human-readable Markdown report (
-cite-check.md), a structured data file (-cite-check.json), and a self-contained interactive HTML report (-cite-check.html) that the user opens in a browser. Default location: same directory as the source document. - CourtListener access. If the
claude.ai CourtListenerMCP server is available, no token is needed — Stage 4 uses the MCP directly. Otherwise, ask for a CourtListener API token (free at courtlistener.com/sign-in/) and note it for the scripted-fallback path in Stage 4.
Supported Formats
- PDF: Use the Read tool. Use the document's own page numbering (footers/headers), NOT raw PDF page numbers.
- Plain text / Markdown: Look for explicit page markers (e.g.,
## PAGE 1). Ask the user to clarify if not obvious. - DOCX / Word with tracked changes: Extract text using the
docx_extract.pyscript in this skill's directory. It emits paragraph text with footnotes inlined as[FNx: ...]markers, accepts accepted/inserted text, and drops deleted text:
``bash python3 plugins/legal-tools/skills/cite-checking/docx_extract.py path/to/brief.docx # or write to a file: python3 plugins/legal-tools/skills/cite-checking/docx_extract.py path/to/brief.docx --output extracted.txt ``
Stdlib-only — no pip install required. Page boundaries in DOCX are approximate — warn the user.
Stage 1 — Document Parsing
Read the document. Identify the substantive pages to process:
- Find body sections. Look for headings: INTRODUCTION, ARGUMENT, STATEMENT OF THE CASE, STATEMENT OF FACTS, CONCLUSION. These are the pages to scan.
- Skip front matter. Exclude cover pages, certificates of interest, tables of contents, any existing Table of Authorities, roman-numeral paginated pages.
- Skip back matter. Exclude certificates of compliance, certificates of service, signature pages after CONCLUSION.
- Include footnotes on substantive pages — they often contain important citations.
- Exclude non-authority references. References to other briefs ("Blue Br.", "Appellant's Br."), appendix cites ("Appx123"), and record cites ("R. at 45") are not legal authorities — skip them.
Retain document structure for JSON emission. While walking the document, keep enough structure to populate the document block of the JSON output (Stage 8): substantive page numbers and their text, section headings and the page ranges they cover, and the footnote-id → footnote-text map (already produced by the DOCX script). For each citation captured in Stage 2, record its location as {page, section, footnote_id?, snippet} where snippet is the surrounding sentence (≈40–80 words centered on the citation) for use as in-context preview.
Page numbering rule for location.page. Use the brief's own page numbering — the number a reader sees in the document's footer/header. Not a 0-indexed offset, not an extraction-ordinal index, not a count of paragraphs. For PDFs, prefer the document's printed pagination over the raw PDF page index (cover pages, certificates of interest, TOCs, and tables of authority shift the offset, typically by several pages). For DOCX where the extraction script does not track pages directly, infer page boundaries from in-text page markers when present (e.g., ## PAGE 6), and always verify by spot-checking at least one emitted location.page value against the source brief before emitting any of them — an off-by-one in page numbering propagates through every citation and silently breaks every downstream tool that uses page references. When unable to verify, ask the user to confirm one or two pages and calibrate the rest, rather than guessing.
Stage 2 — Citation Extraction
Extraction is a two-pass process: eyecite first, then a focused human pass for the gap categories. Do not LLM-scan the document for citations — that is what eyecite is for, and re-doing its work wastes tokens and introduces errors.
Pass 1 — eyecite (authoritative for recognized types). Follow the "Extraction: eyecite is the primitive (local only)" section of citation-toolkit. Run the local eyecite_extract.py script in citation-toolkit/ — extraction stays on-machine because briefs are routinely privileged. Do NOT use the MCP's extract_citations or analyze_citations for this (they upload the document text). The script's output is a JSON array of citations in document order with Id./supra/short cites already linked to their antecedents — that is your citation stack for Stage 3, no manual re-derivation needed.
Pass 2 — gap pass. Walk the substantive text once looking only for the gap categories listed in citation-toolkit (administrative decisions, EU/international cases, popular-name statutes, informal constitutional references, state constitutional provisions, statute subsection breakdowns). Add these to the array produced by Pass 1. Do not re-extract anything eyecite already found.
Pass 3 — proposition extraction. For every citation (eyecite-extracted and gap-pass), capture the assertive clause it supports. eyecite returns the citation strings and their span offsets, not the propositional context — that is on you. Follow the Proposition Extraction rules in citation-toolkit (specific-assertion vs paragraph scope, mid-sentence and footnote handling, string-cite sharing, short-form propositions, ambiguous_proposition flag). Use the spans from Pass 1 to locate each citation in the source text precisely.
Apply the Parenthetical Handling rules from citation-toolkit when deciding whether a parenthetical like (quoting X) or (citing Y) creates an independent citation entry.
Maintain a citation stack only for gap-category cites and for any eyecite short forms flagged unresolved_short_form — eyecite already maintains the stack for everything else.
Stage 3 — Citation Resolution
For eyecite-extracted citations (Pass 1 of Stage 2), structured-component parsing is already done — the JSON entries land in toolkit-schema shape. The only work here is the gap-category cites from Pass 2 and any field cleanup eyecite couldn't do.
- Gap-category cites: parse into the Structured Component Schemas in
citation-toolkit(Cases, Statutes, Federal Regulations, Federal Rules, Secondary Sources, Constitutional Provisions, Legislative Materials) by hand. This is Haiku-tier mechanical work. - Statute subsection cleanup: eyecite's
subsectionfield is unreliable for forms like47 U.S.C. § 230(c)(2). After Pass 1, re-read the source text for any statute entry wheresubsectionis null and a subsection appears in the brief. Either fill it in or flagambiguous_section_reference. - Unresolved short forms: eyecite already flagged these. Try one more pass against the running stack; if still unresolved, leave the
unresolved_short_formflag in place for user review.
Attach any remaining flags from the Flag Vocabulary in citation-toolkit (informal_reference, ambiguous_proposition, uncertain_category, citing_parenthetical; subsequent_negative_history comes later in Stage 6).
Stage 4 — Source Lookup
For each resolved citation, attempt to locate the full source text using the priority-ordered lookup list for its type.
Escalation chain: Try all sources in order → if all fail, ask the user (provide the citation and the sources already tried; the user may know a direct URL or have access to a subscription service) → if the user cannot help, mark as unverifiable.
Lookup Lists by Citation Type
Cases (US): Use the CourtListener API section of citation-toolkit. Stage 1 there (citation resolution) is the components-only call_endpoint("citation-lookup", ...) call — privilege-safe, sends only the citation's volume/reporter/page, not the brief. Loop over the unique citations from your Pass-1 eyecite output and call once per cite. Each successful lookup returns a Cluster ID; resolve cluster → opinion ID via call_endpoint("clusters", {"id": }), walk sub_opinions, prefer 020lead, then fetch text with get_endpoint_item("opinions", , fields=[...]). Do not pass the cluster ID directly to get_endpoint_item("opinions", ...) — it will silently return the wrong opinion. Do not call analyze_citations — it uploads document text. See citation-toolkit's "Step 1/2 (MCP)" section for the full pattern and post-fetch sanity check. If the MCP is unavailable entirely, the equivalent curl-based REST calls (same components-only shape) are documented in the same section. The full escalation chain (CourtListener → Justia → direct court sites → Google Scholar → ask user → mark unverifiable) lives in citation-toolkit.
Opinion fetch. Default to fields=["id", "html_with_citations", "plain_text"]. Prefer html_with_citations (consolidated text with star-pagination markers and inline citation anchors); fall back to plain_text only when HTML is empty. Sub-opinion selection within a cluster follows citation-toolkit's "Choosing the right sub-opinion" rule: prefer 020lead unless the brief explicitly cites a dissent or concurrence.
Upfront pagination check. Immediately after fetching each opinion, run citation-toolkit's pagination-mode detection (single regex pass over html_with_citations) and record pagination_mode as one of "reporter", "slip_only", or "none". This determines which match-ladder tier Stage 5 starts at and is consumed by Stage 6's confidence assessment.
Batching tip: Local eyecite already gave you the deduped list of unique case citations from the brief. Loop over that list and issue one components-only call_endpoint("citation-lookup", ...) per unique cite at the start of Stage 4 — rate-limited to 60 valid citations/minute, so pace accordingly. Then fetch only the opinion texts whose pincite content you actually need for Stage 5. (Do NOT use analyze_citations for batching — it uploads document text.)
Cases (EU / international):
- EUR-Lex (eur-lex.europa.eu) — Court of Justice of the EU; search by ECLI or case name
- HUDOC (hudoc.echr.coe.int) — European Court of Human Rights; search by application number or party names
Federal statutes (U.S.C.): Resolve via lii_fetcher.py in the citation-toolkit skill — see citation-toolkit's "LII source resolution" section for the full pattern. Build a single JSON array of all statute/regulation entries from Pass 1 + Pass 2 of Stage 2 and invoke the script once per run:
python3 plugins/legal-tools/skills/citation-toolkit/lii_fetcher.py --input lii-requests.json > lii-results.json
Each result carries status, section_text, subsection_text (when a subsection was requested and matched), and an LII URL. Set source.fetch_path to "lii" on the citation entry. On status == "not_found", fall through to escalation: ask the user for a direct URL or alternate source; if they can't help, mark unverifiable. On status == "anchor_not_found", treat it as a soft failure — the section is present on LII but the cited subsection wasn't tagged; ask the user to confirm the subsection text before assessing support (this is intentionally a false negative; see citation-toolkit's "No fallback" note).
Federal regulations (C.F.R.): Same path as statutes — use lii_fetcher.py with type: "regulation". LII's CFR coverage is patchier than its U.S.C. coverage (some sections are [RESERVED], some have content but no subsection anchors); rely on the subsection_anchor_not_found and source_not_found flags rather than guessing. eCFR (ecfr.gov), GovInfo (govinfo.gov), and Federal Register (federalregister.gov) remain available as user-supplied fallbacks when LII comes back empty.
Federal rules:
- Cornell LII (law.cornell.edu) — covers FRCP, FRAP, FRE, and other federal rules. Not yet wired into
lii_fetcher.py; navigate to title and section directly until the rules type is folded in. - Direct court websites — Supreme Court Rules at supremecourt.gov
Constitutional provisions: Text is resolved directly from the provision citation — no external lookup required for text. The text of U.S. constitutional provisions is settled; use the standard text.
Law review articles:
- SSRN (ssrn.com) — search by title and author; many articles are freely posted
- Direct law review website — derive from journal name (e.g., "Harvard Law Review" → harvardlawreview.org; "Yale Law Journal" → yalelawjournal.org); look for article by title/volume/page
- Google Scholar — search by title and author
Treatises and books:
- Google Scholar (scholar.google.com, Books tab) — search by title and author
- Google Books (books.google.com) — may have partial preview with pincite pages visible
Legislative materials:
- Congress.gov — search by report number, bill number, or hearing title
- GovInfo (govinfo.gov) — Congressional Record, committee prints, hearings
Web sources / reports:
- If a URL is present in the citation: fetch it directly
- If no URL: search by organization name and document title
EU secondary materials:
- EUR-Lex (eur-lex.europa.eu) — official EU law and secondary sources
- Ask user
Verification
Once a source is located, verify identity before using it:
- Cases: follow the identity-verification rules in
citation-toolkit's CourtListener section (volume, reporter, starting page, party names). - Statutes: Confirm title, code, and section match. Note if the version found is current vs. the year cited.
- Federal rules: Confirm the rule set (e.g., Fed. R. Civ. P.) and rule number match. If you found Rule 56 but the citation was to Rule 12, that is a different rule — do not use it.
- Secondary sources: Confirm author, title, volume, and starting page match.
If identity cannot be confirmed, treat as unverifiable and escalate.
Stage 5 — Pincite Extraction
Once the source is located and identity confirmed, retrieve the text at the pincite.
Cases
Apply the match ladder for pincite extraction defined in citation-toolkit (four tiers: direct phrase match → parenthetical semantic match → pincite-page semantic match → whole-opinion semantic search). Start at Tier 1 and fall through until you get a confident match. Record match_tier_used: on the citation entry — Stage 6 reads this to set confidence.
The starting tier depends on what the brief actually contains, not on the pincite alone:
- Brief contains a direct quote from the source → start at Tier 1.
- Brief uses an explanatory parenthetical (
(holding X),(reasoning X), `(not
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: jslandau
- Source: jslandau/legal-tools
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.