Локальний ШІ-редактор для рукопису: Gemma 4 + pi agent + VS Code

Як зібрати редактора, бета-рідера й continuity-охоронця, які живуть у твоєму репозиторії, читають твою біблію персонажів і жодного байта твого рукопису не відправляють у хмару.

Навіщо це взагалі

У мене є рукопис. Він недописаний, він мій, і він нікуди не має їхати.

Коли я вставляю розділ у чат хмарної моделі, стається дві неприємні речі. Перша очевидна: текст ненаписаної книжки їде на чужі сервери. Друга неочевидніша й, як на мене, гірша — модель не має контексту. Вона не знає, що Цуґі втратив задню ліву лапу. Не знає, що Макс ніколи не каже «мені сумно». Не знає, що дидактика у фіналі розділу — це не стилістичний нюанс, а порушення контракту книжки. Тому вона робить те, що вміє найкраще: пише ввічливу рецензію, у якій усе «загалом добре, але можна попрацювати над темпом».

Це не редактура. Це шум із гарними манерами.

Те, що я опишу нижче, — спроба зробити навпаки. Не «розумну модель», а дурну модель у дуже розумному оточенні. Локальна Gemma 4, агент pi, який живе в терміналі й у VS Code, і купка markdown-файлів, які роблять усю справжню роботу: goals.md, style.md, characters.md. Модель — двигун. Контекст — керма й гальма. Без керми двигун просто гучний.

З чого це складається

Gemma 4 — відкрита сім'я моделей Google під Apache 2.0. Для нашої задачі важливі три речі: вікно контексту до 256K токенів (розділ, стиль-гайд, біблія персонажів і три попередні розділи влазять одночасно), нативний виклик функцій, і те, що 12B крутиться на ноутбуці з 16 ГБ пам'яті. Понад 140 мов, українська зокрема; це не робить її бездоганним стилістом української прози, але робить придатним детектором і асистентом.

pi — мінімалістичний агент для терміналу (@earendil-works/pi-coding-agent). Відкритий, приймає будь-який OpenAI-сумісний ендпойнт, і має рівно те, що потрібно: чотири базові інструменти (read, write, edit, bash), файли контексту AGENTS.md, скіли за стандартом Agent Skills (SKILL.md з фронтматером), шаблони промтів як slash-команди, розширення на TypeScript. Він не тягне за собою екосистему, не має вбудованого plan mode, тудушок і підагентів — і це перевага: ми будуємо не IDE для коду, а редакційний цех.

VS Code — просто вікно. Ліворуч рукопис, праворуч агент, посередині diff. Нічого магічного.

Ключова ідея, яку я хочу, щоб ти забрав звідси, навіть якщо не поставиш нічого: скіли й контекстні файли — це і є продукт. Модель замінна. Промти — ні.

Частина 1. Встановлення

macOS

# Node.js (потрібен для pi)
brew install node

# Ollama — рантайм для локальних моделей
brew install --cask ollama
open -a Ollama

# Модель. Перевір актуальні теги на ollama.com/library — вони змінюються.
ollama pull gemma4:12b
# на M-чипі з 32+ ГБ об'єднаної пам'яті можна брати важче:
# ollama pull gemma4:26b-a4b

# Сам агент
npm install -g @earendil-works/pi-coding-agent
pi --version

На Apple Silicon Ollama використовує Metal; 12B у 4-бітній квантизації комфортно живе на 16 ГБ, 26B MoE — від 32 ГБ. Якщо машина слабша — E4B працює, але для стилістичного аналізу української прози вона вже на межі корисності. Чесно: використовуй її для лічильників і канону, а не для суджень про голос.

Windows

# Node.js
winget install OpenJS.NodeJS.LTS

# Ollama
winget install Ollama.Ollama

# Перезапусти PowerShell, щоб підхопився PATH
ollama pull gemma4:12b

# Агент
npm install -g @earendil-works/pi-coding-agent
pi --version

Три граблі, на які я наступив:

PATH. Після winget install глобальні npm-бінарники лежать у %APPDATA%\npm. Якщо pi не знайдено — перезапусти термінал, а не гугли.

Кодування. PowerShell любить cp1251 у пайпах, і твоя українська перетворюється на кракозябри. Постав у профіль: [Console]::OutputEncoding = [Text.Encoding]::UTF8.

Закінчення рядків. Git на Windows конвертує LF → CRLF, і diff у рукописі стає нечитабельним. У корені проєкту: git config core.autocrlf false.

