revspec 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +19 -0
- package/README.md +6 -2
- package/bin/revspec.ts +39 -55
- package/docs/superpowers/plans/2026-03-14-live-ai-integration.md +1877 -0
- package/docs/superpowers/specs/2026-03-14-live-ai-integration-design.md +518 -0
- package/docs/superpowers/specs/2026-03-14-live-ai-integration-design.review.json +65 -0
- package/package.json +1 -1
- package/scripts/release.sh +12 -35
- package/src/cli/reply.ts +53 -0
- package/src/cli/watch.ts +378 -0
- package/src/protocol/live-events.ts +215 -0
- package/src/protocol/live-merge.ts +66 -0
- package/src/protocol/types.ts +2 -1
- package/src/state/review-state.ts +47 -5
- package/src/tui/app.ts +167 -71
- package/src/tui/comment-input.ts +265 -96
- package/src/tui/help.ts +4 -2
- package/src/tui/live-watcher.ts +40 -0
- package/src/tui/pager.ts +18 -8
- package/src/tui/status-bar.ts +17 -6
- package/src/tui/theme.ts +4 -4
- package/test/cli-reply.test.ts +140 -0
- package/test/cli-watch.test.ts +216 -0
- package/test/cli.test.ts +49 -40
- package/test/e2e-live.test.ts +171 -0
- package/test/live-interaction.test.ts +398 -0
- package/test/protocol/live-events.test.ts +509 -0
- package/test/protocol/live-merge.test.ts +167 -0
- package/test/protocol/merge.test.ts +5 -5
- package/test/protocol/read.test.ts +2 -2
- package/test/protocol/types.test.ts +2 -2
- package/test/protocol/write.test.ts +2 -2
- package/test/state/review-state.test.ts +88 -15
- package/test/tui/pager.test.ts +21 -46
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
# Revspec — Live AI Integration Design
|
|
2
|
+
|
|
3
|
+
Real-time human-AI conversation within the revspec TUI. Instead of batch review rounds (comment → exit → AI processes → relaunch), the human comments and the AI replies immediately — like a chatroom anchored to specific lines in the spec. The spec stays frozen during discussion; once all threads are resolved, the AI rewrites the spec and a new review round begins.
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
Revspec's current workflow is round-based: the human adds all comments, exits the TUI, the AI processes everything, rewrites the spec, and relaunches. This breaks the "immediate feedback" promise — the human waits for the AI between rounds, and the AI can't ask clarifying questions until the next round.
|
|
8
|
+
|
|
9
|
+
The selling point is that AI replies instantly. But today, the review checks multiple places, adds comments, and submits through rounds with no immediate feedback.
|
|
10
|
+
|
|
11
|
+
## Solution
|
|
12
|
+
|
|
13
|
+
Make the review session live. The human stays in revspec for the entire session. Comments trigger immediate AI responses that appear in-thread. The human resolves, continues, or moves on. When all discussions settle and the human approves, the AI rewrites the spec and launches a new round.
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────────────────────────────────────────────┐
|
|
19
|
+
│ Human in revspec TUI (terminal / tmux pane) │
|
|
20
|
+
│ │
|
|
21
|
+
│ comments ──► writes to JSONL ──► AI detects change │
|
|
22
|
+
│ │ │
|
|
23
|
+
│ sees reply ◄── reads from JSONL ◄── AI appends reply │
|
|
24
|
+
│ │
|
|
25
|
+
│ resolves threads ──► approves ──► exits │
|
|
26
|
+
└─────────────────────────────────────────────────────────┘
|
|
27
|
+
│
|
|
28
|
+
▼
|
|
29
|
+
┌─────────────────────────────────────────────────────────┐
|
|
30
|
+
│ AI coding tool (Claude Code, opencode, etc.) │
|
|
31
|
+
│ │
|
|
32
|
+
│ runs: revspec watch spec.md (blocks until comments) │
|
|
33
|
+
│ gets: new human comments (structured output) │
|
|
34
|
+
│ runs: revspec reply spec.md t1 "response text" │
|
|
35
|
+
│ runs: revspec watch spec.md (loop) │
|
|
36
|
+
│ │
|
|
37
|
+
│ on approval: rewrites spec, launches new round │
|
|
38
|
+
└─────────────────────────────────────────────────────────┘
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The two sides communicate through a shared JSONL file. Revspec owns the protocol entirely — the AI tool just runs CLI commands and follows instructions in the output.
|
|
42
|
+
|
|
43
|
+
## Live Communication Protocol (JSONL)
|
|
44
|
+
|
|
45
|
+
### File
|
|
46
|
+
|
|
47
|
+
`spec.review.live.jsonl` — sibling to the spec file. Append-only audit log. Never modified or truncated. Persists after the session ends.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
docs/specs/feature-design.md
|
|
51
|
+
docs/specs/feature-design.review.json # structured state (persists across rounds)
|
|
52
|
+
docs/specs/feature-design.review.live.jsonl # live chat log (append-only audit trail)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Event Types
|
|
56
|
+
|
|
57
|
+
```jsonl
|
|
58
|
+
{"type":"comment","threadId":"t1","line":14,"author":"reviewer","text":"this section is unclear","ts":1710400000}
|
|
59
|
+
{"type":"reply","threadId":"t1","author":"owner","text":"I can restructure around X...","ts":1710400005}
|
|
60
|
+
{"type":"resolve","threadId":"t1","author":"reviewer","ts":1710400010}
|
|
61
|
+
{"type":"unresolve","threadId":"t1","author":"reviewer","ts":1710400015}
|
|
62
|
+
{"type":"reply","threadId":"t1","author":"reviewer","text":"actually what about Y?","ts":1710400020}
|
|
63
|
+
{"type":"reply","threadId":"t1","author":"owner","text":"Y works, here's how...","ts":1710400025}
|
|
64
|
+
{"type":"resolve","threadId":"t1","author":"reviewer","ts":1710400030}
|
|
65
|
+
{"type":"comment","threadId":"t2","line":38,"author":"reviewer","text":"missing error case","ts":1710400035}
|
|
66
|
+
{"type":"reply","threadId":"t2","author":"owner","text":"good catch, I'll add...","ts":1710400040}
|
|
67
|
+
{"type":"resolve","threadId":"t2","author":"reviewer","ts":1710400045}
|
|
68
|
+
{"type":"approve","author":"reviewer","ts":1710400050}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Event Schema
|
|
72
|
+
|
|
73
|
+
| Field | Type | Required | Description |
|
|
74
|
+
|-------|------|----------|-------------|
|
|
75
|
+
| `type` | string | yes | `comment`, `reply`, `resolve`, `unresolve`, `approve`, `delete`, `round` |
|
|
76
|
+
| `threadId` | string | yes (except `approve` and `round`) | Thread identifier (e.g., `t1`) |
|
|
77
|
+
| `line` | integer | only for `comment` | 1-indexed line anchor in spec |
|
|
78
|
+
| `author` | string | yes | `reviewer` or `owner` |
|
|
79
|
+
| `text` | string | only for `comment`/`reply` | Message content |
|
|
80
|
+
| `ts` | integer | yes | `Date.now()` timestamp |
|
|
81
|
+
| `round` | integer | only for `round` | Round number (1-indexed) |
|
|
82
|
+
|
|
83
|
+
### Rules
|
|
84
|
+
|
|
85
|
+
- Append-only — neither side edits or truncates the file
|
|
86
|
+
- `comment` creates a new thread; `reply` adds to an existing one
|
|
87
|
+
- Only reviewers create threads (owners only reply)
|
|
88
|
+
- Thread IDs are auto-incremented by the TUI (`t1`, `t2`, ...). IDs are globally unique across rounds because `nextThreadId()` scans all threads from both the review JSON (prior rounds) and the replayed JSONL (current session) to find the highest existing ID.
|
|
89
|
+
- `approve` signals the session is complete
|
|
90
|
+
- Write atomicity: each event is a single `appendFileSync` call with a trailing `\n`. On read, discard any final line that does not parse as valid JSON (partial write from crash/kill).
|
|
91
|
+
|
|
92
|
+
### Change Detection
|
|
93
|
+
|
|
94
|
+
Both sides track their read position by **byte offset** (not timestamp). Each reader remembers the last byte offset it read to. On file change notification, it reads from that offset forward, parses new lines, and updates the offset.
|
|
95
|
+
|
|
96
|
+
- The `watch` CLI stores its byte offset in a state file: `spec.review.live.offset` (deleted on session end)
|
|
97
|
+
- The TUI tracks its offset in memory (lives as long as the process)
|
|
98
|
+
- Byte offset is more reliable than timestamp — no risk of two events sharing the same millisecond
|
|
99
|
+
- **Alignment safety:** after seeking to a byte offset, skip to the next `\n` boundary before parsing (discard any partial first line). This handles the case where the offset lands mid-line due to a concurrent write. The first read always starts at byte 0 so no alignment is needed.
|
|
100
|
+
|
|
101
|
+
### Concurrency Model
|
|
102
|
+
|
|
103
|
+
This is a deliberate departure from the original spec's single-writer contract. Both the TUI and the AI tool append to the same JSONL file concurrently. This is safe because:
|
|
104
|
+
|
|
105
|
+
1. Each write is a single `appendFileSync` call (atomic for small writes on local filesystems)
|
|
106
|
+
2. Each event is a complete JSON line — partial writes are detectable and discarded
|
|
107
|
+
3. Neither side edits or truncates — append-only eliminates read-modify-write races
|
|
108
|
+
4. The JSONL is a log, not a state file — order of concurrent appends does not affect correctness
|
|
109
|
+
|
|
110
|
+
### Thread Status Mapping
|
|
111
|
+
|
|
112
|
+
During a live session, thread status is derived from the JSONL event stream:
|
|
113
|
+
|
|
114
|
+
| Last event on thread | Derived status |
|
|
115
|
+
|---------------------|----------------|
|
|
116
|
+
| `comment` (reviewer) | `open` (owner's turn) |
|
|
117
|
+
| `reply` (reviewer) | `open` (owner's turn) |
|
|
118
|
+
| `reply` (owner) | `pending` (reviewer's turn) |
|
|
119
|
+
| `resolve` | `resolved` |
|
|
120
|
+
| `unresolve` (reviewer) | `open` (owner's turn — reviewer reopened the thread) |
|
|
121
|
+
| `delete` (reviewer) | status unchanged (message removed, thread state unaffected unless last message deleted — see Delete Behavior) |
|
|
122
|
+
|
|
123
|
+
The `outdated` status is not used during live sessions (the spec is frozen, no anchors move). It is set by the AI between rounds when rewriting the spec and updating anchors.
|
|
124
|
+
|
|
125
|
+
### Delete Behavior (`dd` in live mode)
|
|
126
|
+
|
|
127
|
+
In batch mode, `dd` deletes the last draft message from in-memory state. In live mode, the message has already been appended to the JSONL and may have been seen by the AI watcher.
|
|
128
|
+
|
|
129
|
+
Live mode behavior: `dd` appends a `delete` event to the JSONL:
|
|
130
|
+
|
|
131
|
+
```jsonl
|
|
132
|
+
{"type":"delete","threadId":"t1","author":"reviewer","ts":1710400060}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This signals that the last reviewer message on the thread should be treated as retracted. The merge step excludes deleted messages from the final JSON. The `watch` command, on seeing a `delete` event, includes it in its output so the owner knows to disregard the deleted comment:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
--- Deleted ---
|
|
139
|
+
[t1] line 14: reviewer retracted last message
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
If the AI has already replied to the deleted comment, the AI's reply remains in the JSONL (append-only). The human can resolve the thread to dismiss both.
|
|
143
|
+
|
|
144
|
+
If `delete` removes the only message in a thread (thread was just created), the thread is effectively empty and excluded from the merge.
|
|
145
|
+
|
|
146
|
+
### AI Reply to Resolved Thread
|
|
147
|
+
|
|
148
|
+
If the AI replies to a thread that was resolved after the comment was posted (race condition — human resolves while AI is composing), the reply is still appended to the JSONL. The thread status reverts to `pending` (the AI reply is the latest event). The TUI shows the new AI reply with an unread indicator. The human can re-resolve if the reply doesn't need further discussion.
|
|
149
|
+
|
|
150
|
+
### Merge to JSON
|
|
151
|
+
|
|
152
|
+
On session end (approve or quit), revspec replays the **entire JSONL from byte 0** and merges the final thread state into `spec.review.json`. The JSONL file is kept as an audit log.
|
|
153
|
+
|
|
154
|
+
The merge always replays from the start (not from an offset) to ensure it is idempotent and self-healing — even if an AI reply lands during the merge, the next merge will capture it.
|
|
155
|
+
|
|
156
|
+
**Merge semantics:**
|
|
157
|
+
- Existing threads in `spec.review.json` (from prior rounds) are preserved
|
|
158
|
+
- JSONL threads are merged by ID: if a thread ID exists in both, messages from the JSONL are appended to the existing thread's messages (same logic as the current `mergeDraftIntoReview`)
|
|
159
|
+
- Thread status is set to whatever the JSONL's final event derives (see Thread Status Mapping)
|
|
160
|
+
- Thread IDs use the `t`-prefix format (`t1`, `t2`, ...) — this matches the existing codebase. The original spec's examples showing plain numeric IDs (`"1"`, `"2"`) are outdated.
|
|
161
|
+
|
|
162
|
+
The merge produces the same structured `ReviewFile` format used today:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"file": "spec.md",
|
|
167
|
+
"threads": [
|
|
168
|
+
{
|
|
169
|
+
"id": "t1",
|
|
170
|
+
"line": 14,
|
|
171
|
+
"status": "resolved",
|
|
172
|
+
"messages": [
|
|
173
|
+
{ "author": "reviewer", "text": "this section is unclear", "ts": 1710400000 },
|
|
174
|
+
{ "author": "owner", "text": "I can restructure around X...", "ts": 1710400005 },
|
|
175
|
+
{ "author": "reviewer", "text": "actually what about Y?", "ts": 1710400020 },
|
|
176
|
+
{ "author": "owner", "text": "Y works, here's how...", "ts": 1710400025 }
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The `Message` type in the review JSON gains an optional `ts` field:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
interface Message {
|
|
187
|
+
author: "reviewer" | "owner"
|
|
188
|
+
text: string
|
|
189
|
+
ts?: number // Date.now(), carried over from JSONL during merge. Optional for backward compat with pre-live review files.
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Timestamps are preserved from the JSONL into the merged JSON, giving the review file a built-in history timeline without needing to consult the JSONL audit log.
|
|
194
|
+
|
|
195
|
+
### Draft File Replacement
|
|
196
|
+
|
|
197
|
+
The live JSONL replaces the draft file (`spec.review.draft.json`) entirely. In the original design, the draft was an in-progress snapshot that merged on exit. In live mode, the JSONL is the in-progress log and the merge-on-exit step replays it into the review JSON. There is no need for both.
|
|
198
|
+
|
|
199
|
+
- Old flow: TUI → draft.json → merge into review.json on exit
|
|
200
|
+
- Live flow: TUI → live.jsonl ← AI also writes here → merge into review.json on exit
|
|
201
|
+
|
|
202
|
+
If revspec is launched without an AI watcher (standalone mode), the JSONL still works — it just never receives AI replies. The merge step is identical.
|
|
203
|
+
|
|
204
|
+
## CLI Subcommands
|
|
205
|
+
|
|
206
|
+
Two new subcommands for AI tool integration. Revspec owns the protocol — the AI tool just follows instructions in the output.
|
|
207
|
+
|
|
208
|
+
### `revspec watch <file.md>`
|
|
209
|
+
|
|
210
|
+
Blocks until new human events appear in the JSONL. Returns structured output with the new comments, spec context, thread history, and instructions for how to respond.
|
|
211
|
+
|
|
212
|
+
**Full output example — new comments with context:**
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
$ revspec watch spec.md
|
|
216
|
+
|
|
217
|
+
# Blocks... human adds comments in TUI...
|
|
218
|
+
|
|
219
|
+
--- New threads ---
|
|
220
|
+
|
|
221
|
+
[t1] line 14 (new):
|
|
222
|
+
Context:
|
|
223
|
+
12: The system uses polling to check for updates
|
|
224
|
+
13: every 30 seconds. If a change is detected,
|
|
225
|
+
>14: it sends a notification via webhook.
|
|
226
|
+
15: The notification payload includes the full
|
|
227
|
+
16: resource state.
|
|
228
|
+
Comment: "this section is unclear — polling or webhook?"
|
|
229
|
+
|
|
230
|
+
[t2] line 38 (new):
|
|
231
|
+
Context:
|
|
232
|
+
36: ### Error Handling
|
|
233
|
+
37: Errors are logged to stderr.
|
|
234
|
+
>38: No retry logic is implemented.
|
|
235
|
+
39:
|
|
236
|
+
40: ### Rate Limiting
|
|
237
|
+
Comment: "missing error case — what about network timeouts?"
|
|
238
|
+
|
|
239
|
+
To reply: revspec reply spec.md <threadId> "<your response>"
|
|
240
|
+
When done replying, run: revspec watch spec.md
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Full output example — replies to existing threads:**
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
$ revspec watch spec.md
|
|
247
|
+
|
|
248
|
+
--- Replies ---
|
|
249
|
+
|
|
250
|
+
[t1] line 14 (reply):
|
|
251
|
+
Thread history:
|
|
252
|
+
reviewer: "this section is unclear — polling or webhook?"
|
|
253
|
+
owner: "Good catch. I'll clarify — it uses polling to detect changes, then sends a webhook notification."
|
|
254
|
+
reviewer: "that still doesn't explain the 30-second interval — why not event-driven?"
|
|
255
|
+
Comment: "that still doesn't explain the 30-second interval — why not event-driven?"
|
|
256
|
+
|
|
257
|
+
To reply: revspec reply spec.md <threadId> "<your response>"
|
|
258
|
+
When done replying, run: revspec watch spec.md
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Full output example — resolves (informational, no reply needed):**
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
$ revspec watch spec.md
|
|
265
|
+
|
|
266
|
+
--- Resolved ---
|
|
267
|
+
|
|
268
|
+
[t2] line 38: resolved by reviewer
|
|
269
|
+
|
|
270
|
+
--- Replies ---
|
|
271
|
+
|
|
272
|
+
[t1] line 14 (reply):
|
|
273
|
+
Thread history:
|
|
274
|
+
reviewer: "polling or webhook?"
|
|
275
|
+
owner: "It's event-driven now. Changed the spec."
|
|
276
|
+
reviewer: "perfect, but update the diagram too"
|
|
277
|
+
Comment: "perfect, but update the diagram too"
|
|
278
|
+
|
|
279
|
+
To reply: revspec reply spec.md <threadId> "<your response>"
|
|
280
|
+
When done replying, run: revspec watch spec.md
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Output on approval:**
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
$ revspec watch spec.md
|
|
287
|
+
|
|
288
|
+
Review approved.
|
|
289
|
+
Review file: docs/specs/feature-design.review.json
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Output format notes:**
|
|
293
|
+
- Context shows 2 lines above and below the anchored line (marked with `>`)
|
|
294
|
+
- Thread history shows the full conversation so the AI has context for its reply
|
|
295
|
+
- Resolved threads are listed separately — informational only, no reply expected
|
|
296
|
+
- Unresolve events are not surfaced to the AI (the next human reply will trigger a new watch return)
|
|
297
|
+
- The output is human-readable text, not JSON — AI tools parse natural language well, and this format is easier to debug
|
|
298
|
+
|
|
299
|
+
**Behavior:**
|
|
300
|
+
- Reads JSONL from last-known byte offset stored in `spec.review.live.offset` (created on first call, deleted on session end)
|
|
301
|
+
- Filters for new `author: "reviewer"` events (comments, replies, resolves)
|
|
302
|
+
- Blocks (via `fs.watch()` on the JSONL file) until new human events arrive
|
|
303
|
+
- If JSONL file does not exist yet, blocks until it appears (human has not launched revspec yet)
|
|
304
|
+
- Outputs human events in a structured, AI-readable format (human-readable text, not JSON — AI tools parse natural language well)
|
|
305
|
+
- Includes self-describing instructions so the AI knows what to do next
|
|
306
|
+
- Includes spec context around each comment (surrounding lines) and full thread history so the AI can reply without reading extra files
|
|
307
|
+
- Exits after outputting (AI re-spawns for the next batch)
|
|
308
|
+
- Falls back to polling (check file mtime every 500ms) if `fs.watch()` does not fire within 2 seconds — guards against known `fs.watch` reliability issues on macOS/Linux
|
|
309
|
+
- Detects session end by seeing an `approve` event in the JSONL — outputs "Review approved" and exits
|
|
310
|
+
|
|
311
|
+
**Single watcher enforcement:**
|
|
312
|
+
|
|
313
|
+
Only one `revspec watch` process is supported at a time. On first invocation, `watch` creates a lock file (`spec.review.live.lock`) containing the PID of the process. On subsequent invocations:
|
|
314
|
+
- If the lock file exists and the PID is alive: exit with code 3 (`"Another watch process is already running"`)
|
|
315
|
+
- If the lock file exists but the PID is dead: stale lock — delete and proceed (handles AI tool crashes)
|
|
316
|
+
|
|
317
|
+
The lock file is deleted when `watch` outputs "Review approved" (session complete). Between `watch` invocations within the same session (watch exits after outputting, AI re-spawns), the lock is **not** deleted — the new `watch` process overwrites the lock with its own PID.
|
|
318
|
+
|
|
319
|
+
**Exit codes:**
|
|
320
|
+
|
|
321
|
+
| Code | Meaning |
|
|
322
|
+
|------|---------|
|
|
323
|
+
| 0 | New comments returned, or review approved (distinguish by parsing output) |
|
|
324
|
+
| 1 | Spec file not found or invalid |
|
|
325
|
+
| 2 | JSONL file corrupted (unrecoverable) |
|
|
326
|
+
| 3 | Another watch process is already running |
|
|
327
|
+
|
|
328
|
+
### `revspec reply <file.md> <threadId> "<text>"`
|
|
329
|
+
|
|
330
|
+
Appends an AI reply event to the JSONL. The TUI picks it up via its file watcher.
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
$ revspec reply spec.md t1 "I can restructure this section around X. The key change would be..."
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Behavior:**
|
|
337
|
+
- Validates the thread ID exists in the JSONL
|
|
338
|
+
- Rejects empty text (exit 1)
|
|
339
|
+
- Appends a `{"type":"reply","threadId":"...","author":"owner","text":"...","ts":...}` line. Newlines in text are preserved via JSON string escaping (`\n`).
|
|
340
|
+
- Replying to a resolved thread is allowed — the thread reverts to `pending` (see AI Reply to Resolved Thread). No warning is printed.
|
|
341
|
+
- Exits immediately (exit 0 on success, exit 1 if thread ID not found or empty text)
|
|
342
|
+
- The TUI detects the file change and renders the AI reply
|
|
343
|
+
|
|
344
|
+
**Malformed event handling (TUI side):**
|
|
345
|
+
- Events that fail JSON parsing: discarded (partial write)
|
|
346
|
+
- Events with unknown `threadId`: ignored (logged to stderr if `REVSPEC_DEBUG=1`)
|
|
347
|
+
- Events with missing required fields: ignored
|
|
348
|
+
|
|
349
|
+
### Why This Design
|
|
350
|
+
|
|
351
|
+
The AI tool's job is trivial:
|
|
352
|
+
1. Run `revspec watch` (blocks until comments)
|
|
353
|
+
2. Read the output (structured, self-describing)
|
|
354
|
+
3. Run `revspec reply` for each comment
|
|
355
|
+
4. Run `revspec watch` again (loop)
|
|
356
|
+
|
|
357
|
+
No knowledge of JSONL, timestamps, file formats, or revspec internals needed. Any AI tool that can run shell commands works — Claude Code, opencode, Cursor, etc.
|
|
358
|
+
|
|
359
|
+
### AI Context Model
|
|
360
|
+
|
|
361
|
+
**During the loop:** the AI gets incremental context from each `watch` return. Each batch includes full thread history for active threads, so the AI has enough context to reply without reading extra files. The AI does not need to track cumulative state — `watch` provides everything needed per batch.
|
|
362
|
+
|
|
363
|
+
**On approval (spec rewrite):** the AI reads `spec.review.json` (the merged final state) for a full snapshot of all threads and their resolutions. This is the authoritative source for what feedback to incorporate into the spec rewrite.
|
|
364
|
+
|
|
365
|
+
## Mode Detection and Startup
|
|
366
|
+
|
|
367
|
+
There is no separate "live mode" flag. The TUI always writes to the JSONL and always watches it for AI replies. If no AI is watching, the JSONL just accumulates human events and no AI replies arrive — this is functionally equivalent to the old batch mode.
|
|
368
|
+
|
|
369
|
+
### Startup sequence
|
|
370
|
+
|
|
371
|
+
1. TUI creates `spec.review.live.jsonl` if it does not exist
|
|
372
|
+
2. If `spec.review.json` exists (prior round), load existing threads into `ReviewState`
|
|
373
|
+
3. If `spec.review.live.jsonl` already has content (crashed previous session or continuation), replay events from byte 0 to reconstruct thread state, merging with any threads loaded from the JSON. This handles crash recovery — the JSONL is the in-progress log.
|
|
374
|
+
4. Start `fs.watch()` on the JSONL file for incoming AI replies
|
|
375
|
+
5. The draft file (`spec.review.draft.json`) is not read or written in live mode. If one exists from a pre-live version of revspec, it is ignored (the user can delete it manually).
|
|
376
|
+
|
|
377
|
+
### Session boundaries
|
|
378
|
+
|
|
379
|
+
- A **session** starts when the TUI creates or opens the JSONL file
|
|
380
|
+
- A **session** ends when the TUI merges the JSONL into the JSON (on `:q` or `a`)
|
|
381
|
+
- Between sessions (AI is rewriting the spec), the JSONL persists as an audit log
|
|
382
|
+
- When the AI launches a new round, a `{"type":"round","round":2,"ts":...}` marker is appended to the JSONL before the TUI starts. This separates rounds in the audit log. Line anchors in events from prior rounds refer to the previous version of the spec — the JSON (not the JSONL) is the source of truth for current thread state.
|
|
383
|
+
- On `:q!` (quit without merging), the JSONL persists. If the human relaunches revspec, the JSONL is replayed from byte 0 to restore thread state (crash recovery).
|
|
384
|
+
|
|
385
|
+
### Spec file mutation guard
|
|
386
|
+
|
|
387
|
+
The spec file is frozen during a live session. Revspec records the spec file's mtime on startup. If the mtime changes during the session (someone edited the file), the TUI shows a warning in the status bar: `"Spec file changed externally — line anchors may be stale"`. It does not exit or block — the human decides what to do.
|
|
388
|
+
|
|
389
|
+
## TUI Changes
|
|
390
|
+
|
|
391
|
+
### File Watcher for AI Replies
|
|
392
|
+
|
|
393
|
+
The TUI watches `spec.review.live.jsonl` via `fs.watch()`. On change:
|
|
394
|
+
1. Read new lines from last byte offset
|
|
395
|
+
2. Filter for `author: "owner"` events
|
|
396
|
+
3. Update thread state in `ReviewState` (add message, update indicators)
|
|
397
|
+
4. Re-render affected components
|
|
398
|
+
|
|
399
|
+
### Thread Indicators
|
|
400
|
+
|
|
401
|
+
New "unread AI reply" state for threads:
|
|
402
|
+
|
|
403
|
+
| Indicator | Meaning |
|
|
404
|
+
|-----------|---------|
|
|
405
|
+
| `[*]` or distinct color | Thread has an unread AI reply |
|
|
406
|
+
| Standard indicators | Existing open/pending/resolved/outdated states |
|
|
407
|
+
|
|
408
|
+
Viewing a thread (pressing `c` on its line) clears the "unread" state.
|
|
409
|
+
|
|
410
|
+
### Timestamp Display
|
|
411
|
+
|
|
412
|
+
Thread popups (comment input / thread view) render the `ts` field as ISO time (e.g., `2026-03-14 15:30:05`) next to each message. Messages without a `ts` (pre-live) show no timestamp. The raw `Date.now()` integer is never shown to the user.
|
|
413
|
+
|
|
414
|
+
### Status Bar
|
|
415
|
+
|
|
416
|
+
The status bar shows new AI reply count:
|
|
417
|
+
|
|
418
|
+
```
|
|
419
|
+
Threads: 1 open, 2 resolved | 2 new AI replies
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
The count decreases as the human views threads.
|
|
423
|
+
|
|
424
|
+
### Navigation
|
|
425
|
+
|
|
426
|
+
New keybinding `]r`/`[r` to jump to next/prev thread with unread AI replies. Existing `]t`/`[t` (next/prev active thread) and `n`/`N` (search result cycling) are unchanged.
|
|
427
|
+
|
|
428
|
+
### Quit Semantics in Live Mode
|
|
429
|
+
|
|
430
|
+
In the original batch mode, `:q` submits comments (they weren't live until then). In live mode, comments are already live via the JSONL — there is nothing to "submit."
|
|
431
|
+
|
|
432
|
+
| Command | Live mode behavior |
|
|
433
|
+
|---------|-------------------|
|
|
434
|
+
| `:q` | Merge JSONL → JSON (full replay from byte 0), exit. Warn if unresolved threads exist. Note: an AI reply that lands after the merge completes but before the process exits will be in the JSONL but not the JSON — the next session's startup replay will capture it. |
|
|
435
|
+
| `:wq` | Same as `:q` (no separate "save" step — JSONL is already persisted) |
|
|
436
|
+
| `:q!` | Exit without merging JSONL → JSON. JSONL is preserved as audit log. |
|
|
437
|
+
| `a` (approve) | Append `approve` event to JSONL, merge JSONL → JSON, exit. Requires all threads resolved/outdated. |
|
|
438
|
+
|
|
439
|
+
### No Changes To
|
|
440
|
+
|
|
441
|
+
- Comment input (`c` flow — same)
|
|
442
|
+
- Resolve/unresolve (`r` flow — same)
|
|
443
|
+
- Search (`/`, `n`/`N` — same)
|
|
444
|
+
- Help, theme
|
|
445
|
+
|
|
446
|
+
## Launch Strategy
|
|
447
|
+
|
|
448
|
+
### With tmux (`$TMUX` set)
|
|
449
|
+
|
|
450
|
+
The AI tool splits a tmux pane and launches revspec automatically:
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
tmux split-window -v "revspec spec.md"
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Human reviews in one pane, AI runs in the other.
|
|
457
|
+
|
|
458
|
+
### Without tmux
|
|
459
|
+
|
|
460
|
+
The AI tool instructs the human:
|
|
461
|
+
|
|
462
|
+
```
|
|
463
|
+
Please run in another terminal: revspec spec.md
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Then starts `revspec watch spec.md`, which blocks until the JSONL file appears (signals revspec is running).
|
|
467
|
+
|
|
468
|
+
### Both converge to the same watch/reply loop.
|
|
469
|
+
|
|
470
|
+
## Full Lifecycle
|
|
471
|
+
|
|
472
|
+
```
|
|
473
|
+
1. AI generates/updates spec → spec.md
|
|
474
|
+
2. AI launches revspec (tmux pane or manual instruction)
|
|
475
|
+
3. AI runs: revspec watch spec.md (blocks)
|
|
476
|
+
4. Human reads spec, comments on lines
|
|
477
|
+
5. revspec watch returns with new comments
|
|
478
|
+
6. AI runs: revspec reply spec.md t1 "response..."
|
|
479
|
+
7. AI runs: revspec reply spec.md t2 "response..."
|
|
480
|
+
8. TUI shows AI replies (thread indicators + status bar)
|
|
481
|
+
9. AI runs: revspec watch spec.md (blocks again)
|
|
482
|
+
10. Human reads AI replies, resolves or continues discussion
|
|
483
|
+
11. Repeat steps 5-10 until all threads resolved
|
|
484
|
+
12. Human approves (a) → revspec watch returns "Review approved"
|
|
485
|
+
13. AI reads spec.review.json (merged from JSONL)
|
|
486
|
+
14. AI rewrites spec incorporating all feedback
|
|
487
|
+
15. AI launches new revspec session with updated spec
|
|
488
|
+
16. Repeat from step 3 until clean approval (no comments)
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Tool-Agnostic Design
|
|
492
|
+
|
|
493
|
+
Revspec is deliberately decoupled from any specific AI coding tool:
|
|
494
|
+
|
|
495
|
+
| Layer | Tool-specific? |
|
|
496
|
+
|-------|---------------|
|
|
497
|
+
| JSONL protocol | No — generic file format |
|
|
498
|
+
| `revspec watch` / `revspec reply` CLI | No — any tool that runs shell commands |
|
|
499
|
+
| TUI file watcher + hot reload | No — watches the JSONL, doesn't care who writes to it |
|
|
500
|
+
| Orchestration (launch, loop, spec rewrite) | Yes — each AI tool needs its own wrapper |
|
|
501
|
+
|
|
502
|
+
For Claude Code, the orchestration is a `/review` skill. Other tools would implement their own equivalent. The revspec side is identical regardless.
|
|
503
|
+
|
|
504
|
+
## Backward Compatibility
|
|
505
|
+
|
|
506
|
+
### Standalone Mode
|
|
507
|
+
|
|
508
|
+
If revspec is launched without an AI watcher (`revspec spec.md` with no `revspec watch` running), the JSONL is still written on every human action. It just never receives AI replies. The merge step on exit works identically. This means the old batch workflow still works — the human comments, exits, and the AI processes the review JSON as before.
|
|
509
|
+
|
|
510
|
+
### Existing Review Files
|
|
511
|
+
|
|
512
|
+
The `Message` type changes `author` values from `"human" | "ai"` to `"reviewer" | "owner"`. Existing `spec.review.json` files using the old values will need a one-time migration (find-and-replace in the JSON). The `ts` field is optional — existing messages without timestamps continue to work; new messages from live sessions include them.
|
|
513
|
+
|
|
514
|
+
## Open Questions
|
|
515
|
+
|
|
516
|
+
- **Bash timeout on `revspec watch`:** Claude Code's background Bash has a max 10-minute timeout. If the human takes longer, the watcher times out. The orchestration skill should re-spawn on timeout.
|
|
517
|
+
- **Concurrent replies:** If the human adds 3 comments quickly, the `watch` command batches all new events and returns them together. The AI can reply to all at once.
|
|
518
|
+
- **JSONL across rounds:** Each review round appends to the same JSONL file. The audit log shows the full history across all rounds. A `{"type":"round","round":N,"ts":...}` marker is appended by the orchestration layer before each new round (see Session Boundaries).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "/Users/tuephan/repo/revspec/docs/superpowers/specs/2026-03-14-live-ai-integration-design.md",
|
|
3
|
+
"threads": [
|
|
4
|
+
{
|
|
5
|
+
"id": "t1",
|
|
6
|
+
"line": 95,
|
|
7
|
+
"status": "pending",
|
|
8
|
+
"messages": [
|
|
9
|
+
{
|
|
10
|
+
"author": "human",
|
|
11
|
+
"text": "would this cause parsing issue if the byte is not aligned to the text"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"author": "ai",
|
|
15
|
+
"text": "Good catch. Added alignment safety: after seeking to a byte offset, skip to the next \\n boundary before parsing (discard any partial first line). First read always starts at byte 0 so no alignment needed there."
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "t2",
|
|
21
|
+
"line": 188,
|
|
22
|
+
"status": "pending",
|
|
23
|
+
"messages": [
|
|
24
|
+
{
|
|
25
|
+
"author": "human",
|
|
26
|
+
"text": "this is actually not needed, the ts is not required if not needed, there is no ts in pre-live review files"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"author": "ai",
|
|
30
|
+
"text": "Agreed. Removed ts from the Message type — timestamps only exist in JSONL events, not in the merged review JSON. Cleaner separation."
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "t3",
|
|
36
|
+
"line": 249,
|
|
37
|
+
"status": "pending",
|
|
38
|
+
"messages": [
|
|
39
|
+
{
|
|
40
|
+
"author": "human",
|
|
41
|
+
"text": "let's change this, not human and ai, just reviewer and owner"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"author": "ai",
|
|
45
|
+
"text": "Done. Changed author values throughout the spec from human/ai to reviewer/owner. More generic and decoupled — works whether it's a human reviewing AI work or any other combination."
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "t4",
|
|
51
|
+
"line": 346,
|
|
52
|
+
"status": "pending",
|
|
53
|
+
"messages": [
|
|
54
|
+
{
|
|
55
|
+
"author": "human",
|
|
56
|
+
"text": "at the end of the loop, does AI got a new snapshot of everything or it already knows all the incremental changes?"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"author": "ai",
|
|
60
|
+
"text": "Added an 'AI Context Model' section: during the loop, the AI gets incremental context from each watch return (full thread history per batch). On approval, the AI reads spec.review.json for the full snapshot to use when rewriting the spec."
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
package/package.json
CHANGED
package/scripts/release.sh
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -e
|
|
3
3
|
|
|
4
|
-
# Usage: ./scripts/release.sh
|
|
5
|
-
#
|
|
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
|
|
4
|
+
# Usage: ./scripts/release.sh
|
|
5
|
+
# Publishes the version already set in package.json.
|
|
6
|
+
# Bump the version manually in package.json before running this.
|
|
13
7
|
|
|
14
8
|
# Ensure clean working tree
|
|
15
9
|
if [ -n "$(git status --porcelain)" ]; then
|
|
@@ -28,20 +22,10 @@ fi
|
|
|
28
22
|
echo "Running tests..."
|
|
29
23
|
bun test || { echo "Tests failed. Aborting release."; exit 1; }
|
|
30
24
|
|
|
31
|
-
|
|
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"
|
|
25
|
+
VERSION=$(jq -r '.version' package.json)
|
|
42
26
|
|
|
43
27
|
echo ""
|
|
44
|
-
echo "
|
|
28
|
+
echo " Publishing v$VERSION"
|
|
45
29
|
echo ""
|
|
46
30
|
read -p "Proceed? [y/N] " -n 1 -r
|
|
47
31
|
echo ""
|
|
@@ -50,17 +34,10 @@ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
|
50
34
|
exit 0
|
|
51
35
|
fi
|
|
52
36
|
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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"
|
|
37
|
+
# Tag if not already tagged
|
|
38
|
+
if ! git tag -l "v$VERSION" | grep -q .; then
|
|
39
|
+
git tag "v$VERSION"
|
|
40
|
+
fi
|
|
64
41
|
|
|
65
42
|
# Publish to npm
|
|
66
43
|
echo ""
|
|
@@ -68,9 +45,9 @@ echo "Publishing to npm..."
|
|
|
68
45
|
npm publish
|
|
69
46
|
|
|
70
47
|
# Push
|
|
71
|
-
git push && git push origin "v$
|
|
48
|
+
git push && git push origin "v$VERSION"
|
|
72
49
|
|
|
73
50
|
echo ""
|
|
74
|
-
echo "
|
|
51
|
+
echo "Released v$VERSION"
|
|
75
52
|
echo " npm: https://www.npmjs.com/package/revspec"
|
|
76
|
-
echo " git: https://github.com/icyrainz/revspec/releases/tag/v$
|
|
53
|
+
echo " git: https://github.com/icyrainz/revspec/releases/tag/v$VERSION"
|