1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
|
#+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. *Git state is clean.* All changes committed + pushed to all remotes. Working tree clean.
5. *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 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-<slug>.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 the prose-quality passes silently before committing — humanizer + jargon-rewrite + semicolon-swap + contractions + sentence-split — 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 <remote> <branch>= 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)
- [ ] =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)
|