
# Индексированный текстовый поиск

Индексированный текстовый поиск — это версия [текстового поиска](https://greph.inline0.dev/ru/docs/modes/text) с прогретым кэшем. При первом вызове создается хранилище триграмм и вхождений идентификаторов на диске; при последующих вызовах оно позволяет пропускать файлы, в которых заведомо не может быть шаблона. В результате повторные запросы к крупным репозиториям выполняются с гораздо меньшей задержкой, без запуска демона или процесса наблюдения за файлами.

Теперь прогретый текстовый поиск включает:

- поиск по одному индексу
- повторный поиск `--index-dir` по нескольким индексам
- поиск по набору на основе манифеста
- обработку устаревших данных с учетом жизненного цикла
- диагностику планировщика через `--trace-plan`

Индекс доступен через `greph-index` CLI и методы фасада `Greph::buildTextIndex()` / `Greph::searchTextIndexed()`.

## Создание индекса

```bash
# Full build at the current directory
./vendor/bin/greph-index build .

# Full build at an explicit root
./vendor/bin/greph-index build path/to/repo

# Use a non-default index directory
./vendor/bin/greph-index build . --index-dir /tmp/greph-index

# Build with an explicit lifecycle policy
./vendor/bin/greph-index build . \
  --lifecycle opportunistic-refresh \
  --auto-refresh-max-files 32 \
  --auto-refresh-max-bytes 1048576
```

По умолчанию индекс хранится в `<root>/.greph-index/`.

После успешного создания выводится сводка в одну строку:

```text
Built index for 2547 files in .greph-index (193428 trigrams, +2547 ~0 -0 =0)
```

Четыре счетчика — это количество добавленных, обновленных, удаленных и неизмененных файлов. При первой сборке счетчик добавленных равен количеству файлов, а все остальные равны нулю.

## Обновление индекса

```bash
./vendor/bin/greph-index refresh .
```

`refresh` повторно обходит индексируемый корневой каталог, хэширует каждый файл и перезаписывает только записи с изменившимися метаданными. Результат содержит те же четыре счетчика, чтобы вы точно видели, что изменилось:

```text
Refreshed index for 2547 files in .greph-index (193428 trigrams, +3 ~7 -1 =2536)
```

Запустите `refresh` после редактирования отслеживаемых файлов, если используете `manual-refresh`. Для индексов `opportunistic-refresh` команда Greph может автоматически обновлять индекс во время поиска, если набор изменений достаточно мал.

## Профили жизненного цикла

Greph работает без демона. Профили жизненного цикла определяют поведение прогретого индекса при изменении дерева исходного кода:

- `static`: никогда автоматически не проверять актуальность и не вносить изменения
- `manual-refresh`: показывать сведения об устаревших данных в `stats`, но никогда не обновлять их во время поиска
- `opportunistic-refresh`: обновлять данные во время поиска, только если набор изменений не превышает заданные пороговые значения
- `strict-stale-check`: отклонять поиск по устаревшим прогретым данным вместо их обновления

Используйте `greph-index stats --dry-refresh`, чтобы посмотреть, что произойдет при прогретом поиске, прежде чем запускать его:

```bash
./vendor/bin/greph-index stats . --dry-refresh
```

## Запросы к индексу

```bash
# Fixed-string search
./vendor/bin/greph-index search -F "function" .

# Case-insensitive whole-word
./vendor/bin/greph-index search -F -i -w "function" .

# Counts
./vendor/bin/greph-index search -F -c "function" .

# Files only
./vendor/bin/greph-index search -F -l "function" .

# Multi-index search
./vendor/bin/greph-index search -F "apply_filters" . \
  --index-dir wordpress/.greph-index \
  --index-dir wp-content/plugins/my-plugin/.greph-index \
  --show-index-origin

# Regex search
./vendor/bin/greph-index search "function\s+\w+" .

# JSON output
./vendor/bin/greph-index search --json -F "function" .

# Planner trace to stderr
./vendor/bin/greph-index search --trace-plan -F "function" .
```

Набор флагов такой же, как для нативного текстового поиска. Полный список приведен в [CLIсправочнике](https://greph.inline0.dev/ru/docs/cli#greph-index).

Если индекс отсутствует в указанном месте, `greph-index search` выдает ошибку. Эта команда не переключается автоматически на поиск без индекса (команды AST поддерживают необязательный параметр `--fallback scan`, а текстовая команда — нет). Сначала создайте индекс.

## Именованные наборы индексов

Если вы регулярно ищете по одному и тому же прогретому набору, используйте манифест вместо повторения флагов `--index-dir`:

```json
{
  "name": "wordpress-local",
  "indexes": [
    {
      "name": "core-text",
      "root": "wordpress",
      "mode": "text",
      "lifecycle": "static",
      "priority": 100
    },
    {
      "name": "plugin-text",
      "root": "wp-content/plugins/my-plugin",
      "mode": "text",
      "lifecycle": "opportunistic-refresh",
      "max_changed_files": 16,
      "max_changed_bytes": 262144,
      "priority": 200
    }
  ]
}
```

Сохраните его как `.greph-index-set.json`, затем выполните:

```bash
./vendor/bin/greph-index set build
./vendor/bin/greph-index set stats --dry-refresh
./vendor/bin/greph-index set search --show-index-origin -F "apply_filters" .
```

## Что хранится в индексе

Текстовый индекс — это хранилище триграмм и вхождений идентификаторов. Для каждого индексируемого файла Greph извлекает:

- **Набор триграмм**: набор трехбайтовых последовательностей, присутствующих в содержимом файла. Используется как грубый предварительный фильтр для буквальных запросов и запросов с регулярными выражениями.
- **Вхождения идентификаторов**: набор токенов в форме идентификаторов (`[A-Za-z_][A-Za-z0-9_]*`), присутствующих в файле. Используется для ускорения поиска по целым словам.
- **Метаданные жизненного цикла**: сохраненные пороговые значения и поведение при проверке актуальности.
- **Метаданные содержимого**: размер и время изменения для принятия решения об обновлении и проверки устаревших данных.

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

Некоторые прогретые запросы на сводку Greph может обработать непосредственно по данным о вхождениях:

- целое слово `-l`
- целое слово `-L`
- целое слово `-q`

Так можно вообще не открывать файлы-кандидаты, если данные о вхождениях уже дают однозначный ответ.

Это та же идея, что и необязательная предварительная индексация в ripgrep и индекс триграмм в codesearch, но здесь она адаптирована к формам данных на диске, которые прогретый поиск PHP обрабатывает быстрее всего.

## Диагностика планировщика

`--trace-plan` выводит решение планировщика прогретого текстового поиска в `stderr`, не затрагивая обычный поток результатов.

Трассировка включает:

- профиль жизненного цикла
- количество выбранных файлов
- источник кандидатов (`full-scan`, `trigram-postings`, `word-postings` или `invert-scan`)
- количество термов вхождений
- количество файлов-кандидатов и проверенных файлов
- возможность выдачи сводки напрямую
- возможность использования кэша запросов и решение о его заполнении

## Когда использовать

Индексированный режим подходит, когда:

- Вы многократно подряд ищете в одном и том же репозитории (задания CI, циклы агентов, интеграции с IDE).
- Репозиторий достаточно велик, чтобы нативное сканирование стало узким местом.
- Шаблоны содержат буквальные подстроки или токены-идентификаторы, по которым индекс может предварительно отфильтровать файлы.

Нативное сканирование по-прежнему подходит, когда:

- Вы ищете в небольшом репозитории, где затраты на построение индекса превышают выгоду.
- Вы ищете по пути, который больше проверять не будете.
- Вам нужны все совпадения в одном разовом запросе, и вы не хотите поддерживать индекс.

Опубликованные результаты тестирования в [README](https://github.com/inline0/greph#performance) показывают, что прогретый индексированный режим превосходит `rg` и `grep` при поиске по регулярным выражениям, а также в запросах на подсчет, поиск файлов с совпадениями и поиск файлов без совпадений по корпусу WordPress. При обычном поиске по буквальному тексту он быстрее `grep`, но немного медленнее `rg`.

## Программное использование

```php
use Greph\Greph;
use Greph\Text\TextSearchOptions;
use Greph\Index\IndexLifecycleProfile;

// Build once
Greph::buildTextIndex('.', lifecycle: IndexLifecycleProfile::OpportunisticRefresh);

// Refresh after edits
Greph::refreshTextIndex('.');

// Query
$results = Greph::searchTextIndexed(
    'function',
    'src',
    new TextSearchOptions(fixedString: true),
);
```

`searchTextIndexed` принимает те же `TextSearchOptions`, что и нативный текстовый поиск, и возвращает результаты в той же форме `list<TextFileResult>`. Для потоков с несколькими индексами или на основе манифеста используйте `searchTextIndexedMany(...)` и `searchTextIndexedSet(...)`.
