Files
wtismycode/PLAN.md
Denis Parmeev 3701cee205 Add initial project structure and core functionality for ArchDoc
- Created `.gitignore` files for various directories to exclude unnecessary files.
- Added `PLAN.md` to outline the project goals and architecture documentation generation.
- Implemented the `archdoc-cli` with a command-line interface for initializing and generating documentation.
- Developed the `archdoc-core` library for analyzing Python projects and generating architecture documentation.
- Included caching mechanisms to optimize repeated analysis.
- Established a comprehensive test suite to ensure functionality and error handling.
- Updated `README.md` to provide an overview and installation instructions for ArchDoc.
2026-01-25 20:17:37 +03:00

723 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
```md
# ArchDoc (V1) — Проектный документ для разработки
**Формат:** PRD + Tech Spec (Python-only, CLI-only)
**Стек реализации:** Rust (CLI), анализ Python через AST, генерация Markdown (diff-friendly)
**Дата:** 2026-01-25
---
## 1. Контекст и проблема
### 1.1. Боль
- Документация архитектуры и связей в кодовой базе устаревает практически сразу.
- В новых чатах LLM не имеет контекста проекта и не понимает “рельсы”: где что лежит, какие модули, какие зависимости критичны.
- В MR/PR сложно быстро оценить архитектурный impact: что поменялось в зависимостях, какие точки “пробило” изменения.
### 1.2. Цель
Сделать CLI-инструмент, который по существующему Python-проекту генерирует и поддерживает **человеко- и LLM-читаемую** документацию:
- от верхнего уровня (папки, модули, “рельсы”)
- до **уровня функций/методов** (что делают и с чем связаны)
при этом обновление должно быть **детерминированным** и **diff-friendly**.
---
## 2. Видение продукта
**ArchDoc** — CLI на Rust, который:
1) сканирует репозиторий Python-проекта,
2) строит модель модулей/файлов/символов и связей (imports + best-effort calls),
3) генерирует/обновляет набор Markdown-файлов так, чтобы `git diff` показывал **смысловые** изменения,
4) создаёт “Obsidian-style” навигацию по ссылкам: индекс → модуль → файл → символ (function/class/method).
---
## 3. Область охвата (V1)
### 3.1. In-scope (обязательно)
- Только **CLI** (без MCP/GUI в V1).
- Только **Python** (в дальнейшем расширяемость под другие языки).
- Документация:
- `ARCHITECTURE.md` как входная точка,
- детальные страницы по модулям и файлам,
- детализация по символам (functions/classes/methods) с связями.
- Связи:
- dependency graph по импортам модулей,
- best-effort call graph на уровне файла/символа,
- inbound/outbound зависимости (кто зависит / от кого зависит).
- Diff-friendly обновление:
- маркерные секции,
- перезапись только генерируемых блоков,
- стабильные ID и сортировки.
### 3.2. Out-of-scope (V1)
- MCP, IDE-интеграции.
- Полный семантический резолв вызовов (уровень LSP/type inference) — только best-effort.
- Визуальная “сеточка графа” — в roadmap (V2+).
- LLM-суммаризация кода — V1 не должен “придумывать”; описание берём из docstring + эвристика.
---
## 4. Основные термины
### 4.1. Symbol (символ)
Именованная сущность, которой можно адресно дать документацию и связи:
- `function` / `async function` (def/async def),
- `class`,
- `method` (внутри class),
- (опционально) module/package как верхнеуровневые сущности.
**Symbol ≠ вызов.**
Symbol — это **определение**, call/reference — **использование**.
---
## 5. Пользовательские сценарии
### S1. init
Пользователь выполняет `archdoc init`:
- создаётся `ARCHITECTURE.md` (в корне проекта),
- создаётся `archdoc.toml` (рекомендуемо) и директория `docs/architecture/*` (если нет).
### S2. generate/update
Пользователь выполняет `archdoc generate` (или `archdoc update`):
- анализирует репозиторий,
- создаёт/обновляет Markdown-артефакты,
- в MR/PR дифф отражает только смысловые изменения.
### S3. check (CI)
`archdoc check`:
- завершает процесс с non-zero кодом, если текущие docs не соответствуют тому, что будет сгенерировано.
---
## 6. Продуктовые принципы (не обсуждаются)
1) **Детерминизм:** один и тот же вход → один и тот же выход.
2) **Diff-friendly:** минимальный шум в `git diff`.
3) **Ручной контент не затираем:** всё вне маркеров — зона ответственности человека.
4) **Без “галлюцинаций”:** связи выводим только из анализа (AST + индекс), иначе помечаем как unresolved/external.
5) **Масштабируемость:** кеширование, инкрементальные обновления, параллельная обработка.
---
## 7. Артефакты вывода
### 7.1. Структура файлов (рекомендуемая)
```
ARCHITECTURE.md
docs/
architecture/
_index.md
rails.md
layout.md
modules/
<module_id>.md
files/
<path_sanitized>.md
````
### 7.2. Обязательные требования к контенту
- `ARCHITECTURE.md` содержит:
- название, описание (manual),
- Created/Updated (Updated меняется **только если** изменилась любая генерируемая секция),
- rails/tooling,
- layout,
- индекс модулей,
- критичные dependency points (fan-in/fan-out/cycles).
- `modules/<module_id>.md` содержит:
- intent (manual),
- boundaries (генерируемое),
- deps inbound/outbound (генерируемое),
- symbols overview (генерируемое).
- `files/<path>.md` содержит:
- intent (manual),
- file imports + deps (генерируемое),
- индекс symbols в файле,
- **один блок на каждый symbol** с назначением и связями.
---
## 8. Diff-friendly обновление (ключевое)
### 8.1. Маркерные секции
Любая генерируемая часть окружена маркерами:
- `<!-- ARCHDOC:BEGIN section=<name> -->`
- `<!-- ARCHDOC:END section=<name> -->`
Для символов:
- `<!-- ARCHDOC:BEGIN symbol id=<symbol_id> -->`
- `<!-- ARCHDOC:END symbol id=<symbol_id> -->`
Инструмент **обновляет только содержимое внутри** этих маркеров.
### 8.2. Ручные секции
Рекомендуемый паттерн:
- `<!-- MANUAL:BEGIN -->`
- `<!-- MANUAL:END -->`
Инструмент не трогает текст в этих блоках и вообще не трогает всё, что вне `ARCHDOC` маркеров.
### 8.3. Детерминированные сортировки
- списки модулей/файлов/символов сортируются лексикографически по стабильному ключу,
- таблицы имеют фиксированный набор колонок и формат,
- запрещены “плавающие” элементы (кроме Updated, который обновляется только при изменениях).
### 8.4. Updated-таймстамп без шума
Правило V1:
- пересчитать контент-хеш генерируемых секций,
- **если** он изменился → обновить `Updated`,
- **иначе** не менять дату.
---
## 9. Stable IDs и якоря
### 9.1. Symbol ID
Формат:
- `py::<module_path>::<qualname>`
Примеры:
- `py::app.billing::apply_promo_code`
- `py::app.services.user::UserService.create_user`
Коллизии:
- добавить `#<short_hash>` (например, от сигнатуры/позиции).
### 9.2. File doc имя
`<relative_path>` конвертируется в:
- `files/<path_sanitized>.md`
- где `path_sanitized` = заменить `/` на `__`
Пример:
- `src/app/billing.py` → `docs/architecture/files/src__app__billing.py.md`
### 9.3. Якоря
Внутри file docs якорь для symbol:
- `#<anchor>` где `<anchor>` = безопасная форма от symbol_id
- дополнительно можно вставить `<a id="..."></a>`.
---
## 10. Python анализ (V1)
### 10.1. Что считаем модулем
- Python package: директория с `__init__.py`
- module: `.py` файл, который принадлежит package/root
Поддержка src-layout:
- конфиг `src_roots = ["src", "."]`
### 10.2. Извлекаем из AST (обязательно)
- `import` / `from ... import ...` + алиасы
- определения: `def`, `async def`, `class`, методы в классах
- docstring (первая строка как “краткое назначение”)
- сигнатура: аргументы, defaults, аннотации типов, return annotation (если есть)
### 10.3. Call graph (best-effort, без type inference)
Резолв вызовов:
- `Name()` вызов `foo()`:
- если `foo` определён в этом файле → связываем на локальный symbol,
- если `foo` импортирован через `from x import foo` (или алиас) → связываем на `x.foo`,
- иначе → `external_call::foo`.
- `Attribute()` вызов `mod.foo()`:
- если `mod` — импортированный модуль/алиас → резолвим к `mod.foo`,
- иначе → `unresolved_method_call::mod.foo`.
Важно: лучше пометить как unresolved, чем “натянуть” неверную связь.
### 10.4. Inbound связи (кто зависит)
- на уровне модулей/файлов: строим обратный граф импортов
- на уровне symbols: строим обратный граф calls там, где вызовы резолвятся
---
## 11. “Что делает функция” (без LLM)
### 11.1. Источник истины: docstring
- `purpose.short` = первая строка docstring
- `purpose.long` (опционально) = первые N строк docstring
### 11.2. Эвристика (если docstring нет)
- по имени: `get_*`, `create_*`, `update_*`, `delete_*`, `sync_*`, `validate_*`
- по признакам в AST:
- наличие HTTP клиентов (`requests/httpx/aiohttp`),
- DB libs (`sqlalchemy/peewee/psycopg/asyncpg`),
- tasks/queue (`celery`, `kafka`, `pika`),
- чтение/запись файлов (`open`, `pathlib`),
- raising exceptions, early returns.
Формат результата: одна строка с меткой `[heuristic]`.
### 11.3. Manual override
- секция “Manual notes” для каждого symbol — зона ручного уточнения.
---
## 12. CLI спецификация
### 12.1. Команды
- `archdoc init`
- создаёт `ARCHITECTURE.md`, `docs/architecture/*`, `archdoc.toml` (если нет)
- `archdoc generate` / `archdoc update`
- анализ + запись/обновление файлов
- `archdoc check`
- проверка: docs совпадают с тем, что будет сгенерировано
### 12.2. Флаги (V1)
- `--root <path>` (default: `.`)
- `--out <path>` (default: `docs/architecture`)
- `--config <path>` (default: `archdoc.toml`)
- `--verbose`
- `--include-tests/--exclude-tests` (можно через конфиг)
---
## 13. Конфигурация (`archdoc.toml`)
Минимальный конфиг V1:
```toml
[project]
root = "."
out_dir = "docs/architecture"
entry_file = "ARCHITECTURE.md"
language = "python"
[scan]
include = ["src", "app", "tests"]
exclude = [".venv", "venv", "__pycache__", ".git", "dist", "build", ".mypy_cache", ".ruff_cache"]
follow_symlinks = false
[python]
src_roots = ["src", "."]
include_tests = true
[output]
single_file = false
per_file_docs = true
[diff]
update_timestamp_on_change_only = true
[thresholds]
critical_fan_in = 20
critical_fan_out = 20
````
---
## 14. Шаблоны Markdown (V1)
### 14.1. `ARCHITECTURE.md` (skeleton)
(Важное: ручные блоки + маркерные генерируемые секции.)
```md
# ARCHITECTURE — <PROJECT_NAME>
<!-- MANUAL:BEGIN -->
## Project summary
**Name:** <PROJECT_NAME>
**Description:** <FILL_MANUALLY: what this project does in 37 lines>
## Key decisions (manual)
- <FILL_MANUALLY>
## Non-goals (manual)
- <FILL_MANUALLY>
<!-- MANUAL:END -->
---
## Document metadata
- **Created:** <AUTO_ON_INIT: YYYY-MM-DD>
- **Updated:** <AUTO_ON_CHANGE: YYYY-MM-DD>
- **Generated by:** archdoc (cli) v0.1
---
## Rails / Tooling
<!-- ARCHDOC:BEGIN section=rails -->
> Generated. Do not edit inside this block.
<AUTO: rails summary + links to config files>
<!-- ARCHDOC:END section=rails -->
---
## Repository layout (top-level)
<!-- ARCHDOC:BEGIN section=layout -->
> Generated. Do not edit inside this block.
<AUTO: table of top-level folders + heuristic purpose + link to layout.md>
<!-- ARCHDOC:END section=layout -->
---
## Modules index
<!-- ARCHDOC:BEGIN section=modules_index -->
> Generated. Do not edit inside this block.
<AUTO: table modules + deps counts + links to module docs>
<!-- ARCHDOC:END section=modules_index -->
---
## Critical dependency points
<!-- ARCHDOC:BEGIN section=critical_points -->
> Generated. Do not edit inside this block.
<AUTO: top fan-in/out symbols + cycles>
<!-- ARCHDOC:END section=critical_points -->
---
<!-- MANUAL:BEGIN -->
## Change notes (manual)
- <FILL_MANUALLY>
<!-- MANUAL:END -->
```
### 14.2. `docs/architecture/layout.md`
```md
# Repository layout
<!-- MANUAL:BEGIN -->
## Manual overrides
- `src/app/` — <FILL_MANUALLY>
<!-- MANUAL:END -->
---
## Detected structure
<!-- ARCHDOC:BEGIN section=layout_detected -->
> Generated. Do not edit inside this block.
<AUTO: table of paths>
<!-- ARCHDOC:END section=layout_detected -->
```
### 14.3. `docs/architecture/modules/<module_id>.md`
```md
# Module: <module_id>
- **Path:** <AUTO>
- **Type:** python package/module
- **Doc:** <AUTO: module docstring summary if any>
<!-- MANUAL:BEGIN -->
## Module intent (manual)
<FILL_MANUALLY: boundaries, responsibility, invariants>
<!-- MANUAL:END -->
---
## Dependencies
<!-- ARCHDOC:BEGIN section=module_deps -->
> Generated. Do not edit inside this block.
<AUTO: outbound/inbound modules + counts>
<!-- ARCHDOC:END section=module_deps -->
---
## Symbols overview
<!-- ARCHDOC:BEGIN section=symbols_overview -->
> Generated. Do not edit inside this block.
<AUTO: table of symbols + links into file docs>
<!-- ARCHDOC:END section=symbols_overview -->
```
### 14.4. `docs/architecture/files/<path_sanitized>.md`
```md
# File: <relative_path>
- **Module:** <AUTO: module_id>
- **Defined symbols:** <AUTO>
- **Imports:** <AUTO>
<!-- MANUAL:BEGIN -->
## File intent (manual)
<FILL_MANUALLY>
<!-- MANUAL:END -->
---
## Imports & file-level dependencies
<!-- ARCHDOC:BEGIN section=file_imports -->
> Generated. Do not edit inside this block.
<AUTO: imports list + outbound modules + inbound files>
<!-- ARCHDOC:END section=file_imports -->
---
## Symbols index
<!-- ARCHDOC:BEGIN section=symbols_index -->
> Generated. Do not edit inside this block.
<AUTO: list of links to symbol anchors>
<!-- ARCHDOC:END section=symbols_index -->
---
## Symbol details
<!-- ARCHDOC:BEGIN symbol id=py::<module>::<qualname> -->
<a id="<anchor>"></a>
### `py::<module>::<qualname>`
- **Kind:** function | class | method
- **Signature:** `<AUTO>`
- **Docstring:** `<AUTO: first line | No docstring>`
- **Defined at:** `<AUTO: line>` (optional)
#### What it does
<!-- ARCHDOC:BEGIN section=purpose -->
<AUTO: docstring-first else heuristic with [heuristic]>
<!-- ARCHDOC:END section=purpose -->
#### Relations
<!-- ARCHDOC:BEGIN section=relations -->
**Outbound calls (best-effort):**
- <AUTO: resolved symbol ids>
- external_call::<name>
- unresolved_method_call::<expr>
**Inbound (used by) (best-effort):**
- <AUTO: callers>
<!-- ARCHDOC:END section=relations -->
#### Integrations (heuristic)
<!-- ARCHDOC:BEGIN section=integrations -->
- HTTP: yes/no
- DB: yes/no
- Queue/Tasks: yes/no
<!-- ARCHDOC:END section=integrations -->
#### Risk / impact
<!-- ARCHDOC:BEGIN section=impact -->
- fan-in: <AUTO:int>
- fan-out: <AUTO:int>
- cycle participant: <AUTO: yes/no>
- critical: <AUTO: yes/no + reason>
<!-- ARCHDOC:END section=impact -->
<!-- MANUAL:BEGIN -->
#### Manual notes
<FILL_MANUALLY>
<!-- MANUAL:END -->
<!-- ARCHDOC:END symbol id=py::<module>::<qualname> -->
```
---
## 15. Техническая архитектура реализации (Rust)
### 15.1. Модули приложения (рекомендуемое разбиение crates/modules)
* `cli` — парсинг аргументов, команды init/generate/check
* `scanner` — обход файлов, ignore, include/exclude
* `python_analyzer` — AST парсер/индексатор (Python)
* `model` — IR структуры данных (ProjectModel)
* `renderer` — генерация Markdown (шаблоны)
* `writer` — diff-aware writer: обновление по маркерам
* `cache` — кеш по хешам файлов (опционально в V1, но желательно)
### 15.2. IR (Intermediate Representation) — схема данных
Минимальные сущности:
**ProjectModel**
* modules: Map<module_id, Module>
* files: Map<file_id, FileDoc>
* symbols: Map<symbol_id, Symbol>
* edges:
* module_import_edges: Vec<Edge> (module → module)
* file_import_edges: Vec<Edge> (file → module/file)
* symbol_call_edges: Vec<Edge> (symbol → symbol/external/unresolved)
**Module**
* id, path, files[], doc_summary
* outbound_modules[], inbound_modules[]
* symbols[]
**FileDoc**
* id, path, module_id
* imports[] (normalized)
* outbound_modules[], inbound_files[]
* symbols[]
**Symbol**
* id, kind, module_id, file_id, qualname
* signature (string), annotations (optional structured)
* docstring_first_line
* purpose (docstring/heuristic)
* outbound_calls[], inbound_calls[]
* integrations flags
* metrics: fan_in, fan_out, is_critical, cycle_participant
**Edge**
* from_id, to_id, edge_type, meta (optional)
---
## 16. Алгоритмы (ключевые)
### 16.1. Scanner
* применить exclude/include и игноры
* собрать список `.py` файлов
* определить src_root и module paths
### 16.2. Python Analyzer
Шаги:
1. Пройти по каждому `.py` файлу
2. Распарсить AST
3. Извлечь:
* imports + алиасы
* defs/classes/methods + сигнатуры + docstrings
* calls (best-effort)
4. Построить Symbol Index: `name → symbol_id` в рамках файла и модуля
5. Резолвить calls через:
* локальные defs
* from-import алиасы
* import module алиасы
6. Построить edges, затем обратные edges (inbound)
### 16.3. Writer (diff-aware)
* загрузить существующий md (если есть)
* найти маркеры секций
* заменить содержимое секции детерминированным рендером
* сохранить всё вне маркеров неизменным
* если файл отсутствует → создать по шаблону
* пересчитать общий “генерируемый хеш”:
* если изменился → обновить `Updated`, иначе оставить
---
## 17. Критичные точки (impact analysis)
Метрики:
* **fan-in(symbol)** = число inbound вызовов (resolved)
* **fan-out(symbol)** = число outbound вызовов (resolved + unresolved по отдельному счётчику)
* **critical**:
* `fan-in >= thresholds.critical_fan_in` OR
* `fan-out >= thresholds.critical_fan_out` OR
* участие в цикле модулей
Выводить top-N списки в `ARCHITECTURE.md`.
---
## 18. Нефункциональные требования
* Время генерации: приемлемо на средних репо (ориентир — минуты, с перспективой кеширования).
* Память: не грузить весь исходный текст в память надолго; хранить только необходимое.
* Безопасность: по умолчанию не включать секреты/бинарники; уважать exclude.
* Надёжность: если AST не парсится (битый файл) — лог + продолжить анализ остальных, пометив файл как failed.
---
## 19. Acceptance Criteria (V1)
1. `archdoc init` создаёт:
* `ARCHITECTURE.md` с manual блоками и маркерами секций
* `docs/architecture/*` с базовыми файлами (или создаёт при generate)
2. Повторный `archdoc generate` на неизменном репо даёт:
* нулевой diff (включая `Updated`, который не меняется без контентных изменений)
3. Изменение одной функции/файла приводит:
* к локальному diff только соответствующего symbol блока и агрегатов (indexes/critical points)
4. `archdoc check` корректно детектит рассинхронизацию и возвращает non-zero.
---
## 20. План релизов (Roadmap)
### V1 (текущий документ)
* Python-only CLI
* modules/files/symbols docs
* import graph + best-effort call graph
* diff-friendly writer
* init/generate/check
### V2 (следующий шаг)
* Экспорт графа в JSON/Mermaid
* Простая локальная HTML/MD визуализация “как в Obsidian” (сетка зависимостей)
* Улучшение резолва calls (больше случаев через алиасы/простые типы)
### V3+
* Подключение других языков (через tree-sitter провайдеры)
* Опционально LSP режим для точного call graph
* MCP/IDE интеграции
---
## 21. Backlog (V1 — минимально достаточный)
### Эпик A — CLI и конфиг
* A1: `init` создаёт skeleton + config
* A2: `generate/update` парсит конфиг и пишет docs
* A3: `check` сравнивает с виртуально сгенерированным выводом
### Эпик B — Python анализ
* B1: scanner и определение module paths
* B2: AST import extraction + алиасы
* B3: defs/classes/methods extraction + signatures/docstrings
* B4: call extraction + best-effort resolution
* B5: inbound/outbound построение графов
### Эпик C — Markdown генерация и writer
* C1: renderer шаблонов
* C2: marker-based replace секций
* C3: stable sorting и формат таблиц
* C4: update timestamp on change only
### Эпик D — Critical points
* D1: fan-in/fan-out метрики
* D2: top lists в ARCHITECTURE.md
* D3: module cycles detection (простая графовая проверка)
---
## 22. Примечания по качеству (сразу закладываем тестируемость)
* Golden-tests: на маленьком fixture repo хранить ожидаемые md и проверять детерминизм.
* Unit-tests на writer: заменить секцию без изменения остального файла.
* Unit-tests на import/call resolution: алиасы `import x as y`, `from x import a as b`.
---
## 23. Итог
V1 фиксирует базовый продукт: **полная архитектурная документация до уровня функций** с зависимостями и impact, обновляемая безопасно и читаемо через `git diff`. Инструмент закрывает задачу: дать LLM и человеку стабильную “карту проекта” и контролировать критичные точки при изменениях.
---
```
```