AgentStack
SKILL verified MIT Self-run

Fact Drift

skill-hwajongpark-fact-drift-fact-drift · by hwajongpark

A Claude Code skill that checks whether facts cited from external pages (prices, versions, limits, rules, deadlines) still match both the upstream source and local content. Reads a JSON config of targets, fetches each source URL via WebFetch subagents, extracts a value with an exact source quote, compares against a committed baseline, verifies the configured citation files, writes a snapshot repo…

No reviews yet
0 installs
8 views
0.0% view→install

Install

$ agentstack add skill-hwajongpark-fact-drift-fact-drift

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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 No
  • 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.

Are you the author of Fact Drift? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

fact-drift

Verifies that facts cited in your content still match the source page and that your local citation still matches the remembered source value. Catches silent upstream changes and local content drift.

This skill is the cheap path: it uses parallel subagents calling WebFetch on the Claude Code subscription. It does not spawn a browser runner or any API-billed tool. Reserve a real browser for targets that require JavaScript rendering or login.

Fail-closed invariants

  • OK means two checks passed: the upstream source still matches the baseline, and the configured local citation scope still contains that value.
  • ERROR and NOT_FOUND are never acceptable baseline values. Do not write or refresh a baseline from them.
  • A baseline is evidence memory, not runtime scratch. Tell the user to commit fact-drift-baseline/ or restore it in CI.
  • Never replace every raw occurrence of a short value like 60. Scope changes to configured citation files/globs and show line context before editing.
  • Do not infer a value from examples, snippets, unrelated pages, or model memory. The extracted value must come from the fetched source page and include an exact quote.

Inputs

  • Config: rules.config.json in your project root. A list of targets, each with id, name, url, extract (a natural-language extraction instruction), citation scope, and notes. Citation scope should be citation_files or citation_globs; doc_slug is accepted only as a legacy hint. See examples/rules.config.example.json.
  • Baselines: fact-drift-baseline/{target_id}.json. The last captured value per target. Created by the first --update-baseline run, then committed or restored in CI.

Recommended target shape:

{
  "id": "node-latest-stable",
  "name": "Node.js latest stable release",
  "url": "https://nodejs.org/en",
  "extract": "Return the latest stable Node.js version. Return only the version string.",
  "citation_files": ["docs/install.md"],
  "notes": "Install guide references the current Node version."
}

Baseline shape:

{
  "value": "24.9.0",
  "source_url": "https://nodejs.org/en",
  "captured_at": "2026-05-28",
  "quote": "Latest Current Version: 24.9.0",
  "provenance": "Release banner on the Node.js home page"
}

Outputs

  • Report: fact-drift-snapshots/snapshot-YYYY-MM-DD.md. A status table plus drift detail.
  • Updated baselines: only when --update-baseline is passed with readable source quotes, or after an approved apply.
  • Edited content files: only after you approve an apply step. The skill never writes to your content without confirmation.

Modes

/fact-drift (default)

Diff mode. For each target: fetch, extract, compare to baseline, verify local citations, classify as OK / DRIFT / NEW / ERROR / CONTENT_MISMATCH. Write the snapshot report. If anything drifted, offer to apply the fixes.

/fact-drift --ci

CI mode. Same checks as default, but never apply edits and treat any NEW, DRIFT, ERROR, or CONTENT_MISMATCH as failure. Print the failing statuses and the snapshot path.

/fact-drift --apply

Diff, then fix. For each DRIFT, find scoped files that still contain the old value, show the planned edits, and after you confirm, update approved matches and refresh the baseline. Never edits without showing the plan first.

/fact-drift --update-baseline

Capture mode. Fetch every target and write baselines only for FOUND results. If an existing baseline differs, show the old value, new value, and exact quote, then ask before replacing it. Never overwrite a baseline with NOT_FOUND or ERROR.

/fact-drift --target

Single-target mode. Same as default, restricted to one id.

