azkhh/drift
이 mod 소개
drift
지금 일을 진척시키는지, 어둠 속에 숨고 있는지를 알려 주는 Claude Code mod입니다.
Claude Code의 mod 이벤트를 통해 세션을 지켜보며 두 가지를 셉니다. 실제로 출하한 것(커밋, 푸시, 배포, 통과한 테스트 실행)과 그저 정리한 것(도구, 설정, 문서, 계획 편집)입니다. 앞으로 나아가는 동안에는 조용합니다. 오랫동안 출하한 것이 없거나, 최근 편집 대부분이 설정과 문서에 집중되거나, 같은 파일을 계속 다시 쓰면 프롬프트 위에 담백한 한 줄을 한 번 표시합니다.
모델 토큰을 사용하지 않고, 네트워크 호출이나 프로세스 실행, 파일 읽기도 하지 않습니다. 도구 호출을 세고 그 합계를 컴퓨터에 있는 mod 자체 저장소에 보관합니다.
src/app.ts을(를) 출하 없이 4번 편집했습니다. 최근 20번의 편집 중 12번이 설정과 문서였습니다.
Claude Code 2.1.287 이상이 필요합니다(이 버전부터 mod가 기본으로 켜집니다).
설치
claude plugin marketplace add azkhh/drift
claude plugin install drift@drift
Claude Code 안에서 /plugin을 실행하고 마켓플레이스 azkhh/drift를 추가한 뒤 drift를 설치해도 됩니다. 세션을 다시 시작하고 /drift를 실행하세요.
설치하지 않고 clone한 디렉터리에서 시험하려면 다음을 실행합니다.
claude --plugin-dir ./drift
밴드가 알려 주는 내용
아래 조건 중 하나를 넘기 전에는 아무 말도 하지 않습니다. 각 조건은 넘는 순간 한 번만 알리고, 측정값이 다시 내려갈 때까지 조용히 있습니다(출하하면 첫 번째와 세 번째가 재설정됩니다).
| 임계값 | 표시 문구 |
| --- | --- |
| 무언가를 출하한 뒤 활성 시간이 45분 | 무언가를 출하한 뒤 52분이 지났습니다. |
| 최근 20번 편집 중 60% 이상(최소 10번)이 어둠에 해당 | 최근 20번의 편집 중 14번이 설정과 문서였습니다. |
| 같은 파일을 출하 없이 4번 편집 | src/app.ts을(를) 출하 없이 4번 편집했습니다. |
이 줄은 다음 프롬프트를 보내거나 무언가를 출하할 때까지 프롬프트 위에 남습니다. 출하는 커밋, 푸시, 배포 또는 종료 코드 0인 테스트 실행입니다.
활성 시간에는 이벤트 사이가 10분 이하인 간격만 포함합니다. 점심을 먹으러 가면 유휴 시간이지 drift가 아닙니다.
/drift
| 명령 | 동작 |
| --- | --- |
| /drift | 오늘의 기록과 최근 7일을 보여 줍니다. 활성 시간, 출하 수, 어둠 속 시간, 변경이 몰린 파일, 실패한 시도, 목표와 목표가 유지된 시간을 포함합니다. 모델 턴은 발생하지 않습니다. |
| /drift goal <text> | 이 프로젝트에 목표 하나를 설정합니다(저장소 루트가 키입니다). 그 이후 편집한 경로 또는 커밋 메시지에 목표와 의미 있는 공통 단어가 있는지 기록에 표시합니다. |
| /drift goal | 목표를 표시합니다. /drift goal clear로 삭제합니다. |
| /drift off, /drift on | 이 프로젝트의 집계를 중지하거나 다시 시작합니다. |
| /drift reset | 이 프로젝트의 카운터와 기록을 지웁니다. 목표와 켜기/끄기 상태는 유지합니다. |
작업과 어둠
편집한 경로가 다음 glob 중 하나와 일치하면(대소문자를 구분하지 않으며, 슬래시가 없는 패턴은 어느 깊이에서든 파일 이름과 일치합니다) 그 편집은 “어둠”에 해당합니다. 나머지는 모두 작업입니다.
CLAUDE.md AGENTS.md **/.claude/**
*.config.* package.json tsconfig* **/.github/**
.* **/.*/** (dotfile 및 dot 폴더)
docs/**/*.md **/docs/**/*.md plan.md notes.md todo.md roadmap.md readme.md
*.lock *.lockb package-lock.json pnpm-lock.yaml go.sum
목록은 hooks/paths.ts 한 곳에 있습니다. 교체하려면 플러그인 옵션 Dark patterns(/plugin configure drift@drift 또는 /config에서 이 플러그인 행)을 쉼표로 구분한 glob 목록으로 설정하세요. 설치 시 한 옵션이 아직 설정되지 않았다는 메모가 출력되지만 선택 사항입니다. 비어 있지 않은 값은 기본 목록에 더하는 것이 아니라 기본 목록을 대체합니다.
mod 이벤트가 볼 수 있는 것과 볼 수 없는 것
이 mod는 Claude Code 2.1.287 선언을 기준으로 만들어졌습니다. 각 부분이 무엇에 의존하는지는 다음과 같습니다.
편집. Edit, Write, MultiEdit, NotebookEdit 도구 호출에는 경로가 들어 있습니다. 호출이 실제로 적용된 경우에만 편집으로 셉니다. 실패한 호출(isError)이나 거부된 호출(deny)은 세지 않습니다.
출하. Bash 도구 호출에는 명령 텍스트가 들어 있으며, mod는 여기서 git commit, git push, vercel deploy, wrangler deploy, npm publish, fly deploy, netlify deploy, 그리고 npm test, pnpm test, yarn test, bun test, pytest, go test, cargo test, vitest, jest를 읽습니다. 드라이 런은 무시합니다. 복합 명령은 따옴표 안을 제외하고 &&, ||, ;, |에서 나눕니다.
종료 상태. 2.1.287의 Bash 도구 결과에는 숫자 종료 코드가 없습니다. stdout, stderr, interrupted, backgroundTaskId 등의 필드가 있습니다. hook이 볼 수 있는 것은 isError이며, 도구가 오류를 보고하면 엔진이 설정하고 0이 아닌 종료도 같은 경우입니다. 실제 실행에서 T=1 npm test(종료 1)는 isError: true로 도착해 실패한 시도로 집계되었고, 같은 명령의 종료 0은 통과한 테스트 실행으로 집계되었습니다. 따라서 mod는 0인지 아닌지만 알 수 있고 더 세부적인 상태는 모릅니다. 백그라운드로 보낸 명령은 끝나지 않았으므로 출하로 세지 않습니다.
잡지 못하는 것.
- 명령 체인에는 종료 상태가 하나뿐입니다.
git commit -m x && git push가 0으로 끝나면 둘 다 실행된 것입니다.a; b와a || b는 일부가 실패해도 0으로 끝날 수 있고,npm test | tail은tail의 상태로 끝나므로 다른 명령으로 파이프한 실패 테스트도 통과로 집계됩니다. Bash로 한 편집(sed -i,echo >> file, 포매터)은 mod의 편집이 아닙니다. 편집 도구만 대상입니다.- 인식하지 못하는 출하는 릴리스 스크립트(
npm run release), CI에서의 배포, 다른 터미널에서 한 커밋입니다. - 생각하거나 입력한 시간은 보지 못합니다. 활성 시간은 도구 호출, 프롬프트, 완료된 턴 사이에서 10분 이하인 간격의 합입니다.
- 통과한 테스트 실행은 앞으로 나아가고 있다는 증거이므로 출하로 셉니다. 커밋처럼 출하 이후의 시계를 재설정합니다.
근사치. “어둠 속 시간”은 종료 이벤트가 어두운 파일 편집이었던 활성 시간입니다. 시간 창에 편집이 최소 10번 있어야 어둠의 비율을 표시하므로 설정을 3번 연속 편집한 것만으로는 판단하지 않습니다. 8시간을 넘게 쉬면 새 실행이 시작되어 어제의 편집은 오늘에 반영되지 않습니다. 목표 일치는 공통 단어 하나를 사용합니다(소문자로 바꾸고 복수형을 합치며 흔한 단어와 파일 확장자를 무시합니다). 의도는 읽을 수 없고, 건드린 내용에 목표가 언급되지 않았다는 사실만 알려 줍니다.
개인정보 보호
claude plugin validate는 모듈이 수행하는 모든 호출을 나열합니다. 시계, 세션 루트와 저장소, mod 자체의 상태와 저장소, 명령 등록입니다. $.http, $.process, $.fs, $.model은 호출하지 않습니다. 프로젝트별로 목표와 최근 30일 동안의 날짜별 소수의 카운트, 변경이 몰린 파일 이름을 저장합니다. 파일 경로는 프로젝트 루트 기준 상대 경로로 저장됩니다. 저장소는 Claude Code 플러그인 저장 폴더의 JSON 파일입니다. 어디에도 전송하지 않습니다.
개발
claude plugin test . # 65개 테스트, 엔진 자체 호스트가 실행
claude plugin validate .claude-plugin/plugin.json
claude --plugin-dir . # 실시간으로 시험합니다. 폴더를 감시하며 hot reload합니다
Claude Code는 플러그인 폴더를 로드할 때마다 타입 선언을 .claude-plugin/types/에 씁니다. 이 폴더는 git ignore됩니다. npx -p typescript tsc -p tsconfig.json은 해당 선언을 기준으로 hook과 테스트의 타입을 검사합니다.
각 부분을 따로 테스트할 수 있도록 코드가 나뉘어 있습니다.
| 파일 | 내용 |
| --- | --- |
| hooks/paths.ts | 어둠 목록, glob, 옵션 |
| hooks/ships.ts | 명령줄에서 커밋, 푸시, 배포, 테스트 실행을 찾는 기능 |
| hooks/goal.ts | 목표와 공통 단어 검사 |
| hooks/tracker.ts | 활성 시간, 편집 창, 변경 집중, 출하, 일별 합계, 3가지 임계값 |
| hooks/receipt.ts | /drift가 출력하는 텍스트 |
| hooks/mover-line.ts | /drift를 끝내는 한 줄 |
| hooks/register.ts | hook: tool.call, prompt.submit, turn.complete, session.start, session.end, AbovePrompt 밴드, command.run |
각 hook은 일정한 양의 작업만 하며 디스크를 스캔하지 않습니다. 스크립트 실행에서 엔진 디버그 로그는 편집 자체를 포함한 편집용 tool.call hook이 6~15 ms에 안정적으로 끝났음을 보여 주었습니다.
라이선스
MIT, LICENSE를 참조하세요.
drift는 편집만 봅니다. mover는 하겠다고 말한 일을 기억하고 실제로 한 일과 대조합니다: moveros.dev
설치
먼저 작성자의 README에서 marketplace와 플러그인 이름을 확인하세요. 저장소 구조에 따라 명령어가 달라질 수 있습니다.
claude plugin marketplace add azkhh/drift claude plugin install drift
원문 / README
drift
A Claude Code mod that tells you whether you are moving or hiding in the dark.
It watches a session through Claude Code's mod events and counts two things: what shipped (commits, pushes, deploys, passing test runs) and what you only tidied (edits to tooling, config, docs and plans). It is silent while you are moving. When you have gone a long stretch with nothing shipped, or most of your recent edits went to config and docs, or you keep rewriting one file, one plain line appears above the prompt. Once.
It uses no model tokens, makes no network calls, runs no processes and reads no files. It counts tool calls and keeps the totals in the mod's own store on your machine.
src/app.ts has been edited 4 times with nothing shipped. 12 of your last 20 edits were config and docs.
Needs Claude Code 2.1.287 or later (mods are on by default from that version).
Install
claude plugin marketplace add azkhh/drift
claude plugin install drift@drift
Or from inside Claude Code: /plugin, add the marketplace azkhh/drift, install drift. Restart the session, then run /drift.
To try it from a clone without installing:
claude --plugin-dir ./drift
What the band says
Nothing, until one of these is crossed. Each one speaks once per crossing, then stays quiet until the thing it measures drops back (a ship resets the first and third).
| Threshold | Line |
| --- | --- |
| 45 active minutes since anything shipped | 52 min since anything shipped. |
| 60% or more of your last 20 edits (at least 10 edits) went to the dark | 14 of your last 20 edits were config and docs. |
| The same file edited 4 times with nothing shipped between | src/app.ts has been edited 4 times with nothing shipped. |
The line stays above the prompt until you send your next prompt, or until something ships. A ship is a commit, push, deploy or test run that exited 0.
Active time counts only the gaps between events that are 10 minutes or shorter. Go for lunch and it is idle, not drift.
/drift
| Command | What it does |
| --- | --- |
| /drift | Today's receipt and the last 7 days: active time, shipped count, time in the dark, churn files, failed tries, the goal and how long it has stood. No model turn. |
| /drift goal <text> | Sets one goal for this project (keyed by the repository root). The receipt says whether any edited path or commit message since then shares a meaningful word with it. |
| /drift goal | Shows the goal. /drift goal clear removes it. |
| /drift off, /drift on | Stop or resume counting in this project. |
| /drift reset | Clears this project's counters and history. Keeps the goal and the on/off switch. |
The work and the dark
An edit is "in the dark" when its path matches one of these globs (case does not matter, a pattern with no slash matches a file name at any depth). Everything else is the work.
CLAUDE.md AGENTS.md **/.claude/**
*.config.* package.json tsconfig* **/.github/**
.* **/.*/** (dotfiles and dot folders)
docs/**/*.md **/docs/**/*.md plan.md notes.md todo.md roadmap.md readme.md
*.lock *.lockb package-lock.json pnpm-lock.yaml go.sum
The list lives in one place, hooks/paths.ts. To replace it, set the plugin option Dark patterns (/plugin configure drift@drift, or the row for this plugin in /config) to a comma separated list of globs. The install prints a note that one option is not set yet; it is optional. A non empty value replaces the built in list, it does not add to it.
What the mod events can and cannot see
The mod is built against the Claude Code 2.1.287 declarations. Here is what each part rests on.
Edits. Edit, Write, MultiEdit and NotebookEdit tool calls carry the path. An edit counts only if the call landed: a call that failed (isError) or was refused (deny) counts for nothing.
Ships. Bash tool calls carry the command text, which the mod reads for git commit, git push, vercel deploy, wrangler deploy, npm publish, fly deploy, netlify deploy, and npm test, pnpm test, yarn test, bun test, pytest, go test, cargo test, vitest and jest. Dry runs are ignored. Compound commands are split at &&, ||, ; and |, never inside quotes.
Exit status. The Bash tool result in 2.1.287 has no numeric exit code. Its fields are stdout, stderr, interrupted, backgroundTaskId and a few more. What the hook does see is isError, which the engine sets when the tool reported an error, and a non zero exit is one. In a live run, T=1 npm test (exit 1) arrived as isError: true and was counted as a failed try, and the same command with exit 0 counted as a passing test run. So the mod knows zero from not zero, and nothing finer. A command sent to the background has not finished and is not counted as shipped.
What that does not catch.
- A chain has one exit status.
git commit -m x && git pushexiting 0 means both ran.a; banda || bcan exit 0 with one part failed, andnpm test | tailexits withtail's status, so a failing test run piped through another command counts as a pass. - Edits made through
Bash(sed -i,echo >> file, a formatter) are not edits to the mod. Only the edit tools are. - Ships it does not recognise: a release script (
npm run release), a deploy from CI, a commit you make in another terminal. - It does not see time spent thinking or typing. Active time is the sum of gaps of 10 minutes or less between tool calls, prompts and finished turns.
- A passing test run counts as a ship, because it is evidence of moving. It resets the clock since a ship like a commit does.
Approximations. "Time in the dark" is the active time whose closing event was an edit to a dark file. The dark share needs at least 10 edits in the window before it can speak, so three config edits in a row are not a verdict. A break of more than 8 hours starts a fresh run, so yesterday's edits do not count against today. The goal match is one shared word (lowercased, plurals folded, common words and file extensions ignored); it cannot read intent, it can only tell you that nothing you touched has mentioned the goal.
Privacy
claude plugin validate lists every call the module makes: the clock, the session's root and repository, the mod's own state and store, and the command registration. It makes no $.http, $.process, $.fs or $.model call. Per project it stores the goal and, for the last 30 days, a few counts per day and the names of the files that churned. File paths are stored relative to the project root. The store is a JSON file in Claude Code's plugin store folder. Nothing is sent anywhere.
Development
claude plugin test . # 65 tests, run by the engine's own host
claude plugin validate .claude-plugin/plugin.json
claude --plugin-dir . # try it live; the folder is watched and hot reloads
Claude Code writes its type declarations to .claude-plugin/types/ every time it loads a plugin folder; that folder is git ignored. npx -p typescript tsc -p tsconfig.json type checks the hooks and tests against it.
The code is split so each piece can be tested on its own:
| File | What it holds |
| --- | --- |
| hooks/paths.ts | The dark list, the globs, the option |
| hooks/ships.ts | Finding commits, pushes, deploys and test runs in a command line |
| hooks/goal.ts | The goal and its shared word check |
| hooks/tracker.ts | Active time, the edit window, churn, ships, day totals, the three thresholds |
| hooks/receipt.ts | The text /drift prints |
| hooks/mover-line.ts | The one line that ends /drift |
| hooks/register.ts | The hooks: tool.call, prompt.submit, turn.complete, session.start, session.end, the AbovePrompt band, command.run |
Every hook does a constant amount of work and no disk scan. In a scripted run the engine's debug log showed the tool.call hook settling in 6 to 15 ms for an edit, with the edit itself included in that time.
License
MIT, see LICENSE.
drift only sees your edits. mover remembers what you said you'd do and checks it against what you did: moveros.dev
