From 06b6cbcf086729414ff9a533b1f031fb41c4088b Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Wed, 24 Jun 2026 07:00:48 -0400 Subject: feat(tasks): make cross-project dependencies bidirectional The :blocked: tag only marked the waiting side, so a blocker could stay unaware it was holding up another project: the dependency was visible to the one project that couldn't act on it and invisible to the one that could. This closes that gap. Setting :blocked: now requires a reciprocal inbox-send to the blocker, which files the work with a :BLOCKS: : property on its side. open-tasks.org surfaces :BLOCKS: tasks first, since clearing one unblocks another project (the highest-leverage pick), the mirror of pulling :blocked: tasks out of the cascade. Inbox process mode recognizes the blocking-dependency handoff shape, and the convention documents the resolution flow (drop :BLOCKS:, notify the waiter, who lifts :blocked:). This works for any project pair, since the convention (todo-format.md) and the surfacing (open-tasks.org) live in the shared rule and workflow layer, not in one project. Claude-Session: https://claude.ai/code/session_017PtX1nt1rtYVATuzmzBS4f --- claude-rules/todo-format.md | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) (limited to 'claude-rules') diff --git a/claude-rules/todo-format.md b/claude-rules/todo-format.md index 0bfd3d3..7bc7299 100644 --- a/claude-rules/todo-format.md +++ b/claude-rules/todo-format.md @@ -268,7 +268,11 @@ are noise that pollute his `cj:` greps. A task can be blocked by work that has to happen in a *different project* — a rulesets task that can't finish until `.emacs.d` ships a companion function, say. Left unmarked, two things go wrong: the what's-next workflow keeps recommending the blocked task even though it can't move, and the blocker sits at low priority in the other project, so the dependency stalls silently. -Mark the dependency with the `:blocked:` tag plus a `:BLOCKED_BY:` property: +The dependency is tracked on *both* sides so neither the waiter nor the blocker loses sight of it: the waiting task carries `:blocked:` + `:BLOCKED_BY:`, and the blocking project gets a reciprocal handoff that becomes a `:BLOCKS:`-marked task on its side. This applies to *any* project pair — the convention here and the surfacing in `open-tasks.org` live in the shared rule + workflow layer, not in one project. + +### The waiting side — `:blocked:` + `:BLOCKED_BY:` + +Mark the dependent task with the `:blocked:` tag plus a `:BLOCKED_BY:` property: ``` ** DOING [#B] Wrap-teardown feature :feature:blocked: @@ -278,11 +282,39 @@ Mark the dependency with the `:blocked:` tag plus a `:BLOCKED_BY:` property: Body... ``` -- The `:blocked:` tag on the heading is the filterable marker — it's what `open-tasks.org` reads to pull the task out of the "do this next" recommendation. -- The `:BLOCKED_BY:` property names *which* project blocks the task and *what* that project owes, as `: `. The project token is the short basename (`emacsd`, `home`, `work`), matching the inbox/handoff naming. +- The `:blocked:` tag on the heading is the filterable marker — `open-tasks.org` reads it to pull the task out of the "do this next" recommendation. +- The `:BLOCKED_BY:` property names *which* project blocks the task and *what* it owes, as `: `. The project token is the short basename (`emacsd`, `home`, `work`), matching the inbox/handoff naming. + +### Registering with the blocker — the reciprocal handoff (required) + +Setting `:blocked:` is not complete until the blocking project knows it's blocking. The moment you add `:blocked:` + `:BLOCKED_BY: : ...`, send `` a dependency handoff: + +``` +inbox-send --text "Blocking dependency: 's task \"\" is blocked on you — it needs . It stays :blocked: until this lands. File it on your side as a task with :BLOCKS: : so it surfaces as priority work." +``` + +This is what closes the gap: without it, the blocker only learns it's blocking by accident. The handoff lands in ``'s `inbox/` and its normal inbox processing files it (below). A `:blocked:` task with no matching reciprocal handoff is half-done — the dependency is invisible to the one project that can clear it. (Skip the send only when the blocker demonstrably already tracks the work, e.g. it's the same handoff that spawned the dependency; the blocker dedups against an existing task either way.) + +### The blocking side — `:BLOCKS:` + +When a project processes a blocking-dependency handoff (inbox process mode), it files or links the relevant task and marks it with the inverse property — a `:BLOCKS:` property naming the project + work it's holding up: + +``` +** TODO [#B] ai-term wrap-teardown companion :feature: +:PROPERTIES: +:BLOCKS: rulesets: wrap-teardown feature (the three ai-term functions) +:END: +``` + +The blocking task does *not* carry `:blocked:` — it isn't blocked, it's the blocker. `:BLOCKS:` is a priority signal: `open-tasks.org` surfaces a `:BLOCKS:` task *first*, since clearing it unblocks work in another project, so a dependency that would otherwise stall at low priority gets pulled forward. This is the "surface dependencies first" half of the design. + +### Resolving the dependency + +When the blocker delivers: -A task stays `:blocked:` only while the external dependency is genuinely outstanding. When the other project delivers, or the dependency dissolves, drop the `:blocked:` tag and the `:BLOCKED_BY:` property in the same edit — the task is workable again. +1. The blocking project completes its `:BLOCKS:` task, drops the `:BLOCKS:` property, and notifies the waiter (`inbox-send --text "Delivered: — you're unblocked."`). +2. The waiting project drops the `:blocked:` tag and `:BLOCKED_BY:` property in the same edit; the task is workable again. Either side noticing the delivery can lift its own marker — the notification just makes it prompt. -`open-tasks.org` Next Mode surfaces `:blocked:` tasks in a dedicated "Blocked on other projects" section instead of the cascade, naming each blocker so a stalled cross-project dependency stays visible (and can be nudged via `inbox-send`) rather than rotting. See that workflow for the surfacing behavior. +### Not the same as VERIFY -This is distinct from `VERIFY`, which marks "waiting on Craig's input." `:blocked:` marks "waiting on another *project's* work." If Craig's input is what's needed, it's a VERIFY, not `:blocked:`. +`:blocked:` marks "waiting on another *project's* work"; `VERIFY` marks "waiting on Craig's input." If Craig's input is what's needed, it's a VERIFY, not `:blocked:`. And `:BLOCKS:` only ever sits on the project that *owes* the work, never the one waiting. -- cgit v1.2.3