aboutsummaryrefslogtreecommitdiff
path: root/.ai
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-24 07:00:48 -0400
committerCraig Jennings <c@cjennings.net>2026-06-24 07:00:48 -0400
commit06b6cbcf086729414ff9a533b1f031fb41c4088b (patch)
treefd9ce04b5b749567ebcb8bf65ce54e2bee5af7fb /.ai
parent0127889a41fc4f870def2982d822023e0dcb49dd (diff)
downloadrulesets-06b6cbcf086729414ff9a533b1f031fb41c4088b.tar.gz
rulesets-06b6cbcf086729414ff9a533b1f031fb41c4088b.zip
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: <project>: <what> 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
Diffstat (limited to '.ai')
-rw-r--r--.ai/workflows/inbox.org2
-rw-r--r--.ai/workflows/open-tasks.org9
2 files changed, 11 insertions, 0 deletions
diff --git a/.ai/workflows/inbox.org b/.ai/workflows/inbox.org
index c442d17..f3d400a 100644
--- a/.ai/workflows/inbox.org
+++ b/.ai/workflows/inbox.org
@@ -114,6 +114,8 @@ The item extends a task already filed. Update the parent TODO's body with a date
** File as TODO
Substantive but waits, or needs design/triage before implementation. Add the TODO under =* <Project> Open Work= with priority + tags per the priority-scheme check (core §6). Body summarizes the proposal and links the inbox content if it's been moved to =docs/design/=. Delete the inbox file (or move it to =docs/design/= first if the content survives).
+*Blocking-dependency handoff.* A special shape: another project sends a note that *this* project's work is blocking one of theirs ("your task X is blocked on us — we need Y"). File or link the owning task and add a =:BLOCKS: <their-project>: <what>= property to it (see the cross-project dependency convention in =todo-format.md=). The =:BLOCKS:= marker makes =open-tasks.org= surface that task *first*, since clearing it unblocks the other project. Dedup against an existing task rather than filing a duplicate. When the work later lands, drop =:BLOCKS:= and notify the waiting project (=inbox-send <their-project> --text "Delivered: <what> — you're unblocked."=) so it can lift its own =:blocked:=.
+
** Defer
Rename in place to =inbox/PROCESSED-<original-filename>= and add a brief comment line at the top: =# Deferred YYYY-MM-DD: <condition>=. Don't accumulate deferred items indefinitely — sweep them on a future process pass when the condition is met or the deferral has aged out.
diff --git a/.ai/workflows/open-tasks.org b/.ai/workflows/open-tasks.org
index 7f1bf1a..873fc0e 100644
--- a/.ai/workflows/open-tasks.org
+++ b/.ai/workflows/open-tasks.org
@@ -178,6 +178,8 @@ Apply the prioritization cascade in order. Stop at the first matching step. This
*Exclude blocked tasks.* A task tagged =:blocked:= has an unmet cross-project dependency (its =:BLOCKED_BY:= property names the project and the work owed, per =todo-format.md=). It can't be worked until that other project delivers, so it is *never* the cascade recommendation — skip it at every cascade step below. Blocked tasks are surfaced on their own in Step 3 so the stalled dependency stays visible instead of silently dropping out of view.
+*Surface blocking tasks first.* The mirror of the above: a task carrying a =:BLOCKS:= property is holding up work in *another* project (the property names which project and what's owed, per =todo-format.md=). Clearing it unblocks that project, so it carries borrowed urgency — surface it at the *top* of the cascade recommendation regardless of its own priority cookie, ahead of the normal In-Progress / deadline / priority order. When several =:BLOCKS:= tasks exist, lead with the one blocking the most, or the longest. This is the "do the thing that unblocks someone else first" rule; a =:BLOCKS:= task left at its own low priority is exactly how a cross-project dependency stalls.
+
**** 1. In-Progress Tasks
- Look for tasks marked =DOING= or partially complete.
- *If found:* Recommend that task (always finish what's started).
@@ -243,6 +245,9 @@ If no =:blocked:= tasks exist, omit this surface entirely (the common case).
Pair the cascade recommendation with the friction block beneath it, and the blocked-on-other-projects surface (Step 3) beneath that when any blocked task exists. Recommendation-at-item-1 convention applies to the friction rows — quick+solo first, since it's the strongest low-friction pick.
#+begin_example
+Unblocks other projects (do these first):
+- ai-term wrap-teardown companion — :BLOCKS: rulesets (the three ai-term functions)
+
Cascade recommendation (importance/urgency):
- Fix org-noter reliability — [#A], Method 1, 8/18 complete, blocks daily reading/annotation
@@ -255,16 +260,20 @@ Blocked on other projects (can't advance until the blocker delivers):
- Wrap-teardown feature — blocked by emacsd: ai-term companion functions — nudge?
#+end_example
+The =:BLOCKS:= surface sits at the very top — clearing one of those is the highest-leverage thing on the list, since it frees work in another project. Omit it when no =:BLOCKS:= task exists (the common case).
+
Include for each row:
- Task name / description.
- Priority + tag cluster.
- One-line reasoning. For the cascade row, name which cascade step matched. For friction rows, an effort hint when one is obvious.
- Progress indicator (for V2MOM-structured todos) on the cascade row only.
+- For a =:BLOCKS:= row: the project it unblocks and what's owed (from the =:BLOCKS:= property).
- For a blocked row: the blocking project and what it owes (from =:BLOCKED_BY:=), plus the nudge offer.
**** Edge cases
- *Empty friction block.* If no =:quick:= or =:solo:= tagged tasks exist in the open set, omit the friction block entirely. Present only the cascade recommendation.
+- *No =:BLOCKS:= tasks.* Omit the "Unblocks other projects" surface entirely (the common case) — show it only when a task carries a =:BLOCKS:= property.
- *Dedupe.* If the cascade recommendation IS the same task as one of the friction rows (e.g. it's =:quick:solo:= and also won the cascade), show it once at the top with both labels. Don't list it twice.
- *Decline behavior.* If the user declines the cascade recommendation, drop straight to the friction block as the natural next prompt. Do not fall through to lower-cascade-tier tasks; the friction filter IS the override.