ccmv 1.0.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.
@@ -0,0 +1,438 @@
1
+ # Claude Code & Cursor Internal Architecture
2
+
3
+ This document provides detailed documentation of the internal data structures used by Claude Code and Cursor.
4
+
5
+ ## Claude Code Data Structure
6
+
7
+ ### Directory Layout
8
+
9
+ ```
10
+ ~/.claude/
11
+ ├── projects/ # Per-project session data
12
+ │ └── {encoded-path}/ # Encoded path (see below)
13
+ │ ├── {session-id}.jsonl # Main session file
14
+ │ ├── agent-{agent-id}.jsonl # Sub-agent sessions
15
+ │ └── tool-results/ # Tool execution result cache
16
+ │ └── *.txt
17
+ ├── history.jsonl # Global history (all projects)
18
+ ├── file-history/ # File change history
19
+ ├── backups/ # Backups from ccmv etc.
20
+ ├── commands/ # Custom commands
21
+ ├── agents/ # Custom agents
22
+ ├── cache/ # Various caches
23
+ ├── debug/ # Debug logs
24
+ └── downloads/ # Downloaded files
25
+ ```
26
+
27
+ ### Path Encoding
28
+
29
+ Claude Code encodes project paths using the following rules:
30
+
31
+ | Original | Encoded |
32
+ |----------|---------|
33
+ | `/` (slash) | `-` |
34
+ | `:` (colon) | `-` |
35
+ | ` ` (space) | `-` |
36
+
37
+ **Example:**
38
+ ```
39
+ /Users/jane/Documents/repos/myproject
40
+ → -Users-jane-Documents-repos-myproject
41
+ ```
42
+
43
+ ### Session File Format (*.jsonl)
44
+
45
+ Each line is a single JSON object. Different `type` values per line.
46
+
47
+ #### Record Types
48
+
49
+ | type | Description |
50
+ |------|-------------|
51
+ | `queue-operation` | Queue operations (enqueue/dequeue) |
52
+ | `user` | User messages |
53
+ | `assistant` | Claude's responses |
54
+ | `tool_use` | Tool invocations |
55
+ | `tool_result` | Tool execution results |
56
+
57
+ #### Common Fields
58
+
59
+ Fields present in all records:
60
+
61
+ ```json
62
+ {
63
+ "type": "user|assistant|tool_use|...",
64
+ "uuid": "fd49a8a2-68f3-4cdf-81f8-38508beda957",
65
+ "parentUuid": "UUID of previous message (null for first)",
66
+ "sessionId": "fe4c5fad-9703-4485-9e4f-b73a1f638c02",
67
+ "timestamp": "2026-01-07T12:33:48.236Z",
68
+ "cwd": "/Users/jane/project",
69
+ "gitBranch": "main",
70
+ "version": "2.0.72",
71
+ "isSidechain": false,
72
+ "userType": "external"
73
+ }
74
+ ```
75
+
76
+ #### User Message
77
+
78
+ ```json
79
+ {
80
+ "type": "user",
81
+ "message": {
82
+ "role": "user",
83
+ "content": "User input text"
84
+ },
85
+ ...common fields
86
+ }
87
+ ```
88
+
89
+ #### Assistant Message
90
+
91
+ ```json
92
+ {
93
+ "type": "assistant",
94
+ "message": {
95
+ "model": "claude-sonnet-4-5-20250929",
96
+ "id": "msg_014FrefSYxpSnNHJcpNJZugw",
97
+ "type": "message",
98
+ "role": "assistant",
99
+ "content": [
100
+ { "type": "text", "text": "Response text" },
101
+ { "type": "tool_use", "id": "toolu_xxx", "name": "Read", "input": {...} }
102
+ ],
103
+ "stop_reason": "end_turn|tool_use|null",
104
+ "usage": {
105
+ "input_tokens": 3,
106
+ "cache_creation_input_tokens": 22320,
107
+ "cache_read_input_tokens": 0,
108
+ "output_tokens": 1
109
+ }
110
+ },
111
+ "requestId": "req_011CWt2PcwHgdwPS37vwqRzv",
112
+ ...common fields
113
+ }
114
+ ```
115
+
116
+ ### Agent Session Files (agent-*.jsonl)
117
+
118
+ Sub-agent sessions (launched via Task tool) are stored in separate files.
119
+
120
+ **Characteristics:**
121
+ - `isSidechain: true` - Indicates branching from main session
122
+ - `agentId: "ae1f9f8"` - Short agent ID (used in filename)
123
+ - Same `sessionId` - Belongs to the same session as parent
124
+
125
+ ```json
126
+ {
127
+ "type": "user",
128
+ "isSidechain": true,
129
+ "agentId": "ae1f9f8",
130
+ "message": { "role": "user", "content": "Warmup" },
131
+ "sessionId": "fe4c5fad-9703-4485-9e4f-b73a1f638c02",
132
+ ...
133
+ }
134
+ ```
135
+
136
+ ### Global History (history.jsonl)
137
+
138
+ User input history across all projects. Each line records a user input:
139
+
140
+ ```json
141
+ {
142
+ "display": "want to check PR, what PR do we have?",
143
+ "pastedContents": {},
144
+ "timestamp": 1759203123612,
145
+ "project": "/Users/jane/Documents/myproject"
146
+ }
147
+ ```
148
+
149
+ **Fields:**
150
+ - `display`: Display text (includes commands like "/context ")
151
+ - `pastedContents`: Pasted content (ID → content map)
152
+ - `timestamp`: Unix timestamp (milliseconds)
153
+ - `project`: Absolute path to project
154
+
155
+ ---
156
+
157
+ ## Cursor Data Structure
158
+
159
+ ### Directory Layout
160
+
161
+ ```
162
+ ~/Library/Application Support/Cursor/
163
+ └── User/
164
+ ├── globalStorage/
165
+ │ ├── storage.json # Global settings and profile associations
166
+ │ └── state.vscdb # Global state (SQLite)
167
+ └── workspaceStorage/
168
+ └── {md5-hash}/ # Per-workspace directory
169
+ ├── workspace.json # Workspace configuration
170
+ └── state.vscdb # Workspace state (SQLite, includes chat history)
171
+ ```
172
+
173
+ ### Path Format
174
+
175
+ Cursor stores paths in file:// URI format:
176
+
177
+ ```
178
+ /Users/jane/my project
179
+ → file:///Users/jane/my%20project
180
+ ```
181
+
182
+ **URL Encoding Rules:**
183
+ - Space → `%20`
184
+ - Multibyte characters (Japanese, etc.) are also URL-encoded
185
+
186
+ ### Workspace Hash Calculation
187
+
188
+ Workspace directory names are calculated as **MD5(path + birthtime_ms)**:
189
+
190
+ ```javascript
191
+ const fs = require('fs');
192
+ const crypto = require('crypto');
193
+
194
+ const path = '/Users/jane/myproject';
195
+ const stat = fs.statSync(path);
196
+ const hash = crypto.createHash('md5')
197
+ .update(path)
198
+ .update(String(stat.birthtime.getTime())) // birthtime in milliseconds
199
+ .digest('hex');
200
+ // → "a1b2c3d4e5f6..."
201
+ ```
202
+
203
+ **Important Notes:**
204
+
205
+ 1. **Node.js Required**: Python's `os.stat().st_birthtime` rounds milliseconds differently (e.g., 583 vs 584), causing hash mismatch
206
+ 2. **birthtime Preservation**: `mv` within the same volume preserves birthtime, so the new path's hash is predictable
207
+ 3. **Cross-volume Moves**: Moving to a different volume may change birthtime (untested)
208
+
209
+ ### storage.json
210
+
211
+ Profile-related settings. Multiple path formats coexist:
212
+
213
+ ```json
214
+ {
215
+ "profileAssociations": {
216
+ "workspaces": {
217
+ "file:///Users/jane/project-a": "profile-id-1",
218
+ "/Users/jane/project-b": "profile-id-2",
219
+ "~/Documents/project-c": "profile-id-3"
220
+ }
221
+ },
222
+ ...
223
+ }
224
+ ```
225
+
226
+ **Path Formats:**
227
+ - `file://` URI
228
+ - Absolute path
229
+ - Tilde-expanded path (`~/...`)
230
+
231
+ ### state.vscdb (SQLite)
232
+
233
+ SQLite database. Main tables:
234
+
235
+ #### ItemTable
236
+
237
+ Key-Value store. `value` column contains JSON strings:
238
+
239
+ ```sql
240
+ CREATE TABLE ItemTable (
241
+ key TEXT PRIMARY KEY,
242
+ value TEXT
243
+ );
244
+ ```
245
+
246
+ **Key Keys:**
247
+ - `history.recentlyOpenedPathsList` - Recently opened projects list
248
+ - `repositoryTracker.paths` - Git repository tracking info
249
+
250
+ #### Global state.vscdb vs Workspace state.vscdb
251
+
252
+ | Location | Contents |
253
+ |----------|----------|
254
+ | `globalStorage/state.vscdb` | App-wide state (recently opened projects, etc.) |
255
+ | `workspaceStorage/{hash}/state.vscdb` | Per-workspace state (includes chat history) |
256
+
257
+ ### workspace.json
258
+
259
+ Configuration file for each workspace:
260
+
261
+ ```json
262
+ {
263
+ "folder": "file:///Users/jane/project"
264
+ }
265
+ ```
266
+
267
+ Or (when using workspace file):
268
+
269
+ ```json
270
+ {
271
+ "workspace": "file:///Users/jane/project.code-workspace"
272
+ }
273
+ ```
274
+
275
+ ### Chat History Storage (cursorDiskKV)
276
+
277
+ Cursor's chat history is stored in `workspaceStorage/{hash}/state.vscdb` in the `cursorDiskKV` table:
278
+
279
+ ```sql
280
+ CREATE TABLE cursorDiskKV (
281
+ key TEXT PRIMARY KEY,
282
+ value TEXT
283
+ );
284
+ ```
285
+
286
+ **Key Keys:**
287
+ - `composer.composerData` - Composer (chat) data
288
+
289
+ ---
290
+
291
+ ## Migration Considerations
292
+
293
+ ### ccmv Processing Flow
294
+
295
+ ```
296
+ 1. validate - Validate paths
297
+ 2. detect_cursor - Detect Cursor installation
298
+ 3. check_cursor_not_running - Ensure Cursor is closed
299
+ 4. find_cursor_workspaces - Identify workspace by hash calculation
300
+ 5. create_backup - Create backups
301
+ 6. backup_cursor_data - Backup Cursor data
302
+ 7. move_project - Move project directory
303
+ 8. rename_cursor_workspace - Rename workspace directory
304
+ 9. rename_claude_dir - Rename Claude project directory
305
+ 10. update_project_files - Update cwd in session files
306
+ 11. update_history - Update history.jsonl
307
+ 12. update_cursor_* - Update various Cursor files
308
+ 13. verify - Verify migration
309
+ ```
310
+
311
+ ### Key Update Points
312
+
313
+ #### Claude Code
314
+
315
+ | File/Directory | Update |
316
+ |----------------|--------|
317
+ | `~/.claude/projects/{encoded-old}/` | Rename directory |
318
+ | `cwd` field in `*.jsonl` | String replacement |
319
+ | `project` field in `history.jsonl` | String replacement |
320
+
321
+ #### Cursor
322
+
323
+ | File | Update |
324
+ |------|--------|
325
+ | `workspaceStorage/{old-hash}/` | Rename directory to new hash |
326
+ | `workspace.json` | Update `folder`/`workspace` URI |
327
+ | `storage.json` | Update keys in `profileAssociations.workspaces` |
328
+ | `state.vscdb` (global) | Update paths in ItemTable values |
329
+ | `state.vscdb` (workspace) | Update paths in ItemTable + cursorDiskKV |
330
+
331
+ ### Duplicate Workspace Handling
332
+
333
+ Cursor can create multiple workspace directories for the same path (from failed migrations, Cursor quirks, etc.).
334
+
335
+ **Merge Strategy:**
336
+ 1. Detect all workspaces pointing to the new path
337
+ 2. Compare `state.vscdb` sizes
338
+ 3. Keep the larger one (more data)
339
+ 4. Remove duplicates
340
+
341
+ ---
342
+
343
+ ## Appendix
344
+
345
+ ### JSON Examples
346
+
347
+ #### Complete User Message Record
348
+
349
+ ```json
350
+ {
351
+ "parentUuid": null,
352
+ "isSidechain": false,
353
+ "userType": "external",
354
+ "cwd": "/Users/jane/projects/myapp",
355
+ "sessionId": "fe4c5fad-9703-4485-9e4f-b73a1f638c02",
356
+ "version": "2.0.72",
357
+ "gitBranch": "feature-branch",
358
+ "type": "user",
359
+ "message": {
360
+ "role": "user",
361
+ "content": "want to check PR, what PR do we have?"
362
+ },
363
+ "uuid": "fd49a8a2-68f3-4cdf-81f8-38508beda957",
364
+ "timestamp": "2026-01-07T12:33:48.236Z"
365
+ }
366
+ ```
367
+
368
+ #### Complete Assistant Message Record
369
+
370
+ ```json
371
+ {
372
+ "parentUuid": "fd49a8a2-68f3-4cdf-81f8-38508beda957",
373
+ "isSidechain": false,
374
+ "userType": "external",
375
+ "cwd": "/Users/jane/projects/myapp",
376
+ "sessionId": "fe4c5fad-9703-4485-9e4f-b73a1f638c02",
377
+ "version": "2.0.72",
378
+ "gitBranch": "feature-branch",
379
+ "message": {
380
+ "model": "claude-sonnet-4-5-20250929",
381
+ "id": "msg_014FrefSYxpSnNHJcpNJZugw",
382
+ "type": "message",
383
+ "role": "assistant",
384
+ "content": [
385
+ {
386
+ "type": "text",
387
+ "text": "I'll check the PR list for you."
388
+ }
389
+ ],
390
+ "stop_reason": null,
391
+ "stop_sequence": null,
392
+ "usage": {
393
+ "input_tokens": 3,
394
+ "cache_creation_input_tokens": 22320,
395
+ "cache_read_input_tokens": 0,
396
+ "cache_creation": {
397
+ "ephemeral_5m_input_tokens": 22320,
398
+ "ephemeral_1h_input_tokens": 0
399
+ },
400
+ "output_tokens": 1,
401
+ "service_tier": "standard"
402
+ }
403
+ },
404
+ "requestId": "req_011CWt2PcwHgdwPS37vwqRzv",
405
+ "type": "assistant",
406
+ "uuid": "0b0ede00-8fff-4d9e-b79e-fb4395ea2d34",
407
+ "timestamp": "2026-01-07T12:33:54.356Z"
408
+ }
409
+ ```
410
+
411
+ ### Useful Commands
412
+
413
+ ```bash
414
+ # Claude Code: Check cwd values in session files
415
+ grep -h "\"cwd\"" ~/.claude/projects/*/fe*.jsonl | jq -r '.cwd' | sort -u
416
+
417
+ # Claude Code: Count sessions for a specific project
418
+ ls -la ~/.claude/projects/-Users-jane-Desktop-myproject/*.jsonl | wc -l
419
+
420
+ # Cursor: Calculate workspace hash
421
+ node -e "
422
+ const fs = require('fs');
423
+ const crypto = require('crypto');
424
+ const path = '/Users/jane/myproject';
425
+ const stat = fs.statSync(path);
426
+ console.log(crypto.createHash('md5')
427
+ .update(path)
428
+ .update(String(stat.birthtime.getTime()))
429
+ .digest('hex'));
430
+ "
431
+
432
+ # Cursor: Get recently opened projects from state.vscdb
433
+ sqlite3 ~/Library/Application\ Support/Cursor/User/globalStorage/state.vscdb \
434
+ "SELECT value FROM ItemTable WHERE key = 'history.recentlyOpenedPathsList';" | jq .
435
+
436
+ # Cursor: Check workspace folders
437
+ cat ~/Library/Application\ Support/Cursor/User/workspaceStorage/*/workspace.json | jq -r '.folder // .workspace'
438
+ ```