Предупреждает, если файл недавно менял другой чат Claude Code

Пять модов - маленьких плагинов, которые меняют интерфейс Claude Code: показывают кэш и лимиты, прячут личное на записи экрана, рисуют прогресс цели и не дают двум чатам править один файл вслепую.
Из ролика на канале @amir_aktay.
В Claude Code:
/plugin marketplace add pewdredd/claude-code-mods
/plugin install cache-keeper@aktay-mods
Остальные ставятся так же, меняется только имя: collision-guard, goal-meter, next-steps, recording-mode. Можно поставить любые по отдельности.
| Мод | Что делает | Как пользоваться |
|---|---|---|
| cache-keeper | Над полем ввода: сколько осталось жить кэшу промпта (60 минут), заполнение контекста, лимиты 5 часов и недели, цена сессии. За 5 минут до остывания кэша - уведомление. Кнопка «перенести сессию»: Claude пишет сводку сессии, потом кнопка «Очистить и продолжить» сама делает /clear и отправляет сводку в чистый чат. Пока включён режим записи, здесь же горит красный «● REC» | работает сам |
| next-steps | После ответа - три кнопки «что дальше», одним нажатием отправляют следующий запрос | работает сам, /next-steps - вкл/выкл |
| recording-mode | Пока пишешь экран, прячет в транскрипте почты, ключи, телефоны и суммы. Claude при этом видит всё как есть. Метку «● REC» рисует cache-keeper - ставь их вместе | /record on, /record off |
| goal-meter | Разбивает цель на шаги, отдаёт агенту и показывает полоску прогресса. После каждого ответа отмечает готовые шаги | /goal-meter <цель>, /goal-meter off |
| collision-guard | Если файл в последние 30 минут правил другой чат Claude Code - останавливает правку и просит агента спросить тебя. Повторная правка проходит | работает сам |
CACHE_TTL_MS в cache-keeper/hooks/register.tsx.Опиши словами, что мод должен показывать и когда, и попроси Claude Code его собрать. Любой мод отсюда - хороший образец: три файла в папке плагина и один register.tsx.
hooks/register.tsx 54 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { CollisionGuardMark } from '../types'
5
6// Collision Guard: каждый чат отмечает в общем хранилище, какие файлы он правил.
7// Если файл недавно правил ДРУГОЙ чат — первая попытка правки останавливается
8// с предупреждением, вторая проходит (пользователь сказал «продолжай»).
9const WINDOW_MS = 30 * 60 * 1000
10const warned = atom({ plugin: 'collision-guard', key: 'warned' } as const, [])
11
12const keyOf = (path: string) => `edit:${path}`
13
14async function guard($: EngineInterface, path: string): Promise<string | null> {
15 const mark = (await $.store.get(keyOf(path))) as CollisionGuardMark | undefined
16 if (!mark) return null
17 const me = await $.session.id()
18 const age = (await $.clock.now()) - mark.at
19 if (mark.sessionId === me || age > WINDOW_MS) return null
20 if ((await read($, warned)).includes(path)) return null
21
22 await update($, warned, list => [...list, path])
23 const minutes = Math.max(1, Math.round(age / 60000))
24 $.ui.toast(`Collision Guard: ${path} менял другой чат ${minutes} мин назад`)
25 return (
26 `Collision Guard: файл ${path} ${minutes} мин назад правил другой чат Claude Code. ` +
27 `Правка остановлена. Перечитай файл, скажи пользователю, что изменилось, и спроси, продолжать ли. ` +
28 `Если пользователь скажет продолжать — повтори правку, второй раз она пройдёт.`
29 )
30}
31
32async function remember($: EngineInterface, path: string) {
33 const value: CollisionGuardMark = { sessionId: await $.session.id(), at: await $.clock.now() }
34 await $.store.set(keyOf(path), value)
35}
36
37export const register: Register = on => {
38 on('tool.call', { tool: 'Edit' }, async ($, e, next) => {
39 const deny = await guard($, e.file_path)
40 if (deny) return { deny }
41 const ran = await next(e)
42 if (ran.deny === undefined && !ran.isError) await remember($, e.file_path)
43 return ran
44 })
45
46 on('tool.call', { tool: 'Write' }, async ($, e, next) => {
47 const deny = await guard($, e.file_path)
48 if (deny) return { deny }
49 const ran = await next(e)
50 if (ran.deny === undefined && !ran.isError) await remember($, e.file_path)
51 return ran
52 })
53}
54types/index.d.ts 8 lines1export type CollisionGuardMark = { sessionId: string; at: number }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'collision-guard': { warned: string[] }
6 }
7}
8