cc-sessions 1.4.2 → 1.5.1

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 (5) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/bin/cc +15 -12
  4. data/bin/cc-bookmark +35 -41
  5. metadata +4 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cad93912f48c81f33e5532c4a6015164d192899ba9e0bcaaa6a434270e99f881
4
- data.tar.gz: b3600372722bcd711e490dacbb52e298cb45f1ffc7438c47bbae87dad0d17088
3
+ metadata.gz: 17f3d3abaf69a414fca43e117ab74e8c8ee7ae7160916eeac37f500ede7ba67f
4
+ data.tar.gz: 9090fe2bb7999335aef10e4d9058c741353e2066408f4db764429339e2ace16f
5
5
  SHA512:
6
- metadata.gz: 3f575d1ae31c07665d31b1e31ae25f5bf509bc3a5c23f5598d44d9720d7e870cbc0e15ecb55a2c2ad34ac399bff45bf5bfb97688d02ba2cb5df10e0b79feb1f7
7
- data.tar.gz: 8fb66ea0011e696e15161e7e3dbdf2a13b83dfcc4c3abba8ab8148d8d400d208c4bcfba4fce580aae05c4a0855ad3165290e98807fe1843fd802aa320f65f81e
6
+ metadata.gz: b245ba85c136be80547fcedc840f2e9a90dda0230c0da14f90221cef2471555e42d38b7783b9c872dd60579add274c8cfafe950c6d5de9ee2b3cf06b07b17e6d
7
+ data.tar.gz: 04717d6bde89c7419ceb3176c5998ab30b2a3efedba977cfc1834c15cf7c510e1f5c22d25e4ade820d6920d2089f84dc7c5f065995b82105d848563f3360a249
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.5.1] - 2026-03-04
4
+
5
+ ### Fixed
6
+ - Multiple sessions sharing a project dir no longer steal each other's bookmarks
7
+ - `detect_session_id` (newest `.jsonl`) no longer overrides `CC_SESSION_ID` when the original session file exists
8
+ - Query mode migration only triggers on true context continuation (original `.jsonl` gone), not when another session is simply newer
9
+ - Reserved words ("query", "?", "status", "show") now trigger query mode instead of being saved as tag names
10
+
11
+ ### Added
12
+ - `session_file_exists?` helper to distinguish "context continuation" from "another active session"
13
+
14
+ ## [1.5.0] - 2026-03-03
15
+
16
+ ### Fixed
17
+ - Sessions no longer inherit bookmarks from unrelated sessions in the same directory
18
+ - Removed path-based sibling matching in `find_resume_breadcrumb` — now only matches exact session IDs
19
+ - Removed `find_tags_for_path` from `cc` — running session tags now resolved by session ID, not directory path
20
+ - Running indicator (green dot) in `cc -l` now matches by session ID instead of path
21
+
22
+ ### Changed
23
+ - `find_resume_breadcrumb` takes a session ID only, not a directory — eliminates cross-contamination
24
+ - `cc -C` uses session-ID-based tag lookup instead of path matching
25
+
3
26
  ## [1.4.2] - 2026-03-03
4
27
 
5
28
  ### Fixed
data/bin/cc CHANGED
@@ -139,11 +139,12 @@ def find_session_by_tag(tag)
139
139
  nil
140
140
  end
141
141
 
142
- def find_tags_for_path(path, bookmarks)
143
- bookmarks['sessions'].each_value do |entry|
144
- return entry['tags'] if entry['path'] == path
145
- end
146
- []
142
+ def find_tags_for_session_dir(session_dir, bookmarks)
143
+ session_id = detect_session_id(session_dir)
144
+ return [] unless session_id
145
+
146
+ entry = bookmarks['sessions'][session_id]
147
+ entry ? entry['tags'] : []
147
148
  end
148
149
 
149
150
  def session_exists_in_dir?(dir)
@@ -187,18 +188,20 @@ def delete_bookmark_by_tag(tag)
187
188
  end
188
189
  end
189
190
 
190
- def running_session_dirs
191
+ def running_session_ids
191
192
  pids = `pgrep -x claude 2>/dev/null`.split.map(&:to_i)
192
- dirs = Set.new
193
+ ids = Set.new
193
194
  pids.each do |pid|
