| name | annotations-refresh |
| description | Drain the annotation-candidates queue (from `npm run candidates:annotations`) into shared/data/annotations/curated-annotations.ts. Use weekly during active event periods, monthly otherwise. |
| user_invocable | true |
Annotations Refresh
Editorial sweep of chart-annotation candidates. The producer
(scripts/maintenance/build-annotation-candidates.ts) writes machine-found
events to agents/annotation-candidates.md. This skill reads that queue and
turns each row into a promote / drop / defer decision, updating
shared/data/annotations/curated-annotations.ts for promoted rows.
Promotion is always editorial. Do not auto-write annotations from the queue.
When to run
- Weekly during high-event periods (post-depeg, post-regulatory action,
post-launch burst).
- Monthly otherwise.
- Whenever
npm run digest:curation flags WARN: on annotation queue
health (queue > 30 rows or oldest candidate > 60 days old).
Inputs
agents/annotation-candidates.md — append-only queue. Each row already
carries a date, coin id, kind hint, severity hint, and a source pointer.
The file footer <!-- last_swept_at: YYYY-MM-DD --> records the previous
sweep date.
shared/data/annotations/curated-annotations.ts — current curated set.
Header comments enumerate the curation rules and severity vocabulary.
shared/types/chart-annotation.ts — CHART_ANNOTATION_KINDS and the
ChartAnnotation shape (ts, kind, label ≤80 chars, severity?, href?).
Process
Step 1 — Read the queue and the current annotations file
- Read
agents/annotation-candidates.md from top to bottom. Group rows by
date header so the older candidates surface first.
- Read
shared/data/annotations/curated-annotations.ts. For each coin
referenced in the queue, note the existing annotations so duplicates
can be detected.
- Read
shared/types/chart-annotation.ts to confirm the current
CHART_ANNOTATION_KINDS enum — do not rely on this document's list,
the source file wins.
Step 2 — Per-row decision
For each candidate, choose one of three actions:
Promote
Add an entry to CURATED_ANNOTATIONS[<coin-id>] in
shared/data/annotations/curated-annotations.ts. Required when:
- The event is a discrete, named occurrence (regulatory action, depeg
pivot, launch, governance vote, methodology pivot, blacklist spike).
- A primary source exists (issuer post-mortem, regulator filing,
methodology changelog, on-chain transaction). Secondary press is OK as
fallback but prefer primary.
- The event is not already represented (check existing entries for the
same coin within ±2 days of the candidate
ts).
Entry shape (mirror existing file rules):
{
ts: Date.UTC(YYYY, monthIdx, day),
kind: "depeg" | "regulatory" | "governance" | "mint-burn-spike" | "blacklist-surge" | "methodology-change" | "exploit",
label: string,
severity?: "low" | "med" | "high",
href?: string,
}
Place each new entry inside the per-coin array in ts ascending order.
Drop
Add a dropped: <one-line reason> annotation next to the row in the
queue. Do not delete the row in place — instead, when finishing the
sweep, strip dropped rows wholesale as part of the queue rewrite (Step 4).
Common drop reasons:
- Below severity bar (depeg low ≥ $0.98 with no grade impact).
- Already curated under a different
ts within the same incident.
- Press cycle without a discrete event (e.g. opinion piece, recap).
- Insufficient source — only social-media chatter.
- Promotional / launch announcement without a confirmed live transition.
Defer
Mark the row with defer: <reason> and leave it in the queue. Use when
the event is real but the source set is incomplete (e.g. a regulator
filing is rumored but not yet public). Re-evaluate next sweep.
Step 3 — Apply the edits
- Edit
shared/data/annotations/curated-annotations.ts with the
promoted rows. Use the existing entry style verbatim: inline
// YYYY-MM-DD — short note comment above each ts: line.
- Sort each coin's array by
ts ascending if the new entry breaks the
order.
- Keep the labels ≤80 chars. If a long label is required, prefer a
shorter
label and put the long phrasing in the source-page title at
the href end.
Step 4 — Update the queue
After all per-row decisions, rewrite agents/annotation-candidates.md:
- Remove promoted rows (they are now in the curated file).
- Remove dropped rows.
- Keep deferred rows, with the
defer: annotation visible.
- Update the footer to today:
<!-- last_swept_at: YYYY-MM-DD -->.
- Prepend a short header noting the sweep (e.g.
<!-- swept 2026-05-23: 12 promoted, 8 dropped, 3 deferred -->).
Step 5 — Validate
Run the local gates before declaring done:
npm run check:stablecoin-data
npm test -- curated-annotations
The annotations test enforces: kind enum, ≤80 char labels, plausible
timestamps, and the top-4 coverage gate. If the test fails, fix the entry
(usually a label that crept over 80 chars or a kind typo) and re-run.
Output
Report at the end of the sweep:
- Promoted count, grouped by coin (e.g.
usdc-circle: +1,
usdt-tether: +2).
- Dropped count with a one-line digest of common reasons.
- Deferred count and what each is waiting on.
- Queue health after sweep: rows remaining, oldest deferred row date.
What NOT to do
- Do not invent dates. If the queue row's
ts is fuzzy, defer rather
than guess.
- Do not promote a row whose only source is the producer script's own
signal (the producer surfaces the candidate, but a primary source —
issuer post-mortem, regulator filing, methodology changelog, on-chain
proof — must exist before the event becomes a curated annotation).
- Do not collapse multi-day depegs into a single entry; one annotation
per discrete event, per the file header rules.
- Do not edit the producer script's queue layout. The producer rewrites
the file each run; structural edits will be lost.
- Do not promote launch candidates appended by the orchestrator unless
the coin already has live runtime coverage (the launch annotation is
only meaningful once the chart actually renders data for that date).