Skip to content

Commit 67ad91c

Browse files
committed
feat: separate backlog from actionable items in daily briefing
Backlog items were mixed into the high-priority list, making it hard to distinguish actionable work from parked items. Now backlog is a separate bucket with per-project open/backlog counts, and the skill instructs Claude to present a structured table overview.
1 parent 01e3286 commit 67ad91c

3 files changed

Lines changed: 123 additions & 15 deletions

File tree

pkg/api/briefing.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@ import (
1010

1111
// ProjectBriefing summarises a single project for the daily briefing.
1212
type ProjectBriefing struct {
13-
Name string `json:"name"`
14-
TaskCount int `json:"task_count"`
15-
Summary string `json:"summary,omitempty"` // text from ## Summary section of description.md
13+
Name string `json:"name"`
14+
TaskCount int `json:"task_count"`
15+
OpenCount int `json:"open_count"`
16+
BacklogCount int `json:"backlog_count"`
17+
Summary string `json:"summary,omitempty"` // text from ## Summary section of description.md
1618
}
1719

1820
// DailyBriefing provides a comprehensive overview for starting the day.
1921
type DailyBriefing struct {
2022
Summary struct {
2123
TotalOpen int `json:"total_open"`
24+
TotalBacklog int `json:"total_backlog"`
2225
Overdue int `json:"overdue"`
2326
DueToday int `json:"due_today"`
2427
DueThisWeek int `json:"due_this_week"`
@@ -39,6 +42,7 @@ type DailyBriefing struct {
3942
HighPriority []TodoItem `json:"high_priority"`
4043
InProgress []TodoItem `json:"in_progress"`
4144
Blocked []TodoItem `json:"blocked"`
45+
Backlog []TodoItem `json:"backlog"`
4246
Context BriefingContext `json:"context"`
4347
}
4448

@@ -114,8 +118,11 @@ func GetDailyBriefing(activeDir, dumpPath string) (*DailyBriefing, error) {
114118

115119
// Single-pass categorisation
116120
var openTodos []TodoItem
121+
var backlogTodos []TodoItem
117122
var completedRecently []TodoItem
118123
projectTaskCount := make(map[string]int)
124+
projectOpenCount := make(map[string]int)
125+
projectBacklogCount := make(map[string]int)
119126

120127
// urgentIDs tracks items already in overdue/due_today to deduplicate HighPriority
121128
urgentIDs := make(map[string]bool)
@@ -128,9 +135,18 @@ func GetDailyBriefing(activeDir, dumpPath string) (*DailyBriefing, error) {
128135
continue
129136
}
130137

131-
openTodos = append(openTodos, todo)
132138
projectTaskCount[todo.Project]++
133139

140+
// Backlog items are tracked separately — they don't appear in actionable buckets
141+
if todo.Status == "backlog" {
142+
backlogTodos = append(backlogTodos, todo)
143+
projectBacklogCount[todo.Project]++
144+
continue
145+
}
146+
147+
openTodos = append(openTodos, todo)
148+
projectOpenCount[todo.Project]++
149+
134150
// Date-based urgency categorisation
135151
if todo.DueDate != "" {
136152
if todo.DueDate < today {
@@ -206,6 +222,10 @@ func GetDailyBriefing(activeDir, dumpPath string) (*DailyBriefing, error) {
206222
return priorityLess(briefing.Blocked[i].Priority, briefing.Blocked[j].Priority)
207223
})
208224

225+
sort.Slice(backlogTodos, func(i, j int) bool {
226+
return priorityLess(backlogTodos[i].Priority, backlogTodos[j].Priority)
227+
})
228+
209229
sort.Slice(completedRecently, func(i, j int) bool {
210230
return completedRecently[i].CompletedDate > completedRecently[j].CompletedDate
211231
})
@@ -219,6 +239,7 @@ func GetDailyBriefing(activeDir, dumpPath string) (*DailyBriefing, error) {
219239
briefing.Summary.InProgress = len(briefing.InProgress)
220240
briefing.Summary.Blocked = len(briefing.Blocked)
221241
briefing.Summary.InboxCount = len(dumpItems)
242+
briefing.Summary.TotalBacklog = len(backlogTodos)
222243
briefing.Summary.CompletionsLast3Days = len(completedRecently)
223244
// DueNextMonth was incremented inline above
224245

@@ -240,9 +261,11 @@ func GetDailyBriefing(activeDir, dumpPath string) (*DailyBriefing, error) {
240261
summary = extractSummarySection(desc)
241262
}
242263
briefing.Summary.ProjectSummaries = append(briefing.Summary.ProjectSummaries, ProjectBriefing{
243-
Name: name,
244-
TaskCount: taskCount,
245-
Summary: summary,
264+
Name: name,
265+
TaskCount: taskCount,
266+
OpenCount: projectOpenCount[name],
267+
BacklogCount: projectBacklogCount[name],
268+
Summary: summary,
246269
})
247270
}
248271
// Sort by task count descending (most active project first)
@@ -277,6 +300,9 @@ func GetDailyBriefing(activeDir, dumpPath string) (*DailyBriefing, error) {
277300
}
278301
}
279302

303+
// Fill backlog
304+
briefing.Backlog = backlogTodos
305+
280306
// Fill context
281307
briefing.Context.RecentCompletions = completedRecently
282308
briefing.Context.InboxItems = dumpItems

pkg/api/briefing_test.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ func TestGetDailyBriefing_Categorization(t *testing.T) {
8484
"- [ ] High priority task p:1",
8585
"- [>] In progress task",
8686
"- [-] Blocked task",
87+
"- [~] Backlog task",
8788
}
8889
completedLines := []string{
8990
fmt.Sprintf("- [x] Completed task <!-- done:%s -->", threeDaysAgo),
@@ -120,10 +121,17 @@ func TestGetDailyBriefing_Categorization(t *testing.T) {
120121
if got := briefing.Summary.CompletionsLast3Days; got != 1 {
121122
t.Errorf("expected 1 recent completion, got %d", got)
122123
}
123-
// 7 open tasks (overdue + today + this week + upcoming + high-prio + in-progress + blocked)
124+
// 7 open (non-backlog) tasks
124125
if got := briefing.Summary.TotalOpen; got != 7 {
125126
t.Errorf("expected 7 total open, got %d", got)
126127
}
128+
// 1 backlog task
129+
if got := briefing.Summary.TotalBacklog; got != 1 {
130+
t.Errorf("expected 1 total backlog, got %d", got)
131+
}
132+
if got := len(briefing.Backlog); got != 1 {
133+
t.Errorf("expected 1 backlog item, got %d", got)
134+
}
127135
}
128136

129137
func TestGetDailyBriefing_Deduplication(t *testing.T) {
@@ -181,6 +189,59 @@ func TestGetDailyBriefing_Deduplication(t *testing.T) {
181189
}
182190
}
183191

192+
func TestGetDailyBriefing_BacklogExcludedFromHighPriority(t *testing.T) {
193+
tb := testutil.SetupTestBrain(t)
194+
195+
projectDir := tb.AddProject("backlog-test")
196+
tb.WriteFile(filepath.Join(projectDir, "todo.md"), makeTodoMD(
197+
"- [ ] Open high priority p:1",
198+
"- [~] Backlog high priority p:1",
199+
"- [~] Backlog normal task",
200+
"- [ ] Open normal task",
201+
))
202+
203+
briefing, err := GetDailyBriefing(tb.ActiveDirPath, tb.DumpPath)
204+
if err != nil {
205+
t.Fatalf("unexpected error: %v", err)
206+
}
207+
208+
// Only the open p:1 item should be in HighPriority
209+
if got := len(briefing.HighPriority); got != 1 {
210+
t.Errorf("expected 1 high priority (open only), got %d", got)
211+
}
212+
if len(briefing.HighPriority) > 0 && briefing.HighPriority[0].Status != "open" {
213+
t.Errorf("expected high priority item to be open, got %q", briefing.HighPriority[0].Status)
214+
}
215+
216+
// Backlog should contain both backlog items
217+
if got := len(briefing.Backlog); got != 2 {
218+
t.Errorf("expected 2 backlog items, got %d", got)
219+
}
220+
221+
// TotalOpen should not include backlog
222+
if got := briefing.Summary.TotalOpen; got != 2 {
223+
t.Errorf("expected 2 total open (excluding backlog), got %d", got)
224+
}
225+
if got := briefing.Summary.TotalBacklog; got != 2 {
226+
t.Errorf("expected 2 total backlog, got %d", got)
227+
}
228+
229+
// Project summary should show open/backlog split
230+
if got := len(briefing.Summary.ProjectSummaries); got != 1 {
231+
t.Fatalf("expected 1 project summary, got %d", got)
232+
}
233+
ps := briefing.Summary.ProjectSummaries[0]
234+
if ps.OpenCount != 2 {
235+
t.Errorf("expected open_count=2, got %d", ps.OpenCount)
236+
}
237+
if ps.BacklogCount != 2 {
238+
t.Errorf("expected backlog_count=2, got %d", ps.BacklogCount)
239+
}
240+
if ps.TaskCount != 4 {
241+
t.Errorf("expected task_count=4, got %d", ps.TaskCount)
242+
}
243+
}
244+
184245
func TestGetDailyBriefing_Sorting(t *testing.T) {
185246
tb := testutil.SetupTestBrain(t)
186247
now := time.Now()

pkg/skillscatalog/skills/brain-daily/SKILL.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,37 @@ Call `get_daily_briefing` to retrieve:
3737

3838
### Step 2 — Summarize Clearly
3939

40-
Present a concise, scannable summary. Do not dump raw data. Group by urgency:
40+
Present a concise, scannable summary. Do not dump raw data.
4141

42-
1. **Overdue** — flag prominently with task IDs
42+
#### Project Overview Table
43+
44+
Always start with a project overview table showing the distribution of tasks. Use the `project_summaries` data:
45+
46+
```
47+
| Project | Open | Backlog | Total |
48+
|--------------------|------|---------|-------|
49+
| agentic-project | 15 | 6 | 21 |
50+
| nsa2d | 8 | 3 | 11 |
51+
| ... | ... | ... | ... |
52+
```
53+
54+
**Open** = actionable tasks (status: open, in-progress, blocked). **Backlog** = parked for later (status: backlog). This distinction is critical — backlog items are not on the user's plate today.
55+
56+
#### Actionable Items
57+
58+
After the table, list actionable items grouped by urgency. Only show items with status `open`, `in-progress`, or `blocked` — never backlog:
59+
60+
1. **Overdue** — flag prominently with task IDs and due dates
4361
2. **Due today** — list with IDs
44-
3. **High priority** — P1 tasks without due dates
45-
4. **In progress** — what was already underway
46-
5. **Backlog** — count of items in backlog across projects (brief; this is for awareness, not action)
47-
6. **Inbox** — count of quick-capture items waiting
62+
3. **In progress** — what is already underway
63+
4. **High priority (open)** — P1 tasks that are open (not backlog), without due dates
64+
5. **Blocked** — items needing unblock
65+
66+
Keep it to 10 actionable items max; offer to drill into a project if there's more.
67+
68+
#### Backlog Awareness
4869

49-
Keep it to 10 items max; offer to drill into an area if there's more.
70+
End with a single line like: "**Backlog:** 12 items across 4 projects parked for later." Do not list individual backlog items unless the user asks.
5071

5172
If everything is clear (nothing overdue, nothing due, low inbox):
5273
> "Clean slate today. Here's what's in progress and what you could pick up next."

0 commit comments

Comments
 (0)