Isogram — AI Text Score (VS Code extension)
Per-line AI-edit scores in the editor, from the Isogram detector. Lines the detector attributes to
AI are tinted by class — orange for AI-assisted (AI-edited), red for AI-generated — with
intensity by score and the exact numbers on hover. The status bar shows the document verdict;
click it to rescore.
More at isogram.hails.info/vscode.
Install
Download the .vsix from isogram.hails.info/vscode, then:
code --install-extension isogram-vscode-0.6.0.vsix
Then run Isogram: Set API Token from the command palette and paste your iso_… API key. The
key is stored in the OS keychain via VS Code's SecretStorage, never in settings. Scoring needs a
key; keys are issued on request.
What it sends
The extension sends the contents of each scored document, and its file name (not its
path — the name steers prose vs. code scoring), to the configured endpoint. It refuses untrusted
workspaces, and the endpoint setting is machine-scoped so a workspace cannot redirect your
documents or key elsewhere.
How scoring is scheduled
Every eligible document (file or untitled, non-empty, ≤ isogram.maxDocumentKb) is scored after
isogram.debounceMs of edit-idle, when it becomes the active tab, and immediately on a manual
save. Tab switches share one idle wait, so flipping through several unscored files sends one
request, for the one you settle on. The content is SHA-256-hashed first: an unchanged document
never re-fires, verdicts seen before are served from an in-memory cache, and a newer edit aborts
the superseded request. Documents nobody can see (not shown in an editor, or the window
unfocused) are not scored until they are shown.
Prose is scored at token resolution: each line takes the class of the authorship spans covering
it. Code falls back to overlapping 512-token windows decoded jointly per line. Lines under the
tint threshold (isogram.tintThreshold) get no tint.
Excluded text
Matches of isogram.excludePatterns are cut out of the text before it is sent: they never reach
the endpoint, never move the verdict, and are never tinted. A line that is entirely excluded stays
untinted; a line mixing excluded and kept text is tinted by its kept text alone. The default
excludes HTML comments (<!-- … -->), which rendered markdown never shows; it applies to every
document, since a comment in a code file is rare and is also not prose anyone reads. Set the list
to [] to score everything.
Matches are removed rather than blanked: a run of spaces where a comment was would still be an
input the model reads, and would shift every window after it. The endpoint's offsets are mapped
back onto the document, so tints land on the right characters.
Patterns are JavaScript regex sources compiled with flags gmu (^/$ match per line; use
[\s\S] to cross lines). The setting is resource-scoped, so a workspace can set its own in
.vscode/settings.json, for example to also drop fenced code blocks:
{
"isogram.excludePatterns": ["<!--[\\s\\S]*?-->", "^```[\\s\\S]*?^```"]
}
An invalid pattern raises one warning, is logged, and is ignored; the others still apply.
Changing the list rescores visible documents (from cache when the sent text was scored before).
The endpoint scales to zero: an idle editor costs nothing, and the first score after idle waits
for a GPU cold start (up to a minute or two; warm scores return in seconds).
Commands
| command |
what it does |
| Isogram: Set API Token |
store your iso_… key (empty input clears it) |
| Isogram: Toggle Highlighting |
switch the tints on and off |
| Isogram: Score Current Document |
force a rescore of the active editor |
| Isogram: Score Selection |
score only the selected text (also on the editor right-click menu); the verdict pops up every time, instantly and marked cached when that passage was scored before. Multiple selections are joined with a blank line; isogram.excludePatterns still applies |
| Isogram: Score Files Matching Glob… |
batch-score the workspace (default **/*.{md,mdx,txt}); results open as a markdown report sorted worst-first |
| Isogram: Show Log |
every request, skip and failure |
Settings
| setting |
default |
meaning |
isogram.enabled |
true |
score + highlight open documents |
isogram.endpoint |
https://djrhails--isogram-score.modal.run |
scoring endpoint (POST) |
isogram.debounceMs |
1500 |
edit-idle time before a rescore |
isogram.externalChangeDebounceMs |
60000 |
idle time after an on-disk change (a git checkout, another tool's write) before a visible document is rescored |
isogram.maxDocumentKb |
256 |
larger documents are skipped |
isogram.excludePatterns |
["<!--[\\s\\S]*?-->"] |
regexes whose matches are removed before scoring and never tinted (see Excluded text) |
isogram.tintThreshold |
null |
minimum per-line score to tint (0-1); null uses the detector's own "human" threshold (~0.05). Raise to ~0.6 to show only AI-generated lines |
Verdicts are probabilistic: treat them as evidence, not proof.
Development
pnpm install
pnpm run typecheck && pnpm run lint && pnpm test
pnpm run package # → isogram-vscode-<version>.vsix
pnpm run build bundles src/extension.ts → dist/extension.js with esbuild (CJS — the VS Code
extension host does not load ESM entry points, which is why this package has no "type": "module"). Pure logic (hashing, per-line decode, buckets, cache) lives in src/scoring.ts,
batch-report building and the concurrency pool in src/batch.ts, both tested with vitest;
src/extension.ts is the thin VS Code glue.