Агент-инженер по репозиторию · Модуль 2 · Урок 2.2
Инструменты исследования: list_files/glob и grep
Зачем (какую проблему чиним)
Чтобы взяться за задачу, агент должен сориентироваться в репозитории: какие есть файлы, где искать символ или строку. Читать файлы вслепую — дорого и медленно. Дадим инструменты list_files (обзор структуры по glob) и grep (поиск по содержимому).
Решение и альтернативы
Решение: list_files обходит дерево от корня репозитория, фильтрует по glob-шаблону и исключает мусор (.git, vendor, node_modules) и бинарники; grep ищет строку/регэксп, возвращая путь:строку с ограничением на число совпадений (бюджет контекста). Оба — только чтение.
Альтернативы: звать системные find/grep через shell — соблазнительно, но это тянет нас к произвольному shell (см. урок 2.4, где мы это явно запрещаем) и непереносимо. Реализуем на стандартной библиотеке Go (io/fs, regexp) — детерминированно, переносимо, без shell.
DIFF
Добавляем два инструмента поверх контракта Tool; оба используют safeJoin и игнор-листы.
⚠ Безопасность
Поиск тоже ограничен корнем репозитория (тот же safeJoin для базовой директории). Исключение .git и секрет-подобных файлов (.env, ключи) из выдачи — чтобы агент не затягивал секреты в контекст модели. Ограничение числа совпадений — защита и от взрыва контекста, и от случайного дампа большого файла.
Проверка
Спросите «найди, где определяется safeJoin» — агент вызывает grep, получает tools.go:NN и отвечает точно, не читая весь репозиторий. list_files /*.go возвращает только Go-файлы вне игнор-листа.
Глубже
Почему поиск важнее «прочитать всё» — это уже про retrieval и бюджет окна: курс «Продакшн-разработка», Модуль 1 (context engineering) и Модуль 2 (RAG). В версии v2 мы заменим наивный grep гибридным поиском.
+// grepTool ищет подстроку/регэксп по файлам репозитория. Только чтение.
+type grepTool struct{ root string }
+
+func (grepTool) Spec() anthropic.ToolParam {
+ return anthropic.ToolParam{
+ Name: "grep",
+ Description: anthropic.String("Найти строки по регулярному выражению в файлах репозитория."),
+ InputSchema: anthropic.ToolInputSchemaParam{
+ Properties: map[string]any{
+ "pattern": map[string]any{"type": "string"},
+ "glob": map[string]any{"type": "string", "description": "напр. **/*.go"},
+ },
+ Required: []string{"pattern"},
+ },
+ }
+}
+
+func (g grepTool) Run(ctx context.Context, input json.RawMessage) (string, error) {
+ var in struct{ Pattern, Glob string }
+ if err := json.Unmarshal(input, &in); err != nil {
+ return "", fmt.Errorf("неверные аргументы")
+ }
+ re, err := regexp.Compile(in.Pattern)
+ if err != nil {
+ return "", fmt.Errorf("неверный regexp: %v", err)
+ }
+ const maxHits = 100 // бюджет контекста: не вываливаем весь репозиторий
+ var hits []string
+ walkRepo(g.root, in.Glob, func(rel string, line int, text string) bool {
+ if re.MatchString(text) {
+ hits = append(hits, fmt.Sprintf("%s:%d: %s", rel, line, text))
+ }
+ return len(hits) < maxHits
+ })
+ if len(hits) == 0 {
+ return "совпадений не найдено", nil
+ }
+ return strings.Join(hits, "\n"), nil
+}
+
+// walkRepo обходит файлы вне игнор-листа (.git, vendor, node_modules, секреты).
+// Реализация в helpers.go использует io/fs и safeJoin; shell не вызывается.Anti-patterns
| Грабля | Почему плохо | Как правильно |
|---|---|---|
| Вызывать системные find/grep через shell | Тянет к произвольному shell (дыра безопасности), непереносимо между ОС | Реализовать на io/fs/regexp — детерминированно, переносимо, без shell |
| Возвращать все совпадения без лимита | Взрыв контекста и стоимости; один большой файл может затопить окно | Ограничить число совпадений (maxHits) и длину строки |
| Включать .git, .env, ключи в выдачу поиска | Секреты затягиваются в контекст модели и логи | Игнор-лист служебных и секрет-подобных путей |
Практическое задание (RA-v1)
- Добавить
list_files(glob по дереву с игнор-листом) иgrep(regexp с лимитом совпадений), оба read-only и в пределах корня. - Исключить
.git/vendor/node_modulesи секрет-подобные файлы из обхода. - Закоммитить:
git commit -m "v1: list_files and grep tools".
Проверка знаний
Почему grep-инструмент реализуют на стандартной библиотеке Go, а не вызовом системного grep через shell?
Верный ответ: B
B. Любой путь через shell — это потенциальная инъекция команд и зависимость от внешних утилит/ОС. Реализация на io/fs/regexp безопасна, переносима и детерминирована. Скорость (A) здесь вторична; регэкспы в Go есть (C).