Skip to content

Latest commit

 

History

History
526 lines (413 loc) · 43.7 KB

File metadata and controls

526 lines (413 loc) · 43.7 KB

Архитектура CommentRake

English version

Состояние документа

Документ задаёт целевую архитектуру. Имена сущностей могут уточняться до первой устойчивой версии, но границы безопасности и разделение слоёв должны сохраняться.

Основные принципы

  • одно независимое ядро и несколько интерфейсов;
  • локальная работа по умолчанию;
  • детерминированный и объяснимый анализ;
  • отсутствие изменений во время сканирования;
  • любое изменение проходит через утверждённый EditPlan;
  • защищённые и рискованные комментарии являются отдельными видами;
  • ошибка разбора не приводит к опасному резервному удалению;
  • история, архив и восстановление являются частью предметной модели;
  • интерфейсы отображают общие данные и не дублируют правила.

Слои

┌──────────────────────────────────────────────────────────────┐
│                         Интерфейсы                           │
│  графический      терминальный      командный      агенты   │
└────────────────────────────┬─────────────────────────────────┘
                             │
┌────────────────────────────▼─────────────────────────────────┐
│                      Слой приложения                         │
│  сценарии сканирования, проверки, планирования и истории    │
└────────────────────────────┬─────────────────────────────────┘
                             │
┌────────────────────────────▼─────────────────────────────────┐
│                         Ядро                                 │
│  файлы и комментарии │ правила │ решения │ карта │ планы    │
└───────────────┬───────────────────────────────┬──────────────┘
                │                               │
┌───────────────▼──────────────┐  ┌─────────────▼──────────────┐
│ Адаптеры языков / Tree-sitter│  │ Хранилище SQLite и файлы  │
└──────────────────────────────┘  └────────────────────────────┘

Ядро

Ядро не импортирует PySide6, Textual, Rich или веб-обвязку и не возвращает их типы. Оно содержит предметные модели и чистые службы, которые можно проверять без запуска интерфейса.

ReviewDataset — отдельная scan-local модель проверки: в ней остаются только комментарии из успешного parser-backed разбора, их findings, признаки приоритета и причины защиты. Чистые службы ядра ограничивают этот набор областью, фильтрами и устойчивой сортировкой, выполняют безопасный поиск фрагментов и замораживают точный выбор. Ни фильтр, ни finding, ни совпадение поиска не создают решение пользователя или EditPlan.

EditPlan — неизменяемый контракт между проверкой и будущим Apply. Он содержит только относительные пути, исходный ScanId, точные диапазоны, ожидаемый UTF-8 текст, замену и отпечатки файлов; он не хранит абсолютный путь и не выполняет ввод-вывод. Слой приложения строит план из явных решений, а отдельный read-only dry-run безопасно разрешает пути под корнем проекта, сверяет отпечаток и диапазоны и возвращает различия. Любая устаревшая или недоступная запись блокирует весь план до повторного сканирования.

CleanupSession и ArchiveEntry фиксируют Apply отдельно от снимков проекта. Сеанс и archive intent сохраняются локально до первой записи; далее файлы заменяются атомарно по одному. Глобальной транзакции между SQLite и несколькими файлами нет: после ошибки сеанс остаётся partially_applied с точными фактами. Restore принимает только архивные записи, чей файл полностью совпадает с записанным состоянием после очистки; внешнее изменение превращается в конфликт, а не в автоматическое слияние.

Базовые предметные объекты неизменяемы и используют dataclass(frozen=True, slots=True) либо равнозначную реализацию. Они проверяют собственные инварианты при создании и не выполняют файловый ввод-вывод, разбор, хранение данных или работу с интерфейсом.

Предварительные группы сущностей:

Проект и исходные файлы

  • ProjectId;
  • ScanId;
  • ProjectRoot;
  • ScanPolicy;
  • DiscoveryManifest;
  • SourceFile;
  • SourceKind;
  • ParseStatus;
  • TestClassification;
  • FileFingerprint.

ProjectId, ScanId и DecisionId создаются как UUIDv4. Устойчивые SourceFileId и ProjectNodeId вычисляются как UUIDv5 из идентификатора проекта и канонического имени. Внутри ядра путь проекта хранится только как относительный POSIX-путь без абсолютных, ./.. или Windows-разделителей; преобразование к пути операционной системы выполняет адаптер файловой системы.

