Summary
Four source modules have grown well beyond the ~250-line threshold specified in .github/CODING_GUIDELINES.md:
When a module grows beyond ~250 lines or serves multiple public consumers, extract a _<name>.py private module.
| Module |
Lines |
Multiple of limit |
Concerns mixed |
src/copilot_usage/parser.py |
1 176 |
4.7× |
discovery, identity caching, event parsing, model inference, session building |
src/copilot_usage/report.py |
738 |
2.9× |
live-session view, summary rendering, cost view, session-table helpers |
src/copilot_usage/cli.py |
673 |
2.7× |
5 Click commands, interactive TUI loop, stdin polling, file-watcher integration |
src/copilot_usage/vscode_parser.py |
610 |
2.4× |
log discovery, multi-level LRU caching, request parsing, summary aggregation |
render_detail.py is the existing precedent: it was extracted from report.py when that concern outgrew its module. The same approach should be applied across the remaining four files.
Root cause
The modules grew organically as features were added. No single change violated the limit; the threshold was crossed incrementally without a corresponding extraction step.
Spec for resolving
Each extraction must:
- keep the current module's
__all__ and public call-sites unchanged (only private helpers move)
- use the
_<name>.py private-module naming convention
- add module-level docstrings to the new files
parser.py (1 176 → ~400 lines target)
Extract into two private submodules:
_parser_cache.py — the five cache classes (_DiscoveryCache, _CachedSession, _CachedEvents, _SortedSessionsCache, _CopilotConfig) plus their helper functions (_insert_session_entry, _insert_events_entry, _build_fingerprint, _infer_model_from_metrics, _read_config_model, get_cached_events)
_parser_build.py — _FirstPassResult, _ResumeInfo, _first_pass, _detect_resume, _build_completed_summary, _build_active_summary, _BuildMeta, _build_session_summary_with_meta, and the supporting helpers; build_session_summary in the main module becomes a thin dispatcher
report.py (738 → ~250 lines target)
Extract into two private submodules (following the render_detail.py precedent):
_report_cost.py — render_cost_view and its private helpers (_render_historical_section_from, _render_active_section_from)
_report_summary.py — _render_summary_header, _render_totals, _render_model_table, _render_session_table, _aggregate_model_metrics, _filter_sessions, render_summary, render_full_summary
Re-export all public names from report.py so __all__ is unchanged.
cli.py (673 → ~300 lines target)
Extract into two private submodules:
_cli_interactive.py — _interactive_loop, _draw_home, _draw_detail, _draw_cost, _write_prompt, _read_line_nonblocking, _HOME_PROMPT, _BACK_PROMPT, and related constants
_cli_watchdog.py — _FileChangeEventHandler Protocol, _FileChangeHandler, _Stoppable Protocol, _start_observer, _stop_observer, _WATCHDOG_DEBOUNCE_SECS
vscode_parser.py (610 → ~300 lines target)
Extract into one private submodule:
_vscode_cache.py — the four cache data-classes (_VSCodeDiscoveryCache, _CachedVSCodeLog, _CachedVSCodeSummary, _CachedFileSummary), their module-level dicts, and the cache-management helpers
Testing requirement
This is a pure refactoring — no behaviour changes.
- All existing tests must pass without modification after each extraction (
make check).
- The
__all__ lists in the public modules must remain identical before and after.
tests/test_packaging.py::test_all_names_importable is the regression gate for the public API surface.
- No new tests are required for the extraction itself, but if any helper is made newly testable in isolation by the split, add unit tests for it.
Each module should be extracted in a separate commit to keep diffs reviewable.
Generated by Code Health Analysis · ● 10.5M · ◷
Summary
Four source modules have grown well beyond the ~250-line threshold specified in
.github/CODING_GUIDELINES.md:src/copilot_usage/parser.pysrc/copilot_usage/report.pysrc/copilot_usage/cli.pysrc/copilot_usage/vscode_parser.pyrender_detail.pyis the existing precedent: it was extracted fromreport.pywhen that concern outgrew its module. The same approach should be applied across the remaining four files.Root cause
The modules grew organically as features were added. No single change violated the limit; the threshold was crossed incrementally without a corresponding extraction step.
Spec for resolving
Each extraction must:
__all__and public call-sites unchanged (only private helpers move)_<name>.pyprivate-module naming conventionparser.py(1 176 → ~400 lines target)Extract into two private submodules:
_parser_cache.py— the five cache classes (_DiscoveryCache,_CachedSession,_CachedEvents,_SortedSessionsCache,_CopilotConfig) plus their helper functions (_insert_session_entry,_insert_events_entry,_build_fingerprint,_infer_model_from_metrics,_read_config_model,get_cached_events)_parser_build.py—_FirstPassResult,_ResumeInfo,_first_pass,_detect_resume,_build_completed_summary,_build_active_summary,_BuildMeta,_build_session_summary_with_meta, and the supporting helpers;build_session_summaryin the main module becomes a thin dispatcherreport.py(738 → ~250 lines target)Extract into two private submodules (following the
render_detail.pyprecedent):_report_cost.py—render_cost_viewand its private helpers (_render_historical_section_from,_render_active_section_from)_report_summary.py—_render_summary_header,_render_totals,_render_model_table,_render_session_table,_aggregate_model_metrics,_filter_sessions,render_summary,render_full_summaryRe-export all public names from
report.pyso__all__is unchanged.cli.py(673 → ~300 lines target)Extract into two private submodules:
_cli_interactive.py—_interactive_loop,_draw_home,_draw_detail,_draw_cost,_write_prompt,_read_line_nonblocking,_HOME_PROMPT,_BACK_PROMPT, and related constants_cli_watchdog.py—_FileChangeEventHandlerProtocol,_FileChangeHandler,_StoppableProtocol,_start_observer,_stop_observer,_WATCHDOG_DEBOUNCE_SECSvscode_parser.py(610 → ~300 lines target)Extract into one private submodule:
_vscode_cache.py— the four cache data-classes (_VSCodeDiscoveryCache,_CachedVSCodeLog,_CachedVSCodeSummary,_CachedFileSummary), their module-level dicts, and the cache-management helpersTesting requirement
This is a pure refactoring — no behaviour changes.
make check).__all__lists in the public modules must remain identical before and after.tests/test_packaging.py::test_all_names_importableis the regression gate for the public API surface.Each module should be extracted in a separate commit to keep diffs reviewable.