diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-31 11:21:46 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-31 11:21:46 -0500 |
| commit | 5438c3172ceba7740d28480d161dc68b1a1af4c8 (patch) | |
| tree | a8ec9a40924fdd4a809e8fdd1fd5e3af4c8c09dc | |
| parent | a072f7cab1a534d64b88a9b4c51e16bc01be6b57 (diff) | |
| download | rulesets-5438c3172ceba7740d28480d161dc68b1a1af4c8.tar.gz rulesets-5438c3172ceba7740d28480d161dc68b1a1af4c8.zip | |
refactor(daily-prep): delegate triage to the triage-intake engine
daily-prep's Phase 3 re-implemented email/Slack/Linear/PR scanning inline (sub-steps 3b-3g, ~280 lines): the same fan-out, classify, and reactive-task work the triage-intake engine has owned since its 2026-05-26 plugin refactor. I collapsed those to four steps: 3b runs the engine, 3c surfaces today's reactive items as Day's Priorities thin links, 3d re-sorts by urgency, 3e writes the audit footer from the engine's per-source coverage.
Source coverage carries because the engine's Phase 0 globs both .ai/workflows/ and .ai/project-workflows/ plugins, so the work account's Gmail/Slack/Linear/GHE plugins are still scanned, and a source change now lives in one plugin instead of being duplicated here. I adapted the downstream references (the Prep-Doc-Structure rule, the Heads-up FYI source, the Recommended Approach Pattern reframed as engine-applied), dropped the orphaned Linear-digest note, and added a Living Document entry. The file goes 825 to 576 lines, and the prep-doc contract (Day's Priorities, Heads-up, Sources-checked footer) is unchanged.
| -rw-r--r-- | .ai/workflows/daily-prep.org | 296 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/daily-prep.org | 296 | ||||
| -rw-r--r-- | todo.org | 4 |
3 files changed, 51 insertions, 545 deletions
diff --git a/.ai/workflows/daily-prep.org b/.ai/workflows/daily-prep.org index 6589355..d8e186d 100644 --- a/.ai/workflows/daily-prep.org +++ b/.ai/workflows/daily-prep.org @@ -104,11 +104,11 @@ Exceptions that stay in the prep doc as content (not links): - *Completed-today entries* — once a task is done, its heading becomes a dated log entry (=** YYYY-MM-DD Day @ HH:MM ...=, see [[file:../../.claude/...][feedback_done_tasks_become_dated_log_headings]] — i.e. the dated-heading rule) and stays in the prep doc as the day's record. If the work also has a durable todo.org home, the dated entry links to it. - The =* Standup Briefs=, =* Heads-up=, =* Upcoming Deadlines=, and =* [Next day]'s Anchor Tasks= sections — those are prep-doc-native and don't map to todo.org tasks. -(Craig's instruction, 2026-05-12, extended 2026-05-15. Sub-steps 3b–3f below implement the convention: each Action item becomes its own =** TODO= in =todo.org= with =:quick:= + =:reactive:= tags, and the prep doc references it via a thin link under =* Day's Priorities= when it belongs in today's plan. No grouped =** Email Response= / =** Slack Response= / =** Linear Response= / =** PR Review= sub-sections in the prep doc — the source mix lives in the tags on each todo.org task.) +(Craig's instruction, 2026-05-12, extended 2026-05-15. The triage engine (sub-step 3b) implements the convention: each Action item becomes its own =** TODO= in =todo.org= with =:quick:= + =:reactive:= tags, and the prep doc references it via a thin link under =* Day's Priorities= when it belongs in today's plan. No grouped =** Email Response= / =** Slack Response= / =** Linear Response= / =** PR Review= sub-sections in the prep doc — the source mix lives in the tags on each todo.org task.) ** Triage Action items become =:quick:reactive:= todo.org tasks (2026-05-15 rule) -Every Action item surfaced by Phase 3 sub-steps 3b–3f (email, Slack, Linear, PRs) becomes its own top-level =** TODO= in =todo.org= with =:quick:= + =:reactive:= tags. The prep doc's =* Day's Priorities= section links to those tasks as thin links per the rule above — no grouped Response sub-sections. +Every Action item the triage engine surfaces (sub-step 3b, across email / Slack / Linear / PRs / calendar) becomes its own top-level =** TODO= in =todo.org= with =:quick:= + =:reactive:= tags. The prep doc's =* Day's Priorities= section links to those tasks as thin links per the rule above — no grouped Response sub-sections. Form: @@ -204,15 +204,13 @@ This step keeps the daily prep honest. If a 1-hour task consistently takes 3 hou Assemble priorities automatically from multiple sources. No interactive confirmation. Craig reviews the prep doc as a whole when it's done. -For each Action item: create or update a =:quick:reactive:= task in =todo.org= per the *Triage Action items become =:quick:reactive:= todo.org tasks* rule above. Then, if the item belongs in today's plan, add a thin-link entry under the prep doc's =* Day's Priorities= section. Order Day's Priorities by urgency — most time-sensitive or blocking first. If Craig wants to add or remove a priority, he edits the prep doc directly. +Phase 3 pulls priorities from two places: =todo.org= directly (sub-step 3a) and the triage engine's fresh Action items (sub-step 3b, which creates the =:quick:reactive:= tasks per the rule above). Sub-step 3c surfaces the ones that belong in today's plan as thin links under =* Day's Priorities=, sub-step 3d sorts them by urgency, and Craig edits the prep doc directly to add or drop a priority. -Sources contributing to Day's Priorities: todo.org, email, Slack, Linear, PRs. +Sources contributing to Day's Priorities: todo.org (3a) plus everything the triage engine covers (email, Slack, Linear, PRs, calendar). -*Linear digest / notification emails don't get their own task.* If sub-step 3b returns a Linear digest email saying "you have N unread notifications," that's not an Action item — Linear notifications surface either via 3e (the direct Linear query) or via the underlying per-notification emails Linear sends. Classify the digest as Noise-keep (or Noise-trash if the per-notification emails are also flowing through) and don't add a separate "Linear notifications check" todo.org task. +*** Recommended Approach Pattern (applied by the triage engine when writing reactive-task bodies) -*** Recommended Approach Pattern (used by sub-steps 3b, 3d, 3e) - -When an item involves a non-trivial decision or judgment that benefits from analysis, include a =recommended approach= subheader before the response draft. The approach is the executor's analysis — situation, options, recommendation with rationale, considerations Craig should weigh — so Craig can evaluate the recommendation itself, not just the wording. +When a triage Action item involves a non-trivial decision or judgment that benefits from analysis, the engine includes a =recommended approach= subheader before the response draft in the task body. The approach is the executor's analysis (situation, options, recommendation with rationale, considerations Craig should weigh) so Craig can evaluate the recommendation itself, not just the wording. This pattern is kept here as the shared reference; the triage engine and any response-drafting step follow it. Skip the approach subheader for routine acknowledgments, simple status replies, or "yep, looks good" approvals. @@ -247,287 +245,38 @@ Priorities typically include: - Follow-ups to make - Prep work needed before a meeting -*** Sub-step 3b: From email - -Scan email since the prior prep doc's *mtime* (when the file was last written, not the date in its filename or title) — both accounts, unread or unanswered, with the =summarize-emails=-style noise filter (=NOT flag:list=, addressed-to-Craig). Express the cutoff to Gmail as =after:YYYY/MM/DD HH:MM:SS= so messages dated *between the prior prep's write time and now* land in scope. Anchoring on filename date or Gmail's day-granular =after:= operator drifts when a prep doc is generated late at night for the next day — the wrong window catches a full extra calendar day or misses several hours. - -*Track every message path returned by the scan* (Action, FYI, and Noise alike). Sub-step 3c uses this list to mark-read only the emails actually processed, avoiding a race condition with emails arriving mid-workflow. - -For each email, classify: - -- *Action* — explicit ask, deadline, request for decision, or Craig is the bottleneck -- *FYI* — informational, no action required (note in session-context.org if substantive, otherwise drop) -- *Noise-keep* — automated/CC-only with no expected response, but has residual value (mark read; keep in inbox archive) -- *Noise-trash* — automated/CC-only with no expected response AND no residual value (trash it; see criterion below) - -*Trash criterion.* Trash if both are true: +*** Sub-step 3b: Triage external sources (delegate to triage-intake.org) -1. No transactional / financial / security record value (would I ever search for this in 6 months?) -2. Not direct human-to-human correspondence +Don't scan email / Slack / Linear / PRs inline here. Run the =triage-intake.org= engine. It fans out across its source plugins, classifies each item against the shared four-bucket model (Action / FYI / Noise-keep / Noise-trash), synthesizes a single deduped summary, writes every Action item into =todo.org= as its own =:quick:reactive:= task, and executes the triage actions (star / mark-read / trash) on Craig's confirmation. That is the same work sub-steps 3b-3g used to do inline; the engine now owns it, so a source change lives in one plugin instead of being duplicated here. -Otherwise mark-read but keep. +*Source coverage comes from the engine's Phase 0 plugin load.* triage-intake Phase 0 globs both =.ai/workflows/triage-intake.*.org= (general plugins: cmail, github-prs, personal-calendar, personal-gmail) and =.ai/project-workflows/triage-intake.*.org= (the project's own, e.g. the work account's Gmail, Slack, Linear, and GHE PRs). So everything daily-prep used to scan inline stays covered as long as the project ships its source plugins there. If a source isn't covered, the gap is a missing plugin in =.ai/project-workflows/=, not a daily-prep regression. -*Per-account bias.* DeepSat work account biases toward keeping — defense engineering audit-trail value, storage is free. Personal Gmail biases toward trashing — high volume, low residual value, search clutter. +The engine tracks its own "since when?" sentinel. Pass it the prior prep doc's mtime as the window if the project wants the prep cadence to drive triage scope; otherwise let it use its sentinel. -Common *Noise-trash* patterns: -- Newsletter / blog digest content (Substack subscriptions you don't actually read, daily/weekly roundups) -- Retail / SaaS / EDC marketing promos -- Social-network engagement bait (LinkedIn social digests, Instagram followers, Bandcamp DMs) -- Aggregate notification digests redundant with their source app (Linear digest emails, Notion/Miro daily/weekly engagement pings) -- Wrong-recipient mail (mailing-list typo addressed to a different person) -- Past-event calendar artifacts (cancellations/updates for events that already happened) -- Stock-tip teasers, deal alerts, review-request prompts (Hotels.com / Airbnb "rate your stay") +*** Sub-step 3c: Surface today's reactive items in Day's Priorities -Common *Noise-keep* patterns: -- Financial: dividend statements, monthly statements, payment confirmations, invoices (paid or unpaid) -- Account security: new-login alerts, new payment method added, password reset, OAuth grants -- Domain / registrar admin (renewal notices, WHOIS contact reminders) -- Direct human correspondence (even if FYI, even if you didn't reply) -- Booking confirmations for trips actually being taken -- PR / code review notifications (audit-trail value on the work account) -- CI / deploy failure alerts (incident archive) - -*Email link format.* Every email surfaced in the prep doc must link directly to the actual message in the right Gmail account, not just to a generic Gmail tab. Construct the URL as: +The engine (3b) and sub-step 3a's =:reactive:= pull have populated =todo.org= with the open quick-fire tasks. For each that belongs in *today's* plan (a deadline, a blocker, or someone waiting decides "today"), add a thin link under =* Day's Priorities=: #+begin_example -https://mail.google.com/mail/u/<account-index>/#all/<thread-id> +** TODO [#B] <verb> <Person / topic> — [[file:../todo.org::*<task title>][todo.org]] +<one line on why today> #+end_example -Two pieces matter: - -- *Account index.* The =u/<n>= segment selects which Gmail account opens the link. Indexes are stable per Craig's setup: - - =u/0= → personal account (=craigmartinjennings@gmail.com=, served by the =google-docs= MCP) - - =u/1= → DeepSat work account (=craig.jennings@deepsat.com=, served by the =google-docs-work= MCP) - Pick the index that matches the source MCP. A work email linked with =u/0= opens personal Gmail and shows nothing - that's the failure mode this rule prevents. -- *Thread ID.* Use the =threadId= field from =listMessages= or =getMessage=, not the =messageId=. Gmail's web URL routes by thread. Use the =#all/= view (works whether the message is still in the inbox or has been archived) - don't use =#inbox/=, since the link breaks the moment the message moves out of inbox. - -If a project routes mail through a different account layout (other tenants, additional accounts), record the index mapping in that project's =.ai/notes.org= and override the defaults above. - -For each Action item: - -1. *Star the email in Gmail* so it's flagged in Craig's inbox outside the prep doc: - #+begin_src bash - python3 .ai/scripts/maildir-flag-manager.py star --reindex /path/to/message.eml - #+end_src -2. *Read* the email content (use =eml-view-and-extract-attachments.py= in stdout mode if needed). -3. *Capture substantive content* in the right place based on what kind of information it is: - - =deepsat/knowledge.org= (or the project's equivalent) — persistent facts the project should remember: new contacts to add to the roster, system or infrastructure details, strategic decisions, transcription corrections, vendor relationships. - - =.ai/session-context.org= — today's session context: what was read, decisions made or pending, follow-ups identified for later in the session. - When in doubt, default to session-context. The next session can promote anything worth keeping into knowledge.org during wrap-up. -4. *People-Context Check (runs before the recommended response is drafted).* When the Action item involves a specific person (sender, recipient, or a named third party referenced in the body), look that person up in =deepsat/knowledge.org= (Key People table, plus the Team Details section if present). Pull role and reporting line, relationships to other key people (family, prior employer, advisor vs. employee), tone and working-style signals, and recent context that affects how Craig should phrase the response. If the person isn't in =knowledge.org=, capture what's known there before writing the draft — the people layer is persistent context, every future prep gets faster when this layer is complete. Skip the check for purely transactional senders (Mercury, GitHub notifications, Linear bots). The recommended-approach + recommended-response then incorporates the people-context: tone, channel choice (email vs Slack vs in-person), and framing should reflect the relationship. -5. *Create the =:quick:reactive:= todo.org task* per the *Triage Action items become =:quick:reactive:= todo.org tasks* rule above. The heading carries the verb (Reply / Read / Approve / Decline / Schedule), the source link as =[[mail-link][From X: Subject]]= form, and tags =:quick:reactive:[person]:=. The recommended-approach + recommended-response content (per the Recommended Approach Pattern) goes in the task body, not the heading. -6. *If the item belongs in today's plan, link from Day's Priorities* using the thin-link form: - #+begin_example - ** TODO [#B] Reply to <Person> re: <subject> — [[file:../todo.org::*Reply to <Person>][todo.org]] - <one line on why today — deadline, blocker, who's waiting> - #+end_example - Items that don't belong in today's plan stay in =todo.org= and surface on a future day's prep via sub-step 3a's =:reactive:= pull. - -For each FYI item: read it, capture substantive content into knowledge.org or session-context per the routing above, then drop it from further processing. Don't add to the prep doc or =todo.org=. - -For each Noise-keep item: no per-message action beyond the read-state pass at sub-step 3c. Don't add to the prep doc. - -For each Noise-trash item: trash it. Trashing removes the message from INBOX and parks it in Trash for 30 days before Gmail's auto-purge, so no separate mark-read is needed at sub-step 3c. - -#+begin_src bash -# MCP path (preferred — works directly against Gmail): -mcp__google-docs__trashMessage --messageId <id> # personal account -mcp__google-docs-work__trashMessage --messageId <id> # DeepSat account - -# maildir path (fallback for offline / mu4e workflow): -python3 .ai/scripts/maildir-flag-manager.py trash --reindex /path/to/message.eml -#+end_src - -The =:quick:reactive:= todo.org task is the durable home; the Day's Priorities thin link surfaces it for the day. Don't paste the email body, recommended approach, or response draft into the prep doc — those live in the todo.org task. - -*** Sub-step 3c: Mark processed email as read - -After 3b is fully complete, mark *only the message paths 3b processed* (Action, FYI, Noise-keep) as read — not all unread INBOX emails. Noise-trash items are already handled at 3b and don't need a read-state pass. - -#+begin_src bash -# MCP path (preferred): -mcp__google-docs__modifyMessageLabels --messageId <id> --removeLabelIds '["UNREAD"]' - -# maildir path (fallback): -python3 .ai/scripts/maildir-flag-manager.py mark-read --reindex /path/to/msg1 /path/to/msg2 ... -#+end_src - -Scoped mark-read avoids a race where an email arriving between 3b's scan and 3c's mark-read would be silently marked read without being processed. - -Stars persist (separate flag), so Action items remain findable in Gmail by their star, not by unread state. - -*Before exiting Phase 3, verify the triage actions actually executed:* every Action item has a Gmail star AND is marked read; every FYI and Noise-keep item is marked read; every Noise-trash item is in Trash. The Phase 3 audit footer at sub-step 3g is the forcing function for source coverage; this verification is the parallel for triage execution. Producing only the audit footer without running the API calls leaves Craig with an unread inbox even though every message was *seen* in the prep doc — defeats the inbox-zero purpose of the workflow. Count what was processed (e.g., "44 trashed, 5 noise-keep marked-read, 19 starred + marked-read, 30 FYI marked-read") and confirm the totals match the classification. - -*** Sub-step 3d: From Slack - -Query Slack since the prior prep doc's *mtime* (when the file was last written, not its filename date). Three streams (filters out general channel chatter Craig wasn't directly addressed in): - -1. *DMs Craig hasn't replied to.* Call =mcp__slack-deepsat__conversations_unreads= with =channel_types='dm'= and =max_channels=100=. Each row is a DM message Craig hasn't read. -2. *Channel @mentions of Craig.* Call =mcp__slack-deepsat__conversations_unreads= with =mentions_only=true= and =max_channels=100=. This is the *only reliable* way to find @mentions in this MCP — see the search-doesn't-work-for-mentions note below. Limitation: this only catches *unread* @mentions; ones Craig already saw are out of scope, which is the right trade-off (already-read mentions were seen and either actioned or consciously skipped). -3. *Thread replies in threads Craig started or last commented in.* No direct API for "threads I started." Practical approach: in the unreads pulled by streams 1 and 2, look at the =ThreadTs= column — non-empty values mean the message is a thread reply. Use =mcp__slack-deepsat__conversations_history= on the parent channel to fetch the full thread context if any reply looks substantive. - -*Slack search caveat (don't waste time here).* =mcp__slack-deepsat__conversations_search_messages= does *not* index =<@USERID>= tokens as searchable text. Searches for =<@U0A8AJTEM9V>=, =@craig.jennings=, or even Craig's plain display name return empty even when @mentions exist. Slack's own search has the same limitation — the "Mentions & Reactions" panel in the Slack app uses the unreads-with-mentions API, not search. Don't fall back to =conversations_search_messages= to find @mentions; use =conversations_unreads(mentions_only=true)= as above. The search tool is fine for keyword searches in message text (e.g. "find any message mentioning 'STRATFI'"). - -For each result, classify: - -- *Action* — explicit ask, decision needed, or Craig is the bottleneck -- *FYI* — informational, no action required (capture in knowledge.org or session-context if substantive, otherwise drop) -- *Noise* — bot pings, automated alerts, off-topic mentions (drop silently) - -For each Action item: - -1. *Read* the message and surrounding thread context. -2. *Capture substantive content* in =deepsat/knowledge.org= (or the project's equivalent) for persistent project facts, or =.ai/session-context.org= for today's context. Same routing rules as Sub-step 3b — default to session-context when unsure. -3. *People-Context Check* — same as sub-step 3b's step 4. When the message involves a specific person, look them up in =knowledge.org= before drafting. Skip for transactional bot pings. -4. *Create the =:quick:reactive:= todo.org task* per the *Triage Action items become =:quick:reactive:= todo.org tasks* rule above. The heading carries the verb (Reply / React / Address / Schedule), the source link as =[[slack-permalink][From X in #channel: brief description]]= form, and tags =:quick:reactive:[person]:=. The recommended-approach + recommended-response content goes in the task body. -5. *If the item belongs in today's plan, link from Day's Priorities* using the thin-link form: - #+begin_example - ** TODO [#B] Reply to <Person> re: <topic> — [[file:../todo.org::*Reply to <Person>][todo.org]] - <one line on why today — who's blocked, what just landed> - #+end_example - Items that don't belong in today's plan stay in =todo.org= and surface on a future day's prep via sub-step 3a's =:reactive:= pull. - -For each FYI item: read it, capture substantive content per the routing above, then drop. No prep-doc entry or todo.org task. - -After processing all items, mark *only the specific DMs and @mentions we touched* as read in Slack — not channel-wide. The Slack MCP should expose a per-message mark-read capability; if the available MCP doesn't support it, skip this step and let Craig manage Slack read state himself. Do NOT mark channel chatter as read; only the items the query returned. - -The =:quick:reactive:= todo.org task is the durable home; the Day's Priorities thin link surfaces it for the day. Don't paste the message text, recommended approach, or response draft into the prep doc — those live in the todo.org task. - -*** Sub-step 3e: From Linear - -Query Linear since the prior prep doc's *mtime* (when the file was last written, not its filename date), using =updatedAt= as the timestamp anchor. Three streams: - -1. Tickets *assigned to Craig* with state changes or new activity. -2. Tickets *created by Craig* with state changes or new comments (someone else moved or replied). -3. Tickets where Craig is *@mentioned* in a recent comment. - -Use Linear MCP =list_issues= with the appropriate filters; =get_issue= and =list_comments= for individual reads. +Items that don't fit today stay in =todo.org= and resurface on a future prep via 3a's =:reactive:= pull. Don't duplicate task bodies into the prep doc; the thin link is the whole entry (2026-05-12 rule). -For each result, classify: +*** Sub-step 3d: Urgency re-sort -- *Action* — ticket in "Needs Review" assigned to Craig; @mention requesting input; new comment with a question; deadline within ~48h; ticket assigned to Craig in a "blocked-on-me" state -- *FYI* — state change on Craig's ticket by someone else; comment that's a status update; related-ticket activity worth knowing -- *Noise* — bot updates, automated transitions (drop silently) +Re-order the entries under =* Day's Priorities= by urgency: (1) deadlines today or overdue, (2) Craig blocking someone, (3) deadlines within ~48h, (4) other [#A], (5) time-sensitive lower-stakes, (6) everything else. Entries are all thin links now, so it's a flat reorder of heading lines. Craig refines when he reviews; this just gives a sane starting order. -For each Action item: +*** Sub-step 3e: Phase 3 audit footer (forcing function) -1. *Read the ticket* — description, recent comments, state history. -2. *Capture substantive content* in =deepsat/knowledge.org= (people roles, system facts, strategic decisions) or =.ai/session-context.org= (today's review notes). Same routing rules as Sub-step 3b — default to session-context when unsure. -3. *People-Context Check* — same as sub-step 3b's step 4. When the ticket involves a specific person (assignee, reviewer, commenter, @mentioned), look them up in =knowledge.org= before drafting. Skip for bot updates and automated transitions. -4. *Create the =:quick:reactive:= todo.org task* per the *Triage Action items become =:quick:reactive:= todo.org tasks* rule above. The heading carries the verb (Review / Comment / Resolve / Address), the source link as =[[https://linear.app/...][SE-NNN ticket title]]= form, and tags =:quick:reactive:[person]:=. The recommended approach + recommended response (draft comment, proposed state change, combined action like "comment + reassign to Vrezh" — "what Craig should do," not just reply text) goes in the task body. - - *When the action item is a comment or @mention,* paste the comment text into the task body so Craig can draft a reply against it. Fetch via =mcp__linear__list_comments=: - #+begin_example - ** TODO [#B] Reply to <Author> on [[https://linear.app/...][SE-NNN]] :quick:reactive:[author]: - Why action needed: direct @mention; ties to <related ticket if any>. - - *** <Author> — <YYYY-MM-DD HH:MM> — comment on SE-NNN - #+begin_quote - <the comment text, verbatim> - #+end_quote - *** Craig's reply (fill in / approve, then I post it to the ticket) - <placeholder> - #+end_example - Same idea for an FYI comment Craig should see — paste it, don't just reference it. (Feedback memory: =feedback_linear_comment_as_child_header=.) -5. *If the item belongs in today's plan, link from Day's Priorities* using the thin-link form: - #+begin_example - ** TODO [#B] Review SE-NNN <ticket title> — [[file:../todo.org::*Review SE-NNN][todo.org]] - <one line on why today — Needs Review, blocker, deadline> - #+end_example - Items that don't belong in today's plan stay in =todo.org= and surface on a future day's prep via sub-step 3a's =:reactive:= pull. - -For each FYI item: read it, capture substantive content per the routing above, then drop. No prep-doc entry or todo.org task. - -*Two deliberate divergences from email/Slack:* - -1. *No mark-as-read step.* Linear has no "unread" concept. The processed signal is implicit — once Craig acts on the ticket (comments, changes state), it stops appearing in the next query. Items he doesn't act on re-surface tomorrow, which is correct. -2. *No "star" or "save" equivalent.* Linear ticket URLs in the prep doc are enough. - -*Volume note:* if a query returns >20 tickets, surface the count and prioritize Action signals first (Needs Review assigned to Craig, @mentions, blocked tickets, deadlines). - -The =:quick:reactive:= todo.org task is the durable home; the Day's Priorities thin link surfaces it for the day. Don't paste the ticket description, recommended approach, or response draft into the prep doc — those live in the todo.org task. - -*** Sub-step 3f: From Open PRs - -Scan open pull requests on the project's primary repo (per-project; for DeepSat the canonical repo is `~/projects/work/deepsat/code/orchestration_dashboard_mvp`). Use `gh pr list` to enumerate, classify each, and surface Action items in the prep doc. - -Scan command (single round-trip): - -#+begin_src bash -gh pr list --repo <owner>/<repo> --state open --json number,title,author,reviewRequests,isDraft,updatedAt,headRefName,additions,deletions,url -#+end_src - -For each result, classify: - -- *Action* — Craig is in `reviewRequests`, OR Craig was a reviewer and the author force-pushed since Craig's last review (re-review needed), OR the PR is blocked on Craig's response in a thread. -- *FYI* — open PRs Craig isn't reviewing but worth noting (a teammate's branch in flight, draft PRs, Craig's own PRs awaiting others). -- *Noise* — bot PRs (dependabot, renovate, etc.). Drop silently. - -For each Action item: - -1. *Read the PR* — diff summary, recent commits, review state, any unresolved threads. -2. *People-Context Check* — same as sub-step 3b's step 4 for the PR author and the requesting reviewer. -3. *Create the =:quick:reactive:= todo.org task* per the *Triage Action items become =:quick:reactive:= todo.org tasks* rule above. The heading carries the verb (Review / Re-review / Unblock), the source link as =[[PR-URL][PR #N title (author)]]= form, and tags =:quick:reactive:[author]:=. The recommended-approach + recommended-response content goes in the task body. -4. *If the item belongs in today's plan, link from Day's Priorities* using the thin-link form: - -#+begin_example -** TODO [#B] Review PR #N <title> — [[file:../todo.org::*Review PR #N][todo.org]] -<one line on why today — review requested, blocking author, re-review after force-push> -#+end_example - -Items that don't belong in today's plan stay in =todo.org= and surface on a future day's prep via sub-step 3a's =:reactive:= pull. - -For FYI items (Craig's own PRs awaiting review, draft PRs in flight): don't create a todo.org task. Surface them in =* Heads-up= (Phase 7) as a one-line note ("Craig's open PRs awaiting review: #145, #167") so the queue stays visible without inflating the action surface. - -*Two deliberate divergences from email/Slack:* - -1. *No mark-as-read step.* PR review state is implicit — once Craig submits a review or a comment, the PR's review-status changes and re-classification happens on the next prep. -2. *No "star" equivalent.* PR URLs in the prep doc are enough; Craig's review queue is already filterable in the GitHub UI. - -*Per-project repo configuration.* The repo path is project-specific. For projects without a primary repo (personal documentation projects, etc.), skip this sub-step and mark `prs ✗ (no primary repo)` in the audit footer. For projects with multiple repos in active development, scan each in turn; surface Action items with the repo name prefixed in the title. - -The =:quick:reactive:= todo.org task is the durable home; the Day's Priorities thin link surfaces it for the day. Don't paste the PR description, recommended approach, or response draft into the prep doc — those live in the todo.org task. - -*** Sub-step 3g: Cross-source dedup and urgency re-sort - -After 3a-3f have written all their =:quick:reactive:= todo.org tasks and Day's Priorities thin links, do a final cleanup pass. - -*Cross-source dedup.* Scan the =:reactive:= tasks created this prep cycle for items that reference the same conversation or topic. Common cases: - -- Vrezh DMs Craig about a Linear ticket *and* comments on the ticket itself — both surface as separate =:reactive:= tasks -- An email points at "let's discuss on the ticket" — the ticket also has activity -- A Slack thread is the followup to an email exchange - -For each candidate duplicate pair, surface to Craig: - -#+begin_quote -These two reactive tasks look like the same conversation: [task with Email link] and [task with Linear link]. Should I collapse them into one, or keep both? -#+end_quote - -Wait for Craig's call. If he says collapse, keep the source he picks, paste the other source link into the task body as "see also: [other-link]", and drop the other task. Drop the matching Day's Priorities thin link too. Don't auto-collapse. - -*Urgency re-sort.* Re-order all entries under =* Day's Priorities= by urgency: - -1. Items with deadlines today or already overdue -2. Items where Craig is blocking someone (Slack blocked-on-Craig, Linear Blocked tickets, Needs-Review assigned to Craig) -3. Items with deadlines within ~48 hours -4. Other [#A] tasks -5. Time-sensitive but lower-stakes items -6. Everything else - -Day's Priorities entries are all thin links to todo.org tasks now (no grouped source sub-sections), so re-ordering is a flat sort — just move the heading lines into urgency order. Craig can re-order further when he reviews the prep doc; this just gives him a sane starting order. - -*** Sub-step 3h: Phase 3 audit footer (forcing function) - -After 3a-3g are done, write a single comment line at the top of the prep doc — directly below the =#+DATE:= header — recording which sources actually got checked: +After 3a-3d, write a single comment line directly below the =#+DATE:= header recording which sources the triage engine actually covered: #+begin_example # Sources checked: todo.org ✓ | email-personal ✓ | email-deepsat ✓ | Slack ✓ | Linear ✓ | prs ✓ #+end_example -Replace the ✓ with ✗ for any source that was skipped, and add a parenthetical reason after each ✗ (e.g. =Slack ✗ (MCP disconnected)= or =email-personal ✗ (auth scope error)=). If a source was scanned but returned no items, keep the ✓ — empty is a valid scan result; "didn't run at all" is what this line catches. - -This footer is the canary that surfaces silent skips. The reason it's a forcing function: writing the line means deciding what mark each source gets, which means actually checking that each source ran. A skipped sub-step now requires an explicit ✗, not a silent omission of a section. +Take the per-source marks from the engine's synthesis (its Phase C reports per-plugin coverage). Replace ✓ with ✗ plus a parenthetical reason for any source the engine couldn't reach (e.g. =Slack ✗ (MCP disconnected)=). A source scanned with no items keeps its ✓ — empty is a valid result; this line catches a source that did not run at all. ** Phase 4: Time Blocking @@ -654,7 +403,7 @@ Write a top-level =* Heads-up= section near the top of the prep doc (above =* Da Heads-up is the executive summary of what would change Craig's frame for the day. Terse, situational, day-shaping. Pull from sources earlier phases surfaced: -- *Substantial FYIs* from Phase 3 sub-steps 3b/3d/3e — items like "Eric quietly progressed two partnership threads on Apr 24" or "Vrezh force-pushed all three branches overnight" that affect what Craig should expect today +- *Substantial FYIs* from the triage engine's synthesis (sub-step 3b) — items like "Eric quietly progressed two partnership threads on Apr 24" or "Vrezh force-pushed all three branches overnight" that affect what Craig should expect today - *Schedule changes* from Phase 1 — "Arusyak 15:00-16:00 is being rescheduled" or "DeepSat GTM declined for tonight" - *Urgent deadlines bubbling up* — items in =* Upcoming Deadlines= that hit within ~2 days, surfaced as standalone Heads-up items so Craig sees them immediately - *Active Reminders* (from Phase A's notes.org read) that frame today specifically — "first thing in the morning, register for SOFWeek" @@ -823,3 +572,6 @@ Craig's call. The prep doc's =* Day's Priorities= entries point at todo.org task *** 2026-05-15: Triage Action items become =:quick:reactive:= todo.org tasks — no grouped Response sub-sections Craig's call, originally landed in the work project's =triage-intake= workflow on 2026-05-15 and propagated here on 2026-05-16. Each Action item surfaced by Phase 3 sub-steps 3b/3d/3e/3f (email, Slack, Linear, PRs) becomes its own =** TODO [#B] <verb-led description> :quick:reactive:[person]:= in =todo.org=. The Day's Priorities section in the prep doc references it via a thin link when it belongs in today's plan, otherwise the task stays in =todo.org= for later. Replaces the prior pattern of building grouped =** Email Response= / =** Slack Response= / =** Linear Response= / =** PR Review= sub-headings under Day's Priorities — those sub-sections no longer exist. Source mix now lives in the tags on each task, not in the prep doc structure. Full statement in *Prep Doc Structure ▸ Triage Action items become =:quick:reactive:= todo.org tasks* above. (Closes the 2026-05-12 follow-up about rewording sub-steps 3b–3f.) + +*** 2026-05-31: Delegate triage to the triage-intake engine +Phase 3's inline source scans — sub-steps 3b (email), 3c (mark-read), 3d (Slack), 3e (Linear), 3f (PRs), and 3g's cross-source dedup, ~280 lines — duplicated what the =triage-intake.org= engine has owned since its 2026-05-26 engine/plugin refactor. Collapsed them to four steps: 3b runs the engine (it fans out across general + project source plugins, classifies, synthesizes, writes the =:quick:reactive:= tasks, and executes star/mark-read/trash on confirmation); 3c surfaces today's reactive items as Day's Priorities thin links; 3d re-sorts by urgency; 3e writes the audit footer from the engine's per-source coverage. Source coverage carries because the engine's Phase 0 globs both =.ai/workflows/= and =.ai/project-workflows/= plugins, so the work account's Gmail/Slack/Linear/GHE plugins are still scanned — the delegation must not drop them, and Phase 0's two-dir glob is what guarantees it. The Recommended Approach Pattern stayed here as the shared reference (the engine applies it when writing reactive-task bodies); a future tidy could move it into the engine. Also closes the 2026-05-12 follow-up about rewording sub-steps 3b-3f. diff --git a/claude-templates/.ai/workflows/daily-prep.org b/claude-templates/.ai/workflows/daily-prep.org index 6589355..d8e186d 100644 --- a/claude-templates/.ai/workflows/daily-prep.org +++ b/claude-templates/.ai/workflows/daily-prep.org @@ -104,11 +104,11 @@ Exceptions that stay in the prep doc as content (not links): - *Completed-today entries* — once a task is done, its heading becomes a dated log entry (=** YYYY-MM-DD Day @ HH:MM ...=, see [[file:../../.claude/...][feedback_done_tasks_become_dated_log_headings]] — i.e. the dated-heading rule) and stays in the prep doc as the day's record. If the work also has a durable todo.org home, the dated entry links to it. - The =* Standup Briefs=, =* Heads-up=, =* Upcoming Deadlines=, and =* [Next day]'s Anchor Tasks= sections — those are prep-doc-native and don't map to todo.org tasks. -(Craig's instruction, 2026-05-12, extended 2026-05-15. Sub-steps 3b–3f below implement the convention: each Action item becomes its own =** TODO= in =todo.org= with =:quick:= + =:reactive:= tags, and the prep doc references it via a thin link under =* Day's Priorities= when it belongs in today's plan. No grouped =** Email Response= / =** Slack Response= / =** Linear Response= / =** PR Review= sub-sections in the prep doc — the source mix lives in the tags on each todo.org task.) +(Craig's instruction, 2026-05-12, extended 2026-05-15. The triage engine (sub-step 3b) implements the convention: each Action item becomes its own =** TODO= in =todo.org= with =:quick:= + =:reactive:= tags, and the prep doc references it via a thin link under =* Day's Priorities= when it belongs in today's plan. No grouped =** Email Response= / =** Slack Response= / =** Linear Response= / =** PR Review= sub-sections in the prep doc — the source mix lives in the tags on each todo.org task.) ** Triage Action items become =:quick:reactive:= todo.org tasks (2026-05-15 rule) -Every Action item surfaced by Phase 3 sub-steps 3b–3f (email, Slack, Linear, PRs) becomes its own top-level =** TODO= in =todo.org= with =:quick:= + =:reactive:= tags. The prep doc's =* Day's Priorities= section links to those tasks as thin links per the rule above — no grouped Response sub-sections. +Every Action item the triage engine surfaces (sub-step 3b, across email / Slack / Linear / PRs / calendar) becomes its own top-level =** TODO= in =todo.org= with =:quick:= + =:reactive:= tags. The prep doc's =* Day's Priorities= section links to those tasks as thin links per the rule above — no grouped Response sub-sections. Form: @@ -204,15 +204,13 @@ This step keeps the daily prep honest. If a 1-hour task consistently takes 3 hou Assemble priorities automatically from multiple sources. No interactive confirmation. Craig reviews the prep doc as a whole when it's done. -For each Action item: create or update a =:quick:reactive:= task in =todo.org= per the *Triage Action items become =:quick:reactive:= todo.org tasks* rule above. Then, if the item belongs in today's plan, add a thin-link entry under the prep doc's =* Day's Priorities= section. Order Day's Priorities by urgency — most time-sensitive or blocking first. If Craig wants to add or remove a priority, he edits the prep doc directly. +Phase 3 pulls priorities from two places: =todo.org= directly (sub-step 3a) and the triage engine's fresh Action items (sub-step 3b, which creates the =:quick:reactive:= tasks per the rule above). Sub-step 3c surfaces the ones that belong in today's plan as thin links under =* Day's Priorities=, sub-step 3d sorts them by urgency, and Craig edits the prep doc directly to add or drop a priority. -Sources contributing to Day's Priorities: todo.org, email, Slack, Linear, PRs. +Sources contributing to Day's Priorities: todo.org (3a) plus everything the triage engine covers (email, Slack, Linear, PRs, calendar). -*Linear digest / notification emails don't get their own task.* If sub-step 3b returns a Linear digest email saying "you have N unread notifications," that's not an Action item — Linear notifications surface either via 3e (the direct Linear query) or via the underlying per-notification emails Linear sends. Classify the digest as Noise-keep (or Noise-trash if the per-notification emails are also flowing through) and don't add a separate "Linear notifications check" todo.org task. +*** Recommended Approach Pattern (applied by the triage engine when writing reactive-task bodies) -*** Recommended Approach Pattern (used by sub-steps 3b, 3d, 3e) - -When an item involves a non-trivial decision or judgment that benefits from analysis, include a =recommended approach= subheader before the response draft. The approach is the executor's analysis — situation, options, recommendation with rationale, considerations Craig should weigh — so Craig can evaluate the recommendation itself, not just the wording. +When a triage Action item involves a non-trivial decision or judgment that benefits from analysis, the engine includes a =recommended approach= subheader before the response draft in the task body. The approach is the executor's analysis (situation, options, recommendation with rationale, considerations Craig should weigh) so Craig can evaluate the recommendation itself, not just the wording. This pattern is kept here as the shared reference; the triage engine and any response-drafting step follow it. Skip the approach subheader for routine acknowledgments, simple status replies, or "yep, looks good" approvals. @@ -247,287 +245,38 @@ Priorities typically include: - Follow-ups to make - Prep work needed before a meeting -*** Sub-step 3b: From email - -Scan email since the prior prep doc's *mtime* (when the file was last written, not the date in its filename or title) — both accounts, unread or unanswered, with the =summarize-emails=-style noise filter (=NOT flag:list=, addressed-to-Craig). Express the cutoff to Gmail as =after:YYYY/MM/DD HH:MM:SS= so messages dated *between the prior prep's write time and now* land in scope. Anchoring on filename date or Gmail's day-granular =after:= operator drifts when a prep doc is generated late at night for the next day — the wrong window catches a full extra calendar day or misses several hours. - -*Track every message path returned by the scan* (Action, FYI, and Noise alike). Sub-step 3c uses this list to mark-read only the emails actually processed, avoiding a race condition with emails arriving mid-workflow. - -For each email, classify: - -- *Action* — explicit ask, deadline, request for decision, or Craig is the bottleneck -- *FYI* — informational, no action required (note in session-context.org if substantive, otherwise drop) -- *Noise-keep* — automated/CC-only with no expected response, but has residual value (mark read; keep in inbox archive) -- *Noise-trash* — automated/CC-only with no expected response AND no residual value (trash it; see criterion below) - -*Trash criterion.* Trash if both are true: +*** Sub-step 3b: Triage external sources (delegate to triage-intake.org) -1. No transactional / financial / security record value (would I ever search for this in 6 months?) -2. Not direct human-to-human correspondence +Don't scan email / Slack / Linear / PRs inline here. Run the =triage-intake.org= engine. It fans out across its source plugins, classifies each item against the shared four-bucket model (Action / FYI / Noise-keep / Noise-trash), synthesizes a single deduped summary, writes every Action item into =todo.org= as its own =:quick:reactive:= task, and executes the triage actions (star / mark-read / trash) on Craig's confirmation. That is the same work sub-steps 3b-3g used to do inline; the engine now owns it, so a source change lives in one plugin instead of being duplicated here. -Otherwise mark-read but keep. +*Source coverage comes from the engine's Phase 0 plugin load.* triage-intake Phase 0 globs both =.ai/workflows/triage-intake.*.org= (general plugins: cmail, github-prs, personal-calendar, personal-gmail) and =.ai/project-workflows/triage-intake.*.org= (the project's own, e.g. the work account's Gmail, Slack, Linear, and GHE PRs). So everything daily-prep used to scan inline stays covered as long as the project ships its source plugins there. If a source isn't covered, the gap is a missing plugin in =.ai/project-workflows/=, not a daily-prep regression. -*Per-account bias.* DeepSat work account biases toward keeping — defense engineering audit-trail value, storage is free. Personal Gmail biases toward trashing — high volume, low residual value, search clutter. +The engine tracks its own "since when?" sentinel. Pass it the prior prep doc's mtime as the window if the project wants the prep cadence to drive triage scope; otherwise let it use its sentinel. -Common *Noise-trash* patterns: -- Newsletter / blog digest content (Substack subscriptions you don't actually read, daily/weekly roundups) -- Retail / SaaS / EDC marketing promos -- Social-network engagement bait (LinkedIn social digests, Instagram followers, Bandcamp DMs) -- Aggregate notification digests redundant with their source app (Linear digest emails, Notion/Miro daily/weekly engagement pings) -- Wrong-recipient mail (mailing-list typo addressed to a different person) -- Past-event calendar artifacts (cancellations/updates for events that already happened) -- Stock-tip teasers, deal alerts, review-request prompts (Hotels.com / Airbnb "rate your stay") +*** Sub-step 3c: Surface today's reactive items in Day's Priorities -Common *Noise-keep* patterns: -- Financial: dividend statements, monthly statements, payment confirmations, invoices (paid or unpaid) -- Account security: new-login alerts, new payment method added, password reset, OAuth grants -- Domain / registrar admin (renewal notices, WHOIS contact reminders) -- Direct human correspondence (even if FYI, even if you didn't reply) -- Booking confirmations for trips actually being taken -- PR / code review notifications (audit-trail value on the work account) -- CI / deploy failure alerts (incident archive) - -*Email link format.* Every email surfaced in the prep doc must link directly to the actual message in the right Gmail account, not just to a generic Gmail tab. Construct the URL as: +The engine (3b) and sub-step 3a's =:reactive:= pull have populated =todo.org= with the open quick-fire tasks. For each that belongs in *today's* plan (a deadline, a blocker, or someone waiting decides "today"), add a thin link under =* Day's Priorities=: #+begin_example -https://mail.google.com/mail/u/<account-index>/#all/<thread-id> +** TODO [#B] <verb> <Person / topic> — [[file:../todo.org::*<task title>][todo.org]] +<one line on why today> #+end_example -Two pieces matter: - -- *Account index.* The =u/<n>= segment selects which Gmail account opens the link. Indexes are stable per Craig's setup: - - =u/0= → personal account (=craigmartinjennings@gmail.com=, served by the =google-docs= MCP) - - =u/1= → DeepSat work account (=craig.jennings@deepsat.com=, served by the =google-docs-work= MCP) - Pick the index that matches the source MCP. A work email linked with =u/0= opens personal Gmail and shows nothing - that's the failure mode this rule prevents. -- *Thread ID.* Use the =threadId= field from =listMessages= or =getMessage=, not the =messageId=. Gmail's web URL routes by thread. Use the =#all/= view (works whether the message is still in the inbox or has been archived) - don't use =#inbox/=, since the link breaks the moment the message moves out of inbox. - -If a project routes mail through a different account layout (other tenants, additional accounts), record the index mapping in that project's =.ai/notes.org= and override the defaults above. - -For each Action item: - -1. *Star the email in Gmail* so it's flagged in Craig's inbox outside the prep doc: - #+begin_src bash - python3 .ai/scripts/maildir-flag-manager.py star --reindex /path/to/message.eml - #+end_src -2. *Read* the email content (use =eml-view-and-extract-attachments.py= in stdout mode if needed). -3. *Capture substantive content* in the right place based on what kind of information it is: - - =deepsat/knowledge.org= (or the project's equivalent) — persistent facts the project should remember: new contacts to add to the roster, system or infrastructure details, strategic decisions, transcription corrections, vendor relationships. - - =.ai/session-context.org= — today's session context: what was read, decisions made or pending, follow-ups identified for later in the session. - When in doubt, default to session-context. The next session can promote anything worth keeping into knowledge.org during wrap-up. -4. *People-Context Check (runs before the recommended response is drafted).* When the Action item involves a specific person (sender, recipient, or a named third party referenced in the body), look that person up in =deepsat/knowledge.org= (Key People table, plus the Team Details section if present). Pull role and reporting line, relationships to other key people (family, prior employer, advisor vs. employee), tone and working-style signals, and recent context that affects how Craig should phrase the response. If the person isn't in =knowledge.org=, capture what's known there before writing the draft — the people layer is persistent context, every future prep gets faster when this layer is complete. Skip the check for purely transactional senders (Mercury, GitHub notifications, Linear bots). The recommended-approach + recommended-response then incorporates the people-context: tone, channel choice (email vs Slack vs in-person), and framing should reflect the relationship. -5. *Create the =:quick:reactive:= todo.org task* per the *Triage Action items become =:quick:reactive:= todo.org tasks* rule above. The heading carries the verb (Reply / Read / Approve / Decline / Schedule), the source link as =[[mail-link][From X: Subject]]= form, and tags =:quick:reactive:[person]:=. The recommended-approach + recommended-response content (per the Recommended Approach Pattern) goes in the task body, not the heading. -6. *If the item belongs in today's plan, link from Day's Priorities* using the thin-link form: - #+begin_example - ** TODO [#B] Reply to <Person> re: <subject> — [[file:../todo.org::*Reply to <Person>][todo.org]] - <one line on why today — deadline, blocker, who's waiting> - #+end_example - Items that don't belong in today's plan stay in =todo.org= and surface on a future day's prep via sub-step 3a's =:reactive:= pull. - -For each FYI item: read it, capture substantive content into knowledge.org or session-context per the routing above, then drop it from further processing. Don't add to the prep doc or =todo.org=. - -For each Noise-keep item: no per-message action beyond the read-state pass at sub-step 3c. Don't add to the prep doc. - -For each Noise-trash item: trash it. Trashing removes the message from INBOX and parks it in Trash for 30 days before Gmail's auto-purge, so no separate mark-read is needed at sub-step 3c. - -#+begin_src bash -# MCP path (preferred — works directly against Gmail): -mcp__google-docs__trashMessage --messageId <id> # personal account -mcp__google-docs-work__trashMessage --messageId <id> # DeepSat account - -# maildir path (fallback for offline / mu4e workflow): -python3 .ai/scripts/maildir-flag-manager.py trash --reindex /path/to/message.eml -#+end_src - -The =:quick:reactive:= todo.org task is the durable home; the Day's Priorities thin link surfaces it for the day. Don't paste the email body, recommended approach, or response draft into the prep doc — those live in the todo.org task. - -*** Sub-step 3c: Mark processed email as read - -After 3b is fully complete, mark *only the message paths 3b processed* (Action, FYI, Noise-keep) as read — not all unread INBOX emails. Noise-trash items are already handled at 3b and don't need a read-state pass. - -#+begin_src bash -# MCP path (preferred): -mcp__google-docs__modifyMessageLabels --messageId <id> --removeLabelIds '["UNREAD"]' - -# maildir path (fallback): -python3 .ai/scripts/maildir-flag-manager.py mark-read --reindex /path/to/msg1 /path/to/msg2 ... -#+end_src - -Scoped mark-read avoids a race where an email arriving between 3b's scan and 3c's mark-read would be silently marked read without being processed. - -Stars persist (separate flag), so Action items remain findable in Gmail by their star, not by unread state. - -*Before exiting Phase 3, verify the triage actions actually executed:* every Action item has a Gmail star AND is marked read; every FYI and Noise-keep item is marked read; every Noise-trash item is in Trash. The Phase 3 audit footer at sub-step 3g is the forcing function for source coverage; this verification is the parallel for triage execution. Producing only the audit footer without running the API calls leaves Craig with an unread inbox even though every message was *seen* in the prep doc — defeats the inbox-zero purpose of the workflow. Count what was processed (e.g., "44 trashed, 5 noise-keep marked-read, 19 starred + marked-read, 30 FYI marked-read") and confirm the totals match the classification. - -*** Sub-step 3d: From Slack - -Query Slack since the prior prep doc's *mtime* (when the file was last written, not its filename date). Three streams (filters out general channel chatter Craig wasn't directly addressed in): - -1. *DMs Craig hasn't replied to.* Call =mcp__slack-deepsat__conversations_unreads= with =channel_types='dm'= and =max_channels=100=. Each row is a DM message Craig hasn't read. -2. *Channel @mentions of Craig.* Call =mcp__slack-deepsat__conversations_unreads= with =mentions_only=true= and =max_channels=100=. This is the *only reliable* way to find @mentions in this MCP — see the search-doesn't-work-for-mentions note below. Limitation: this only catches *unread* @mentions; ones Craig already saw are out of scope, which is the right trade-off (already-read mentions were seen and either actioned or consciously skipped). -3. *Thread replies in threads Craig started or last commented in.* No direct API for "threads I started." Practical approach: in the unreads pulled by streams 1 and 2, look at the =ThreadTs= column — non-empty values mean the message is a thread reply. Use =mcp__slack-deepsat__conversations_history= on the parent channel to fetch the full thread context if any reply looks substantive. - -*Slack search caveat (don't waste time here).* =mcp__slack-deepsat__conversations_search_messages= does *not* index =<@USERID>= tokens as searchable text. Searches for =<@U0A8AJTEM9V>=, =@craig.jennings=, or even Craig's plain display name return empty even when @mentions exist. Slack's own search has the same limitation — the "Mentions & Reactions" panel in the Slack app uses the unreads-with-mentions API, not search. Don't fall back to =conversations_search_messages= to find @mentions; use =conversations_unreads(mentions_only=true)= as above. The search tool is fine for keyword searches in message text (e.g. "find any message mentioning 'STRATFI'"). - -For each result, classify: - -- *Action* — explicit ask, decision needed, or Craig is the bottleneck -- *FYI* — informational, no action required (capture in knowledge.org or session-context if substantive, otherwise drop) -- *Noise* — bot pings, automated alerts, off-topic mentions (drop silently) - -For each Action item: - -1. *Read* the message and surrounding thread context. -2. *Capture substantive content* in =deepsat/knowledge.org= (or the project's equivalent) for persistent project facts, or =.ai/session-context.org= for today's context. Same routing rules as Sub-step 3b — default to session-context when unsure. -3. *People-Context Check* — same as sub-step 3b's step 4. When the message involves a specific person, look them up in =knowledge.org= before drafting. Skip for transactional bot pings. -4. *Create the =:quick:reactive:= todo.org task* per the *Triage Action items become =:quick:reactive:= todo.org tasks* rule above. The heading carries the verb (Reply / React / Address / Schedule), the source link as =[[slack-permalink][From X in #channel: brief description]]= form, and tags =:quick:reactive:[person]:=. The recommended-approach + recommended-response content goes in the task body. -5. *If the item belongs in today's plan, link from Day's Priorities* using the thin-link form: - #+begin_example - ** TODO [#B] Reply to <Person> re: <topic> — [[file:../todo.org::*Reply to <Person>][todo.org]] - <one line on why today — who's blocked, what just landed> - #+end_example - Items that don't belong in today's plan stay in =todo.org= and surface on a future day's prep via sub-step 3a's =:reactive:= pull. - -For each FYI item: read it, capture substantive content per the routing above, then drop. No prep-doc entry or todo.org task. - -After processing all items, mark *only the specific DMs and @mentions we touched* as read in Slack — not channel-wide. The Slack MCP should expose a per-message mark-read capability; if the available MCP doesn't support it, skip this step and let Craig manage Slack read state himself. Do NOT mark channel chatter as read; only the items the query returned. - -The =:quick:reactive:= todo.org task is the durable home; the Day's Priorities thin link surfaces it for the day. Don't paste the message text, recommended approach, or response draft into the prep doc — those live in the todo.org task. - -*** Sub-step 3e: From Linear - -Query Linear since the prior prep doc's *mtime* (when the file was last written, not its filename date), using =updatedAt= as the timestamp anchor. Three streams: - -1. Tickets *assigned to Craig* with state changes or new activity. -2. Tickets *created by Craig* with state changes or new comments (someone else moved or replied). -3. Tickets where Craig is *@mentioned* in a recent comment. - -Use Linear MCP =list_issues= with the appropriate filters; =get_issue= and =list_comments= for individual reads. +Items that don't fit today stay in =todo.org= and resurface on a future prep via 3a's =:reactive:= pull. Don't duplicate task bodies into the prep doc; the thin link is the whole entry (2026-05-12 rule). -For each result, classify: +*** Sub-step 3d: Urgency re-sort -- *Action* — ticket in "Needs Review" assigned to Craig; @mention requesting input; new comment with a question; deadline within ~48h; ticket assigned to Craig in a "blocked-on-me" state -- *FYI* — state change on Craig's ticket by someone else; comment that's a status update; related-ticket activity worth knowing -- *Noise* — bot updates, automated transitions (drop silently) +Re-order the entries under =* Day's Priorities= by urgency: (1) deadlines today or overdue, (2) Craig blocking someone, (3) deadlines within ~48h, (4) other [#A], (5) time-sensitive lower-stakes, (6) everything else. Entries are all thin links now, so it's a flat reorder of heading lines. Craig refines when he reviews; this just gives a sane starting order. -For each Action item: +*** Sub-step 3e: Phase 3 audit footer (forcing function) -1. *Read the ticket* — description, recent comments, state history. -2. *Capture substantive content* in =deepsat/knowledge.org= (people roles, system facts, strategic decisions) or =.ai/session-context.org= (today's review notes). Same routing rules as Sub-step 3b — default to session-context when unsure. -3. *People-Context Check* — same as sub-step 3b's step 4. When the ticket involves a specific person (assignee, reviewer, commenter, @mentioned), look them up in =knowledge.org= before drafting. Skip for bot updates and automated transitions. -4. *Create the =:quick:reactive:= todo.org task* per the *Triage Action items become =:quick:reactive:= todo.org tasks* rule above. The heading carries the verb (Review / Comment / Resolve / Address), the source link as =[[https://linear.app/...][SE-NNN ticket title]]= form, and tags =:quick:reactive:[person]:=. The recommended approach + recommended response (draft comment, proposed state change, combined action like "comment + reassign to Vrezh" — "what Craig should do," not just reply text) goes in the task body. - - *When the action item is a comment or @mention,* paste the comment text into the task body so Craig can draft a reply against it. Fetch via =mcp__linear__list_comments=: - #+begin_example - ** TODO [#B] Reply to <Author> on [[https://linear.app/...][SE-NNN]] :quick:reactive:[author]: - Why action needed: direct @mention; ties to <related ticket if any>. - - *** <Author> — <YYYY-MM-DD HH:MM> — comment on SE-NNN - #+begin_quote - <the comment text, verbatim> - #+end_quote - *** Craig's reply (fill in / approve, then I post it to the ticket) - <placeholder> - #+end_example - Same idea for an FYI comment Craig should see — paste it, don't just reference it. (Feedback memory: =feedback_linear_comment_as_child_header=.) -5. *If the item belongs in today's plan, link from Day's Priorities* using the thin-link form: - #+begin_example - ** TODO [#B] Review SE-NNN <ticket title> — [[file:../todo.org::*Review SE-NNN][todo.org]] - <one line on why today — Needs Review, blocker, deadline> - #+end_example - Items that don't belong in today's plan stay in =todo.org= and surface on a future day's prep via sub-step 3a's =:reactive:= pull. - -For each FYI item: read it, capture substantive content per the routing above, then drop. No prep-doc entry or todo.org task. - -*Two deliberate divergences from email/Slack:* - -1. *No mark-as-read step.* Linear has no "unread" concept. The processed signal is implicit — once Craig acts on the ticket (comments, changes state), it stops appearing in the next query. Items he doesn't act on re-surface tomorrow, which is correct. -2. *No "star" or "save" equivalent.* Linear ticket URLs in the prep doc are enough. - -*Volume note:* if a query returns >20 tickets, surface the count and prioritize Action signals first (Needs Review assigned to Craig, @mentions, blocked tickets, deadlines). - -The =:quick:reactive:= todo.org task is the durable home; the Day's Priorities thin link surfaces it for the day. Don't paste the ticket description, recommended approach, or response draft into the prep doc — those live in the todo.org task. - -*** Sub-step 3f: From Open PRs - -Scan open pull requests on the project's primary repo (per-project; for DeepSat the canonical repo is `~/projects/work/deepsat/code/orchestration_dashboard_mvp`). Use `gh pr list` to enumerate, classify each, and surface Action items in the prep doc. - -Scan command (single round-trip): - -#+begin_src bash -gh pr list --repo <owner>/<repo> --state open --json number,title,author,reviewRequests,isDraft,updatedAt,headRefName,additions,deletions,url -#+end_src - -For each result, classify: - -- *Action* — Craig is in `reviewRequests`, OR Craig was a reviewer and the author force-pushed since Craig's last review (re-review needed), OR the PR is blocked on Craig's response in a thread. -- *FYI* — open PRs Craig isn't reviewing but worth noting (a teammate's branch in flight, draft PRs, Craig's own PRs awaiting others). -- *Noise* — bot PRs (dependabot, renovate, etc.). Drop silently. - -For each Action item: - -1. *Read the PR* — diff summary, recent commits, review state, any unresolved threads. -2. *People-Context Check* — same as sub-step 3b's step 4 for the PR author and the requesting reviewer. -3. *Create the =:quick:reactive:= todo.org task* per the *Triage Action items become =:quick:reactive:= todo.org tasks* rule above. The heading carries the verb (Review / Re-review / Unblock), the source link as =[[PR-URL][PR #N title (author)]]= form, and tags =:quick:reactive:[author]:=. The recommended-approach + recommended-response content goes in the task body. -4. *If the item belongs in today's plan, link from Day's Priorities* using the thin-link form: - -#+begin_example -** TODO [#B] Review PR #N <title> — [[file:../todo.org::*Review PR #N][todo.org]] -<one line on why today — review requested, blocking author, re-review after force-push> -#+end_example - -Items that don't belong in today's plan stay in =todo.org= and surface on a future day's prep via sub-step 3a's =:reactive:= pull. - -For FYI items (Craig's own PRs awaiting review, draft PRs in flight): don't create a todo.org task. Surface them in =* Heads-up= (Phase 7) as a one-line note ("Craig's open PRs awaiting review: #145, #167") so the queue stays visible without inflating the action surface. - -*Two deliberate divergences from email/Slack:* - -1. *No mark-as-read step.* PR review state is implicit — once Craig submits a review or a comment, the PR's review-status changes and re-classification happens on the next prep. -2. *No "star" equivalent.* PR URLs in the prep doc are enough; Craig's review queue is already filterable in the GitHub UI. - -*Per-project repo configuration.* The repo path is project-specific. For projects without a primary repo (personal documentation projects, etc.), skip this sub-step and mark `prs ✗ (no primary repo)` in the audit footer. For projects with multiple repos in active development, scan each in turn; surface Action items with the repo name prefixed in the title. - -The =:quick:reactive:= todo.org task is the durable home; the Day's Priorities thin link surfaces it for the day. Don't paste the PR description, recommended approach, or response draft into the prep doc — those live in the todo.org task. - -*** Sub-step 3g: Cross-source dedup and urgency re-sort - -After 3a-3f have written all their =:quick:reactive:= todo.org tasks and Day's Priorities thin links, do a final cleanup pass. - -*Cross-source dedup.* Scan the =:reactive:= tasks created this prep cycle for items that reference the same conversation or topic. Common cases: - -- Vrezh DMs Craig about a Linear ticket *and* comments on the ticket itself — both surface as separate =:reactive:= tasks -- An email points at "let's discuss on the ticket" — the ticket also has activity -- A Slack thread is the followup to an email exchange - -For each candidate duplicate pair, surface to Craig: - -#+begin_quote -These two reactive tasks look like the same conversation: [task with Email link] and [task with Linear link]. Should I collapse them into one, or keep both? -#+end_quote - -Wait for Craig's call. If he says collapse, keep the source he picks, paste the other source link into the task body as "see also: [other-link]", and drop the other task. Drop the matching Day's Priorities thin link too. Don't auto-collapse. - -*Urgency re-sort.* Re-order all entries under =* Day's Priorities= by urgency: - -1. Items with deadlines today or already overdue -2. Items where Craig is blocking someone (Slack blocked-on-Craig, Linear Blocked tickets, Needs-Review assigned to Craig) -3. Items with deadlines within ~48 hours -4. Other [#A] tasks -5. Time-sensitive but lower-stakes items -6. Everything else - -Day's Priorities entries are all thin links to todo.org tasks now (no grouped source sub-sections), so re-ordering is a flat sort — just move the heading lines into urgency order. Craig can re-order further when he reviews the prep doc; this just gives him a sane starting order. - -*** Sub-step 3h: Phase 3 audit footer (forcing function) - -After 3a-3g are done, write a single comment line at the top of the prep doc — directly below the =#+DATE:= header — recording which sources actually got checked: +After 3a-3d, write a single comment line directly below the =#+DATE:= header recording which sources the triage engine actually covered: #+begin_example # Sources checked: todo.org ✓ | email-personal ✓ | email-deepsat ✓ | Slack ✓ | Linear ✓ | prs ✓ #+end_example -Replace the ✓ with ✗ for any source that was skipped, and add a parenthetical reason after each ✗ (e.g. =Slack ✗ (MCP disconnected)= or =email-personal ✗ (auth scope error)=). If a source was scanned but returned no items, keep the ✓ — empty is a valid scan result; "didn't run at all" is what this line catches. - -This footer is the canary that surfaces silent skips. The reason it's a forcing function: writing the line means deciding what mark each source gets, which means actually checking that each source ran. A skipped sub-step now requires an explicit ✗, not a silent omission of a section. +Take the per-source marks from the engine's synthesis (its Phase C reports per-plugin coverage). Replace ✓ with ✗ plus a parenthetical reason for any source the engine couldn't reach (e.g. =Slack ✗ (MCP disconnected)=). A source scanned with no items keeps its ✓ — empty is a valid result; this line catches a source that did not run at all. ** Phase 4: Time Blocking @@ -654,7 +403,7 @@ Write a top-level =* Heads-up= section near the top of the prep doc (above =* Da Heads-up is the executive summary of what would change Craig's frame for the day. Terse, situational, day-shaping. Pull from sources earlier phases surfaced: -- *Substantial FYIs* from Phase 3 sub-steps 3b/3d/3e — items like "Eric quietly progressed two partnership threads on Apr 24" or "Vrezh force-pushed all three branches overnight" that affect what Craig should expect today +- *Substantial FYIs* from the triage engine's synthesis (sub-step 3b) — items like "Eric quietly progressed two partnership threads on Apr 24" or "Vrezh force-pushed all three branches overnight" that affect what Craig should expect today - *Schedule changes* from Phase 1 — "Arusyak 15:00-16:00 is being rescheduled" or "DeepSat GTM declined for tonight" - *Urgent deadlines bubbling up* — items in =* Upcoming Deadlines= that hit within ~2 days, surfaced as standalone Heads-up items so Craig sees them immediately - *Active Reminders* (from Phase A's notes.org read) that frame today specifically — "first thing in the morning, register for SOFWeek" @@ -823,3 +572,6 @@ Craig's call. The prep doc's =* Day's Priorities= entries point at todo.org task *** 2026-05-15: Triage Action items become =:quick:reactive:= todo.org tasks — no grouped Response sub-sections Craig's call, originally landed in the work project's =triage-intake= workflow on 2026-05-15 and propagated here on 2026-05-16. Each Action item surfaced by Phase 3 sub-steps 3b/3d/3e/3f (email, Slack, Linear, PRs) becomes its own =** TODO [#B] <verb-led description> :quick:reactive:[person]:= in =todo.org=. The Day's Priorities section in the prep doc references it via a thin link when it belongs in today's plan, otherwise the task stays in =todo.org= for later. Replaces the prior pattern of building grouped =** Email Response= / =** Slack Response= / =** Linear Response= / =** PR Review= sub-headings under Day's Priorities — those sub-sections no longer exist. Source mix now lives in the tags on each task, not in the prep doc structure. Full statement in *Prep Doc Structure ▸ Triage Action items become =:quick:reactive:= todo.org tasks* above. (Closes the 2026-05-12 follow-up about rewording sub-steps 3b–3f.) + +*** 2026-05-31: Delegate triage to the triage-intake engine +Phase 3's inline source scans — sub-steps 3b (email), 3c (mark-read), 3d (Slack), 3e (Linear), 3f (PRs), and 3g's cross-source dedup, ~280 lines — duplicated what the =triage-intake.org= engine has owned since its 2026-05-26 engine/plugin refactor. Collapsed them to four steps: 3b runs the engine (it fans out across general + project source plugins, classifies, synthesizes, writes the =:quick:reactive:= tasks, and executes star/mark-read/trash on confirmation); 3c surfaces today's reactive items as Day's Priorities thin links; 3d re-sorts by urgency; 3e writes the audit footer from the engine's per-source coverage. Source coverage carries because the engine's Phase 0 globs both =.ai/workflows/= and =.ai/project-workflows/= plugins, so the work account's Gmail/Slack/Linear/GHE plugins are still scanned — the delegation must not drop them, and Phase 0's two-dir glob is what guarantees it. The Recommended Approach Pattern stayed here as the shared reference (the engine applies it when writing reactive-task bodies); a future tidy could move it into the engine. Also closes the 2026-05-12 follow-up about rewording sub-steps 3b-3f. @@ -1107,7 +1107,9 @@ The four canonical rules (=commits=, =testing=, =verification=, =subagents=) are The Elisp pair is the most suspicious — three repos using essentially the same rules. Audit: diff these across the projects, check for drift, then decide whether to canonicalize them under =~/code/rulesets/claude-rules/languages/<lang>/= and symlink, or leave them as project-local. -** TODO [#C] Refactor =daily-prep.org= to delegate to =triage-intake.org= for the triage section :chore:solo: +** DONE [#C] Refactor =daily-prep.org= to delegate to =triage-intake.org= for the triage section :chore:solo: +CLOSED: [2026-05-31 Sun] +Collapsed Phase 3's inline source scans (sub-steps 3b email / 3c mark-read / 3d Slack / 3e Linear / 3f PRs / 3g dedup, ~280 lines) into four: 3b runs the triage-intake engine, 3c surfaces today's reactive items as Day's Priorities thin links, 3d re-sorts by urgency, 3e writes the audit footer from the engine's coverage. Source coverage carries via the engine's Phase 0 two-dir glob (general + .ai/project-workflows/ plugins), so the work account's Gmail/Slack/Linear/GHE plugins still get scanned. Adapted the downstream refs (Prep Doc Structure rule, Heads-up FYI source, Recommended Approach Pattern reframed as engine-applied), removed the orphaned Linear-digest note, added a Living Document entry. Verified: workflow-integrity clean (no dangling script refs), sync-check clean, full suite green. daily-prep.org went 825 → 576 lines. :PROPERTIES: :LAST_REVIEWED: 2026-05-28 :END: |