SourceFile отдельно хранит вид файла, язык и профиль, классификацию test/application, состояние разбора, размер, SHA-256 исходных байтов и формат текста (кодировку, BOM, переводы строк и завершающий перевод строки). Только успешный parser-backed разбор допускает правило или обычный автоматизированный выбор. Отдельный утверждённый лексический профиль известного текстового формата может передать точный диапазон только для прямого ручного решения пользователя; он не создаёт rule finding, массовый выбор или обходной путь после ошибки parser-backed разбора.

Между DiscoveryManifest и SourceFile находятся независимые от интерфейсов этапы SourceDetectionManifest и TestClassificationManifest. Первый даёт каждому включённому текстовому файлу объяснимый статус supported, unsupported, ambiguous или unknown, основание сопоставления и его источник. Неоднозначность сохраняет все варианты и не зависит от порядка регистрации. Реестр описателей владеет именами, расширениями, составными суффиксами, шаблонами пути, безопасными shebang и признаком доступности parser-backed адаптера; сами языковые адаптеры присоединяют к нему грамматику и запросы комментариев позднее.

TestClassificationManifest сохраняет для каждого результата определения языка значение application, test или unknown, основание, правило и источник решения. Точное локальное правило имеет приоритет над общей политикой команды и встроенными соглашениями. Конфликт равных правил не зависит от порядка регистрации: он остаётся unknown с ограниченной диагностикой. Классификация не читает текст, не меняет решение обнаружения и не создаёт finding, решение или план изменений. Языковые адаптеры позднее могут дать точные parser-backed диапазоны тестового контекста внутри файла; это не превращает файловую классификацию в неясное значение mixed.

Только однозначный supported вместе с уже вычисленной классификацией передаётся загрузчику исходника. Он повторно проверяет метаданные до и после полного чтения, вычисляет SHA-256 точных байтов и определяет формат текста. Изменённый файл, ошибка чтения и неподдерживаемая кодировка остаются безопасными результатами без SourceFile. Успешно загруженный файл получает ParseStatus.NOT_ATTEMPTED; распознавание языка не является успешным разбором и не разрешает изменения диапазонов. Машинная схема ядра повышена до версии 11: JSON версий 1–10 остаётся читаемым, а старое ParseStatus.UNSUPPORTED версии 1 безопасно мигрируется в NOT_ATTEMPTED. Версии 4–10 добавили типы метрик, ProjectMap, историю, планы и их безопасные перечисления; версия 11 добавляет чистые типы границы перевода. Новые данные всегда записываются в версии 11.

До определения языка обход формирует плоский версионированный DiscoveryManifest. Он содержит канонизированный корень, упорядоченные относительные пути, доступные метаданные объектов, источник и точное правило каждого исключения, а также структурированные диагностики, но не содержит текста исходных файлов, отпечатков, SourceFile или узлов карты. Неверный либо недоступный корень возвращается как неуспешный манифест; локальная ошибка записи сохраняет доступную часть результата как частичный манифест. До определения языка сканер отбрасывает файлы больше настраиваемого предела (10 МиБ по умолчанию) и читает не более первых 64 КиБ для предварительной проверки: известный BOM распознаётся до NUL-байтов. Если файл исчез или изменил размер либо время изменения между чтением префикса и повторной проверкой метаданных, он остаётся в манифесте с безопасным статусом и не является кандидатом для следующего этапа. Обход не создаёт файлов в открытом проекте. Интерфейс может получать только счётчики хода обхода и запрашивать отмену; отменённый запуск возвращает частичный манифест без записи файлов. При включённом follow_gitignore применяются корневой и вложенные .gitignore через pathspec.GitIgnoreSpec; для исключения сохраняются относительный путь файла правил, номер сработавшей строки и сам шаблон. Некорректная строка .gitignore образует локальную диагностику с номером строки, но не прерывает обход. Глобальные исключения Git и .git/info/exclude намеренно не участвуют в версии 1, чтобы сканирование не зависело от локальной настройки Git. Явные include и exclude используют относительные POSIX-шаблоны; include переопределяет Git и мягкие исключения, но не жёсткие границы, двоичные или слишком крупные файлы. Совпадение обнаруженного пути с обоими списками является ошибкой конфигурации, а не неявным выбором приоритета.

