revdeck 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/CONTRIBUTING.md +108 -0
  3. package/LICENSE +21 -0
  4. package/README.md +262 -2
  5. package/README.ru.md +249 -0
  6. package/SECURITY.md +45 -0
  7. package/dist/cli.js +3226 -0
  8. package/dist/cli.js.map +7 -0
  9. package/dist/daemon.js +4002 -0
  10. package/dist/daemon.js.map +7 -0
  11. package/dist/mcp.js +901 -0
  12. package/dist/mcp.js.map +7 -0
  13. package/dist/ui/assets/index-BAejn_X6.js +10 -0
  14. package/dist/ui/assets/index-xv-zA2Sf.css +1 -0
  15. package/dist/ui/favicon.svg +4 -0
  16. package/dist/ui/index.html +16 -0
  17. package/docs/README.md +17 -0
  18. package/docs/adr/0001-daemon-first.md +64 -0
  19. package/docs/architecture.md +92 -0
  20. package/docs/configuration.md +123 -0
  21. package/docs/integrations.md +123 -0
  22. package/docs/protocol.md +102 -0
  23. package/docs/ru/README.md +17 -0
  24. package/docs/ru/adr/0001-daemon-first.md +62 -0
  25. package/docs/ru/architecture.md +92 -0
  26. package/docs/ru/configuration.md +121 -0
  27. package/docs/ru/integrations.md +122 -0
  28. package/docs/ru/protocol.md +102 -0
  29. package/docs/ru/threat-model.md +52 -0
  30. package/docs/screenshot.png +0 -0
  31. package/docs/screenshot.ru.png +0 -0
  32. package/docs/threat-model.md +53 -0
  33. package/examples/monorepo.yml +38 -0
  34. package/examples/simple.yml +36 -0
  35. package/examples/web-chat-core.yml +45 -0
  36. package/package.json +79 -4
  37. package/skills/revdeck/en/Criteria.md +265 -0
  38. package/skills/revdeck/en/Feedback.md +64 -0
  39. package/skills/revdeck/en/Review.md +157 -0
  40. package/skills/revdeck/en/Rules.md +75 -0
  41. package/skills/revdeck/en/SKILL.md +78 -0
  42. package/skills/revdeck/en/Sources.md +47 -0
  43. package/skills/revdeck/en/agents/openai.yaml +4 -0
  44. package/skills/revdeck/ru/Criteria.md +249 -0
  45. package/skills/revdeck/ru/Feedback.md +64 -0
  46. package/skills/revdeck/ru/Review.md +148 -0
  47. package/skills/revdeck/ru/Rules.md +69 -0
  48. package/skills/revdeck/ru/SKILL.md +73 -0
  49. package/skills/revdeck/ru/Sources.md +46 -0
  50. package/skills/revdeck/ru/agents/openai.yaml +4 -0