WSL2 — робочий варіант, але тоді Ollama краще лишити на хості й ходити в нього по мережі, інакше GPU-прокидання перетвориться на окремий вечір твого життя.

Підключаємо Gemma до pi

pi не знає про твій Ollama, поки ти йому не скажеш. Створюємо ~/.pi/agent/models.json (на Windows — %USERPROFILE%\.pi\agent\models.json):

{
"providers": {
"ollama": {
"baseUrl": "http://localhost:11434/v1",
"api": "openai-completions",
"apiKey": "ollama",
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false
},
"models": [
{
"id": "gemma4:12b",
"name": "Gemma 4 12B (local)",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 256000,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
}
}

apiKey — фікція: Ollama його ігнорує, але pi вимагає, щоб модель вважалася автентифікованою й з'явилася в /model. Блок compat — тому що локальні OpenAI-сумісні сервери часто не розуміють роль developer і поле reasoning_effort; без цих двох прапорців ти отримаєш загадкові 400-ті й проведеш вечір не з книжкою.

Файл перечитується щоразу, коли ти відкриваєш /model. Рестарт не потрібен.

Окрема, дуже важлива дрібниця: підніми вікно контексту в самому Ollama. За замовчуванням він ріже контекст до кількох тисяч токенів, і твої characters.md тихо випадуть з пам'яті моделі — а ти сидітимеш і думатимеш, чому агент забув про лапу. Або через змінну середовища OLLAMA_CONTEXT_LENGTH, або через власний Modelfile з PARAMETER num_ctx. Перевір: якщо агент «забуває» стиль-гайд на другому розділі — справа майже завжди в цьому, а не в моделі.

VS Code

Тут два табори, і обидва робочі.

Табір «термінал у редакторі» — просто Ctrl+ і piв інтегрованому терміналі, поруч із відкритимch07.md`. Мінус конфігурації, нуль магії. Я живу тут.

Табір «розширення» — у маркетплейсі є кілька розширень для pi (шукай pi coding agent): бічна панель із чатом, стрімінг, картки викликів інструментів, inline-дифи, чекпойнти. Деякі просто запускають pi як термінал з мостом до стану редактора, деякі говорять з ним по RPC (pi --mode rpc). Обери одне, не став три — вони конфліктують за той самий CLI.

Що варто налаштувати незалежно від табору, у .vscode/settings.json:

{
"editor.wordWrap": "on",
"editor.rulers": [100],
"files.eol": "\n",
"[markdown]": { "editor.quickSuggestions": false },
"workbench.editor.enablePreview": false
}

І постав щось для порівняння версій розділу — вбудований git-панель цілком годиться, якщо рукопис під git. А він має бути під git. Кожна правка агента — комміт. Це не про програмування, це про можливість сказати «ні» й повернутися назад.

Частина 2. Архітектура цеху

book-editor/
├── AGENTS.md ← конституція: що агент може і чого не сміє
├── manuscript/ ← джерело правди. ch01.md, ch02.md …
├── context/
│ ├── goals.md ← навіщо ця книжка існує
│ ├── style.md ← стилістичний контракт
│ └── characters.md ← біблія персонажів
├── .pi/
│ ├── settings.json ← провайдер, модель, компакція
│ ├── skills/
│ │ ├── manuscript-metrics/SKILL.md ← оцінка й категоризація
│ │ ├── line-editor/SKILL.md ← порядкова редактура
│ │ ├── beta-reader/SKILL.md ← симуляція читача
│ │ └── continuity-guard/SKILL.md ← канон і хронологія
│ └── prompts/
│ ├── review.md ← /review ch07
│ ├── harsh.md ← /harsh ch07
│ ├── polish.md ← /polish ch07
│ └── book.md ← /book
├── scripts/textstats.py ← детерміновані лічильники
└── reports/ ← усе, що агент народжує

Три речі, які тут неочевидні, але критичні.

Перше: розділення контексту й навичок. context/* — це що є істиною про твою книжку. .pi/skills/* — це як дивитися. Одне змінюється разом із книжкою, друге — разом із твоїми уявленнями про редактуру. Змішаєш — і за місяць не зрозумієш, чому агент раптом почав хвалити те, що вчора рубав.

Друге: прогресивне розкриття. pi тримає в системному промті лише name + description кожного скіла. Повний SKILL.md вантажиться тільки тоді, коли скіл потрібен, — сам собою або примусово через /skill:назва. Це означає, що ти можеш написати дуже довгу, дуже деталізовану рубрику, і вона не з'їдатиме контекст на кожному повідомленні. Це, власне, і є вся інженерія: не «промт», а керована подача промтів.

Третє: агент за замовчуванням нічого не пише в manuscript/. Ніколи. Його вихід — файл у reports/. Рукопис змінює тільки людина, або агент за явною командою «застосуй патч». Це записано в AGENTS.md як залізне правило, і воно там не для краси: модель, яка має доступ до edit, рано чи пізно вирішить «допомогти» й перепише твій найкращий абзац на середньоарифметичний.

Частина 3. Контекстні файли — справжня робота

Тут я скажу неприємне: якість твого ШІ-редактора на 80% визначається цими трьома файлами й на 20% — моделлю. Люди роблять навпаки: женуться за 70B-моделлю й годують її фразою «оціни цей розділ».

goals.md — навіщо існує книжка

Контракт цілей. Він відповідає на питання, на які модель інакше вигадає власні відповіді: якого обсягу книжка, для кого, яку обіцянку дано читачеві, яка емоційна крива по розділах, і — найважливіше — які ходи заборонені.

Секція заборон робить більше, ніж усі похвали разом. «Мораль, промовлена вголос наприкінці розділу» → автоматична оцінка ≤3 за віссю GOAL_FIT, без обговорення. Модель не має права сказати «ну, це мило написано». Вона має сказати: «порушено goals.md §6, рядок 141».

Так абстрактний смак перетворюється на перевірювану умову.

style.md — стилістичний контракт

Головна функція цього файлу — захистити тебе від моделі. Будь-яка LLM, залишена наодинці з прозою, тягне її до середнього: розбиває довгі речення, вирівнює фрагменти, дописує пояснювальні прислівники, полірує голос до нейтрального. Вона робить твій текст правильнішим і мертвішим.

Тому в style.md пишеться не «пиши добре», а точні дозволи й заборони:

фрагменти дозволені, не більше двох підряд — тобто агент не має права позначати фрагмент як помилку;

середня довжина речення 9–14 слів, не більш ніж одне >25 слів на сторінку;

атрибуція діалогу — тільки «сказав/спитав»; прислівник при дієслові мовлення — помилка;

чорний список штампів, буквальний, рядками;

і, найцінніше — калібрувальні приклади: пара «добре / погано» з розбором, що саме зламано в поганому, з посиланням на номер правила.

Один такий приклад дає більше, ніж сторінка інструкцій. Модель — це машина аналогій; годуй її аналогіями.

І остання секція style.md: як агент репортує. Формат зауваження — «цитата → номер правила → чому не працює тут → одна конкретна альтернатива». Без альтернативи зауваження не зараховується. Це вбиває 90% води на виході.

characters.md — біблія персонажів

Пишеться не як есей, а як машинно перевірювані картки. Ключове поле — факти (canon): ліворукість, шрам, годинник, що стоїть, задня ліва лапа. Це те, проти чого continuity-guard звіряє кожен розділ.

Далі — голос і не каже ніколи. Друге сильніше за перше. «Макс ніколи не каже "я відчуваю, що…"» — це умова, яку модель може перевірити, на відміну від «Макс говорить по-дитячому щиро».

І функція персонажа в сцені, з тестом на непотрібність:

Якщо сцену з Цуґі можна переписати з собакою і нічого не зміниться — сцена мертва.

Такі тести — найдорожче, що є у файлі. Вони перетворюють агента з коментатора на детектор.

Окремо: blind-line test. Агент бере п'ять реплік без атрибуції й намагається вгадати мовця. Точність нижче 60% — діагноз «голоси злилися». Метрика, яку неможливо підробити компліментом.

Частина 4. Скіли

manuscript-metrics — оцінка й категоризація

Серце системи.

Крок 1. Об'єктивні лічильники. Не моделлю — скриптом. LLM не вміє рахувати; вона вміє вигадувати правдоподібні числа, і це найгірша її властивість у ролі редактора. Тому scripts/textstats.py рахує слова, довжину речень, відсоток речень >25 слів, частку діалогу, абстрактні іменники на 1000 слів, «сказав + прислівник», штампи зі списку, слова-фільтри й перевикористані леми. Модель отримує факти і думає над ними, замість фантазувати.

Крок 2. Суб'єктивні осі, 0–10:

HOOK · STAKES · PACE · SCENE_VS_SUMMARY · VOICE · DIALOGUE · EMOTION · CLARITY · CANON · GOAL_FIT

Три правила, без яких уся конструкція розсиплеться в улесливість:

Спершу цитата, потім число. Не можеш знайти цитату-доказ — не маєш права ставити оцінку.

Антиінфляційна квота: не більш ніж трьом осям можна поставити ≥8 в одному розділі без окремого обґрунтування. Без цієї квоти будь-яка модель через три розділи почне ставити вісімки всьому, бо її вчили бути приємною.

Якірні описи. У references/rubric.md для кожної осі є поведінкові якорі: STAKES 0–2 — «провал героя нічого не коштує»; STAKES 8–10 — «ціна конкретна і б'є точно в рану персонажа з characters.md». Плюс питання-детектор: «Що станеться, якщо він просто піде додому?» Якщо відповідь «нічого» — вище трійки не піднімайся.

Крок 3. Категоризація. Один ярлик на розділ, без «і те, і те»:

Ярлик

Що означає

Дія

KEEP

середнє ≥7.5, жодної осі <6

не чіпати

TIGHTEN

суть сильна, темп провисає

різати, не переписувати

REWRITE-SCENE

сцена є, але вона не про те

переписати з іншою метою сцени

MERGE

розділ не має власної функції

злити з сусіднім

CUT

сюжет не постраждає

прибрати

STRUCTURAL

проблема не в розділі, а в книжці

ескалювати

Ярлик CUT існує саме для того, щоб його ставили. Агент, який ніколи не пропонує викинути розділ, — це не редактор, а вболівальник.

Крок 4. Пріоритезація. Не «двадцять зауважень», а три найбільші проблеми й одна перша дія. Автор не має бюджету уваги на двадцять пунктів.

line-editor — порядкова редактура

П'ять проходів у фіксованому порядку: вирізання → штампи → ритм → діалог → точність. Порядок не декоративний: якщо спершу шліфувати ритм, а потім різати, ти шліфував те, чого вже нема.

Два запобіжники:

Ліміт втручання: одна правка на 60 слів. Якщо їх виходить більше — розділ не редагують, його переписують; агент зобов'язаний зупинитися й ескалювати до manuscript-metrics.

Заборона подовжувати. Правка, що додає слів, потребує окремого обґрунтування.

Вихід — не переписаний файл, а звіт із блоками - рядок / + заміна / чому і секцією ## Патч у кінці, яку автор може прийняти однією командою. Рукопис лишається недоторканим.

І червона лінія, вписана прямо в скіл: якщо після трьох проходів текст став «чистішим, але мертвішим» — агент має сказати це вголос і зупинитися. Стерильна проза — гірша помилка, ніж штамп.

beta-reader — це не редактор

Найпоширеніша помилка: просити одну модель бути і редактором, і читачем. Виходить кентавр, який каже «мені було нецікаво, раджу посилити конфлікт» — тобто не робить ні першого, ні другого.

Тому бета-рідер має власний скіл із жорсткими заборонами: жодних порад, жодного аналізу стилю (слова «метафора», «фокалізація», «арка» — заборонені), не забігати наперед, не бути ввічливим.

Метод — карта уваги. Текст ріжеться на блоки по ~200 слів, кожен отримує маркер:

🔥 не можу зупинитися · 👀 читаю далі · 😐 за інерцією · 💤 почав перегортати · ❓ не зрозумів · 🙄 не вірю · 🚪 тут я закрив би книжку

І шість обов'язкових питань у кінці, з яких три найважливіші:

Що я думаю, буде далі? Якщо вгадав точно — це погана новина.

Чи перегорнув би я сторінку о 22:30, коли мама вже сказала спати? Так або ні. Одне слово. Без «залежить».

Що застрягло в голові через десять хвилин? Якщо нічого — це найважливіший рядок усього звіту.

Три режими: cold (без контексту), serial (пам'ятає попередні розділи приблизно, як жива людина), hostile (шукає привід кинути книжку). Останній — найкорисніший. Запускай його перед тим, як щось комусь показувати.

continuity-guard — пам'ять книжки

Автор пам'яті не має. Ти справді не пам'ятаєш, чи сів ліхтарик у п'ятому розділі.

Скіл тримає reports/continuity-ledger.md як живий документ: таблиця хронології (розділ → день історії → час доби), інвентар предметів, стан ран — фізичних і емоційних (синець не зникає за ніч; сором теж), правила світу (Велес з'являється тільки на порогах — двері, міст, підземний перехід, поріг сну).

І реєстр чеховських рушниць із трьома станами: PENDING (введено з натиском, ще не вистрілило), FIRED (відпрацювало), UNPLANTED — вистрілило без введення. Останнє — найгірший клас помилки, рояль у кущах, і саме його автор ніколи не бачить сам.

Правило: агент не виправляє тихо. Він виводить конфлікт назовні з двома посиланнями (де встановлено / де порушено) і рекомендацією, яку версію вважати канонічною. Рішення ухвалює людина.

Частина 5. Як це виглядає в житті

Промти-шаблони лежать у .pi/prompts/ і стають slash-командами. $ARGUMENTS підставляє те, що ти написав після команди.

$ cd book-editor && pi

> /review ch07
→ читає goals/style/characters
→ bash: python scripts/textstats.py manuscript/ch07.md
→ skill:manuscript-metrics → reports/metrics-ch07-2026-07-12.md
→ skill:beta-reader (serial) → reports/beta-ch07-2026-07-12.md
→ skill:continuity-guard → тільки конфлікти рівня A

PACE 3 · STAKES 4 · GOAL_FIT 4 · ярлик TIGHTEN
Читач відпав на рядку 137 — зрозумів, що пса тут не знайдуть.
Канон, рівень A: ch07:L88 — «передня лапа». У ch02 — задня ліва.
Перша дія: рядки 40–96 (тиждень переказу) → одна сцена одного дня.

Далі — людський крок. Ти дивишся на це, лаєшся, погоджуєшся з двома пунктами з трьох, третій ігноруєш, бо агент не розуміє, навіщо тобі та млявість (а вона потрібна — контраст перед розділом 8). Переписуєш сам. Потім:

> /polish ch07 # порядкова, з патчем, рукопис не чіпає
> /harsh ch07 # ворожий читач: одне місце, після якого він закрив би книжку
> /book # рівень книги: де ламається емоційна крива, де три TIGHTEN підряд

/book — та штука, заради якої все й будувалося. Коли є десять metrics-*.md, агент збирає скоркард по всій книжці й бачить те, чого не бачиш ти: не «розділ 7 слабкий», а «розділи 6–8 усі TIGHTEN, отже, проблема не в них — у вас арка стоїть на місці три розділи поспіль».

Локальний агент з цього боку особливо приємний: прогнати всі 22 розділи через метрики — це кілька годин роботи вентилятора й нуль євро. Хмарна модель за такий прогін виставила б рахунок, і ти б робив це раз на місяць, а не щовечора.

Частина 6. Чесні межі

Скажу прямо, бо інакше стаття перетвориться на рекламу.

Gemma 4 12B не є вправним стилістом української художньої прози. Вона не відчує, що речення провисає на півтакту. Вона не почує ритм. Її пропозиції заміни штампів часто будуть гіршими за штампи. Не бери її правки як правки — бери як детекцію. Вона показує, де дивитися. Куди рухатись — вирішуєш ти.

Числа — це не істина. PACE 3 — не вимір, а структурована думка моделі про текст. Її цінність не в точності, а в повторюваності: та сама рубрика, застосована до 22 розділів, дає порівнянну картину, і саме порівняння між розділами (а не абсолютне число) щось означає.

Модель улещуватиме. Постійно. Антиінфляційна квота, обов'язкові цитати-докази й заборона писати похвалу як буфер — це не стилістичні забаганки, це протези проти вбудованої сикофантії. Перевіряй їх час від часу: підсунь агентові навмисно поганий розділ. Якщо він поставив 7 — твої скіли зламані, а не рукопис хороший.

І головне. Ця система нічого не пише за тебе. Вона не має думки про те, чи має Макс пробачити батька. Вона — дзеркало, поставлене під незручним кутом, і рівно настільки чесне, наскільки чесно ти заповнив goals.md.

Що забрати з собою

Не модель. Не агента. Не VS Code.

Забери ідею: перетвори свій смак на файл. Усе, що ти знаєш про власну книжку, але тримаєш у голові — заборонені ходи, тести на непотрібність, «Макс ніколи не каже "я відчуваю"», емоційна крива по розділах — усе це можна записати як умови, які машина здатна перевірити. І тоді машина стає корисною не тому, що вона розумна, а тому, що вона невтомна і їй байдуже, о котрій годині ти попросив.

Ті три markdown-файли переживуть Gemma 4, переживуть pi, переживуть VS Code. Це твоя редакційна пам'ять у формі, яку можна віддати наступному інструменту за п'ять хвилин.

Велес водив худобу через пороги і знав про кожну тварину, скільки в ній молока і скільки норову. Він не любив її й не хвалив. Він просто вів облік — і саме тому стадо доходило.

Твій рукопис заслуговує на такого пастуха. Але ім'я на обкладинці лишиться твоє.

посилання на проект

book-editor-kit.zip