jfl 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +77 -7
  2. package/clawdbot-plugin/clawdbot.plugin.json +20 -0
  3. package/clawdbot-plugin/index.js +555 -0
  4. package/clawdbot-plugin/index.ts +582 -0
  5. package/clawdbot-skill/SKILL.md +33 -336
  6. package/clawdbot-skill/index.ts +491 -321
  7. package/clawdbot-skill/skill.json +4 -13
  8. package/dist/commands/clawdbot.d.ts +11 -0
  9. package/dist/commands/clawdbot.d.ts.map +1 -0
  10. package/dist/commands/clawdbot.js +215 -0
  11. package/dist/commands/clawdbot.js.map +1 -0
  12. package/dist/commands/gtm-process-update.d.ts +10 -0
  13. package/dist/commands/gtm-process-update.d.ts.map +1 -0
  14. package/dist/commands/gtm-process-update.js +101 -0
  15. package/dist/commands/gtm-process-update.js.map +1 -0
  16. package/dist/commands/onboard.d.ts.map +1 -1
  17. package/dist/commands/onboard.js +203 -15
  18. package/dist/commands/onboard.js.map +1 -1
  19. package/dist/commands/openclaw.d.ts +56 -0
  20. package/dist/commands/openclaw.d.ts.map +1 -0
  21. package/dist/commands/openclaw.js +700 -0
  22. package/dist/commands/openclaw.js.map +1 -0
  23. package/dist/commands/service-validate.d.ts +12 -0
  24. package/dist/commands/service-validate.d.ts.map +1 -0
  25. package/dist/commands/service-validate.js +611 -0
  26. package/dist/commands/service-validate.js.map +1 -0
  27. package/dist/commands/services-create.d.ts +15 -0
  28. package/dist/commands/services-create.d.ts.map +1 -0
  29. package/dist/commands/services-create.js +1452 -0
  30. package/dist/commands/services-create.js.map +1 -0
  31. package/dist/commands/services-sync-agents.d.ts +23 -0
  32. package/dist/commands/services-sync-agents.d.ts.map +1 -0
  33. package/dist/commands/services-sync-agents.js +207 -0
  34. package/dist/commands/services-sync-agents.js.map +1 -0
  35. package/dist/commands/services.d.ts +7 -1
  36. package/dist/commands/services.d.ts.map +1 -1
  37. package/dist/commands/services.js +347 -22
  38. package/dist/commands/services.js.map +1 -1
  39. package/dist/commands/update.js +0 -0
  40. package/dist/commands/validate-settings.d.ts +37 -0
  41. package/dist/commands/validate-settings.d.ts.map +1 -0
  42. package/dist/commands/validate-settings.js +197 -0
  43. package/dist/commands/validate-settings.js.map +1 -0
  44. package/dist/index.js +155 -60
  45. package/dist/index.js.map +1 -1
  46. package/dist/lib/agent-generator.d.ts.map +1 -1
  47. package/dist/lib/agent-generator.js +94 -1
  48. package/dist/lib/agent-generator.js.map +1 -1
  49. package/dist/lib/openclaw-registry.d.ts +48 -0
  50. package/dist/lib/openclaw-registry.d.ts.map +1 -0
  51. package/dist/lib/openclaw-registry.js +181 -0
  52. package/dist/lib/openclaw-registry.js.map +1 -0
  53. package/dist/lib/openclaw-sdk.d.ts +107 -0
  54. package/dist/lib/openclaw-sdk.d.ts.map +1 -0
  55. package/dist/lib/openclaw-sdk.js +208 -0
  56. package/dist/lib/openclaw-sdk.js.map +1 -0
  57. package/dist/lib/peer-agent-generator.d.ts +44 -0
  58. package/dist/lib/peer-agent-generator.d.ts.map +1 -0
  59. package/dist/lib/peer-agent-generator.js +286 -0
  60. package/dist/lib/peer-agent-generator.js.map +1 -0
  61. package/dist/lib/service-detector.d.ts +1 -1
  62. package/dist/lib/service-detector.d.ts.map +1 -1
  63. package/dist/lib/service-detector.js +118 -5
  64. package/dist/lib/service-detector.js.map +1 -1
  65. package/dist/lib/service-gtm.d.ts +157 -0
  66. package/dist/lib/service-gtm.d.ts.map +1 -0
  67. package/dist/lib/service-gtm.js +786 -0
  68. package/dist/lib/service-gtm.js.map +1 -0
  69. package/dist/lib/service-mcp-base.d.ts +10 -1
  70. package/dist/lib/service-mcp-base.d.ts.map +1 -1
  71. package/dist/lib/service-mcp-base.js +20 -1
  72. package/dist/lib/service-mcp-base.js.map +1 -1
  73. package/dist/mcp/service-peer-mcp.d.ts +36 -0
  74. package/dist/mcp/service-peer-mcp.d.ts.map +1 -0
  75. package/dist/mcp/service-peer-mcp.js +220 -0
  76. package/dist/mcp/service-peer-mcp.js.map +1 -0
  77. package/dist/mcp/service-registry-mcp.js +0 -0
  78. package/dist/utils/settings-validator.d.ts +4 -1
  79. package/dist/utils/settings-validator.d.ts.map +1 -1
  80. package/dist/utils/settings-validator.js +25 -1
  81. package/dist/utils/settings-validator.js.map +1 -1
  82. package/package.json +2 -1
  83. package/template/.claude/service-settings.json +32 -0
  84. package/template/.claude/settings.json +10 -0
  85. package/template/.claude/skills/end/SKILL.md +1780 -0
  86. package/template/.jfl/config.json +2 -1
  87. package/template/.mcp.json +1 -7
  88. package/template/CLAUDE.md +1042 -248
  89. package/template/CLAUDE.md.bak +1187 -0
  90. package/template/scripts/commit-gtm.sh +56 -0
  91. package/template/scripts/commit-product.sh +68 -0
  92. package/template/scripts/migrate-to-branch-sessions.sh +201 -0
  93. package/template/scripts/session/auto-commit.sh +4 -3
  94. package/template/scripts/session/jfl-doctor.sh +222 -83
  95. package/template/scripts/session/session-cleanup.sh +109 -21
  96. package/template/scripts/session/session-end.sh +26 -13
  97. package/template/scripts/session/session-init.sh +280 -98
  98. package/template/scripts/session/test-critical-infrastructure.sh +293 -0
  99. package/template/scripts/session/test-experience-level.sh +336 -0
  100. package/template/scripts/session/test-session-cleanup.sh +268 -0
  101. package/template/scripts/session/test-session-sync.sh +320 -0
  102. package/template/scripts/where-am-i.sh +78 -0
  103. package/template/templates/service-agent/.claude/settings.json +32 -0
  104. package/template/templates/service-agent/CLAUDE.md +334 -0
  105. package/template/templates/service-agent/knowledge/ARCHITECTURE.md +115 -0
  106. package/template/templates/service-agent/knowledge/DEPLOYMENT.md +199 -0
  107. package/template/templates/service-agent/knowledge/RUNBOOK.md +412 -0
  108. package/template/templates/service-agent/knowledge/SERVICE_SPEC.md +77 -0
  109. package/dist/commands/session-mgmt.d.ts +0 -33
  110. package/dist/commands/session-mgmt.d.ts.map +0 -1
  111. package/dist/commands/session-mgmt.js +0 -404
  112. package/dist/commands/session-mgmt.js.map +0 -1
