How it works

Detection model

How Blamely knows which lines came from an AI and which came from you.

Overview

Detection uses three complementary signal layers, layered from most precise to least:

  1. PostToolUse hooks — AI tools call blamely record <tool> directly. Exact file and line range known. Confidence: high.
  2. Editor plugins — the VS Code/Cursor/JetBrains plugin watches the editor command that fires when a Tab completion is accepted. Confidence: high (command-triggered) or medium (heuristic fallback).
  3. Background watchers — the daemon tails log files and session stores. Confidence: medium or low depending on the source (high for the Devin IDE session databases, which record the exact edit).

Work done entirely off your machine (Devin Cloud) is covered separately, by commit trailers.

At commit time, the attribution join picks the newest edit record covering each added line. Higher-confidence records win ties if they overlap.

PostToolUse hooks (AI tools → daemon)

Each AI tool is configured (by blamely install) to call blamely record <tool> after it writes a file. These are the most precise signals: the hook receives the exact file path and edit content, so Blamely knows the precise lines that changed.

ToolHook triggered byGen type
Claude CodeEvery Edit, Write, MultiEdit, NotebookEdit tool callchat
Cursor (Composer/Chat)Same PostToolUse hook — detected by cursor_version in payloadchat
Codex CLIapply_patch operations in session JSONLcli
Copilot CLICLI chat apply eventscli
Gemini CLIFile-write operationscli
Devin CLIwrite, edit, and apply_patch tool calls; file deletions via exec / shellcli

For Claude Code and Cursor, the hook payload also carries the transcript path — Blamely reads the last few turns of the conversation and attaches them to the commit note so you can see what was asked for. Devin CLI’s payload has no transcript, so its edits carry no model, token, or conversation data.

Editor plugins (Tab completion)

Cursor Tab and Copilot inline completions don’t fire a hook. The only way to see them is from inside the editor.

High-confidence path

The plugin listens to the VS Code command bus. When the user accepts an inline completion the editor executes one of:

editor.action.inlineSuggest.commit        (Tab — accept full suggestion)
editor.action.inlineSuggest.acceptNextWord (Cmd/Ctrl+Right — accept next word)
editor.action.inlineSuggest.acceptNextLine (accept next line)

These commands fire only on completion accepts — not on snippet expansion, code actions, pastes, or LSP quick-fixes. When the plugin sees one of these, it marks the next document change as confidence=high with no heuristics involved.

Medium-confidence fallback

For completions whose provider doesn’t route through those commands (rare), the plugin falls back to a heuristic: a single document change with ≥ 8 non-whitespace characters after trimming is treated as a completion candidate.

Plain Enter keypresses are excluded

Plain Enter keypresses (\n or \n with auto-indent) are explicitly excluded — they don’t pass the non-whitespace threshold. Only insertions with substantial code content qualify.

Paste detection

Before recording a completion, the plugin reads the clipboard. If the inserted text matches the clipboard content (exact, prefix, suffix, or substring match for ≥ 8 chars), the insert is classified as copypaste — a distinct bucket in by_tool that rolls up to the Human side of the AI vs Human bar.

JetBrains plugin

The JetBrains plugin uses AnActionListener and matches action IDs that contain InlineCompletion combined with Insert, Accept, or Apply. GitHub Copilot’s IntelliJ plugin is covered by a separate pattern on com.github.copilot.* accept/insert/apply actions.

Background watchers

The daemon runs these watchers continuously alongside hook ingestion.

CursorLogWatcher

Tails Cursor’s extension-host log files for keywords that indicate a Composer or Agent apply event: composerApply, composer.apply, agentEdit, agent.edit, agent.apply.

Emits tool=cursor, gen_type=chat, confidence=medium with the file path extracted from the log line.

Cursor Tab log files (Cursor Tab.log inside cursor-always-local directories) are explicitly skipped — those log entries fire on suggestion generation, not on acceptance, so using them would over-count completions. Tab completions are exclusively handled by the editor plugin.

CopilotLogWatcher

Tails the GitHub Copilot plugin’s log files across VS Code, Cursor, and all JetBrains IDEs:

  • VS Code / Cursor: ~/Library/Application Support/{Code,Cursor}/logs/ (macOS)
  • JetBrains IDEs: all subdirectories under ~/Library/Logs/JetBrains/ are discovered dynamically — new IDEs are picked up without code changes.

Two detection strategies run in parallel:

StrategyPatternResult
JetBrains fetch log (high precision)#copilot - [fetchCompletions] …gen_type=completion with model from URL
JetBrains fetch log (high precision)#copilot - [fetchChat] …gen_type=chat
AutoModelService line#copilot - [AutoModelService] Fetched auto model for active … : <model>Updates active model for subsequent fetch lines
Generic accept markerLines matching copilot + accept/applied/completion/inlinegen_type=completion or chat based on keyword

Cursor Tab log files are skipped here too — the isCursorTabLog guard prevents mis-attributing Cursor completions as Copilot.

CopilotChatWatcher