194
195
  cwd_link = "/proc/#{pid}/cwd"
195
196
  next unless File.exist?(cwd_link)
196
197
  begin
197
- dirs << resolve_session_dir(File.readlink(cwd_link))
198
+ session_dir = resolve_session_dir(File.readlink(cwd_link))
199
+ sid = detect_session_id(session_dir)
200
+ ids << sid if sid
198
201
  rescue Errno::EACCES
199
202
  end
200
203
  end
201
- dirs
204
+ ids
202
205
  end
203
206
 
204
207
  def list_bookmarks
@@ -211,11 +214,11 @@ def list_bookmarks
211
214
  return
212
215
  end
213
216
 
214
- running = running_session_dirs
217
+ running = running_session_ids
215
218
 
216
219
  items = bookmarks['sessions'].map do |id, entry|
217
220
  { id: id, path: entry['path'], tags: entry['tags'], exists: Dir.exist?(entry['path']),
218
- running: running.include?(entry['path']) }
221
+ running: running.include?(id) }
219
222
  end
220
223
 
221
224
  index = 0
@@ -337,7 +340,7 @@ def show_current_sessions
337
340
  begin
338
341
  cwd = File.readlink(cwd_link)
339
342
  session_dir = resolve_session_dir(cwd)
340
- tags = find_tags_for_path(session_dir, bookmarks)
343
+ tags = find_tags_for_session_dir(session_dir, bookmarks)
341
344
  tag_str = tags.empty? ? dim('(no tags)') : green(tags.join(', '))
342
345
 
343
346
  puts " PID #{cyan(pid.to_s)}: #{session_dir}"
data/bin/cc-bookmark CHANGED
@@ -39,33 +39,23 @@ def detect_session_id(session_dir, exclude_id: nil)
39
39
  File.basename(newest, '.jsonl')
40
40
  end
41
41
 
42
- # Find all session IDs in the same project directory
43
- def sibling_session_ids(session_dir)
42
+ # Check if a session's .jsonl file still exists in the project dir
43
+ def session_file_exists?(session_id, session_dir)
44
+ return false unless session_id
44
45
  encoded = session_dir.gsub('/', '-')
45
- project_dir = File.join(CLAUDE_PROJECTS, encoded)
46
- return [] unless Dir.exist?(project_dir)
47
-
48
- Dir.glob(File.join(project_dir, '*.jsonl'))
49
- .select { |f| File.file?(f) }
50
- .map { |f| File.basename(f, '.jsonl') }
46
+ File.exist?(File.join(CLAUDE_PROJECTS, encoded, "#{session_id}.jsonl"))
51
47
  end
52
48
 
53
- # Check resume breadcrumbs left by `cc` to find tags for a continued session.
54
- # When `cc <tag>` resumes a session that later gets a new ID (context reset),
55
- # the breadcrumb links the old session ID to its tags.
56
- def find_resume_breadcrumb(session_dir, current_id, bookmarks)
57
- return nil unless Dir.exist?(RESUME_DIR)
49
+ # Find resume breadcrumb for a specific session ID only.
50
+ # Only matches exact session IDs never scans siblings by path.
51
+ def find_resume_breadcrumb(session_id)
52
+ return nil unless session_id && Dir.exist?(RESUME_DIR)
58
53
 
59
- siblings = sibling_session_ids(session_dir)
60
- siblings.each do |sid|
61
- next if sid == current_id
62
- breadcrumb = File.join(RESUME_DIR, "#{sid}.json")
63
- next unless File.exist?(breadcrumb)
54
+ breadcrumb = File.join(RESUME_DIR, "#{session_id}.json")
55
+ return nil unless File.exist?(breadcrumb)
64
56
 
65
- tags = JSON.parse(File.read(breadcrumb))
66
- return [sid, tags] if tags.is_a?(Array)
67
- end
68
- nil
57
+ tags = JSON.parse(File.read(breadcrumb))
58
+ tags.is_a?(Array) ? tags : nil
69
59
  rescue
70
60
  nil
71
61
  end
@@ -116,15 +106,23 @@ cwd ||= resolve_session_dir(Dir.pwd)
116
106
  session_id = detect_session_id(cwd) # Fallback — picks newest .jsonl