@@ -0,0 +1,92 @@
1
+ # Архитектура
2
+
3
+ [English](../architecture.md) | Русский
4
+
5
+ ## Цели
6
+
7
+ 1. Агент получает контекст ревью и правила проекта детерминированно, без ручных файлов и без доступа
8
+ к токенам платформ.
9
+ 2. Публикация в GitLab/GitHub происходит только после подтверждения человеком и идемпотентна.
10
+ 3. Один инструмент работает в любом харнессе: MCP для агентских клиентов, CLI как универсальный
11
+ слой, веб-интерфейс для человека.
12
+ 4. Правила проекта конфигурируются, а не зашиты в промпт.
13
+
14
+ ## Не цели
15
+
16
+ - Замена нативного diff-вьюера GitLab/GitHub. UI оптимизирован под триаж и подтверждение.
17
+ - Собственный статический анализ. Проверки делегируются линту, типам и тестам проекта.
18
+ - Многопользовательский сервер. Модель — один пользователь, несколько ревью, демон на хосте.
19
+
20
+ ## Компоненты
21
+
22
+ ```text
23
+ agent (MCP/CLI) ──▶ revdeck daemon ──glab/gh──▶ GitLab/GitHub
24
+ │
25
+ SQLite + UI ◀── human
26
+ ```
27
+
28
+ - `src/core` — чистые функции и модели: парсер unified diff, правила, fingerprints замечаний,
29
+ санитайзер публикации, редакция секретов, безопасный запуск процессов.
30
+ - `src/providers` — единственное место, знающее о платформах. `Provider` нормализует MR/PR, diff,
31
+ треды, ответы, резолв и создание комментариев.
32
+ - `src/daemon/service.ts` — оркестратор: обновление ревью, черновики, решения, план публикации,
33
+ approval, идемпотентная запись, фоновый sync.
34
+ - `src/daemon/store.ts` — SQLite (`node:sqlite`): ревью, снимок diff/тредов, черновики, замечания,
35
+ решения, комментарии, approvals, аудит.
36
+ - `src/daemon/rpc.ts` — JSON-RPC поверх unix-сокета (0600) для CLI и MCP.
37
+ - `src/daemon/http.ts` — UI и `/api/*`, SSE-события, cookie-сессия с одноразовым токеном.
38
+ - `src/cli` — команды, `init`, `doctor`, экспорт/импорт старого файлового формата.
39
+ - `src/mcp` — MCP-сервер: инструменты и промпты, тонкая обёртка над RPC.
40
+ - `ui` — Svelte 5 SPA, собирается в `dist/ui`.
41
+
42
+ ## Поток данных
43
+
44
+ 1. `revdeck open 42` → RPC `reviews.open` с `root` рабочей копии.
45
+ 2. Демон определяет платформу/хост/проект (аргументы → конфиг → git remote), создаёт провайдер,
46
+ читает MR, diff и треды, сохраняет снимок и синтетические черновики.
47
+ 3. Агент запрашивает `revdeck_context`: демон собирает эффективные правила (документы и секции для
48
+ изменённых путей), сниппеты, треды и замечания.
49
+ 4. Агент пишет черновики (`drafts.put`, `findings.put`, `summary.put`, `notes.update`).
50
+ 5. Человек в UI вызывает `publish.preview` → видит точный план и хеш → `publish.approve` →
51
+ `publish.commit`. Демон выполняет только пункты, вошедшие в подтверждённый план.
52
+ 6. Фоновый sync раз в 2 минуты обновляет открытые ревью и рассылает SSE-события UI.
53
+
54
+ ## Модель данных
55
+
56
+ - `ReviewRef` — платформа, хост, проект, номер. `reviewId` — `platform:host/project!number`.
57
+ - `DiffFile`/`Hunk`/`DiffLine` — номера строк по старой и новой версии; `line` замечания всегда по
58
+ новому файлу.
59
+ - `Thread`/`Note` — общая модель обсуждений.
60
+ - `Finding` — замечание с `id`, `fingerprint`, уровнем, файлом/строкой, текстом и статусом
61
+ (`draft`, `accepted`, `rejected`, `published`, `fixed`, `stale`).
62
+ - `Approval` — одноразовый id, привязанный к `hash = sha256(canonicalJson({reviewId, kind, items}))`,
63
+ TTL 10 минут.
64
+
65
+ ## Идемпотентность и конкурентность
66
+
67
+ - Записи черновиков и замечаний идут через один процесс; SQLite в WAL.
68
+ - Публикация помечает approval использованным до выполнения. Пункты, которые уже ушли (совпадающий
69
+ `posted`, `published`), пропускаются. Ошибка одного пункта не отменяет остальные и записывается в
70
+ черновик/замечание.
71
+ - Хеш плана защищает от правки агентом между показом человеку и публикацией: изменился текст → хеш
72
+ не совпал → approve отклонён.
73
+ - `findings.put` — замена набора с сохранением `id` и решения; исчезнувшие замечания помечаются
74
+ `stale`, а не удаляются.
75
+
76
+ ## Расширение
77
+
78
+ - **Новая платформа**: реализовать `Provider` и добавить ветку в `createProvider`; всё остальное не
79
+ меняется.
80
+ - **Новый харнесс**: `revdeck init --adapters <имя>` пишет MCP-конфиг или файл команды и ссылается на
81
+ канонический skill `.agents/skills/revdeck`. Для
82
+ харнессов без MCP используется CLI.
83
+ - **Правила**: `rules.map`/`rules.sources` в `.revdeck.yml`; паки правил можно распространять как
84
+ обычный файл конфигурации.
85
+ - **Проверки**: `checks` с argv без shell; запуск только по явному подтверждению.
86
+
87
+ ## Производительность
88
+
89
+ - Diff парсится на сервере, UI получает файлы списком и hunks по одному файлу.
90
+ - Состояние ревью не содержит полный diff; SSE сообщает об изменениях, UI перезапрашивает состояние.
91
+ - Сниппеты для тредов и замечаний считаются на сервере и ограничены по размеру.
92
+ - Контекст правил ограничен 320 КБ; MCP-контекст — параметром `max_bytes`.
@@ -0,0 +1,121 @@
1
+ # Конфигурация
2
+
3
+ [English](../configuration.md) | Русский
4
+
5
+ Файл `.revdeck.yml` (или `.revdeck.yaml`) в корне репозитория. Схема проверяется через zod;
6
+ неизвестное поле — ошибка с путём.
7
+
8
+ ```yaml
9
+ version: 1
10
+ provider:
11
+ type: auto # auto | gitlab | github
12
+ host: gitlab.example.com
13
+ project: group/project
14
+ base: develop
15
+ output:
16
+ language: auto # auto | ru | en — язык замечаний и ответов
17
+ rules:
18
+ sources: [AGENTS.md]
19
+ map:
20
+ - match: ["src/**", "lib/**"]
21
+ docs: ["docs/Architecture.md", "docs/Styleguide.md#Базовый стиль"]
22
+ note: "прикладной код"
23
+ ignore: ["**/generated/**"]
24
+ checks:
25
+ - id: lint
26
+ name: ESLint
27
+ argv: [npm, run, lint]
28
+ when: ["src/**"]
29
+ consent: true
30
+ timeoutMs: 300000
31
+ publish:
32
+ requireApproval: true
33
+ maxComments: 50
34
+ ```
35
+
36
+ ## provider
37
+
38
+ | Поле | По умолчанию | Смысл |
39
+ |---|---|---|
40
+ | `type` | `auto` | `auto` — по git remote; иначе явная платформа |
41
+ | `host` | из remote | хост без схемы |
42
+ | `project` | из remote | GitLab `group/project`, GitHub `owner/repo` |
43
+ | `base` | `develop`/`main` | база для локального diff |
44
+
45
+ Явные аргументы CLI (`--platform`, `--project`, `--host`) важнее конфига, конфиг важнее git remote.
46
+ Так подмена `.git/config` не перенаправит ревью на чужой проект.
47
+
48
+ ## output
49
+
50
+ - `language: auto` — замечания и ответы пишутся на языке описания MR и обсуждений; если текста нет,
51
+ используется английский. `ru`/`en` фиксируют язык явно.
52
+ - Язык правил и документов от этого не зависит: агент читает их как есть и цитирует в оригинале.
53
+ - Это язык **результата ревью**, а не интерфейса CLI/UI. Интерфейс переключается `--lang ru|en`,
54
+ `REVDECK_LANG` или переключателем в UI.
55
+ - `revdeck init --lang en` выбирает английский skill и пишет `REVDECK_LANG` в MCP-конфиг.
56
+
57
+ ## rules
58
+
59
+ - `sources` — документы по умолчанию, если ни одна строка `map` не подошла. Пусто — автообнаружение
60
+ (`AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `docs/ARCHITECTURE.md`, `docs/Architecture.md`,
61
+ `docs/STYLEGUIDE.md`, `docs/Styleguide.md`, `docs/CONTRIBUTING.md`).
62
+ - `map` — список правил: `match` (glob или массив glob) и `docs` (массив селекторов). Несколько
63
+ совпавших строк объединяются.
64
+ - `ignore` — glob-исключения: файл не попадает в анализ правил.
65
+
66
+ ### Селекторы документов
67
+
68
+ | Селектор | Что берётся |
69
+ |---|---|
70
+ | `docs/Architecture.md` | весь документ |
71
+ | `docs/Styleguide.md#Базовый стиль` | секция заголовка (уровень и ниже, до заголовка того же уровня) |
72
+ | `docs/File.md#L10-L25` | диапазон строк |
73
+ | `docs/File.md#10-25` | то же |
74
+
75
+ Заголовок нормализуется: регистр, пробелы и пунктуация не важны, `#Слои и зависимости` и
76
+ `#layers` — разные секции, а `#Слои-и-зависимости` и `#Слои и зависимости` — одна. Если секция не
77
+ найдена, revdeck пишет предупреждение и не подставляет весь документ.
78
+
79
+ ### Glob
80
+
81
+ `*` — в пределах одного сегмента, `**` — любая глубина, `?` — один символ, `{a,b}` — альтернатива.
82
+ Примеры: `src/**/*.ts`, `docs/{guides,adr}/**`, `**/*.test.ts`.
83
+
84
+ ## checks
85
+
86
+ Проверки — argv-массивы, без shell-строк. Это важно: конфиг лежит в репозитории, и строка shell
87
+ позволила бы исполнить произвольный код при ревью.
88
+
89
+ | Поле | Смысл |
90
+ |---|---|
91
+ | `id` | стабильный идентификатор |
92
+ | `name` | отображаемое имя |
93
+ | `argv` | команда и аргументы; первым идёт исполняемый файл |
94
+ | `when` | glob-пути: запускать, только если diff их затронул; пусто — всегда |
95
+ | `consent` | требует явного подтверждения (по умолчанию `true`) |
96
+ | `timeoutMs` | таймаут (по умолчанию 300 000) |
97
+
98
+ Запуск: `revdeck checks <N> --run --yes`. MCP-инструмент только показывает план.
99
+
100
+ ## publish
101
+
102
+ - `requireApproval` — оставлен для совместимости; approval требуется всегда, когда публикует не
103
+ интерактивный процесс.
104
+ - `maxComments` — максимум комментариев за один план (1–200).
105
+
106
+ ## Переменные окружения
107
+
108
+ | Переменная | Смысл |
109
+ |---|---|
110
+ | `REVDECK_STATE_DIR` | каталог состояния (по умолчанию `~/.local/state/revdeck`) |
111
+ | `REVDECK_RUNTIME_DIR` | каталог сокета (по умолчанию `$XDG_RUNTIME_DIR/revdeck`) |
112
+ | `REVDECK_URL` + `REVDECK_TOKEN` | подключение к демону по HTTP (контейнер/проброс порта) |
113
+ | `REVDECK_DEV_CONTAINER=1` | не запускать локальный демон, а подсказать про демон хоста |
114
+ | `REVDECK_UI_HOST` | хост для `revdeck ui` из контейнера (например, `host.docker.internal`) |
115
+ | `REVDECK_ROOT` | корень проекта для MCP-сервера |
116
+ | `REVDECK_LANG` | язык CLI/MCP/демона: `ru` или `en` (по умолчанию системная локаль) |
117
+
118
+ ## Валидация
119
+
120
+ `revdeck config --json` показывает эффективный конфиг, найденный путь и предупреждения.
121
+ `revdeck rules --explain src/app.ts` показывает, какие документы и секции выбраны для файла.
@@ -0,0 +1,122 @@
1
+ # Интеграция с харнессами
2
+
3
+ [English](../integrations.md) | Русский
4
+
5
+ У revdeck два переносимых слоя и один адаптерный:
6
+
7
+ - **MCP** (stdio) — инструменты `revdeck_*` работают в любом клиенте с MCP.
8
+ - **CLI** — те же возможности без MCP: `revdeck open/context/findings/reply/publish`.
9
+ - **`/`-команда** — у каждого харнесса своя; `revdeck init` создаёт нужный файл.
10
+
11
+ Полноценная интеграция = MCP + skill (процедуры) + команда (точка входа). Без MCP остаются CLI и
12
+ skill: агент вызывает команды через shell.
13
+
14
+ ## Матрица
15
+
16
+ | Харнесс | `/`-команда | Что пишет `init` |
17
+ |---|---|---|
18
+ | Claude Code | `/revdeck` | `.claude/commands/revdeck.md`, `.claude/skills/revdeck` → `../../.agents/skills/revdeck`, `.mcp.json` |
19
+ | Cursor | `/revdeck` | `.cursor/commands/revdeck.md`, `.cursor/mcp.json` |
20
+ | Codex CLI | prompt `revdeck` | `.codex/prompts/revdeck.md`; MCP — в `~/.codex/config.toml` (вручную) |
21
+ | Gemini CLI | `/revdeck` | `.gemini/commands/revdeck.toml`; MCP — в настройках Gemini (вручную) |
22
+ | OpenCode | `/revdeck` | `.opencode/command/revdeck.md` |
23
+ | GitHub Copilot | prompt-файл | `.github/prompts/revdeck.prompt.md` |
24
+ | DSH | плагин (см. ниже) | `.agents/skills/revdeck/*` (включая `agents/openai.yaml`) + YAML-сниппет в выводе |
25
+ | Универсальный | — | `.agents/skills/revdeck/*` (включая `agents/openai.yaml`) + MCP |
26
+
27
+ `revdeck init --adapters dsh,claude` включает нужные явно. Повторный `init` не перезаписывает
28
+ существующие командные файлы и не затирает чужие ключи `.mcp.json`.
29
+
30
+ Скилл хранится в одном каноническом месте: `.agents/skills/revdeck` (включая `agents/openai.yaml`
31
+ для OpenAI-харнессов). Харнессы со своим каталогом скиллов получают ссылку на него:
32
+ `.claude/skills/revdeck` — симлинк на `../../.agents/skills/revdeck`; на Windows и ФС без симлинков
33
+ используется копия. Старые копии, созданные предыдущим `init`, заменяются, если в них остался
34
+ `name: revdeck`.
35
+
36
+ Вложения комментариев загружает демон на хосте. GitLab использует project uploads и возвращает
37
+ абсолютный URL; у GitHub публичного API загрузки нет, поэтому редактор просит вставить ссылку.
38
+
39
+ ## MCP
40
+
41
+ ```json
42
+ {
43
+ "mcpServers": {
44
+ "revdeck": {
45
+ "command": "npx",
46
+ "args": ["-y", "revdeck", "mcp"],
47
+ "env": { "REVDECK_LANG": "en" }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ `init` пишет `npx -y revdeck mcp` в MCP-конфиг. Публикация через MCP
54
+ требует `approval_id`: его выдаёт человек в UI или CLI с `--yes`, агент получить не может.
55
+
56
+ ## DSH
57
+
58
+ MCP-клиент DSH подключается в профиле Cordis:
59
+
60
+ ```yaml
61
+ - id: mcp-revdeck
62
+ name: '@deepseek-ai/dsh-mcp-client'
63
+ config:
64
+ serverName: revdeck
65
+ transport: stdio
66
+ command: npx
67
+ args: ['-y', 'revdeck', 'mcp']
68
+ env:
69
+ REVDECK_LANG: en
70
+ ```
71
+
72
+ Инструменты появятся как `mcp__revdeck__*`. MCP prompt templates DSH не поддерживает, поэтому
73
+ `/revdeck` требует host-команды (`ctx.commands.register`) или client action — это отдельный
74
+ плагин-обёртка. До него используйте skill + MCP-инструменты: `/`-слэш не обязателен для работы.
75
+
76
+ ## Контейнер и devcontainer
77
+
78
+ Демон, `glab` и `gh` остаются на хосте; контейнеру нужен только CLI или MCP-сервер revdeck.
79
+ Настройки devcontainer генерируются из корня репозитория:
80
+
81
+ ```bash
82
+ revdeck devcontainer # показать mounts и окружение
83
+ revdeck devcontainer --write # добавить их в .devcontainer/devcontainer.json
84
+ ```
85
+
86
+ `--write` монтирует runtime- и state-каталоги хоста по фиксированным путям и выставляет:
87
+
88
+ ```json
89
+ {
90
+ "REVDECK_RUNTIME_DIR": "/run/revdeck",
91
+ "REVDECK_STATE_DIR": "/var/lib/revdeck",
92
+ "REVDECK_DEV_CONTAINER": "1",
93
+ "REVDECK_UI_HOST": "host.docker.internal"
94
+ }
95
+ ```
96
+
97
+ Runtime-каталог монтируется на запись (там сокет), state-каталог — только на чтение (клиент читает
98
+ `daemon.json` с токеном). Чужие mounts, переменные окружения и `runArgs` сохраняются; revdeck
99
+ управляет только двумя своими mounts и ключами `REVDECK_*`.
100
+
101
+ После пересборки контейнера запустите демон на хосте (`revdeck daemon start`). В контейнере revdeck
102
+ никогда не запускает свой демон: если сокет недоступен, команда падает с ошибкой `DEVCONTAINER` и
103
+ подсказкой. `revdeck doctor` не ищет `glab`/`gh` в контейнере.
104
+
105
+ Пользователь контейнера должен иметь доступ к сокету хоста (обычно совпадает UID; сокет 0600). Чтобы
106
+ открыть UI из контейнера, запустите демон на хосте с `revdeck daemon start --host 0.0.0.0` и
107
+ используйте `host.docker.internal`; иначе открывайте UI на хосте.
108
+
109
+ Запасные варианты:
110
+
111
+ 1. `REVDECK_URL=http://host.docker.internal:<port>` и `REVDECK_TOKEN=<token>` — HTTP-транспорт,
112
+ демон на хосте запущен с `--host 0.0.0.0`.
113
+ 2. `revdeck export <N>` на хосте и `revdeck import <N>` в контейнере — файловый мост.
114
+
115
+ ## Проверка
116
+
117
+ ```bash
118
+ revdeck doctor
119
+ ```
120
+
121
+ Показывает найденные бинарники, авторизацию `glab`/`gh`, состояние демона и установленный адаптер. В
122
+ devcontainer проверяет демон хоста, а не ищет `glab`/`gh` внутри.
@@ -0,0 +1,102 @@
1
+ # Протокол
2
+
3
+ [English](../protocol.md) | Русский
4
+
5
+ ## Транспорты
6
+
7
+ 1. **Unix-сокет** (основной): `$XDG_RUNTIME_DIR/revdeck/daemon.sock`, каталог 0700, сокет 0600.
8
+ Строки JSON, по одному запросу/ответу на строку.
9
+ 2. **HTTP**: `POST /api/rpc` на loopback с cookie-сессией. Нужен для контейнеров, куда сокет не
10
+ проброшен (`REVDECK_URL` + `REVDECK_TOKEN`).
11
+ 3. **UI HTTP**: `/api/*` и SSE `/api/events` для браузера.
12
+
13
+ ## Сообщения
14
+
15
+ Запрос:
16
+
17
+ ```json
18
+ {"id": 1, "method": "reviews.get", "params": {"review": "gitlab:gitlab.com/group/project!42"}}
19
+ ```
20
+
21
+ Ответ:
22
+
23
+ ```json
24
+ {"id": 1, "result": {"review": {"id": "...", "info": {}, "threads": []}}}
25
+ ```
26
+
27
+ Ошибка:
28
+
29
+ ```json
30
+ {"id": 1, "error": {"message": "...", "code": "APPROVAL", "hint": "..."}}
31
+ ```
32
+
33
+ `id` — число. Соединение считается недоверенным до первого успешного запроса: авторизация — права
34
+ файла сокета.
35
+
36
+ ## Методы
37
+
38
+ | Метод | Параметры | Результат |
39
+ |---|---|---|
40
+ | `ping` | — | версия, протокол, pid |
41
+ | `reviews.list` | — | сохранённые ревью |
42
+ | `reviews.remoteList` | `root`, `scope`, `limit` | открытые ревью с платформы |
43
+ | `reviews.open` | `root`, `input`/`platform`/`project`/`host`/`branch` | снимок ревью |
44
+ | `reviews.refresh` | `review` | обновлённый снимок |
45
+ | `reviews.get` | `review`, `refresh?` | снимок (без полного diff) |
46
+ | `diff.get` | `review`, `file` | hunks файла |
47
+ | `drafts.put` | `review`, `threadId`, `patch` | черновик |
48
+ | `findings.put` | `review`, `findings[]` | замечания после merge |
49
+ | `findings.list` | `review` | замечания |
50
+ | `summary.put` | `review`, `summary` | выжимка |
51
+ | `decisions.put` | `review`, `decisions` | решения человека |
52
+ | `notes.add` | `review`, `file`, `line`, `text`, `toAgent` | комментарий к строке |
53
+ | `notes.update` | `review`, `noteId`, `patch` | обновлённый комментарий |
54
+ | `reactions.list` | `review`, `noteIds[]` | реакции, сгруппированные по id комментария |
55
+ | `reactions.toggle` | `review`, `noteId`, `emoji` | `active` и актуальный список реакций |
56
+ | `attachments.upload` | `review`, `name`, `contentType`, `data` (base64) | `name`, абсолютный `url`, `markdown` |
57
+ | `notes.delete` | `review`, `noteId` | `{ok}` |
58
+ | `publish.preview` | `review`, `kind`, `ids[]` | план + предупреждения |
59
+ | `publish.approve` | `review`, `kind`, `ids[]`, `hash` | `approvalId`, `expiresAt` |
60
+ | `publish.commit` | `review`, `approvalId` | результаты по пунктам |
61
+ | `rules.get` | `review` | markdown правил, проверки, предупреждения |
62
+ | `checks.run` | `review`, `allowRun`, `ids[]` | результаты проверок |
63
+ | `audit.tail` | `limit` | последние действия |
64
+
65
+ ## Approval
66
+
67
+ 1. `publish.preview` строит `items` — точные тела, пути, строки, флаги резолва — и
68
+ `hash = sha256(canonicalJson({reviewId, kind, items}))`.
69
+ 2. Человек видит план. `publish.approve` пересчитывает план по тем же `ids`; если хеш не совпал,
70
+ возвращает ошибку. Иначе создаёт одноразовый `approval_id` с TTL 10 минут.
71
+ 3. `publish.commit` проверяет, что approval не использован и не истёк, помечает его использованным и
72
+ выполняет только `items`. Уже отправленные пункты пропускаются.
73
+
74
+ Агент не может вызвать `publish.approve`: MCP-инструмент его не предоставляет. Человек подтверждает
75
+ в UI или CLI с `--yes`.
76
+
77
+ ## HTTP API (UI)
78
+
79
+ | Метод | Путь | Назначение |
80
+ |---|---|---|
81
+ | GET | `/` | SPA; `?t=<token>` ставит cookie и редиректит |
82
+ | GET | `/assets/*` | статика сборки |
83
+ | GET | `/api/health` | живость |
84
+ | GET | `/api/reviews` | список ревью |
85
+ | GET | `/api/state?review=` | снимок для UI |
86
+ | GET | `/api/diff?review=&file=` | hunks файла |
87
+ | GET | `/api/rules?review=` | правила |
88
+ | GET | `/api/events` | SSE: `review` |
89
+ | POST | `/api/reviews/open`, `/api/reviews/refresh` | открыть/обновить |
90
+ | POST | `/api/drafts`, `/api/findings`, `/api/summary`, `/api/decisions` | черновики |
91
+ | POST | `/api/notes` | `action: add/update/delete` |
92
+ | GET | `/api/reactions?review=&notes=` | реакции для id комментариев |
93
+ | POST | `/api/reactions` | переключить реакцию |
94
+ | POST | `/api/attachments` | загрузить картинку/видео и вернуть абсолютную ссылку |
95
+ | POST | `/api/publish/preview`, `/approve`, `/commit` | публикация |
96
+ | POST | `/api/checks` | проверки |
97
+ | POST | `/api/rpc` | RPC-прокси для HTTP-транспорта |
98
+
99
+ ## Версионирование
100
+
101
+ `PROTOCOL_VERSION` в `src/version.ts` сверяется при рукопожатии; клиент и демон одной версии пакета.
102
+ Несовпадение методов возвращает `METHOD` с подсказкой обновить revdeck.
@@ -0,0 +1,52 @@
1
+ # Модель угроз
2
+
3
+ [English](../threat-model.md) | Русский
4
+
5
+ ## Активы
6
+
7
+ 1. Токены GitLab/GitHub (в конфигурации `glab`/`gh` на хосте).
8
+ 2. Код и diff ревьюируемого репозитория, включая приватный.
9
+ 3. Треды и комментарии, включая персональные данные.
10
+ 4. Право публикации от имени пользователя.
11
+ 5. Токен запуска UI и токен сессии браузера.
12
+ 6. Локальная база SQLite с текстами комментариев и черновиков.
13
+
14
+ ## Доверенные границы
15
+
16
+ 1. **Агент (песочница/контейнер) ↔ демон на хосте.** Агент недоверенный: его вывод может быть
17
+ подменён содержимым MR.
18
+ 2. **Браузер ↔ демон.** Браузер доверенный, но на loopback может прийти чужая страница.
19
+ 3. **Репозиторий ↔ демон.** Конфиг и документы репозитория контролирует автор MR; они не должны
20
+ исполняться.
21
+ 4. **Демон ↔ `glab`/`gh`.** CLI — доверенный, но его окружение и рабочая копия могут быть подменены.
22
+
23
+ ## Угрозы и меры
24
+
25
+ | Угроза | Мера | Остаточный риск |
26
+ |---|---|---|
27
+ | Агент вытягивает токен платформы | Токен не читается: `glab`/`gh` берут его из своей конфигурации. Агенту доступен только RPC; токены вычищаются из env дочерних процессов, редактируются в логах/ошибках | Процесс с чтением `$HOME` пользователя может прочитать конфиг CLI. Поэтому нужна изоляция агента |
28
+ | Подмена проекта/хоста через `.git/config` | Явные `host`/`project` в конфиге и аргументах важнее remote; `glab`/`gh` вызываются с `cwd` вне рабочей копии | Если проект не закреплён, remote используется — UI показывает проект для сверки |
29
+ | Prompt injection из комментариев/diff | Инструкции в тексте помечены как данные; агент не публикует сам; публикация требует approval человека; санитайзер блокирует скрытые символы и быстрые команды | Модель всё равно может предложить вредный текст — его увидит человек в плане |
30
+ | Случайная публикация не того текста | План с точными телами и хешем; `approve` сверяет хеш; `commit` выполняет только пункты плана; повторная отправка пропускается | Человек может подтвердить не глядя — UI показывает полный текст |
31
+ | Межсайтовые запросы к loopback (DNS rebinding, CSRF) | Только `127.0.0.1`/`localhost` в `Host`; проверка `Origin` для POST; cookie `HttpOnly`, `SameSite=Strict`; одноразовый токен в ссылке запуска; CSP | Локальный процесс другого пользователя с доступом к сокету — вне модели (права 0600/0700) |
32
+ | Символическая ссылка/TOCTOU в рабочем каталоге | Состояние хранится в приватном каталоге, запись атомарная; чтение файлов репозитория только внутри корня, без следования ссылкам в служебных путях | Гонка на файлах репозитория возможна, но не даёт прав публикации |
33
+ | Вредоносный `.revdeck.yml` исполняет код | `checks.argv` — массив без shell; запуск только по `--run --yes`; MCP не умеет запускать | Пользователь может подтвердить запуск вредоносной команды из чужого MR — читайте план |
34
+ | Утечка через логи/ошибки | Редакция токенов, ANSI, управляющих символов; ограничение длины; логи в приватном каталоге | Текст комментариев может содержать секреты — не публикуйте их |
35
+ | Supply chain | Минимум зависимостей, lock-файл, сборка esbuild/Vite, provenance при публикации npm | Как у любого npm-пакета |
36
+ | Кража approval | Approval — случайные 16 байт, TTL 10 минут, одноразовый, привязан к review+hash, хранится только в SQLite на хосте | Доступ к сокету = доступ к RPC; защищён правами файла |
37
+
38
+ ## Что не является целью
39
+
40
+ - Защита от администратора/пользователя, который уже имеет доступ к процессам хоста.
41
+ - Скрытие кода ревью от платформы: он и так там.
42
+ - Многопользовательская авторизация и роли.
43
+ - Защита от вредоносного `glab`/`gh`: CLI считается доверенным, как и npm-пакет.
44
+
45
+ ## Проверки модели
46
+
47
+ - Комментарии проходят `sanitizeBody`: управляющие и bidi-символы, HTML, быстрые команды GitLab,
48
+ `@all`/`@here`/`@everyone`, внешние картинки. Картинки разрешены только с хоста платформы
49
+ (или из GitLab `/uploads/`); вложения ограничены растровыми картинками и видео и проверяются по
50
+ расширению, MIME и сигнатуре файла.
51
+ - Публикация невозможна без `approval_id`; MCP-инструмент публикации его не создаёт.
52
+ - `revdeck doctor` показывает, какие права и адаптеры активны.
Binary file
Binary file
@@ -0,0 +1,53 @@
1
+ # Threat model
2
+
3
+ [English](threat-model.md) | [Русский](ru/threat-model.md)
4
+
5
+ ## Assets
6
+
7
+ 1. GitLab/GitHub tokens (in the `glab`/`gh` configuration on the host).
8
+ 2. The code and diff of the repository under review, including private repositories.
9
+ 3. Threads and comments, including personal data.
10
+ 4. The right to publish on the user's behalf.
11
+ 5. The UI launch token and the browser session token.
12
+ 6. The local SQLite database with the text of comments and drafts.
13
+
14
+ ## Trust boundaries
15
+
16
+ 1. **Agent (sandbox/container) ↔ daemon on the host.** The agent is untrusted: its output can be
17
+ subverted by MR content.
18
+ 2. **Browser ↔ daemon.** The browser is trusted, but a foreign page can reach loopback.
19
+ 3. **Repository ↔ daemon.** The MR author controls the repository's config and documents; they must
20
+ not be executed.
21
+ 4. **Daemon ↔ `glab`/`gh`.** The CLI is trusted, but its environment and working copy can be tampered
22
+ with.
23
+
24
+ ## Threats and mitigations
25
+
26
+ | Threat | Mitigation | Residual risk |
27
+ |---|---|---|
28
+ | The agent extracts a platform token | The token is never read: `glab`/`gh` take it from their own configuration. The agent gets only RPC; tokens are scrubbed from child process env and redacted in logs/errors | A process that can read the user's `$HOME` can read the CLI config. That is why agent isolation is required |
29
+ | Spoofing the project/host via `.git/config` | Explicit `host`/`project` in the config and arguments take precedence over the remote; `glab`/`gh` are invoked with a `cwd` outside the working copy | If the project is not pinned, the remote is used — the UI shows the project for verification |
30
+ | Prompt injection from comments/diff | Instructions in the text are marked as data; the agent does not publish on its own; publishing requires human approval; the sanitizer blocks hidden characters and quick actions | The model can still propose harmful text — a human sees it in the plan |
31
+ | Accidental publication of the wrong text | A plan with the exact bodies and a hash; `approve` verifies the hash; `commit` executes only the plan items; a repeat send is skipped | A human may approve without looking — the UI shows the full text |
32
+ | Cross-site requests to loopback (DNS rebinding, CSRF) | Only `127.0.0.1`/`localhost` in `Host`; an `Origin` check for POST; `HttpOnly`, `SameSite=Strict` cookies; a one-time token in the launch link; CSP | A local process of another user with access to the socket is out of scope (permissions 0600/0700) |
33
+ | Symlink/TOCTOU in the working directory | State is stored in a private directory, writes are atomic; repository files are read only inside the root, without following symlinks in its own paths | A race on repository files is possible but does not grant publish rights |
34
+ | A malicious `.revdeck.yml` executes code | `checks.argv` is an array without a shell; execution only with `--run --yes`; MCP cannot run checks | A user may approve running a malicious command from someone else's MR — read the plan |
35
+ | Leakage through logs/errors | Redaction of tokens, ANSI and control characters; length limits; logs in a private directory | Comment text may contain secrets — do not publish it |
36
+ | Supply chain | Minimal dependencies, lockfile, esbuild/Vite build, provenance on npm publish | The same as for any npm package |
37
+ | Approval theft | An approval is 16 random bytes, TTL 10 minutes, single-use, bound to review+hash, stored only in SQLite on the host | Socket access = RPC access; protected by file permissions |
38
+
39
+ ## Out of scope
40
+
41
+ - Protection from an administrator/user who already has access to host processes.
42
+ - Hiding the code under review from the platform: it is already there.
43
+ - Multi-user authorization and roles.
44
+ - Protection from a malicious `glab`/`gh`: the CLI is trusted, just like the npm package.
45
+
46
+ ## Model checks
47
+
48
+ - Comments go through `sanitizeBody`: control and bidi characters, HTML, GitLab quick actions,
49
+ `@all`/`@here`/`@everyone`, external images. Images are allowed only from the platform host
50
+ (or GitLab project `/uploads/`); attachments are restricted to raster images and videos and are
51
+ checked by extension, MIME and file signature.
52
+ - Publishing is impossible without an `approval_id`; the MCP publish tool does not create one.
53
+ - `revdeck doctor` shows which permissions and adapters are active.
@@ -0,0 +1,38 @@
1
+ # Монорепозиторий: разные документы для разных пакетов.
2
+ version: 1
3
+ provider:
4
+ type: gitlab
5
+ host: gitlab.example.com
6
+ project: group/web-chat-core
7
+ base: develop
8
+ rules:
9
+ sources:
10
+ - AGENTS.md
11
+ map:
12
+ - match: "**/package.json"
13
+ docs: [AGENTS.md, "docs/guides/Styleguide.md#Зависимости"]
14
+ - match: ["apps/*/src/**", "packages/*/src/**"]
15
+ docs:
16
+ - AGENTS.md
17
+ - docs/guides/Styleguide.md
18
+ - match: "apps/*/src/state/**"
19
+ docs: [docs/guides/StateManagement.md]
20
+ - match: "apps/*/docs/**"
21
+ docs:
22
+ - apps/*/docs/Architecture.md
23
+ - docs/guides/Styleguide.md#Документация
24
+ - match: ["packages/ui-kit/**", "packages/toolkit/**"]
25
+ docs: [packages/ui-kit/README.md, docs/guides/Styleguide.md]
26
+ - match: "docs/adr/**"
27
+ docs: [AGENTS.md#Размещение-технических-знаний, docs/adr/README.md]
28
+ - match: [".claude/**", ".codex/**", ".cursor/**", ".agents/**"]
29
+ docs: [AGENTS.md#Источники-и-приоритет-инструкций]
30
+ checks:
31
+ - id: lint
32
+ argv: [npm, run, lint]
33
+ when: ["**/src/**"]
34
+ - id: types
35
+ argv: [npm, run, typecheck]
36
+ when: ["**/*.ts", "**/*.tsx"]
37
+ publish:
38
+ maxComments: 40
@@ -0,0 +1,36 @@
1
+ # Простой проект: правила лежат рядом с кодом.
2
+ # Скопируйте в корень репозитория как .revdeck.yml.
3
+ version: 1
4
+ provider:
5
+ type: auto
6
+ # Явно закрепите проект, если remote может быть подменён:
7
+ # host: gitlab.example.com
8
+ # project: group/project
9
+ base: main
10
+ rules:
11
+ sources:
12
+ - AGENTS.md
13
+ - CONTRIBUTING.md
14
+ map:
15
+ - match: "src/**"
16
+ docs:
17
+ - AGENTS.md
18
+ - docs/Architecture.md
19
+ - match: "docs/**"
20
+ docs:
21
+ - AGENTS.md
22
+ ignore:
23
+ - "**/node_modules/**"
24
+ - "**/dist/**"
25
+ checks:
26
+ - id: lint
27
+ name: lint
28
+ argv: [npm, run, lint]
29
+ when: ["src/**", "*.json", "*.ts"]
30
+ - id: test
31
+ name: tests
32
+ argv: [npm, test]
33
+ when: ["src/**"]
34
+ publish:
35
+ requireApproval: true
36
+ maxComments: 50