Комментарии и правила

  • CommentSpan;
  • CommentKind;
  • ProtectionReason;
  • Finding;
  • RuleId;
  • RuleResult;
  • Evidence;
  • RiskLevel;
  • ReviewPriority и объясняющие его признаки;
  • CommentFragmentMatch с точным диапазоном внутри комментария.

Проверка и изменение

  • Decision;
  • PlanId;
  • PlannedEdit;
  • EditPlan;
  • ApplyResult;
  • RestoreResult.

Карта проекта

  • ProjectNode;
  • ProjectMetrics;
  • MetricKind;
  • ProjectMapViewModel или равнозначная независимая модель состояния.

История

  • ScanSnapshot;
  • FileSnapshot;
  • CleanupSession или EditBatch;
  • ArchiveEntry;
  • HistoryRange.

Необязательные интеграции

  • NaturalLanguageTag;
  • TranslationTransport;
  • TranslationProviderMetadata;
  • TranslationRequest;
  • TranslationResult;
  • TranslationProvider;
  • NoOpTranslationProvider.

Это только контракт без поставщика и пользовательской функции. TranslationRequest несёт идентификатор одного явно выбранного комментария, только его текст, необязательный source tag, обязательный target tag, transport и явное согласие для network; пути, имя файла, код вокруг комментария, finding, Decision, EditPlan, storage, endpoint и credentials в него не входят. TranslationResult не сохраняет исходный текст. Без отдельного решения никакой сетевой поставщик не подключается; NoOpTranslationProvider не выполняет I/O. Перевод не влияет на классификацию, правила, решения, EditPlan, историю, storage или журналы.

Координаты и тождественность комментария

CommentSpan хранит:

  • относительный путь;
  • язык;
  • вид;
  • диапазон байтов;
  • строки и столбцы;
  • исходный текст с маркерами комментария и структурой строк; смежные совместимые отдельные комментарии образуют одну логическую группу;
  • количество исходных строк логического комментария;
  • нормализованный текст только для анализа;
  • отпечаток исходного фрагмента;
  • окружающий контекст;
  • защиту и её причину;
  • срабатывания;
  • решение пользователя.

Для изменения авторитетны диапазон байтов, ожидаемый исходный текст и отпечаток файла. Номеров строк недостаточно.

ByteRange использует полуоткрытые границы [start, end). SourcePoint хранит нулевые номера строки и байтового столбца в формате Tree-sitter; пользовательскую нумерацию отображает соответствующий интерфейсный адаптер. SourceRange сохраняет обе формы координат вместе и не позволяет противоречивое направление диапазона.

Логический CommentSpan сохраняет исходный текст без нормализации и состоит из одного или нескольких упорядоченных непересекающихся parser-backed CommentSegment. Поэтому несколько соседних строковых комментариев образуют один объект, но не теряют маркеры и точные диапазоны каждой строки. Синтаксический вид, расположение, семантический вид и набор причин защиты хранятся независимо и могут сочетаться.

Поиск и разбор

Последовательность поиска:

обход проекта
   → исключения и .gitignore
   → определение двоичного или текстового файла
   → сопоставление языка и профиля
   → классификация основного и тестового кода
   → разбор Tree-sitter или безопасный режим просмотра
   → извлечение комментариев

Адаптер языка предоставляет расширения, составные имена, грамматику, запрос комментариев, виды комментариев, защищённые конструкции и проверочные примеры. В ядре не должно быть разрозненных проверок расширений. Поставщик грамматик остаётся за границей адаптеров: он использует только заранее загруженные бинарные файлы из пользовательского каталога CommentRake, создаёт отдельный parser для задачи и не имеет операции загрузки во время сканирования.

Сомнительный разбор разрешает отчёт, но запрещает применение изменений к неопределённым диапазонам. Недоступная грамматика и синтаксическая ошибка файла — разные статусы; оба случая не создают регулярный или иной разрушительный резервный путь. Для .rc и .rc.in отдельный профиль windows-resource работает лишь после проверки текстового содержимого, знает только // и /* ... */, пропускает строки и останавливается при неопределённой конструкции. Его диапазоны допускают лишь явное ручное решение через общий EditPlan.