@@ -0,0 +1,1780 @@
1
+ ---
2
+ name: end
3
+ description: End the current JFL session gracefully with automatic merge and cleanup
4
+ triggers:
5
+ - done
6
+ - that's it
7
+ - I'm finished
8
+ - end session
9
+ - /end
10
+ - let's wrap up
11
+ - all set for today
12
+ ---
13
+
14
+ # /end - Session Reconciliation
15
+
16
+ End the current JFL session gracefully with work preservation, conflict resolution, and handoff visibility.
17
+
18
+ ---
19
+
20
+ ## Core Principle
21
+
22
+ **Session ending is a critical handoff point.** Poor UX here creates uncertainty ("Did my work get saved?"), risks lost work if errors aren't handled clearly, and loses context if journal entries aren't captured.
23
+
24
+ JFL's session architecture guarantees work preservation through multiple safety layers:
25
+
26
+ 1. **Continuous auto-commit** - Background process commits every 2 minutes
27
+ 2. **Stop hook** - Auto-commits and merges when terminal closes
28
+ 3. **This skill** - User-initiated clean ending with full visibility
29
+
30
+ When you invoke `/end`, the user wants to:
31
+ - **Know what happened** - What commits merged? What changed?
32
+ - **Trust their work is saved** - No uncertainty about state
33
+ - **Understand next steps** - What should they or others do next?
34
+ - **Get handoff context** - Synopsis of work accomplished
35
+
36
+ This skill provides **comprehensive UX orchestration** around the solid `session-cleanup.sh` infrastructure. The script handles all git operations correctly - we wrap it with clear communication, pre-flight checks, and error guidance.
37
+
38
+ ### What "Clean Ending" Means
39
+
40
+ A clean session end has these properties:
41
+
42
+ 1. **All work committed** - No uncommitted changes left behind
43
+ 2. **Merged to working branch** - Session branch integrated, not stranded
44
+ 3. **Journal entry exists** - Future sessions understand what happened
45
+ 4. **Conflicts resolved** - No manual cleanup required later
46
+ 5. **Remote updated** - Team can see the work
47
+ 6. **Synopsis shown** - Clear summary of accomplishments
48
+
49
+ This skill ensures all six properties are met, or guides the user to resolve issues that prevent them.
50
+
51
+ ### Relationship to Other Safety Mechanisms
52
+
53
+ ```
54
+ User working...
55
+
56
+ ├─ Auto-commit (every 2 minutes)
57
+ │ └─ Prevents data loss during crashes
58
+
59
+ ├─ Stop hook (terminal close)
60
+ │ └─ Automatic fallback if /end not called
61
+
62
+ └─ /end skill (user-initiated) ← YOU ARE HERE
63
+ └─ Best UX: visibility + control
64
+ ```
65
+
66
+ **When to use each:**
67
+ - **Auto-commit**: Runs automatically, you don't invoke it
68
+ - **Stop hook**: Triggered automatically on terminal close
69
+ - **This skill**: User explicitly says "done" → invoke this for best experience
70
+
71
+ ---
72
+
73
+ ## When to Use This Skill
74
+
75
+ ### Explicit Triggers (HIGH CONFIDENCE)
76
+
77
+ User says any of these phrases → **immediately invoke this skill**:
78
+
79
+ | Phrase | Confidence | Action |
80
+ |--------|-----------|--------|
81
+ | "done" | 100% | Invoke immediately |
82
+ | "that's it" | 100% | Invoke immediately |
83
+ | "I'm finished" | 100% | Invoke immediately |
84
+ | "end session" | 100% | Invoke immediately |
85
+ | "/end" | 100% | Invoke immediately |
86
+ | "let's wrap up" | 95% | Invoke immediately |
87
+ | "all set for today" | 95% | Invoke immediately |
88
+ | "I'm out" | 90% | Invoke immediately |
89
+ | "good for now" | 85% | Invoke immediately |
90
+ | "ship it" | 80% | Check context - might mean commit, not end |
91
+
92
+ ### Implicit Triggers (CONTEXT-DEPENDENT)
93
+
94
+ User says these in a concluding context → **consider invoking**:
95
+
96
+ | Phrase | When to Invoke | When NOT to Invoke |
97
+ |--------|---------------|-------------------|
98
+ | "looks good" | After reviewing final work | After reviewing one piece of ongoing work |
99
+ | "perfect" | At end of conversation | In middle of iteration |
100
+ | "thanks" | With no pending questions | After getting help mid-session |
101
+ | "bye" | Clear goodbye | Just casual acknowledgment |
102
+
103
+ **Test: Is this the end of the session or just the end of a task?**
104
+
105
+ ```
106
+ User: "Great, the auth flow works. Thanks!"
107
+
108
+ → NOT an ending (still building, just finished one feature)
109
+ → Don't invoke /end
110
+
111
+ User: "Auth flow is done. That's all I needed today."
112
+
113
+ → IS an ending (explicit scope closure)
114
+ → Invoke /end
115
+ ```
116
+
117
+ ### When NOT to Use
118
+
119
+ **Do NOT invoke this skill if:**
120
+
121
+ 1. **User is continuing work** - "That's done, let's do X next"
122
+ 2. **In middle of iteration** - "Okay that works, but change the color to blue"
123
+ 3. **Just answered a question** - "Got it, thanks" (not ending, just acknowledging)
124
+ 4. **Unclear intent** - If unsure, ask: "Ready to end the session, or keep going?"
125
+
126
+ ---
127
+
128
+ ## Pre-Flight Check
129
+
130
+ Before executing cleanup, gather complete session state. This informs what to show the user and what prompts are needed.
131
+
132
+ ### Step 1: Detect Session Mode
133
+
134
+ ```bash
135
+ # Read worktree state
136
+ WORKTREE_PATH=$(cat .jfl/current-worktree.txt 2>/dev/null || echo "")
137
+
138
+ if [[ "$WORKTREE_PATH" == "direct" ]]; then
139
+ MODE="direct"
140
+ LOCATION=$(pwd)
141
+ elif [[ -n "$WORKTREE_PATH" ]]; then
142
+ MODE="worktree"
143
+ LOCATION="$WORKTREE_PATH"
144
+ else
145
+ # Not in a session
146
+ MODE="none"
147
+ fi
148
+ ```
149
+
150
+ **What this tells you:**
151
+ - `direct` → Single session, working on branch directly
152
+ - `worktree` → Multiple concurrent sessions, isolated worktree
153
+ - `none` → Not in a JFL session (shouldn't happen, but handle gracefully)
154
+
155
+ ### Step 1.5: Detect Service Context
156
+
157
+ After detecting session mode, check if running in a service:
158
+
159
+ ```bash
160
+ # Read config to detect environment
161
+ CONFIG_TYPE=$(jq -r '.type // "unknown"' .jfl/config.json 2>/dev/null)
162
+
163
+ if [[ "$CONFIG_TYPE" == "service" ]]; then
164
+ # Running in a service
165
+ GTM_PARENT=$(jq -r '.gtm_parent // empty' .jfl/config.json)
166
+
167
+ if [[ -z "$GTM_PARENT" ]]; then
168
+ echo "⚠️ Service not linked to GTM workspace"
169
+ echo ""
170
+ echo "This service can still be cleaned up, but changes won't sync to a GTM."
171
+ echo "To link: cd <gtm> && jfl services register $(pwd)"
172
+ echo ""
173
+ fi
174
+
175
+ SERVICE_NAME=$(jq -r '.name' .jfl/config.json)
176
+ SYNC_TO_GTM=true
177
+ echo "📡 Service context detected: $SERVICE_NAME"
178
+ echo " GTM parent: $GTM_PARENT"
179
+ else
180
+ # Running in GTM or standalone
181
+ SYNC_TO_GTM=false
182
+ fi
183
+ ```
184
+
185
+ **What this tells you:**
186
+ - If `SYNC_TO_GTM=true`: This is a service session, sync after cleanup
187
+ - If `GTM_PARENT` is empty: Service exists but not linked
188
+ - Otherwise: Regular GTM or standalone session
189
+
190
+ ### Step 1.6: Validate Service Configuration (Services Only)
191
+
192
+ If running in a service (`SYNC_TO_GTM=true`), validate configuration before ending:
193
+
194
+ ```bash
195
+ if [[ "$SYNC_TO_GTM" == "true" ]]; then
196
+ echo ""
197
+ echo "🔍 Validating service configuration..."
198
+
199
+ # Run validation (non-blocking, just show warnings)
200
+ if jfl services validate --json > /tmp/validation-result.json 2>/dev/null; then
201
+ # Parse results
202
+ ERRORS=$(jq -r '.summary.errors' /tmp/validation-result.json)
203
+ WARNINGS=$(jq -r '.summary.warnings' /tmp/validation-result.json)
204
+
205
+ if [[ "$ERRORS" -gt 0 ]]; then
206
+ echo "⚠️ Service validation found $ERRORS error(s)"
207
+ echo ""
208
+ echo "Run 'jfl services validate' to see details"
209
+ echo "Run 'jfl services validate --fix' to auto-repair"
210
+ echo ""
211
+
212
+ # Ask if they want to fix before ending
213
+ read -p "Auto-fix issues now? (y/N) " -n 1 -r
214
+ echo
215
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
216
+ jfl services validate --fix
217
+ fi
218
+ elif [[ "$WARNINGS" -gt 0 ]]; then
219
+ echo "✓ Validation passed ($WARNINGS warning(s))"
220
+ else
221
+ echo "✓ Service configuration valid"
222
+ fi
223
+ fi
224
+
225
+ rm -f /tmp/validation-result.json
226
+ fi
227
+ ```
228
+
229
+ **What this does:**
230
+ - Validates service configuration before ending session
231
+ - Non-blocking: Shows warnings but doesn't prevent session end
232
+ - Offers to auto-fix issues if errors found
233
+ - Only runs for services, not GTM workspaces
234
+
235
+ **Why this matters:**
236
+ - Catches configuration issues before they cause problems
237
+ - Prevents services from ending in an invalid state
238
+ - Ensures hooks, journal, and GTM integration are correct
239
+
240
+ ### Step 2: Get Branch Information
241
+
242
+ ```bash
243
+ # Current session branch
244
+ BRANCH=$(cat .jfl/current-session-branch.txt 2>/dev/null || git branch --show-current 2>/dev/null || echo "")
245
+
246
+ # Working branch (where session merges to)
247
+ WORKING_BRANCH=$(jq -r '.working_branch // "main"' .jfl/config.json 2>/dev/null || echo "main")
248
+
249
+ # Verify session branch format
250
+ if [[ ! "$BRANCH" =~ ^session- ]]; then
251
+ echo "⚠ Not on a session branch (current: $BRANCH)"
252
+ echo "Session branches start with 'session-'"
253
+ echo ""
254
+ echo "You might already be on $WORKING_BRANCH."
255
+ echo "No cleanup needed."
256
+ exit 0
257
+ fi
258
+ ```
259
+
260
+ **What this tells you:**
261
+ - `BRANCH` → Current session being worked on
262
+ - `WORKING_BRANCH` → Where work will merge (usually `main` or `develop`)
263
+
264
+ ### Step 3: Check for Uncommitted Changes
265
+
266
+ ```bash
267
+ # Check working directory and staging area
268
+ if ! git diff --quiet || ! git diff --cached --quiet; then
269
+ UNCOMMITTED=true
270
+ UNCOMMITTED_COUNT=$(git status --porcelain | wc -l | tr -d ' ')
271
+ else
272
+ UNCOMMITTED=false
273
+ UNCOMMITTED_COUNT=0
274
+ fi
275
+ ```
276
+
277
+ **What this tells you:**
278
+ - `true` → User has uncommitted changes, need to prompt
279
+ - `false` → Clean working directory, can proceed
280
+
281
+ ### Step 4: Check for Journal Entry
282
+
283
+ ```bash
284
+ # Session journal file
285
+ JOURNAL_FILE=".jfl/journal/${BRANCH}.jsonl"
286
+
287
+ if [[ -s "$JOURNAL_FILE" ]]; then
288
+ JOURNAL_EXISTS=true
289
+ JOURNAL_ENTRY_COUNT=$(wc -l < "$JOURNAL_FILE" | tr -d ' ')
290
+ else
291
+ JOURNAL_EXISTS=false
292
+ JOURNAL_ENTRY_COUNT=0
293
+ fi
294
+ ```
295
+
296
+ **What this tells you:**
297
+ - `true` → Session is documented, good handoff
298
+ - `false` → No journal entry, should warn user
299
+
300
+ ### Step 5: Count Session Work
301
+
302
+ ```bash
303
+ # Commits in this session (since branching from working branch)
304
+ COMMIT_COUNT=$(git rev-list --count $WORKING_BRANCH..HEAD 2>/dev/null || echo "0")
305
+
306
+ # Files changed in this session
307
+ FILES_CHANGED=$(git diff --name-only $WORKING_BRANCH..HEAD 2>/dev/null | wc -l | tr -d ' ')
308
+
309
+ # Lines changed (rough metric)
310
+ LINES_ADDED=$(git diff --numstat $WORKING_BRANCH..HEAD 2>/dev/null | awk '{sum+=$1} END {print sum+0}')
311
+ LINES_REMOVED=$(git diff --numstat $WORKING_BRANCH..HEAD 2>/dev/null | awk '{sum+=$2} END {print sum+0}')
312
+ ```
313
+
314
+ **What this tells you:**
315
+ - How much work will be merged
316
+ - Whether session had any meaningful work
317
+ - Context for journal warning (lots of work, no journal = bad)
318
+
319
+ ### Step 6: Calculate Session Duration
320
+
321
+ ```bash
322
+ # First commit in session (timestamp)
323
+ SESSION_START=$(git log --format=%ct --reverse $WORKING_BRANCH..HEAD 2>/dev/null | head -1)
324
+
325
+ if [[ -n "$SESSION_START" ]]; then
326
+ NOW=$(date +%s)
327
+ DURATION_SECONDS=$((NOW - SESSION_START))
328
+ DURATION_HOURS=$((DURATION_SECONDS / 3600))
329
+ DURATION_MINUTES=$(((DURATION_SECONDS % 3600) / 60))
330
+ else
331
+ # No commits yet
332
+ DURATION_HOURS=0
333
+ DURATION_MINUTES=0
334
+ fi
335
+ ```
336
+
337
+ **What this tells you:**
338
+ - How long user has been working
339
+ - Used for synopsis timeframe (show last N hours)
340
+
341
+ ### Complete Pre-Flight Check Script
342
+
343
+ Here's the full pattern to gather all session state:
344
+
345
+ ```bash
346
+ #!/bin/bash
347
+
348
+ # Pre-flight check - gather complete session state
349
+
350
+ echo "Gathering session state..."
351
+
352
+ # 1. Detect mode
353
+ WORKTREE_PATH=$(cat .jfl/current-worktree.txt 2>/dev/null || echo "")
354
+ if [[ "$WORKTREE_PATH" == "direct" ]]; then
355
+ MODE="direct"
356
+ elif [[ -n "$WORKTREE_PATH" ]]; then
357
+ MODE="worktree"
358
+ else
359
+ MODE="none"
360
+ fi
361
+
362
+ # 2. Get branches
363
+ BRANCH=$(cat .jfl/current-session-branch.txt 2>/dev/null || git branch --show-current 2>/dev/null || echo "")
364
+ WORKING_BRANCH=$(jq -r '.working_branch // "main"' .jfl/config.json 2>/dev/null || echo "main")
365
+
366
+ # Verify this is a session
367
+ if [[ ! "$BRANCH" =~ ^session- ]]; then
368
+ echo "⚠ Not in a JFL session"
369
+ exit 1
370
+ fi
371
+
372
+ # 3. Check uncommitted changes
373
+ if ! git diff --quiet || ! git diff --cached --quiet; then
374
+ UNCOMMITTED=true
375
+ UNCOMMITTED_COUNT=$(git status --porcelain | wc -l | tr -d ' ')
376
+ else
377
+ UNCOMMITTED=false
378
+ UNCOMMITTED_COUNT=0
379
+ fi
380
+
381
+ # 4. Check journal
382
+ JOURNAL_FILE=".jfl/journal/${BRANCH}.jsonl"
383
+ if [[ -s "$JOURNAL_FILE" ]]; then
384
+ JOURNAL_EXISTS=true
385
+ JOURNAL_ENTRY_COUNT=$(wc -l < "$JOURNAL_FILE" | tr -d ' ')
386
+ else
387
+ JOURNAL_EXISTS=false
388
+ JOURNAL_ENTRY_COUNT=0
389
+ fi
390
+
391
+ # 5. Count work
392
+ COMMIT_COUNT=$(git rev-list --count $WORKING_BRANCH..HEAD 2>/dev/null || echo "0")
393
+ FILES_CHANGED=$(git diff --name-only $WORKING_BRANCH..HEAD 2>/dev/null | wc -l | tr -d ' ')
394
+ LINES_ADDED=$(git diff --numstat $WORKING_BRANCH..HEAD 2>/dev/null | awk '{sum+=$1} END {print sum+0}')
395
+ LINES_REMOVED=$(git diff --numstat $WORKING_BRANCH..HEAD 2>/dev/null | awk '{sum+=$2} END {print sum+0}')
396
+
397
+ # 6. Calculate duration
398
+ SESSION_START=$(git log --format=%ct --reverse $WORKING_BRANCH..HEAD 2>/dev/null | head -1)
399
+ if [[ -n "$SESSION_START" ]]; then
400
+ NOW=$(date +%s)
401
+ DURATION_SECONDS=$((NOW - SESSION_START))
402
+ DURATION_HOURS=$((DURATION_SECONDS / 3600))
403
+ DURATION_MINUTES=$(((DURATION_SECONDS % 3600) / 60))
404
+ else
405
+ DURATION_HOURS=0
406
+ DURATION_MINUTES=0
407
+ fi
408
+
409
+ # Export for use by skill
410
+ echo "MODE=$MODE"
411
+ echo "BRANCH=$BRANCH"
412
+ echo "WORKING_BRANCH=$WORKING_BRANCH"
413
+ echo "UNCOMMITTED=$UNCOMMITTED"
414
+ echo "UNCOMMITTED_COUNT=$UNCOMMITTED_COUNT"
415
+ echo "JOURNAL_EXISTS=$JOURNAL_EXISTS"
416
+ echo "JOURNAL_ENTRY_COUNT=$JOURNAL_ENTRY_COUNT"
417
+ echo "COMMIT_COUNT=$COMMIT_COUNT"
418
+ echo "FILES_CHANGED=$FILES_CHANGED"
419
+ echo "LINES_ADDED=$LINES_ADDED"
420
+ echo "LINES_REMOVED=$LINES_REMOVED"
421
+ echo "DURATION_HOURS=$DURATION_HOURS"
422
+ echo "DURATION_MINUTES=$DURATION_MINUTES"
423
+ ```
424
+
425
+ You can run this and parse the output, or inline the checks directly in your skill logic.
426
+
427
+ ---
428
+
429
+ ## User Experience Flows
430
+
431
+ ### Scenario 1: Clean State (Happy Path)
432
+
433
+ **State:** No uncommitted changes, journal exists, clean merge expected
434
+
435
+ **What user sees:**
436
+
437
+ ```
438
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
439
+ Ending Session
440
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
441
+ Session: session-goose-20260210-1430-a1b2c3
442
+ Mode: worktree
443
+ Merging to: main
444
+
445
+ Changes:
446
+ • 8 commits
447
+ • 12 files modified
448
+ • +234 / -67 lines
449
+
450
+ ✓ Journal entry exists (3 entries)
451
+ ✓ No uncommitted changes
452
+
453
+ Executing cleanup...
454
+ ✓ Merged to main
455
+ ✓ Pushed to origin
456
+ ✓ Removed worktree
457
+ ✓ Deleted session branch
458
+
459
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
460
+ Work Summary (2h 15m session)
461
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
462
+
463
+ [Synopsis output here...]
464
+
465
+ ✓ Session ended successfully
466
+ ```
467
+
468
+ **Implementation:**
469
+
470
+ ```bash
471
+ # After pre-flight check shows clean state
472
+
473
+ echo ""
474
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
475
+ echo " Ending Session"
476
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
477
+ echo "Session: $BRANCH"
478
+ echo "Mode: $MODE"
479
+ echo "Merging to: $WORKING_BRANCH"
480
+ echo ""
481
+ echo "Changes:"
482
+ echo " • $COMMIT_COUNT commits"
483
+ echo " • $FILES_CHANGED files modified"
484
+ echo " • +$LINES_ADDED / -$LINES_REMOVED lines"
485
+ echo ""
486
+ echo "✓ Journal entry exists ($JOURNAL_ENTRY_COUNT entries)"
487
+ echo "✓ No uncommitted changes"
488
+ echo ""
489
+ echo "Executing cleanup..."
490
+
491
+ # Call session-cleanup.sh
492
+ ./scripts/session/session-cleanup.sh 2>&1 | while IFS= read -r line; do
493
+ # Filter output to show only key steps
494
+ if [[ "$line" =~ ^✓ ]] || [[ "$line" =~ ^⚠ ]] || [[ "$line" =~ "Merged" ]] || [[ "$line" =~ "Pushed" ]]; then
495
+ echo " $line"
496
+ fi
497
+ done
498
+
499
+ EXIT_CODE=${PIPESTATUS[0]}
500
+
501
+ if [[ $EXIT_CODE -eq 0 ]]; then
502
+ # Show synopsis
503
+ echo ""
504
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
505
+ echo " Work Summary (${DURATION_HOURS}h ${DURATION_MINUTES}m session)"
506
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
507
+ jfl synopsis $((DURATION_HOURS + 1)) # Round up to next hour
508
+ echo ""
509
+ echo "✓ Session ended successfully"
510
+ else
511
+ echo ""
512
+ echo "⚠ Session cleanup encountered issues"
513
+ echo "See log: .jfl/logs/session-cleanup.log"
514
+ fi
515
+ ```
516
+
517
+ ### Scenario 2: Uncommitted Changes
518
+
519
+ **State:** User has uncommitted changes (forgot to commit before ending)
520
+
521
+ **What user sees:**
522
+
523
+ ```
524
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
525
+ Uncommitted Changes Detected
526
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
527
+
528
+ You have 5 uncommitted changes:
529
+
530
+ M src/lib/auth.ts
531
+ M src/components/LoginForm.tsx
532
+ A src/lib/session-manager.ts
533
+ ?? src/lib/types.ts
534
+ M README.md
535
+
536
+ Options:
537
+ 1. Auto-commit these changes (recommended)
538
+ 2. Show me the diff first
539
+ 3. Discard these changes (⚠ cannot be undone)
540
+ 4. Cancel (stay in session)
541
+
542
+ What would you like to do? [1-4]:
543
+ ```
544
+
545
+ **Implementation:**
546
+
547
+ ```bash
548
+ # After detecting UNCOMMITTED=true
549
+
550
+ echo ""
551
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
552
+ echo " Uncommitted Changes Detected"
553
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
554
+ echo ""
555
+ echo "You have $UNCOMMITTED_COUNT uncommitted changes:"
556
+ echo ""
557
+ git status --porcelain | head -10 | sed 's/^/ /'
558
+ if [[ $UNCOMMITTED_COUNT -gt 10 ]]; then
559
+ echo " ... and $((UNCOMMITTED_COUNT - 10)) more"
560
+ fi
561
+ echo ""
562
+ ```
563
+
564
+ **Then present options to the user via AskUserQuestion tool:**
565
+
566
+ ```json
567
+ {
568
+ "questions": [
569
+ {
570
+ "question": "What would you like to do with uncommitted changes?",
571
+ "header": "Uncommitted",
572
+ "multiSelect": false,
573
+ "options": [
574
+ {
575
+ "label": "Auto-commit (Recommended)",
576
+ "description": "Automatically commit all changes with message 'session: end'. Safe and fast."
577
+ },
578
+ {
579
+ "label": "Show diff first",
580
+ "description": "Review changes before deciding. Lets you see exactly what will be committed."
581
+ },
582
+ {
583
+ "label": "Discard changes",
584
+ "description": "⚠ Permanently delete uncommitted changes. Cannot be undone. Only use if you're sure."
585
+ },
586
+ {
587
+ "label": "Cancel",
588
+ "description": "Stay in the session. Commit changes manually, then run /end again."
589
+ }
590
+ ]
591
+ }
592
+ ]
593
+ }
594
+ ```
595
+
596
+ **Handle each choice:**
597
+
598
+ **Choice 1: Auto-commit**
599
+ ```bash
600
+ echo "Auto-committing changes..."
601
+ git add -A
602
+ git commit -m "session: end $(date +%Y-%m-%d\ %H:%M)"
603
+ echo "✓ Changes committed"
604
+ echo ""
605
+ # Continue to cleanup
606
+ ```
607
+
608
+ **Choice 2: Show diff**
609
+ ```bash
610
+ echo "Changes to be committed:"
611
+ echo ""
612
+ git diff HEAD
613
+ echo ""
614
+ echo "What now?"
615
+ echo " 1. Commit these changes"
616
+ echo " 2. Discard these changes"
617
+ echo " 3. Cancel"
618
+ # Prompt again
619
+ ```
620
+
621
+ **Choice 3: Discard**
622
+ ```bash
623
+ echo "⚠ WARNING: This will permanently delete all uncommitted changes."
624
+ echo ""
625
+ echo "Type 'discard' to confirm: "
626
+ # Wait for user confirmation
627
+ # If confirmed:
628
+ git reset --hard HEAD
629
+ git clean -fd
630
+ echo "✓ Changes discarded"
631
+ # Continue to cleanup
632
+ ```
633
+
634
+ **Choice 4: Cancel**
635
+ ```bash
636
+ echo "Session still active."
637
+ echo "Commit your changes, then run /end again."
638
+ exit 0
639
+ ```
640
+
641
+ ### Scenario 3: No Journal Entry
642
+
643
+ **State:** Session has substantial work but no journal entry
644
+
645
+ **What user sees:**
646
+
647
+ ```
648
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
649
+ Missing Journal Entry
650
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
651
+
652
+ This session has work, but no journal entry:
653
+ • 8 commits
654
+ • 12 files changed
655
+ • 2h 15m duration
656
+
657
+ Journal entries help you (and others) understand what
658
+ happened when resuming work later.
659
+
660
+ Would you like to:
661
+ 1. Write a quick journal entry now (30 seconds)
662
+ 2. Skip (not recommended, but allowed)
663
+
664
+ Choice [1-2]:
665
+ ```
666
+
667
+ **Implementation:**
668
+
669
+ ```bash
670
+ # After detecting JOURNAL_EXISTS=false and COMMIT_COUNT > 0
671
+
672
+ echo ""
673
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
674
+ echo " Missing Journal Entry"
675
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
676
+ echo ""
677
+ echo "This session has work, but no journal entry:"
678
+ echo " • $COMMIT_COUNT commits"
679
+ echo " • $FILES_CHANGED files changed"
680
+ echo " • ${DURATION_HOURS}h ${DURATION_MINUTES}m duration"
681
+ echo ""
682
+ echo "Journal entries help you (and others) understand what"
683
+ echo "happened when resuming work later."
684
+ echo ""
685
+ ```
686
+
687
+ **Present options:**
688
+
689
+ ```json
690
+ {
691
+ "questions": [
692
+ {
693
+ "question": "Would you like to write a journal entry?",
694
+ "header": "Journal",
695
+ "multiSelect": false,
696
+ "options": [
697
+ {
698
+ "label": "Write entry (Recommended)",
699
+ "description": "Quick 30-second entry. Just a title and summary of what you did."
700
+ },
701
+ {
702
+ "label": "Skip",
703
+ "description": "Not recommended. Future sessions won't have context for this work."
704
+ }
705
+ ]
706
+ }
707
+ ]
708
+ }
709
+ ```
710
+
711
+ **If write entry:**
712
+
713
+ Prompt for minimal info:
714
+
715
+ ```
716
+ What did you work on? (one sentence):
717
+ ```
718
+
719
+ Wait for user input, then write basic journal entry:
720
+
721
+ ```bash
722
+ TITLE="$USER_INPUT"
723
+ SUMMARY="Session work (auto-generated)"
724
+ TS=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
725
+
726
+ # Get files changed
727
+ FILES=$(git diff --name-only $WORKING_BRANCH..HEAD | jq -R -s -c 'split("\n") | map(select(length > 0))')
728
+
729
+ # Write entry
730
+ cat >> "$JOURNAL_FILE" << EOF
731
+ {"v":1,"ts":"$TS","session":"$BRANCH","type":"session-end","status":"complete","title":"$TITLE","summary":"$SUMMARY","files":$FILES}
732
+ EOF
733
+
734
+ echo "✓ Journal entry created"
735
+ ```
736
+
737
+ **If skip:**
738
+
739
+ ```bash
740
+ echo "⚠ Proceeding without journal entry"
741
+ echo "Note: This makes handoffs harder for future sessions"
742
+ echo ""
743
+ # Continue to cleanup
744
+ ```
745
+
746
+ ### Scenario 4: Merge Conflicts
747
+
748
+ **State:** Cleanup script detects conflicts that can't be auto-resolved
749
+
750
+ **What user sees:**
751
+
752
+ ```
753
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
754
+ Merge Conflicts Detected
755
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
756
+
757
+ Conflicts in:
758
+ • src/lib/auth.ts
759
+ • src/components/LoginForm.tsx
760
+
761
+ Your session branch has been preserved: session-goose-20260210-1430-a1b2c3
762
+
763
+ To resolve manually:
764
+
765
+ 1. Review changes:
766
+ git log main..session-goose-20260210-1430-a1b2c3
767
+
768
+ 2. Merge manually:
769
+ git checkout main
770
+ git merge session-goose-20260210-1430-a1b2c3
771
+
772
+ 3. Resolve conflicts in your editor
773
+
774
+ 4. Complete merge:
775
+ git add .
776
+ git commit
777
+ git push origin main
778
+
779
+ 5. Delete session branch:
780
+ git branch -D session-goose-20260210-1430-a1b2c3
781
+
782
+ Need help? Ask Claude to guide you through conflict resolution.
783
+ ```
784
+
785
+ **Implementation:**
786
+
787
+ ```bash
788
+ # session-cleanup.sh handles conflict detection
789
+ # If it exits with conflicts, parse output and guide user
790
+
791
+ EXIT_CODE=${PIPESTATUS[0]}
792
+
793
+ if [[ $EXIT_CODE -ne 0 ]]; then
794
+ # Check if conflicts were the issue
795
+ if grep -q "Merge conflicts remain" .jfl/logs/session-cleanup.log; then
796
+ echo ""
797
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
798
+ echo " Merge Conflicts Detected"
799
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
800
+ echo ""
801
+ echo "Conflicts in:"
802
+ grep "Cannot auto-resolve:" .jfl/logs/session-cleanup.log | sed 's/^.*: / • /'
803
+ echo ""
804
+ echo "Your session branch has been preserved: $BRANCH"
805
+ echo ""
806
+ echo "To resolve manually:"
807
+ echo ""
808
+ echo "1. Review changes:"
809
+ echo " git log $WORKING_BRANCH..$BRANCH"
810
+ echo ""
811
+ echo "2. Merge manually:"
812
+ echo " git checkout $WORKING_BRANCH"
813
+ echo " git merge $BRANCH"
814
+ echo ""
815
+ echo "3. Resolve conflicts in your editor"
816
+ echo ""
817
+ echo "4. Complete merge:"
818
+ echo " git add ."
819
+ echo " git commit"
820
+ echo " git push origin $WORKING_BRANCH"
821
+ echo ""
822
+ echo "5. Delete session branch:"
823
+ echo " git branch -D $BRANCH"
824
+ echo ""
825
+ echo "Need help? Ask Claude to guide you through conflict resolution."
826
+ fi
827
+ fi
828
+ ```
829
+
830
+ ---
831
+
832
+ ## Implementation Steps
833
+
834
+ ### Complete Execution Flow
835
+
836
+ Here's the full implementation pattern for the `/end` skill:
837
+
838
+ ```bash
839
+ #!/bin/bash
840
+
841
+ # /end skill implementation
842
+
843
+ set -e
844
+
845
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
846
+ echo " Preparing to End Session"
847
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
848
+ echo ""
849
+
850
+ # ============================================================
851
+ # STEP 1: Pre-Flight Check
852
+ # ============================================================
853
+
854
+ echo "Running pre-flight checks..."
855
+ echo ""
856
+
857
+ # Detect mode
858
+ WORKTREE_PATH=$(cat .jfl/current-worktree.txt 2>/dev/null || echo "")
859
+ if [[ "$WORKTREE_PATH" == "direct" ]]; then
860
+ MODE="direct"
861
+ elif [[ -n "$WORKTREE_PATH" ]]; then
862
+ MODE="worktree"
863
+ else
864
+ MODE="none"
865
+ fi
866
+
867
+ # Get branches
868
+ BRANCH=$(cat .jfl/current-session-branch.txt 2>/dev/null || git branch --show-current 2>/dev/null || echo "")
869
+ WORKING_BRANCH=$(jq -r '.working_branch // "main"' .jfl/config.json 2>/dev/null || echo "main")
870
+
871
+ # Verify session
872
+ if [[ ! "$BRANCH" =~ ^session- ]]; then
873
+ echo "⚠ Not in a JFL session (current branch: ${BRANCH:-none})"
874
+ echo ""
875
+ echo "You might already be on $WORKING_BRANCH."
876
+ echo "Session branches start with 'session-'."
877
+ echo ""
878
+ echo "No cleanup needed."
879
+ exit 0
880
+ fi
881
+
882
+ # Check uncommitted
883
+ if ! git diff --quiet || ! git diff --cached --quiet; then
884
+ UNCOMMITTED=true
885
+ UNCOMMITTED_COUNT=$(git status --porcelain | wc -l | tr -d ' ')
886
+ else
887
+ UNCOMMITTED=false
888
+ UNCOMMITTED_COUNT=0
889
+ fi
890
+
891
+ # Check journal
892
+ JOURNAL_FILE=".jfl/journal/${BRANCH}.jsonl"
893
+ if [[ -s "$JOURNAL_FILE" ]]; then
894
+ JOURNAL_EXISTS=true
895
+ JOURNAL_ENTRY_COUNT=$(wc -l < "$JOURNAL_FILE" | tr -d ' ')
896
+ else
897
+ JOURNAL_EXISTS=false
898
+ JOURNAL_ENTRY_COUNT=0
899
+ fi
900
+
901
+ # Count work
902
+ COMMIT_COUNT=$(git rev-list --count $WORKING_BRANCH..HEAD 2>/dev/null || echo "0")
903
+ FILES_CHANGED=$(git diff --name-only $WORKING_BRANCH..HEAD 2>/dev/null | wc -l | tr -d ' ')
904
+ LINES_ADDED=$(git diff --numstat $WORKING_BRANCH..HEAD 2>/dev/null | awk '{sum+=$1} END {print sum+0}')
905
+ LINES_REMOVED=$(git diff --numstat $WORKING_BRANCH..HEAD 2>/dev/null | awk '{sum+=$2} END {print sum+0}')
906
+
907
+ # Calculate duration
908
+ SESSION_START=$(git log --format=%ct --reverse $WORKING_BRANCH..HEAD 2>/dev/null | head -1)
909
+ if [[ -n "$SESSION_START" ]]; then
910
+ NOW=$(date +%s)
911
+ DURATION_SECONDS=$((NOW - SESSION_START))
912
+ DURATION_HOURS=$((DURATION_SECONDS / 3600))
913
+ DURATION_MINUTES=$(((DURATION_SECONDS % 3600) / 60))
914
+ else
915
+ DURATION_HOURS=0
916
+ DURATION_MINUTES=0
917
+ fi
918
+
919
+ # ============================================================
920
+ # STEP 2: Display Session Summary
921
+ # ============================================================
922
+
923
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
924
+ echo " Session Summary"
925
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
926
+ echo "Session: $BRANCH"
927
+ echo "Mode: $MODE"
928
+ echo "Merging to: $WORKING_BRANCH"
929
+ echo "Duration: ${DURATION_HOURS}h ${DURATION_MINUTES}m"
930
+ echo ""
931
+ echo "Changes:"
932
+ echo " • $COMMIT_COUNT commits"
933
+ echo " • $FILES_CHANGED files modified"
934
+ echo " • +$LINES_ADDED / -$LINES_REMOVED lines"
935
+ echo ""
936
+
937
+ # ============================================================
938
+ # STEP 3: Handle Uncommitted Changes
939
+ # ============================================================
940
+
941
+ if [[ "$UNCOMMITTED" == "true" ]]; then
942
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
943
+ echo " Uncommitted Changes Detected"
944
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
945
+ echo ""
946
+ echo "You have $UNCOMMITTED_COUNT uncommitted changes:"
947
+ echo ""
948
+ git status --porcelain | head -10 | sed 's/^/ /'
949
+ if [[ $UNCOMMITTED_COUNT -gt 10 ]]; then
950
+ echo " ... and $((UNCOMMITTED_COUNT - 10)) more"
951
+ fi
952
+ echo ""
953
+
954
+ # Use AskUserQuestion tool here in actual implementation
955
+ # For script, prompt directly:
956
+ echo "Options:"
957
+ echo " 1. Auto-commit (recommended)"
958
+ echo " 2. Show diff first"
959
+ echo " 3. Discard changes (⚠ cannot undo)"
960
+ echo " 4. Cancel"
961
+ echo ""
962
+ read -p "Choice [1-4]: " CHOICE
963
+
964
+ case $CHOICE in
965
+ 1)
966
+ echo "Auto-committing changes..."
967
+ git add -A
968
+ git commit -m "session: end $(date +%Y-%m-%d\ %H:%M)"
969
+ echo "✓ Changes committed"
970
+ echo ""
971
+ ;;
972
+ 2)
973
+ git diff HEAD
974
+ echo ""
975
+ echo "Commit these changes? [y/n]"
976
+ read -p "> " CONFIRM
977
+ if [[ "$CONFIRM" =~ ^[Yy] ]]; then
978
+ git add -A
979
+ git commit -m "session: end $(date +%Y-%m-%d\ %H:%M)"
980
+ echo "✓ Changes committed"
981
+ else
982
+ echo "Cancelled."
983
+ exit 0
984
+ fi
985
+ ;;
986
+ 3)
987
+ echo "⚠ WARNING: Permanently delete uncommitted changes?"
988
+ read -p "Type 'discard' to confirm: " CONFIRM
989
+ if [[ "$CONFIRM" == "discard" ]]; then
990
+ git reset --hard HEAD
991
+ git clean -fd
992
+ echo "✓ Changes discarded"
993
+ else
994
+ echo "Cancelled."
995
+ exit 0
996
+ fi
997
+ ;;
998
+ 4)
999
+ echo "Session still active."
1000
+ echo "Commit your changes, then run /end again."
1001
+ exit 0
1002
+ ;;
1003
+ esac
1004
+ fi
1005
+
1006
+ # ============================================================
1007
+ # STEP 4: Handle Missing Journal
1008
+ # ============================================================
1009
+
1010
+ if [[ "$JOURNAL_EXISTS" == "false" ]] && [[ $COMMIT_COUNT -gt 0 ]]; then
1011
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1012
+ echo " Missing Journal Entry"
1013
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1014
+ echo ""
1015
+ echo "This session has work, but no journal entry:"
1016
+ echo " • $COMMIT_COUNT commits"
1017
+ echo " • $FILES_CHANGED files changed"
1018
+ echo " • ${DURATION_HOURS}h ${DURATION_MINUTES}m duration"
1019
+ echo ""
1020
+ echo "Journal entries help future sessions understand this work."
1021
+ echo ""
1022
+ echo "Options:"
1023
+ echo " 1. Write quick entry (30 seconds)"
1024
+ echo " 2. Skip (not recommended)"
1025
+ echo ""
1026
+ read -p "Choice [1-2]: " CHOICE
1027
+
1028
+ if [[ "$CHOICE" == "1" ]]; then
1029
+ echo ""
1030
+ read -p "What did you work on? (one sentence): " TITLE
1031
+ SUMMARY="Session work"
1032
+ TS=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
1033
+ FILES=$(git diff --name-only $WORKING_BRANCH..HEAD | jq -R -s -c 'split("\n") | map(select(length > 0))')
1034
+
1035
+ mkdir -p .jfl/journal
1036
+ cat >> "$JOURNAL_FILE" << EOF
1037
+ {"v":1,"ts":"$TS","session":"$BRANCH","type":"session-end","status":"complete","title":"$TITLE","summary":"$SUMMARY","files":$FILES}
1038
+ EOF
1039
+
1040
+ echo "✓ Journal entry created"
1041
+ echo ""
1042
+ else
1043
+ echo "⚠ Proceeding without journal entry"
1044
+ echo ""
1045
+ fi
1046
+ else
1047
+ echo "✓ Journal entry exists ($JOURNAL_ENTRY_COUNT entries)"
1048
+ fi
1049
+
1050
+ if [[ "$UNCOMMITTED" == "false" ]]; then
1051
+ echo "✓ No uncommitted changes"
1052
+ fi
1053
+
1054
+ # ============================================================
1055
+ # STEP 5: Execute Cleanup
1056
+ # ============================================================
1057
+
1058
+ echo ""
1059
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1060
+ echo " Executing Cleanup"
1061
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1062
+ echo ""
1063
+
1064
+ # Create logs directory
1065
+ mkdir -p .jfl/logs
1066
+
1067
+ # Run session-cleanup.sh and capture output
1068
+ ./scripts/session/session-cleanup.sh 2>&1 | tee .jfl/logs/session-cleanup.log | while IFS= read -r line; do
1069
+ # Filter to show only key steps
1070
+ if [[ "$line" =~ ^✓ ]] || [[ "$line" =~ ^⚠ ]] || [[ "$line" =~ "Merged" ]] || [[ "$line" =~ "Pushed" ]] || [[ "$line" =~ "Removing" ]] || [[ "$line" =~ "Deleting" ]]; then
1071
+ echo " $line"
1072
+ fi
1073
+ done
1074
+
1075
+ EXIT_CODE=${PIPESTATUS[0]}
1076
+
1077
+ # ============================================================
1078
+ # STEP 5.5: Sync to GTM (if in service)
1079
+ # ============================================================
1080
+
1081
+ if [[ "$SYNC_TO_GTM" == "true" && -n "$GTM_PARENT" ]]; then
1082
+ echo ""
1083
+ echo "📡 Syncing to GTM workspace..."
1084
+
1085
+ # Validate GTM parent exists
1086
+ if [[ ! -d "$GTM_PARENT" ]]; then
1087
+ echo "⚠️ GTM parent not found: $GTM_PARENT"
1088
+ echo "Skipping sync. Session cleaned up locally."
1089
+ else
1090
+ # Validate it's actually a GTM
1091
+ GTM_TYPE=$(jq -r '.type // empty' "$GTM_PARENT/.jfl/config.json" 2>/dev/null)
1092
+ if [[ "$GTM_TYPE" != "gtm" ]]; then
1093
+ echo "⚠️ Parent is not a GTM workspace (type: $GTM_TYPE)"
1094
+ echo "Skipping sync. Session cleaned up locally."
1095
+ else
1096
+ # 1. Sync journal entries
1097
+ mkdir -p "$GTM_PARENT/.jfl/journal"
1098
+
1099
+ SYNCED_COUNT=0
1100
+ for journal in .jfl/journal/*.jsonl; do
1101
+ if [[ -f "$journal" ]]; then
1102
+ BASENAME=$(basename "$journal")
1103
+ TARGET="$GTM_PARENT/.jfl/journal/service-${SERVICE_NAME}-${BASENAME}"
1104
+
1105
+ # Copy journal with preserved permissions
1106
+ cp "$journal" "$TARGET"
1107
+ ((SYNCED_COUNT++))
1108
+ echo " ✓ Synced: $BASENAME"
1109
+ fi
1110
+ done
1111
+
1112
+ # 2. Update GTM's last_sync timestamp
1113
+ cd "$GTM_PARENT"
1114
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1115
+
1116
+ # Use jq to update the timestamp (create registered_services if needed)
1117
+ jq --arg name "$SERVICE_NAME" \
1118
+ --arg ts "$TIMESTAMP" \
1119
+ '(.registered_services // []) |= (
1120
+ if any(.name == $name) then
1121
+ map(if .name == $name then .last_sync = $ts else . end)
1122
+ else
1123
+ . + [{"name": $name, "last_sync": $ts}]
1124
+ end
1125
+ )' \
1126
+ .jfl/config.json > .jfl/config.json.tmp && \
1127
+ mv .jfl/config.json.tmp .jfl/config.json
1128
+
1129
+ # 3. Create sync entry in GTM journal
1130
+ GTM_SESSION=$(git branch --show-current 2>/dev/null || echo "main")
1131
+ GTM_JOURNAL=".jfl/journal/${GTM_SESSION}.jsonl"
1132
+
1133
+ cat >> "$GTM_JOURNAL" << EOF
1134
+ {"v":1,"ts":"$TIMESTAMP","session":"$GTM_SESSION","type":"sync","title":"Service sync: $SERVICE_NAME","summary":"Synced $SYNCED_COUNT journal file(s) from $SERVICE_NAME","service":"$SERVICE_NAME","files_synced":$SYNCED_COUNT}
1135
+ EOF
1136
+
1137
+ echo " ✓ Updated GTM registry"
1138
+ echo ""
1139
+ echo "✅ Sync complete ($SYNCED_COUNT journal file(s) → GTM)"
1140
+ fi
1141
+ fi
1142
+ fi
1143
+
1144
+ # ============================================================
1145
+ # STEP 6: Report Results
1146
+ # ============================================================
1147
+
1148
+ echo ""
1149
+
1150
+ if [[ $EXIT_CODE -eq 0 ]]; then
1151
+ # Check if merge happened or if conflicts remained
1152
+ if grep -q "Merge conflicts remain" .jfl/logs/session-cleanup.log; then
1153
+ # Conflicts - branch preserved
1154
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1155
+ echo " Merge Conflicts Detected"
1156
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1157
+ echo ""
1158
+ echo "Conflicts in:"
1159
+ grep "Cannot auto-resolve:" .jfl/logs/session-cleanup.log | sed 's/^.*: / • /'
1160
+ echo ""
1161
+ echo "Your session branch has been preserved: $BRANCH"
1162
+ echo ""
1163
+ echo "To resolve manually:"
1164
+ echo ""
1165
+ echo "1. Review changes:"
1166
+ echo " git log $WORKING_BRANCH..$BRANCH"
1167
+ echo ""
1168
+ echo "2. Merge manually:"
1169
+ echo " git checkout $WORKING_BRANCH"
1170
+ echo " git merge $BRANCH"
1171
+ echo ""
1172
+ echo "3. Resolve conflicts in your editor"
1173
+ echo ""
1174
+ echo "4. Complete merge:"
1175
+ echo " git add ."
1176
+ echo " git commit"
1177
+ echo " git push origin $WORKING_BRANCH"
1178
+ echo ""
1179
+ echo "5. Delete session branch:"
1180
+ echo " git branch -D $BRANCH"
1181
+ echo ""
1182
+ echo "Need help? Ask Claude to guide you through conflict resolution."
1183
+ else
1184
+ # Success - show synopsis
1185
+ echo "✓ Session ended successfully"
1186
+
1187
+ # ============================================================
1188
+ # STEP 7: Show Synopsis
1189
+ # ============================================================
1190
+
1191
+ echo ""
1192
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1193
+ echo " Work Summary (${DURATION_HOURS}h ${DURATION_MINUTES}m session)"
1194
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1195
+ echo ""
1196
+
1197
+ # Round up duration for synopsis
1198
+ SYNOPSIS_HOURS=$((DURATION_HOURS + 1))
1199
+ if [[ $SYNOPSIS_HOURS -lt 1 ]]; then
1200
+ SYNOPSIS_HOURS=1
1201
+ fi
1202
+
1203
+ # Run synopsis command
1204
+ if command -v jfl >/dev/null 2>&1; then
1205
+ jfl synopsis $SYNOPSIS_HOURS 2>/dev/null || echo "Synopsis not available (jfl command not found or synopsis failed)"
1206
+ else
1207
+ echo "Synopsis not available (jfl command not in PATH)"
1208
+ fi
1209
+
1210
+ echo ""
1211
+ echo "✓ All changes merged to $WORKING_BRANCH and pushed to origin"
1212
+ fi
1213
+ else
1214
+ # Generic failure
1215
+ echo "⚠ Session cleanup encountered issues"
1216
+ echo ""
1217
+ echo "Check the log for details:"
1218
+ echo " cat .jfl/logs/session-cleanup.log"
1219
+ echo ""
1220
+ echo "Your session branch is preserved: $BRANCH"
1221
+ echo "No work has been lost."
1222
+ echo ""
1223
+ echo "Common issues:"
1224
+ echo " • Push failed → retry: git push origin $WORKING_BRANCH"
1225
+ echo " • Merge conflicts → see conflict resolution steps above"
1226
+ echo " • Other errors → check log and ask for help"
1227
+ fi
1228
+
1229
+ echo ""
1230
+ ```
1231
+
1232
+ ---
1233
+
1234
+ ## Error Handling
1235
+
1236
+ ### Error Pattern 1: Not in a Session
1237
+
1238
+ **Detection:**
1239
+ ```bash
1240
+ if [[ ! "$BRANCH" =~ ^session- ]]; then
1241
+ # Not in a session
1242
+ fi
1243
+ ```
1244
+
1245
+ **Message:**
1246
+ ```
1247
+ ⚠ Not in a JFL session
1248
+
1249
+ Current branch: main
1250
+
1251
+ You're already on your working branch.
1252
+ Session branches start with 'session-'.
1253
+
1254
+ No cleanup needed.
1255
+ ```
1256
+
1257
+ **Recovery:** None needed, gracefully exit.
1258
+
1259
+ ### Error Pattern 2: Cleanup Script Fails
1260
+
1261
+ **Detection:**
1262
+ ```bash
1263
+ EXIT_CODE=${PIPESTATUS[0]}
1264
+ if [[ $EXIT_CODE -ne 0 ]]; then
1265
+ # Cleanup failed
1266
+ fi
1267
+ ```
1268
+
1269
+ **Message:**
1270
+ ```
1271
+ ⚠ Session cleanup encountered issues
1272
+
1273
+ Check the log for details:
1274
+ cat .jfl/logs/session-cleanup.log
1275
+
1276
+ Your session branch is preserved: session-goose-20260210-1430-a1b2c3
1277
+ No work has been lost.
1278
+
1279
+ Common issues:
1280
+ • Push failed → retry: git push origin main
1281
+ • Merge conflicts → see conflict resolution guide
1282
+ • Other errors → share log with Claude for help
1283
+ ```
1284
+
1285
+ **Recovery:** Guide user to check log, provide common solutions.
1286
+
1287
+ ### Error Pattern 3: Push Fails
1288
+
1289
+ **Detection:**
1290
+ ```bash
1291
+ if grep -q "Push failed" .jfl/logs/session-cleanup.log; then
1292
+ # Push to remote failed
1293
+ fi
1294
+ ```
1295
+
1296
+ **Message:**
1297
+ ```
1298
+ ✓ Merged to main locally
1299
+ ⚠ Push to origin failed
1300
+
1301
+ Your work is merged locally but not on GitHub yet.
1302
+
1303
+ To retry push:
1304
+ git push origin main
1305
+
1306
+ Common causes:
1307
+ • No network connection
1308
+ • Remote has new commits → fetch and merge first
1309
+ • Authentication issue → check git credentials
1310
+ ```
1311
+
1312
+ **Recovery:** Provide retry command, list common causes.
1313
+
1314
+ ### Error Pattern 4: Synopsis Command Fails
1315
+
1316
+ **Detection:**
1317
+ ```bash
1318
+ if ! jfl synopsis $HOURS 2>/dev/null; then
1319
+ # Synopsis failed
1320
+ fi
1321
+ ```
1322
+
1323
+ **Handling:**
1324
+ ```bash
1325
+ # Don't fail the entire skill if synopsis fails
1326
+ jfl synopsis $HOURS 2>/dev/null || echo "Synopsis not available"
1327
+ ```
1328
+
1329
+ **Message:**
1330
+ ```
1331
+ Synopsis not available (jfl command failed)
1332
+
1333
+ But your session ended successfully:
1334
+ ✓ Merged to main
1335
+ ✓ Pushed to origin
1336
+ ```
1337
+
1338
+ **Recovery:** Session end still successful, synopsis is nice-to-have.
1339
+
1340
+ ### Error Pattern 5: Journal Write Fails
1341
+
1342
+ **Detection:**
1343
+ ```bash
1344
+ if ! cat >> "$JOURNAL_FILE" << EOF ...; then
1345
+ # Journal write failed
1346
+ fi
1347
+ ```
1348
+
1349
+ **Handling:**
1350
+ ```bash
1351
+ # Try to write journal, but don't block session end if it fails
1352
+ if ! cat >> "$JOURNAL_FILE" << EOF
1353
+ ...
1354
+ EOF
1355
+ then
1356
+ echo "⚠ Failed to write journal entry (file permissions?)"
1357
+ echo "Continuing with session end anyway..."
1358
+ fi
1359
+ ```
1360
+
1361
+ **Message:**
1362
+ ```
1363
+ ⚠ Failed to write journal entry
1364
+
1365
+ Possible causes:
1366
+ • .jfl/journal/ directory doesn't exist
1367
+ • Permission issue
1368
+
1369
+ Session ending anyway (journal is recommended but not required).
1370
+ ```
1371
+
1372
+ **Recovery:** Session ends, user can manually add journal later.
1373
+
1374
+ ---
1375
+
1376
+ ## Synopsis Integration
1377
+
1378
+ **Always run synopsis after successful session end.** This provides handoff context for future sessions.
1379
+
1380
+ ### Calculating Duration
1381
+
1382
+ ```bash
1383
+ # Get first commit timestamp in session
1384
+ SESSION_START=$(git log --format=%ct --reverse $WORKING_BRANCH..HEAD 2>/dev/null | head -1)
1385
+
1386
+ if [[ -n "$SESSION_START" ]]; then
1387
+ NOW=$(date +%s)
1388
+ DURATION_SECONDS=$((NOW - SESSION_START))
1389
+ DURATION_HOURS=$((DURATION_SECONDS / 3600))
1390
+ DURATION_MINUTES=$(((DURATION_SECONDS % 3600) / 60))
1391
+ else
1392
+ # No commits yet (shouldn't happen at session end, but handle gracefully)
1393
+ DURATION_HOURS=0
1394
+ DURATION_MINUTES=0
1395
+ fi
1396
+
1397
+ # Round up for synopsis (show at least last hour)
1398
+ SYNOPSIS_HOURS=$((DURATION_HOURS + 1))
1399
+ if [[ $SYNOPSIS_HOURS -lt 1 ]]; then
1400
+ SYNOPSIS_HOURS=1
1401
+ fi
1402
+ ```
1403
+
1404
+ ### Calling Synopsis
1405
+
1406
+ ```bash
1407
+ echo ""
1408
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1409
+ echo " Work Summary (${DURATION_HOURS}h ${DURATION_MINUTES}m session)"
1410
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1411
+ echo ""
1412
+
1413
+ # Call jfl synopsis command
1414
+ if command -v jfl >/dev/null 2>&1; then
1415
+ jfl synopsis $SYNOPSIS_HOURS 2>/dev/null || {
1416
+ echo "Synopsis command failed"
1417
+ echo ""
1418
+ echo "Manual summary:"
1419
+ echo " Commits: $COMMIT_COUNT"
1420
+ echo " Files: $FILES_CHANGED"
1421
+ echo " Lines: +$LINES_ADDED / -$LINES_REMOVED"
1422
+ echo ""
1423
+ git log --oneline $WORKING_BRANCH..HEAD~10 2>/dev/null | head -10
1424
+ }
1425
+ else
1426
+ echo "Synopsis not available (jfl not in PATH)"
1427
+ echo ""
1428
+ echo "Manual summary:"
1429
+ echo " Commits: $COMMIT_COUNT"
1430
+ echo " Files: $FILES_CHANGED"
1431
+ echo " Lines: +$LINES_ADDED / -$LINES_REMOVED"
1432
+ echo ""
1433
+ echo "Recent commits:"
1434
+ git log --oneline $WORKING_BRANCH..HEAD~10 2>/dev/null | head -10
1435
+ fi
1436
+ ```
1437
+
1438
+ ### Fallback if Synopsis Unavailable
1439
+
1440
+ If `jfl synopsis` fails, show manual summary:
1441
+
1442
+ ```
1443
+ Manual summary:
1444
+ Commits: 8
1445
+ Files: 12
1446
+ Lines: +234 / -67
1447
+
1448
+ Recent commits:
1449
+ a1b2c3d feat: add session manager
1450
+ e4f5g6h fix: auth token refresh
1451
+ i7j8k9l docs: update README
1452
+ m1n2o3p style: format code
1453
+ ...
1454
+ ```
1455
+
1456
+ ---
1457
+
1458
+ ## Integration Points
1459
+
1460
+ ### 1. session-cleanup.sh
1461
+
1462
+ **Location:** `scripts/session/session-cleanup.sh`
1463
+
1464
+ **What it does:**
1465
+ - Stops background processes (auto-commit, context-hub)
1466
+ - Auto-commits uncommitted changes
1467
+ - Detects worktree vs direct mode
1468
+ - Pre-merge cleanup (removes session metadata)
1469
+ - Merges session branch to working branch with `-X ours`
1470
+ - Auto-resolves common conflicts (.jfl files, submodules)
1471
+ - Pushes to remote
1472
+ - Removes worktrees and deletes session branches
1473
+ - Notifies jfl-services API
1474
+
1475
+ **How skill uses it:**
1476
+ - Calls it after pre-flight checks and user prompts
1477
+ - Captures output to show key steps
1478
+ - Parses exit code and output for error handling
1479
+ - Logs full output to `.jfl/logs/session-cleanup.log`
1480
+
1481
+ ### 2. Journal System
1482
+
1483
+ **Location:** `.jfl/journal/*.jsonl`
1484
+
1485
+ **Format:** One JSON object per line (JSONL)
1486
+
1487
+ **Example entry:**
1488
+ ```json
1489
+ {
1490
+ "v": 1,
1491
+ "ts": "2026-02-10T14:30:00.000Z",
1492
+ "session": "session-goose-20260210-1430-a1b2c3",
1493
+ "type": "session-end",
1494
+ "status": "complete",
1495
+ "title": "Session management enhancements",
1496
+ "summary": "Enhanced /end skill with comprehensive UX",
1497
+ "files": ["SKILL.md", "session-cleanup.sh"]
1498
+ }
1499
+ ```
1500
+
1501
+ **How skill uses it:**
1502
+ - Checks if file exists: `[[ -s ".jfl/journal/${BRANCH}.jsonl" ]]`
1503
+ - Counts entries: `wc -l < "$JOURNAL_FILE"`
1504
+ - Warns if missing and offers to create
1505
+ - Creates minimal entry if user agrees
1506
+
1507
+ ### 3. Synopsis Command
1508
+
1509
+ **Location:** `src/commands/synopsis.ts` (TypeScript)
1510
+
1511
+ **Usage:** `jfl synopsis [hours] [author]`
1512
+
1513
+ **What it returns:**
1514
+ - Journal entries from all sessions/worktrees
1515
+ - Git commits from all branches
1516
+ - File headers (@purpose, @spec tags)
1517
+ - Time audit with category breakdown
1518
+ - Health checks and next steps
1519
+
1520
+ **How skill uses it:**
1521
+ - Called after successful cleanup
1522
+ - Duration calculated from session start time
1523
+ - Rounds up to next hour (e.g., 2h 15m → 3 hours)
1524
+ - Fallback to manual summary if command fails
1525
+
1526
+ ### 4. Session State Files
1527
+
1528
+ **Location:** `.jfl/`
1529
+
1530
+ | File | Purpose |
1531
+ |------|---------|
1532
+ | `current-session-branch.txt` | Current session branch name |
1533
+ | `current-worktree.txt` | Worktree path or "direct" |
1534
+ | `config.json` | Project config (working_branch, etc.) |
1535
+ | `logs/session-cleanup.log` | Last cleanup output |
1536
+
1537
+ **How skill uses it:**
1538
+ - Reads to detect mode and branch info
1539
+ - Used by pre-flight check
1540
+ - cleanup.sh removes metadata files before merge
1541
+
1542
+ ### 5. Working Branch Config
1543
+
1544
+ **Location:** `.jfl/config.json`
1545
+
1546
+ **Format:**
1547
+ ```json
1548
+ {
1549
+ "name": "project-name",
1550
+ "working_branch": "develop"
1551
+ }
1552
+ ```
1553
+
1554
+ **How skill uses it:**
1555
+ - Reads working_branch (defaults to "main")
1556
+ - Shows in summary: "Merging to: develop"
1557
+ - Passed to session-cleanup.sh
1558
+
1559
+ ---
1560
+
1561
+ ## Dependencies & Testing
1562
+
1563
+ ### Required Commands
1564
+
1565
+ The skill requires these commands to be available:
1566
+
1567
+ | Command | Purpose | Fallback if Missing |
1568
+ |---------|---------|-------------------|
1569
+ | `git` | Version control | **REQUIRED** - skill cannot run |
1570
+ | `jq` | JSON parsing | Use sed/awk for simple parsing |
1571
+ | `jfl` | Synopsis command | Show manual summary |
1572
+ | `bash` | Shell execution | **REQUIRED** - skill runs in bash |
1573
+
1574
+ **Checking dependencies:**
1575
+
1576
+ ```bash
1577
+ # Check git
1578
+ if ! command -v git >/dev/null 2>&1; then
1579
+ echo "Error: git is required"
1580
+ exit 1
1581
+ fi
1582
+
1583
+ # Check jq (optional but recommended)
1584
+ if ! command -v jq >/dev/null 2>&1; then
1585
+ echo "Warning: jq not found, using basic parsing"
1586
+ USE_JQ=false
1587
+ else
1588
+ USE_JQ=true
1589
+ fi
1590
+
1591
+ # Check jfl (optional)
1592
+ if ! command -v jfl >/dev/null 2>&1; then
1593
+ echo "Warning: jfl not found, synopsis unavailable"
1594
+ USE_SYNOPSIS=false
1595
+ else
1596
+ USE_SYNOPSIS=true
1597
+ fi
1598
+ ```
1599
+
1600
+ ### Required Files
1601
+
1602
+ | File | Purpose | What if Missing |
1603
+ |------|---------|----------------|
1604
+ | `scripts/session/session-cleanup.sh` | Core cleanup | **CRITICAL** - skill fails |
1605
+ | `.jfl/config.json` | Working branch | Use "main" as default |
1606
+ | `.jfl/current-session-branch.txt` | Session info | Read from git |
1607
+
1608
+ ### Testing Checklist
1609
+
1610
+ Test these scenarios to verify the skill works correctly:
1611
+
1612
+ #### 1. Happy Path (Clean State)
1613
+ - [ ] No uncommitted changes
1614
+ - [ ] Journal entry exists
1615
+ - [ ] Merges cleanly to working branch
1616
+ - [ ] Pushes to origin successfully
1617
+ - [ ] Shows synopsis
1618
+ - [ ] Success message displayed
1619
+
1620
+ #### 2. Uncommitted Changes → Auto-commit
1621
+ - [ ] Make changes without committing
1622
+ - [ ] Choose "auto-commit" option
1623
+ - [ ] Changes committed automatically
1624
+ - [ ] Merge succeeds
1625
+ - [ ] Synopsis shows all work
1626
+
1627
+ #### 3. Uncommitted Changes → Show Diff
1628
+ - [ ] Make changes without committing
1629
+ - [ ] Choose "show diff" option
1630
+ - [ ] Diff displayed correctly
1631
+ - [ ] Prompted for next action
1632
+ - [ ] Can commit or cancel
1633
+
1634
+ #### 4. Uncommitted Changes → Discard
1635
+ - [ ] Make changes without committing
1636
+ - [ ] Choose "discard" option
1637
+ - [ ] Warning shown
1638
+ - [ ] Confirmation required
1639
+ - [ ] Changes discarded (verified)
1640
+ - [ ] Merge proceeds
1641
+
1642
+ #### 5. Uncommitted Changes → Cancel
1643
+ - [ ] Make changes without committing
1644
+ - [ ] Choose "cancel" option
1645
+ - [ ] Session stays active
1646
+ - [ ] No cleanup performed
1647
+ - [ ] Can continue working
1648
+
1649
+ #### 6. No Journal → Write Entry
1650
+ - [ ] End session without journal
1651
+ - [ ] Prompted to write entry
1652
+ - [ ] Choose "write entry"
1653
+ - [ ] Provide title
1654
+ - [ ] Entry created (verified in .jfl/journal/)
1655
+ - [ ] Merge proceeds
1656
+
1657
+ #### 7. No Journal → Skip
1658
+ - [ ] End session without journal
1659
+ - [ ] Prompted to write entry
1660
+ - [ ] Choose "skip"
1661
+ - [ ] Warning shown
1662
+ - [ ] Merge proceeds anyway
1663
+
1664
+ #### 8. Worktree Mode
1665
+ - [ ] Start second concurrent session (creates worktree)
1666
+ - [ ] Make commits in worktree
1667
+ - [ ] End worktree session
1668
+ - [ ] Worktree removed (verified)
1669
+ - [ ] Branch deleted
1670
+ - [ ] Main repo untouched
1671
+
1672
+ #### 9. Direct Mode
1673
+ - [ ] Start single session (no worktree)
1674
+ - [ ] Make commits
1675
+ - [ ] End session
1676
+ - [ ] Merges on same branch
1677
+ - [ ] No worktree cleanup needed
1678
+
1679
+ #### 10. Merge Conflicts
1680
+ - [ ] Create intentional conflict (modify same line in session and working branch)
1681
+ - [ ] End session
1682
+ - [ ] Conflicts detected
1683
+ - [ ] Branch preserved (not deleted)
1684
+ - [ ] Clear resolution guidance shown
1685
+ - [ ] Can resolve manually
1686
+
1687
+ #### 11. Synopsis Display
1688
+ - [ ] Work for 10+ minutes
1689
+ - [ ] Make several commits
1690
+ - [ ] Write journal entries
1691
+ - [ ] End session
1692
+ - [ ] Synopsis shows commits, files, journal entries
1693
+ - [ ] Time breakdown displayed
1694
+
1695
+ #### 12. Not in Session
1696
+ - [ ] Checkout main branch manually
1697
+ - [ ] Try to invoke /end
1698
+ - [ ] Graceful message shown
1699
+ - [ ] Explains not in session
1700
+ - [ ] No errors thrown
1701
+
1702
+ ### Success Criteria
1703
+
1704
+ The skill is working correctly if:
1705
+
1706
+ - ✅ All 12 test scenarios pass
1707
+ - ✅ User always knows what's happening at each step
1708
+ - ✅ No work is lost in any scenario
1709
+ - ✅ Error messages provide clear recovery steps
1710
+ - ✅ Synopsis integrates cleanly after successful cleanup
1711
+ - ✅ Session ends in < 30 seconds (without conflicts)
1712
+ - ✅ Journal compliance encouraged without being blocking
1713
+
1714
+ ---
1715
+
1716
+ ## Notes for Claude
1717
+
1718
+ ### When to Invoke This Skill
1719
+
1720
+ **Immediate invocation (HIGH CONFIDENCE):**
1721
+ - User says "done", "that's it", "I'm finished", "end session", "/end"
1722
+ - User says "wrap up", "all set", "good for now" in concluding context
1723
+
1724
+ **Check context first:**
1725
+ - User says "thanks", "looks good", "perfect" → Is this end of session or just task?
1726
+ - If unclear, ask: "Ready to end the session, or keep working?"
1727
+
1728
+ ### What You Should Do
1729
+
1730
+ When you invoke this skill:
1731
+
1732
+ 1. **Don't run commands yourself** - The skill script handles everything
1733
+ 2. **Present prompts to user** - Use AskUserQuestion for uncommitted/journal decisions
1734
+ 3. **Parse script output** - Show key steps, not full git output
1735
+ 4. **Guide on errors** - If conflicts, walk user through resolution
1736
+ 5. **Always show synopsis** - User chose "always show" preference
1737
+
1738
+ ### What You Should NOT Do
1739
+
1740
+ - ❌ Don't manually run `git commit`, `git merge`, etc. - Let the script do it
1741
+ - ❌ Don't skip uncommitted change check - User must decide what to do
1742
+ - ❌ Don't force journal entries - Encourage but allow skip
1743
+ - ❌ Don't hide errors - Parse and explain clearly
1744
+
1745
+ ### User Preferences (From Plan)
1746
+
1747
+ | Setting | User Choice | How to Handle |
1748
+ |---------|-------------|---------------|
1749
+ | Synopsis | Always show | Run synopsis after every session end |
1750
+ | Journal | Warn if missing | Prompt to write or skip, don't block |
1751
+ | Verbosity | Balanced | Show summary + key steps, not full output |
1752
+ | Auto-commit | Prompt | Ask what to do with uncommitted changes |
1753
+
1754
+ ### Common User Questions
1755
+
1756
+ **"Did my work get saved?"**
1757
+ → Yes! Show what merged: "✓ 8 commits, 12 files merged to main and pushed"
1758
+
1759
+ **"What if I made a mistake?"**
1760
+ → On main, you can: `git revert <commit>` or `git reset --hard HEAD~1` (before push)
1761
+
1762
+ **"Can I undo the session end?"**
1763
+ → If merge happened: No, but commits are on main, can revert. If conflicts: Yes, branch preserved.
1764
+
1765
+ **"Where did my session branch go?"**
1766
+ → Deleted after successful merge (work is on main now). If conflicts: Preserved for manual resolution.
1767
+
1768
+ ---
1769
+
1770
+ ## Summary
1771
+
1772
+ This skill provides comprehensive UX orchestration for session ending:
1773
+
1774
+ 1. ✅ **Pre-flight checks** - Gather complete state (mode, branches, uncommitted, journal, work stats)
1775
+ 2. ✅ **User prompts** - Handle uncommitted changes and missing journal gracefully
1776
+ 3. ✅ **Execute cleanup** - Call session-cleanup.sh with clear progress display
1777
+ 4. ✅ **Error handling** - Guide user through conflicts and failures
1778
+ 5. ✅ **Synopsis** - Always show work summary for handoff context
1779
+
1780
+ **Key principle:** Wrap solid infrastructure (session-cleanup.sh) with excellent UX.