Install
$ agentstack add skill-hwajongpark-fact-drift-fact-drift ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
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
OKmeans two checks passed: the upstream source still matches the baseline, and the configured local citation scope still contains that value.ERRORandNOT_FOUNDare 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.jsonin your project root. A list of targets, each withid,name,url,extract(a natural-language extraction instruction), citation scope, andnotes. Citation scope should becitation_filesorcitation_globs;doc_slugis accepted only as a legacy hint. Seeexamples/rules.config.example.json. - Baselines:
fact-drift-baseline/{target_id}.json. The last captured value per target. Created by the first--update-baselinerun, 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-baselineis 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
- Resolve today's date (
date +%Y-%m-%d). - Read
rules.config.json. Filter to--targetif provided. - Confirm
fact-drift-baseline/exists; create it if not. - Spawn one subagent per target in parallel (single message, multiple Agent calls,
subagent_type: general-purpose). Each subagent receives the target'surlandextractinstruction, plus the subagent rules below. - Aggregate subagent results. If a result is invalid JSON or lacks required fields, classify it as
ERROR. - For each result:
- If result status is
ERRORorNOT_FOUND: classifyERRORimmediately; do not compare or write a baseline. - If
--update-baselineand result status isFOUND: - 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_SOURCEandDRIFT: - Resolve citation scope from
citation_filesfirst, thencitation_globs, then legacydoc_slug. - Search only that scope for the relevant value using
rg -n --fixed-strings. - If source state is
OK_SOURCEand no scoped citation contains the current value, classifyCONTENT_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_SOURCEbecomeOK.
- 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.
- 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
60can 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
quoteandprovenance. - Report which files changed. Do not git commit.
- 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:
- Find a static or printable version of the same data.
- Build a one-off browser script for just that target, accepting the API cost.
- 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.
- Author: hwajongpark
- Source: hwajongpark/fact-drift
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.