Подробности: анализ комментариев.

Правила и решения

Правило получает данные комментария и синтаксический контекст, а возвращает объяснимое срабатывание. Оно не изменяет файл.

Реализованный Finding принадлежит ядру и содержит идентификатор находки, идентификатор комментария, rule_id, причину, доказательства, рекомендуемый шаг, силу сигнала и флаги риска. Никакой вариант Finding не содержит решения пользователя или диапазона EditPlan. Обычные правила запускаются лишь для подтверждённых Tree-sitter отдельных обычных комментариев; защита, документация, TODO-семейство, внутристочные комментарии, закомментированный код и неуверенный разбор отсекаются раньше. Снимок с находками хранит также версию и отпечаток политики правил.

Приоритет показа отделён от риска, уверенности и предлагаемого действия. Структурный маркер или дата могут поднять комментарий в списке проверки, но не являются самостоятельным основанием для удаления.

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

Совпадение внутри комментария также отделено от решения. Оно может остаться без изменения, стать точечным удалением или заменой либо помочь пользователю подготовить ручную редакцию. Удаление фрагмента, ручная редакция и удаление всего комментария являются разными видами PlannedEdit; каждый хранит ожидаемый исходный текст и точный диапазон. Ядро не расширяет выбранный фрагмент до всего комментария автоматически.

Фундаментальная модель отдельно представляет совпадения и признаки приоритетной проверки: они не создают решение. Отсутствие объекта Decision означает отсутствие решения. Явные варианты решения — сохранить, подавить правило, удалить комментарий, удалить/заменить выбранные фрагменты либо вручную отредактировать комментарий; каждый содержит идентификатор и источник явного выбора пользователя.

До появления публичного машинного интерфейса фундаментальные DTO имеют внутреннее версионированное JSON-представление с явным discriminator. Публичные JSON-схемы, совместимость и коды завершения остаются задачей блока 24.

Карта проекта

Общая модель карты содержит неизменяемый снимок дерева и метрик одного ScanId, а также производные текущую область, фильтр тестов, навигацию и выбор метрики. Неизменившийся относительный путь имеет стабильный ProjectNodeId между снимками. Графическая радиальная диаграмма, терминальное дерево и текстовый вывод командного интерфейса являются только разными представлениями.

Компонент отображения не читает файловую систему и SQLite и не определяет предметные метрики. Слой приложения отмечает устаревший снимок при изменении исходников, а после нового сканирования переносит область к одноимённому узлу либо к существующему предку. Карта не смешивает старые и новые метрики и не пытается сама следить за файловыми событиями.

Подробности: карта проекта.

Слой приложения

Слой приложения организует сценарии:

  • открыть проект;
  • сканировать;
  • построить карту и отчёт;
  • сохранить снимок;
  • получить и изменить решения;
  • создать план;
  • построить различия;
  • выполнить предварительный запуск;
  • применить план;
  • открыть архив и историю;
  • восстановить изменение.

Он не зависит от конкретных виджетов и формата терминального представления.

ProjectWorkflow — интерфейсно-независимый сценарий одного явно открытого проекта. Он координирует scan, получение immutable данных Review, dry-run, Apply, архив, restore и локальную историю; GUI и TUI передают в него только явно подтверждённый источник решения. ReviewSession также принадлежит application: смена scope, фильтра, сортировки или поиска не создаёт решений, а EditPlan строится только из уже записанного ReviewDecisionSet. Адаптеры не импортируют друг друга и не получают доступ к SQLite помимо этого сценария.

Журналирование

Постоянный журнал настраивается в слое приложения и хранится только в пользовательском каталоге CommentRake. Ядро не пишет файлы журнала и не зависит от logging.

Журнал принимает только структурированные события с закрытым списком технических полей. Произвольные сообщения, исходный код, текст комментариев, абсолютные и относительные пути, значения переменных окружения, секреты и полные трассировки исключений в него не передаются. Ошибка представляется безопасным типом и идентификаторами операции. Формат — JSON Lines в UTF-8; ротация сохраняет текущий файл и не более четырёх архивов по 2 МБ.

План и применение

