AI Provenance Tracker
A VS Code extension that tracks — per character — whether code was human-written, AI-written, or AI-modified (AI code a human has since edited). Think of it as Track Changes for the AI era: write code normally, then reveal who wrote what.
How it works
Tracking runs silently in the background on every edit. Nothing is highlighted while you write. Right-clicking a file (editor, tab, or Explorer) offers two views:
- Show Active Provenance Highlighting — toggles live coloring of the file you're editing: 🟩 green = human, 🟥 red = AI-written, 🟦 blue/purple = AI-modified, 🟧 orange = pasted (origin unknown). Same menu hides it again.
- Show Provenance — opens a diff-style popup: the latest saved state, fully colored, beside the current file. A history dropdown button in the diff's title bar switches between all previous versions.
Hovering any highlight shows what it is and when it was written (e.g. "AI-written · 8/7/2026, 4:24 PM"). Provenance: Show File Summary (command palette) shows the percentage breakdown.
Persistence: the journal
Provenance is persisted to a plain, human-readable file in your workspace: .vscode/ai-provenance.jsonl. On every file save, one JSON record is appended containing a timestamp, the file's workspace-relative path, a fingerprint of its content, and the full span list. The journal is append-only — records are never edited or removed, so the file is a complete timestamped history of every saved state.
Each record also carries the full file text at snapshot time (enabling history playback) and prev: the SHA-256 hash of the previous line's exact text, forming a cryptographic hash chain. Editing or deleting any past record breaks the chain for everything after it. Provenance: Verify Journal Integrity (command palette) checks the whole journal — chain links, sequence numbers, span coverage, and text-vs-hash consistency — and reports exactly which record fails, if any. Commit the journal to git and push — the remote history then anchors the chain in time, which is what makes it useful as evidence (e.g. teachers verifying student work). Note this provides tamper-evidence, not tamper-proofing.
Time travel
Right-click a file (in the Explorer, editor, or editor tab) → View Provenance History. A picker lists every saved state with its timestamp and human/AI/AI-modified percentage badges. Picking one opens a diff-style popup, like reviewing a merge request: the snapshot on the left — read-only, with full provenance coloring — and the current file on the right, so you see both who wrote what and what changed since.
Because everything is rendered from the journal, the journal itself is the shareable artifact: commit .vscode/ai-provenance.jsonl, push, and anyone who clones the repo with this extension installed can browse and verify the complete history — no export step. The journal reloads automatically when it changes externally (git pull, branch switches).
The journal is mirrored to a backup outside the workspace (VS Code's global storage). Deleting either copy — accidentally or otherwise — is self-healing: on the next load or save, the surviving copy rebuilds the missing one, chain intact. To genuinely start over, use Provenance: Reset Journal, which deletes both copies after confirmation. (For a student, this also means quietly deleting the journal file doesn't erase the history; combined with pushed git commits, the record is hard to lose or to scrub.)
Committing the history
.vscode/ is gitignored in many repos, and git can't re-include a file under an ignored directory — so instead of editing your .gitignore, right-click any file or folder in the Explorer → Export Provenance Journal. This writes a visible copy to ai-provenance.jsonl at the workspace root, ready to commit. Re-run it whenever you want the committed copy refreshed. The export is also a source: cloning a repo that contains only the exported file restores the full history (and verification) for anyone with the extension.
On open, a file's tracker is restored from its latest journal snapshot only if the current text matches the recorded fingerprint; if the file changed outside the editor (git checkout, external edit), provenance resets to human rather than showing misaligned colors. Without a workspace folder open, tracking is in-memory only.
AI detection
There is no public "suggestion accepted" API from Copilot and friends, so detection is heuristic: humans type one character per change event, while accepting a completion inserts a multi-character burst in a single change. Bursts in a visible editor are marked AI unless they're a known non-AI source:
- Paste — the inserted text matches the clipboard. Pasted code gets its own 🟧 pasted state rather than a free pass as human: it came from outside the editor, and nobody can tell whether the clipboard held your code or a chatbot's. Honest uncertainty beats a wrong answer.
- Undo/redo — flagged by VS Code's change reason
- Enter + auto-indent / auto-closing brackets — whitespace-only or below the length floor (
aiProvenance.minCompletionLength, default 6)
- IntelliSense word completion — single identifiers are only flagged when implausibly long
- Invisible documents — bursts in files no editor is showing are refactors/formatters/bulk code actions, classified human
Manual overrides
You are the final authority: Provenance: Mark Selection as AI-Written and Provenance: Mark Selection as Human-Written (command palette) reclassify any selection. Manually marked spans show "marked by user" in their hover and are kept distinct from automatic classifications.
Agent ground truth (Claude Code hook)
Coding agents that edit files from outside the editor can report their edits precisely — no heuristics. Add this hook to your project's .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "node .vscode/ai-provenance-hook.js" }]
}
]
}
}
And save this as .vscode/ai-provenance-hook.js:
let input = '';
process.stdin.on('data', (d) => (input += d));
process.stdin.on('end', () => {
try {
const j = JSON.parse(input);
const file = j.tool_input?.file_path;
const text = j.tool_input?.new_string ?? j.tool_input?.content;
if (!file || !text) return;
require('fs').appendFileSync(
'.vscode/ai-provenance-agent.jsonl',
JSON.stringify({ ts: new Date().toISOString(), file, text }) + '\n'
);
} catch {}
});
Every edit Claude Code makes is logged to .vscode/ai-provenance-agent.jsonl; the extension consumes the log (then clears it) and marks exactly that text as AI-written — including in files that were closed at the time, applied when they open. Add the log file to .gitignore; it's a channel, not a record.
Known limits, stated plainly: retyping AI output by hand is undetectable by any client-side tool, and edits made while VS Code is closed (without the agent hook) reset the file to human rather than guessing.
Development
npm install
npm test # compile + unit tests for the span model
Press F5 in VS Code to launch an Extension Development Host with the extension loaded (npm run watch keeps the build fresh).
Architecture
src/provenance.ts — the model. Each document is a sorted list of contiguous character-offset spans tagged with a state. Every edit splices the list: overlapped spans are trimmed/split, later spans shift, and the inserted text gets a new span. Pure logic, no VS Code runtime dependency, fully unit-tested in src/test/.
src/journal.ts — the append-only, hash-chained journal: persistence, verification, backup mirroring, and export. Also pure logic, unit-tested.
src/extension.ts — VS Code wiring: change-event classification, decorations and hovers, the history diff views, the agent-hook channel, commands, and file watchers.
State rules (the subtle parts)
- New text is
ai if it came from a detected/declared AI source; ai-modified if a human edit overwrote AI text, typed strictly inside an AI span, or typed immediately after an ai-modified span (continuing an in-progress edit); otherwise human.
- Surviving remnants of a cut-into span keep their original state — editing one character never repaints a whole block.
- Insertions at the boundary of a plain AI span (Enter after an AI block, typing after it) count as human.