diff options
| -rw-r--r-- | .ai/workflows/INDEX.org | 2 | ||||
| -rw-r--r-- | .ai/workflows/triage-intake.org | 122 | ||||
| -rw-r--r-- | .ai/workflows/wrap-it-up.org | 2 |
3 files changed, 124 insertions, 2 deletions
diff --git a/.ai/workflows/INDEX.org b/.ai/workflows/INDEX.org index 7349d74..de1737b 100644 --- a/.ai/workflows/INDEX.org +++ b/.ai/workflows/INDEX.org @@ -29,6 +29,8 @@ This index must list every =.org= file in =.ai/workflows/= except this one. Star - =daily-prep.org= — prep brief for the next workday. Two modes: full-prep (default) or standup-only. - Full-prep triggers: "let's prep for tomorrow", "daily prep" - Standup-only triggers: "what's my standup report", "let's do the daily standup report", "give me the standup brief" +- =triage-intake.org= — on-demand triage: scan every inbox source (DeepSat Gmail, personal Gmail, cmail/Proton, Slack, Linear, GitHub PRs, both calendars, recent =todo.org= edits), surface what's moved, run the Linear Dev-Review sweep, mark *all* unread INBOX email across the three accounts and every touched Slack conversation as read. Lighter scope than =daily-prep.org='s triage section. Projects that want it called from =wrap-it-up.org= (or elsewhere) can opt in via a =.ai/project-workflows/<name>.org= extension. + - Triggers: "do a triage intake", "triage intake", "what's moved?", "what's new?", "check for movement" - =journal-entry.org= — capture a daily journal entry. - Triggers: "let's do a journal entry", "create a journal entry" - =clean-todo.org= — tidy =todo.org=: hygiene pass + =--archive-done=, then summarize. Wrap-up does this automatically; this is the manual entry point. diff --git a/.ai/workflows/triage-intake.org b/.ai/workflows/triage-intake.org new file mode 100644 index 0000000..a0657b5 --- /dev/null +++ b/.ai/workflows/triage-intake.org @@ -0,0 +1,122 @@ +#+TITLE: Triage-Intake Workflow +#+AUTHOR: Craig Jennings & Claude +#+DATE: 2026-05-11 + +* Overview + +On-demand triage of all inbox sources — invoked any time the user wants a "what's moved?" snapshot. Scans every source for recent activity (the three mail accounts, Slack, Linear, open PRs, both calendars, recent =todo.org= edits), surfaces what's moved and what's actionable, runs the Linear Dev-Review sweep, and clears unread state at the end — *every* unread INBOX message across all three mail accounts, plus every Slack conversation it surfaced, marked read. Same source set as =daily-prep.org='s triage section, lighter scope: no =notes.org= reading, no inbox-file processing, no meeting prep, no time-blocking, no prep-doc generation. Projects that want this workflow called from =wrap-it-up.org= (or elsewhere) opt in via a =.ai/project-workflows/<name>.org= extension; the template workflows don't call triage-intake themselves. + +* When to Use + +When the user says: +- "do a triage intake" / "triage intake" / "triage intake now" +- "what's moved?" / "what's new?" / "anything new since X?" +- "check for movement" + +* The Workflow + +** Phase 1: Fan-out (one parallel batch) + +Issue all of these in a single parallel batch (mix of MCP and Bash calls). Each source is optional — skip with a note if unavailable in this project (no MCP configured, no Slack workspace, no GitHub-family remote, etc.). This phase *surfaces recent activity for the report*; it doesn't bound what Phase 3 marks read — Phase 3 runs its own full unread query. + +1. *DeepSat Gmail* (=craig.jennings@deepsat.com=) — recent messages via =mcp__google-docs-work__listMessages= with =maxResults ~25=. +2. *Personal Gmail* (=craigmartinjennings@gmail.com=) — recent messages via =mcp__google-docs-personal__listMessages= with =maxResults ~15=. +3. *cmail / Proton* (=c@cjennings.net=) — local Maildir at =~/.mail/cmail/=, indexed by =mu=. Mirrors the mu4e unread-cmail query bound to =C-; e c u= in =mail-config.el=: + #+begin_src bash + mu find 'maildir:/cmail/INBOX AND flag:unread AND NOT flag:trashed' \ + --sortfield=date --reverse \ + --fields='d f s' \ + | head -30 + #+end_src + Proton has no Gmail API, so cmail is surfaced through =mu= (the two Google accounts use the richer Gmail MCPs above). Phase 3 re-queries the full unread set across all three accounts rather than relying on this capped list. +4. *Slack (slack-deepsat)* — =mcp__slack-deepsat__conversations_unreads= (all channels + DMs). +5. *Linear* — =mcp__linear__list_issues= with =assignee: "me"=, limit 30, ordered by recently updated. Surface tickets touched recently (new comments, status changes, new assignments). +6. *GitHub PRs* — for each active repo in the project, list open PRs. For GitHub Enterprise, pass =--hostname=: + #+begin_src bash + gh api repos/<owner>/<repo>/pulls --hostname <ghe-host> \ + -q '.[] | "#\(.number) \(.state) \(.title) — by \(.user.login), updated \(.updated_at)"' + #+end_src + Active repos and the host come from =.ai/project-workflows/triage-intake.org= (per-project list); fall back to the project's current-repo origin if no list is configured. Skip if the project has no GitHub-family remote. +7. *Calendars* — DeepSat work + personal (no cmail-associated calendar). Two parallel reads, looking at the next ~7 days: + - =mcp__google-docs-work__listEvents= (or the unified =mcp__google-calendar__list-events= scoped to the DeepSat account). + - =mcp__google-docs-personal__listEvents= for personal. + Surface newly-added events the user may not have noticed yet — calendar invites that landed during the session, meetings someone else scheduled, time blocks added. Highlight conflicts with existing commitments where they matter. +8. *(Optional) =todo.org= recent edits* — =git log -5 --oneline -- todo.org= or =stat= the file's mtime, to catch in-flight edits the user made between sessions. + +** Phase 2: Synthesize + Linear Dev-Review sweep + +Group findings by source, one short line per item. For each actionable item, propose the next step (reply / move ticket / close out / etc.). Don't propose actions for noise. Distinguish: + +- *Movement* — something changed (reply landed, ticket moved, PR pushed). Includes the actionable subset. +- *Noise* — promos, notifications, automated emails. Cleared by the mark-as-read pass in Phase 3, not reported individually. + +*** Linear Dev-Review sweep + +For every ticket assigned to the user with status =Dev Review= (from the Phase 1 =list_issues= output), check whether its linked PR is merged. Use the =gitBranchName= field on each ticket to find the PR on the project's remote: + +#+begin_src bash +gh pr list --search "head:<gitBranchName>" --state all \ + --json number,state,headRefName,mergedAt,title +#+end_src + +Skip the PR-merge check if the project has no GitHub-family remote (a self-hosted Gitea or plain-SSH remote has no =gh= support) — in that case surface the Dev-Review tickets and ask the user to confirm merge status manually. + +If a Dev-Review ticket's PR is *merged*, propose a status move: + +- *Done* — chores, refactors, test-coverage backfills, dead-code removal, e2e-flake fixes, anything with no PM-visible behavior change. PR titles prefixed =chore:=, =test:=, =refactor:=, =docs:= almost always belong here. +- *PM Acceptance* — real behavior fixes or new features a PM (or end user) could verify by clicking through. PR titles prefixed =fix:=, =feat:= usually belong here unless the change is invisible to users. + +When in doubt, ask the user per ticket — don't auto-pick. After approval, move via =mcp__linear__save_issue= with the new state. Several can run in parallel. + +Skip the sweep entirely if the project doesn't use Linear (personal projects, the rulesets repo, etc.). + +** Phase 3: Mark-as-read (at the end — not as you go) + +Clear unread state. Default behavior unless the user explicitly says "leave this unread." + +*** Email — every unread INBOX message, all three accounts + +All three mail accounts are synced to local Maildirs and =mu=-indexed: =~/.mail/gmail/= (personal), =~/.mail/dmail/= (DeepSat), =~/.mail/cmail/= (Proton). Phase 1 surfaces the two Google accounts through the Gmail MCPs (richer thread/label data for the report) and cmail through =mu= (no Gmail API for Proton). Mark-as-read goes through the Maildirs *uniformly* — it's the one mechanism that cleanly expresses "all unread, not just what I surfaced." + +1. Query every unread INBOX message across the three accounts: + #+begin_src bash + mu find 'flag:unread AND NOT flag:trashed AND (maildir:/gmail/INBOX OR maildir:/dmail/INBOX OR maildir:/cmail/INBOX)' \ + --fields='p' + #+end_src +2. Pass *all* the returned paths to the flag manager in one call: + #+begin_src bash + ~/code/rulesets/.ai/scripts/maildir-flag-manager.py mark-read --reindex \ + <path-1> <path-2> ... + #+end_src + *Always pass explicit paths.* Never call =mark-read= with no positional args — the bare default is "mark every unread message across every configured INBOX maildir on the machine," which happens to be the same set here but becomes a footgun the moment a project's extension narrows the scope. =--reindex= re-runs =mu index= so the local index reflects the new flags. =--dry-run= previews without modifying. +3. Flag changes land on disk; =mbsync= propagates them to the servers on its next run. If the user wants the servers updated immediately, run =mbsync -a= after — but don't auto-sync; ask first (matches =summarize-emails.org='s "ask before syncing"). +4. Trash obvious junk separately and *only with user approval* — never trash without an explicit go-ahead. + +*** Slack — every conversation surfaced + +=mcp__slack-deepsat__conversations_mark= for each conversation touched during the intake, advancing the read pointer to the latest message surfaced. Don't mark conversations the intake didn't surface. + +*** Skip + +Linear, GitHub PRs, and calendars — none has a clean "mark this read" concept that maps to inbox hygiene. + +** Phase 4: Report + +One concise summary back to the user: + +- *What moved (by source)* — one line per change worth knowing. +- *Actionable* — numbered, with the proposed next step per item. +- *Cleared* — total count of emails (across the three accounts) and Slack conversations marked read. +- *Pending decisions* — anything needing user input before action (draft replies awaiting approval, ambiguous routing, =mbsync= sync offer, etc.). + +* Principles + +- *Read-only by default.* Triage intake surfaces and asks; it doesn't reply, move tickets, or trash. Mark-as-read is the one default exception — it's hygiene, not a state change worth a gate — and it covers *all* unread INBOX mail, not just what got surfaced in the report. +- *Surface first, mark second.* Mark-as-read happens at the end of the workflow, not inline. If the user pivots ("never mind, leave them"), nothing's already been touched. +- *Same sources as daily-prep, lighter scope.* This workflow exists for mid-day or on-demand checks; daily-prep is the once-a-day full version (and runs its own triage section as part of its flow). Keep the two consistent when either changes; the planned end state is daily-prep delegating its triage section to this workflow. +- *Each source is optional.* If a project doesn't have one (no Slack workspace, no GitHub-family remote, no cmail), skip it with a one-line note and continue. +- *Per-project extension* via =.ai/project-workflows/triage-intake.org=: override the cmail / Maildir paths and the =maildir-flag-manager= command, add project-specific sources (extra Slack workspaces, additional mail accounts, JIRA, etc.), list the GitHub repos and host to check. + +* Living Document + +Update this workflow when source MCPs or local mail tooling change (new Slack workspace, switch from =mu= to =notmuch=, different cmail account, additional Maildir, etc.). diff --git a/.ai/workflows/wrap-it-up.org b/.ai/workflows/wrap-it-up.org index 849950c..5c1af26 100644 --- a/.ai/workflows/wrap-it-up.org +++ b/.ai/workflows/wrap-it-up.org @@ -122,8 +122,6 @@ For each result, look up the linked PR (the =gitBranchName= field on the issue m When in doubt, ask Craig per ticket. Don't auto-pick. After Craig confirms, move via =mcp__linear__save_issue= with =state="Done"= or =state="PM Acceptance"=. Several can run in parallel. -This step is also part of =triage-intake.org=, so during a session that already triaged it may be a no-op. Run it anyway — it's idempotent, and the sweep catches anything merged between the last triage and the wrap-up. - Skip the step entirely if the project doesn't use Linear (e.g. personal projects, the rulesets repo). ** Step 4: Git commit + push |
