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.
Files changed (48) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/CONTRIBUTING.md +98 -0
  3. package/LICENSE +21 -0
  4. package/README.md +223 -2
  5. package/README.ru.md +210 -0
  6. package/SECURITY.md +45 -0
  7. package/dist/cli.js +2669 -0
  8. package/dist/cli.js.map +7 -0
  9. package/dist/daemon.js +3715 -0
  10. package/dist/daemon.js.map +7 -0
  11. package/dist/mcp.js +885 -0
  12. package/dist/mcp.js.map +7 -0
  13. package/dist/ui/assets/index-ByfGHwCb.css +1 -0
  14. package/dist/ui/assets/index-mvPA8Avw.js +10 -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 +63 -0
  19. package/docs/architecture.md +91 -0
  20. package/docs/configuration.md +121 -0
  21. package/docs/integrations.md +84 -0
  22. package/docs/protocol.md +96 -0
  23. package/docs/ru/README.md +17 -0
  24. package/docs/ru/adr/0001-daemon-first.md +61 -0
  25. package/docs/ru/architecture.md +91 -0
  26. package/docs/ru/configuration.md +119 -0
  27. package/docs/ru/integrations.md +82 -0
  28. package/docs/ru/protocol.md +96 -0
  29. package/docs/ru/threat-model.md +50 -0
  30. package/docs/screenshot.png +0 -0
  31. package/docs/screenshot.ru.png +0 -0
  32. package/docs/threat-model.md +51 -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/ru/Criteria.md +249 -0
  44. package/skills/revdeck/ru/Feedback.md +64 -0
  45. package/skills/revdeck/ru/Review.md +148 -0
  46. package/skills/revdeck/ru/Rules.md +69 -0
  47. package/skills/revdeck/ru/SKILL.md +73 -0
  48. package/skills/revdeck/ru/Sources.md +46 -0
