revspec 0.2.2 → 0.3.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 +10 -2
- package/README.md +86 -29
- package/bin/revspec.ts +2 -1
- package/package.json +1 -1
- package/scripts/install-skill.sh +20 -0
- package/scripts/release.sh +5 -6
- package/skills/revspec/SKILL.md +137 -0
- package/src/protocol/live-events.ts +3 -2
- package/src/tui/app.ts +51 -111
- package/src/tui/help.ts +5 -5
- package/src/tui/pager.ts +394 -81
- package/src/tui/status-bar.ts +85 -33
package/CLAUDE.md
CHANGED
|
@@ -4,16 +4,24 @@
|
|
|
4
4
|
- npm: `revspec` | GitHub: icyrainz/revspec
|
|
5
5
|
- Run: `bun run bin/revspec.ts <file.md>`
|
|
6
6
|
- Test: `bun test`
|
|
7
|
-
- Release: `./scripts/release.sh
|
|
7
|
+
- Release: `./scripts/release.sh` (version is set manually in package.json)
|
|
8
|
+
- Dev: `bun link` to symlink local build to global `revspec` command
|
|
8
9
|
|
|
9
10
|
## OpenTUI Gotchas
|
|
10
|
-
- Don't use StyledText
|
|
11
|
+
- Don't use StyledText at all — BigInt FFI crash happens even on small content
|
|
11
12
|
- Don't use ANSI escape codes in TextRenderable content (renders as literal text)
|
|
12
13
|
- MarkdownRenderable needs `syntaxStyle` + `conceal: true` for proper rendering
|
|
13
14
|
- Use `visible: false` to hide renderables, not removal/re-addition
|
|
15
|
+
- ScrollBox: don't use `stickyScroll` with manual scrolling (fights scroll position)
|
|
16
|
+
- ScrollBox: `scrollBy` overshoots silently on large deltas — use `scrollTo` with clamped position
|
|
17
|
+
- Textarea consumes Ctrl+D/U (emacs bindings) — blur textarea in normal mode for vim-style scroll
|
|
14
18
|
|
|
15
19
|
## Conventions
|
|
16
20
|
- Line mode is default, markdown mode via `m` toggle
|
|
17
21
|
- Tab to submit in all text inputs (works through tmux)
|
|
18
22
|
- Destructive actions need confirmation (dd double-tap, approve confirm dialog)
|
|
19
23
|
- All review actions auto-switch to line mode
|
|
24
|
+
- Thread popup uses vim-style normal/insert modes (blur textarea in normal)
|
|
25
|
+
- Hint bars use `[key] action` bracket format consistently
|
|
26
|
+
- No inline comment previews in pager — gutter indicators only (▌/█/✓)
|
|
27
|
+
- Live integration: JSONL for communication, `revspec watch`/`reply` CLI subcommands
|
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Revspec
|
|
2
2
|
|
|
3
|
-
A review tool for AI-generated spec documents
|
|
3
|
+
A review tool for AI-generated spec documents with real-time AI conversation. Comment on specific lines, get AI replies instantly, resolve discussions, and approve — all without leaving the terminal.
|
|
4
4
|
|
|
5
5
|
## Why
|
|
6
6
|
|
|
7
|
-
When an AI generates a spec, the human review step breaks the agentic loop. You have to open the file separately, read it, then type unstructured feedback
|
|
7
|
+
When an AI generates a spec, the human review step breaks the agentic loop. You have to open the file separately, read it, then type unstructured feedback. Revspec closes this loop with a TUI that lets you comment inline and discuss with the AI in real-time — like a chatroom anchored to the spec.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -27,67 +27,124 @@ cd revspec && bun install && bun link
|
|
|
27
27
|
revspec spec.md
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
Opens a TUI with
|
|
31
|
-
|
|
32
|
-
- **Markdown mode** (default) — rendered markdown for reading. `j/k` scrolls.
|
|
33
|
-
- **Line mode** (`m`) — line numbers + thread indicators for commenting.
|
|
30
|
+
Opens a TUI in line mode with vim-style navigation. Press `c` on any line to open a thread and start commenting.
|
|
34
31
|
|
|
35
32
|
### Keybindings
|
|
36
33
|
|
|
37
34
|
| Key | Action |
|
|
38
35
|
|-----|--------|
|
|
39
|
-
| `j/k` |
|
|
36
|
+
| `j/k` | Move cursor down/up |
|
|
40
37
|
| `gg` / `G` | Go to top / bottom |
|
|
41
38
|
| `Ctrl+D/U` | Half page down/up |
|
|
42
39
|
| `m` | Toggle markdown / line mode |
|
|
43
|
-
| `c` |
|
|
40
|
+
| `c` | Open thread / comment on line |
|
|
44
41
|
| `r` | Resolve thread (toggle) |
|
|
45
42
|
| `R` | Resolve all pending |
|
|
46
43
|
| `dd` | Delete draft comment (double-tap) |
|
|
47
44
|
| `/` | Search |
|
|
48
45
|
| `n/N` | Next/prev search match |
|
|
49
46
|
| `]t/[t` | Next/prev thread |
|
|
47
|
+
| `]r/[r` | Next/prev unread AI reply |
|
|
50
48
|
| `l` | List threads |
|
|
51
49
|
| `a` | Approve spec |
|
|
52
|
-
| `:w` |
|
|
53
|
-
| `:
|
|
54
|
-
| `:
|
|
55
|
-
| `:q!` | Quit without
|
|
50
|
+
| `:w` | Merge changes to review JSON |
|
|
51
|
+
| `:wq` | Merge and quit |
|
|
52
|
+
| `:q` | Quit (only if merged) |
|
|
53
|
+
| `:q!` | Quit without merging |
|
|
56
54
|
| `?` | Help |
|
|
57
55
|
|
|
58
|
-
###
|
|
56
|
+
### Thread popup
|
|
57
|
+
|
|
58
|
+
The thread popup has two modes:
|
|
59
|
+
|
|
60
|
+
- **Insert mode** — type your comment, `Tab` sends, `Esc` switches to normal mode
|
|
61
|
+
- **Normal mode** — `j/k` and `Ctrl+D/U` scroll the conversation history, `c` to reply, `r` to resolve, `Esc` to close
|
|
62
|
+
|
|
63
|
+
## Live AI Integration
|
|
64
|
+
|
|
65
|
+
Revspec supports real-time communication with AI coding tools (Claude Code, opencode, etc.) via two CLI subcommands:
|
|
66
|
+
|
|
67
|
+
### `revspec watch <file.md>`
|
|
68
|
+
|
|
69
|
+
Blocks until the reviewer adds comments, then returns them with spec context:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
=== New Comments ===
|
|
73
|
+
Thread: t1 (line 14)
|
|
74
|
+
Context:
|
|
75
|
+
12: The system uses polling...
|
|
76
|
+
> 14: it sends a notification via webhook.
|
|
77
|
+
16: resource state.
|
|
78
|
+
[reviewer]: this is unclear
|
|
79
|
+
|
|
80
|
+
To reply: revspec reply spec.md t1 "<your response>"
|
|
81
|
+
When done replying, run: revspec watch spec.md
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `revspec reply <file.md> <threadId> "<text>"`
|
|
85
|
+
|
|
86
|
+
Sends an AI reply that appears instantly in the reviewer's TUI:
|
|
59
87
|
|
|
60
|
-
|
|
88
|
+
```bash
|
|
89
|
+
revspec reply spec.md t1 "Good point. I'll clarify the polling vs webhook distinction."
|
|
90
|
+
```
|
|
61
91
|
|
|
62
|
-
|
|
92
|
+
### The loop
|
|
63
93
|
|
|
64
|
-
|
|
94
|
+
```
|
|
95
|
+
1. AI generates spec
|
|
96
|
+
2. AI launches: revspec spec.md (in tmux pane or separate terminal)
|
|
97
|
+
3. AI runs: revspec watch spec.md (blocks)
|
|
98
|
+
4. Reviewer comments on lines in the TUI
|
|
99
|
+
5. Watch returns with comments → AI replies → watch again
|
|
100
|
+
6. Reviewer resolves threads → approves
|
|
101
|
+
7. AI reads review JSON, rewrites spec, launches new round
|
|
102
|
+
8. Repeat until clean approval
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Claude Code skill
|
|
106
|
+
|
|
107
|
+
Install the `/revspec` skill for Claude Code:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
./scripts/install-skill.sh
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Then use `/revspec` in Claude Code after generating a spec.
|
|
114
|
+
|
|
115
|
+
## Protocol
|
|
116
|
+
|
|
117
|
+
Communication happens through a JSONL file (`spec.review.live.jsonl`) — append-only, both sides write to it. On session end, events are merged into `spec.review.json`.
|
|
118
|
+
|
|
119
|
+
### Event types
|
|
120
|
+
|
|
121
|
+
```jsonl
|
|
122
|
+
{"type":"comment","threadId":"t1","line":14,"author":"reviewer","text":"unclear","ts":1710400000}
|
|
123
|
+
{"type":"reply","threadId":"t1","author":"owner","text":"I'll fix it","ts":1710400005}
|
|
124
|
+
{"type":"resolve","threadId":"t1","author":"reviewer","ts":1710400010}
|
|
125
|
+
{"type":"approve","author":"reviewer","ts":1710400050}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Review JSON
|
|
65
129
|
|
|
66
130
|
```json
|
|
67
131
|
{
|
|
68
132
|
"file": "spec.md",
|
|
69
133
|
"threads": [
|
|
70
134
|
{
|
|
71
|
-
"id": "
|
|
72
|
-
"line":
|
|
73
|
-
"status": "
|
|
135
|
+
"id": "t1",
|
|
136
|
+
"line": 14,
|
|
137
|
+
"status": "resolved",
|
|
74
138
|
"messages": [
|
|
75
|
-
{ "author": "
|
|
139
|
+
{ "author": "reviewer", "text": "this is unclear", "ts": 1710400000 },
|
|
140
|
+
{ "author": "owner", "text": "I'll restructure this section", "ts": 1710400005 }
|
|
76
141
|
]
|
|
77
142
|
}
|
|
78
143
|
]
|
|
79
144
|
}
|
|
80
145
|
```
|
|
81
146
|
|
|
82
|
-
Thread statuses: `open` (
|
|
83
|
-
|
|
84
|
-
The AI reads this JSON, addresses comments, updates the spec, rewrites the review file with updated anchors/statuses, and re-invokes Revspec. The loop continues until the human approves.
|
|
85
|
-
|
|
86
|
-
## Roadmap
|
|
87
|
-
|
|
88
|
-
- **v1** (current): Built-in TUI pager
|
|
89
|
-
- **v2**: Neovim plugin, web UI, diff highlighting
|
|
90
|
-
- **v3**: Google Docs integration
|
|
147
|
+
Thread statuses: `open` (owner's turn), `pending` (reviewer's turn), `resolved`, `outdated`.
|
|
91
148
|
|
|
92
149
|
## License
|
|
93
150
|
|
package/bin/revspec.ts
CHANGED
|
@@ -66,7 +66,8 @@ const draftPath = join(specDir, `${specBase}.review.draft.json`);
|
|
|
66
66
|
|
|
67
67
|
// 3. Launch TUI (skip if REVSPEC_SKIP_TUI=1)
|
|
68
68
|
if (process.env.REVSPEC_SKIP_TUI !== "1") {
|
|
69
|
-
await
|
|
69
|
+
const pkg = await Bun.file(new URL("../package.json", import.meta.url)).json();
|
|
70
|
+
await runTui(specPath, reviewPath, draftPath, pkg.version);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
// 4. After TUI exits, check if approved via JSONL
|
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Install/sync the revspec Claude Code skill to ~/.claude/skills/revspec/
|
|
5
|
+
# Run this after cloning the repo or pulling updates.
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
REPO_SKILL="$SCRIPT_DIR/../skills/revspec"
|
|
9
|
+
LOCAL_SKILL="$HOME/.claude/skills/revspec"
|
|
10
|
+
|
|
11
|
+
if [ ! -f "$REPO_SKILL/SKILL.md" ]; then
|
|
12
|
+
echo "Error: skill not found at $REPO_SKILL/SKILL.md"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
mkdir -p "$LOCAL_SKILL"
|
|
17
|
+
cp "$REPO_SKILL/SKILL.md" "$LOCAL_SKILL/SKILL.md"
|
|
18
|
+
|
|
19
|
+
echo "Installed revspec skill to $LOCAL_SKILL"
|
|
20
|
+
echo "Use /revspec in Claude Code to launch spec reviews."
|
package/scripts/release.sh
CHANGED
|
@@ -34,18 +34,17 @@ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
|
34
34
|
exit 0
|
|
35
35
|
fi
|
|
36
36
|
|
|
37
|
-
# Tag if
|
|
38
|
-
|
|
39
|
-
git tag "v$VERSION"
|
|
40
|
-
fi
|
|
37
|
+
# Tag current commit (force-update if tag exists)
|
|
38
|
+
git tag -f "v$VERSION"
|
|
41
39
|
|
|
42
40
|
# Publish to npm
|
|
43
41
|
echo ""
|
|
44
42
|
echo "Publishing to npm..."
|
|
45
43
|
npm publish
|
|
46
44
|
|
|
47
|
-
# Push
|
|
48
|
-
git push
|
|
45
|
+
# Push commit and tag (force-update remote tag if it exists)
|
|
46
|
+
git push
|
|
47
|
+
git push origin "v$VERSION" --force
|
|
49
48
|
|
|
50
49
|
echo ""
|
|
51
50
|
echo "Released v$VERSION"
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: revspec
|
|
3
|
+
description: Launch revspec to review a spec document with real-time AI feedback. Use when the user says /revspec, "review the spec", "let me review this", or after generating a spec/design document that needs human review. Also use when a brainstorming or writing-plans skill produces a markdown spec file.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Revspec — Live Spec Review
|
|
7
|
+
|
|
8
|
+
Launch revspec to let the human review a spec document with real-time AI conversation. The reviewer comments on specific lines, you reply instantly, and the discussion continues until the spec is approved.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- After writing or updating a spec/design document
|
|
13
|
+
- When the user explicitly asks to review a spec
|
|
14
|
+
- After the brainstorming or writing-plans skill produces a `.md` file
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
You and the human communicate through revspec's CLI:
|
|
19
|
+
- `revspec watch <file.md>` — blocks until the reviewer adds comments, then returns them
|
|
20
|
+
- `revspec reply <file.md> <threadId> "<text>"` — sends your reply (appears in the TUI instantly)
|
|
21
|
+
|
|
22
|
+
The reviewer stays in the revspec TUI for the entire session. You run the watch/reply loop.
|
|
23
|
+
|
|
24
|
+
## Step 1: Find the Spec File
|
|
25
|
+
|
|
26
|
+
Detect which spec was recently created or modified in this conversation. Look for:
|
|
27
|
+
- Files written to `docs/superpowers/specs/*.md`
|
|
28
|
+
- The last `.md` file you created or edited
|
|
29
|
+
- If ambiguous, ask the user which file to review
|
|
30
|
+
|
|
31
|
+
## Step 2: Launch Revspec
|
|
32
|
+
|
|
33
|
+
Check if running inside tmux:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
echo $TMUX
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**If tmux is available:**
|
|
40
|
+
```bash
|
|
41
|
+
tmux split-window -v "revspec <spec-file>"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**If no tmux:**
|
|
45
|
+
Tell the user: "Please run in another terminal: `revspec <spec-file>`"
|
|
46
|
+
|
|
47
|
+
## Step 3: Run the Watch/Reply Loop
|
|
48
|
+
|
|
49
|
+
Start watching for reviewer comments:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
revspec watch <spec-file>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This blocks until the reviewer adds comments. When it returns, you'll see output like:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
--- New threads ---
|
|
59
|
+
|
|
60
|
+
[t1] line 14 (new):
|
|
61
|
+
Context:
|
|
62
|
+
12: The system uses polling...
|
|
63
|
+
>14: it sends a notification via webhook.
|
|
64
|
+
16: resource state.
|
|
65
|
+
Comment: "this is unclear"
|
|
66
|
+
|
|
67
|
+
To reply: revspec reply spec.md <threadId> "<your response>"
|
|
68
|
+
When done replying, run: revspec watch spec.md
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**For each comment:** Read the context, understand the concern, and reply thoughtfully:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
revspec reply <spec-file> t1 "Good point. I'll clarify — it uses polling to detect changes, then sends a webhook notification to downstream services."
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
After replying to all comments, run `revspec watch` again to wait for the next batch.
|
|
78
|
+
|
|
79
|
+
**If watch returns "Session ended. Reviewer exited revspec."** — the reviewer closed the TUI. Check the review JSON for resolved threads that require spec changes (see Step 4). If no resolved threads, stop and wait — the user can invoke `/revspec` again later to resume.
|
|
80
|
+
|
|
81
|
+
**Important:** Your replies should be substantive — address the concern, explain your reasoning, or acknowledge the change you'll make. Don't just say "noted" or "will fix."
|
|
82
|
+
|
|
83
|
+
## Step 4: Handle Session End or Approval
|
|
84
|
+
|
|
85
|
+
The watch loop ends in one of two ways:
|
|
86
|
+
|
|
87
|
+
### Session ended (reviewer exited with `:q`)
|
|
88
|
+
|
|
89
|
+
Read the review JSON and check for **resolved threads**. Resolved = the reviewer acknowledged your reply and wants you to make that change.
|
|
90
|
+
|
|
91
|
+
**If resolved threads with actionable feedback exist:**
|
|
92
|
+
1. Rewrite the spec incorporating the feedback from resolved threads
|
|
93
|
+
2. Commit the updated spec
|
|
94
|
+
3. Append a round marker to the JSONL:
|
|
95
|
+
```bash
|
|
96
|
+
echo '{"type":"round","author":"owner","round":2,"ts":'$(date +%s000)'}' >> <spec-file>.review.live.jsonl
|
|
97
|
+
```
|
|
98
|
+
4. Launch a new revspec session (go back to Step 2) so the reviewer can verify the changes
|
|
99
|
+
|
|
100
|
+
**If no resolved threads (or only open/pending threads):**
|
|
101
|
+
The reviewer left without resolving anything — stop and wait. The user can invoke `/revspec` again later.
|
|
102
|
+
|
|
103
|
+
### Approved (reviewer pressed `a`)
|
|
104
|
+
|
|
105
|
+
Watch returns:
|
|
106
|
+
```
|
|
107
|
+
Review approved.
|
|
108
|
+
Review file: <path-to-review.json>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The spec is finalized. Report: "Spec approved and finalized at `<spec-file>`. Ready to proceed with implementation."
|
|
112
|
+
|
|
113
|
+
## Thread Status Meanings
|
|
114
|
+
|
|
115
|
+
- **Open** — under discussion, AI replied, reviewer hasn't responded yet
|
|
116
|
+
- **Pending** — owner replied, waiting for reviewer to read
|
|
117
|
+
- **Resolved** — reviewer acknowledged the plan, AI should make the change
|
|
118
|
+
- **Approve** — spec is final, proceed to implementation
|
|
119
|
+
|
|
120
|
+
## Loop Summary
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
1. Launch revspec on spec file
|
|
124
|
+
2. Watch for comments
|
|
125
|
+
3. Reply to each comment
|
|
126
|
+
4. Watch again (repeat 2-3 until session-end or approval)
|
|
127
|
+
5. On session-end: check for resolved threads
|
|
128
|
+
6. If resolved threads need spec changes: rewrite spec, launch new round (go to 1)
|
|
129
|
+
7. On approval: spec is finalized, done
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Tips
|
|
133
|
+
|
|
134
|
+
- Read the full thread history in the watch output before replying — the reviewer may have added context across multiple messages
|
|
135
|
+
- When rewriting the spec after approval, address every resolved thread — the reviewer trusted you to incorporate their feedback
|
|
136
|
+
- Keep replies concise but complete — the reviewer can see them instantly and will follow up if needed
|
|
137
|
+
- If a comment is about a section you're unsure about, say so honestly — "I'm not sure about the best approach here. Options are X or Y — which do you prefer?"
|
|
@@ -29,6 +29,7 @@ const VALID_LIVE_EVENT_TYPES: readonly LiveEventType[] = [
|
|
|
29
29
|
"approve",
|
|
30
30
|
"delete",
|
|
31
31
|
"round",
|
|
32
|
+
"session-end",
|
|
32
33
|
];
|
|
33
34
|
|
|
34
35
|
export function isValidLiveEvent(value: unknown): value is LiveEvent {
|
|
@@ -47,8 +48,8 @@ export function isValidLiveEvent(value: unknown): value is LiveEvent {
|
|
|
47
48
|
if (typeof v.ts !== "number") return false;
|
|
48
49
|
if (typeof v.author !== "string") return false;
|
|
49
50
|
|
|
50
|
-
// threadId required for all except approve and
|
|
51
|
-
if (v.type !== "approve" && v.type !== "round") {
|
|
51
|
+
// threadId required for all except approve, round, and session-end
|
|
52
|
+
if (v.type !== "approve" && v.type !== "round" && v.type !== "session-end") {
|
|
52
53
|
if (typeof v.threadId !== "string") return false;
|
|
53
54
|
}
|
|
54
55
|
|