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,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.
@@ -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=&notes=` | 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.