Skip to content

Commit aa16600

Browse files
committed
fix: review comments
1 parent c7ab45c commit aa16600

9 files changed

Lines changed: 54 additions & 51 deletions

File tree

api/export_api.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
from services.workspace_db import (
2323
_build_composer_id_to_workspace_id,
2424
_collect_workspace_entries,
25-
_load_bubble_map,
26-
_load_code_block_diff_map,
25+
load_bubble_map,
26+
load_code_block_diff_map,
2727
_open_global_db,
2828
)
2929
from services.workspace_resolver import (
@@ -116,8 +116,8 @@ def export_chats():
116116
if global_db is None:
117117
return jsonify({"error": "Cursor global storage not found"}), 404
118118

119-
bubble_map = _load_bubble_map(global_db)
120-
code_block_diff_map = _load_code_block_diff_map(global_db)
119+
bubble_map = load_bubble_map(global_db)
120+
code_block_diff_map = load_code_block_diff_map(global_db)
121121

122122
try:
123123
composer_rows = global_db.execute(

api/workspaces.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from utils.workspace_path import resolve_workspace_path, get_cli_chats_path
1717
from utils.cli_chat_reader import list_cli_projects
1818
from utils.path_helpers import get_workspace_folder_paths, get_workspace_display_name
19-
from utils.workspace_descriptor import _read_json_file
19+
from utils.workspace_descriptor import read_json_file
2020
from services.workspace_resolver import (
2121
_infer_workspace_name_from_context,
2222
# Re-exported for back-compat with existing tests that import from api.workspaces
@@ -107,7 +107,7 @@ def get_workspace(workspace_id):
107107
folder = None
108108
workspace_name = workspace_id
109109
try:
110-
wd = _read_json_file(wj_path)
110+
wd = read_json_file(wj_path)
111111
folder_paths = get_workspace_folder_paths(wd)
112112
folder = folder_paths[0] if folder_paths else wd.get("folder")
113113
derived_name = get_workspace_display_name(wd)

scripts/export.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@
5858
_build_composer_id_to_workspace_id,
5959
_collect_invalid_workspace_ids,
6060
_collect_workspace_entries,
61-
_load_bubble_map,
62-
_load_code_block_diff_map,
63-
_load_project_layouts_map,
61+
load_bubble_map,
62+
load_code_block_diff_map,
63+
load_project_layouts_map,
6464
_open_global_db,
6565
)
6666
from services.workspace_resolver import ( # noqa: E402
@@ -221,9 +221,9 @@ def main():
221221
file=sys.stderr,
222222
)
223223
else:
224-
project_layouts_map = _load_project_layouts_map(global_db)
225-
bubble_map = _load_bubble_map(global_db)
226-
code_block_diff_map = _load_code_block_diff_map(global_db)
224+
project_layouts_map = load_project_layouts_map(global_db)
225+
bubble_map = load_bubble_map(global_db)
226+
code_block_diff_map = load_code_block_diff_map(global_db)
227227

228228
try:
229229
ide_composer_rows = global_db.execute(

services/workspace_db.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
from __future__ import annotations
22

33
import json
4+
import logging
45
import os
56
import sqlite3
67
from contextlib import closing, contextmanager
78
from pathlib import Path
89

10+
_logger = logging.getLogger(__name__)
11+
912
from utils.path_helpers import get_workspace_folder_paths
10-
from utils.workspace_descriptor import _read_json_file
13+
from utils.workspace_descriptor import read_json_file
1114

1215

1316
# ── Global-DB KV loaders ────────────────────────────────────────────────────
@@ -17,11 +20,11 @@
1720
# corrupt table cannot propagate to callers.
1821

1922

20-
def _load_bubble_map(global_db) -> dict[str, dict]:
23+
def load_bubble_map(global_db) -> dict[str, dict]:
2124
"""Load all ``bubbleId:*`` KV entries into ``{bubble_id: bubble_dict}``.
2225
23-
Skips rows whose JSON value is not a dict; JSON parse errors are silently
24-
discarded so a single malformed row cannot block the rest.
26+
Skips rows whose JSON value is not a dict; JSON parse errors are logged at
27+
DEBUG level so a single malformed row cannot block the rest.
2528
"""
2629
bubble_map: dict[str, dict] = {}
2730
try:
@@ -39,12 +42,12 @@ def _load_bubble_map(global_db) -> dict[str, dict]:
3942
b = json.loads(row["value"])
4043
if isinstance(b, dict):
4144
bubble_map[bid] = b
42-
except Exception:
43-
pass
45+
except (json.JSONDecodeError, ValueError, KeyError, TypeError) as e:
46+
_logger.debug("Skipping malformed bubbleId row %s: %s", row["key"], e)
4447
return bubble_map
4548

4649

47-
def _load_project_layouts_map(global_db) -> dict[str, list]:
50+
def load_project_layouts_map(global_db) -> dict[str, list]:
4851
"""Load ``projectLayouts`` from ``messageRequestContext:*`` KV entries.
4952
5053
Returns ``{composer_id: [root_path_str, ...]}``. String-encoded layout
@@ -73,14 +76,14 @@ def _load_project_layouts_map(global_db) -> dict[str, list]:
7376
o = json.loads(layout) if isinstance(layout, str) else layout
7477
if isinstance(o, dict) and o.get("rootPath"):
7578
layouts_map[cid].append(o["rootPath"])
76-
except Exception:
77-
pass
78-
except Exception:
79-
pass
79+
except (json.JSONDecodeError, ValueError, KeyError, TypeError) as e:
80+
_logger.debug("Skipping malformed layout entry in %s: %s", row["key"], e)
81+
except (json.JSONDecodeError, ValueError, KeyError, TypeError) as e:
82+
_logger.debug("Skipping malformed messageRequestContext row %s: %s", row["key"], e)
8083
return layouts_map
8184

8285

83-
def _load_code_block_diff_map(global_db) -> dict[str, list]:
86+
def load_code_block_diff_map(global_db) -> dict[str, list]:
8487
"""Load ``codeBlockDiff:*`` KV entries into ``{composer_id: [diff_dict]}``.
8588
8689
Each diff dict contains all fields from the raw JSON value plus a
@@ -105,8 +108,8 @@ def _load_code_block_diff_map(global_db) -> dict[str, list]:
105108
**d,
106109
"diffId": parts[2] if len(parts) > 2 else None,
107110
})
108-
except Exception:
109-
pass
111+
except (json.JSONDecodeError, ValueError, KeyError, TypeError) as e:
112+
_logger.debug("Skipping malformed codeBlockDiff row %s: %s", row["key"], e)
110113
return diff_map
111114

112115

@@ -133,7 +136,7 @@ def _collect_invalid_workspace_ids(workspace_entries: list[dict]) -> set[str]:
133136
invalid: set[str] = set()
134137
for entry in workspace_entries:
135138
try:
136-
wd = _read_json_file(entry["workspaceJsonPath"])
139+
wd = read_json_file(entry["workspaceJsonPath"])
137140
folders = get_workspace_folder_paths(wd)
138141
if not folders:
139142
invalid.add(entry["name"])

services/workspace_listing.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
normalize_file_path,
1313
to_epoch_ms,
1414
)
15-
from utils.workspace_descriptor import _read_json_file
15+
from utils.workspace_descriptor import read_json_file
1616
from utils.workspace_path import get_cli_chats_path
1717
from services.workspace_db import (
1818
_build_composer_id_to_workspace_id,
1919
_collect_invalid_workspace_ids,
2020
_collect_workspace_entries,
21-
_load_bubble_map,
22-
_load_project_layouts_map,
21+
load_bubble_map,
22+
load_project_layouts_map,
2323
_open_global_db,
2424
)
2525
from services.workspace_resolver import (
@@ -56,8 +56,8 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
5656
"SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%' AND LENGTH(value) > 10"
5757
)
5858

