← Статті Harupa
2026-08-23RU EN UA BY PL

CRM на Markdown, частина 2: 27 годин, 23 коміти й каталог провалів

У першій частині я показав, як поставити CRM на markdown-файлах за вечір. Тут — як вона будувалася насправді: з цифрами, хронологією і, головне, провалами. Провали — не додаток до історії, а її скелет: майже кожна частина системи з'явилася як відповідь на конкретну аварію, і без цих аварій устрій незрозумілий.

Контекст: я робив аутрич для свого відеогенератора — шукав перших користувачів і розмови з ними. Допомагав Claude Code. Усе, що нижче, сталося за 27 календарних годин 22–23 серпня 2026 року: 23 коміти, 16 карток контактів, 12 документів, 307 рядків коду. І кожен факт тут підтверджується файлом, комітом або дослівною цитатою — правило «нічого не додумувати» поширюється й на статті про систему.

Навіщо — і чому при трьох контактах

Систему я завів, коли контактів було три. Не тридцять — три. Формулювання з коміту, з якого все почалося: «зараз три живі контакти в трьох різних місцях (листування в телезі, профіль у LinkedIn, голосове), і жодного місця, де видно, чий хід і що обіцяно».

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

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

І третє, про ролі. Машина збирає, готує тексти й пам'ятає все. Людина вирішує і надсилає. Цю межу проведено жорстко і, як з'ясується нижче, недарма.

Устрій: дошку не вели руками жодного дня

Картка, дошка і скрипт народилися одним комітом. Це важливіше, ніж здається: рішення «джерело правди — frontmatter картки, таблиця — похідна» було ухвалене до того, як з'явився перший рядок таблиці. Дошку не довелося «переводити на генерацію» — її взагалі ніколи не вели руками.

Три рішення в устрої, які я вважаю переносними куди завгодно.

Два пороги простроченості, а не один. Свій хід горить через два дні, чужий — через п'ять. Обґрунтування етичне, а не технічне: свій борг горить швидше за чужий — людина не зобов'язана нам відповідати, а ми зобов'язані. Одна колонка «днів з останнього дотику» злила б ці дві метрики докупи й сховала б власну бездіяльність за чужим мовчанням.

Три стани гіпотези, а не два. Скрипт друкує «НЕ ПИТАЛИ» словом, а не нулем. «Не підтвердилося» і «не питали» — різні діагнози. Перевірилося на живих даних майже одразу: одна гіпотеза дала 0 підтверджень із 3 — і це виявився промах таргетингу, у вибірці просто не було жодної людини потрібного типу. Нуль, надрукований як нуль, прочитався б як спростування.

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

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

Провали. Порода перша: дані

Система запам'ятала неправду або втратила правду.

Чужа фраза стала голосом власника — у трьох файлах. Репліка співрозмовниці потрапила у профіль мого голосу як моя. Умова, яка це дозволила: дослівного логу листування не існувало, картку було зібрано переказом. Переказ втрачає автора — репліки без явного «хто сказав» злипаються в один голос, і злипання спливає через тижні, коли джерело вже не відновити. Закрито правилом «дослівно, а не переказом» і ⚠-врізкою в зіпсованих файлах — не видаленням: мовчки стерти помилку означає стерти й урок.

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

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

Порода друга: процес

Правило було — і не спрацювало.

Готовий текст поїхав під пост чужої людини. Найдорожчий промах: коментар, приготований для найціннішого входу у воронці, було опубліковано під постом іншої людини — до того ж позначеної «НЕ писати». Механізм чесний, і він не про неуважність: у картці лежало посилання на профіль, а коментар пишеться під пост. Це різні адреси, і різницю видно тільки в момент відправлення. Звідси сторож: якщо наступний крок — відправлення, а точної адреси немає, прогін падає, а на дошці горить «АДРЕСИ НЕМАЄ».

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

Мої поправки не записувалися нікуди. Рішення щодо продукту писалися в один файл, профіль голосу — в інший, факти — у картки. А поправки «як зі мною розмовляти» не писалися нікуди й лишалися в чаті — і ті самі помилки повторювалися. Розкрилося прямим запитанням: «як ти фіксуєш мої фідбеки, ти їх враховуєш?» Відповідь була «ніяк», і тієї ж години з'явився журнал поправок. Якщо в категорії знання немає файлу, знання живе в листуванні й помирає зі зміною сесії — або співробітника.

