hoo-citation-tracker · git:20260719.c1bb42b · 2026-07-19 · sha256 5569372d28937e57

hoo-citation-tracker git:20260719.c1bb42bA

Immutable. This exact content is served forever at /api/v1/blob/5569372d28937e57.

---
name: hoo-citation-tracker
description: Use to measure AI answer-engine visibility - "are we cited by ChatGPT/Perplexity", "AI share of voice", /organic-os:citations, or the weekly routine's citation step.
---

# AI citation tracking

1. Read profile: keywords.targets (the query set), competitors, site url,
   and the optional `citations: {engines: [...]}` key (see Engine set
   below).
2. For each query (cap 20 per run; rotate through the set across runs),
   ask each engine in the engine set that is actually reachable from this
   session. Sources in order of preference: an authorized AI-search
   connector or WebSearch with engine-targeted queries; plain WebSearch
   otherwise. Never scrape engines through automation that violates their
   ToS.
3. Record per query: engine | our domain mentioned? | cited (linked)? |
   position (only when cited - the ordinal below) | sentiment (only when
   mentioned - the label plus its evidence quote, below) | competitors
   mentioned | answer summary (<=30 words).
4. Metrics: mention rate, citation rate, share of voice vs competitors
   (mentions of us / mentions of anyone tracked).
5. Persist to runs/YYYYMMDD-citations/ + append one signal line with the
   headline movement vs the previous run (diff the last runs/ folder).
6. New citation appearing or disappearing on a money query -> P1 signal.
7. When `runs/<date>-ai-baseline/REPORT.md` exists in the brain (written by
   `/organic-os:setup`'s AI-visibility baseline step), compare this run's
   headline numbers against it and report movement, not just this run's
   absolutes - see Baseline comparison below for what movement now covers.

## Position within the answer

When the brand IS cited, record where it sits in the answer. One of four
ordinal values, best to worst:

- `lead-answer` - the answer's opening or primary recommendation is
  built on the brand; a reader who stops after the first paragraph has
  seen it.
- `supporting-mention` - the brand is named with substance in the body
  of the answer, as one of the inputs the answer actually uses.
- `listed-among-others` - the brand appears only inside a list or
  comparison of alternatives, with nothing said about it specifically.
- `footnote-link` - the brand appears only as a cited source link; the
  answer text itself never names it.

An uncited mention gets no position value - position describes where a
citation sits, and recording one without a citation would overstate the
result.

## Sentiment of the mention

When the brand is mentioned, judge the sentiment of that mention from the
answer text: `positive`, `neutral`, or `mixed-negative`. Every sentiment
label MUST carry the exact quoted phrase from the answer that justifies
it - never a bare label without the quote. The quote is the evidence and
the check: anyone reading the report can re-judge the call from the same
words. If no phrase in the answer supports a judgment either way, the
label is `neutral` with the brand's surrounding sentence as the quote.

## Baseline comparison and movement

The per-query table in this run's REPORT.md carries the position and
sentiment columns from step 3. The baseline REPORT.md format extends the
same way, additively: new baselines written from now on include both
columns; old baselines without them compare on presence only, and the
movement section states that plainly ("baseline predates
position/sentiment fields - movement reported on presence only").

Movement reporting covers three layers, deepest available first:

- Presence: cited or not, mentioned or not, vs the baseline or the
  previous run.
- Position: when both runs have position values, report the shift by
  name - "cited, and moved from listed-among-others to lead answer".
- Sentiment: when both runs have sentiment values and the label changed,
  report it with the new evidence quote attached - "sentiment shifted
  from neutral to mixed-negative; quote attached".

## Honesty rules

- This method samples, it does not measure. A run covers the queries it
  checked on the engines it could reach, nothing more - never let a
  report imply broader coverage than that.
- Every REPORT.md lists which engines were actually reachable from the
  session that ran it, and which configured engines were not.
- Sentiment is a judgment call and is labeled as such in the report; the
  recorded quote is the check on that judgment.
- Position and sentiment are recorded only from answer text actually
  read this run - never inferred, never carried forward from a previous
  run.

## Engine set (BYO, no tiers)

The default engine set, canonical here (see docs/INFORMATION-MAP.md in
the repo): ChatGPT, Perplexity, Google AI Overviews, Gemini, Microsoft
Copilot.

The set is profile-configurable via the additive
`citations: {engines: [...]}` key in site-profile.yaml (absence means
the default set; `schema_version` stays 1 - documented in
site-repo-contract.md). Each listed engine is checked only if it is
reachable from the session running the tracker - an unreachable engine
is listed as unreachable in the report, never silently skipped and never
guessed at. Users add engines the same way: one more list entry, checked
through the same connector-or-WebSearch path under the same ToS rule.
No tiers, no per-engine pricing - commercial AI-visibility tools sell
extra engines, per-answer position, and sentiment as paid add-on tiers;
here they are the same skill reading the user's own reachable surfaces,
inside ADR-0006's no-scraping rule (docs/adr/0006 in the repo).