You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The session export logic exists in two independent, near-identical implementations:
bulk_export() in api/export_api.py:89-272 (HTTP API — writes zip to BytesIO)
cmd_export() in scripts/export.py:452-627 (CLI — writes to filesystem or zip)
Both independently perform the same sequence: list projects, iterate sessions, check exclusion rules, parse JSONL, skip untitled sessions, compute stats, format to Markdown, build manifest entries, and persist export state. Divergence is already present (manifest fields, zip inner paths, error handling). Every behavioral fix must be applied in two places.
Goal
Extract a shared export engine that owns the common session loop. HTTP and CLI callers delegate to it and provide sink/path-builder strategies for their respective output targets.
Prerequisites
Monday PR2 merged (runtime JSONL validation at parse_session() boundary)
Tuesday fixtures merged (optional but useful for parity tests)
Problem
The session export logic exists in two independent, near-identical implementations:
bulk_export()inapi/export_api.py:89-272(HTTP API — writes zip toBytesIO)cmd_export()inscripts/export.py:452-627(CLI — writes to filesystem or zip)Both independently perform the same sequence: list projects, iterate sessions, check exclusion rules, parse JSONL, skip untitled sessions, compute stats, format to Markdown, build manifest entries, and persist export state. Divergence is already present (manifest fields, zip inner paths, error handling). Every behavioral fix must be applied in two places.
Goal
Extract a shared export engine that owns the common session loop. HTTP and CLI callers delegate to it and provide sink/path-builder strategies for their respective output targets.
Prerequisites
parse_session()boundary)Scope
New module:
utils/export_engine.py(orexport_pipeline.py)ExportSinkprotocol (or equivalent callback/strategy):ZipSinkwriting tozipfile.ZipFile/BytesIO{proj_slug}/{file}.mdlayout and CLI keeps{date}/{proj_slug}/{file}.mdwithout duplicating the loop.exported_count,failure_count,skipped_count,manifest,new_sessions_map, etc.Caller changes
api/export_api.pybulk_export()delegates to engine; retains request validation, 422 on empty export,send_file,_write_statescripts/export.pycmd_export()delegates bulk loop to engine; retains argparse, disk/zip output,_save_state, human-readable printsTests
tests/test_export_engine_parity.py):/api/export) vs CLI path (export --no-zip)session_id,title,project,tokens,tool_calls)tests/test_export_api_bulk.pytests/test_cli_e2e.pytests/test_export_exclusion_filtering.pyOut of Scope
feat/cli-export-exit-codes)Acceptance Criteria
BytesIOzip) vs CLI (filesystem/zip)api/export_api.pyandscripts/export.pyboth delegate to the shared enginemypy --strictpasses on new modulepytest -qgreen in CI