← Статьи 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 разные поля при одинаковом скелете.

И третье, про роли. Машина собирает, готовит тексты и помнит всё. Человек решает и отправляет. Эта граница проведена жёстко и, как выяснится ниже, не зря.

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

Карточка, доска и скрипт родились одним коммитом. Это важнее, чем кажется: решение «источник правды — фронтматтер карточки, таблица — производная» было принято до того, как появилась первая строка таблицы. Доску не пришлось «переводить на генерацию» — она не велась руками вообще никогда.

Три решения в устройстве, которые я считаю переносимыми куда угодно.

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

Три состояния гипотезы, а не два. Скрипт печатает «НЕ СПРАШИВАЛИ» словом, а не нулём. «Не подтвердилось» и «не спрашивали» — разные диагнозы. Проверилось на живых данных почти сразу: одна гипотеза дала 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 символа. Первый прогон шёл по другой ветке кода и выглядел исправным, а каждый следующий врезался на два символа внутрь предыдущей строки журнала. Восемь итераций скрипт обрастал проверками карточек — и ни одной проверки самого себя.

Что из этого настройки, а что скелет

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

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

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