решения пользователя
   → EditPlan
   → проверка отпечатков и пересечений
   → различия и предварительный запуск
   → архивные данные
   → атомарная запись
   → проверка результата
   → новый снимок

Изменённый после сканирования файл отклоняется. Восстановление проверяет последующие изменения и не перезаписывает их молча.

Подробности: безопасное изменение файлов.

Хранилище

SQLite версии 1 хранит версию схемы, реестр ProjectId, известные локальные расположения и цельные снимки сканирования в пользовательском каталоге данных CommentRake по умолчанию. На Windows это %LOCALAPPDATA%/CommentRake/state.sqlite3; при явном выборе пользователя БД может располагаться в <проект>/.commentrake/state.sqlite3. Сеансы очистки и архивные записи появятся только после своих предметных моделей в блоках Apply. Сканирование и предварительный запуск не создают в исходном проекте собственные файлы или каталоги. Общий commentrake.toml и локальная .commentrake/ создаются только отдельным сценарием сохранения настроек; изменение .gitignore — самостоятельное, идемпотентное действие этого сценария. Полный исходный код не должен дублироваться без необходимости.

Доступ к SQLite скрывается за узкими интерфейсами реестра проектов и снимков. Предметные сущности не должны зависеть от строк SQL или соединения с базой. Один снимок сохраняется одной транзакцией; неизвестная будущая или повреждённая БД не заменяется автоматически.

История состояния и архив изменений остаются разными понятиями.

Метки времени истории сохраняются как локальное ISO-8601 время пользователя с явным числовым UTC-смещением; порядок и диапазоны сравнивают реальный момент, а не строку времени. Чистое сравнение снимков отделяет изменение исходника от изменения parser/rule facts и сообщает совместимость политики и полноту анализа. Атрибуция Apply возможна только по точным отпечаткам до/после; попадание сеанса в интервал остаётся лишь recorded evidence.

Подробности: история проекта.

Интерфейсы

Командный

Typer и Rich преобразуют команды и вывод в вызовы слоя приложения. JSON имеет версию схемы, устойчивые коды завершения и не требует интерактивного ввода в автоматическом режиме.

Терминальный

Textual отображает общую карту, список результатов, план, архив и историю. Фоновая работа и отмена относятся к интерфейсному слою.

Графический

PySide6, Qt 6 и Qt Widgets отображают утверждённые макеты. Виджеты не редактируют файлы напрямую. Сканирование выполняется в фоне без небезопасных межпоточных вызовов Qt.

Исходные Qt-каталоги переводов размещаются в gui/i18n/, а собранные .qm считаются артефактами сборки. GUI по умолчанию использует английский и поддерживает русский перевод. CLI остаётся только английским; локализация TUI необязательна и не меняет контрактов ядра.

Подсветка синтаксиса должна использовать готовый механизм, совместимый с выбранным разбором. Не следует создавать собственные лексеры для каждого поддерживаемого языка.

Общая навигация, обязательные макеты и критерии их утверждения описаны в документе о проектировании интерфейсов.

Будущие клиенты

Редакторы кода, локальный программный интерфейс или веб-клиент смогут использовать слой приложения и версионированные схемы. Поднимать сетевой сервер в первой версии не требуется.

Конфигурация

Типизированная конфигурация TOML включает:

  • включаемые и исключаемые пути;
  • настройки языков;
  • включённые правила и их строгость;
  • пользовательские защищённые шаблоны;
  • настройки архива;
  • профиль осторожности;
  • версию схемы.

Настройки представления интерфейса хранятся отдельно от смысловой политики проекта.

Версия схемы, поиск файлов, языковые сопоставления, тесты, правила, защита и развитие формата описаны в документе о конфигурации.

Зависимости

Предварительный стек:

  • Python 3.12 и новее; основное окружение разработки остаётся на Python 3.12;
  • Tree-sitter;
  • PySide6 и Qt 6;
  • Typer и Rich;
  • Textual;
  • SQLite;
  • TOML;
  • pytest;
  • Ruff;
  • выбранное средство проверки типов.

Зависимости времени выполнения, разработки и сборки утверждаются отдельно до установки.

Предварительная структура пакета

