revspec 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +18 -0
- package/README.md +90 -0
- package/bin/revspec.ts +109 -0
- package/bun.lock +213 -0
- package/docs/superpowers/plans/2026-03-14-spectral-v1-implementation.md +2139 -0
- package/docs/superpowers/specs/2026-03-14-spec-review-tool-design.md +331 -0
- package/docs/superpowers/specs/2026-03-14-spec-review-tool-design.review.json +141 -0
- package/docs/superpowers/specs/claude-code-integration-notes.md +26 -0
- package/package.json +21 -0
- package/scripts/release.sh +76 -0
- package/src/protocol/merge.ts +52 -0
- package/src/protocol/read.ts +25 -0
- package/src/protocol/types.ts +55 -0
- package/src/protocol/write.ts +10 -0
- package/src/state/review-state.ts +136 -0
- package/src/tui/app.ts +691 -0
- package/src/tui/comment-input.ts +189 -0
- package/src/tui/confirm.ts +93 -0
- package/src/tui/help.ts +134 -0
- package/src/tui/pager.ts +158 -0
- package/src/tui/search.ts +119 -0
- package/src/tui/status-bar.ts +76 -0
- package/src/tui/theme.ts +34 -0
- package/src/tui/thread-list.ts +145 -0
- package/test/cli.test.ts +151 -0
- package/test/opentui-smoke.test.ts +12 -0
- package/test/protocol/merge.test.ts +100 -0
- package/test/protocol/read.test.ts +92 -0
- package/test/protocol/types.test.ts +95 -0
- package/test/protocol/write.test.ts +72 -0
- package/test/state/review-state.test.ts +326 -0
- package/test/tui/pager.test.ts +184 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# Revspec — Design
|
|
2
|
+
|
|
3
|
+
A review tool for AI-generated spec documents. Unlike traditional spec review (human reviews, human author edits), the author here is an AI — it reads structured feedback and acts on every comment instantly. This enables multiple review rounds in a single sitting, with the human's only job being to give feedback.
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
In AI-assisted development (e.g., Claude Code's superpowers workflow), the AI generates spec documents that need human review before implementation. The current review step breaks the agentic loop — the user has to open the markdown file separately, read it, then type unstructured feedback in the terminal. There's no way to attach comments inline, highlight regions, or give feedback the AI can precisely act on.
|
|
8
|
+
|
|
9
|
+
Traditional review tools (Google Docs, PR reviews) are designed for human-to-human workflows. They optimize for discussion, consensus, and collaborative editing. But when the spec author is an AI, the workflow is fundamentally different: the human only comments, the AI always edits, and turnaround is instant.
|
|
10
|
+
|
|
11
|
+
## Solution
|
|
12
|
+
|
|
13
|
+
A CLI tool (`revspec`) that opens a spec file in a reviewable UI, lets the user add line-anchored comments, and outputs structured JSON on close. The AI reads the JSON, addresses each comment, updates the spec, and re-invokes the review tool — completing the human-AI feedback loop in seconds rather than days.
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌──────────────────────────────────────────────────────────┐
|
|
19
|
+
│ Protocol: JSON schema (language-agnostic) │
|
|
20
|
+
├──────────────────────────────────────────────────────────┤
|
|
21
|
+
│ CLI: revspec <file> [--tui|--nvim|--web] │
|
|
22
|
+
├──────────┬──────────────┬────────────────────────────────┤
|
|
23
|
+
│ Built-in │ Neovim Plugin│ Web UI │
|
|
24
|
+
│ TUI │ (Lua) │ (React/Express) │
|
|
25
|
+
│ v1 │ v2 │ v2 │
|
|
26
|
+
└──────────┴──────────────┴────────────────────────────────┘
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Three layers:
|
|
30
|
+
|
|
31
|
+
- **Protocol** — A JSON schema defining review threads. Language-agnostic. Any UI that outputs this format works with Claude Code.
|
|
32
|
+
- **CLI** — Node-based entry point. Manages file lifecycle (draft/final/resume/cleanup), renders the TUI, prints the output path.
|
|
33
|
+
- **UI Adapters** — Built-in TUI pager (v1), neovim plugin (v2), web UI (v2), future touch/iPad client. Each reads the spec + existing review threads, lets the user add messages, writes to the draft JSON.
|
|
34
|
+
|
|
35
|
+
## Protocol
|
|
36
|
+
|
|
37
|
+
### Review File Format (JSON)
|
|
38
|
+
|
|
39
|
+
The review file is a JSON document with threads as first-class objects. The AI reads and rewrites this file (to update anchors, add responses, set statuses). The human UI writes to a separate draft file that the CLI merges in.
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"file": "docs/specs/2026-03-14-feature-design.md",
|
|
44
|
+
"threads": [
|
|
45
|
+
{
|
|
46
|
+
"id": "1",
|
|
47
|
+
"line": 12,
|
|
48
|
+
"status": "resolved",
|
|
49
|
+
"messages": [
|
|
50
|
+
{ "author": "human", "text": "why webhook not polling?" },
|
|
51
|
+
{ "author": "ai", "text": "Changed to polling. Good call." }
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "2",
|
|
56
|
+
"line": 52,
|
|
57
|
+
"status": "pending",
|
|
58
|
+
"messages": [
|
|
59
|
+
{ "author": "human", "text": "this term is ambiguous" },
|
|
60
|
+
{ "author": "ai", "text": "I think it's clear because X" }
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "3",
|
|
65
|
+
"line": 20,
|
|
66
|
+
"status": "open",
|
|
67
|
+
"messages": [
|
|
68
|
+
{ "author": "human", "text": "this whole section needs rethinking" }
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Top-level fields:** `file` is the spec path. `threads` contains the review threads.
|
|
76
|
+
|
|
77
|
+
**Thread model:** Each thread is an object with a line anchor, a status, and an ordered array of messages. The AI can update anchors directly (e.g., after editing the spec shifts line numbers) without appending workaround messages.
|
|
78
|
+
|
|
79
|
+
### Thread Statuses
|
|
80
|
+
|
|
81
|
+
| Status | Meaning | Who sets it |
|
|
82
|
+
|--------|---------|-------------|
|
|
83
|
+
| `open` | Needs AI attention | Human (default on creation, or human replies) |
|
|
84
|
+
| `pending` | AI responded, needs human attention | AI |
|
|
85
|
+
| `resolved` | Done — human accepts | Human |
|
|
86
|
+
| `outdated` | Anchor target was removed from spec | AI |
|
|
87
|
+
|
|
88
|
+
The status tracks whose turn it is: `open` = AI's turn, `pending` = human's turn. The review is complete when every thread is either `resolved` or `outdated`. Approve blocks on `open` and `pending` threads.
|
|
89
|
+
|
|
90
|
+
### Anchor Reanchoring
|
|
91
|
+
|
|
92
|
+
When the AI edits the spec, it updates thread anchors in the review file:
|
|
93
|
+
|
|
94
|
+
- **Line shifted** — AI updates the `line` field to the new position
|
|
95
|
+
- **Line modified** — AI keeps the anchor (thread is still relevant there)
|
|
96
|
+
- **Line removed** — AI sets status to `outdated`
|
|
97
|
+
|
|
98
|
+
### Anchor Types
|
|
99
|
+
|
|
100
|
+
v1 supports line anchors only. The protocol reserves fields for future anchor types (span, region, quoted) but the TUI does not implement them.
|
|
101
|
+
|
|
102
|
+
| Type | Fields | Version | Use Case |
|
|
103
|
+
|------|--------|---------|----------|
|
|
104
|
+
| Line | `line` | v1 | Comment on a whole line |
|
|
105
|
+
| Span | `line`, `startChar`, `endChar` | v2+ | Highlight characters within a line |
|
|
106
|
+
| Region | `startLine`, `startChar`, `endLine`, `endChar` | v2+ | Multi-line selection |
|
|
107
|
+
| Quoted | `quotedText` | v3+ | Text-anchored comment (Google Docs) |
|
|
108
|
+
|
|
109
|
+
### Thread Field Reference
|
|
110
|
+
|
|
111
|
+
| Field | Type | Required | Description |
|
|
112
|
+
|-------|------|----------|-------------|
|
|
113
|
+
| `id` | string | yes | Thread identifier |
|
|
114
|
+
| `status` | string | yes | `open`, `pending`, `resolved`, `outdated` |
|
|
115
|
+
| `messages` | array | yes | Ordered list of `{ author, text }` objects |
|
|
116
|
+
| `line` | integer (1-indexed) | yes (v1) | Line number anchor |
|
|
117
|
+
|
|
118
|
+
Fields reserved for v2+: `startChar`, `endChar`, `startLine`, `endLine`, `quotedText`.
|
|
119
|
+
|
|
120
|
+
### File Naming
|
|
121
|
+
|
|
122
|
+
Review file lives next to the spec:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
docs/specs/2026-03-14-feature-design.md
|
|
126
|
+
docs/specs/2026-03-14-feature-design.review.json # the review
|
|
127
|
+
docs/specs/2026-03-14-feature-design.review.draft.json # in-progress (human hasn't submitted yet)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
No round numbers — the file is a living conversation. Git tracks history.
|
|
131
|
+
|
|
132
|
+
The human UI writes only to the draft file. The CLI merges the draft into the review file. The AI reads and rewrites the review file directly (to update anchors, add responses, set statuses).
|
|
133
|
+
|
|
134
|
+
### Thread ID Generation
|
|
135
|
+
|
|
136
|
+
Simple incrementing strings: `"1"`, `"2"`, `"3"`, etc. The UI reads the highest existing `id` in the review + draft files and increments. The AI does the same when creating new threads. No collision risk — only one writer at a time (see Concurrency below).
|
|
137
|
+
|
|
138
|
+
### Concurrency
|
|
139
|
+
|
|
140
|
+
Single-writer contract. The human UI and the AI never write concurrently — the workflow alternates: human reviews (CLI blocks) → CLI merges draft → AI reads and rewrites review file → AI invokes CLI again → human reviews. If this assumption is violated, the file may be corrupted.
|
|
141
|
+
|
|
142
|
+
## CLI
|
|
143
|
+
|
|
144
|
+
### Usage
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
revspec <file.md> # opens with default UI (TUI)
|
|
148
|
+
revspec <file.md> --tui # explicit TUI (default)
|
|
149
|
+
revspec <file.md> --nvim # neovim plugin (v2)
|
|
150
|
+
revspec <file.md> --web # web UI (v2)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Responsibilities
|
|
154
|
+
|
|
155
|
+
1. Validate the spec file exists and is readable; exit 1 with error message if not
|
|
156
|
+
2. Check for `.review.draft.json` — if exists, validate it's parseable JSON. If corrupted, warn the user ("Draft file corrupted, starting fresh") and delete it. If valid, pass to UI for resume
|
|
157
|
+
3. Open the TUI (or chosen UI adapter), passing: spec file path, review file path, draft file path
|
|
158
|
+
4. Block until the UI exits
|
|
159
|
+
5. Read the draft file. If it contains `"approved": true`, this is an approval (see Approve Mechanism below). Otherwise, merge threads into `.review.json` (add new threads, append new messages to existing threads). Delete the draft.
|
|
160
|
+
6. Output to stdout and exit:
|
|
161
|
+
- **Approved:** print `APPROVED: <review-file-path>` and exit 0
|
|
162
|
+
- **Has review file:** print `<review-file-path>` and exit 0
|
|
163
|
+
- **No review file:** print nothing and exit 0 (first round, human closed without commenting)
|
|
164
|
+
|
|
165
|
+
### CLI→UI Interface
|
|
166
|
+
|
|
167
|
+
The CLI communicates with UI adapters via environment variables:
|
|
168
|
+
|
|
169
|
+
- `REVSPEC_FILE` — absolute path to the spec markdown file
|
|
170
|
+
- `REVSPEC_REVIEW` — absolute path to the review JSON file (read existing threads)
|
|
171
|
+
- `REVSPEC_DRAFT` — absolute path to the draft JSON file (write new threads/messages here)
|
|
172
|
+
|
|
173
|
+
For the built-in TUI (v1), these are used internally. For external adapters (neovim plugin, v2), they are set as env vars before spawning the process.
|
|
174
|
+
|
|
175
|
+
### Tech
|
|
176
|
+
|
|
177
|
+
- Node.js (TypeScript)
|
|
178
|
+
- Renders the built-in TUI (v1), spawns neovim (v2), or starts Express server (v2)
|
|
179
|
+
- Single entry point, swappable UI backend
|
|
180
|
+
|
|
181
|
+
## Built-in TUI (v1)
|
|
182
|
+
|
|
183
|
+
### UX
|
|
184
|
+
|
|
185
|
+
A terminal pager that renders the spec with line numbers, status indicators, and inline comment hints. No external dependencies — ships with the CLI.
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
189
|
+
│ docs/specs/2026-03-14-feature-design.md [Review] │
|
|
190
|
+
│ Threads: 1 open, 1 pending, 1 resolved │
|
|
191
|
+
├──────────────────────────────────────────────────────────────┤
|
|
192
|
+
│ 12 The system uses polling... ✔ resolved │
|
|
193
|
+
│ 13 to notify downstream │
|
|
194
|
+
│ 14 │
|
|
195
|
+
│ 45 Events are sent via webhook 🔵 pending (AI replied) │
|
|
196
|
+
│ 46 │
|
|
197
|
+
│ 20 The retry logic should 💬 open │
|
|
198
|
+
│ 21 handle exponential backoff │
|
|
199
|
+
├──────────────────────────────────────────────────────────────┤
|
|
200
|
+
│ j/k scroll /search c comment e expand r resolve │
|
|
201
|
+
│ R resolve-all a approve :w save :q submit :q! quit │
|
|
202
|
+
│ n/N next/prev thread │
|
|
203
|
+
└──────────────────────────────────────────────────────────────┘
|
|
204
|
+
|
|
205
|
+
Status indicators:
|
|
206
|
+
💬 open — needs AI attention
|
|
207
|
+
🔵 pending — AI replied, needs your attention
|
|
208
|
+
✔ resolved — done
|
|
209
|
+
⚠ outdated — anchor removed
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Pressing `e` on a line with a thread expands it inline:
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
┌─ Thread #2 (pending) ───────────────────────────┐
|
|
216
|
+
│ 👤 this term is ambiguous │
|
|
217
|
+
│ │
|
|
218
|
+
│ 🤖 I think it's clear because X. The term │
|
|
219
|
+
│ refers to the webhook delivery mechanism │
|
|
220
|
+
│ defined in section 3. │
|
|
221
|
+
│ │
|
|
222
|
+
│ [r]esolve [c]ontinue [q]uit │
|
|
223
|
+
└──────────────────────────────────────────────────┘
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Keybindings
|
|
227
|
+
|
|
228
|
+
| Key | Action |
|
|
229
|
+
|-----|--------|
|
|
230
|
+
| `j` / `k` / `↑` / `↓` | Scroll line by line |
|
|
231
|
+
| `Space` / `b` | Page down / up |
|
|
232
|
+
| `/` | Search text in spec |
|
|
233
|
+
| `c` | Add comment on current line / reply to existing thread |
|
|
234
|
+
| `e` | Expand thread on current line |
|
|
235
|
+
| `r` | Resolve thread on current line |
|
|
236
|
+
| `R` | Resolve all `pending` threads (batch resolve) |
|
|
237
|
+
| `a` | Approve spec — all threads must be resolved/outdated first |
|
|
238
|
+
| `d` | Delete most recent draft message by human on current line's thread |
|
|
239
|
+
| `l` | List all open/pending threads (jump to selected) |
|
|
240
|
+
| `n` / `N` | Jump to next / previous open/pending thread |
|
|
241
|
+
| `:w` | Save draft (without submitting) |
|
|
242
|
+
| `:q` | Submit review and quit (confirmation: "Submit N comments? [y/n]") |
|
|
243
|
+
| `:q!` | Quit without submitting (discard draft) |
|
|
244
|
+
|
|
245
|
+
### Approve Mechanism
|
|
246
|
+
|
|
247
|
+
When the human presses `a`:
|
|
248
|
+
|
|
249
|
+
1. TUI checks all threads are `resolved` or `outdated`. If not, shows an error ("N threads still open/pending").
|
|
250
|
+
2. TUI writes `{ "approved": true }` to `$REVSPEC_DRAFT` and exits.
|
|
251
|
+
3. CLI reads the draft, detects the `approved` flag, prints `APPROVED: <review-file-path>` to stdout.
|
|
252
|
+
|
|
253
|
+
### Implementation
|
|
254
|
+
|
|
255
|
+
- Built into the CLI (Node.js/TypeScript)
|
|
256
|
+
- Uses a terminal UI library (e.g., `ink`, `blessed`, or raw ANSI escape codes)
|
|
257
|
+
- Reads `$REVSPEC_REVIEW` if it exists (missing file = first review, start fresh), loads existing threads
|
|
258
|
+
- Checks `$REVSPEC_DRAFT` for in-progress comments (resume)
|
|
259
|
+
- On `:q` — writes draft JSON to `$REVSPEC_DRAFT` (CLI handles merge to review file)
|
|
260
|
+
- On `:w` — writes draft JSON for manual save / crash recovery
|
|
261
|
+
|
|
262
|
+
### Comment Input
|
|
263
|
+
|
|
264
|
+
When the user presses `c`:
|
|
265
|
+
|
|
266
|
+
- **On a line with no thread:** opens an inline text input below the current line. A new thread `id` is assigned.
|
|
267
|
+
- **On a line with an existing thread:** expands the thread first, opens a reply input at the bottom. Reply flips status back to `open`.
|
|
268
|
+
|
|
269
|
+
`Ctrl+Enter` submits the comment. `Enter` adds a newline. `Escape` cancels. The TUI shows a hint: `[Ctrl+Enter] submit [Enter] newline [Esc] cancel`.
|
|
270
|
+
|
|
271
|
+
## Review Lifecycle
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
1. Claude Code generates spec → spec.md, commits it
|
|
275
|
+
2. Claude Code runs: revspec spec.md
|
|
276
|
+
3. CLI checks for draft → resumes or starts fresh
|
|
277
|
+
4. TUI opens, human reads spec
|
|
278
|
+
5. Human adds comments (c), searches (/), navigates threads (n/N)
|
|
279
|
+
6. :w to save draft, :q to submit (with confirmation), :q! to discard
|
|
280
|
+
7. CLI merges draft into .review.json, exits
|
|
281
|
+
8. Claude Code reads .review.json, processes open threads:
|
|
282
|
+
- Updates spec or responds with explanation
|
|
283
|
+
- Sets threads to pending
|
|
284
|
+
- Rewrites .review.json with updated anchors/statuses/responses
|
|
285
|
+
- Commits spec changes
|
|
286
|
+
9. Claude Code runs revspec again → human sees AI responses on pending threads
|
|
287
|
+
10. Human resolves threads (r), replies to reopen (c), or batch resolves (R)
|
|
288
|
+
11. When all threads are resolved/outdated → human presses a (approve)
|
|
289
|
+
12. CLI exits with "APPROVED: path/to/review.json" → Claude Code proceeds to implementation plan
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Crash Recovery
|
|
293
|
+
|
|
294
|
+
If the TUI crashes or is killed, the draft file persists. Next invocation of `revspec` detects the draft and resumes with all previous comments loaded.
|
|
295
|
+
|
|
296
|
+
## Claude Code Integration
|
|
297
|
+
|
|
298
|
+
A `/review-revspec` skill wraps the `revspec` CLI. The skill:
|
|
299
|
+
|
|
300
|
+
1. Runs `revspec <spec-file>` (blocks while human reviews)
|
|
301
|
+
2. Reads stdout — if `APPROVED:`, proceeds to implementation plan
|
|
302
|
+
3. If a review file path is returned, reads the JSON, processes each thread:
|
|
303
|
+
- `open` threads: update spec or respond with explanation, set to `pending`
|
|
304
|
+
- `pending` threads with new human replies (flipped back to `open`): re-evaluate
|
|
305
|
+
4. Commits spec changes, rewrites `.review.json` with updated anchors/statuses/responses
|
|
306
|
+
5. Loops back to step 1 (runs `revspec` again)
|
|
307
|
+
6. On `APPROVED:`, invokes the writing-plans skill
|
|
308
|
+
|
|
309
|
+
## V2 Scope (not built in v1)
|
|
310
|
+
|
|
311
|
+
- **Neovim plugin** — Lua plugin using extmarks for comment overlays, floating windows for thread expansion, built-in diff mode for split-view review. Power-user adapter for those already in neovim. Inspired by [reviewthem.nvim](https://github.com/KEY60228/reviewthem.nvim).
|
|
312
|
+
- **Web UI** — Express server + React frontend, difit-inspired. Markdown rendered with line numbers, click/drag to comment, submit button finalizes.
|
|
313
|
+
- **Touch/region gestures** — circle-to-select on iPad, translated to region anchors by the UI.
|
|
314
|
+
- **Diff highlighting** — `specRevision` field in review JSON stores git commit hash. TUI/UI highlights lines changed since last review via `git diff <specRevision> -- <spec-file>`.
|
|
315
|
+
- **Span/region/quoted anchors** — character-level and multi-line selection in web/neovim UIs.
|
|
316
|
+
- **History navigation** — view previous review states via git history for context.
|
|
317
|
+
|
|
318
|
+
## V3 Scope (not built in v1 or v2)
|
|
319
|
+
|
|
320
|
+
- **Google Docs integration** — Use Google Docs as the review UI. Upload the spec as a Google Doc (markdown import), human reviews using native Google Docs commenting, pull comments back via API and map to our JSON protocol.
|
|
321
|
+
- **[gws CLI](https://github.com/googleworkspace/cli)** — Rust CLI for Google Workspace APIs with structured JSON output, MCP server mode, and full Docs API access. Handles doc creation, comment retrieval, and markdown import/export. Designed for AI agent workflows.
|
|
322
|
+
- **Flow:** `revspec spec.md --gdocs` → upload via `gws` → open doc URL → human adds native Google Docs comments → signal done → pull comments via `gws` → map to JSON protocol → merge into `.review.json`
|
|
323
|
+
|
|
324
|
+
## Future Considerations
|
|
325
|
+
|
|
326
|
+
- **Multi-reviewer support** — v1 is one human, one AI, one JSON file. Supporting multiple concurrent reviewers would require a database (e.g., CouchDB) for concurrency, conflict resolution, and real-time sync. This is a fundamentally different product — a collaboration platform, not a review tool. Deferred indefinitely.
|
|
327
|
+
|
|
328
|
+
## Inspirations
|
|
329
|
+
|
|
330
|
+
- **[difit](https://github.com/yoshiko-pg/difit)** — CLI that spins up a web server for reviewing git diffs with comments. Validated the "CLI → local server → browser → structured output on close" pattern.
|
|
331
|
+
- **[reviewthem.nvim](https://github.com/KEY60228/reviewthem.nvim)** — Neovim plugin for adding review comments with gutter signs and JSON export.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "/Users/tuephan/repo/spectral/docs/superpowers/specs/2026-03-14-spec-review-tool-design.md",
|
|
3
|
+
"threads": [
|
|
4
|
+
{
|
|
5
|
+
"id": "t1",
|
|
6
|
+
"line": 29,
|
|
7
|
+
"status": "resolved",
|
|
8
|
+
"messages": [
|
|
9
|
+
{
|
|
10
|
+
"author": "human",
|
|
11
|
+
"text": "this is good"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"author": "human",
|
|
15
|
+
"text": "this is good"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"author": "human",
|
|
19
|
+
"text": "hello"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"author": "human",
|
|
23
|
+
"text": "is it me you are looking for"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"author": "ai",
|
|
27
|
+
"text": "Thanks! The three-layer architecture (Protocol, CLI, UI Adapters) is designed to keep each concern independent. Glad it reads well."
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "t2",
|
|
33
|
+
"line": 41,
|
|
34
|
+
"status": "resolved",
|
|
35
|
+
"messages": [
|
|
36
|
+
{
|
|
37
|
+
"author": "human",
|
|
38
|
+
"text": "this is bad"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"author": "ai",
|
|
42
|
+
"text": "Could you clarify what you'd like changed about the review file format? The JSON structure uses threads as first-class objects — is it the schema itself, the naming, or the overall approach you'd like revised?"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "t3",
|
|
48
|
+
"line": 86,
|
|
49
|
+
"status": "resolved",
|
|
50
|
+
"messages": [
|
|
51
|
+
{
|
|
52
|
+
"author": "human",
|
|
53
|
+
"text": "are these status enough"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"author": "ai",
|
|
57
|
+
"text": "The four statuses (`open`, `pending`, `resolved`, `outdated`) cover the core ping-pong model. We discussed simplifying from 5 to 4 statuses earlier — dropping `addressed`/`discussed` in favor of the cleaner `open`/`pending` turn-based model. If you feel a status is missing for a specific workflow, happy to add it."
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "t4",
|
|
63
|
+
"line": 245,
|
|
64
|
+
"status": "resolved",
|
|
65
|
+
"messages": [
|
|
66
|
+
{
|
|
67
|
+
"author": "human",
|
|
68
|
+
"text": "gello"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"author": "ai",
|
|
72
|
+
"text": "Did you mean to type something else here? If this was a test comment, feel free to resolve it."
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "t5",
|
|
78
|
+
"line": 264,
|
|
79
|
+
"status": "resolved",
|
|
80
|
+
"messages": [
|
|
81
|
+
{
|
|
82
|
+
"author": "human",
|
|
83
|
+
"text": "this is bad"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"author": "ai",
|
|
87
|
+
"text": "This section describes the `:q` submit confirmation. Would you like a different confirmation flow, or is the concern about the keybinding choice itself?"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "t6",
|
|
93
|
+
"line": 296,
|
|
94
|
+
"status": "resolved",
|
|
95
|
+
"messages": [
|
|
96
|
+
{
|
|
97
|
+
"author": "human",
|
|
98
|
+
"text": "this is good"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"author": "ai",
|
|
102
|
+
"text": "Great — the Claude Code integration loop is the key piece that closes the human-AI feedback cycle. The `/review-revspec` skill handles the full loop: invoke revspec → read results → process threads → re-invoke."
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "t7",
|
|
108
|
+
"line": 279,
|
|
109
|
+
"status": "resolved",
|
|
110
|
+
"messages": [
|
|
111
|
+
{
|
|
112
|
+
"author": "human",
|
|
113
|
+
"text": "hello"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"author": "human",
|
|
117
|
+
"text": "hello again"
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"id": "t8",
|
|
123
|
+
"line": 15,
|
|
124
|
+
"status": "open",
|
|
125
|
+
"messages": [
|
|
126
|
+
{
|
|
127
|
+
"author": "human",
|
|
128
|
+
"text": "hello"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"author": "human",
|
|
132
|
+
"text": "hello again"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"author": "human",
|
|
136
|
+
"text": "dfgdfgggggggggggggggggggggggggggg"
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Claude Code Integration Notes
|
|
2
|
+
|
|
3
|
+
Deferred — to be addressed when building the `/review-revspec` skill.
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
Claude Code's Bash tool runs without TTY (no stdin, no terminal). A TUI app can't take over the terminal directly.
|
|
8
|
+
|
|
9
|
+
## Launch strategies
|
|
10
|
+
|
|
11
|
+
| Environment | Approach |
|
|
12
|
+
|---|---|
|
|
13
|
+
| tmux session (`$TMUX` set) | `tmux split-window -v "revspec <file>"` + `tmux wait-for` — seamless, human reviews in a pane |
|
|
14
|
+
| No tmux | Claude Code prints "Please run: `revspec <file>`" and polls for `.review.json` changes |
|
|
15
|
+
|
|
16
|
+
## Existing infrastructure
|
|
17
|
+
|
|
18
|
+
- `~/.config/tmux/claude-hook.sh` — generic hook dispatcher
|
|
19
|
+
- `~/.config/tmux/claude-hooks.d/` — per-event hook directories
|
|
20
|
+
- `tmux-sudo-pane` skill pattern — existing precedent for spawning interactive commands in tmux
|
|
21
|
+
|
|
22
|
+
## Open questions
|
|
23
|
+
|
|
24
|
+
- Can we use Claude Code's Ctrl+G (`$EDITOR`) mechanism? It has TTY access but is designed for text editors, not arbitrary TUIs.
|
|
25
|
+
- Should we register a Claude Code hook that auto-launches revspec when a review file is created?
|
|
26
|
+
- Polling frequency and mechanism for the no-tmux fallback.
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "revspec",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"revspec": "./bin/revspec.ts"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start": "bun run bin/revspec.ts",
|
|
10
|
+
"test": "bun test"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@types/bun": "latest"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"typescript": "^5"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@opentui/core": "^0.1.87"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Usage: ./scripts/release.sh [patch|minor|major]
|
|
5
|
+
# Defaults to patch if no argument given
|
|
6
|
+
|
|
7
|
+
INCREMENT="${1:-patch}"
|
|
8
|
+
|
|
9
|
+
if [[ "$INCREMENT" != "patch" && "$INCREMENT" != "minor" && "$INCREMENT" != "major" ]]; then
|
|
10
|
+
echo "Usage: ./scripts/release.sh [patch|minor|major]"
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Ensure clean working tree
|
|
15
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
16
|
+
echo "Error: working tree is dirty. Commit or stash changes first."
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Ensure on main
|
|
21
|
+
BRANCH=$(git branch --show-current)
|
|
22
|
+
if [ "$BRANCH" != "main" ]; then
|
|
23
|
+
echo "Error: must be on main branch (currently on $BRANCH)"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Run tests
|
|
28
|
+
echo "Running tests..."
|
|
29
|
+
bun test || { echo "Tests failed. Aborting release."; exit 1; }
|
|
30
|
+
|
|
31
|
+
# Bump version
|
|
32
|
+
OLD_VERSION=$(jq -r '.version' package.json)
|
|
33
|
+
IFS='.' read -r MAJOR MINOR PATCH <<< "$OLD_VERSION"
|
|
34
|
+
|
|
35
|
+
case "$INCREMENT" in
|
|
36
|
+
patch) PATCH=$((PATCH + 1)) ;;
|
|
37
|
+
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
|
|
38
|
+
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
|
|
39
|
+
esac
|
|
40
|
+
|
|
41
|
+
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
|
|
42
|
+
|
|
43
|
+
echo ""
|
|
44
|
+
echo " $OLD_VERSION → $NEW_VERSION ($INCREMENT)"
|
|
45
|
+
echo ""
|
|
46
|
+
read -p "Proceed? [y/N] " -n 1 -r
|
|
47
|
+
echo ""
|
|
48
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
49
|
+
echo "Aborted."
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Update package.json
|
|
54
|
+
jq --arg v "$NEW_VERSION" '.version = $v' package.json > package.json.tmp && mv package.json.tmp package.json
|
|
55
|
+
|
|
56
|
+
# Also update the --version output in bin/revspec.ts
|
|
57
|
+
sed -i '' "s/revspec $OLD_VERSION/revspec $NEW_VERSION/" bin/revspec.ts 2>/dev/null || \
|
|
58
|
+
sed -i "s/revspec $OLD_VERSION/revspec $NEW_VERSION/" bin/revspec.ts
|
|
59
|
+
|
|
60
|
+
# Commit and tag
|
|
61
|
+
git add package.json bin/revspec.ts
|
|
62
|
+
git commit -m "release: v$NEW_VERSION"
|
|
63
|
+
git tag "v$NEW_VERSION"
|
|
64
|
+
|
|
65
|
+
# Publish to npm
|
|
66
|
+
echo ""
|
|
67
|
+
echo "Publishing to npm..."
|
|
68
|
+
npm publish
|
|
69
|
+
|
|
70
|
+
# Push
|
|
71
|
+
git push && git push origin "v$NEW_VERSION"
|
|
72
|
+
|
|
73
|
+
echo ""
|
|
74
|
+
echo "✔ Released v$NEW_VERSION"
|
|
75
|
+
echo " npm: https://www.npmjs.com/package/revspec"
|
|
76
|
+
echo " git: https://github.com/icyrainz/revspec/releases/tag/v$NEW_VERSION"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ReviewFile, DraftFile, Thread } from "./types";
|
|
2
|
+
|
|
3
|
+
export function mergeDraftIntoReview(
|
|
4
|
+
review: ReviewFile | null,
|
|
5
|
+
draft: DraftFile,
|
|
6
|
+
specFile: string = ""
|
|
7
|
+
): ReviewFile {
|
|
8
|
+
// Build a starting ReviewFile
|
|
9
|
+
const base: ReviewFile = review ?? { file: specFile, threads: [] };
|
|
10
|
+
|
|
11
|
+
// If draft has no threads, return review unchanged
|
|
12
|
+
const draftThreads = draft.threads;
|
|
13
|
+
if (!draftThreads || draftThreads.length === 0) {
|
|
14
|
+
return { ...base, threads: [...base.threads] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Index existing threads by id for O(1) lookup
|
|
18
|
+
const existingById = new Map<string, Thread>(
|
|
19
|
+
base.threads.map((t) => [t.id, t])
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const mergedById = new Map<string, Thread>(
|
|
23
|
+
base.threads.map((t) => [t.id, { ...t, messages: [...t.messages] }])
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
for (const draftThread of draftThreads) {
|
|
27
|
+
const existing = existingById.get(draftThread.id);
|
|
28
|
+
if (!existing) {
|
|
29
|
+
// New thread — add it wholesale
|
|
30
|
+
mergedById.set(draftThread.id, draftThread);
|
|
31
|
+
} else {
|
|
32
|
+
// Existing thread — append only new messages (draft has full history)
|
|
33
|
+
const existingCount = existing.messages.length;
|
|
34
|
+
const newMessages = draftThread.messages.slice(existingCount);
|
|
35
|
+
const merged = mergedById.get(draftThread.id)!;
|
|
36
|
+
merged.messages.push(...newMessages);
|
|
37
|
+
merged.status = draftThread.status;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Preserve original ordering of existing threads, then append new ones
|
|
42
|
+
const orderedIds = [
|
|
43
|
+
...base.threads.map((t) => t.id),
|
|
44
|
+
...draftThreads
|
|
45
|
+
.filter((t) => !existingById.has(t.id))
|
|
46
|
+
.map((t) => t.id),
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const threads = orderedIds.map((id) => mergedById.get(id)!);
|
|
50
|
+
|
|
51
|
+
return { ...base, threads };
|
|
52
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import type { ReviewFile, DraftFile } from "./types";
|
|
3
|
+
import { isValidReviewFile } from "./types";
|
|
4
|
+
|
|
5
|
+
export function readReviewFile(path: string): ReviewFile | null {
|
|
6
|
+
try {
|
|
7
|
+
const raw = readFileSync(path, "utf8");
|
|
8
|
+
const parsed: unknown = JSON.parse(raw);
|
|
9
|
+
if (!isValidReviewFile(parsed)) return null;
|
|
10
|
+
return parsed;
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function readDraftFile(path: string): DraftFile | null {
|
|
17
|
+
try {
|
|
18
|
+
const raw = readFileSync(path, "utf8");
|
|
19
|
+
const parsed: unknown = JSON.parse(raw);
|
|
20
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
21
|
+
return parsed as DraftFile;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|