117
107
  tags = ARGV
118
108
 
109
+ # Treat reserved words as query mode, not tag names
110
+ QUERY_WORDS = %w[? query status show].freeze
111
+ if tags.length == 1 && QUERY_WORDS.include?(tags.first.downcase)
112
+ tags = [] # Convert to query mode
113
+ end
114
+
119
115
  if tags.empty?
120
116
  # Query mode: show tags for current session
121
117
  # Priority: check the known original session ID first (from env var)
122
118
  if original_id
123
119
  entry = bookmarks['sessions'][original_id]
124
120
  if entry
125
- # Check if session was continued (newer .jsonl exists)
126
- if session_id && session_id != original_id
127
- # Migrate bookmark to the new session ID
121
+ # Check if session was continued: original .jsonl is GONE and a newer one exists.
122
+ # If original .jsonl still exists, another session is just more recent — NOT a continuation.
123
+ original_gone = !session_file_exists?(original_id, cwd)
124
+ if original_gone && session_id && session_id != original_id
125
+ # True context continuation — migrate bookmark to the new session ID
128
126
  bookmarks['sessions'].delete(original_id)
129
127
  bookmarks['sessions'][session_id] = entry
130
128
  save_bookmarks(bookmarks)
@@ -169,29 +167,25 @@ if tags.empty?
169
167
  if entry
170
168
  puts "Current bookmark: #{entry['tags'].join(', ')}"
171
169
  else
172
- # Fallback: check resume breadcrumbs (legacy behavior, no env var)
173
- match = find_resume_breadcrumb(cwd, session_id, bookmarks)
174
- if match
175
- old_id, old_tags = match
176
- bookmarks['sessions'].delete(old_id)
177
- bookmarks['sessions'][session_id] = { 'path' => cwd, 'tags' => old_tags }
178
- save_bookmarks(bookmarks)
179
- FileUtils.rm_f(File.join(RESUME_DIR, "#{old_id}.json"))
180
- puts "Current bookmark: #{old_tags.join(', ')}"
181
- else
182
- puts "No bookmark for this session. Usage: /bm tag1 tag2 ..."
183
- end
170
+ puts "No bookmark for this session. Usage: /bm tag1 tag2 ..."
184
171
  end
185
172
  else
186
173
  puts "Could not detect session ID. Usage: /bm tag1 tag2 ..."
187
174
  end
188
175
  else
189
176
  # Bookmark mode: set tags for current session
190
- # Prefer detected (newest) session ID over env var the env var may be stale
191
- # after a context continuation where Claude created a new session ID
192
- key = session_id || original_id || "path:#{cwd}"
177
+ # Use CC_SESSION_ID when its .jsonl still exists (it's the real session).
178
+ # Only fall back to detect_session_id when the original is gone (context continuation).
179
+ if original_id && session_file_exists?(original_id, cwd)
180
+ key = original_id
181
+ elsif original_id && session_id && session_id != original_id
182
+ # Context continuation — original .jsonl gone, use the new one
183
+ key = session_id
184
+ else
185
+ key = session_id || original_id || "path:#{cwd}"
186
+ end
193
187
 
194
- # Clean up old bookmark if session ID changed (migration)
188
+ # Clean up old bookmark if session ID changed (true migration)
195
189
  if original_id && original_id != key && bookmarks['sessions'][original_id]
196
190
  bookmarks['sessions'].delete(original_id)
197
191
  FileUtils.rm_f(File.join(RESUME_DIR, "#{original_id}.json"))
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cc-sessions
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-03 00:00:00.000000000 Z
11
+ date: 2026-03-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 'A simple tool for bookmarking and resuming Claude Code sessions. Tag
14
14
  sessions with meaningful names using /bm inside Claude Code, then quickly resume
15
- them with ''cc <tag>'' from anywhere. v1.4.2: Fix cwd drift in session detection
16
- on context continuations via hook.'
15
+ them with ''cc <tag>'' from anywhere. v1.5.1: Fix multi-session bookmark isolation
16
+ sessions sharing a project dir no longer steal each other''s bookmarks.'
17
17
  email:
18
18
  - g@isene.com
19
19
  executables: