Перейти к содержимому
GitRiverGitRiver
EN
Навигация

Формат записей знания

Формат записей знания: из чего состоит запись, как она хранится рядом с кодом и как её читает помощник

Записи знания живут файлами в каталоге .gitriver/knowledge/ основного репозитория — не в отдельной базе и не в вики. Поэтому у записи есть ветка, рецензирование, CODEOWNERS, защита ветки и откат, и она едет в той же ветке, что и породившее её изменение кода: одна рецензия, одно одобрение, один откат.

Один файл — одна запись. Два человека, правящие разные записи, правят разные файлы, и конфликтов слияния между ними нет.

Устройство файла

Markdown-тело под YAML-заголовком (frontmatter) между строками ---:

---
id: 0192ec1a-7b1a-7def-abcd-0987654321fe
origin: agent
contract:
  - mechanism: on_query
    params:
      words: ["развёртывание", "откат"]
  - mechanism: on_tool
    params:
      name: run_migration
links:
  - 0192ec1b-0000-7000-8000-000000000001
---

Первая законченная мысль записи — она же фрагмент показа в списках.

Дальше — обоснование: почему так, что попробовать, когда не работает.

Поля заголовка

Поле Обязательное Смысл
id да Устойчивый идентификатор записи, UUIDv7. Живёт внутри файла: переживает переименование файла и любую правку текста. Счётчики показов и чтений ведутся по нему
contract нет Контракт извлечения: массив объявлений {"mechanism": ..., "params": {...}}. Пустой означает «не всплывать никогда» — такая запись считается недостижимой, но хранится
origin нет Происхождение: human или agent. По умолчанию agent. Поле в файле — заявление клиента, а не удостоверение: гарантией происхождения является одобрение запроса на слияние, а не заголовок
links нет Связи с другими записями — список их устойчивых идентификаторов
summary нет Явный фрагмент показа. См. правило переопределения ниже

Имена файлов человекочитаемы и меняются свободно — идентификатор в пути не нужен и потому не теряется. Записи, которые собирает сам сервер (см. «Как знание выходит наружу»), кладутся под именем <id>.md; переименовать такой файл потом можно свободно.

Тело и фрагмент показа

Фрагмент для показа в списках выводится из тела: первый абзац до пустой строки, с потолком 512 байт по границе символа.

summary переопределяет выведенный фрагмент, но обязано говорить иначе, чем тело. Переопределение, повторяющее выведенный фрагмент (после нормализации пробелов), отмечается в диагностике разбора, и используется выведенный фрагмент. Пустой по смыслу или упёршийся в потолок выведенный фрагмент тоже виден в диагностике разбора — исправлять его следует в самой записи.

Строго на запись, терпимо на чтение

Правило решается по происхождению данных:

  • создание записи через API — строгий разбор: неизвестный ключ отклоняется на месте как опечатка;
  • разбор файла из репозитория — терпимый: файл мог записать инструмент более новой версии, чем сервер.

Терпимость различает непонятое и ошибочное:

  • непонятое — незнакомый этой версии ключ или механизм. Запись принимается целиком, объявленные механизмы сохраняются как написаны, а запись считается недостижимой, пока сервер не научится этим механизмам; после обновления сервера она оживает сама, без пересборки указателя. Неизвестный ключ называется вместе с ближайшим известным: «ключ „on_trigers“ не известен, ближайший известный — „contract“» — это опечатка правившего руками; «похожих нет» — запись новее версии сервера;
  • ошибочное — негодное значение известного поля (id не UUIDv7), файл крупнее предела, дубликат идентификатора в пачке, пустое тело. Такая запись пропускается, остальные разбираются дальше: один сломанный файл не мешает остальным.

Непонятые механизмы сводятся в отчёте разбора: «3 записи требуют механизм „on_path“, которого эта версия не умеет». По такой строке администратор видит, что серверу пора обновиться.

Отчёт разбора приходит автору в ответ на отправку (push) — строками remote: рядом с выводом git push (по HTTP и по встроенному SSH-серверу): по строке на замечание к файлу, «путь: причина», и строкой-сводкой на каждый непонятый механизм. Отправка при этом проходит.

Пределы

Предел Значение
Размер одного файла 64 КБ
Суммарный размер пачки разбора 10 МБ

Превышение — диагностика и пропуск файла, а не отказ в отправке.

Оба предела действуют и на входе. Черновик проверяется по размеру будущего файла вместе со служебным заголовком, а не только текста: принятый черновик всегда можно доставить. Пачка при сборке режется по пределу разбора: остаток уезжает следующей пачкой, а не отказом.

Разбор при отправке

Указатель знания наполняется двумя источниками: черновики пишутся прямо в него, файлы .gitriver/knowledge/ разбираются при приёме изменений — только изменённые в этой отправке файлы, содержимое читается из нового коммита.

  • Разбор знания никогда не валит отправку: любая ошибка — запись в журнал.
  • Файловые записи попадают в указатель только с ветки по умолчанию. Знание ветки, которая ещё не слита, видно команде черновиком.
  • Разбор при этом идёт при любой отправке: об опечатке в записи автор узнаёт сразу, а не после слияния.
  • Слияние запроса через интерфейс или очередь слияния тоже разбирается: знание становится доступным сразу по слиянию запроса, не дожидаясь следующей отправки в ветку по умолчанию.
  • Удаление файла снимает запись указателя только на ветке по умолчанию. Если указатель на миг разошёлся с деревом, расхождение снимается само при следующем чтении, без действий администратора.
  • Чтение знания всегда идёт из указателя, а не из файлов ветки: черновики одной ветки видны и из других ещё до слияния. Выдача фильтруется по правам вызывающего на каждый репозиторий.

Как знание выходит наружу