Стан процесу не жив ніде. Стан кожного контакту зберігався, а стан самого циклу — ні. Запитання «завтра відкрию нову сесію — чи зрозуміє вона, що тиждень ще не минув?» мало відповідь «ніяк». Так з'явився журнал прогонів: у процесу стан такий самий, як у запису, і він потребує власної таблиці.

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

Порода третя: інтерпретація

З правильних даних — неправильний висновок. Найдорожча порода.

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

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

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

Опитувальник збирав підтвердження нас самих. Перша версія анкети — шкали від 1 до 5 за нашим же списком болів. Людина вибере з нашого списку, ми отримаємо підтвердження себе й назвемо це дослідженням. Впіймано до розсилки — а розсилати збиралися по чужій мережі, тобто ціною помилки була чужа репутація. Список варіантів відповіді — це ваші гіпотези; агрегат за ними вимірює вас, а не ринок.

Біль 9 з 9 мало не став офером. Дев'ять із дев'яти підтвердили проблему — а троє з дев'яти вже розв'язали її безкоштовно локальним інструментом і звітували про успіх, не про біль. Лічильник підтверджень без розмітки «у кого вже розв'язано» продає чужий успіх як ваш попит.

Чотири скасування власних рішень

Цього в статтях про «як я побудував X» зазвичай немає, а для читача воно цінніше за удачі.

Що скасувалиЧерез скількиЧим спростовано
Висновок «його зупиняє продукт»2 хвилинифакт із життя людини: переїзд
«У суперечках пріоритет за власником»45 хвилинсам власник: «моя мета — ефективність, а не спроба мені догодити»
Відмова від окремого скіла на пошук7 годинкритерій був хибний: скіли діляться не за частотою, а за питанням, яке ставить людина
Правка пошукового рядкаодин прогінвимірювання: було 1 із 3, стало 0 із 3

Останній рядок — окремий урок. Лагодив я не той дефект, і правка зробила гірше. З'ясувалося це тільки тому, що промах було записано: без запису я лагодив би те саме ще раз. Запис негативного результату — і є механізм покращення; правку без виміряного «до» не відрізнити від ритуалу.

Сюди ж арифметика, яка забороняє A/B на моїх обсягах: формула n = 16σ²/δ² при базі відгуку 20% і бажаному прирості в 10 процентних пунктів вимагає 256 контактів на варіант. На сорока — шістдесяти ловиться тільки дворазовий ефект. Важливе застереження: уся ця кількісна база — з чужих каналів, B2B-пошти й американських холодних дзвінків; я переношу механізми, не цифри. Замість A/B — раунди по п'ять розмов на одному формулюванні з порогом рішення, оголошеним заздалегідь.

Правило без сторожа не виживає

Головний висновок усієї історії, і він зароблений, а не придуманий.

Щоразу, коли правило було записане прозою, його порушували. Включно з випадками, коли порушував той самий чоловік, який його написав, — у тому самому файлі, двома абзацами нижче. Вижили тільки правила, під які написано скрипт, що валить прогін. П'ять із восьми сторожів — пряма відповідь на аварію, яка вже сталася; жоден не написаний «на майбутнє, раптом знадобиться».

Сторож при цьому перевіряється мутацією: ламаєш картку навмисно — прогін зобов'язаний почервоніти рівно на ній; повертаєш — позеленіти. Зелений тест, який не можна зробити червоним, — декорація. Чесна цифра з оригіналу: мутацією я перевірив три сторожі з восьми, решта жили на слово. В опублікованій версії md-crm цей борг закрито — усі сторожі накриті 24 автотестами, і сам набір тестів перевірено мутацією: вимикаєш сторож — червоніють рівно його тести.

І фінальний штрих, що підтвердив правило на самому інструменті. Єдиний баг, який мовчки псував дані, жив у єдиному місці скрипта, не накритому тестом: магічний зсув «+24» при реальній довжині розділювача 22 символи. Перший прогін ішов іншою гілкою коду й виглядав справним, а кожен наступний врізався на два символи всередину попереднього рядка журналу. Вісім ітерацій скрипт обростав перевірками карток — і жодної перевірки самого себе.

Що з цього налаштування, а що скелет

Скелет: один файл на людину, стан у frontmatter, дошка похідна, сторожі валять прогін, журнал дослівний, стан циклу окремо від стану контактів. Це переноситься куди завгодно — включно з наймом: ATS відрізняється від CRM словником стадій, не архітектурою.

Налаштування: пороги 2 і 5 днів, контрольні точки 5/12/25 розмов, коди гіпотез. Мої значення — орієнтири із зазначеними джерелами, не константи; кожен добере свої.

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