doc-drift-watch
Modelin attığı commit'in hangi doc satırlarını bayattığını modele söyleyen bir Claude Code Mod'u. Modelin çalıştırdığı her git commit sonrasında mod, ripwire ile hangi markdown anchor'larının artık tutmadığını sorar ve commit'in bozduklarını commit'in sonucuna ekler.
Ne yapar
Mod Bash tool'unu hook'lar.
git commitçalıştıran bir komut kontrol edilir (git -C <dir> commitde,--dry-runve--helpdeğil).Komut çalışmadan önce repository root'unu bulur: session'ın dizini, commit'ten önceki son
cdve commit'in kendigit -Cdeğeri. Sonraripwire <root> --doc-drift --with-historykomutunu argv ile çalıştırır.Başarılı bir commit'ten sonra aynı komutu tekrar çalıştırır ve iki koşuyu karşılaştırır. İki anchor, doc'u, kind'ı, sebebi ve referansı aynıysa aynı sayılır; satır numarası hesaba katılmaz, çünkü üstteki bir edit onu kaydırır.
Sadece commit'in eklediği anchor'lar raporlanır. Model, commit'in sonucundan sonra şu notu okur:
doc-drift-watch: this commit made 1 doc line(s) stale: README.md:7 points at other.go:3, a file that no longer exists. Update them in a follow-up commit, or tell the user why a line stays.En fazla 8 satır adlandırılır, gerisi sayılır.
Aynı anda transcript'e bir satır yazılır, böylece modele ne söylendiğini görürsünüz. Bu satır talimat cümlesi olmadan yalnız bayat satırları taşır:
doc-drift-watch: 1 doc line(s) stale: README.md:7 points at other.go:3, a file that no longer existsNot ve satır ayrı iki kanaldır: model satırı hiç okumaz, siz notu hiç okumazsınız.
sidebar açıkken bu satırlar oraya gider, doc başına bir entry, kırmızı, stream'in içinde bir kayıt olarak; transcript temiz kalır. Sidebar kapalıyken ya da o mod kurulu değilken yukarıdaki transcript satırı yazılır.
Bulgu sonra açık kalır, doc başına bir tane. Her main-loop turn sonunda mod her açık doc'u tekrar ölçer:
ripwire <root> --doc-drift=<doc> --with-history, yani yalnız o doc'a daraltılmış bir koşu. Anchor'larının hepsi tekrar tutan bir doc kapanır: kırmızı entry temizlenir, yerine bir yeşil satır gelir.doc-drift-watch: README.md: 1 doc line(s) hold againSilinmiş bir doc bulguyu diğer taraftan kapatır ve satır bunu söyler:
README.md is gone, and its 1 stale line(s) with it. Bayat satırlarının bir kısmı duran doc açık kalır, çünkü kısmen düzelmiş düzelmemiş sayılır.Geriye kalan, bir sonraki prompt ile modele tek not olarak ulaşır, turn başına bir not:
doc-drift-watch: 1 doc(s) still hold stale lines: README.md (1). Update them.
Commit'ten önce de bayat olan bir anchor tekrarlanmaz, yani bir README'deki örnek path her commit'te geri gelmez. Yazarının tarih attığı bir anchor (ripwire kind="dated-record") raporlanmaz, çünkü o kayıt o günün doğrusunu tutar.
Canlı testte model, bir README'nin other.go:3 ile gösterdiği dosyayı sildi, commit sonrası notu okudu ve kelimesi kelimesine tekrarladı. Aynı README'deki daha eski bir bayat anchor notta yer almadı.
İki mod
note varsayılandır: mod raporlar ve hiçbir şeyi durdurmaz.
deny modunda bir doc hâlâ bayat satır tutarken git commit, git push ve git merge durdurulur. Mod cevap vermeden önce her açık doc'u tekrar ölçer, yani modelin düzelttiği bir doc gate'i kendisi açar ve hiçbir gate kalıcı olarak kilitlenmez:
doc-drift-watch: stopped: 1 doc(s) still hold stale lines: README.md (1). Update them and run the command again; there is no way around this gate.
Bir git commit yalnız kendi dosyalarından sorumludur: mod index'i okur (git rev-parse --show-toplevel ve git diff --cached --name-only -z) ve commit açık doc'lardan hiçbirini tutmuyorsa çalışmasına izin verir, kaç bulgunun durduğunu söyleyen bir satırla. git commit -a, -am ve -- sonrası pathspec taşıyan bir commit daraltılmaz, çünkü index tek başına onların neyi commit ettiğini söylemez. push ve merge hiçbir index okumaz, bu yüzden orada her bulgu durur.
Kaçış yolu ve tek seferlik geçiş yoktur. Gate, doc'lar tekrar tuttuğunda ya da /doc-drift-watch mode note yazdığınızda açılır.
Komut
/doc-drift-watch durum: on ya da off, mod ve hâlâ bayat olan doc'lar
/doc-drift-watch on | off varsayılan on
/doc-drift-watch mode note sadece rapor, varsayılan
/doc-drift-watch mode deny bir doc bayatken git commit, push ve merge'i de durdur
Kurulum
claude plugin marketplace add KilimcininKorOglu/claude-code-mods
claude plugin install doc-drift-watch@kilimcininkoroglu-mods
Function hook'lar early access. Flag olmadan hiçbir şey yüklenmez. Flag'i kalıcı yapmak için ~/.claude/settings.json dosyasına ekleyin:
{ "env": { "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1" } }
Kurulumdan sonra
- ripwire kurun ve PATH'e koyun. Olmadan her commit bir kere
the docs were not checked: ...satırını yazar ve commit eskisi gibi çalışır. - Claude Code'u yeniden başlatın.
Nereye uzanır
Claude Code 2.1.278 üzerinde claude plugin validate ile doğrulandı:
❯ ./register.ts hooks: session.start, command.run{command=doc-drift-watch}, tool.call{tool=Bash}, turn.complete, prompt.submit
❯ ./register.ts calls: $.command.register, $.fs.read (via isGone), $.process.run (via driftNow, repoRoot, stagedPaths), $.session.cwd (via beforeCommit, stagedPaths), $.sidebar.clear (via closeOne), $.sidebar.set (via toPerson), $.store.get, $.store.set (via runCommand, setMode), $.ui.log (via gate, report, toPerson)
Reach L2, process çalıştırır.
1. Okur: Bash komut metnini, her açık doc'un kendi dosyasını; ripwire üzerinden repository'nin markdown, kaynak kodu ve git history'sini
2. Çalıştırır: git rev-parse, git diff --cached ve ripwire --doc-drift, salt okuma, argv ile: commit başına iki kere, her turn sonunda ve her guarded komutta açık doc başına bir kere
3. Gönderir: commit'in sonucundan sonra ve bir sonraki prompt'ta modele bir not, transcript'e ya da sidebar'a bir satır; makineden hiçbir şey çıkmaz
4. Saklar: $.store içinde on/off ayarını ve modu
5. Düşman girdi: dizin komut metninden gelir ve git'e yalnız working directory olarak ulaşır, hiçbir zaman shell üzerinden geçmez; doc path'i ripwire'ın kendi çıktısından gelir ve ripwire'a tek bir argv değeri olarak gider
Sınırlar
- ripwire file:line referanslarını, backtick içindeki sembol adlarını,
= Nsabitlerini ve[N]dizi uzunluklarını kontrol eder. Düz metin kontrol edilmez ve ripwire bilerek eksik raporlar: adı başka bir yerde de geçen, yeniden adlandırılmış bir sembol raporlanmaz. - ripwire commit başına iki kere, her turn sonunda açık doc başına bir kere çalışır. Bu repository'de tüm repo koşusu 0.1 ile 0.2 saniye sürdü, daraltılmış koşu daha az.
- Turn sonu yalnız açık doc'ları ölçer. Commit'in dokunmadığı ve başka bir yolla bayatlayan bir doc turn sonunda değil, bir sonraki commit'te bulunur.
--doc-drift=<doc>path substring'i ile filtreler, yani tek doc'a daraltılmış bir koşu, path'i onu içeren başka bir doc'u da okur. Cevap sonradan doc'un kendi path'i ile filtrelenir.git commitkomutunu gizleyen bir script ya da alias üzerinden atılan commit görülmez.cd ~/xgenişletilmez: root o zaman session'ın dizininden gelir.
Geliştirme
make install # eslint, typescript-eslint, typescript
make lint # complexity limiti 10, üstünde build'i düşürür
make typecheck # /plugin-types ile üretilen .claude/types/ gerekir
make validate
make test # claude plugin test
doc-drift-watch
A Claude Code Mod that tells the model which doc lines its commit made stale. After each git commit the model runs, the mod asks ripwire which markdown anchors no longer hold, and adds the ones the commit broke to the commit's result.
What it does
The mod hooks the Bash tool. A command that runs
git commit(alsogit -C <dir> commit, not--dry-runor--help) is checked.Before the command runs, it finds the repository root from the session's directory, the last
cdbefore the commit and the commit'sgit -C, and runsripwire <root> --doc-drift --with-historyby argv.After a successful commit it runs the same command again and compares the two runs. A stale anchor is the same one when its doc, kind, reason and reference match; the line number is left out, because an edit above it moves it.
Only the anchors the commit added are reported. The model reads this note after the commit's result:
doc-drift-watch: this commit made 1 doc line(s) stale: README.md:7 points at other.go:3, a file that no longer exists. Update them in a follow-up commit, or tell the user why a line stays.At most 8 lines are named, the rest counted.
The same moment writes one line to the transcript, so you see what the model was told. The line holds the stale lines alone, without the instruction:
doc-drift-watch: 1 doc line(s) stale: README.md:7 points at other.go:3, a file that no longer existsThe note and the line are separate channels: the model never reads the line, and you never read the note.
While the sidebar is open, those lines go there instead, one entry per doc, red, as an entry in its stream, and the transcript stays clean. With the sidebar closed, or without that mod installed, the transcript line is written as above.
The finding then stays open, one per doc. At each main-loop turn's end the mod measures each open doc again with
ripwire <root> --doc-drift=<doc> --with-history, a run narrowed to that one doc. A doc whose anchors all hold again is closed: the red entry is cleared and one green line takes its place.doc-drift-watch: README.md: 1 doc line(s) hold againA doc that is gone closes the finding from the other side, and the line says so:
README.md is gone, and its 1 stale line(s) with it. A doc that holds some of its stale lines still stays open, because a part fixed is not fixed.What is left reaches the model as one note with the next prompt, one note per turn:
doc-drift-watch: 1 doc(s) still hold stale lines: README.md (1). Update them.
A stale anchor that was stale before the commit is not repeated, so an example path in a README does not come back on every commit. An anchor its author dated (ripwire kind="dated-record") is not reported, because it records what was true then.
In the live check the model deleted a file that a README pointed at with other.go:3, read the note after the commit, and repeated it word for word. An older stale anchor in the same README was not in the note.
The two modes
note is the default: the mod reports and stops nothing.
In deny mode a git commit, git push or git merge is stopped while a doc still holds a stale line. The mod measures each open doc again before it answers, so a doc the model fixed opens the gate itself and no gate holds for good:
doc-drift-watch: stopped: 1 doc(s) still hold stale lines: README.md (1). Update them and run the command again; there is no way around this gate.
A git commit answers for its own files alone: the mod reads the index (git rev-parse --show-toplevel and git diff --cached --name-only -z) and lets the commit run when it holds none of the open docs, with one line saying how many still stand. A git commit -a, a -am and a commit with a pathspec after -- are not narrowed, because the index alone does not say what they commit. A push and a merge read no index, so every finding stands there.
There is no bypass and no one-time pass. The gate opens when the docs hold again, or when you set /doc-drift-watch mode note.
Command
/doc-drift-watch the status: on or off, the mode, and the docs still stale
/doc-drift-watch on | off on by default
/doc-drift-watch mode note report only, the default
/doc-drift-watch mode deny also stop git commit, push and merge while a doc is stale
Install
claude plugin marketplace add KilimcininKorOglu/claude-code-mods
claude plugin install doc-drift-watch@kilimcininkoroglu-mods
Function hooks are early access. Nothing loads without the flag. To keep it on, add this to ~/.claude/settings.json:
{ "env": { "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1" } }
After installing
- Install ripwire and put it on PATH. Without it each commit logs
the docs were not checked: ...once, and the commit runs as before. - Restart Claude Code.
What it can reach
Validated with claude plugin validate on Claude Code 2.1.278:
❯ ./register.ts hooks: session.start, command.run{command=doc-drift-watch}, tool.call{tool=Bash}, turn.complete, prompt.submit
❯ ./register.ts calls: $.command.register, $.fs.read (via isGone), $.process.run (via driftNow, repoRoot, stagedPaths), $.session.cwd (via beforeCommit, stagedPaths), $.sidebar.clear (via closeOne), $.sidebar.set (via toPerson), $.store.get, $.store.set (via runCommand, setMode), $.ui.log (via gate, report, toPerson)
Reach L2, runs processes.
1. Reads: the Bash command text, each open doc's own file; through ripwire, the repository's markdown, source and git history
2. Runs: git rev-parse, git diff --cached and ripwire --doc-drift, read-only, by argv: twice per commit, once per open doc at each turn's end and at a guarded command
3. Sends: a note to the model after the commit's result and at the next prompt, and one line to the transcript or the sidebar; nothing leaves the machine
4. Persists: in $.store, the on/off setting and the mode
5. Hostile input: the directory comes from the command text and reaches git only as the working directory, never through a shell; a doc path comes from ripwire's own output and reaches ripwire as one argv value
Limits
- ripwire checks file:line references, backticked symbol names,
= Nconstants and[N]array extents. Prose is not checked, and ripwire under-reports on purpose: a renamed symbol whose name still occurs elsewhere is not reported. - ripwire runs twice per commit, and once per open doc at each turn's end. In this repository one whole-repository run took 0.1 to 0.2 s, and a narrowed one less.
- A turn's end measures the open docs alone. A doc a commit did not touch and that went stale another way is found at the next commit, not at the turn's end.
--doc-drift=<doc>filters by path substring, so a run narrowed to one doc also reads a doc whose path holds that one. The answer is filtered by the doc's own path afterwards.- A commit through a script or an alias that hides
git commitis not seen. cd ~/xis not expanded: the root then comes from the session's directory.
Development
make install # eslint, typescript-eslint, typescript
make lint # complexity limit 10, fails the build above it
make typecheck # needs .claude/types/ from /plugin-types
make validate
make test # claude plugin test