59-
project_layouts_map: dict[str, list] = _load_project_layouts_map(global_db)
60-
bubble_map: dict[str, dict] = _load_bubble_map(global_db)
59+
project_layouts_map: dict[str, list] = load_project_layouts_map(global_db)
60+
bubble_map: dict[str, dict] = load_bubble_map(global_db)
6161

6262
invalid_workspace_aliases = _infer_invalid_workspace_aliases(
6363
composer_rows=composer_rows,
@@ -109,7 +109,7 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
109109
for entry in workspace_entries:
110110
norm_folder = ""
111111
try:
112-
wd = _read_json_file(entry["workspaceJsonPath"])
112+
wd = read_json_file(entry["workspaceJsonPath"])
113113
folders = get_workspace_folder_paths(wd)
114114
first_folder = folders[0] if folders else None
115115
if first_folder:

services/workspace_resolver.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
get_workspace_folder_paths,
1414
normalize_file_path,
1515
)
16-
from utils.workspace_descriptor import _basename_from_pathish, _read_json_file
16+
from utils.workspace_descriptor import basename_from_pathish, read_json_file
1717
from services.workspace_db import _open_global_db
1818
from models import SchemaError, Workspace
1919

@@ -24,7 +24,7 @@ def _get_workspace_display_name(workspace_path: str, workspace_id: str) -> str:
2424
return "Other chats"
2525
wj_path = os.path.join(workspace_path, workspace_id, "workspace.json")
2626
try:
27-
workspace = Workspace.from_dict(_read_json_file(wj_path), workspace_id=workspace_id)
27+
workspace = Workspace.from_dict(read_json_file(wj_path), workspace_id=workspace_id)
2828
name = get_workspace_display_name(workspace.raw)
2929
if name:
3030
return name
@@ -103,7 +103,7 @@ def _infer_workspace_name_from_context(workspace_path: str, workspace_id: str) -
103103
obj = layout
104104
if not isinstance(obj, dict):
105105
continue
106-
hint = _basename_from_pathish(obj.get("rootPath"))
106+
hint = basename_from_pathish(obj.get("rootPath"))
107107
if hint:
108108
counts[hint] = counts.get(hint, 0) + 1
109109