commentrake/
├── src/
│   └── commentrake/
│       ├── core/
│       ├── application/
│       ├── config/
│       ├── languages/
│       │   └── shared/
│       ├── rules/
│       ├── metrics/
│       ├── plugins/
│       ├── storage/
│       ├── integrations/
│       ├── cli/
│       ├── tui/
│       └── gui/
│           └── i18n/
├── tests/
│   ├── unit/
│   ├── fixtures/
│   ├── golden/
│   └── regression/
├── docs/
│   ├── ru/
│   ├── en/
│   └── ai/
├── .codex/
│   └── skills/
├── AGENTS.md
├── ARCHITECTURE.md
├── ARCHITECTURE.ru.md
├── CONTRIBUTING.md
├── CONTRIBUTING.ru.md
├── SECURITY.md
├── SECURITY.ru.md
├── LICENSE
├── README.md
└── README.ru.md

Структура может уточняться после создания первых сущностей. Разделение ядра, приложения, адаптеров, хранилища и интерфейсов должно сохраниться.

Точки расширения

Пакет plugins/ объявляет, что в CommentRake можно расширять, и, что важнее, чего расширять нельзя. Расширяемы четыре вещи:

Точка Что добавляет
LanguagePlugin ещё один разбираемый язык
RulePlugin ещё одно правило, которое вправе сообщить о кандидате
MetricPlugin ещё одну производную метрику над уже собранными фактами
ReportExportPlugin ещё один формат вывода отчёта

Не расширяемы три вещи, и это осознанно — именно они делают инструмент безопасным для чужого дерева исходников:

  • защита. Перечень защищённых конструкций и пять категорий docs/ru/protected-constructs.md принадлежат ядру. Расширение не может снять защиту с директивы, документационного комментария или комментария семейства TODO;
  • решения. Расширение вправе сообщить о CommentFragmentMatch; Decision создаёт только человек. Срабатывание не является решением, и превратить одно в другое через эту границу нельзя;
  • запись. У последовательности EditPlan → проверка отпечатков → diff → dry-run → архив → атомарная запись точки расширения нет вообще. Расширение не видит путь применения и не получает доступного для записи обращения к исходному файлу.

Регистрация — явный вызов, а не обход окружения. PluginRegistry принимает только объект, удовлетворяющий протоколу своей точки, с уникальным kebab-case идентификатором и совпадающей версией контракта расширений; порядок регистрации сохраняется, чтобы прогон был воспроизводим. Обнаружение сторонних пакетов (например, через entry points) сознательно не реализовано: инструмент, который переписывает исходный код, не должен отдавать выполнение в своём процессе всему, что случайно оказалось установлено рядом. Такой механизм потребует отдельного решения владельца и модели доверия — списка разрешённых поставщиков, явного включения и отражения в интерфейсе.

Направление зависимостей

  • Интерфейсы зависят от слоя приложения.
  • Слой приложения зависит от ядра и абстракций хранилища.
  • Адаптеры реализуют протоколы ядра.
  • Ядро не зависит от интерфейсов, SQLite и сетевых служб.
  • Перевод и будущие интеграции подключаются через отдельные протоколы.
  • plugins/ зависит только от ядра и не знает ни об интерфейсах, ни о хранилище.

Первый сценарий ScanProject находится в слое приложения. Он один раз связывает поиск, конфигурацию, определение языка, классификацию тестов, проверенное чтение, Tree-sitter и карту проекта. Он не зависит от Typer или Rich, не сохраняет историю и не создаёт данных в открытом проекте. CLI только преобразует аргументы в этот сценарий и отображает его результат.

TranslationProvider также принадлежит слою приложения, но пока не подключён к ScanProject, CLI, TUI или GUI. Его единственная реализация — локальный NoOpTranslationProvider; реальный local или network provider требует отдельного решения и не может создавать или изменять EditPlan.

Проверка архитектуры

Основные испытания:

  • ядро без графического интерфейса;
  • одинаковые результаты во всех интерфейсах;
  • защищённые комментарии;
  • ошибка разбора;
  • нестандартные имена файлов;
  • идемпотентность;
  • устаревший план;
  • атомарная запись и отказ;
  • архив и конфликт восстановления;
  • версия схемы SQLite;
  • Windows, Linux и macOS;
  • отсутствие обязательной сети.

Полная стратегия испытаний, регрессионные примеры и измерения быстродействия описаны в документе о проверке качества.