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

25 KiB
Raw Blame History

# 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)

(Важное: ручные блоки + маркерные генерируемые секции.)

# 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

# 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

# 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

# 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 (module → module)
    • file_import_edges: Vec (file → module/file)
    • symbol_call_edges: Vec (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 и человеку стабильную “карту проекта” и контролировать критичные точки при изменениях.