From 3627d5e61e2adc8cd196958f62579fcdf9d20889 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Wed, 20 May 2026 13:41:13 -0400 Subject: docs(workflows): swap wrap-up date-coverage scan for task-review health check The date-coverage scan flagged every open [#A]/[#B] task with no DEADLINE or SCHEDULED, on the assumption that high-priority work needs a date. That assumption is wrong (research and watch-list tasks are legitimately dateless), so the scan generated dismiss-or-paper-over cleanup at every wrap-up. The replacement watches the daily task-review habit instead. It calls task-review-staleness.sh todo.org 30 and, when the count is non-zero, writes one summary line to the follow-ups file: N top-level tasks unreviewed for >30 days. There's no per-task dump, because the per-task walk is the review habit's job. Staleness, not datelessness, is the signal worth surfacing. The change lands in both the canonical workflow and the project mirror. --- .ai/workflows/wrap-it-up.org | 33 ++++++++------------------- claude-templates/.ai/workflows/wrap-it-up.org | 33 ++++++++------------------- 2 files changed, 20 insertions(+), 46 deletions(-) diff --git a/.ai/workflows/wrap-it-up.org b/.ai/workflows/wrap-it-up.org index e2a535b..fc8c398 100644 --- a/.ai/workflows/wrap-it-up.org +++ b/.ai/workflows/wrap-it-up.org @@ -174,11 +174,11 @@ Preview without writing — same flags as =--check= on the other scripts: The wrap-up never blocks on judgment items — they're deferred by design. For an interactive walk of the judgments mid-day, run =/lint-org todo.org=. -*** Date-coverage scan (surface =[#A]= / =[#B]= tasks lacking a timestamp) +*** Review-habit health check (surface a slipped daily task-review) -Scan =todo.org= for open =[#A]= and =[#B]= tasks that have neither a =DEADLINE:= nor a =SCHEDULED:= line directly under the heading, and surface the candidates to the follow-ups file so the morning's daily-prep flags them for review. +The daily task-review habit walks the open top-level tasks on a rotating cycle, stamping =:LAST_REVIEWED:= as it goes (see =task-review.org=). This check is the watchdog for that habit. When tasks have gone too long unreviewed, the habit has slipped, and the wrap-up says so in one line — it does not re-list the tasks. -The two timestamps mean different things (=DEADLINE:= = external, consequence-bearing; =SCHEDULED:= = social, accountability-bearing — see the priority spec at the top of =todo.org=). High-priority work that carries neither is suspicious: either it has an implicit deadline that should be made explicit, or it has someone waiting that should surface in the agenda, or its priority is wrong. The scan flags candidates; the operator decides. +=task-review-staleness.sh= counts top-level =[#A]= / =[#B]= / =[#C]= tasks (TODO/DOING/VERIFY) whose =:LAST_REVIEWED:= is missing or older than the threshold. Threshold 30 days is about 2.5 review cycles of slack at the default batch size — one missed week is fine, three weeks signals a problem. #+begin_src bash if [ -n "$LINT_ORG_FOLLOWUPS" ]; then @@ -188,29 +188,16 @@ elif [ -d "./inbox" ]; then else followups=".ai/lint-followups.org" fi -[ -f todo.org ] && awk ' - /^\*\* (TODO|DOING|VERIFY) \[#[AB]\]/ { - if (heading != "" && !has_date) print line ": " heading - heading = $0 - line = NR - has_date = 0 - next - } - /^(SCHEDULED|DEADLINE|CLOSED):/ { has_date = 1; next } - /^\*+ / { if (heading != "" && !has_date) print line ": " heading; heading = ""; next } - END { if (heading != "" && !has_date) print line ": " heading } -' todo.org > /tmp/date-coverage.out -if [ -s /tmp/date-coverage.out ]; then - { - printf "\n* %s — Date coverage: [#A] / [#B] tasks without DEADLINE or SCHEDULED\n" "$(date '+%Y-%m-%d %a')" - printf "%s\n" "Review each: add a date, drop the priority, or confirm 'no-date by intent' inline." - sed 's/^/- /' /tmp/date-coverage.out - } >> "$followups" +if [ -f todo.org ]; then + stale=$(.ai/scripts/task-review-staleness.sh todo.org 30 2>/dev/null || echo 0) + if [ "$stale" -gt 0 ]; then + printf "\n* %s — Task-review health: %s top-level [#A]/[#B]/[#C] tasks unreviewed for >30 days (daily review may have slipped)\n" \ + "$(date '+%Y-%m-%d %a')" "$stale" >> "$followups" + fi fi -rm -f /tmp/date-coverage.out #+end_src -The scan is intentionally conservative — it surfaces every candidate. False positives (tasks that legitimately have no date) are cheap to dismiss; false negatives would let high-priority work drift undated. No-date is a valid resting state for some tasks (research, watch-list), and the operator can mark those as such in the daily-prep review rather than tagging them in =todo.org=. +A non-zero count writes one summary line and nothing else — the per-task walk is the review habit's job, not the wrap-up's. This supersedes the old date-coverage scan, which flagged every dateless =[#A]= / =[#B]= task on the wrong assumption that high-priority work needs a date. No-date is a valid resting state for research and watch-list tasks; staleness, not datelessness, is the real signal. ** Step 3.5: Linear ticket-state hygiene (skip if project doesn't use Linear) diff --git a/claude-templates/.ai/workflows/wrap-it-up.org b/claude-templates/.ai/workflows/wrap-it-up.org index e2a535b..fc8c398 100644 --- a/claude-templates/.ai/workflows/wrap-it-up.org +++ b/claude-templates/.ai/workflows/wrap-it-up.org @@ -174,11 +174,11 @@ Preview without writing — same flags as =--check= on the other scripts: The wrap-up never blocks on judgment items — they're deferred by design. For an interactive walk of the judgments mid-day, run =/lint-org todo.org=. -*** Date-coverage scan (surface =[#A]= / =[#B]= tasks lacking a timestamp) +*** Review-habit health check (surface a slipped daily task-review) -Scan =todo.org= for open =[#A]= and =[#B]= tasks that have neither a =DEADLINE:= nor a =SCHEDULED:= line directly under the heading, and surface the candidates to the follow-ups file so the morning's daily-prep flags them for review. +The daily task-review habit walks the open top-level tasks on a rotating cycle, stamping =:LAST_REVIEWED:= as it goes (see =task-review.org=). This check is the watchdog for that habit. When tasks have gone too long unreviewed, the habit has slipped, and the wrap-up says so in one line — it does not re-list the tasks. -The two timestamps mean different things (=DEADLINE:= = external, consequence-bearing; =SCHEDULED:= = social, accountability-bearing — see the priority spec at the top of =todo.org=). High-priority work that carries neither is suspicious: either it has an implicit deadline that should be made explicit, or it has someone waiting that should surface in the agenda, or its priority is wrong. The scan flags candidates; the operator decides. +=task-review-staleness.sh= counts top-level =[#A]= / =[#B]= / =[#C]= tasks (TODO/DOING/VERIFY) whose =:LAST_REVIEWED:= is missing or older than the threshold. Threshold 30 days is about 2.5 review cycles of slack at the default batch size — one missed week is fine, three weeks signals a problem. #+begin_src bash if [ -n "$LINT_ORG_FOLLOWUPS" ]; then @@ -188,29 +188,16 @@ elif [ -d "./inbox" ]; then else followups=".ai/lint-followups.org" fi -[ -f todo.org ] && awk ' - /^\*\* (TODO|DOING|VERIFY) \[#[AB]\]/ { - if (heading != "" && !has_date) print line ": " heading - heading = $0 - line = NR - has_date = 0 - next - } - /^(SCHEDULED|DEADLINE|CLOSED):/ { has_date = 1; next } - /^\*+ / { if (heading != "" && !has_date) print line ": " heading; heading = ""; next } - END { if (heading != "" && !has_date) print line ": " heading } -' todo.org > /tmp/date-coverage.out -if [ -s /tmp/date-coverage.out ]; then - { - printf "\n* %s — Date coverage: [#A] / [#B] tasks without DEADLINE or SCHEDULED\n" "$(date '+%Y-%m-%d %a')" - printf "%s\n" "Review each: add a date, drop the priority, or confirm 'no-date by intent' inline." - sed 's/^/- /' /tmp/date-coverage.out - } >> "$followups" +if [ -f todo.org ]; then + stale=$(.ai/scripts/task-review-staleness.sh todo.org 30 2>/dev/null || echo 0) + if [ "$stale" -gt 0 ]; then + printf "\n* %s — Task-review health: %s top-level [#A]/[#B]/[#C] tasks unreviewed for >30 days (daily review may have slipped)\n" \ + "$(date '+%Y-%m-%d %a')" "$stale" >> "$followups" + fi fi -rm -f /tmp/date-coverage.out #+end_src -The scan is intentionally conservative — it surfaces every candidate. False positives (tasks that legitimately have no date) are cheap to dismiss; false negatives would let high-priority work drift undated. No-date is a valid resting state for some tasks (research, watch-list), and the operator can mark those as such in the daily-prep review rather than tagging them in =todo.org=. +A non-zero count writes one summary line and nothing else — the per-task walk is the review habit's job, not the wrap-up's. This supersedes the old date-coverage scan, which flagged every dateless =[#A]= / =[#B]= task on the wrong assumption that high-priority work needs a date. No-date is a valid resting state for research and watch-list tasks; staleness, not datelessness, is the real signal. ** Step 3.5: Linear ticket-state hygiene (skip if project doesn't use Linear) -- cgit v1.2.3