# Cite Checking

> Use when verifying citations in a legal brief or document — extracts every citation locally via eyecite (document text never leaves the machine, important for privileged work product), looks up each source online via CourtListener and other public sources, extracts the pincite text, and produces a standalone report assessing how well each citation supports the proposition it is cited for, with a…

- **Type:** Skill
- **Install:** `agentstack add skill-jslandau-legal-tools-cite-checking`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [jslandau](https://agentstack.voostack.com/s/jslandau)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [jslandau](https://github.com/jslandau)
- **Source:** https://github.com/jslandau/legal-tools/tree/main/plugins/legal-tools/skills/cite-checking

## Install

```sh
agentstack add skill-jslandau-legal-tools-cite-checking
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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:
1. **What is the document?** (file path or paste content)
2. **What format is it in?** PDF, plain text/Markdown, DOCX, or Word with tracked changes
3. **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.
4. **CourtListener access.** If the `claude.ai CourtListener` MCP 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.py` script 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:

1. **Find body sections.** Look for headings: INTRODUCTION, ARGUMENT, STATEMENT OF THE CASE, STATEMENT OF FACTS, CONCLUSION. These are the pages to scan.
2. **Skip front matter.** Exclude cover pages, certificates of interest, tables of contents, any existing Table of Authorities, roman-numeral paginated pages.
3. **Skip back matter.** Exclude certificates of compliance, certificates of service, signature pages after CONCLUSION.
4. **Include footnotes** on substantive pages — they often contain important citations.
5. **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 `subsection` field is unreliable for forms like `47 U.S.C. § 230(c)(2)`. After Pass 1, re-read the source text for any statute entry where `subsection` is null and a subsection appears in the brief. Either fill it in or flag `ambiguous_section_reference`.
- **Unresolved short forms:** eyecite already flagged these. Try one more pass against the running stack; if still unresolved, leave the `unresolved_short_form` flag 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):**
1. EUR-Lex (eur-lex.europa.eu) — Court of Justice of the EU; search by ECLI or case name
2. 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:

```bash
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:**
1. 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.
2. 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:**
1. SSRN (ssrn.com) — search by title and author; many articles are freely posted
2. 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
3. Google Scholar — search by title and author

**Treatises and books:**
1. Google Scholar (scholar.google.com, Books tab) — search by title and author
2. Google Books (books.google.com) — may have partial preview with pincite pages visible

**Legislative materials:**
1. Congress.gov — search by report number, bill number, or hearing title
2. 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:**
1. EUR-Lex (eur-lex.europa.eu) — official EU law and secondary sources
2. 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](https://github.com/jslandau)
- **Source:** [jslandau/legal-tools](https://github.com/jslandau/legal-tools)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-jslandau-legal-tools-cite-checking
- Seller: https://agentstack.voostack.com/s/jslandau
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