@@ -121,7 +121,7 @@ def _get_project_from_file_path(
121121
best_len = 0
122122
for entry in workspace_entries:
123123
try:
124-
wd = _read_json_file(entry["workspaceJsonPath"])
124+
wd = read_json_file(entry["workspaceJsonPath"])
125125
for folder in get_workspace_folder_paths(wd):
126126
wp = normalize_file_path(folder)
127127
try:
@@ -140,7 +140,7 @@ def _create_project_name_to_workspace_id_map(workspace_entries):
140140
mapping = {}
141141
for entry in workspace_entries:
142142
try:
143-
wd = _read_json_file(entry["workspaceJsonPath"])
143+
wd = read_json_file(entry["workspaceJsonPath"])
144144
for folder in get_workspace_folder_paths(wd):
145145
wp = re.sub(r"^file://", "", folder)
146146
parts = wp.replace("\\", "/").split("/")
@@ -156,7 +156,7 @@ def _create_workspace_path_to_id_map(workspace_entries):
156156
out = {}
157157
for entry in workspace_entries:
158158
try:
159-
wd = _read_json_file(entry["workspaceJsonPath"])
159+
wd = read_json_file(entry["workspaceJsonPath"])
160160
for folder in get_workspace_folder_paths(wd):
161161
normalized = normalize_file_path(folder)
162162
out[normalized] = entry["name"]
@@ -269,7 +269,7 @@ def _determine_project_for_conversation(
269269
folder_name_to_ws = []
270270
for entry in workspace_entries:
271271
try:
272-
wd = _read_json_file(entry["workspaceJsonPath"])
272+
wd = read_json_file(entry["workspaceJsonPath"])
273273
for folder in get_workspace_folder_paths(wd):
274274
name = re.sub(r"^file://", "", folder).replace("\\", "/").split("/")[-1]
275275
if name:

services/workspace_tabs.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
from utils.exclusion_rules import build_searchable_text, is_excluded_by_rules
1515
from utils.text_extract import extract_text_from_bubble
1616
from utils.tool_parser import parse_tool_call as _parse_tool_call
17-
from utils.workspace_descriptor import _read_json_file
17+
from utils.workspace_descriptor import read_json_file
1818
from models import Bubble, Composer, SchemaError
1919
from services.workspace_db import (
2020
_build_composer_id_to_workspace_id,
2121
_collect_invalid_workspace_ids,
2222
_collect_workspace_entries,
23-
_load_code_block_diff_map,
23+
load_code_block_diff_map,
2424
_open_global_db,
2525
)
2626
from services.workspace_resolver import (
@@ -64,7 +64,7 @@ def assemble_workspace_tabs(
6464
target_folder = ""
6565
wj_path = os.path.join(workspace_path, workspace_id, "workspace.json")
6666
try:
67-
wd = _read_json_file(wj_path)
67+
wd = read_json_file(wj_path)
6868
folders = get_workspace_folder_paths(wd)
6969
first_folder = folders[0] if folders else None
7070
if first_folder:
@@ -74,7 +74,7 @@ def assemble_workspace_tabs(
7474
if target_folder:
7575
for entry in workspace_entries:
7676
try:
77-
wd2 = _read_json_file(entry["workspaceJsonPath"])
77+
wd2 = read_json_file(entry["workspaceJsonPath"])
7878
folders2 = get_workspace_folder_paths(wd2)
7979
f2 = folders2[0] if folders2 else None
8080
if f2 and normalize_file_path(f2) == target_folder:
@@ -116,7 +116,7 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
116116
print(f"Schema drift in bubble {bid}: {e}")
117117

118118
# Load codeBlockDiffs
119-
code_block_diff_map = _load_code_block_diff_map(global_db)
119+
code_block_diff_map = load_code_block_diff_map(global_db)
120120

121121
# Load messageRequestContext rows once; build both
122122
# message_request_context_map and project_layouts_map from the same pass.

utils/cursor_md_exporter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ def cursor_ide_chat_to_markdown(
208208
into ``code_block_diff_map``.
209209
bubble_map:
210210
Global ``{bubble_id: bubble_dict}`` map loaded from
211-
``cursorDiskKV`` (see ``services.workspace_db._load_bubble_map``).
211+
``cursorDiskKV`` (see ``services.workspace_db.load_bubble_map``).
212212
code_block_diff_map:
213213
Optional ``{composer_id: [diff_dict]}`` map. When ``None`` no code
214214
edit bubbles are appended.
@@ -364,7 +364,7 @@ def cursor_ide_chat_to_markdown(
364364

365365
# ── Frontmatter ───────────────────────────────────────────────────────────
366366
fm_lines = ["---"]
367-
fm_lines.append(f"log_id: {composer_id}")
367+
fm_lines.append(f"log_id: {json.dumps(composer_id, ensure_ascii=False)}")
368368
fm_lines.append("log_type: chat")
369369
fm_lines.append(f"title: {json.dumps(title, ensure_ascii=False)}")
370370
fm_lines.append(f"created_at: {datetime.fromtimestamp(created_ms / 1000).isoformat()}")
@@ -374,14 +374,14 @@ def cursor_ide_chat_to_markdown(
374374
fm_lines.append(f"workspace: {ws_slug}")
375375
fm_lines.append(f"workspace_name: {json.dumps(ws_display_name, ensure_ascii=False)}")
376376
if model_name and model_name != "default":
377-
fm_lines.append(f"model: {model_name}")
377+
fm_lines.append(f"model: {json.dumps(model_name, ensure_ascii=False)}")
378378
fm_lines.append(f"message_count: {len(bubbles)}")
379379
if total_tool_calls:
380380
fm_lines.append(f"total_tool_calls: {total_tool_calls}")
381381
if tool_breakdown:
382382
fm_lines.append("tool_call_breakdown:")
383383
for tn, cnt in sorted(tool_breakdown.items(), key=lambda x: -x[1]):
384-
fm_lines.append(f" {tn}: {cnt}")
384+
fm_lines.append(f" {json.dumps(tn, ensure_ascii=False)}: {cnt}")
385385
total_think = sum(1 for bub in bubbles if bub.get("thinking"))
386386
if total_think:
387387
fm_lines.append(f"thinking_count: {total_think}")

utils/workspace_descriptor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from urllib.parse import unquote, urlparse
88

99

10-
def _read_json_file(path: str):
10+
def read_json_file(path: str):
1111
"""Read a workspace.json with Cursor indirection applied."""
1212
return _resolve_workspace_descriptor(path)
1313

@@ -70,7 +70,7 @@ def _resolve_workspace_descriptor(path: str, depth: int = 0):
7070
return out
7171

7272

73-
def _basename_from_pathish(path_value: str | None) -> str | None:
73+
def basename_from_pathish(path_value: str | None) -> str | None:
7474
"""Extract a readable leaf folder name from file URI or filesystem path."""
7575
if not path_value:
7676
return None

0 commit comments

Comments
 (0)