@@ -0,0 +1,63 @@
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.
53
+ - One daemon serves several repositories: `root` is passed on `open`, and the config is read from that
54
+ root.
55
+ - Client and daemon versions must match; [`PROTOCOL_VERSION`](../protocol.md) was added.
56
+ - This creates room for background sync and SSE, which improves UX compared to manually refreshing
57
+ the page.
58
+
59
+ ## Follow-up decisions
60
+
61
+ - Native drafts (GitLab draft notes, GitHub pending review) are the next step; the provider interface
62
+ already contains `capabilities.nativeDrafts`.
63
+ - A multi-user mode is not planned; if needed, a separate ADR.
@@ -0,0 +1,91 @@
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>` copies the skill and writes the MCP config.
81
+ Harnesses without MCP use the CLI.
82
+ - **Rules**: `rules.map`/`rules.sources` in `.revdeck.yml`; rule packs can be distributed as a regular
83
+ configuration file.
84
+ - **Checks**: `checks` with argv, no shell; run only on explicit confirmation.
85
+
86
+ ## Performance
87
+
88
+ - The diff is parsed on the server; the UI receives files as a list and hunks one file at a time.
89
+ - Review state does not contain the full diff; SSE reports changes and the UI refetches the state.
90
+ - Snippets for threads and findings are computed on the server and size-limited.
91
+ - Rule context is capped at 320 KB; MCP context is capped by the `max_bytes` parameter.
@@ -0,0 +1,121 @@
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_ROOT` | project root for the MCP server |
116
+ | `REVDECK_LANG` | CLI/MCP/daemon language: `ru` or `en` (default: system locale) |
117
+
118
+ ## Validation
119
+
120
+ `revdeck config --json` shows the effective config, the path where it was found, and warnings.
121
+ `revdeck rules --explain src/app.ts` shows which documents and sections were selected for the file.
@@ -0,0 +1,84 @@
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/*`, `.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/*` + YAML snippet in the output |
25
+ | Universal | — | `.agents/skills/revdeck/*` + MCP |
26
+
27
+ `revdeck init --adapters dsh,claude` explicitly enables the adapters you need. A repeated `init`
28
+ does not overwrite existing command files and does not clobber other keys in `.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` writes `npx -y revdeck mcp` into the MCP config. Publishing over MCP
45
+ requires an `approval_id`: a human issues it in the UI or in the CLI with `--yes`, and the agent
46
+ cannot obtain it.
47
+
48
+ ## DSH
49
+
50
+ The DSH MCP client is configured in the Cordis profile:
51
+
52
+ ```yaml
53
+ - id: mcp-revdeck
54
+ name: '@deepseek-ai/dsh-mcp-client'
55
+ config:
56
+ serverName: revdeck
57
+ transport: stdio
58
+ command: npx
59
+ args: ['-y', 'revdeck', 'mcp']
60
+ env:
61
+ REVDECK_LANG: en
62
+ ```
63
+
64
+ The tools will appear as `mcp__revdeck__*`. DSH does not support MCP prompt templates, so `/revdeck`
65
+ requires a host command (`ctx.commands.register`) or a client action — that is a separate wrapper
66
+ plugin. Until then, use the skill + MCP tools: the `/` slash is not required for things to work.
67
+
68
+ ## Container
69
+
70
+ The daemon runs on the host, the agent in a container. Options in decreasing order of convenience:
71
+
72
+ 1. Mount `$XDG_RUNTIME_DIR/revdeck` into the container at the same path — the socket works as is.
73
+ 2. `REVDECK_URL=http://host.docker.internal:<port>` and `REVDECK_TOKEN=<token>` — the HTTP transport.
74
+ The port and token are printed by `revdeck daemon start` (or in `daemon.json` in the state
75
+ directory).
76
+ 3. `revdeck export <N>` on the host and `revdeck import <N>` in the container — a file bridge.
77
+
78
+ ## Verification
79
+
80
+ ```bash
81
+ revdeck doctor
82
+ ```
83
+
84
+ Shows the binaries found, `glab`/`gh` authorization, daemon status and the installed adapter.
@@ -0,0 +1,96 @@
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
+ | `notes.delete` | `review`, `noteId` | `{ok}` |
55
+ | `publish.preview` | `review`, `kind`, `ids[]` | plan + warnings |
56
+ | `publish.approve` | `review`, `kind`, `ids[]`, `hash` | `approvalId`, `expiresAt` |
57
+ | `publish.commit` | `review`, `approvalId` | per-item results |
58
+ | `rules.get` | `review` | rules markdown, checks, warnings |
59
+ | `checks.run` | `review`, `allowRun`, `ids[]` | check results |
60
+ | `audit.tail` | `limit` | latest actions |
61
+
62
+ ## Approval
63
+
64
+ 1. `publish.preview` builds `items` — the exact bodies, paths, lines, and resolve flags — and
65
+ `hash = sha256(canonicalJson({reviewId, kind, items}))`.
66
+ 2. The human sees the plan. `publish.approve` recomputes the plan from the same `ids`; if the hash does
67
+ not match, it returns an error. Otherwise it creates a one-time `approval_id` with a 10-minute TTL.
68
+ 3. `publish.commit` verifies that the approval has not been used and has not expired, marks it as used,
69
+ and executes only `items`. Items that have already been posted are skipped.
70
+
71
+ The agent cannot call `publish.approve`: the MCP tool does not expose it. A human confirms in the UI or
72
+ in the CLI with `--yes`.
73
+
74
+ ## HTTP API (UI)
75
+
76
+ | Method | Path | Purpose |
77
+ |---|---|---|
78
+ | GET | `/` | SPA; `?t=<token>` sets the cookie and redirects |
79
+ | GET | `/assets/*` | static build assets |
80
+ | GET | `/api/health` | liveness |
81
+ | GET | `/api/reviews` | review list |
82
+ | GET | `/api/state?review=` | snapshot for the UI |
83
+ | GET | `/api/diff?review=&file=` | file hunks |
84
+ | GET | `/api/rules?review=` | rules |
85
+ | GET | `/api/events` | SSE: `review` |
86
+ | POST | `/api/reviews/open`, `/api/reviews/refresh` | open/refresh |
87
+ | POST | `/api/drafts`, `/api/findings`, `/api/summary`, `/api/decisions` | drafts |
88
+ | POST | `/api/notes` | `action: add/update/delete` |
89
+ | POST | `/api/publish/preview`, `/approve`, `/commit` | publishing |
90
+ | POST | `/api/checks` | checks |
91
+ | POST | `/api/rpc` | RPC proxy for the HTTP transport |
92
+
93
+ ## Versioning
94
+
95
+ `PROTOCOL_VERSION` in `src/version.ts` is checked during the handshake; the client and daemon must be
96
+ 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,61 @@
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` и запуск на хосте.
51
+ - Один демон обслуживает несколько репозиториев: `root` передаётся при `open`, конфиг читается из
52
+ этого корня.
53
+ - Версии клиента и демона должны совпадать; добавлен `PROTOCOL_VERSION`.
54
+ - Появляется место для фонового sync и SSE, что улучшает UX по сравнению с ручным обновлением
55
+ страницы.
56
+
57
+ ## Последующие решения
58
+
59
+ - Native drafts (GitLab draft notes, GitHub pending review) — следующий шаг; интерфейс провайдера
60
+ уже содержит `capabilities.nativeDrafts`.
61
+ - Многопользовательский режим не планируется; при необходимости — отдельный ADR.
@@ -0,0 +1,91 @@
1
+ # Архитектура
2
+
3
+ [English](../architecture.md) | Русский
4
+
5
+ ## Цели
6
+
7
+ 1. Агент получает контекст ревью и правила проекта детерминированно, без ручных файлов и без доступа
8
+ к токенам платформ.
9
+ 2. Публикация в GitLab/GitHub происходит только после подтверждения человеком и идемпотентна.
10
+ 3. Один инструмент работает в любом харнессе: MCP для агентских клиентов, CLI как универсальный
11
+ слой, веб-интерфейс для человека.
12
+ 4. Правила проекта конфигурируются, а не зашиты в промпт.
13
+
14
+ ## Не цели
15
+
16
+ - Замена нативного diff-вьюера GitLab/GitHub. UI оптимизирован под триаж и подтверждение.
17
+ - Собственный статический анализ. Проверки делегируются линту, типам и тестам проекта.
18
+ - Многопользовательский сервер. Модель — один пользователь, несколько ревью, демон на хосте.
19
+
20
+ ## Компоненты
21
+
22
+ ```text
23
+ agent (MCP/CLI) ──▶ revdeck daemon ──glab/gh──▶ GitLab/GitHub
24
+ │
25
+ SQLite + UI ◀── human
26
+ ```
27
+
28
+ - `src/core` — чистые функции и модели: парсер unified diff, правила, fingerprints замечаний,
29
+ санитайзер публикации, редакция секретов, безопасный запуск процессов.
30
+ - `src/providers` — единственное место, знающее о платформах. `Provider` нормализует MR/PR, diff,
31
+ треды, ответы, резолв и создание комментариев.
32
+ - `src/daemon/service.ts` — оркестратор: обновление ревью, черновики, решения, план публикации,
33
+ approval, идемпотентная запись, фоновый sync.
34
+ - `src/daemon/store.ts` — SQLite (`node:sqlite`): ревью, снимок diff/тредов, черновики, замечания,
35
+ решения, комментарии, approvals, аудит.
36
+ - `src/daemon/rpc.ts` — JSON-RPC поверх unix-сокета (0600) для CLI и MCP.
37
+ - `src/daemon/http.ts` — UI и `/api/*`, SSE-события, cookie-сессия с одноразовым токеном.
38
+ - `src/cli` — команды, `init`, `doctor`, экспорт/импорт старого файлового формата.
39
+ - `src/mcp` — MCP-сервер: инструменты и промпты, тонкая обёртка над RPC.
40
+ - `ui` — Svelte 5 SPA, собирается в `dist/ui`.
41
+
42
+ ## Поток данных
43
+
44
+ 1. `revdeck open 42` → RPC `reviews.open` с `root` рабочей копии.
45
+ 2. Демон определяет платформу/хост/проект (аргументы → конфиг → git remote), создаёт провайдер,
46
+ читает MR, diff и треды, сохраняет снимок и синтетические черновики.
47
+ 3. Агент запрашивает `revdeck_context`: демон собирает эффективные правила (документы и секции для
48
+ изменённых путей), сниппеты, треды и замечания.
49
+ 4. Агент пишет черновики (`drafts.put`, `findings.put`, `summary.put`, `notes.update`).
50
+ 5. Человек в UI вызывает `publish.preview` → видит точный план и хеш → `publish.approve` →
51
+ `publish.commit`. Демон выполняет только пункты, вошедшие в подтверждённый план.
52
+ 6. Фоновый sync раз в 2 минуты обновляет открытые ревью и рассылает SSE-события UI.
53
+
54
+ ## Модель данных
55
+
56
+ - `ReviewRef` — платформа, хост, проект, номер. `reviewId` — `platform:host/project!number`.
57
+ - `DiffFile`/`Hunk`/`DiffLine` — номера строк по старой и новой версии; `line` замечания всегда по
58
+ новому файлу.
59
+ - `Thread`/`Note` — общая модель обсуждений.
60
+ - `Finding` — замечание с `id`, `fingerprint`, уровнем, файлом/строкой, текстом и статусом
61
+ (`draft`, `accepted`, `rejected`, `published`, `fixed`, `stale`).
62
+ - `Approval` — одноразовый id, привязанный к `hash = sha256(canonicalJson({reviewId, kind, items}))`,
63
+ TTL 10 минут.
64
+
65
+ ## Идемпотентность и конкурентность
66
+
67
+ - Записи черновиков и замечаний идут через один процесс; SQLite в WAL.
68
+ - Публикация помечает approval использованным до выполнения. Пункты, которые уже ушли (совпадающий
69
+ `posted`, `published`), пропускаются. Ошибка одного пункта не отменяет остальные и записывается в
70
+ черновик/замечание.
71
+ - Хеш плана защищает от правки агентом между показом человеку и публикацией: изменился текст → хеш
72
+ не совпал → approve отклонён.
73
+ - `findings.put` — замена набора с сохранением `id` и решения; исчезнувшие замечания помечаются
74
+ `stale`, а не удаляются.
75
+
76
+ ## Расширение
77
+
78
+ - **Новая платформа**: реализовать `Provider` и добавить ветку в `createProvider`; всё остальное не
79
+ меняется.
80
+ - **Новый харнесс**: `revdeck init --adapters <имя>` копирует skill и пишет MCP-конфиг. Для
81
+ харнессов без MCP используется CLI.
82
+ - **Правила**: `rules.map`/`rules.sources` в `.revdeck.yml`; паки правил можно распространять как
83
+ обычный файл конфигурации.
84
+ - **Проверки**: `checks` с argv без shell; запуск только по явному подтверждению.
85
+
86
+ ## Производительность
87
+
88
+ - Diff парсится на сервере, UI получает файлы списком и hunks по одному файлу.
89
+ - Состояние ревью не содержит полный diff; SSE сообщает об изменениях, UI перезапрашивает состояние.
90
+ - Сниппеты для тредов и замечаний считаются на сервере и ограничены по размеру.
91
+ - Контекст правил ограничен 320 КБ; MCP-контекст — параметром `max_bytes`.