ai-answer-citations 0.1.0

Extract the cited sources from an AI answer: HTML anchors, numbered markers, Markdown links, footnote definitions and bare URLs, then canonicalise and tally them per host.
Documentation
# ai-answer-citations

Pull the cited sources out of an AI answer.

ChatGPT, Perplexity, Claude and Google AI Overviews all attach sources to the
answers they write, but they attach them differently: an HTML anchor with a
site-name chip, a numbered marker like `[3]` that links out, a Markdown link in
a copied answer, a footnote definition at the bottom, or a bare URL in plain
text. Anyone measuring *which pages AI answers cite* has to read all of those
forms before counting anything. This crate reads them.

```rust
use ai_answer_citations::{extract, canonical, tally, chatgpt_tagged};

let answer = r#"<p>Boil the kettle first.<a href="https://example.com/tea?utm_source=chatgpt.com">[1]</a>
Also see [the guide](https://guide.example.org/tea) and https://example.com/tea.</p>"#;

let cites = extract(answer);
assert_eq!(cites.len(), 3);
assert_eq!(cites[0].marker, Some(1));
assert!(chatgpt_tagged(&cites[0].url));
assert_eq!(canonical(&cites[0].url), "https://example.com/tea");

let counts = tally(&cites);
assert_eq!(counts[0].host, "example.com");
assert_eq!(counts[0].citations, 2);
```

## What it does

- `extract(text)` returns every absolute `http(s)` link in document order, tagged
  with the form it was found in (`HtmlAnchor`, `MarkdownLink`, `Footnote`,
  `BareUrl`), its visible label, and the numeric marker when the label is one
  (`1`, `[1]`, `[^1]`, `(1)`). A URL inside an anchor or Markdown link is
  reported once, in the richer form.
- `canonical(url)` lower-cases scheme and host, drops a default port and the
  fragment, and removes click-tracking query keys (`utm_*`, `fbclid`, `gclid`,
  `srsltid`, ...) so two citations of one page compare equal. Path case, the
  remaining query, and the trailing slash are kept: on a real server those can
  name different pages.
- `host(url)` gives the lower-cased host without port, userinfo or a leading
  `www.`.
- `chatgpt_tagged(url)` reports the `utm_source=chatgpt.com` tag ChatGPT appends
  to the links it cites.
- `tally(&citations)` counts citations per host; `distinct_pages(&citations)`
  lists canonical URLs in first-seen order.

## What it does not do

It does not fetch anything, resolve relative links (an answer copied out of a
chat has no base URL, and a relative link in one is almost never a citation),
or carry a public-suffix list. It is not an HTML parser: it reads `<a>` tags
and Markdown link syntax out of whatever string you hand it, which is what an
exported answer is.

No dependencies, no `unsafe`.

## Where it comes from

Written as the measurement side of [Seen in AI Answers](https://seeninaianswers.com/),
a page engine built around the question of what AI answers cite. The engine
shapes pages to be cited; this crate reads what an answer actually cited, so
the two can be compared once a citation sample exists.

## License

MIT OR Apache-2.0