Плагин ext-search расширяет возможности встроенных инструментов grep и glob в OpenCode, позволяя прозрачно для ИИ искать файлы в заранее настроенных внешних директориях монорепозитория.
При работе с монорепозиторием в OpenCode возникает дилемма:
- Если открыть всю монорепу — поиск
grep/globработает по всему дереву, что медленно. - Если открыть только подпроект — ИИ не видит файлы из shared-пакетов (типы, утилиты и т.д.).
Плагин решает эту проблему: он перехватывает результаты grep и glob и дополняет их результатами поиска по внешним директориям. Для ИИ всё прозрачно — он продолжает пользоваться теми же инструментами.
Плагин также регистрирует кастомный инструмент deps_read для чтения файлов из внешних директорий. Это удобнее, чем встроенный read, который может требовать подтверждения для каждого файла за пределами проекта.
-
OpenCode — установлен и доступен в системе (включает Bun)
-
ripgrep (
rg) — для поиска по содержимому файлов (grep). Плагин автоматически ищетrgв следующем порядке:- Системный
PATH - Стандартные директории OpenCode — плагин формирует все возможные пути путём комбинирования платформенных базовых директорий (XDG cache/data,
~, AppData и др.) со стандартными суффиксами (opencode/bin,.opencode/bin,.cache/opencode/bin,.local/share/opencode/bin,Library/Caches/opencode/bin,Library/Application Support/opencode/bin)
OpenCode автоматически скачивает
rgпри первом запуске, поэтому отдельная установка обычно не требуется. Поиск файлов (glob) не зависит отrg— используется нативныйBun.Glob.Если
rgне найден, плагин продолжает работу: поиск файлов (glob) не затрагивается, а при каждом вызовеgrepк результатам добавляется подсказка с абсолютными путями внешних директорий, чтобы ИИ мог использоватьdeps_readилиglobдля их проверки:(ripgrep not available. External dependency directories: /home/user/my-monorepo/shared-types, /home/user/my-monorepo/common-utils. Use the deps-read tool or search with glob specifying an external directory path to explore their contents.) - Системный
Соберите плагин (npm run build) и поместите артефакты в .opencode/plugins/ext-search/ внутри директории команды:
монорепа/
├── shared-types/
├── common-utils/
└── team-alpha/
├── opencode.json
├── .opencode/
│ └── plugins/
│ └── ext-search/
│ ├── package.json
│ └── index.js ← из plugins/ext-search/dist
└── my-app/ ← подпроект, открываемый в OpenCode
Добавьте плагин в конфигурацию OpenCode в файле opencode.json в директории команды:
Для работы встроенного инструмента read с файлами из внешних директорий необходимо задать permission.external_directory. Поскольку эта секция содержит абсолютные пути, специфичные для машины разработчика, её не следует помещать в проектный opencode.json — вместо этого используйте глобальный конфиг OpenCode.
Примечание: плагин автоматически одобряет запросы
external_directoryдля путей внутри настроенных внешних директорий и внутриconfigDir(см. Авто-permit). Ручная настройкаpermission.external_directoryв глобальном конфиге по-прежнему поддерживается для полноты, но в большинстве случаев авто-permit делает её необязательной.
Путь к глобальному конфигу зависит от платформы:
| Платформа | Путь |
|---|---|
| Linux | ~/.config/opencode/opencode.json |
| macOS | ~/Library/Application Support/opencode/opencode.json |
| Windows | %APPDATA%\opencode\opencode.json |
Пример глобального конфига:
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"external_directory": {
"/абсолютный/путь/к/монорепе/shared-types/*": "allow",
"/абсолютный/путь/к/монорепе/common-utils/*": "allow"
}
}
}Для каждой внешней директории укажите абсолютный путь с glob-суффиксом /*.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
root |
string |
Нет | Путь к корню монорепы (относительно открытого подпроекта). Если не указан — используется ctx.worktree (корень git-репозитория) |
directories |
string[] |
Условно | Список путей к внешним директориям (относительных от root или абсолютных). Обязателен, если не указан compile_commands_dir |
excludePatterns |
string[] |
Нет | Glob-паттерны для исключения файлов (по умолчанию: ["node_modules", ".git", "dist"]) |
maxResults |
number |
Нет | Верхний предел результатов из внешних директорий на один вызов (по умолчанию: 50). Фактический лимит динамически уменьшается, если основной поиск занял большую часть общего бюджета (см. ниже) |
strict_path_restrictions |
boolean |
Нет | При true перехватывает вызовы glob/grep и перенаправляет пути поиска за пределы configDir и внешних директорий обратно в configDir (по умолчанию: false). Подробнее см. Ограничение путей поиска |
compile_commands_dir |
string |
Нет | Относительный путь к директории с compile_commands.json относительно configDir. Плагин автоматически извлечёт уникальные директории исходных файлов из базы компиляции и будет использовать их как внешние директории для поиска. Если configDir не найден (opencode.json отсутствует) — парсинг compile_commands.json молча пропускается. Подробнее см. Поддержка compile_commands.json |
Плагин разрешает пути из directories относительно базовой директории. По умолчанию это ctx.worktree — корень git-репозитория, автоматически определяемый OpenCode. Если монорепа не использует git (или использует собственный VCS без маркера .git), OpenCode не сможет определить корень монорепы. В этом случае укажите root — путь от открытого подпроекта до корня монорепы:
{
"root": "../../",
"directories": ["shared-types", "common-utils"]
}(Полный список параметров см. в таблице «Параметры конфигурации» и в примере ниже.)
Если root указан — пути из directories разрешаются от path.resolve(configDir || openDir, root), иначе — от ctx.worktree.
- Относительные — разрешаются от базовой директории (определяемой
rootилиctx.worktree):"shared-types" - Абсолютные — используются как есть:
"/opt/shared-libs" - Домашняя директория — поддерживается
~/:"~/projects/shared"
Когда ИИ вызывает grep, плагин после выполнения встроенного поиска:
- Запускает
ripgrepпо всем настроенным внешним директориям (используяrgиз PATH или из директории OpenCode). ПараметрmaxResultsпередаётся как--max-count, ограничивая количество совпадений на файл - Если встроенный поиск нашёл результаты — дополняет их секцией
--- External dependencies --- - Если встроенный поиск не нашёл результатов — заменяет результат на внешние
- Обновляет счётчик совпадений в метаданных
- Строки длиннее 2000 символов обрезаются
Пример вывода с внешними результатами:
Found 3 matches
/home/user/monorepo/packages/my-app/src/main.ts:
Line 10: import { UserProfile } from "../shared-types/types"
--- External dependencies ---
Found 2 matches
/home/user/monorepo/packages/shared-types/types.ts:
Line 1: export interface UserProfile {
Line 5: export type UserId = string;
Аналогично grep — результаты поиска файлов во внешних директориях добавляются к результатам встроенного glob. Поиск файлов выполняется через нативный Bun.Glob. Если Bun.Glob недоступен — используется fallback через рекурсивный обход (readdirSync), который возвращает все файлы без применения glob-паттерна.
Кастомный инструмент для чтения файлов из внешних директорий:
- Преимущество: не требует подтверждения
external_directoryдля каждого файла - Параметры:
filePath(обязательный) — абсолютный путь к файлуoffset(необязательный) — начальная строка (с 1)limit(необязательный) — максимальное количество строк (по умолчанию 2000)
- Проверяет, что файл находится внутри настроенных внешних директорий
- Файлы размером более 10 МБ отклоняются с предложением использовать
offset/limit - Строки длиннее 2000 символов обрезаются
- При запросе строк за пределами файла возвращается ошибка с указанием запрошенного диапазона и реального числа строк
Плагин использует три механизма, чтобы избежать дублирования и пропускает внешний поиск, если он не нужен:
- Прямое совпадение с внешней директорией — если
pathуказывает на одну из внешних директорий, внешний поиск пропускается (встроенный инструмент уже найдёт файлы там). - Узкие пути поиска — если
pathявляется подкаталогом, не лежащим на прямом пути отopenDirкconfigDir, внешний поиск не запускается (поиск по узкому пути не покрывает внешние директории). - Исключение покрытых директорий — если
pathпокрывает одну или несколько внешних директорий (например,path= корень монорепы), эти директории исключаются из внешнего поиска, но поиск по оставшимся директориям выполняется.
При включении опции strict_path_restrictions: true плагин перехватывает вызовы glob и grep до их выполнения через хук tool.execute.before. Если путь поиска (path) выходит за пределы configDir и внешних директорий — он перенаправляется в configDir. Это предотвращает поиск по произвольным областям файловой системы.
Ключевые особенности:
- Хук регистрируется только при
strict_path_restrictions === trueиconfigDir !== null - Относительные пути разрешаются относительно
openDir - Разрешённые области: внутри
configDirили внутри любой из внешних директорий - Остальные параметры вызова (pattern, include и т.д.) не изменяются
Подробнее см. Ограничение путей поиска.
Встроенные инструменты grep и glob в OpenCode возвращают не более 100 результатов каждый. Чтобы не нарушать это ограничение, плагин динамически рассчитывает бюджет для внешних результатов:
- После выполнения встроенного поиска плагин подсчитывает количество непустых строк в основном выводе
- Бюджет =
max(0, 100 - количество_непустых_строк_основного_вывода) - Фактический лимит внешних результатов =
min(бюджет, maxResults)— параметрmaxResultsиз конфига служит верхним пределом
Если бюджет = 0 (основной поиск вернул 100+ строк): внешний поиск не запускается. Вместо этого к выводу добавляется подсказка с абсолютными путями внешних директорий:
(External dependencies may contain additional matches: /home/user/my-monorepo/shared-types, /home/user/my-monorepo/common-utils.
Use the deps-read tool or search with grep/glob specifying an external directory path.)
Если бюджет > 0, но внешних результатов больше бюджета: поиск выполняется по всем внешним директориям (даже если первые уже заполнили лимит). Выводятся результаты, умещающиеся в бюджет. Затем добавляется подсказка, но только с теми директориями, где найдены результаты, но не все из них уместились в бюджет. Директории без результатов и те, чьи результаты полностью вошли в вывод, из подсказки исключаются.
Пример: основной grep вернул 90 результатов → бюджет = 10 → плагин добавит до 10 внешних совпадений. Если maxResults в конфиге = 5 — будет добавлено не более 5.
Структура проекта:
my-monorepo/
├── shared-types/ ← общие типы (корень монорепы)
├── common-utils/ ← общие утилиты (корень монорепы)
└── team-alpha/ ← директория команды
├── opencode.json
├── .opencode/
│ └── plugins/
│ └── ext-search/
│ ├── package.json
│ └── index.js ← из plugins/ext-search/dist
└── my-app/ ← подпроект, открытый в OpenCode
└── src/
team-alpha/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
[
"./.opencode/plugins/ext-search",
{
"root": "../../",
"directories": [
"shared-types",
"common-utils"
],
"excludePatterns": ["node_modules", ".git", "dist"],
"maxResults": 50,
"strict_path_restrictions": false,
"compile_commands_dir": null
}
]
]
}Глобальный конфиг (~/.config/opencode/opencode.json на Linux):
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"external_directory": {
"/home/user/my-monorepo/shared-types/*": "allow",
"/home/user/my-monorepo/common-utils/*": "allow"
}
}
}Теперь при открытии my-monorepo/team-alpha/my-app в OpenCode:
- OpenCode найдёт
my-monorepo/team-alpha/opencode.json - Загрузит плагин из
my-monorepo/team-alpha/.opencode/plugins/ext-search/ - Плагин определит корень монорепы через
root(../../отmy-app→ корень монорепы) - Каждый вызов
grep/globбудет автоматически дополняться результатами изshared-typesиcommon-utils - ИИ сможет использовать
deps_readдля чтения файлов из этих пакетов
- Сценарии работы плагина — основной сценарий и ссылки на подробные разделы:
- Инициализация плагина
- Обработка grep / glob
- deps_read
- Авто-permit — автоматическое одобрение запросов доступа к внешним директориям
- Ограничение путей поиска — перехват glob/grep, проверка и перенаправление путей
- Поддержка compile_commands.json — автоматическое извлечение директорий из базы компиляции
- Toast-уведомления об ошибках
- Логирование
- Внутренняя инфраструктура — FsHost, _testing, spawn, IGNORE_TOOLS
- Глоссарий — термины и определения
Проект содержит три уровня тестов:
npm run test:unit # юнит-тесты
npm run test:integration # интеграционные тесты
npm run test:e2e # e2e-тесты
npm run test:all # все уровниТребования для e2e-тестов:
- OpenCode установлен и доступен по пути
~/.opencode/bin/opencode(или задан черезOPENCODE_BIN) - ripgrep доступен через PATH или в стандартных директориях OpenCode (для grep-тестов)
Тестовая фикстура эмулирует монорепу без git (нет .git), чтобы верифицировать работу поля root.
Плагин работает на всех основных операционных системах: Linux, macOS, Windows.
- Glob-поиск использует нативный
Bun.Glob(при недоступности — fallback через рекурсивный обходreaddirSync) - Grep-поиск автоматически находит
ripgrep(который OpenCode скачивает при установке) по стандартным путям для каждой платформы - Для выполнения команд используется
Bun.spawnс fallback наchild_process.execFileSync
Все пути обрабатываются через path.resolve() с учётом разделителей конкретной платформы.
{ "$schema": "https://opencode.ai/config.json", "plugin": [ [ "./.opencode/plugins/ext-search", { "root": "../../", "directories": [ "shared-types", "common-utils" ], "excludePatterns": ["node_modules", ".git", "dist", "*.test.*"], "maxResults": 50, "strict_path_restrictions": false, "compile_commands_dir": null } ] ] }