cc-sidebar 0.1.7 → 0.1.8
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/README.md +30 -4
- package/package.json +1 -1
- package/skills/clarify/SKILL.md +120 -193
- package/skills/prioritize/SKILL.md +12 -1
- package/src/components/RawSidebar.tsx +246 -204
- package/src/persistence/store.ts +20 -1
package/README.md
CHANGED
|
@@ -90,11 +90,37 @@ Data is stored in `~/.claude-sidebar/`:
|
|
|
90
90
|
|
|
91
91
|
## Claude Code Setup
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
Three optional integrations to make Claude aware of the sidebar:
|
|
94
94
|
|
|
95
|
-
### 1.
|
|
95
|
+
### 1. TodoWrite Sync Hook (Recommended)
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
This hook syncs Claude's TodoWrite output to the sidebar, so you can see Claude's progress in real-time.
|
|
98
|
+
|
|
99
|
+
Add to your `~/.claude/settings.json`:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"hooks": {
|
|
104
|
+
"PostToolUse": [
|
|
105
|
+
{
|
|
106
|
+
"matcher": "TodoWrite",
|
|
107
|
+
"hooks": [
|
|
108
|
+
{
|
|
109
|
+
"type": "command",
|
|
110
|
+
"command": "bun ~/.bun/install/global/node_modules/cc-sidebar/src/sync-todos.ts"
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
If you already have other hooks, merge the `PostToolUse` array with your existing hooks.
|
|
120
|
+
|
|
121
|
+
### 2. Auto-completion (Optional)
|
|
122
|
+
|
|
123
|
+
Add this to your `~/.claude/CLAUDE.md` so Claude automatically marks sidebar tasks as done when it finishes work:
|
|
98
124
|
|
|
99
125
|
```markdown
|
|
100
126
|
## Sidebar Integration
|
|
@@ -122,7 +148,7 @@ Keep it simple - if no clear match, don't move anything. User can manually mark
|
|
|
122
148
|
```
|
|
123
149
|
```
|
|
124
150
|
|
|
125
|
-
###
|
|
151
|
+
### 3. Install Skills
|
|
126
152
|
|
|
127
153
|
cc-sidebar includes skills that integrate with Claude Code:
|
|
128
154
|
|
package/package.json
CHANGED
package/skills/clarify/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clarify
|
|
3
3
|
description: |
|
|
4
|
-
Clarify tasks through conversation - works for new ideas or existing sidebar tasks.
|
|
4
|
+
Clarify tasks through deep conversation - works for new ideas or existing sidebar tasks.
|
|
5
5
|
|
|
6
6
|
Use when: (1) You have thoughts/ideas that need clarification before becoming actionable,
|
|
7
7
|
(2) You want to clarify a single task through conversation,
|
|
@@ -9,12 +9,12 @@ description: |
|
|
|
9
9
|
|
|
10
10
|
Triggers: "/clarify", "clarify this", "let me explain this task".
|
|
11
11
|
|
|
12
|
-
Requires: Claude Code sidebar for todo integration
|
|
12
|
+
Requires: Claude Code sidebar for todo integration.
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
# Clarify Skill
|
|
16
16
|
|
|
17
|
-
Clarify tasks through structured conversation.
|
|
17
|
+
Clarify tasks through structured, in-depth conversation. Creates a **spec for each todo** (not a shared plan).
|
|
18
18
|
|
|
19
19
|
## Invocation
|
|
20
20
|
|
|
@@ -24,111 +24,65 @@ Clarify tasks through structured conversation. Works for both new ideas (creates
|
|
|
24
24
|
|
|
25
25
|
## Process
|
|
26
26
|
|
|
27
|
-
Follow these steps in order:
|
|
28
|
-
|
|
29
27
|
### 1. Detect Mode
|
|
30
28
|
|
|
31
29
|
Check if invoked with `--task-id`:
|
|
32
30
|
- **With task ID**: Updating an existing sidebar task
|
|
33
31
|
- **Without task ID**: Creating new todo(s) from scratch
|
|
34
32
|
|
|
35
|
-
### 2.
|
|
36
|
-
|
|
37
|
-
Check the project's CLAUDE.md for the Atomic Plans folder path. Look for a section like:
|
|
38
|
-
|
|
39
|
-
```markdown
|
|
40
|
-
## Clarify Configuration
|
|
41
|
-
Plan folder: /path/to/plans
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Or check if Atomic Plans is already configured:
|
|
45
|
-
|
|
46
|
-
```markdown
|
|
47
|
-
## Project Planning
|
|
48
|
-
/atomic-plans /path/to/folder
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
**If no path is found:**
|
|
52
|
-
1. Ask the user: "Where should I save plans for this project?"
|
|
53
|
-
2. After they provide a path, ask: "Would you like me to add this to CLAUDE.md so I remember next time?"
|
|
54
|
-
3. If yes, append the configuration to CLAUDE.md
|
|
55
|
-
|
|
56
|
-
### 3. Interview Phase
|
|
33
|
+
### 2. Interview Phase
|
|
57
34
|
|
|
58
|
-
|
|
35
|
+
**Interview me in detail using AskUserQuestion about literally anything:**
|
|
36
|
+
- Technical implementation details
|
|
37
|
+
- UI & UX considerations
|
|
38
|
+
- Concerns and risks
|
|
39
|
+
- Tradeoffs between approaches
|
|
40
|
+
- Edge cases and error handling
|
|
41
|
+
- Dependencies and blockers
|
|
42
|
+
- Success criteria
|
|
59
43
|
|
|
60
|
-
**
|
|
44
|
+
**CRITICAL RULES:**
|
|
45
|
+
- Make sure questions are NOT obvious - don't ask things that are clear from context
|
|
46
|
+
- Be very in-depth - continue interviewing continually until complete
|
|
47
|
+
- Multiple rounds of questions are expected and encouraged
|
|
48
|
+
- Always end with "Anything else I should know?" as a final question
|
|
61
49
|
|
|
62
|
-
**If the user provided text
|
|
63
|
-
- Skip
|
|
64
|
-
- Go straight to AskUserQuestion to clarify specifics:
|
|
65
|
-
- Ambiguous parts that need clarification
|
|
66
|
-
- Implementation approach (if relevant)
|
|
67
|
-
- Edge cases or constraints
|
|
68
|
-
- Missing context: priorities, dependencies
|
|
69
|
-
- Anything else you need to fully understand the task(s)
|
|
50
|
+
**If the user provided text** (`/clarify [text]`):
|
|
51
|
+
- Skip opening invitation - go straight to clarifying questions
|
|
70
52
|
|
|
71
53
|
**If no text was provided** (`/clarify` alone):
|
|
72
|
-
- Start with
|
|
73
|
-
|
|
74
|
-
"What's on your mind? Tell me everything - tasks, concerns, ideas,
|
|
75
|
-
whatever's floating around. I'll help clarify and organize it."
|
|
76
|
-
|
|
77
|
-
- Let the user ramble freely in their response
|
|
78
|
-
- THEN use AskUserQuestion to clarify specifics about what they said
|
|
79
|
-
|
|
80
|
-
**Interview guidelines:**
|
|
81
|
-
- Don't ask obvious questions - if something is clear from the task description, skip it
|
|
82
|
-
- Be thorough - keep interviewing until you have complete clarity
|
|
83
|
-
- Always include "Anything else I should know?" as a final question
|
|
54
|
+
- Start with: "What's on your mind? Tell me everything - tasks, concerns, ideas, whatever's floating around."
|
|
55
|
+
- Let them ramble, then ask clarifying questions
|
|
84
56
|
|
|
85
|
-
|
|
86
|
-
- What's the most important thing here?
|
|
87
|
-
- Are there dependencies between these tasks?
|
|
88
|
-
- What constraints should I know about?
|
|
89
|
-
- Is there anything blocking progress on any of these?
|
|
57
|
+
### 3. Write Spec for Each Todo
|
|
90
58
|
|
|
91
|
-
**
|
|
92
|
-
If the user mentions things unrelated to the current project (personal tasks, other projects), ask:
|
|
93
|
-
"Some of this seems unrelated to [current project]. Should I include those tasks anyway, or focus only on [project]-related work?"
|
|
59
|
+
After interviewing, create a **spec for each todo**. The spec is stored directly on the task (in the `spec` field), not in a separate plan file.
|
|
94
60
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
**Filename:** `NNN-clarify-[topic].md` where NNN is the next number in sequence.
|
|
100
|
-
|
|
101
|
-
**Content:**
|
|
102
|
-
```markdown
|
|
103
|
-
---
|
|
104
|
-
plan: NNN
|
|
105
|
-
title: [Topic]
|
|
106
|
-
state: active
|
|
107
|
-
created: YYYY-MM-DD
|
|
108
|
-
updated: YYYY-MM-DD
|
|
109
|
-
---
|
|
61
|
+
**Spec format:**
|
|
62
|
+
```
|
|
63
|
+
## [Task Title]
|
|
110
64
|
|
|
111
|
-
|
|
65
|
+
### What
|
|
66
|
+
[Clear description of what needs to be done]
|
|
112
67
|
|
|
113
|
-
|
|
114
|
-
[
|
|
68
|
+
### Why
|
|
69
|
+
[The purpose/motivation]
|
|
115
70
|
|
|
116
|
-
|
|
117
|
-
[
|
|
71
|
+
### How
|
|
72
|
+
[Implementation approach based on interview]
|
|
118
73
|
|
|
119
|
-
|
|
120
|
-
[
|
|
74
|
+
### Acceptance Criteria
|
|
75
|
+
- [ ] Criterion 1
|
|
76
|
+
- [ ] Criterion 2
|
|
121
77
|
|
|
122
|
-
|
|
123
|
-
|
|
78
|
+
### Notes
|
|
79
|
+
[Any constraints, risks, or considerations from interview]
|
|
124
80
|
```
|
|
125
81
|
|
|
126
|
-
###
|
|
82
|
+
### 4. Update or Create Todos
|
|
127
83
|
|
|
128
84
|
**If updating existing task** (has `--task-id`):
|
|
129
85
|
|
|
130
|
-
Update the existing task in the sidebar:
|
|
131
|
-
|
|
132
86
|
```bash
|
|
133
87
|
node << 'SCRIPT'
|
|
134
88
|
const fs = require('fs');
|
|
@@ -141,30 +95,28 @@ let tasks = JSON.parse(fs.readFileSync(tasksPath, 'utf-8'));
|
|
|
141
95
|
const task = tasks.find(t => t.id === 'TASK_ID_HERE'); // REPLACE with actual task ID
|
|
142
96
|
if (task) {
|
|
143
97
|
task.clarified = true;
|
|
144
|
-
task.
|
|
98
|
+
task.section = 'clarified';
|
|
99
|
+
task.spec = `SPEC_CONTENT_HERE`; // REPLACE with actual spec
|
|
145
100
|
fs.writeFileSync(tasksPath, JSON.stringify(tasks, null, 2));
|
|
146
|
-
console.log('Task clarified
|
|
101
|
+
console.log('Task clarified with spec');
|
|
147
102
|
}
|
|
148
103
|
SCRIPT
|
|
149
104
|
```
|
|
150
105
|
|
|
151
106
|
**If creating new todos** (no `--task-id`):
|
|
152
107
|
|
|
153
|
-
Extract discrete, actionable tasks
|
|
108
|
+
Extract discrete, actionable tasks. Each todo should be:
|
|
154
109
|
- **Specific**: Clear what needs to be done
|
|
155
110
|
- **Actionable**: Can be worked on independently
|
|
156
|
-
- **
|
|
157
|
-
|
|
158
|
-
**Act as a staff engineer to prioritize:**
|
|
159
|
-
Assign each task a numeric priority (1 = most important). Consider:
|
|
160
|
-
- **Dependencies**: Blockers come first
|
|
161
|
-
- **Urgency**: Bugs, deadlines, user-facing issues
|
|
162
|
-
- **Impact**: High-value features over nice-to-haves
|
|
163
|
-
- **Effort**: Quick wins can be prioritized to build momentum
|
|
111
|
+
- **Has a spec**: Detailed specification from interview
|
|
164
112
|
|
|
165
|
-
|
|
113
|
+
**Prioritization (act as staff engineer):**
|
|
114
|
+
- Dependencies first (blockers)
|
|
115
|
+
- Urgency (bugs, deadlines)
|
|
116
|
+
- Impact (high-value over nice-to-have)
|
|
117
|
+
- Quick wins for momentum
|
|
166
118
|
|
|
167
|
-
|
|
119
|
+
Mark top 3 as "recommended".
|
|
168
120
|
|
|
169
121
|
```bash
|
|
170
122
|
node << 'SCRIPT'
|
|
@@ -177,144 +129,119 @@ const hash = crypto.createHash('sha256').update(process.cwd()).digest('hex').sli
|
|
|
177
129
|
const projectDir = path.join(sidebarDir, 'projects', hash);
|
|
178
130
|
const tasksPath = path.join(projectDir, 'tasks.json');
|
|
179
131
|
|
|
180
|
-
// Ensure directory exists
|
|
181
132
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
182
133
|
|
|
183
|
-
// Read existing todos
|
|
184
134
|
let existing = [];
|
|
185
135
|
try { existing = JSON.parse(fs.readFileSync(tasksPath, 'utf-8')); } catch {}
|
|
186
136
|
|
|
187
|
-
//
|
|
188
|
-
const planPath = "NNN-clarify-topic.md"; // REPLACE with actual plan filename
|
|
189
|
-
|
|
190
|
-
// New todos - REPLACE THIS ARRAY with extracted tasks
|
|
191
|
-
// Each task needs: content, priority (1=highest), recommended (true for top 3)
|
|
137
|
+
// REPLACE with extracted tasks - each has its own spec
|
|
192
138
|
const newTodos = [
|
|
193
|
-
{
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
139
|
+
{
|
|
140
|
+
content: "Task 1 here",
|
|
141
|
+
priority: 1,
|
|
142
|
+
recommended: true,
|
|
143
|
+
spec: `## Task 1\n\n### What\n...\n\n### Why\n...\n\n### How\n...\n\n### Acceptance Criteria\n- [ ] ...`
|
|
144
|
+
},
|
|
145
|
+
// ... more tasks
|
|
197
146
|
].map(task => ({
|
|
198
147
|
id: crypto.randomUUID(),
|
|
199
148
|
content: task.content,
|
|
200
149
|
createdAt: new Date().toISOString(),
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
150
|
+
section: 'clarified',
|
|
151
|
+
clarified: true,
|
|
152
|
+
priority: task.priority,
|
|
153
|
+
recommended: task.recommended,
|
|
154
|
+
spec: task.spec
|
|
205
155
|
}));
|
|
206
156
|
|
|
207
|
-
// Append and write
|
|
208
157
|
const combined = [...existing, ...newTodos];
|
|
209
158
|
fs.writeFileSync(tasksPath, JSON.stringify(combined, null, 2));
|
|
210
|
-
console.log('Added ' + newTodos.length + ' todos to sidebar');
|
|
159
|
+
console.log('Added ' + newTodos.length + ' clarified todos to sidebar');
|
|
211
160
|
SCRIPT
|
|
212
161
|
```
|
|
213
162
|
|
|
214
|
-
###
|
|
215
|
-
|
|
216
|
-
After creating/updating todos and plan:
|
|
217
|
-
|
|
218
|
-
1. Show a summary of what was created/updated
|
|
219
|
-
2. Ask: **"Execute this task now, or save for later?"**
|
|
220
|
-
- If execute → work on the task immediately
|
|
221
|
-
- If save → confirm the task is clarified and stop
|
|
163
|
+
### 5. Present and Prompt
|
|
222
164
|
|
|
223
|
-
|
|
224
|
-
```
|
|
225
|
-
Clarified your task and created plan 007-auth-refactor.md
|
|
165
|
+
After creating/updating todos:
|
|
226
166
|
|
|
227
|
-
|
|
228
|
-
|
|
167
|
+
1. Show summary of what was clarified
|
|
168
|
+
2. Show the spec for each task
|
|
169
|
+
3. Ask: **"Execute this task now, or save for later?"**
|
|
170
|
+
- Execute → start working immediately
|
|
171
|
+
- Save → confirm clarified and stop
|
|
229
172
|
|
|
230
|
-
|
|
173
|
+
**Example output:**
|
|
231
174
|
```
|
|
175
|
+
Clarified 2 todos:
|
|
232
176
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
Created 3 todos in your sidebar and plan 008-api-improvements.md
|
|
177
|
+
1. ★ Implement user authentication
|
|
178
|
+
Spec: JWT-based auth with 24hr expiry, refresh tokens...
|
|
236
179
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
2. Implement request validation
|
|
240
|
-
3. Add API documentation
|
|
180
|
+
2. ★ Add rate limiting to API
|
|
181
|
+
Spec: 100 req/min per user, 429 response, Redis backend...
|
|
241
182
|
|
|
242
183
|
Execute the first task now, or save for later?
|
|
243
184
|
```
|
|
244
185
|
|
|
245
|
-
## Relationship to Other
|
|
246
|
-
|
|
247
|
-
| Feature | Purpose | When to Use |
|
|
248
|
-
|---------|---------|-------------|
|
|
249
|
-
| `/clarify` | Interview and clarify task(s) | When you need to think through a task before doing it |
|
|
250
|
-
| Sidebar `c` key | Invokes `/clarify --task-id` | Quick way to clarify a queued task |
|
|
251
|
-
| `/prioritize` | Re-evaluate and reorder all tasks | When context changes or you want fresh prioritization |
|
|
252
|
-
| Ralph-loop | Batch process todos autonomously | After clarifying, to work through all todos |
|
|
253
|
-
|
|
254
|
-
## Example Sessions
|
|
255
|
-
|
|
256
|
-
### From Sidebar (existing task)
|
|
257
|
-
```
|
|
258
|
-
[User presses 'c' on task "Add user authentication"]
|
|
259
|
-
|
|
260
|
-
Sidebar sends: /clarify --task-id abc123 Add user authentication
|
|
261
|
-
|
|
262
|
-
Claude: [Uses AskUserQuestion]
|
|
263
|
-
- What auth method? (JWT, session, OAuth?)
|
|
264
|
-
- Any specific security requirements?
|
|
265
|
-
- Which routes need protection?
|
|
186
|
+
## Relationship to Other Skills
|
|
266
187
|
|
|
267
|
-
|
|
188
|
+
| Skill | Purpose |
|
|
189
|
+
|-------|---------|
|
|
190
|
+
| `/clarify` | Interview and write spec for task(s) |
|
|
191
|
+
| `/prioritize` | Re-evaluate all tasks as staff engineer |
|
|
192
|
+
| Atomic Plans | Track execution progress when working on task |
|
|
268
193
|
|
|
269
|
-
|
|
194
|
+
**Flow:**
|
|
195
|
+
1. **Clarify** creates specs (the "what")
|
|
196
|
+
2. **Prioritize** decides what's next
|
|
197
|
+
3. **Atomic Plans** tracks execution (the "how/progress")
|
|
270
198
|
|
|
271
|
-
|
|
199
|
+
## Example Session
|
|
272
200
|
|
|
273
|
-
Execute this task now, or save for later?
|
|
274
201
|
```
|
|
202
|
+
User: /clarify I need to add dark mode support
|
|
275
203
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
204
|
+
Claude: [AskUserQuestion]
|
|
205
|
+
- Should this affect the entire app or just certain components?
|
|
206
|
+
- Do you want system preference detection (prefers-color-scheme)?
|
|
207
|
+
- Are there specific colors/themes you want, or should I design them?
|
|
208
|
+
- Should the preference persist across sessions (localStorage)?
|
|
279
209
|
|
|
280
|
-
|
|
281
|
-
- Which input modes need this? (adding new todos, editing existing, both?)
|
|
282
|
-
- Should Enter still submit, with Shift+Enter for newlines?
|
|
210
|
+
User: [Answers each question]
|
|
283
211
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
Execute this task now, or save for later?
|
|
291
|
-
```
|
|
212
|
+
Claude: [AskUserQuestion - follow-up]
|
|
213
|
+
- For the color palette, do you have brand colors to work with?
|
|
214
|
+
- Should transitions be animated when switching themes?
|
|
215
|
+
- Any components that should NOT support dark mode?
|
|
216
|
+
- Anything else I should know?
|
|
292
217
|
|
|
293
|
-
|
|
294
|
-
```
|
|
295
|
-
User: /clarify
|
|
218
|
+
User: [Answers]
|
|
296
219
|
|
|
297
|
-
Claude:
|
|
220
|
+
Claude: [Creates todo with spec]
|
|
298
221
|
|
|
299
|
-
|
|
300
|
-
patterns. also need to update the docs. oh and there's that bug with
|
|
301
|
-
the session timeout.
|
|
222
|
+
Clarified 1 todo:
|
|
302
223
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
224
|
+
★ Add dark mode support
|
|
225
|
+
Spec:
|
|
226
|
+
## Dark Mode Support
|
|
306
227
|
|
|
307
|
-
|
|
228
|
+
### What
|
|
229
|
+
Add system-wide dark mode toggle with OS preference detection
|
|
308
230
|
|
|
309
|
-
|
|
231
|
+
### Why
|
|
232
|
+
User requested for better nighttime usability
|
|
310
233
|
|
|
311
|
-
|
|
234
|
+
### How
|
|
235
|
+
- CSS custom properties for colors
|
|
236
|
+
- React context for theme state
|
|
237
|
+
- localStorage for persistence
|
|
238
|
+
- prefers-color-scheme media query for initial
|
|
312
239
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
240
|
+
### Acceptance Criteria
|
|
241
|
+
- [ ] Toggle switches between light/dark
|
|
242
|
+
- [ ] Respects OS preference on first visit
|
|
243
|
+
- [ ] Persists choice in localStorage
|
|
244
|
+
- [ ] Smooth transition animation
|
|
318
245
|
|
|
319
|
-
Execute
|
|
246
|
+
Execute this task now, or save for later?
|
|
320
247
|
```
|
|
@@ -51,6 +51,8 @@ As a staff engineer, evaluate each task considering:
|
|
|
51
51
|
- **Impact**: High-value features over nice-to-haves.
|
|
52
52
|
- **Effort**: Quick wins can build momentum; large tasks may need breakdown.
|
|
53
53
|
- **Context**: If user provided context, weight it heavily.
|
|
54
|
+
- **Specs**: Read the `spec` field of clarified tasks for detailed requirements.
|
|
55
|
+
- **Section**: Clarified tasks (with specs) are generally ready to execute.
|
|
54
56
|
|
|
55
57
|
Assign each task:
|
|
56
58
|
- `priority`: Numeric (1 = most important, higher = less important)
|
|
@@ -124,9 +126,18 @@ Other tasks:
|
|
|
124
126
|
Tasks have been re-prioritized in your sidebar.
|
|
125
127
|
```
|
|
126
128
|
|
|
129
|
+
## Skill Flow
|
|
130
|
+
|
|
131
|
+
This skill is part of a chain:
|
|
132
|
+
|
|
133
|
+
1. **Clarify** → Creates specs for each task (the "what")
|
|
134
|
+
2. **Prioritize** → Staff engineer decides what's next (this skill)
|
|
135
|
+
3. **Atomic Plans** → Track execution progress when working
|
|
136
|
+
|
|
127
137
|
## Notes
|
|
128
138
|
|
|
129
139
|
- This skill reads and modifies the sidebar's tasks.json directly
|
|
130
|
-
- It preserves all existing task fields (id, content, clarified,
|
|
140
|
+
- It preserves all existing task fields (id, content, clarified, spec, etc.)
|
|
131
141
|
- Only updates `priority` and `recommended` fields
|
|
132
142
|
- Can be run anytime - safe to run multiple times
|
|
143
|
+
- Read specs to understand task complexity when prioritizing
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
updateTask,
|
|
14
14
|
removeTask,
|
|
15
15
|
markTaskClarified,
|
|
16
|
+
setTaskSection,
|
|
16
17
|
getActiveTask,
|
|
17
18
|
setActiveTask,
|
|
18
19
|
activateTask,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
removeFromDone,
|
|
22
23
|
returnToActive,
|
|
23
24
|
type Task,
|
|
25
|
+
type TaskSection,
|
|
24
26
|
type ActiveTask,
|
|
25
27
|
type DoneTask,
|
|
26
28
|
type StatuslineData,
|
|
@@ -110,15 +112,16 @@ function wrapText(text: string, maxWidth: number): string[] {
|
|
|
110
112
|
return lines.length > 0 ? lines : [''];
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
type SidebarSection = "inbox" | "clarified" | "in_progress" | "review";
|
|
116
|
+
|
|
113
117
|
interface State {
|
|
114
118
|
tasks: Task[];
|
|
115
119
|
activeTask: ActiveTask | null;
|
|
116
120
|
doneTasks: DoneTask[];
|
|
117
121
|
claudeTodos: ClaudeTodo[];
|
|
118
122
|
statusline: StatuslineData | null;
|
|
119
|
-
selectedSection:
|
|
123
|
+
selectedSection: SidebarSection;
|
|
120
124
|
selectedIndex: number;
|
|
121
|
-
doneSelectedIndex: number;
|
|
122
125
|
inputMode: InputMode;
|
|
123
126
|
editingTaskId: string | null;
|
|
124
127
|
inputBuffer: string;
|
|
@@ -132,9 +135,8 @@ export class RawSidebar {
|
|
|
132
135
|
doneTasks: [],
|
|
133
136
|
claudeTodos: [],
|
|
134
137
|
statusline: null,
|
|
135
|
-
selectedSection: "
|
|
138
|
+
selectedSection: "inbox",
|
|
136
139
|
selectedIndex: 0,
|
|
137
|
-
doneSelectedIndex: 0,
|
|
138
140
|
inputMode: "none",
|
|
139
141
|
editingTaskId: null,
|
|
140
142
|
inputBuffer: "",
|
|
@@ -169,6 +171,44 @@ export class RawSidebar {
|
|
|
169
171
|
});
|
|
170
172
|
}
|
|
171
173
|
|
|
174
|
+
// Get tasks for a specific section
|
|
175
|
+
private getTasksForSection(section: "inbox" | "clarified"): Task[] {
|
|
176
|
+
return this.getSortedTasks().filter(t => {
|
|
177
|
+
const taskSection = t.section || (t.clarified ? "clarified" : "inbox");
|
|
178
|
+
return taskSection === section;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Get items for the currently selected section
|
|
183
|
+
private getCurrentSectionItems(): { type: "task" | "todo" | "done"; item: Task | ClaudeTodo | DoneTask }[] {
|
|
184
|
+
const { selectedSection, claudeTodos, doneTasks } = this.state;
|
|
185
|
+
|
|
186
|
+
if (selectedSection === "inbox") {
|
|
187
|
+
return this.getTasksForSection("inbox").map(t => ({ type: "task" as const, item: t }));
|
|
188
|
+
}
|
|
189
|
+
if (selectedSection === "clarified") {
|
|
190
|
+
return this.getTasksForSection("clarified").map(t => ({ type: "task" as const, item: t }));
|
|
191
|
+
}
|
|
192
|
+
if (selectedSection === "in_progress") {
|
|
193
|
+
return claudeTodos.map(t => ({ type: "todo" as const, item: t }));
|
|
194
|
+
}
|
|
195
|
+
if (selectedSection === "review") {
|
|
196
|
+
return doneTasks.map(t => ({ type: "done" as const, item: t }));
|
|
197
|
+
}
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Get the ordered list of non-empty sections for navigation
|
|
202
|
+
private getNavigableSections(): SidebarSection[] {
|
|
203
|
+
const sections: SidebarSection[] = [];
|
|
204
|
+
if (this.getTasksForSection("inbox").length > 0) sections.push("inbox");
|
|
205
|
+
if (this.getTasksForSection("clarified").length > 0) sections.push("clarified");
|
|
206
|
+
// in_progress always navigable (shows active task or Claude todos)
|
|
207
|
+
sections.push("in_progress");
|
|
208
|
+
if (this.state.doneTasks.length > 0) sections.push("review");
|
|
209
|
+
return sections;
|
|
210
|
+
}
|
|
211
|
+
|
|
172
212
|
constructor(onClose?: () => void) {
|
|
173
213
|
this.width = process.stdout.columns || 50;
|
|
174
214
|
this.height = process.stdout.rows || 40;
|
|
@@ -594,124 +634,86 @@ export class RawSidebar {
|
|
|
594
634
|
process.exit(0);
|
|
595
635
|
}
|
|
596
636
|
|
|
597
|
-
// Up arrow or k
|
|
637
|
+
// Up arrow or k - navigate within section or to previous section
|
|
598
638
|
if (str === '\x1b[A' || str === '\x1bOA' || str === 'k') {
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (doneTasks.length > 0) {
|
|
605
|
-
this.state.selectedSection = "done";
|
|
606
|
-
this.state.doneSelectedIndex = doneTasks.length - 1;
|
|
607
|
-
}
|
|
608
|
-
} else if (selectedIndex > 0) {
|
|
609
|
-
this.state.selectedIndex--;
|
|
610
|
-
} else {
|
|
611
|
-
// At top of queue, wrap to bottom of done (or bottom of queue)
|
|
612
|
-
if (doneTasks.length > 0) {
|
|
613
|
-
this.state.selectedSection = "done";
|
|
614
|
-
this.state.doneSelectedIndex = Math.min(doneTasks.length - 1, 4); // Max 5 shown
|
|
615
|
-
} else {
|
|
616
|
-
this.state.selectedIndex = tasks.length - 1;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
639
|
+
const items = this.getCurrentSectionItems();
|
|
640
|
+
const { selectedIndex } = this.state;
|
|
641
|
+
|
|
642
|
+
if (selectedIndex > 0) {
|
|
643
|
+
this.state.selectedIndex--;
|
|
619
644
|
} else {
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
this.state.doneSelectedIndex = Math.min(doneTasks.length - 1, 4);
|
|
630
|
-
}
|
|
645
|
+
// Move to previous section
|
|
646
|
+
const sections = this.getNavigableSections();
|
|
647
|
+
const currentIdx = sections.indexOf(this.state.selectedSection);
|
|
648
|
+
if (currentIdx > 0) {
|
|
649
|
+
const prevSection = sections[currentIdx - 1];
|
|
650
|
+
this.state.selectedSection = prevSection!;
|
|
651
|
+
// Select last item in previous section
|
|
652
|
+
const prevItems = this.getCurrentSectionItems();
|
|
653
|
+
this.state.selectedIndex = Math.max(0, prevItems.length - 1);
|
|
631
654
|
}
|
|
632
655
|
}
|
|
633
656
|
this.render();
|
|
634
657
|
return;
|
|
635
658
|
}
|
|
636
659
|
|
|
637
|
-
// Down arrow or j
|
|
660
|
+
// Down arrow or j - navigate within section or to next section
|
|
638
661
|
if (str === '\x1b[B' || str === '\x1bOB' || str === 'j') {
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
if (doneTasks.length > 0) {
|
|
645
|
-
this.state.selectedSection = "done";
|
|
646
|
-
this.state.doneSelectedIndex = 0;
|
|
647
|
-
}
|
|
648
|
-
} else if (selectedIndex < tasks.length - 1) {
|
|
649
|
-
this.state.selectedIndex++;
|
|
650
|
-
} else {
|
|
651
|
-
// At bottom of queue, wrap to top of done (or top of queue)
|
|
652
|
-
if (doneTasks.length > 0) {
|
|
653
|
-
this.state.selectedSection = "done";
|
|
654
|
-
this.state.doneSelectedIndex = 0;
|
|
655
|
-
} else {
|
|
656
|
-
this.state.selectedIndex = 0;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
662
|
+
const items = this.getCurrentSectionItems();
|
|
663
|
+
const { selectedIndex } = this.state;
|
|
664
|
+
|
|
665
|
+
if (selectedIndex < items.length - 1) {
|
|
666
|
+
this.state.selectedIndex++;
|
|
659
667
|
} else {
|
|
660
|
-
//
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
this.state.selectedSection = "queue";
|
|
668
|
-
this.state.selectedIndex = 0;
|
|
669
|
-
} else {
|
|
670
|
-
this.state.doneSelectedIndex = 0;
|
|
671
|
-
}
|
|
668
|
+
// Move to next section
|
|
669
|
+
const sections = this.getNavigableSections();
|
|
670
|
+
const currentIdx = sections.indexOf(this.state.selectedSection);
|
|
671
|
+
if (currentIdx < sections.length - 1) {
|
|
672
|
+
const nextSection = sections[currentIdx + 1];
|
|
673
|
+
this.state.selectedSection = nextSection!;
|
|
674
|
+
this.state.selectedIndex = 0;
|
|
672
675
|
}
|
|
673
676
|
}
|
|
674
677
|
this.render();
|
|
675
678
|
return;
|
|
676
679
|
}
|
|
677
680
|
|
|
678
|
-
//
|
|
679
|
-
if (
|
|
680
|
-
const
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}
|
|
681
|
+
// Tab - switch between sections
|
|
682
|
+
if (str === '\t') {
|
|
683
|
+
const sections = this.getNavigableSections();
|
|
684
|
+
const currentIdx = sections.indexOf(this.state.selectedSection);
|
|
685
|
+
const nextIdx = (currentIdx + 1) % sections.length;
|
|
686
|
+
this.state.selectedSection = sections[nextIdx]!;
|
|
687
|
+
this.state.selectedIndex = 0;
|
|
688
|
+
this.render();
|
|
687
689
|
return;
|
|
688
690
|
}
|
|
689
691
|
|
|
690
|
-
// Enter - send task to Claude (
|
|
692
|
+
// Enter - send task to Claude (works in inbox and clarified sections)
|
|
691
693
|
if (str === '\r' || str === '\n') {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
694
|
+
const { selectedSection, selectedIndex } = this.state;
|
|
695
|
+
if (selectedSection !== "inbox" && selectedSection !== "clarified") return;
|
|
696
|
+
|
|
697
|
+
const tasks = this.getTasksForSection(selectedSection);
|
|
698
|
+
const task = tasks[selectedIndex];
|
|
695
699
|
if (task) {
|
|
696
700
|
// Send to Claude and move to active
|
|
697
701
|
sendToClaudePane(task.content);
|
|
698
702
|
activateTask(task.id);
|
|
699
703
|
this.loadData();
|
|
700
|
-
this.state.selectedIndex = Math.max(0,
|
|
704
|
+
this.state.selectedIndex = Math.max(0, selectedIndex - 1);
|
|
701
705
|
this.render();
|
|
702
706
|
focusClaudePane();
|
|
703
707
|
}
|
|
704
708
|
return;
|
|
705
709
|
}
|
|
706
710
|
|
|
707
|
-
//
|
|
708
|
-
// CSI u format: \x1b[13;5u (iTerm2), 'c' as fallback
|
|
711
|
+
// 'c' - clarify (only works in inbox section)
|
|
709
712
|
if (str === '\x1b[13;5u' || str === '\x1b\r' || str === '\x1b\n' || str === 'c') {
|
|
710
|
-
if (this.state.selectedSection !== "
|
|
711
|
-
const
|
|
712
|
-
const task =
|
|
713
|
+
if (this.state.selectedSection !== "inbox") return;
|
|
714
|
+
const tasks = this.getTasksForSection("inbox");
|
|
715
|
+
const task = tasks[this.state.selectedIndex];
|
|
713
716
|
if (task) {
|
|
714
|
-
// Invoke the clarify skill with task ID
|
|
715
717
|
sendToClaudePane(`/clarify --task-id ${task.id} ${task.content}`);
|
|
716
718
|
this.render();
|
|
717
719
|
focusClaudePane();
|
|
@@ -719,24 +721,26 @@ export class RawSidebar {
|
|
|
719
721
|
return;
|
|
720
722
|
}
|
|
721
723
|
|
|
722
|
-
// 'a' - add task (
|
|
724
|
+
// 'a' - add task (adds to inbox)
|
|
723
725
|
if (str === 'a') {
|
|
724
726
|
this.pausePolling();
|
|
725
|
-
this.state.selectedSection = "
|
|
727
|
+
this.state.selectedSection = "inbox";
|
|
726
728
|
this.state.inputMode = "add";
|
|
727
729
|
this.state.inputBuffer = "";
|
|
728
730
|
this.state.inputCursor = 0;
|
|
729
|
-
this.prevInputLineCount = 1;
|
|
731
|
+
this.prevInputLineCount = 1;
|
|
730
732
|
this.render();
|
|
731
733
|
this.setupInputCursor();
|
|
732
734
|
return;
|
|
733
735
|
}
|
|
734
736
|
|
|
735
|
-
// 'e' - edit task (only
|
|
737
|
+
// 'e' - edit task (only in inbox/clarified sections)
|
|
736
738
|
if (str === 'e') {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
739
|
+
const { selectedSection, selectedIndex } = this.state;
|
|
740
|
+
if (selectedSection !== "inbox" && selectedSection !== "clarified") return;
|
|
741
|
+
|
|
742
|
+
const tasks = this.getTasksForSection(selectedSection);
|
|
743
|
+
const task = tasks[selectedIndex];
|
|
740
744
|
if (task) {
|
|
741
745
|
this.pausePolling();
|
|
742
746
|
this.state.inputMode = "edit";
|
|
@@ -751,33 +755,31 @@ export class RawSidebar {
|
|
|
751
755
|
return;
|
|
752
756
|
}
|
|
753
757
|
|
|
754
|
-
// 'd' - delete from
|
|
758
|
+
// 'd' - delete from inbox/clarified, or confirm done in review
|
|
755
759
|
if (str === 'd') {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
760
|
+
const { selectedSection, selectedIndex } = this.state;
|
|
761
|
+
|
|
762
|
+
if (selectedSection === "inbox" || selectedSection === "clarified") {
|
|
763
|
+
const tasks = this.getTasksForSection(selectedSection);
|
|
764
|
+
const task = tasks[selectedIndex];
|
|
759
765
|
if (task) {
|
|
760
766
|
removeTask(task.id);
|
|
761
767
|
this.state.tasks = getTasks();
|
|
762
|
-
this.state.selectedIndex = Math.max(0,
|
|
768
|
+
this.state.selectedIndex = Math.max(0, selectedIndex - 1);
|
|
763
769
|
this.render();
|
|
764
770
|
}
|
|
765
|
-
} else {
|
|
766
|
-
|
|
767
|
-
const task = this.state.doneTasks[this.state.doneSelectedIndex];
|
|
771
|
+
} else if (selectedSection === "review") {
|
|
772
|
+
const task = this.state.doneTasks[selectedIndex];
|
|
768
773
|
if (task) {
|
|
769
774
|
removeFromDone(task.id);
|
|
770
775
|
this.state.doneTasks = getRecentlyDone();
|
|
771
|
-
// Adjust selection if needed
|
|
772
776
|
if (this.state.doneTasks.length === 0) {
|
|
773
|
-
//
|
|
774
|
-
|
|
775
|
-
this.state.
|
|
777
|
+
// Move to another section
|
|
778
|
+
const sections = this.getNavigableSections();
|
|
779
|
+
this.state.selectedSection = sections[0] || "inbox";
|
|
780
|
+
this.state.selectedIndex = 0;
|
|
776
781
|
} else {
|
|
777
|
-
this.state.
|
|
778
|
-
this.state.doneSelectedIndex,
|
|
779
|
-
this.state.doneTasks.length - 1
|
|
780
|
-
);
|
|
782
|
+
this.state.selectedIndex = Math.min(selectedIndex, this.state.doneTasks.length - 1);
|
|
781
783
|
}
|
|
782
784
|
this.render();
|
|
783
785
|
}
|
|
@@ -785,21 +787,21 @@ export class RawSidebar {
|
|
|
785
787
|
return;
|
|
786
788
|
}
|
|
787
789
|
|
|
788
|
-
// 'r' - return
|
|
790
|
+
// 'r' - return review item to in_progress
|
|
789
791
|
if (str === 'r') {
|
|
790
|
-
if (this.state.selectedSection === "
|
|
791
|
-
const task = this.state.doneTasks[this.state.
|
|
792
|
+
if (this.state.selectedSection === "review") {
|
|
793
|
+
const task = this.state.doneTasks[this.state.selectedIndex];
|
|
792
794
|
if (task) {
|
|
793
795
|
returnToActive(task.id);
|
|
794
796
|
this.loadData();
|
|
795
|
-
// Adjust selection if needed
|
|
796
797
|
if (this.state.doneTasks.length === 0) {
|
|
797
|
-
|
|
798
|
-
this.state.
|
|
798
|
+
const sections = this.getNavigableSections();
|
|
799
|
+
this.state.selectedSection = sections[0] || "inbox";
|
|
800
|
+
this.state.selectedIndex = 0;
|
|
799
801
|
} else {
|
|
800
|
-
this.state.
|
|
801
|
-
this.state.
|
|
802
|
-
|
|
802
|
+
this.state.selectedIndex = Math.min(
|
|
803
|
+
this.state.selectedIndex,
|
|
804
|
+
this.state.doneTasks.length - 1
|
|
803
805
|
);
|
|
804
806
|
}
|
|
805
807
|
this.render();
|
|
@@ -904,7 +906,7 @@ export class RawSidebar {
|
|
|
904
906
|
if (!this.running) return;
|
|
905
907
|
|
|
906
908
|
const lines: string[] = [];
|
|
907
|
-
const {
|
|
909
|
+
const { inputMode, editingTaskId, inputBuffer, inputCursor } = this.state;
|
|
908
910
|
|
|
909
911
|
// Use dimmed colors when unfocused
|
|
910
912
|
const bg = this.focused ? ansi.bgGray : ansi.dimBg;
|
|
@@ -936,120 +938,156 @@ export class RawSidebar {
|
|
|
936
938
|
lines.push(`${bg} ${text}${headerContent}${ansi.clearToEnd}${ansi.reset}`);
|
|
937
939
|
lines.push(bgLine); // Space after header
|
|
938
940
|
|
|
939
|
-
// In Progress section - combines sidebar active task + Claude's TodoWrite items
|
|
940
|
-
const { claudeTodos } = this.state;
|
|
941
|
-
const { activeTask, doneTasks } = this.state;
|
|
942
|
-
const activeTodos = claudeTodos.filter(t => t.status !== "completed");
|
|
943
941
|
// Content width: total width - 2 (margin) - 4 (indicator like "[ ] ") - 2 (right padding)
|
|
944
942
|
const maxContentWidth = this.width - 8;
|
|
945
|
-
|
|
946
|
-
// Always show In Progress section
|
|
947
|
-
lines.push(`${bg} ${bold}${text}In Progress${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
948
|
-
|
|
949
|
-
// Show sidebar active task first (sent from queue)
|
|
950
|
-
if (activeTask) {
|
|
951
|
-
const content = activeTask.content.slice(0, maxContentWidth);
|
|
952
|
-
lines.push(`${bg} ${ansi.green}▸ ${content}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// Show Claude's TodoWrite items (what Claude is tracking)
|
|
956
|
-
activeTodos.forEach((todo) => {
|
|
957
|
-
let statusIcon: string;
|
|
958
|
-
let todoColor = text;
|
|
959
|
-
if (todo.status === "in_progress") {
|
|
960
|
-
statusIcon = "● ";
|
|
961
|
-
todoColor = ansi.green;
|
|
962
|
-
} else {
|
|
963
|
-
statusIcon = "○ ";
|
|
964
|
-
}
|
|
965
|
-
const content = todo.content.slice(0, maxContentWidth);
|
|
966
|
-
lines.push(`${bg} ${todoColor}${statusIcon}${content}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
967
|
-
});
|
|
968
|
-
|
|
969
|
-
lines.push(bgLine);
|
|
970
|
-
|
|
971
|
-
// Review section (tasks Claude thinks are done, awaiting user confirmation)
|
|
972
|
-
const { selectedSection, doneSelectedIndex } = this.state;
|
|
973
|
-
if (doneTasks.length > 0) {
|
|
974
|
-
lines.push(`${bg} ${bold}${text}Review (${doneTasks.length})${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
975
|
-
doneTasks.slice(0, 5).forEach((task, index) => {
|
|
976
|
-
const isSelected = selectedSection === "done" && index === doneSelectedIndex && this.focused;
|
|
977
|
-
const content = task.content.slice(0, maxContentWidth);
|
|
978
|
-
const icon = isSelected ? "[?] " : " ? ";
|
|
979
|
-
const color = isSelected ? text : muted;
|
|
980
|
-
lines.push(`${bg} ${color}${icon}${content}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
981
|
-
});
|
|
982
|
-
lines.push(bgLine);
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// To-dos section - single flat list sorted by priority
|
|
986
|
-
const sortedTasks = this.getSortedTasks();
|
|
943
|
+
const { claudeTodos, activeTask, doneTasks, selectedSection, selectedIndex } = this.state;
|
|
987
944
|
|
|
988
945
|
// Track where the input line is for cursor positioning
|
|
989
946
|
let inputLineRow = 0;
|
|
990
947
|
|
|
991
|
-
//
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
948
|
+
// ANSI strikethrough
|
|
949
|
+
const strikethrough = '\x1b[9m';
|
|
950
|
+
const noStrike = '\x1b[29m';
|
|
951
|
+
|
|
952
|
+
// Helper to render a task in inbox/clarified sections
|
|
953
|
+
const renderTask = (task: Task, index: number, section: "inbox" | "clarified") => {
|
|
954
|
+
const isSelected = selectedSection === section && index === selectedIndex;
|
|
996
955
|
const isEditing = inputMode === "edit" && editingTaskId === task.id;
|
|
997
956
|
const star = task.recommended ? "★ " : " ";
|
|
998
957
|
const bracket = (isSelected && this.focused) ? "[>] " : "[ ] ";
|
|
999
|
-
const color =
|
|
958
|
+
const color = section === "clarified" ? text : muted;
|
|
1000
959
|
|
|
1001
960
|
if (isEditing) {
|
|
1002
961
|
inputLineRow = lines.length + 1;
|
|
1003
962
|
this.inputRow = inputLineRow;
|
|
1004
|
-
const wrappedLines = wrapText(inputBuffer, maxContentWidth - 2);
|
|
963
|
+
const wrappedLines = wrapText(inputBuffer, maxContentWidth - 2);
|
|
1005
964
|
if (wrappedLines.length === 0) wrappedLines.push('');
|
|
1006
965
|
wrappedLines.forEach((line, i) => {
|
|
1007
|
-
const prefix = i === 0 ? `${star}${bracket}` : " ";
|
|
966
|
+
const prefix = i === 0 ? `${star}${bracket}` : " ";
|
|
1008
967
|
const padding = ' '.repeat(Math.max(0, maxContentWidth - 2 - line.length));
|
|
1009
968
|
lines.push(`${bg} ${prefix}${text}${line}${padding}${ansi.reset}`);
|
|
1010
969
|
});
|
|
1011
970
|
} else if (isSelected && this.focused && (task.content.length > maxContentWidth - 2 || task.content.includes('\n'))) {
|
|
1012
|
-
// Wrap long content or content with newlines when selected
|
|
1013
971
|
const wrappedLines = wrapText(task.content, maxContentWidth - 2);
|
|
1014
972
|
wrappedLines.forEach((line, i) => {
|
|
1015
973
|
const prefix = i === 0 ? `${star}${bracket}` : " ";
|
|
1016
974
|
lines.push(`${bg} ${color}${prefix}${line}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1017
975
|
});
|
|
1018
976
|
} else {
|
|
1019
|
-
// For non-selected or short content without newlines, show first line only
|
|
1020
977
|
const firstLine = task.content.split('\n')[0] || task.content;
|
|
1021
978
|
const content = firstLine.slice(0, maxContentWidth - 2);
|
|
1022
979
|
lines.push(`${bg} ${color}${star}${bracket}${content}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1023
980
|
}
|
|
1024
981
|
|
|
1025
|
-
// Show
|
|
1026
|
-
if (isSelected && this.focused && task.planPath && !isEditing) {
|
|
1027
|
-
const
|
|
1028
|
-
|
|
982
|
+
// Show spec preview or plan path when selected
|
|
983
|
+
if (isSelected && this.focused && (task.spec || task.planPath) && !isEditing) {
|
|
984
|
+
const detail = task.spec
|
|
985
|
+
? task.spec.split('\n')[0]?.slice(0, maxContentWidth - 4) || ''
|
|
986
|
+
: `→ ${task.planPath}`;
|
|
987
|
+
lines.push(`${bg} ${muted} ${detail.slice(0, maxContentWidth - 2)}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1029
988
|
}
|
|
1030
989
|
};
|
|
1031
990
|
|
|
1032
|
-
//
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
991
|
+
// === INBOX SECTION ===
|
|
992
|
+
const inboxTasks = this.getTasksForSection("inbox");
|
|
993
|
+
if (inboxTasks.length > 0 || inputMode === "add") {
|
|
994
|
+
const count = inboxTasks.length > 0 ? ` (${inboxTasks.length})` : '';
|
|
995
|
+
lines.push(`${bg} ${bold}${text}Inbox${count}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
996
|
+
inboxTasks.forEach((task, index) => renderTask(task, index, "inbox"));
|
|
1038
997
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
998
|
+
// Add new task input (in inbox section)
|
|
999
|
+
if (inputMode === "add") {
|
|
1000
|
+
inputLineRow = lines.length + 1;
|
|
1001
|
+
this.inputRow = inputLineRow;
|
|
1002
|
+
const wrappedLines = wrapText(inputBuffer, maxContentWidth - 2);
|
|
1003
|
+
if (wrappedLines.length === 0) wrappedLines.push('');
|
|
1004
|
+
wrappedLines.forEach((line, i) => {
|
|
1005
|
+
const prefix = i === 0 ? ' [ ] ' : ' ';
|
|
1006
|
+
const padding = ' '.repeat(Math.max(0, maxContentWidth - 2 - line.length));
|
|
1007
|
+
lines.push(`${bg} ${prefix}${text}${line}${padding}${ansi.reset}`);
|
|
1008
|
+
});
|
|
1009
|
+
} else if (this.focused && selectedSection === "inbox") {
|
|
1010
|
+
lines.push(`${bg} ${ansi.gray} [ ] press a to add${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1011
|
+
}
|
|
1012
|
+
lines.push(bgLine);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// === CLARIFIED SECTION ===
|
|
1016
|
+
const clarifiedTasks = this.getTasksForSection("clarified");
|
|
1017
|
+
if (clarifiedTasks.length > 0) {
|
|
1018
|
+
lines.push(`${bg} ${bold}${text}Clarified (${clarifiedTasks.length})${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1019
|
+
clarifiedTasks.forEach((task, index) => renderTask(task, index, "clarified"));
|
|
1020
|
+
lines.push(bgLine);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// === IN PROGRESS SECTION ===
|
|
1024
|
+
// Show active task with Claude's TodoWrite items indented as subtasks
|
|
1025
|
+
lines.push(`${bg} ${bold}${text}In Progress${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1026
|
+
|
|
1027
|
+
if (activeTask) {
|
|
1028
|
+
const content = activeTask.content.slice(0, maxContentWidth);
|
|
1029
|
+
lines.push(`${bg} ${ansi.green}▸ ${content}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1030
|
+
|
|
1031
|
+
// Show Claude's TodoWrite items indented as subtasks
|
|
1032
|
+
claudeTodos.forEach((todo) => {
|
|
1033
|
+
let statusIcon: string;
|
|
1034
|
+
let todoColor = text;
|
|
1035
|
+
let strike = '';
|
|
1036
|
+
let strikeEnd = '';
|
|
1037
|
+
|
|
1038
|
+
if (todo.status === "completed") {
|
|
1039
|
+
statusIcon = "✓ ";
|
|
1040
|
+
todoColor = muted;
|
|
1041
|
+
strike = strikethrough;
|
|
1042
|
+
strikeEnd = noStrike;
|
|
1043
|
+
} else if (todo.status === "in_progress") {
|
|
1044
|
+
statusIcon = "● ";
|
|
1045
|
+
todoColor = ansi.green;
|
|
1046
|
+
} else {
|
|
1047
|
+
statusIcon = "○ ";
|
|
1048
|
+
}
|
|
1049
|
+
const todoContent = todo.content.slice(0, maxContentWidth - 4); // Extra indent
|
|
1050
|
+
lines.push(`${bg} ${todoColor}${strike}${statusIcon}${todoContent}${strikeEnd}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1051
|
+
});
|
|
1052
|
+
} else if (claudeTodos.length > 0) {
|
|
1053
|
+
// No active task but Claude has todos
|
|
1054
|
+
claudeTodos.forEach((todo) => {
|
|
1055
|
+
let statusIcon: string;
|
|
1056
|
+
let todoColor = text;
|
|
1057
|
+
let strike = '';
|
|
1058
|
+
let strikeEnd = '';
|
|
1059
|
+
|
|
1060
|
+
if (todo.status === "completed") {
|
|
1061
|
+
statusIcon = "✓ ";
|
|
1062
|
+
todoColor = muted;
|
|
1063
|
+
strike = strikethrough;
|
|
1064
|
+
strikeEnd = noStrike;
|
|
1065
|
+
} else if (todo.status === "in_progress") {
|
|
1066
|
+
statusIcon = "● ";
|
|
1067
|
+
todoColor = ansi.green;
|
|
1068
|
+
} else {
|
|
1069
|
+
statusIcon = "○ ";
|
|
1070
|
+
}
|
|
1071
|
+
const todoContent = todo.content.slice(0, maxContentWidth);
|
|
1072
|
+
lines.push(`${bg} ${todoColor}${strike}${statusIcon}${todoContent}${strikeEnd}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1049
1073
|
});
|
|
1050
|
-
} else
|
|
1051
|
-
|
|
1052
|
-
|
|
1074
|
+
} else {
|
|
1075
|
+
lines.push(`${bg} ${muted} (nothing active)${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1076
|
+
}
|
|
1077
|
+
lines.push(bgLine);
|
|
1078
|
+
|
|
1079
|
+
// === REVIEW SECTION ===
|
|
1080
|
+
if (doneTasks.length > 0) {
|
|
1081
|
+
lines.push(`${bg} ${bold}${text}Review (${doneTasks.length})${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1082
|
+
doneTasks.slice(0, 5).forEach((task, index) => {
|
|
1083
|
+
const isSelected = selectedSection === "review" && index === selectedIndex && this.focused;
|
|
1084
|
+
const content = task.content.slice(0, maxContentWidth);
|
|
1085
|
+
const icon = isSelected ? "[✓] " : " ✓ ";
|
|
1086
|
+
const color = isSelected ? text : muted;
|
|
1087
|
+
// Strikethrough for completed items
|
|
1088
|
+
lines.push(`${bg} ${color}${strikethrough}${icon}${content}${noStrike}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1089
|
+
});
|
|
1090
|
+
lines.push(bgLine);
|
|
1053
1091
|
}
|
|
1054
1092
|
|
|
1055
1093
|
// Fill remaining space
|
|
@@ -1064,10 +1102,14 @@ export class RawSidebar {
|
|
|
1064
1102
|
let helpText: string;
|
|
1065
1103
|
if (inputMode !== "none") {
|
|
1066
1104
|
helpText = "↵: submit | ⇧↵: newline | Esc: cancel";
|
|
1067
|
-
} else if (selectedSection === "
|
|
1068
|
-
helpText = "d: done | r: return
|
|
1105
|
+
} else if (selectedSection === "review") {
|
|
1106
|
+
helpText = "d: confirm done | r: return | ↑↓: nav | Tab: section";
|
|
1107
|
+
} else if (selectedSection === "inbox") {
|
|
1108
|
+
helpText = "a: add | c: clarify | ↵: send | d: del | Tab: section";
|
|
1109
|
+
} else if (selectedSection === "clarified") {
|
|
1110
|
+
helpText = "↵: send | e: edit | d: del | Tab: section";
|
|
1069
1111
|
} else {
|
|
1070
|
-
helpText = "
|
|
1112
|
+
helpText = "↑↓: navigate | Tab: switch section";
|
|
1071
1113
|
}
|
|
1072
1114
|
lines.push(`${bg} ${muted}${helpText}${ansi.reset}${bg}${ansi.clearToEnd}${ansi.reset}`);
|
|
1073
1115
|
lines.push(bgLine);
|
package/src/persistence/store.ts
CHANGED
|
@@ -48,14 +48,18 @@ function updateProjectMapping(): void {
|
|
|
48
48
|
writeFileSync(mappingPath, JSON.stringify(mapping, null, 2));
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export type TaskSection = "inbox" | "clarified" | "in_progress" | "review";
|
|
52
|
+
|
|
51
53
|
export interface Task {
|
|
52
54
|
id: string;
|
|
53
55
|
content: string;
|
|
54
56
|
createdAt: string;
|
|
57
|
+
section?: TaskSection; // Which section the task belongs to (default: inbox)
|
|
55
58
|
clarified?: boolean; // Was this task clarified via /clarify skill?
|
|
56
59
|
priority?: number; // Sort order (lower = higher priority, 1 = most important)
|
|
57
60
|
recommended?: boolean; // Claude's top picks (shown with star)
|
|
58
61
|
planPath?: string; // Filename of associated Atomic Plan
|
|
62
|
+
spec?: string; // Spec content for the task (from /clarify)
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
export interface ActiveTask {
|
|
@@ -162,10 +166,12 @@ export function setTasks(tasks: Task[]): void {
|
|
|
162
166
|
export function addTask(
|
|
163
167
|
content: string,
|
|
164
168
|
options?: {
|
|
169
|
+
section?: TaskSection;
|
|
165
170
|
clarified?: boolean;
|
|
166
171
|
priority?: number;
|
|
167
172
|
recommended?: boolean;
|
|
168
173
|
planPath?: string;
|
|
174
|
+
spec?: string;
|
|
169
175
|
}
|
|
170
176
|
): Task {
|
|
171
177
|
const tasks = getTasks();
|
|
@@ -173,10 +179,12 @@ export function addTask(
|
|
|
173
179
|
id: crypto.randomUUID(),
|
|
174
180
|
content,
|
|
175
181
|
createdAt: new Date().toISOString(),
|
|
182
|
+
section: options?.section || "inbox",
|
|
176
183
|
clarified: options?.clarified,
|
|
177
184
|
priority: options?.priority,
|
|
178
185
|
recommended: options?.recommended,
|
|
179
186
|
planPath: options?.planPath,
|
|
187
|
+
spec: options?.spec,
|
|
180
188
|
};
|
|
181
189
|
tasks.push(task);
|
|
182
190
|
setTasks(tasks);
|
|
@@ -192,11 +200,22 @@ export function updateTask(id: string, content: string): void {
|
|
|
192
200
|
}
|
|
193
201
|
}
|
|
194
202
|
|
|
195
|
-
export function markTaskClarified(id: string): void {
|
|
203
|
+
export function markTaskClarified(id: string, spec?: string): void {
|
|
196
204
|
const tasks = getTasks();
|
|
197
205
|
const task = tasks.find((t) => t.id === id);
|
|
198
206
|
if (task) {
|
|
199
207
|
task.clarified = true;
|
|
208
|
+
task.section = "clarified";
|
|
209
|
+
if (spec) task.spec = spec;
|
|
210
|
+
setTasks(tasks);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function setTaskSection(id: string, section: TaskSection): void {
|
|
215
|
+
const tasks = getTasks();
|
|
216
|
+
const task = tasks.find((t) => t.id === id);
|
|
217
|
+
if (task) {
|
|
218
|
+
task.section = section;
|
|
200
219
|
setTasks(tasks);
|
|
201
220
|
}
|
|
202
221
|
}
|