aboutsummaryrefslogtreecommitdiff
path: root/.ai/workflows/wrap-it-up.org
diff options
context:
space:
mode:
Diffstat (limited to '.ai/workflows/wrap-it-up.org')
-rw-r--r--.ai/workflows/wrap-it-up.org116
1 files changed, 107 insertions, 9 deletions
diff --git a/.ai/workflows/wrap-it-up.org b/.ai/workflows/wrap-it-up.org
index b1560eb..d0c4e75 100644
--- a/.ai/workflows/wrap-it-up.org
+++ b/.ai/workflows/wrap-it-up.org
@@ -4,7 +4,7 @@
* Overview
-This workflow defines the process for ending a Claude Code session cleanly. It finalizes the session record, commits + pushes all work, and provides a warm handoff.
+This workflow defines the process for ending a Claude Code session cleanly. It finalizes the session record, commits + pushes all work, and provides a warm handoff. A bare wrap also tears the session down (kills the ai-term buffer + tmux session, restoring geometry); a qualified wrap keeps the buffer, and a shutdown wrap powers the machine off. The teardown variants are set by the trigger phrase (see Teardown mode below) and act only at the very end, in Step 6.
Triggered by Craig saying "wrap it up," "that's a wrap," "let's call it a wrap," or similar.
@@ -25,10 +25,22 @@ The wrap-up is complete when:
3. *todo.org is clean.* Cleanup script ran. Any auto-fixes are staged for the wrap-up commit. Orphan planning lines surfaced for manual fix if there are any.
4. *Linear board is honest* (skip if project doesn't use Linear). Any Dev-Review ticket whose PR has merged was moved to Done or PM Acceptance per the classification rule.
5. *Git state is clean.* All changes committed + pushed to all remotes. Working tree clean.
-6. *Valediction delivered.* Brief, warm closing with key accomplishments and reminders.
+6. *Valediction delivered.* Brief, warm closing with key accomplishments and reminders, ending with =session wrapped.= on its own line as the signoff marker.
The absence of =.ai/session-context.org= is the signal that the last session wrapped up cleanly. Its presence at session start means the previous session was interrupted.
+* Teardown mode (set from the trigger phrase)
+
+The wrap itself — Steps 1 through 5 — is identical in every mode. The trigger phrase only decides what Step 6 does once commit + push and the valediction are done. Resolve the mode from the phrase before starting:
+
+- *Teardown* (the default) — bare "wrap it up", "that's a wrap", "let's call it a wrap". The full wrap, then Step 6 kills the ai-term buffer + the =aiv-<project>= tmux session (which takes =claude= with it) and restores the saved window geometry. This is Craig's typical end-of-day case.
+- *No-teardown* — "wrap it up with summary" or "wrap it up and summarize". The full wrap, but Step 6 leaves the buffer and session intact so the summary stays readable. The explicit qualifier is what opts out of teardown.
+- *Shutdown* — "wrap it up and shutdown". The full wrap, then Step 6 gates on this being the only live ai-term session and powers the machine off. Shutdown supersedes teardown (killing the buffer is moot if the box is going down).
+
+Why teardown waits for Step 6 and runs through a hook, never inline: teardown kills the very tmux session =claude= runs in, so an inline kill would cut the valediction off before it renders. Step 6 instead drops a sentinel after everything else is verified, and the =Stop= hook (=ai-wrap-teardown.sh=) does the actual teardown when this response ends — by which point the valediction has already been delivered.
+
+This depends on three functions in =.emacs.d/modules/ai-term.el= (=cj/ai-term-quit=, =cj/ai-term-live-count=, =cj/ai-term-shutdown-countdown=) and on the =Stop= hook being wired in =settings.json= (=hooks/settings-snippet.json=). If =emacsclient= or the daemon is unreachable, the sentinel is cleared and the session simply stays up — teardown degrades to a no-op, never a wedge.
+
* The Workflow
** Step 1: Finalize the Summary
@@ -94,15 +106,15 @@ Replace =DESCRIPTION= with your picked slug. (=AI_AGENT_ID= should be filename-s
If the project has a =todo.org= at its root, run the cleanup script before committing. Two passes, both fast and idempotent: a hygiene pass and an archive pass.
-*** Roam inbox sweep (inbox-zero)
+*** Roam inbox sweep (inbox roam mode)
-Before the cleanup scripts, sweep the roam global inbox (=~/org/roam/inbox.org=) for items that belong to this project, so any imported tasks get linted and ride the wrap commit. Delegate to [[file:inbox-zero.org][inbox-zero.org]] for the claimed set.
+Before the cleanup scripts, sweep the roam global inbox (=~/org/roam/inbox.org=) for items that belong to this project, so any imported tasks get linted and ride the wrap commit. Delegate to [[file:inbox.org][inbox.org]] roam mode for the claimed set.
#+begin_src bash
[ -f "$HOME/org/roam/inbox.org" ] && grep -cE '^\*\* ' "$HOME/org/roam/inbox.org" || true
#+end_src
-Skip-fast when nothing matches: if the roam clone isn't on this machine, or no item is prefixed for this project, this is a silent no-op. When claimed items exist, run inbox-zero's Phase B–C (file each into =todo.org=, then remove them from the shared inbox in a separate roam commit). Report the total count and how many appeared related to this project, per inbox-zero's scan-summary rule.
+Skip-fast when nothing matches: if the roam clone isn't on this machine, or no item is prefixed for this project, this is a silent no-op. When claimed items exist, run roam mode's Phase B–D (file each into =todo.org=, then remove them from the shared inbox and let =roam-sync= commit + push the edit). Report the total count and how many appeared related to this project, per roam mode's scan-summary rule.
*** Hygiene pass
@@ -125,6 +137,22 @@ Run the report-only variant first if you want to see what would change without w
emacs --batch -q -l .ai/scripts/todo-cleanup.el --check todo.org
#+end_src
+*** Convert done sub-tasks to dated entries
+
+#+begin_src bash
+[ -f todo.org ] && emacs --batch -q -l .ai/scripts/todo-cleanup.el --convert-subtasks todo.org
+#+end_src
+
+=--convert-subtasks= rewrites every heading at level 3 or deeper whose TODO state is DONE/CANCELLED/FAILED into a dated event-log entry (=<stars> YYYY-MM-DD Day @ HH:MM:SS -ZZZZ <text>=), dropping the keyword, priority cookie, and tags, and removing the now-redundant =CLOSED:= line. This enforces the =todo-format.md= depth rule that a completed *sub-task* (a heading under a parent task) becomes dated history, not a lingering DONE keyword — a shape an interactive org close (=org-log-done= → DONE + CLOSED) never applies and =--archive-done= (level-2 only) never reaches. The timestamp comes from each entry's own =CLOSED= cookie; a date-only close yields =00:00:00=. Heading text is kept verbatim. Idempotent (an already-dated heading has no keyword to match), and a done sub-task with no parseable =CLOSED= is flagged and left alone rather than stamped with a fabricated date.
+
+Run this *before* =--archive-done= so that when a completed level-2 parent is archived, its sub-tasks already carry their dated form. Any rewrites show up in the wrap-up commit's diff for review before push.
+
+Preview without writing:
+
+#+begin_src bash
+emacs --batch -q -l .ai/scripts/todo-cleanup.el --convert-subtasks --check todo.org
+#+end_src
+
*** Archive completed work
#+begin_src bash
@@ -208,7 +236,7 @@ For an interactive walk of the judgments mid-day, run =/lint-org todo.org=.
*** Inbox sanity check (surface unprocessed handoffs)
-If the project has an =inbox/= directory, verify it holds nothing but =.gitkeep=, =lint-followups.org= (the lint-org pipeline file the next morning's daily-prep consumes), and any explicitly-deferred =PROCESSED-*= files before the wrap completes. An inbox that arrived at session start with handoffs from other projects, or that received handoffs mid-session, needs the =process-inbox.org= workflow to run and apply its value-gate dispositions. Wrapping with a dirty inbox silently defers the work to next session and accumulates handoff debt that the sender can't see.
+If the project has an =inbox/= directory, verify it holds nothing but =.gitkeep=, =lint-followups.org= (the lint-org pipeline file the next morning's daily-prep consumes), and any explicitly-deferred =PROCESSED-*= files before the wrap completes. An inbox that arrived at session start with handoffs from other projects, or that received handoffs mid-session, needs =inbox.org= process mode to run and apply its value-gate dispositions. Wrapping with a dirty inbox silently defers the work to next session and accumulates handoff debt that the sender can't see.
#+begin_src bash
unprocessed=$(find inbox -maxdepth 1 -type f \
@@ -217,7 +245,7 @@ unprocessed=$(find inbox -maxdepth 1 -type f \
! -name 'PROCESSED-*' \
2>/dev/null | wc -l)
if [ "$unprocessed" -gt 0 ]; then
- echo "wrap-up: inbox/ has $unprocessed unprocessed item(s). Run process-inbox.org before wrapping, or explicitly defer each item with a one-line reason in the valediction."
+ echo "wrap-up: inbox/ has $unprocessed unprocessed item(s). Run inbox.org process mode before wrapping, or explicitly defer each item with a one-line reason in the valediction."
find inbox -maxdepth 1 -type f \
! -name '.gitkeep' \
! -name 'lint-followups.org' \
@@ -230,7 +258,33 @@ If the count is zero or the project has no =inbox/= directory, the check is a si
The check exempts =lint-followups.org= explicitly because lint-org runs earlier in the same wrap-up workflow and writes its judgment items to that file in =inbox/= by design. The file is a pipeline artifact for the next morning's =daily-prep=, not a handoff that needs the value gate.
-This integrates with =process-inbox.org=, which stamps =:LAST_INBOX_PROCESS:= in =notes.org='s *Workflow State* section on completion. Wrap-up doesn't double-stamp. It only ensures the inbox carries nothing but the expected pipeline artifacts at session end.
+This integrates with =inbox.org= process mode, which stamps =:LAST_INBOX_PROCESS:= in =notes.org='s *Workflow State* section on completion. Wrap-up doesn't double-stamp. It only ensures the inbox carries nothing but the expected pipeline artifacts at session end.
+
+*** Cross-project router (optional — route filed keepers to their home projects)
+
+Runs directly after the inbox sanity check. The split between the two: the sanity check *gates* the wrap (a dirty inbox blocks until resolved); the router is *optional* (skipping it never blocks anything — the candidates just stay local until a future wrap). Spec: =docs/specs/wrapup-routing-spec.org= (D7/D8/D9).
+
+The candidate set is exactly the local tasks carrying a =:ROUTE_CANDIDATE:= property — keepers that inbox process mode filed this session whose inferred home is another project. Never scan the standing backlog.
+
+#+begin_src bash
+.ai/scripts/route-batch --list
+#+end_src
+
+*Empty set = zero interaction.* =--list= prints nothing when there are no candidates; continue the wrap silently — no prompt, no "0 items" line.
+
+When candidates exist, surface the batch as one line per task — the task heading, the destination project, the delivery mode (=inbox-send= file handoff), and the engine's confidence — then offer exactly two options: *go* (route the whole batch) or *skip* (leave everything local). Derive each confidence label by running the engine on the task's heading + body (=python3 .ai/scripts/route_recommend.py --item "..." --exclude "$(basename "$PWD")"=); label weak matches visibly ("weak — verify the destination") so a low-confidence route gets a human glance before the keystroke.
+
+On *go*:
+
+#+begin_src bash
+.ai/scripts/route-batch --go
+#+end_src
+
+Per candidate, the helper writes the task's subtree (children ride along; =:ROUTE_CANDIDATE:= stripped, headings promoted to top level) to a one-task handoff, delivers it via =inbox-send <destination> --file= (so the =from-<this-project>= provenance is stamped and the destination's inbox process mode dispositions it as a single item), and only after a successful send removes the subtree from the local =todo.org= — a single-file local edit the wrap is already committing. A failed send leaves that task in place and exits non-zero; report it and continue the wrap. Never write the destination's =todo.org= directly; its own inbox processing files the task per its conventions.
+
+On *skip*, leave every candidate in place, marker included — they resurface next wrap.
+
+Mis-routes are recoverable: the receiving project rejects via inbox process mode's reject-from-another-project flow, which returns the item to this project's inbox with the rationale. That reject path is why removing the local source on send is safe.
*** Review-habit health check (surface a slipped daily task-review)
@@ -448,6 +502,8 @@ Include:
Tone: warm but professional. No emoji unless Craig has explicitly requested. Acknowledge effort when session was long or difficult.
+End on a clear signoff: the *last* line of the valediction is always =session wrapped.= on its own line (lowercase, with the period, nothing after it). It's the unmistakable end-of-session marker, so don't trail it with another sentence. This is the last user-facing output — Step 6's teardown is silent.
+
Example:
#+begin_example
That's a wrap. Today we restructured the entire claude-templates
@@ -460,8 +516,47 @@ from earlier) and archsetup's layout-navigate tests. Both are
ratio-local uncommitted state.
Good session. Talk tomorrow.
+
+session wrapped.
#+end_example
+** Step 6: Session teardown (mode-dependent)
+
+The last action of the wrap, and only after Step 4's commit + push is verified and the Step 5 valediction is composed. The teardown itself happens when this response ends (via the =Stop= hook), so the valediction always renders first. Act by the mode resolved up front:
+
+*** No-teardown mode
+
+Do nothing. The buffer, the =aiv-<project>= tmux session, and =claude= all stay up so the summary stays readable. The wrap is complete.
+
+*** Teardown mode (default)
+
+Confirm commit + push succeeded (Exit Criteria 5 — never tear down over unpushed work), then drop the sentinel:
+
+#+begin_src bash
+touch "/tmp/ai-wrap-teardown-$(basename "$PWD")"
+#+end_src
+
+That is the whole step. Don't run any =tmux kill-session=, =emacsclient=, or buffer kill inline — the =Stop= hook reads the sentinel when this response ends and runs =cj/ai-term-quit=, which kills the =aiv-<project>= session (taking =claude= with it), kills the vterm buffer, and restores geometry. The basename of =$PWD= is the key the hook matches, so the sentinel names the session it tears down.
+
+*** Shutdown mode
+
+Confirm commit + push succeeded, then evaluate the safety gate *before* committing to the shutdown — never power the box off out from under another live session:
+
+#+begin_src bash
+emacsclient -e '(cj/ai-term-live-count)'
+#+end_src
+
+- *Count > 1* — another ai-term session is alive. ABORT the shutdown. List the other live =aiv-*= sessions, drop *no* sentinel, and tell Craig in the valediction that it fell back to a normal wrap (no poweroff, no teardown). This gate is the load-bearing safety of the whole feature.
+- *Count = 1* — this session is the only one. Drop the shutdown sentinel:
+
+ #+begin_src bash
+ touch "/tmp/ai-wrap-shutdown-$(basename "$PWD")"
+ #+end_src
+
+ The =Stop= hook fires =cj/ai-term-shutdown-countdown= when this response ends: it re-checks the gate, runs an abort-able 10→1 countdown in the Emacs echo area (=C-g= cancels), then =sudo shutdown now=. Shutdown supersedes teardown — do *not* also drop the teardown sentinel.
+
+If =emacsclient= isn't resolvable or the daemon is down, the gate can't run — abort the shutdown, fall back to a normal wrap, and say so. Don't power off on an unverifiable gate.
+
* Common Mistakes to Avoid
1. *Skipping Step 1 (Summary)* — the file becomes the record; an empty Summary makes it hard to scan at catch-up
@@ -483,7 +578,7 @@ Before considering wrap-up complete:
- [ ] The Summary ends with the =KB: promoted N / consulted yes-no= line (promotion check ran)
- [ ] File renamed to =.ai/sessions/YYYY-MM-DD-HH-MM-description.org=
- [ ] =.ai/session-context.org= no longer exists
-- [ ] =todo-cleanup.el= ran — hygiene pass + =--archive-done= + =--sync-child-priority= (if =todo.org= exists at project root)
+- [ ] =todo-cleanup.el= ran — hygiene pass + =--convert-subtasks= + =--archive-done= + =--sync-child-priority= (if =todo.org= exists at project root)
- [ ] =lint-org.el= ran on =todo.org= — mechanical fixes applied, judgments appended to follow-ups file (if =todo.org= exists)
- [ ] Any orphan-planning-line warnings reviewed (fix or accept)
- [ ] Inbox carries nothing but expected pipeline artifacts (=.gitkeep=, =lint-followups.org=, =PROCESSED-*= prefixes), OR each remaining handoff has an explicit deferral logged in the valediction
@@ -497,5 +592,8 @@ Before considering wrap-up complete:
- [ ] Current branch pushed to ALL remotes (verified with =git remote -v=)
- [ ] All other local branches with a tracking upstream pushed to their remote
- [ ] Any untracked-upstream branches surfaced for manual =git push -u=
+- [ ] Step 6 teardown matches the trigger phrase: no-teardown leaves the buffer; teardown drops only =/tmp/ai-wrap-teardown-<project>=; shutdown gates on =cj/ai-term-live-count= = 1 and drops only =/tmp/ai-wrap-shutdown-<project>=
+- [ ] No teardown/shutdown sentinel was dropped before commit + push was verified
+- [ ] Shutdown aborted (fell back to normal wrap, logged in the valediction) when another =aiv-*= session was live or the gate couldn't run
- [ ] Commit message follows format (no =session:=, no Claude attribution)
- [ ] Valediction delivered (brief, specific, warm)