Reads Copilot Chat’s per-workspace session JSONL files (workspaceStorage/<hash>/chatSessions/<id>.jsonl). These use a delta-encoding format:

  • kind=0 — initial snapshot, captures selectedModel.identifier
  • kind=1 — model update
  • kind=2 at key path response — Copilot streamed a response; emits tool=copilot, gen_type=chat, model=<selected model>

This gives Blamely the exact model the user had selected in the chat panel (e.g. copilot/gpt-4o, copilot/claude-3-5-sonnet).

CodexWatcher

Tails ~/.codex/sessions/*.jsonl for apply_patch operations. Captures model and token usage from the surrounding response events.

ClaudeDesktopWatcher

The Claude Desktop app’s “cowork” sandbox runs file operations inside a VM with your repo mounted, and — unlike Claude Code — exposes no PostToolUse hook. The only observable signal is the app’s own logs under ~/Library/Logs/Claude:

  • coworkd.log records the VM’s process spawns (the bash commands that write, move, or delete files).
  • main.log maps each VM mount back to the host repo path on your machine.

The watcher tails coworkd.log for file-mutating commands and attributes them as tool=claude, gen_type=chat, confidence=medium:

  • Deletions are recorded straight from the rm / git rm targets, hashed from HEAD content — so they attribute correctly even if you commit immediately after.
  • Writes are reconciled from the working tree a few seconds after the command, once the VM has synced the change back to the host filesystem.

Best-effort and macOS-only

Because the log format is private to Claude Desktop, this watcher is best-effort and macOS-only today; on other platforms (and where those logs don’t exist) it idles. Claude Code’s hook-based detection is unaffected — this covers only the Desktop app’s sandbox.

DevinIDEWatcher

The agent inside the Devin IDE has no hook framework. The IDE keeps one SQLite database per agent session, and the watcher reads completed edit tool calls from them — with the exact path and text written:

  • macOS: ~/Library/Application Support/devin/User/acp-messages/*.db
  • Linux: ~/.config/devin/User/acp-messages/*.db
  • Windows: %APPDATA%\devin\User\acp-messages\*.db

Emits tool=devin, gen_type=chat, confidence=high. Each database is opened read-only and closed right away, so it coexists with the running IDE. Cloud sessions recorded in the same database are skipped: their paths point into a remote sandbox and carry no content.

Commit trailers (Devin Cloud)

Every other signal observes an edit as it happens on your machine. A Devin Cloud session edits files in a remote sandbox, so there is nothing local to observe — the work arrives as a finished commit carrying Devin’s own trailer:

Co-Authored-By: Devin <…+devin-ai-integration[bot]@users.noreply.github.com>

When a git pull brings in such a commit, the global post-merge hook attributes it and credits its lines to tool=devin, gen_type=chat. The match keys on the devin-ai-integration bot account, not the editable display name.

This is commit-scoped — it can’t tell which lines the agent wrote, so it credits all of them — and it never overrides an edit that was recorded locally. Only Devin’s trailer is read: other tools that write Co-Authored-By trailers are already observed by their hooks.

Gen types

The gen_type field in every line entry shows how the code was produced.

Gen typeWhen it’s setTools
chatA chat or Composer session applied an editClaude Code, Cursor Composer, Copilot Chat, Gemini chat, Devin IDE, Devin Cloud
completionAn inline Tab completion was acceptedCursor Tab, Copilot Tab
cliA CLI agent ran and patched filesCodex CLI, Gemini CLI, Copilot CLI, Devin CLI
humanHuman-typed lines, deleted lines, and copy-pasted code of unknown origin—

by_gen_type in the git note tallies all four. Deleted lines always count as human — removing code is a human action regardless of who wrote the original.

Confidence levels

ConfidenceSourceMeaning
highPostToolUse hook; editor plugin inlineSuggest.commit command; DevinIDEWatcherExact file + line range known; no guessing
mediumCursorLogWatcher; CopilotLogWatcher JetBrains fetch lines; editor plugin heuristicFile identified but range is whole-file or approximate
lowCopilotChatWatcher; CopilotWatcher session markerSession active, no file path; used to corroborate other signals

The attribution join is newest-wins per line. A later high row always beats an earlier low row covering the same line.

What is not detected

ScenarioWhy
Plain Enter keypressesExcluded explicitly — only inserts with ≥ 8 non-whitespace chars qualify on the medium-confidence plugin path
Snippet expansion (typing for<Tab>)Looks identical to a Tab completion from the editor’s API on the medium-confidence path; may be attributed as completion
LSP code actions and auto-importsSame limitation — the editor API doesn’t distinguish them from completions at the document-change level
Edits older than 8 hours before a commitOutside the lookback window; intentional — older edits drift too far from the assistant action that produced them
Cursor Tab via log filesThe log fires on suggestion generation, not acceptance; Cursor Tab is tracked only via the editor plugin
Whitespace-only insertsFiltered at attribution time; no meaningful content to attribute

Next

Something unclear or wrong? Tell us.