revdeck 0.0.0-stage → 0.1.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.
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +98 -0
- package/LICENSE +21 -0
- package/README.md +223 -2
- package/README.ru.md +210 -0
- package/SECURITY.md +45 -0
- package/dist/cli.js +2669 -0
- package/dist/cli.js.map +7 -0
- package/dist/daemon.js +3715 -0
- package/dist/daemon.js.map +7 -0
- package/dist/mcp.js +885 -0
- package/dist/mcp.js.map +7 -0
- package/dist/ui/assets/index-ByfGHwCb.css +1 -0
- package/dist/ui/assets/index-mvPA8Avw.js +10 -0
- package/dist/ui/favicon.svg +4 -0
- package/dist/ui/index.html +16 -0
- package/docs/README.md +17 -0
- package/docs/adr/0001-daemon-first.md +63 -0
- package/docs/architecture.md +91 -0
- package/docs/configuration.md +121 -0
- package/docs/integrations.md +84 -0
- package/docs/protocol.md +96 -0
- package/docs/ru/README.md +17 -0
- package/docs/ru/adr/0001-daemon-first.md +61 -0
- package/docs/ru/architecture.md +91 -0
- package/docs/ru/configuration.md +119 -0
- package/docs/ru/integrations.md +82 -0
- package/docs/ru/protocol.md +96 -0
- package/docs/ru/threat-model.md +50 -0
- package/docs/screenshot.png +0 -0
- package/docs/screenshot.ru.png +0 -0
- package/docs/threat-model.md +51 -0
- package/examples/monorepo.yml +38 -0
- package/examples/simple.yml +36 -0
- package/examples/web-chat-core.yml +45 -0
- package/package.json +79 -4
- package/skills/revdeck/en/Criteria.md +265 -0
- package/skills/revdeck/en/Feedback.md +64 -0
- package/skills/revdeck/en/Review.md +157 -0
- package/skills/revdeck/en/Rules.md +75 -0
- package/skills/revdeck/en/SKILL.md +78 -0
- package/skills/revdeck/en/Sources.md +47 -0
- package/skills/revdeck/ru/Criteria.md +249 -0
- package/skills/revdeck/ru/Feedback.md +64 -0
- package/skills/revdeck/ru/Review.md +148 -0
- package/skills/revdeck/ru/Rules.md +69 -0
- package/skills/revdeck/ru/SKILL.md +73 -0
- package/skills/revdeck/ru/Sources.md +46 -0
|
@@ -0,0 +1,119 @@
|
|
|
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_ROOT` | корень проекта для MCP-сервера |
|
|
114
|
+
| `REVDECK_LANG` | язык CLI/MCP/демона: `ru` или `en` (по умолчанию системная локаль) |
|
|
115
|
+
|
|
116
|
+
## Валидация
|
|
117
|
+
|
|
118
|
+
`revdeck config --json` показывает эффективный конфиг, найденный путь и предупреждения.
|
|
119
|
+
`revdeck rules --explain src/app.ts` показывает, какие документы и секции выбраны для файла.
|
|
@@ -0,0 +1,82 @@
|
|
|
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/*`, `.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/*` + YAML-сниппет в выводе |
|
|
25
|
+
| Универсальный | — | `.agents/skills/revdeck/*` + MCP |
|
|
26
|
+
|
|
27
|
+
`revdeck init --adapters dsh,claude` включает нужные явно. Повторный `init` не перезаписывает
|
|
28
|
+
существующие командные файлы и не затирает чужие ключи `.mcp.json`.
|
|
29
|
+
|
|
30
|
+
## MCP
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"revdeck": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "revdeck", "mcp"],
|
|
38
|
+
"env": { "REVDECK_LANG": "en" }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`init` пишет `npx -y revdeck mcp` в MCP-конфиг. Публикация через MCP
|
|
45
|
+
требует `approval_id`: его выдаёт человек в UI или CLI с `--yes`, агент получить не может.
|
|
46
|
+
|
|
47
|
+
## DSH
|
|
48
|
+
|
|
49
|
+
MCP-клиент DSH подключается в профиле Cordis:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
- id: mcp-revdeck
|
|
53
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
54
|
+
config:
|
|
55
|
+
serverName: revdeck
|
|
56
|
+
transport: stdio
|
|
57
|
+
command: npx
|
|
58
|
+
args: ['-y', 'revdeck', 'mcp']
|
|
59
|
+
env:
|
|
60
|
+
REVDECK_LANG: en
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Инструменты появятся как `mcp__revdeck__*`. MCP prompt templates DSH не поддерживает, поэтому
|
|
64
|
+
`/revdeck` требует host-команды (`ctx.commands.register`) или client action — это отдельный
|
|
65
|
+
плагин-обёртка. До него используйте skill + MCP-инструменты: `/`-слэш не обязателен для работы.
|
|
66
|
+
|
|
67
|
+
## Контейнер
|
|
68
|
+
|
|
69
|
+
Демон работает на хосте, агент — в контейнере. Варианты по убыванию удобства:
|
|
70
|
+
|
|
71
|
+
1. Смонтировать `$XDG_RUNTIME_DIR/revdeck` в контейнер тем же путём — сокет заработает как есть.
|
|
72
|
+
2. `REVDECK_URL=http://host.docker.internal:<port>` и `REVDECK_TOKEN=<token>` — HTTP-транспорт.
|
|
73
|
+
Порт и токен печатает `revdeck daemon start` (или `daemon.json` в state-каталоге).
|
|
74
|
+
3. `revdeck export <N>` на хосте и `revdeck import <N>` в контейнере — файловый мост.
|
|
75
|
+
|
|
76
|
+
## Проверка
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
revdeck doctor
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Показывает найденные бинарники, авторизацию `glab`/`gh`, состояние демона и установленный адаптер.
|
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
| `notes.delete` | `review`, `noteId` | `{ok}` |
|
|
55
|
+
| `publish.preview` | `review`, `kind`, `ids[]` | план + предупреждения |
|
|
56
|
+
| `publish.approve` | `review`, `kind`, `ids[]`, `hash` | `approvalId`, `expiresAt` |
|
|
57
|
+
| `publish.commit` | `review`, `approvalId` | результаты по пунктам |
|
|
58
|
+
| `rules.get` | `review` | markdown правил, проверки, предупреждения |
|
|
59
|
+
| `checks.run` | `review`, `allowRun`, `ids[]` | результаты проверок |
|
|
60
|
+
| `audit.tail` | `limit` | последние действия |
|
|
61
|
+
|
|
62
|
+
## Approval
|
|
63
|
+
|
|
64
|
+
1. `publish.preview` строит `items` — точные тела, пути, строки, флаги резолва — и
|
|
65
|
+
`hash = sha256(canonicalJson({reviewId, kind, items}))`.
|
|
66
|
+
2. Человек видит план. `publish.approve` пересчитывает план по тем же `ids`; если хеш не совпал,
|
|
67
|
+
возвращает ошибку. Иначе создаёт одноразовый `approval_id` с TTL 10 минут.
|
|
68
|
+
3. `publish.commit` проверяет, что approval не использован и не истёк, помечает его использованным и
|
|
69
|
+
выполняет только `items`. Уже отправленные пункты пропускаются.
|
|
70
|
+
|
|
71
|
+
Агент не может вызвать `publish.approve`: MCP-инструмент его не предоставляет. Человек подтверждает
|
|
72
|
+
в UI или CLI с `--yes`.
|
|
73
|
+
|
|
74
|
+
## HTTP API (UI)
|
|
75
|
+
|
|
76
|
+
| Метод | Путь | Назначение |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| GET | `/` | SPA; `?t=<token>` ставит cookie и редиректит |
|
|
79
|
+
| GET | `/assets/*` | статика сборки |
|
|
80
|
+
| GET | `/api/health` | живость |
|
|
81
|
+
| GET | `/api/reviews` | список ревью |
|
|
82
|
+
| GET | `/api/state?review=` | снимок для UI |
|
|
83
|
+
| GET | `/api/diff?review=&file=` | hunks файла |
|
|
84
|
+
| GET | `/api/rules?review=` | правила |
|
|
85
|
+
| GET | `/api/events` | SSE: `review` |
|
|
86
|
+
| POST | `/api/reviews/open`, `/api/reviews/refresh` | открыть/обновить |
|
|
87
|
+
| POST | `/api/drafts`, `/api/findings`, `/api/summary`, `/api/decisions` | черновики |
|
|
88
|
+
| POST | `/api/notes` | `action: add/update/delete` |
|
|
89
|
+
| POST | `/api/publish/preview`, `/approve`, `/commit` | публикация |
|
|
90
|
+
| POST | `/api/checks` | проверки |
|
|
91
|
+
| POST | `/api/rpc` | RPC-прокси для HTTP-транспорта |
|
|
92
|
+
|
|
93
|
+
## Версионирование
|
|
94
|
+
|
|
95
|
+
`PROTOCOL_VERSION` в `src/version.ts` сверяется при рукопожатии; клиент и демон одной версии пакета.
|
|
96
|
+
Несовпадение методов возвращает `METHOD` с подсказкой обновить revdeck.
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
- Публикация невозможна без `approval_id`; MCP-инструмент публикации его не создаёт.
|
|
50
|
+
- `revdeck doctor` показывает, какие права и адаптеры активны.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,51 @@
|
|
|
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.
|
|
50
|
+
- Publishing is impossible without an `approval_id`; the MCP publish tool does not create one.
|
|
51
|
+
- `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
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Пример переноса старой таблицы Criteria.md для монорепозитория web-chat-core.
|
|
2
|
+
# Правила по путям вынесены из промпта в конфиг: агент получает только нужные секции.
|
|
3
|
+
version: 1
|
|
4
|
+
provider:
|
|
5
|
+
type: gitlab
|
|
6
|
+
base: develop
|
|
7
|
+
rules:
|
|
8
|
+
sources:
|
|
9
|
+
- AGENTS.md
|
|
10
|
+
map:
|
|
11
|
+
- match: "**/package.json"
|
|
12
|
+
docs: [AGENTS.md, "docs/guides/Styleguide.md#Базовый стиль"]
|
|
13
|
+
- match: "messenger/src/**"
|
|
14
|
+
docs: [messenger/AGENTS.md, messenger/docs/Architecture.md, docs/guides/Styleguide.md]
|
|
15
|
+
- match: "messenger/src/version2/ui/**"
|
|
16
|
+
docs: [messenger/docs/Architecture.md, messenger/src/version2/ui/README.md]
|
|
17
|
+
- match: "messenger/src/version2/api/**"
|
|
18
|
+
docs: [messenger/docs/Architecture.md, messenger/src/version2/api/rest/README.md]
|
|
19
|
+
- match: ["docs/guides/StateManagement.md", "**/store/**", "**/useCase/**", "**/selectors/**"]
|
|
20
|
+
docs: [docs/guides/StateManagement.md]
|
|
21
|
+
- match: "meet/src/**"
|
|
22
|
+
docs: [meet/AGENTS.md, meet/docs/Architecture.md, docs/guides/Styleguide.md, docs/guides/StateManagement.md]
|
|
23
|
+
- match: "tube/src/**"
|
|
24
|
+
docs: [tube/AGENTS.md, tube/docs/Architecture.md, tube/docs/guides/Styleguide.md, tube/docs/guides/StateManagement.md]
|
|
25
|
+
- match: ["ui-kit/**", "toolkit/**", "messenger-kit/**"]
|
|
26
|
+
docs: [AGENTS.md, docs/guides/Styleguide.md]
|
|
27
|
+
- match: ["**/generated/**", "**/openapi-generator/**", "**/apigen.config.ts"]
|
|
28
|
+
docs: [docs/API-codegen.md]
|
|
29
|
+
- match: ["docs/**", "*.md"]
|
|
30
|
+
docs: [AGENTS.md, "AGENTS.md#Размещение технических знаний"]
|
|
31
|
+
- match: [".claude/**", ".codex/**", ".cursor/**", ".agents/**", ".devcontainer/**", "devtools/**"]
|
|
32
|
+
docs: [AGENTS.md, .devcontainer/README.md]
|
|
33
|
+
ignore:
|
|
34
|
+
- "**/node_modules/**"
|
|
35
|
+
- "**/dist/**"
|
|
36
|
+
- "**/coverage/**"
|
|
37
|
+
checks:
|
|
38
|
+
- id: lint
|
|
39
|
+
argv: [npm, run, lint]
|
|
40
|
+
when: ["**/src/**", "package-lock.json", "**/.eslintrc*"]
|
|
41
|
+
- id: types
|
|
42
|
+
argv: [npm, run, typecheck]
|
|
43
|
+
when: ["**/*.ts", "**/*.tsx"]
|
|
44
|
+
publish:
|
|
45
|
+
maxComments: 40
|
package/package.json
CHANGED
|
@@ -1,6 +1,81 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "revdeck",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agent-first code review for GitLab and GitHub: local daemon, CLI, MCP server and a triage UI.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.13"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"revdeck": "dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"skills",
|
|
16
|
+
"docs",
|
|
17
|
+
"examples",
|
|
18
|
+
"README.md",
|
|
19
|
+
"README.ru.md",
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"CONTRIBUTING.md",
|
|
22
|
+
"SECURITY.md"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "npm run build:server && npm run build:ui",
|
|
26
|
+
"build:server": "node scripts/build-server.mjs",
|
|
27
|
+
"build:ui": "vite build --config ui/vite.config.ts",
|
|
28
|
+
"prepack": "npm run build",
|
|
29
|
+
"prepublishOnly": "npm run typecheck && npm test",
|
|
30
|
+
"typecheck": "tsc --noEmit && svelte-check --tsconfig ui/tsconfig.json --threshold error",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"dev:daemon": "tsx src/daemon/main.ts",
|
|
34
|
+
"dev:ui": "vite --config ui/vite.config.ts",
|
|
35
|
+
"dev:mcp": "tsx src/mcp/main.ts"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
39
|
+
"yaml": "^2.9.1",
|
|
40
|
+
"zod": "^4.6.7"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@sveltejs/vite-plugin-svelte": "^7.3.1",
|
|
44
|
+
"@types/node": "^26.6.6",
|
|
45
|
+
"esbuild": "^0.28.2",
|
|
46
|
+
"playwright-core": "^1.65.0",
|
|
47
|
+
"shiki": "^4.6.0",
|
|
48
|
+
"svelte": "^5.57.3",
|
|
49
|
+
"svelte-check": "^4.5.0",
|
|
50
|
+
"tsx": "^4.23.15",
|
|
51
|
+
"typescript": "^6.0.3",
|
|
52
|
+
"vite": "^8.3.6",
|
|
53
|
+
"vitest": "^5.1.0"
|
|
54
|
+
},
|
|
55
|
+
"keywords": [
|
|
56
|
+
"code-review",
|
|
57
|
+
"gitlab",
|
|
58
|
+
"github",
|
|
59
|
+
"pull-request",
|
|
60
|
+
"merge-request",
|
|
61
|
+
"mcp",
|
|
62
|
+
"model-context-protocol",
|
|
63
|
+
"agent",
|
|
64
|
+
"cli",
|
|
65
|
+
"svelte",
|
|
66
|
+
"developer-tools"
|
|
67
|
+
],
|
|
68
|
+
"author": "Alexander Katrov",
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/katrov/revdeck.git"
|
|
72
|
+
},
|
|
73
|
+
"homepage": "https://github.com/katrov/revdeck#readme",
|
|
74
|
+
"bugs": {
|
|
75
|
+
"url": "https://github.com/katrov/revdeck/issues"
|
|
76
|
+
},
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public",
|
|
79
|
+
"provenance": true
|
|
80
|
+
}
|
|
81
|
+
}
|