Индексация и ограничения
Реализуйте CLI, который строит JSON-каталог документов в заданном дереве каталогов. Обязательны --root, --output, --workers и --max-files; --max-files задаёт верхний предел числа индексируемых записей. Допускается повторяемый флаг --ext для белого списка расширений, а --snippet=N включает извлечение первых N UTF-8-символов текста, где N ограничен диапазоном 1–1000. По умолчанию индексируйте обычные файлы с расширениями .txt, .md, .pdf, .docx и .csv без чтения содержимого. Расширения сравниваются без учёта регистра. Символические ссылки не обходятся и не индексируются; относительные пути в результате вычисляются от корня.
Запись JSON содержит путь, имя, расширение в нижнем регистре, размер в байтах, время изменения в UTC RFC3339 и, если включено и формат поддерживается, snippet. Фрагменты извлекайте только из UTF-8 текстовых файлов .txt, .md и .csv; для PDF и DOCX поле пропускается, а не эмулирует извлечение. Невалидный UTF-8 диагностируется и файл остаётся в индексе без фрагмента. Данные отсортируйте по относительному пути лексикографически перед публикацией, сериализуйте детерминированно с отступами и завершающим переводом строки.
Не читайте все документы целиком: метаданные получают через файловую систему, текст читается не более чем до объёма, достаточного для заданных символов, плюс небольшой запас для UTF-8. Канал заданий ограничен workers элементами, число записей не может превысить --max-files; если обнаружен ещё один подходящий файл сверх --max-files, остановите обход и верните ненулевой код с неполным индексом. Одновременно обрабатывайте не более workers файлов и ограничьте число открытых файлов этим же пределом. Проверяйте context.Context при обходе, чтении и получении задач. CLI обязан создавать context через обработчик SIGINT и SIGTERM. При отмене прекращайте новые задачи, закрывайте файлы и дожидайтесь работников; отменённый индекс не публикуйте, временный файл удаляйте и сохраняйте прежний output, сообщая о неполноте только в диагностике. Ошибки отдельных файлов выводите в stderr и формируйте частичный индекс с ненулевым кодом; ошибка корня фатальна. Запись делайте во временный файл рядом с output и атомарно переименовывайте только после завершения индексации. README объясняет неполноту и ограничения извлечения; требуются тесты и race-проверка.
Структура программы
В internal/indexfiles реализуйте обход, извлечение метаданных и ограниченных текстовых фрагментов; в internal/catalog — поля записи, сортировку и лимиты индекса. Оставьте main.go создание контекст, отменяемый сигналом, запуск ограниченных workers и атомарную публикацию. Пакет правил получает данные через файловые функции, без циклических зависимостей.