Агент не коммитит каждую мысль. Он пишет черновик — черновик сразу виден всей команде с доступом к репозиторию и помечен непроверенным, — а наружу знание выходит пачкой: все накопленные в одной ветке черновики становятся файлами одним коммитом и проходят одну рецензию.

Путей два, и первый основной.

  1. В ту же ветку, что и код. Файлы кладутся в ветку, где агент работал: знание приезжает в запросе на слияние разработчика — одна рецензия, одно одобрение, отдельного шага нет вовсе.
  2. Отдельной веткой и своим запросом на слияние. Ветка называется knowledge/<исходная ветка>-<метка>. Запасной путь: нужен, когда исходной ветки уже нет (слита и удалена) или знание не привязано к изменению кода.

Своего порядка одобрения у знания нет ни на одном пути: наружу оно выходит обычным запросом на слияние, и защита ветки, CODEOWNERS, требуемые одобрения и проверки слияния действуют на него так же, как на любой другой. Запись файлов сервером подчиняется защите ветки наравне с отправкой: в ветку, куда отправка запрещена, пачка не ляжет.

Происхождение запроса ставит сервер

Запрос на слияние, открытый сборкой знания, помечен машинным происхождением. Пометку ставит сервер: в теле запроса на создание запроса на слияние такого поля нет, и объявить свой запрос машинным клиент не может. То же правило, что у машинных комментариев к запросам на слияние.

Что происходит с черновиком

Ничего — до одобрения. Черновик помечается доставленным (куда, когда, в каком запросе ждёт) и второй раз в пачку не попадает, но продолжает существовать как черновик: если запрос отклонят, а ветку удалят, знание не потеряется.

Снимает черновик попадание его файла в ветку по умолчанию. Если пачка ушла прямо в ветку по умолчанию (небольшая команда работает без веток), это происходит сразу, и запроса на слияние не появляется вовсе. Запись указателя при этом остаётся той же — идентификатор общий, поэтому счётчики показов и чтений переживают превращение черновика в файл.

Интерфейс

Знание читается и пишется через /api/v1; чтение всегда идёт из указателя, а не из файлов дерева.

Вызов Что делает
GET /repos/{owner}/{name}/knowledge Указатель репозитория: {items, partial?} — записи с контрактом и признаком достижимости
GET /repos/{owner}/{name}/knowledge/drafts Черновики репозитория — все, а не только свои; страница {items, next_cursor} (per_page, after)
POST /repos/{owner}/{name}/knowledge/drafts Записать черновик
DELETE /repos/{owner}/{name}/knowledge/drafts/{id} Снять черновик
POST /repos/{owner}/{name}/knowledge/drafts/deliver Собрать накопленные в ветке черновики в файлы
POST /repos/{owner}/{name}/knowledge/shown Отметить показ пачки записей
POST /repos/{owner}/{name}/knowledge/entries/{id}/read Отметить, что запись открыли
GET /repos/{owner}/{name}/knowledge/stats/me Мои счётчики показов и чтений: {items, partial?}
GET /repos/{owner}/{name}/knowledge/stats/team Сведение по команде, та же форма (Pro). Строка есть у КАЖДОЙ записи указателя, в том числе у непоказанной — с нулями
GET /repos/{owner}/{name}/knowledge/stats/dead Разбор мёртвой памяти (Pro): {items, partial?, min_shown, min_age_days}. У каждой строки ailment — shown_not_read, unreachable (с unmet_mechanisms) или never_shown. Пороги задаются min_shown и min_age_days, применённые возвращаются в ответе
GET·PUT·DELETE /admin/knowledge/limits/{users|groups}/{id} Предел черновиков на владельца: просмотр и снятие — в любой редакции, заведение и изменение (PUT) — редакция Max

Показ отмечается пачкой — до 256 записей за вызов.

Отмечать показ можно так часто, как он случается на самом деле: предела частоты этого вызова (600 обращений в минуту на токен) хватает с запасом. Чтение счётчиков всегда точное, даже сразу после отметки показа.

Указатель отдаётся целиком, без разбиения на страницы, — отбор по контракту делает потребитель. Действует защитный потолок в тысячу записей; если выдачу обрезали, ответ несёт оговорку partial: "truncated". Тем же потолком и той же оговоркой ограничены сведения счётчиков. Отсутствие поля partial означает полный ответ, поэтому клиент, не знающий о нём, продолжает работать без правок.

Тела записи в указателе нет — у файловой записи оно читается обычным чтением файла по file_path, у черновика приходит в списке черновиков.

Права — knowledge:read и knowledge:write; они же выводятся из уровня доступа к репозиторию (чтение знания = чтение репозитория, запись = запись). Токен нужен всегда, в том числе на публичном репозитории.

Разделение редакций выражено АДРЕСАМИ, а не полем запроса: знание одного репозитория и личные счётчики работают в Community целиком; платны только сведение по команде и разбор мёртвой памяти (Pro) и заведение пределов черновиков (Max). Платный вызов либо доступен целиком, либо отклоняется целиком — получить платную часть ответа через бесплатный вызов нельзя. Подробнее — licensing.md.

Отказ по лицензии несёт машинный код license_required, отличный от отказа по правам: разбирать переводимый текст клиенту не нужно.

Пример полного файла

---
id: 0192ec1a-7b1a-7def-abcd-0987654321fe
origin: human
contract:
  - mechanism: on_query
    params:
      words: ["миграция", "sqlx"]
summary: Подготовка базы перед sqlx migrate — иначе миграции падают на правах
---

Миграции sqlx требуют владельца схемы, а не суперпользователя.

Миграция, применённая под суперпользователем, создаёт объекты от его имени, и
следующая миграция под обычной учётной записью падает на правах. Заводите
отдельную роль-владельца схемы и применяйте миграции под ней.