Skip to main content

max / goingson

The dashboard, the README and the changelog catch up with dependencies Three loose ends from the dependency work, none of them behavioural except the first. The dashboard's high-urgency list ordered on the `urgency` column alone, so a blocked task could head it while sitting at the bottom of the list it links to. It reads the effective score now, like every other task query. The go-mcp README documented a tool surface that is four tools short and describes two others as they were before the graph existed.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-09 19:57 UTC
Signed with PGP, not checked
Commit: a83db71fb3a09e4c0791a088997ff285d92a175c
Parent: 338aa9f
3 files changed, +40 insertions, -8 deletions
M CHANGELOG.md +12
@@ -6,6 +6,18 @@
6 6
7 7 ## [Unreleased]
8 8
9 + ### Added
10 + - Tasks can block each other. An edge records that one task must be completed
11 + before another can start, and blocked work no longer reads as available:
12 + it sinks in every list, carries a marker in the task list, and is excluded
13 + from the ready-work queries. A blocker rises by how much finishing it frees.
14 + - A Graph view under Work draws the blocking structure, laid out in columns by
15 + how many completions each task is from being startable.
16 + - The task detail panel gains a Dependencies section for adding and removing
17 + blockers, showing both what a task waits on and what waits on it.
18 + - Dependency cycles are refused when drawn and reported when they arrive
19 + through sync, since nothing on a cycle can ever become available.
20 +
9 21 ## [0.5.0] — 2026-07-16
10 22
11 23 ### Removed
@@ -20,8 +20,8 @@
20 20 - `--db` defaults to the desktop app's database (`com.goingson.app`'s
21 21 `app_data_dir`). The database must already exist; run GoingsOn once to create
22 22 the schema and the single desktop user.
23 - - Reads (`list_projects`, `list_tasks`, `get_task`, `list_events`, `get_event`)
24 - are always callable.
23 + - Reads (`list_projects`, `list_tasks`, `get_task`, `list_ready_tasks`,
24 + `get_task_graph`, `list_events`, `get_event`) are always callable.
25 25 - Writes are refused unless their capability is granted. Grant them with
26 26 `--grant go.task.bulk_import` (repeatable) or `--grant-all` for a fully-trusted
27 27 local session.
@@ -35,11 +35,25 @@
35 35
36 36 - `list_projects`: id, name, type, status. Every write that takes a project
37 37 takes its `project_id`, so this is how a name becomes an id.
38 - - `list_tasks(project_id?, project?, status?, tag?, limit?, offset?)`: compact task rows,
39 - paged (default 50, max 200). Descriptions over 240 chars are clipped and the
40 - row marked `truncated`; `get_task` has the full text. The reply carries
38 + - `list_tasks(project_id?, project?, status?, tag?, blocked?, limit?, offset?)`: compact task
39 + rows, paged (default 50, max 200). Descriptions over 240 chars are clipped and
40 + the row marked `truncated`; `get_task` has the full text. The reply carries
41 41 `total` and, while pages remain, `next_offset`. Walk until it is absent.
42 - - `get_task(id)`: one task with subtasks and annotations.
42 + `blocked` filters on the dependency graph: true keeps only tasks waiting on
43 + something unfinished, false only startable ones.
44 + - `get_task(id)`: one task with subtasks, annotations, and its `blocked_by` /
45 + `blocks` edges. Every edge is listed, satisfied ones included, each carrying
46 + `satisfied`, so what is still in the way is distinguishable from what the task
47 + once waited for.
48 + - `list_ready_tasks(project_id?, depth?, limit?)`: the work that can actually be
49 + started, shallowest first then by urgency. `depth` (default 0) is how many
50 + blocker-hops back to include, so 0 is available now and 1 also returns what
51 + opens after one more completion. Prefer this over `list_tasks` when the
52 + question is "what should I do next".
53 + - `get_task_graph(project_id?)`: nodes, edges and cycles in one call, for
54 + reasoning about ordering across a project. A project scope still includes
55 + blockers living in other projects, since those are why its tasks are not
56 + ready. Tasks with no edges either way are omitted.
43 57 - `list_events(from?, to?, project_id?, project?, recurring_only?, expand?, limit?, offset?)`:
44 58 calendar events in a window, paged on the same terms as `list_tasks`. Defaults
45 59 to the next 30 days. See "Events and recurrence" below for what `expand` does.
@@ -58,6 +72,8 @@
58 72 | `bulk_import_tasks([...])`, the `/dellm` primitive | `go.task.bulk_import` |
59 73 | `update_task(id, fields)`, overlays only the fields you pass | `go.task.update` |
60 74 | `complete_task(id)` | `go.task.complete` |
75 + | `add_dependency(blocked_id, blocker_id)`, idempotent, refuses a cycle | `go.task.dependency.add` |
76 + | `remove_dependency(blocked_id, blocker_id)` | `go.task.dependency.remove` |
61 77 | `report_problems([...], source?)`, the `/audit` and `/fuzz` primitive | `go.problem.report` |
62 78 | `promote_problem(id, description?, priority?)`, problem to task | `go.problem.promote` |
63 79 | `update_problem(id, {status?, project_id?})`, triage state and attribution | `go.problem.update` |
@@ -157,11 +157,15 @@
157 157 }
158 158 }
159 159
160 + // The effective score, base plus graph term, matching every task list
161 + // and `Task::effective_urgency`. Ordering on the base column alone would
162 + // let a blocked task top the dashboard while sitting at the bottom of
163 + // the list it links to, which reads as a bug in one of the two.
160 164 let rows = query_all(
161 165 &conn,
162 - "SELECT id, title, urgency, status, due FROM tasks \
166 + "SELECT id, title, (urgency + graph_urgency) AS urgency, status, due FROM tasks \
163 167 WHERE user_id = ? AND status NOT IN ('Completed', 'Deleted') \
164 - ORDER BY urgency DESC LIMIT 5",
168 + ORDER BY (urgency + graph_urgency) DESC LIMIT 5",
165 169 params![&user_id_str],
166 170 HighUrgencyRow::from_row,
167 171 )?;