#+TITLE: Session Wrap-Up Workflow #+AUTHOR: Craig Jennings & Claude #+DATE: 2026-04-20 * 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. Triggered by Craig saying "wrap it up," "that's a wrap," "let's call it a wrap," or similar. * The Session Record Throughout the session, =.ai/session-context.org= has been maintained with: - =* Summary= — structured distillation (empty or draft during session) - =* Session Log= — chronological narrative of what happened, written as you go At wrap-up, this file becomes the permanent session record by being renamed to =.ai/sessions/YYYY-MM-DD-HH-MM-description.org=. No transcription elsewhere. The file IS the record. * Exit Criteria The wrap-up is complete when: 1. *Summary is written.* The =* Summary= section of =.ai/session-context.org= is populated by reading the =* Session Log= — Active Goal, Decisions, Data Collected / Findings, Files Modified, Next Steps. 2. *File is archived.* =.ai/session-context.org= has been renamed to =.ai/sessions/YYYY-MM-DD-HH-MM-description.org=. The old path no longer exists. 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. 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. * The Workflow ** Step 1: Finalize the Summary Read through the =* Session Log= in =.ai/session-context.org=. Populate (or refine) the =* Summary= section: - *Active Goal* — one or two sentences describing the session's focus - *Decisions* — key choices made, with enough context to recall the /why/ - *Data Collected / Findings* — anything concrete (measurements, root causes, paths, discoveries) - *Files Modified* — what was changed, with one-line rationale per significant file - *Next Steps* — what should happen in the next session Don't repeat everything from the Log in the Summary. The Summary is distillation — pull out what's load-bearing. The Log stays in the file and is available if a future reader wants detail. ** Step 2: Pick a description + rename Read the Summary's Active Goal and the prominent entries in the Session Log. Pick a 4-6 word description that would make sense as a git-commit-message-series summary for the whole session. Good descriptions are concrete nouns/verbs: - =docs-ai-migration-and-ai-launcher= - =mybitch-usb-disconnect-diagnosis= - =ratio-system-health-check= - =orchestration-dashboard-bug-triage= Avoid vague ones: - =session-work= (useless) - =various-improvements= (useless) - =updates= (useless) Get current time and rename: #+begin_src bash mkdir -p .ai/sessions now=$(date +%Y-%m-%d-%H-%M) mv .ai/session-context.org .ai/sessions/${now}-DESCRIPTION.org #+end_src Replace =DESCRIPTION= with your picked slug. ** Step 3: todo.org hygiene pass If the project has a =todo.org= at its root, run the cleanup script before committing. It catches a recurring pattern: org sometimes leaves noise lines like =- State "X" from "X" [date]= when a state-change log lands outside a =:LOGBOOK:= drawer and the state didn't actually change. These lines carry no information and they break org's planning-line parser by wedging between the heading and =DEADLINE:=/=SCHEDULED:=, which kicks the entry out of agenda views. #+begin_src bash [ -f todo.org ] && emacs --batch -q -l .ai/scripts/todo-cleanup.el todo.org #+end_src The script is fast (under half a second on a 4000-line file) and idempotent — if there's nothing to fix, it reports zero changes and exits clean. What it does: 1. *Auto-deletes* bogus state-log lines (matched on identical from/to states). Any deletions show up in the wrap-up commit's diff, so they get reviewed before push. 2. *Reports* "orphan planning lines" — entries whose body has =DEADLINE:= or =SCHEDULED:= but =org-entry-get= can't read it (some other malformation kept it out of canonical position). The script doesn't auto-rewrite these because the right fix depends on whether real state-log history needs preserving — surface them and fix manually if they matter for the agenda. Run the report-only variant first if you want to see what would change without writing: #+begin_src bash emacs --batch -q -l .ai/scripts/todo-cleanup.el --check todo.org #+end_src ** Step 3.5: Linear ticket-state hygiene (skip if project doesn't use Linear) If the project uses Linear and has any tickets currently in *Dev Review* assigned to Craig, sweep them before the wrap-up commit. The check is fast and keeps the board honest — tickets stuck in Dev Review after their PR merges hide actual work-in-progress. #+begin_src mcp__linear__list_issues assignee="me" state="Dev Review" limit=50 #+end_src For each result, look up the linked PR (the =gitBranchName= field on the issue maps to a =headRefName= on the project's GitHub remote — use =gh pr list --author --state all --json number,state,headRefName,mergedAt,title=). If a Dev-Review ticket's PR is *merged*, propose a 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 the app. PR titles prefixed =fix:=, =feat:= usually belong here unless the change is invisible to users. 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 *** Review changes #+begin_src bash git status git diff --stat #+end_src Decide the scope of the wrap-up commit. Usually everything that changed during the session goes into one commit. If anything is intentionally not part of this session's work (pre-existing WIP, unrelated files), leave it out. *** Stage Add the renamed session file and all other session changes: #+begin_src bash git add .ai/sessions/ [other modified paths] #+end_src Do NOT blindly =git add .= — review what's being staged so unrelated dirty state isn't dragged in. *** Commit Commit message rules (also see protocols.org "Git Commit Requirements"): - Subject line: concise, describes what /shipped/. Use conventional prefixes (=docs:=, =refactor:=, =fix:=, =feat:=, =chore:=) — NEVER =session:=. - Body: 1-3 terse sentences describing what was accomplished. - NO Claude Code attribution. NO =Co-Authored-By=. NO references to =notes.org=, =session-context.org=, =.ai/sessions/=, "session wrap-up", or session timestamps. *Wrap-up commits skip the inline-approval gate.* The =commits.md= rule that requires writing the message to =/tmp/commit-.md=, printing inline, and waiting for an approve / request-changes / open-in-editor response does *not* apply to wrap-up commits. The wrap-up flow is meant to be quick — Craig has already authorized the wrap by triggering the workflow ("wrap it up"), and stopping again to approve a commit message disrupts the cadence. Still apply =/voice personal= silently before committing so the message reads cleanly. Just don't print and ask. Commit directly with the cleaned message. If a wrap-up commit needs Craig's eyes for a content reason (sensitive change, unusual scope, something he flagged earlier), surface it explicitly. Otherwise commit and move on. Example: #+begin_example docs: restructure docs/ to .ai/ and unify aix+hey into ai launcher Hidden .ai/ now holds Claude tooling; project-level docs/ reserved for user-facing docs. Single 'ai' launcher (fzf multi + smart tmux + git-aware fetch/pull) replaces the aix script and hey alias. #+end_example Use heredoc for multi-line: #+begin_src bash git commit -m "$(cat <<'EOF' subject line here body sentences here. EOF )" #+end_src *** Push to all remotes #+begin_src bash git remote -v #+end_src Push the current branch to every remote (preserves the mirror behavior — rulesets and a few other repos have github.com + cjennings.net mirrors that should both stay current): #+begin_src bash current=$(git symbolic-ref --short HEAD) for r in $(git remote); do git push "$r" "$current"; done #+end_src Then push every other local branch with a tracking upstream to its tracking remote. This catches feature branches that advanced during the session but aren't the one being wrapped up — without it, work-in-progress branches stay local-only and are at risk if the machine dies before the next wrap-up. #+begin_src bash git for-each-ref --format='%(refname:short) %(upstream:remotename)' refs/heads/ | \ while read branch remote; do [ "$branch" = "$current" ] && continue if [ -z "$remote" ]; then echo " $branch: no tracking upstream — skipped (push manually with 'git push -u')" else git push "$remote" "$branch" fi done #+end_src Behavior: - *Tracked branches* → pushed to their upstream remote. - *Untracked branches* (no upstream set) → surfaced, not pushed. Craig sets the upstream manually with =git push -u = when he's ready. Auto-creating an upstream would commit to a remote choice the workflow can't make safely. - *Diverged or rejected pushes* → surface and stop. Don't force-push from this workflow; resolve manually. *** Verify clean #+begin_src bash git status #+end_src Should show "working tree clean" and "branch is up to date" with each remote. If not, resolve before proceeding. ** Step 5: Valediction Brief, warm closing. 3-4 sentences max. Include: - What was accomplished (specific, not generic) - What's ready for next session - Any critical reminders or deadlines Tone: warm but professional. No emoji unless Craig has explicitly requested. Acknowledge effort when session was long or difficult. Example: #+begin_example That's a wrap. Today we restructured the entire claude-templates ecosystem: docs/ → .ai/ across all 23 projects, unified aix + hey into a single 'ai' launcher with git-aware fetch/pull, and cleaned up 4 code projects on velox. Both machines fully in sync. Two things to pick up next: the chime README WIP (your inline notes from earlier) and archsetup's layout-navigate tests. Both are ratio-local uncommitted state. Good session. Talk tomorrow. #+end_example * 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 2. *Vague description in filename* — =2026-04-20-updates.org= is useless next to =2026-04-20-13-45-docs-ai-migration.org= 3. *=git add .= without review* — drags in unrelated dirty state 4. *=session:= prefix in commit message* — explicitly forbidden; use real change categories 5. *Claude-tooling references in commit message* — describes tooling, not what shipped 6. *Forgetting to push to all remotes* — check =git remote -v=, push to each 7. *Leaving =.ai/session-context.org= in place* — its presence means "interrupted session", confuses next startup 8. *Long preachy valediction* — brief beats thorough * Validation Checklist Before considering wrap-up complete: - [ ] =.ai/session-context.org= =* Summary= section populated - [ ] File renamed to =.ai/sessions/YYYY-MM-DD-HH-MM-description.org= - [ ] =.ai/session-context.org= no longer exists - [ ] =todo-cleanup.el= ran (if =todo.org= exists at project root) - [ ] Any orphan-planning-line warnings reviewed (fix or accept) - [ ] Linear Dev-Review sweep ran; any merged-PR tickets moved to Done or PM Acceptance (skip if project doesn't use Linear) - [ ] =git status= clean after commit + push - [ ] 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= - [ ] Commit message follows format (no =session:=, no Claude attribution) - [ ] Valediction delivered (brief, specific, warm)