Execution flow

  1. Resolve today's date (date +%Y-%m-%d).
  2. Read rules.config.json. Filter to --target if provided.
  3. Confirm fact-drift-baseline/ exists; create it if not.
  4. Spawn one subagent per target in parallel (single message, multiple Agent calls, subagent_type: general-purpose). Each subagent receives the target's url and extract instruction, plus the subagent rules below.
  5. Aggregate subagent results. If a result is invalid JSON or lacks required fields, classify it as ERROR.
  6. For each result:
  • If result status is ERROR or NOT_FOUND: classify ERROR immediately; do not compare or write a baseline.
  • If --update-baseline and result status is FOUND:
  • No existing baseline: write { value, source_url, captured_at, quote, provenance }.
  • Existing baseline differs: show the diff and ask before replacing.
  • Else read the existing baseline and classify source state:
  • No baseline file -> NEW
  • Baseline value differs -> DRIFT
  • Baseline value matches -> OK_SOURCE
  • Verify local content for every OK_SOURCE and DRIFT:
  • Resolve citation scope from citation_files first, then citation_globs, then legacy doc_slug.
  • Search only that scope for the relevant value using rg -n --fixed-strings.
  • If source state is OK_SOURCE and no scoped citation contains the current value, classify CONTENT_MISMATCH.
  • If source state is DRIFT, record scoped matches for the old value and for the new value. If no stale match exists but the new value is present, report "content already updated; baseline needs refresh" instead of raw global replacement.
  • Only after content verification may OK_SOURCE become OK.
  1. Write the snapshot report:
# fact-drift snapshot, {today}

| Target | Status | Current | Baseline | Source |
| ------ | ------ | ------- | -------- | ------ |
| {id}   | OK               | 60      | 60       | [source]({url}) |
| {id}   | DRIFT            | 80      | 60       | [source]({url}) |
| {id}   | CONTENT_MISMATCH | 60      | 60       | [source]({url}) |

## Drift detail

### {id}
- Notes: {target.notes}
- Citation scope: {target.citation_files or target.citation_globs or target.doc_slug}
- Diff: was 60, now 80
- Source quote: "{quote}"
- Recommended action: run `--apply` to update approved stale citations.
  1. Apply (on --apply, or when the user confirms after a default run). For each DRIFT:
  • Search the configured citation scope for files containing the old value. Use rg -n --fixed-strings.
  • Show the user every match with file and line context, and the proposed change (old -> new).
  • Ask for confirmation. Let the user deselect any match: a raw value like 60 can appear in unrelated places, and only the user knows which should change.
  • On confirmation, edit each approved match, then refresh that target's baseline to the new value including quote and provenance.
  • Report which files changed. Do not git commit.
  1. Print a one-screen summary: X of Y targets OK, any DRIFT / NEW / ERROR / CONTENT_MISMATCH items with the doc each affects, what was edited if anything, and the path to the full snapshot. In --ci, state that the check fails if any non-OK status exists.

Subagent prompt template

Use this as the body of each parallel Agent call. Substitute {url} and {extract} from the target's config.

> You are checking a single source page. Use WebFetch to retrieve {url}. Read the returned content carefully and answer this question: > > {extract} > > Return ONLY minified JSON in this schema: > {"status":"FOUND","value":"","quote":"","provenance":""} > > If you cannot find it after reading the page in full, return: > {"status":"NOT_FOUND","value":null,"quote":null,"provenance":""} > > If the page cannot be read, is login-walled, is JavaScript shell content, or returns an error, return: > {"status":"ERROR","value":null,"quote":null,"provenance":""} > > Do not call any other tool. Do not search the web. Do not use memory or examples. Do not summarize the rest of the page. The quote is required for FOUND and must contain the returned value.

What this skill does not do

  • It does not edit anything without showing you the plan and getting a yes. You can deselect any individual change.
  • It does not handle login-walled or JS-rendered pages. If WebFetch returns boilerplate ("Enable JavaScript to view this page"), report ERROR with that message.
  • It does not commit anything. It writes the report and baseline files only.
  • It does not certify facts without a readable source quote and matching local citation.

When the cheap path stops being enough

If a target consistently returns ERROR: requires-JS, the page genuinely needs a real browser. Options:

  1. Find a static or printable version of the same data.
  2. Build a one-off browser script for just that target, accepting the API cost.
  3. Drop the target if the data is not worth the cost.

Document the decision in the target's notes field.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

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

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.