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.
- package/CHANGELOG.md +58 -0
- package/CONTRIBUTING.md +108 -0
- package/LICENSE +21 -0
- package/README.md +262 -2
- package/README.ru.md +249 -0
- package/SECURITY.md +45 -0
- package/dist/cli.js +3226 -0
- package/dist/cli.js.map +7 -0
- package/dist/daemon.js +4002 -0
- package/dist/daemon.js.map +7 -0
- package/dist/mcp.js +901 -0
- package/dist/mcp.js.map +7 -0
- package/dist/ui/assets/index-BAejn_X6.js +10 -0
- package/dist/ui/assets/index-xv-zA2Sf.css +1 -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 +64 -0
- package/docs/architecture.md +92 -0
- package/docs/configuration.md +123 -0
- package/docs/integrations.md +123 -0
- package/docs/protocol.md +102 -0
- package/docs/ru/README.md +17 -0
- package/docs/ru/adr/0001-daemon-first.md +62 -0
- package/docs/ru/architecture.md +92 -0
- package/docs/ru/configuration.md +121 -0
- package/docs/ru/integrations.md +122 -0
- package/docs/ru/protocol.md +102 -0
- package/docs/ru/threat-model.md +52 -0
- package/docs/screenshot.png +0 -0
- package/docs/screenshot.ru.png +0 -0
- package/docs/threat-model.md +53 -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/en/agents/openai.yaml +4 -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
- package/skills/revdeck/ru/agents/openai.yaml +4 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ADR-0001: Daemon-first architecture
|
|
2
|
+
|
|
3
|
+
[English](0001-daemon-first.md) | [Русский](../ru/adr/0001-daemon-first.md)
|
|
4
|
+
|
|
5
|
+
- **Status:** Accepted
|
|
6
|
+
- **Date:** 2026-11-11
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
The first version of revdeck ran as a process that a human started manually on the host
|
|
11
|
+
(`node devtools/mr-ui/server.mjs <N>`), and the agent communicated with it through `.revdeck/*.json`
|
|
12
|
+
files. This gave the right security boundary (the agent does not publish, and the token stays on the
|
|
13
|
+
host), but:
|
|
14
|
+
|
|
15
|
+
- the server died with the terminal, and the link had to be copied manually;
|
|
16
|
+
- file exchange caused races (file version, 409) and required syncing fields between the agent and
|
|
17
|
+
the UI;
|
|
18
|
+
- it was impossible to have several reviews at once;
|
|
19
|
+
- each project required its own copy of the script and ran in one process.
|
|
20
|
+
|
|
21
|
+
What was needed: launch from a `/` command in any harness, a full-featured interface, several reviews,
|
|
22
|
+
GitLab and GitHub, configurable project rules, and installation into any agent project.
|
|
23
|
+
|
|
24
|
+
## Decision
|
|
25
|
+
|
|
26
|
+
We build the product around a [local daemon](../architecture.md):
|
|
27
|
+
|
|
28
|
+
- the `revdeck daemon` — a long-lived process owned by the user that holds credentials, SQLite,
|
|
29
|
+
providers, RPC, HTTP/SSE, and the UI;
|
|
30
|
+
- the CLI and MCP are thin clients over RPC; the MCP server contains no review logic;
|
|
31
|
+
- the agent writes only drafts; publication requires an approval tied to the plan hash;
|
|
32
|
+
- the UI is a Svelte SPA served by the daemon and outlives the agent session;
|
|
33
|
+
- state lives in SQLite under `~/.local/state/revdeck`, not in JSON in the repository;
|
|
34
|
+
- the file format remains as `export`/`import` for compatibility and for sandboxes without a socket.
|
|
35
|
+
|
|
36
|
+
## Alternatives considered
|
|
37
|
+
|
|
38
|
+
1. **An MCP process per session, without a daemon.** Simpler installation, but the UI does not survive
|
|
39
|
+
the session, several agents compete for state, the token ends up in the process that the agent
|
|
40
|
+
launches, and the container scenario still requires a separate process on the host. Rejected.
|
|
41
|
+
2. **File exchange only.** Maximum compatibility, but races and manual startup remain. Kept as
|
|
42
|
+
`export/import`, not as the primary path.
|
|
43
|
+
3. **A cloud service.** Removes installation, but requires sending code and tokens outside. Contradicts
|
|
44
|
+
the "local, one person" requirement.
|
|
45
|
+
|
|
46
|
+
## Consequences
|
|
47
|
+
|
|
48
|
+
- A daemon lifecycle appears: socket, pid, logs, autostart (`daemon install`), handling of a stale
|
|
49
|
+
socket. The client can start the daemon itself.
|
|
50
|
+
- Socket permissions are part of the security model: directory 0700, socket 0600.
|
|
51
|
+
- In a container, either a mounted socket or `REVDECK_URL`/`REVDECK_TOKEN` is needed; otherwise, use
|
|
52
|
+
`export`/`import` and run on the host. `revdeck devcontainer --write` generates the socket/state
|
|
53
|
+
mounts and marks the container with `REVDECK_DEV_CONTAINER=1`, so it never starts its own daemon.
|
|
54
|
+
- One daemon serves several repositories: `root` is passed on `open`, and the config is read from that
|
|
55
|
+
root.
|
|
56
|
+
- Client and daemon versions must match; [`PROTOCOL_VERSION`](../protocol.md) was added.
|
|
57
|
+
- This creates room for background sync and SSE, which improves UX compared to manually refreshing
|
|
58
|
+
the page.
|
|
59
|
+
|
|
60
|
+
## Follow-up decisions
|
|
61
|
+
|
|
62
|
+
- Native drafts (GitLab draft notes, GitHub pending review) are the next step; the provider interface
|
|
63
|
+
already contains `capabilities.nativeDrafts`.
|
|
64
|
+
- A multi-user mode is not planned; if needed, a separate ADR.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
[English](architecture.md) | [Русский](ru/architecture.md)
|
|
4
|
+
|
|
5
|
+
## Goals
|
|
6
|
+
|
|
7
|
+
1. The agent receives review context and project rules deterministically, without manual files and
|
|
8
|
+
without access to platform tokens.
|
|
9
|
+
2. Publishing to GitLab/GitHub happens only after human approval and is idempotent.
|
|
10
|
+
3. One tool works in any harness: MCP for agent clients, the CLI as the universal layer, and a web UI
|
|
11
|
+
for humans.
|
|
12
|
+
4. Project rules are [configured](configuration.md), not hardcoded in the prompt.
|
|
13
|
+
|
|
14
|
+
## Non-goals
|
|
15
|
+
|
|
16
|
+
- Replacing GitLab/GitHub's native diff viewer. The UI is optimized for triage and approval.
|
|
17
|
+
- Built-in static analysis. Checks are delegated to the project's linter, types, and tests.
|
|
18
|
+
- A multi-user server. The model is one user, several reviews, and a
|
|
19
|
+
[daemon on the host](adr/0001-daemon-first.md).
|
|
20
|
+
|
|
21
|
+
## Components
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
agent (MCP/CLI) ──▶ revdeck daemon ──glab/gh──▶ GitLab/GitHub
|
|
25
|
+
│
|
|
26
|
+
SQLite + UI ◀── human
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- `src/core` — pure functions and models: unified diff parser, rules, finding fingerprints,
|
|
30
|
+
[publication sanitizer](threat-model.md), secret redaction, safe process execution.
|
|
31
|
+
- `src/providers` — the only place that knows about platforms. `Provider` normalizes MR/PR, diff,
|
|
32
|
+
threads, replies, resolution, and comment creation.
|
|
33
|
+
- `src/daemon/service.ts` — orchestrator: review refresh, drafts, decisions, publication plan,
|
|
34
|
+
approval, idempotent writes, background sync.
|
|
35
|
+
- `src/daemon/store.ts` — SQLite (`node:sqlite`): reviews, diff/thread snapshot, drafts, findings,
|
|
36
|
+
decisions, comments, approvals, audit.
|
|
37
|
+
- `src/daemon/rpc.ts` — [JSON-RPC](protocol.md) over a Unix socket (0600) for the CLI and MCP.
|
|
38
|
+
- `src/daemon/http.ts` — UI and `/api/*`, SSE events, cookie session with a one-time token.
|
|
39
|
+
- `src/cli` — commands, `init`, `doctor`, export/import of the old file format.
|
|
40
|
+
- `src/mcp` — MCP server: tools and prompts, a thin wrapper over RPC.
|
|
41
|
+
- `ui` — Svelte 5 SPA, built into `dist/ui`.
|
|
42
|
+
|
|
43
|
+
## Data flow
|
|
44
|
+
|
|
45
|
+
1. `revdeck open 42` → RPC `reviews.open` with the working copy `root`.
|
|
46
|
+
2. The daemon determines the platform/host/project (arguments → config → git remote), creates a
|
|
47
|
+
provider, reads the MR, diff, and threads, and saves a snapshot and synthetic drafts.
|
|
48
|
+
3. The agent requests `revdeck_context`: the daemon assembles effective rules (documents and sections
|
|
49
|
+
for changed paths), snippets, threads, and findings.
|
|
50
|
+
4. The agent writes drafts (`drafts.put`, `findings.put`, `summary.put`, `notes.update`).
|
|
51
|
+
5. A human in the UI calls `publish.preview` → sees the exact plan and hash → `publish.approve` →
|
|
52
|
+
`publish.commit`. The daemon executes only the items included in the approved plan.
|
|
53
|
+
6. Background sync every 2 minutes refreshes open reviews and broadcasts SSE events to the UI.
|
|
54
|
+
|
|
55
|
+
## Data model
|
|
56
|
+
|
|
57
|
+
- `ReviewRef` — platform, host, project, number. `reviewId` — `platform:host/project!number`.
|
|
58
|
+
- `DiffFile`/`Hunk`/`DiffLine` — line numbers in the old and new versions; a finding's `line` always
|
|
59
|
+
refers to the new file.
|
|
60
|
+
- `Thread`/`Note` — shared discussion model.
|
|
61
|
+
- `Finding` — a review finding with `id`, `fingerprint`, severity, file/line, text, and status
|
|
62
|
+
(`draft`, `accepted`, `rejected`, `published`, `fixed`, `stale`).
|
|
63
|
+
- `Approval` — a one-time id tied to `hash = sha256(canonicalJson({reviewId, kind, items}))`,
|
|
64
|
+
with a 10-minute TTL.
|
|
65
|
+
|
|
66
|
+
## Idempotency and concurrency
|
|
67
|
+
|
|
68
|
+
- Draft and finding writes go through a single process; SQLite in WAL mode.
|
|
69
|
+
- Publication marks the approval as used before execution. Items that have already been posted
|
|
70
|
+
(matching `posted`, `published`) are skipped. A failure of one item does not cancel the others and
|
|
71
|
+
is recorded in the draft/finding.
|
|
72
|
+
- The plan hash protects against agent edits between the moment the plan is shown to the human and
|
|
73
|
+
publication: text changed → hash mismatch → approval rejected.
|
|
74
|
+
- `findings.put` — replaces the set while preserving `id` and decision; findings that disappear are
|
|
75
|
+
marked `stale` rather than deleted.
|
|
76
|
+
|
|
77
|
+
## Extension
|
|
78
|
+
|
|
79
|
+
- **New platform**: implement `Provider` and add a branch in `createProvider`; nothing else changes.
|
|
80
|
+
- **New harness**: `revdeck init --adapters <name>` writes the MCP config or command file and links the
|
|
81
|
+
canonical `.agents/skills/revdeck` skill.
|
|
82
|
+
Harnesses without MCP use the CLI.
|
|
83
|
+
- **Rules**: `rules.map`/`rules.sources` in `.revdeck.yml`; rule packs can be distributed as a regular
|
|
84
|
+
configuration file.
|
|
85
|
+
- **Checks**: `checks` with argv, no shell; run only on explicit confirmation.
|
|
86
|
+
|
|
87
|
+
## Performance
|
|
88
|
+
|
|
89
|
+
- The diff is parsed on the server; the UI receives files as a list and hunks one file at a time.
|
|
90
|
+
- Review state does not contain the full diff; SSE reports changes and the UI refetches the state.
|
|
91
|
+
- Snippets for threads and findings are computed on the server and size-limited.
|
|
92
|
+
- Rule context is capped at 320 KB; MCP context is capped by the `max_bytes` parameter.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
[English](configuration.md) | [Русский](ru/configuration.md)
|
|
4
|
+
|
|
5
|
+
Configuration lives in the `.revdeck.yml` (or `.revdeck.yaml`) file at the repository root. The schema
|
|
6
|
+
is validated with zod; an unknown field produces an error with the field path.
|
|
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 — language of findings and replies
|
|
17
|
+
rules:
|
|
18
|
+
sources: [AGENTS.md]
|
|
19
|
+
map:
|
|
20
|
+
- match: ["src/**", "lib/**"]
|
|
21
|
+
docs: ["docs/Architecture.md", "docs/Styleguide.md#Базовый стиль"]
|
|
22
|
+
note: "application code"
|
|
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
|
+
| Field | Default | Meaning |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `type` | `auto` | `auto` — inferred from the git remote; otherwise an explicit platform |
|
|
41
|
+
| `host` | from the remote | host without a scheme |
|
|
42
|
+
| `project` | from the remote | GitLab `group/project`, GitHub `owner/repo` |
|
|
43
|
+
| `base` | `develop`/`main` | base for the local diff |
|
|
44
|
+
|
|
45
|
+
Explicit CLI arguments (`--platform`, `--project`, `--host`) take precedence over the config, and the
|
|
46
|
+
config takes precedence over the git remote. This way, tampering with `.git/config` cannot redirect the
|
|
47
|
+
review to a different project.
|
|
48
|
+
|
|
49
|
+
## output
|
|
50
|
+
|
|
51
|
+
- `language: auto` — findings and replies are written in the language of the MR description and
|
|
52
|
+
discussions; if there is no text, English is used. `ru`/`en` pin the language explicitly.
|
|
53
|
+
- The language of the rules and documents does not depend on this: the agent reads them as they are and
|
|
54
|
+
quotes them verbatim.
|
|
55
|
+
- This is the language of the **review output**, not of the CLI/UI interface. The interface is switched
|
|
56
|
+
via `--lang ru|en`, `REVDECK_LANG`, or the toggle in the UI.
|
|
57
|
+
- `revdeck init --lang en` selects the English skill and writes `REVDECK_LANG` to the MCP config.
|
|
58
|
+
|
|
59
|
+
## rules
|
|
60
|
+
|
|
61
|
+
- `sources` — the default documents when none of the `map` rows match. Empty — auto-discovery
|
|
62
|
+
(`AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `docs/ARCHITECTURE.md`, `docs/Architecture.md`,
|
|
63
|
+
`docs/STYLEGUIDE.md`, `docs/Styleguide.md`, `docs/CONTRIBUTING.md`).
|
|
64
|
+
- `map` — a list of rules: `match` (a glob or an array of globs) and `docs` (an array of selectors).
|
|
65
|
+
Multiple matching rows are merged.
|
|
66
|
+
- `ignore` — glob exclusions: a matching file is left out of rule analysis.
|
|
67
|
+
|
|
68
|
+
### Document selectors
|
|
69
|
+
|
|
70
|
+
| Selector | What is included |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `docs/Architecture.md` | the entire document |
|
|
73
|
+
| `docs/Styleguide.md#Базовый стиль` | the section for the heading (that level and below, up to the next heading of the same level) |
|
|
74
|
+
| `docs/File.md#L10-L25` | a line range |
|
|
75
|
+
| `docs/File.md#10-25` | the same range |
|
|
76
|
+
|
|
77
|
+
A heading is normalized: case, whitespace, and punctuation do not matter. `#Слои и зависимости` and
|
|
78
|
+
`#layers` are different sections, while `#Слои-и-зависимости` and `#Слои и зависимости` are the same
|
|
79
|
+
section. If a section is not found, revdeck emits a warning and does not fall back to the whole document.
|
|
80
|
+
|
|
81
|
+
### Glob
|
|
82
|
+
|
|
83
|
+
`*` matches within one segment, `**` matches any depth, `?` matches one character, `{a,b}` is an
|
|
84
|
+
alternative. Examples: `src/**/*.ts`, `docs/{guides,adr}/**`, `**/*.test.ts`.
|
|
85
|
+
|
|
86
|
+
## checks
|
|
87
|
+
|
|
88
|
+
Checks are argv arrays, not shell strings. This matters: the config lives in the repository, and a shell
|
|
89
|
+
string would allow arbitrary code to be executed during a review.
|
|
90
|
+
|
|
91
|
+
| Field | Meaning |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `id` | stable identifier |
|
|
94
|
+
| `name` | display name |
|
|
95
|
+
| `argv` | command and arguments; the executable comes first |
|
|
96
|
+
| `when` | glob paths: run only if the diff touched them; empty — always |
|
|
97
|
+
| `consent` | requires explicit confirmation (default `true`) |
|
|
98
|
+
| `timeoutMs` | timeout (default 300,000) |
|
|
99
|
+
|
|
100
|
+
Running: `revdeck checks <N> --run --yes`. The MCP tool only shows the plan.
|
|
101
|
+
|
|
102
|
+
## publish
|
|
103
|
+
|
|
104
|
+
- `requireApproval` — kept for backward compatibility; approval is always required when publishing is
|
|
105
|
+
done by a non-interactive process.
|
|
106
|
+
- `maxComments` — maximum number of comments per plan (1–200).
|
|
107
|
+
|
|
108
|
+
## Environment variables
|
|
109
|
+
|
|
110
|
+
| Variable | Meaning |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `REVDECK_STATE_DIR` | state directory (default `~/.local/state/revdeck`) |
|
|
113
|
+
| `REVDECK_RUNTIME_DIR` | socket directory (default `$XDG_RUNTIME_DIR/revdeck`) |
|
|
114
|
+
| `REVDECK_URL` + `REVDECK_TOKEN` | connect to the daemon over HTTP (container/port forwarding) |
|
|
115
|
+
| `REVDECK_DEV_CONTAINER=1` | do not start a local daemon; fail with a host-daemon hint instead |
|
|
116
|
+
| `REVDECK_UI_HOST` | host used by `revdeck ui` from a container (for example `host.docker.internal`) |
|
|
117
|
+
| `REVDECK_ROOT` | project root for the MCP server |
|
|
118
|
+
| `REVDECK_LANG` | CLI/MCP/daemon language: `ru` or `en` (default: system locale) |
|
|
119
|
+
|
|
120
|
+
## Validation
|
|
121
|
+
|
|
122
|
+
`revdeck config --json` shows the effective config, the path where it was found, and warnings.
|
|
123
|
+
`revdeck rules --explain src/app.ts` shows which documents and sections were selected for the file.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Harness integrations
|
|
2
|
+
|
|
3
|
+
[English](integrations.md) | [Русский](ru/integrations.md)
|
|
4
|
+
|
|
5
|
+
revdeck has two portable layers and one adapter layer:
|
|
6
|
+
|
|
7
|
+
- **MCP** (stdio) — the `revdeck_*` tools work in any MCP-capable client.
|
|
8
|
+
- **CLI** — the same capabilities without MCP: `revdeck open/context/findings/reply/publish`.
|
|
9
|
+
- **`/` command** — each harness has its own; `revdeck init` creates the right file.
|
|
10
|
+
|
|
11
|
+
A full integration = MCP + skill (procedures) + command (entry point). Without MCP, the CLI and
|
|
12
|
+
skill remain: the agent calls commands through the shell.
|
|
13
|
+
|
|
14
|
+
## Matrix
|
|
15
|
+
|
|
16
|
+
| Harness | `/` command | What `init` writes |
|
|
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 in `~/.codex/config.toml` (manually) |
|
|
21
|
+
| Gemini CLI | `/revdeck` | `.gemini/commands/revdeck.toml`; MCP in the Gemini settings (manually) |
|
|
22
|
+
| OpenCode | `/revdeck` | `.opencode/command/revdeck.md` |
|
|
23
|
+
| GitHub Copilot | prompt file | `.github/prompts/revdeck.prompt.md` |
|
|
24
|
+
| DSH | plugin (see below) | `.agents/skills/revdeck/*` (including `agents/openai.yaml`) + YAML snippet in the output |
|
|
25
|
+
| Universal | — | `.agents/skills/revdeck/*` (including `agents/openai.yaml`) + MCP |
|
|
26
|
+
|
|
27
|
+
Comment attachments are uploaded by the host daemon. GitLab uses project uploads and returns an
|
|
28
|
+
absolute URL; GitHub has no public attachment API, so the editor asks for a URL instead.
|
|
29
|
+
|
|
30
|
+
`revdeck init --adapters dsh,claude` explicitly enables the adapters you need. A repeated `init`
|
|
31
|
+
does not overwrite existing command files and does not clobber other keys in `.mcp.json`.
|
|
32
|
+
|
|
33
|
+
The skill has one canonical copy: `.agents/skills/revdeck` (including `agents/openai.yaml` for OpenAI
|
|
34
|
+
harnesses). Harnesses that keep skills under their own directory get a link to it — `.claude/skills/revdeck`
|
|
35
|
+
is a symlink to `../../.agents/skills/revdeck`, with a copy fallback on Windows and on filesystems without
|
|
36
|
+
symlink support. Old copies created by an earlier `init` are replaced when they still carry
|
|
37
|
+
`name: revdeck`.
|
|
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` writes `npx -y revdeck mcp` into the MCP config. Publishing over MCP
|
|
54
|
+
requires an `approval_id`: a human issues it in the UI or in the CLI with `--yes`, and the agent
|
|
55
|
+
cannot obtain it.
|
|
56
|
+
|
|
57
|
+
## DSH
|
|
58
|
+
|
|
59
|
+
The DSH MCP client is configured in the Cordis profile:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
- id: mcp-revdeck
|
|
63
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
64
|
+
config:
|
|
65
|
+
serverName: revdeck
|
|
66
|
+
transport: stdio
|
|
67
|
+
command: npx
|
|
68
|
+
args: ['-y', 'revdeck', 'mcp']
|
|
69
|
+
env:
|
|
70
|
+
REVDECK_LANG: en
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The tools will appear as `mcp__revdeck__*`. DSH does not support MCP prompt templates, so `/revdeck`
|
|
74
|
+
requires a host command (`ctx.commands.register`) or a client action — that is a separate wrapper
|
|
75
|
+
plugin. Until then, use the skill + MCP tools: the `/` slash is not required for things to work.
|
|
76
|
+
|
|
77
|
+
## Container and devcontainer
|
|
78
|
+
|
|
79
|
+
The daemon, `glab` and `gh` stay on the host; the container only needs the revdeck CLI or MCP server.
|
|
80
|
+
Generate the devcontainer settings from the repository root:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
revdeck devcontainer # print the mounts and environment
|
|
84
|
+
revdeck devcontainer --write # merge them into .devcontainer/devcontainer.json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`--write` mounts the host runtime and state directories at fixed paths and sets:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"REVDECK_RUNTIME_DIR": "/run/revdeck",
|
|
92
|
+
"REVDECK_STATE_DIR": "/var/lib/revdeck",
|
|
93
|
+
"REVDECK_DEV_CONTAINER": "1",
|
|
94
|
+
"REVDECK_UI_HOST": "host.docker.internal"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The runtime directory is mounted read-write (the socket lives there), the state directory is
|
|
99
|
+
read-only (the client reads `daemon.json` with the token). Existing mounts, environment variables and
|
|
100
|
+
`runArgs` are preserved; only the two revdeck mounts and the `REVDECK_*` keys are managed.
|
|
101
|
+
|
|
102
|
+
After rebuilding the container, start the daemon on the host (`revdeck daemon start`). Inside the
|
|
103
|
+
container `revdeck` never starts its own daemon: if the socket is unreachable it fails with a
|
|
104
|
+
`DEVCONTAINER` error and a hint. `revdeck doctor` does not look for `glab`/`gh` in the container.
|
|
105
|
+
|
|
106
|
+
The container user must be able to connect to the host socket (normally the same UID; the socket is
|
|
107
|
+
0600). To open the UI from the container, start the host daemon with `revdeck daemon start --host
|
|
108
|
+
0.0.0.0` and use `host.docker.internal`; otherwise open the UI on the host.
|
|
109
|
+
|
|
110
|
+
Fallbacks:
|
|
111
|
+
|
|
112
|
+
1. `REVDECK_URL=http://host.docker.internal:<port>` and `REVDECK_TOKEN=<token>` — the HTTP transport,
|
|
113
|
+
with the daemon started on the host with `--host 0.0.0.0`.
|
|
114
|
+
2. `revdeck export <N>` on the host and `revdeck import <N>` in the container — a file bridge.
|
|
115
|
+
|
|
116
|
+
## Verification
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
revdeck doctor
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Shows the binaries found, `glab`/`gh` authorization, daemon status and the installed adapter. In a
|
|
123
|
+
devcontainer it checks the host daemon instead of looking for `glab`/`gh` inside.
|
package/docs/protocol.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Protocol
|
|
2
|
+
|
|
3
|
+
[English](protocol.md) | [Русский](ru/protocol.md)
|
|
4
|
+
|
|
5
|
+
## Transports
|
|
6
|
+
|
|
7
|
+
1. **Unix socket** (primary): `$XDG_RUNTIME_DIR/revdeck/daemon.sock`, directory 0700, socket 0600.
|
|
8
|
+
JSON lines, one request/response per line.
|
|
9
|
+
2. **HTTP**: `POST /api/rpc` on loopback with a cookie session. Needed for containers where the socket
|
|
10
|
+
is not forwarded (`REVDECK_URL` + `REVDECK_TOKEN`).
|
|
11
|
+
3. **UI HTTP**: `/api/*` and SSE `/api/events` for the browser.
|
|
12
|
+
|
|
13
|
+
## Messages
|
|
14
|
+
|
|
15
|
+
Request:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{"id": 1, "method": "reviews.get", "params": {"review": "gitlab:gitlab.com/group/project!42"}}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Response:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{"id": 1, "result": {"review": {"id": "...", "info": {}, "threads": []}}}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Error:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{"id": 1, "error": {"message": "...", "code": "APPROVAL", "hint": "..."}}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`id` is a number. A connection is considered untrusted until the first successful request:
|
|
34
|
+
authorization is enforced by the socket file's permissions.
|
|
35
|
+
|
|
36
|
+
## Methods
|
|
37
|
+
|
|
38
|
+
| Method | Parameters | Result |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `ping` | — | version, protocol, pid |
|
|
41
|
+
| `reviews.list` | — | saved reviews |
|
|
42
|
+
| `reviews.remoteList` | `root`, `scope`, `limit` | open reviews from the platform |
|
|
43
|
+
| `reviews.open` | `root`, `input`/`platform`/`project`/`host`/`branch` | review snapshot |
|
|
44
|
+
| `reviews.refresh` | `review` | refreshed snapshot |
|
|
45
|
+
| `reviews.get` | `review`, `refresh?` | snapshot (without the full diff) |
|
|
46
|
+
| `diff.get` | `review`, `file` | file hunks |
|
|
47
|
+
| `drafts.put` | `review`, `threadId`, `patch` | draft |
|
|
48
|
+
| `findings.put` | `review`, `findings[]` | findings after merge |
|
|
49
|
+
| `findings.list` | `review` | findings |
|
|
50
|
+
| `summary.put` | `review`, `summary` | summary |
|
|
51
|
+
| `decisions.put` | `review`, `decisions` | human decisions |
|
|
52
|
+
| `notes.add` | `review`, `file`, `line`, `text`, `toAgent` | line comment |
|
|
53
|
+
| `notes.update` | `review`, `noteId`, `patch` | updated comment |
|
|
54
|
+
| `reactions.list` | `review`, `noteIds[]` | reactions grouped by comment id |
|
|
55
|
+
| `reactions.toggle` | `review`, `noteId`, `emoji` | `active`, fresh reactions |
|
|
56
|
+
| `attachments.upload` | `review`, `name`, `contentType`, `data` (base64) | `name`, absolute `url`, `markdown` |
|
|
57
|
+
| `notes.delete` | `review`, `noteId` | `{ok}` |
|
|
58
|
+
| `publish.preview` | `review`, `kind`, `ids[]` | plan + warnings |
|
|
59
|
+
| `publish.approve` | `review`, `kind`, `ids[]`, `hash` | `approvalId`, `expiresAt` |
|
|
60
|
+
| `publish.commit` | `review`, `approvalId` | per-item results |
|
|
61
|
+
| `rules.get` | `review` | rules markdown, checks, warnings |
|
|
62
|
+
| `checks.run` | `review`, `allowRun`, `ids[]` | check results |
|
|
63
|
+
| `audit.tail` | `limit` | latest actions |
|
|
64
|
+
|
|
65
|
+
## Approval
|
|
66
|
+
|
|
67
|
+
1. `publish.preview` builds `items` — the exact bodies, paths, lines, and resolve flags — and
|
|
68
|
+
`hash = sha256(canonicalJson({reviewId, kind, items}))`.
|
|
69
|
+
2. The human sees the plan. `publish.approve` recomputes the plan from the same `ids`; if the hash does
|
|
70
|
+
not match, it returns an error. Otherwise it creates a one-time `approval_id` with a 10-minute TTL.
|
|
71
|
+
3. `publish.commit` verifies that the approval has not been used and has not expired, marks it as used,
|
|
72
|
+
and executes only `items`. Items that have already been posted are skipped.
|
|
73
|
+
|
|
74
|
+
The agent cannot call `publish.approve`: the MCP tool does not expose it. A human confirms in the UI or
|
|
75
|
+
in the CLI with `--yes`.
|
|
76
|
+
|
|
77
|
+
## HTTP API (UI)
|
|
78
|
+
|
|
79
|
+
| Method | Path | Purpose |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| GET | `/` | SPA; `?t=<token>` sets the cookie and redirects |
|
|
82
|
+
| GET | `/assets/*` | static build assets |
|
|
83
|
+
| GET | `/api/health` | liveness |
|
|
84
|
+
| GET | `/api/reviews` | review list |
|
|
85
|
+
| GET | `/api/state?review=` | snapshot for the UI |
|
|
86
|
+
| GET | `/api/diff?review=&file=` | file hunks |
|
|
87
|
+
| GET | `/api/rules?review=` | rules |
|
|
88
|
+
| GET | `/api/events` | SSE: `review` |
|
|
89
|
+
| POST | `/api/reviews/open`, `/api/reviews/refresh` | open/refresh |
|
|
90
|
+
| POST | `/api/drafts`, `/api/findings`, `/api/summary`, `/api/decisions` | drafts |
|
|
91
|
+
| POST | `/api/notes` | `action: add/update/delete` |
|
|
92
|
+
| GET | `/api/reactions?review=¬es=` | reactions for comment ids |
|
|
93
|
+
| POST | `/api/reactions` | toggle a reaction |
|
|
94
|
+
| POST | `/api/attachments` | upload an image/video and return an absolute link |
|
|
95
|
+
| POST | `/api/publish/preview`, `/approve`, `/commit` | publishing |
|
|
96
|
+
| POST | `/api/checks` | checks |
|
|
97
|
+
| POST | `/api/rpc` | RPC proxy for the HTTP transport |
|
|
98
|
+
|
|
99
|
+
## Versioning
|
|
100
|
+
|
|
101
|
+
`PROTOCOL_VERSION` in `src/version.ts` is checked during the handshake; the client and daemon must be
|
|
102
|
+
the same package version. A method mismatch returns `METHOD` with a hint to update revdeck.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Документация revdeck
|
|
2
|
+
|
|
3
|
+
[English](../README.md) | Русский
|
|
4
|
+
|
|
5
|
+
Английская документация в `docs/` — каноническая; русские переводы лежат рядом в `docs/ru/` и могут
|
|
6
|
+
немного отставать.
|
|
7
|
+
|
|
8
|
+
| Документ | Английский | Русский |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| Архитектура | [../architecture.md](../architecture.md) | [architecture.md](architecture.md) |
|
|
11
|
+
| Конфигурация и правила | [../configuration.md](../configuration.md) | [configuration.md](configuration.md) |
|
|
12
|
+
| Протокол RPC и HTTP | [../protocol.md](../protocol.md) | [protocol.md](protocol.md) |
|
|
13
|
+
| Модель угроз | [../threat-model.md](../threat-model.md) | [threat-model.md](threat-model.md) |
|
|
14
|
+
| Интеграции с харнессами | [../integrations.md](../integrations.md) | [integrations.md](integrations.md) |
|
|
15
|
+
| ADR-0001: демон-first | [../adr/0001-daemon-first.md](../adr/0001-daemon-first.md) | [adr/0001-daemon-first.md](adr/0001-daemon-first.md) |
|
|
16
|
+
|
|
17
|
+
Начните с [README](../../README.ru.md): установка, команды и настройка MCP.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# ADR-0001: Демон-first архитектура
|
|
2
|
+
|
|
3
|
+
[English](../../adr/0001-daemon-first.md) | Русский
|
|
4
|
+
|
|
5
|
+
- **Статус:** Accepted
|
|
6
|
+
- **Дата:** 2026-11-11
|
|
7
|
+
|
|
8
|
+
## Контекст
|
|
9
|
+
|
|
10
|
+
Первая версия revdeck работала как процесс, который человек запускал вручную на хосте
|
|
11
|
+
(`node devtools/mr-ui/server.mjs <N>`), а агент общался с ним через файлы `.revdeck/*.json`.
|
|
12
|
+
Это давало правильную границу безопасности (агент не публикует, токен остаётся на хосте), но:
|
|
13
|
+
|
|
14
|
+
- сервер умирал вместе с терминалом, ссылку нужно было копировать вручную;
|
|
15
|
+
- файловый обмен давал гонки (версия файла, 409) и требовал синхронизации полей между агентом и UI;
|
|
16
|
+
- нельзя было держать несколько ревью одновременно;
|
|
17
|
+
- каждый проект требовал своей копии скрипта и шёл в одном процессе.
|
|
18
|
+
|
|
19
|
+
Нужны: запуск по `/`-команде в любом харнессе, полноценный интерфейс, несколько ревью, GitLab и
|
|
20
|
+
GitHub, конфигурируемые правила проекта и установка в любой агентский проект.
|
|
21
|
+
|
|
22
|
+
## Решение
|
|
23
|
+
|
|
24
|
+
Строим продукт вокруг локального демона:
|
|
25
|
+
|
|
26
|
+
- демон `revdeck daemon` — долгоживущий процесс пользователя, держит креды, SQLite, провайдеры, RPC,
|
|
27
|
+
HTTP/SSE и UI;
|
|
28
|
+
- CLI и MCP — тонкие клиенты к RPC; MCP-сервер не содержит логики ревью;
|
|
29
|
+
- агент пишет только черновики; публикация требует approval, привязанного к хешу плана;
|
|
30
|
+
- UI — Svelte SPA, отдаётся демоном, живёт дольше сессии агента;
|
|
31
|
+
- состояние — SQLite в `~/.local/state/revdeck`, а не JSON в репозитории;
|
|
32
|
+
- файловый формат остаётся как `export`/`import` для совместимости и песочниц без сокета.
|
|
33
|
+
|
|
34
|
+
## Рассмотренные альтернативы
|
|
35
|
+
|
|
36
|
+
1. **MCP-процесс на сессию, без демона.** Проще установка, но UI не переживает сессию, несколько
|
|
37
|
+
агентов конкурируют за состояние, токен оказывается в процессе, который запускает агент, и
|
|
38
|
+
контейнерный сценарий всё равно требует отдельного процесса на хосте. Отклонено.
|
|
39
|
+
2. **Только файловый обмен.** Максимальная совместимость, но гонки и ручной запуск остаются.
|
|
40
|
+
Оставлено как `export/import`, не как основной путь.
|
|
41
|
+
3. **Облачный сервис.** Снимает установку, но требует передавать код и токены наружу. Противоречит
|
|
42
|
+
требованию «локально, один человек».
|
|
43
|
+
|
|
44
|
+
## Последствия
|
|
45
|
+
|
|
46
|
+
- Появляется жизненный цикл демона: сокет, pid, логи, автозапуск (`daemon install`), обработка
|
|
47
|
+
зависшего сокета. Клиент умеет поднять демон сам.
|
|
48
|
+
- Права на сокет — часть модели безопасности: каталог 0700, сокет 0600.
|
|
49
|
+
- В контейнере нужен либо смонтированный сокет, либо `REVDECK_URL`/`REVDECK_TOKEN`; иначе —
|
|
50
|
+
`export`/`import` и запуск на хосте. `revdeck devcontainer --write` генерирует mounts сокета и
|
|
51
|
+
state и ставит `REVDECK_DEV_CONTAINER=1`, поэтому контейнер не запускает свой демон.
|
|
52
|
+
- Один демон обслуживает несколько репозиториев: `root` передаётся при `open`, конфиг читается из
|
|
53
|
+
этого корня.
|
|
54
|
+
- Версии клиента и демона должны совпадать; добавлен `PROTOCOL_VERSION`.
|
|
55
|
+
- Появляется место для фонового sync и SSE, что улучшает UX по сравнению с ручным обновлением
|
|
56
|
+
страницы.
|
|
57
|
+
|
|
58
|
+
## Последующие решения
|
|
59
|
+
|
|
60
|
+
- Native drafts (GitLab draft notes, GitHub pending review) — следующий шаг; интерфейс провайдера
|
|
61
|
+
уже содержит `capabilities.nativeDrafts`.
|
|
62
|
+
- Многопользовательский режим не планируется; при необходимости — отдельный ADR.
|