glean
records which files a tool has already processed, so the next run only sees what changed since. A skill on a loop scopes itself to new edits instead of re-sweeping the whole diff every tick.
Per-consumer baselines. Each skill or tool tracks its own position, so parallel passes share a tree without redoing each other's work.Content-hashed. A file is changed when its bytes differ from the baseline — surviving rebases, branch switches, and rewrites that only bump mtimes.Scales with the diff. The candidate set is the uncommitted working set, so a monorepo with three edits costs three reads.Out of history. Baselines live in.git/glean/
, never committed, separate per worktree.
A single Rust binary. The /glean
skill and the MCP server both resolve it by name on PATH
:
cargo install --path . --root ~/.local # → ~/.local/bin/glean
glean list [--as <consumer>] [-z] [-q] [--json] files changed since this consumer's last mark
glean mark [--as <consumer>] [--stdin] [-z] [paths…] record files as processed (no paths = the whole changed set)
glean status [--as <consumer>] [--json] counts; with no --as, summarize every consumer
glean reset [--as <consumer>] [--all] forget a baseline to force a full re-sweep
glean run [--as <consumer>] [--each] -- <cmd>… run <cmd> on the changed files, mark on success
glean mcp serve the change-set as MCP tools over stdio
A consumer is one skill or tool with its own baseline. --as
names it; it defaults to default
.
$ glean list --as slop # what has changed since slop last ran?
src/auth.rs
src/db.rs
$ glean mark --as slop src/auth.rs src/db.rs # record them as done
$ glean list --as slop # nothing new since
$
-z
writes NUL-separated paths, safe for paths with spaces or newlines; --json
writes a JSON array. -q
prints nothing and exits 0 if anything changed, 1 if not. mark --stdin
reads paths from stdin:
glean list -z --as slop | glean mark --stdin -z --as slop
Consumers are independent. When a file changes, every consumer that has not seen those exact bytes picks it up, including files the other passes edited:
glean list --as slop # code smells
glean list --as simplify # structure
glean list --as check # type checking
glean ships as a Claude Code plugin — the /glean
skill and the glean
MCP server:
/plugin marketplace add amalucelli/glean
/plugin install glean@glean
Both call the glean
binary by name, so install that too. Without the plugin, copy the skill where Claude Code looks for it:
cp -r skills/glean ~/.claude/skills/
/glean <skill>
scopes <skill>
to what changed since it last ran, invokes it, and marks the result. Point a loop at it:
/loop 2m /glean slop # slop only new edits, every 2 minutes
/loop 5m /glean simplify # works with built-in skills too
It runs in a forked subagent, so the loop's main context grows by one line per tick rather than by every file the pass reads. Run the skill directly to watch or steer it. The wrapped skill must accept file paths as arguments. Forking from a plugin needs Claude Code v2.1.101+; older versions run it inline.
glean run
packages list → run → mark into one command:
tool <args> <files> → glean run --as tool -- tool <args>
It snapshots the changed set, runs the command on it, and marks those files only if the command succeeds — a failing pass leaves its files for the next tick. run
exits with the command's status. In CI, cache .git/glean/
between runs and a step only re-checks files that changed since they last passed.
Formatters rewrite in place, so batch (the default) fits: glean records the formatted bytes as the new baseline.
glean run --as gofmt -- gofmt -w
Linters report per file, so use --each
: the command runs once per file and only passing files are marked. A file with errors returns each tick until it is fixed, instead of pinning the whole batch.
glean run --each --as eslint -- eslint
Outside Claude Code that is a plain shell loop:
while true; do
glean run --each --as eslint -- eslint
sleep 300
done
Whole-project tools that cannot take file paths (cargo clippy
, tsc
) do not fit run
. Gate them on -q
instead:
glean list -q --as clippy && cargo clippy && glean mark --as clippy
glean mcp
serves the change-set over stdio. The plugin registers it automatically; for another client, run glean mcp
as the server command.
glean_list
— paths changed since a consumer last marked them.glean_mark
— record paths as processed; omitpaths
to mark the whole changed set.glean_status
— tracked and changed counts per consumer.
run
is not exposed: the MCP surface reads and advances the cursor, it does not execute commands.
- The candidate set is
git diff HEAD
plusgit ls-files --others
— staged, unstaged, and untracked. - Baselines are stored at
.git/glean/<consumer>.json
. Git does not track its own directory, so the state stays out of history and out of glean's view of the tree. - Each consumer is the sole writer of its own file, so consumers need no locking.
- Lockfiles and binary files are excluded from
list
. core.fsmonitor
andcore.untrackedCache
speed up large trees.
There is no base flag. For committed work the file set (git diff <ref>
) is static and swept once, so a cursor adds nothing — scope that with git, or with the skill's own branch mode.
MIT. See LICENSE.