Конфигурация терминального агента живёт в двух слоях. Глобальный файл в пользовательском каталоге задаёт постоянные предпочтения: модель, режим подтверждений, оформление вывода. Проектный файл рядом с репозиторием отвечает только за разрешения - и это осознанное ограничение, а не недоработка. Корень конфигурации при необходимости переопределяется переменными окружения, что удобно для контейнеров и стендов.
Асимметрия слоёв объясняется просто. Личные предпочтения не должны навязываться всем, кто клонировал репозиторий: чужой выбор модели или режима отображения - это не свойство проекта. А вот разрешения - именно свойство проекта: какие команды считаются нормальными в этом репозитории, что читать нельзя, куда нельзя писать. Поэтому проектный слой узкий и посвящён только границам.
Полезно один раз увидеть цельный файл. Ниже - версия схемы, модель, режим подтверждений, настройки редактора и отображения и блок разрешений с разрешающими и запрещающими правилами. Читается он как профиль работы: что запускаем, насколько автономно, что показываем на экране и в каких границах действуем.
Режим подтверждений принимает документированные значения: работа только по списку разрешённого, работа с автоматической проверкой и работа без ограничений. Это те же три модели поведения, что и в редакторе, и выбирают их так же - по цене ошибки. Для автоматизации разумен строгий вариант со списком: он предсказуем и объясним, а вероятностная оценка в неинтерактивном прогоне никого не спасёт, потому что спрашивать некого.
У того, что разрешения лежат в репозитории, есть последствие, ради которого стоит терпеть узость проектного слоя. Расширение прав становится изменением в файле, а значит, попадает в обсуждение изменений и в историю. На вопрос, кто и зачем разрешил агенту сеть или запись за пределами исходников, есть ответ с датой и аргументом. Права, живущие в чьей-то домашней папке, такого ответа не дают: у каждого своя настройка, и совпадать они перестают в первый же месяц.
Переопределение корня конфигурации переменными окружения служит той же цели - сделать прогон независимым от машины. В контейнере или на общем исполнителе домашний каталог может быть чужим, временным или отсутствовать вовсе, и агент подхватит не тот файл. Симптом узнаваем: локально всё работает, на исполнителе агент ведёт себя иначе - не тот режим подтверждений, не та модель. Лечится это указанием корня явно, а не подгонкой окружения по факту.
Отдельно стоит держать в голове, что набор полей шире показанного. Есть канал обновлений, параметры модели, уведомления, подсказки, откат, предложения следующего запроса, настройки песочницы, сети и атрибуции. Перечислять их все в книге бессмысленно: справочник меняется вместе с версиями. Практичнее знать, где посмотреть текущий набор, - в самом агенте есть команды для просмотра и обновления конфигурации.
Это приводит к общему правилу работы с быстро меняющимся продуктом. Статический справочник - карта, а не контракт. Изменения в командной строке нередко приезжают раньше, чем обновляется документация, поэтому перед тем, как зашивать поле в общий конфигурационный файл команды, полезно посмотреть, как оно называется и как ведёт себя в вашей версии. Цена проверки - одна команда, цена ошибки - молча проигнорированная настройка у всей команды.
Инженерный вывод простой: держите глобальный файл личным, а проектный - командным и узким. Тогда клонирование репозитория даёт коллеге ровно те границы, которые вы имели в виду, и не тащит ваши привычки. А воспроизводимость в автоматизации обеспечивается не файлом, а явными флагами в команде: они не зависят от того, что лежит в домашнем каталоге исполнителя.
Типичные провалы предсказуемы. Положить личные предпочтения в проектный файл и удивиться, что они не действуют. Понадеяться в непрерывной интеграции на конфигурацию машины вместо явных флагов. Скопировать чужой профиль целиком вместе с разрешениями, которые вам не нужны. И считать список полей в статическом справочнике полным.
// ~/.cursor/cli-config.json
{
"version": 1,
"approvalMode": "allowlist",
"editor": { "vimMode": false },
"display": {
"showLineNumbers": true,
"showThinkingBlocks": false,
"showStatusIndicators": true,
"showStatusLineRunningTime": true
},
"permissions": {
"allow": ["Shell(git)", "Read(src/**)", "Write(src/**)"],
"deny": ["Read(.env*)", "Write(**/*.key)", "Shell(rm)"]
}
}
// режимы подтверждений: allowlist, auto-review, unrestricted