Skip to content

Commit 2eebda6

Browse files
fix: address PR #70 review — duplicate exclusion check, ruff format CI, and test cleanup
1 parent d42e730 commit 2eebda6

39 files changed

Lines changed: 949 additions & 792 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ jobs:
7171
- name: Ruff
7272
run: ruff check .
7373

74+
- name: Ruff format check
75+
run: ruff format --check .
76+
7477
- name: pip-audit
7578
run: pip-audit -r requirements.txt
7679

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This project is pre-release. Security fixes are applied to the **latest `master`
66

77
| Version | Supported |
88
| -------------- | --------- |
9-
| latest `master`| Yes |
9+
| latest `master` | Yes |
1010
| older commits | No |
1111

1212
## Reporting a Vulnerability

api/export_api.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
from api.error_codes import ErrorCode, error_response
1313
from models.export import ExportStateDict
1414
from utils.exclusion_rules import is_session_excluded
15-
from utils.export_engine import EXPORT_ERRORS as _EXPORT_ERRORS
16-
from utils.export_engine import ZipSink, run_bulk_export
15+
from utils.export_engine import EXPORT_ERRORS as _EXPORT_ERRORS, ZipSink, run_bulk_export
1716
from utils.export_state_store import (
1817
EXPORT_STATE_FILE,
1918
atomic_write_export_state,
@@ -94,10 +93,7 @@ def bulk_export() -> FlaskReturn:
9493
since=since,
9594
)
9695

97-
base = (
98-
current_app.config.get("CLAUDE_PROJECTS_DIR")
99-
or get_claude_projects_dir()
100-
)
96+
base = current_app.config.get("CLAUDE_PROJECTS_DIR") or get_claude_projects_dir()
10197
projects = list_projects(base)
10298
rules = current_app.config.get("EXCLUSION_RULES") or []
10399

@@ -109,9 +105,7 @@ def bulk_export() -> FlaskReturn:
109105
buf = io.BytesIO()
110106

111107
def _on_export_error(sid: str, exc: Exception) -> None:
112-
current_app.logger.warning(
113-
"Failed to export %s: %s", sid[:10], exc
114-
)
108+
current_app.logger.warning("Failed to export %s: %s", sid[:10], exc)
115109

116110
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
117111
result = run_bulk_export(
@@ -163,10 +157,7 @@ def _on_export_error(sid: str, exc: Exception) -> None:
163157
def export_session(project_name: str, session_id: str) -> FlaskReturn:
164158
from utils.session_path import safe_join
165159

166-
base = (
167-
current_app.config.get("CLAUDE_PROJECTS_DIR")
168-
or get_claude_projects_dir()
169-
)
160+
base = current_app.config.get("CLAUDE_PROJECTS_DIR") or get_claude_projects_dir()
170161
try:
171162
filepath = safe_join(base, project_name, f"{session_id}.jsonl")
172163
except ValueError:
@@ -183,9 +174,7 @@ def export_session(project_name: str, session_id: str) -> FlaskReturn:
183174
try:
184175
session = parse_session(filepath)
185176
except _EXPORT_ERRORS:
186-
current_app.logger.exception(
187-
"Failed to parse session %s for export", session_id
188-
)
177+
current_app.logger.exception("Failed to parse session %s for export", session_id)
189178
return error_response(
190179
ErrorCode.PARSE_ERROR,
191180
"Failed to parse session",
@@ -203,9 +192,7 @@ def export_session(project_name: str, session_id: str) -> FlaskReturn:
203192
try:
204193
stats = compute_stats(session)
205194
except _EXPORT_ERRORS:
206-
current_app.logger.exception(
207-
"Failed to compute stats for export %s", session_id
208-
)
195+
current_app.logger.exception("Failed to compute stats for export %s", session_id)
209196
return error_response(
210197
ErrorCode.INTERNAL_ERROR,
211198
"Failed to compute session stats",

api/projects.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def get_projects() -> FlaskReturn:
4949
# so the landing page matches what the workspace page shows.
5050
# Uses quick_session_info() which peeks at files without full parsing.
5151
from utils.jsonl_parser import quick_session_info
52+
5253
for project in projects:
5354
sessions = list_sessions(project["path"])
5455
titled_count = 0
@@ -81,6 +82,7 @@ def get_project_sessions(project_name: str) -> FlaskReturn:
8182
sessions = list_sessions(project_dir)
8283
# Add summary preview for each session
8384
from utils.jsonl_parser import parse_session
85+
8486
rules = current_app.config.get("EXCLUSION_RULES") or []
8587
result: list[ProjectSessionRowDict] = []
8688
for s in sessions:

api/search.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Search endpoint. Brute-force substring match across all sessions."""
22

3-
43
from flask import Blueprint, current_app, request
54

65
from api._flask_types import FlaskReturn, json_response
@@ -70,14 +69,16 @@ def search() -> FlaskReturn:
7069
end = min(len(text), idx + len(query) + 80)
7170
snippet = text[start:end]
7271

73-
results.append({
74-
"project": project["name"],
75-
"session_id": session["session_id"],
76-
"title": session["title"],
77-
"role": msg["role"],
78-
"timestamp": msg.get("timestamp"),
79-
"snippet": snippet,
80-
})
72+
results.append(
73+
{
74+
"project": project["name"],
75+
"session_id": session["session_id"],
76+
"title": session["title"],
77+
"role": msg["role"],
78+
"timestamp": msg.get("timestamp"),
79+
"snippet": snippet,
80+
}
81+
)
8182
if len(results) >= max_results:
8283
break
8384

api/sessions.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,6 @@ def get_session_stats(project_name: str, session_id: str) -> FlaskReturn:
8989
500,
9090
)
9191

92-
rules = current_app.config.get("EXCLUSION_RULES") or []
93-
if is_session_excluded(rules, session, project_name):
94-
return error_response(
95-
ErrorCode.SESSION_NOT_FOUND,
96-
"Session not found",
97-
404,
98-
)
99-
10092
try:
10193
stats = compute_stats(session)
10294
return json_response(stats)

app.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,12 @@ def build_cli_parser() -> argparse.ArgumentParser:
106106
)
107107
parser.add_argument("--base-dir", default=None, help="Override Claude projects dir")
108108
parser.add_argument(
109-
"--exclude-rules", "-e",
109+
"--exclude-rules",
110+
"-e",
110111
default=None,
111112
metavar="PATH",
112113
help="Path to exclusion rules file (sensitive sessions are omitted). "
113-
"If omitted, uses ~/.claude-code-chat-browser/exclusion-rules.txt if present.",
114+
"If omitted, uses ~/.claude-code-chat-browser/exclusion-rules.txt if present.",
114115
)
115116
return parser
116117

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ line-length = 100
2424
[tool.ruff.lint]
2525
select = ["E", "F", "W", "I"]
2626

27+
[tool.ruff.lint.isort]
28+
combine-as-imports = true
29+
2730
[tool.ruff.lint.per-file-ignores]
2831
# CLI bootstrap: sys.path must be set before local imports.
2932
"scripts/export.py" = ["E402"]

0 commit comments

Comments
 (0)