09. CLI¶
Purpose¶
Define observable CLI behavior: argument handling, summaries, error UI, and output writing.
Public surface¶
- CLI runner:
codeclone/cli.py:main,codeclone/cli.py:_main_impl - Parser:
codeclone/_cli_args.py:build_parser - Summary renderer:
codeclone/_cli_summary.py:_print_summary - Path validation:
codeclone/_cli_paths.py:_validate_output_path - Message catalog:
codeclone/ui_messages.py
Data model¶
CLI modes:
- Normal mode
- Gating mode (
--ci,--fail-on-new,--fail-threshold>=0) - Update mode (
--update-baseline)
Summary metrics:
- files found/analyzed/cache hits/skipped
- structural counters: analyzed lines/functions/methods/classes
- function/block/segment groups
- excluded golden-fixture clone groups (when configured)
- suppressed segment groups
- dead-code active/suppressed status in metrics line
- adoption coverage in the normal
Metricsblock: parameter typing, return typing, public docstrings, andAnycount - public API surface in the normal
Metricsblock whenapi_surfacewas collected: symbol/module counts plus added/breaking deltas when a trusted metrics baseline is available - coverage join in the normal
Metricsblock when--coverage FILEwas provided: joined Cobertura overall line coverage, untested hotspot count, and threshold/source context - new vs baseline
Metrics-related CLI gates:
- threshold gates:
--fail-complexity,--fail-coupling,--fail-cohesion,--fail-health - coverage threshold gates:
--min-typing-coverage,--min-docstring-coverage - baseline-aware delta gates:
--fail-on-new-metrics,--fail-on-typing-regression,--fail-on-docstring-regression,--fail-on-api-break - external coverage join gate:
--coverage FILE,--coverage-min PERCENT,--fail-on-untested-hotspots - update mode:
--update-metrics-baseline - opt-in metrics family:
--api-surface - In unified baseline mode,
--update-baselinerewrites embedded metric surfaces from the current enabled config; disabled optional surfaces are dropped.
Refs:
codeclone/_cli_summary.py:_print_summarycodeclone/ui_messages.py:fmt_summary_files
Contracts¶
- Help output includes canonical exit-code section and project links.
- Reporting flag UX uses explicit pairs (
--no-progress/--progress,--no-color/--color) and avoids generated double-negation aliases. --open-html-reportis a local UX action layered on top of--html; it does not implicitly enable HTML output.--timestamped-report-pathsonly rewrites default report paths requested via bare report flags; explicit FILE values stay unchanged.- Changed-scope clone review uses:
--changed-only--diff-against GIT_REF--paths-from-git-diff GIT_REFTypical usage:codeclone . --changed-only --diff-against maincodeclone . --paths-from-git-diff HEAD~1
- Contract errors are prefixed by
CONTRACT ERROR:. - Gating failures are prefixed by
GATING FAILURE:. - Internal errors use
fmt_internal_errorwith optional debug details. - Runtime footer uses explicit wording:
Pipeline done in <seconds>s. This metric is CLI pipeline time and does not include external launcher/startup overhead (for exampleuv run). - Dead-code metric line is stateful and deterministic:
N found (M suppressed)when active dead-code items exist✔ cleanwhen both active and suppressed are zero✔ clean (M suppressed)when active is zero but suppressed > 0
- The normal rich
Metricsblock includes:Adoptionwhen adoption coverage facts were computedPublic APIwhenapi_surfacefacts were computedCoveragewhen Cobertura coverage was joined with--coverage
- Quiet compact metrics output stays on the existing fixed one-line summary and does not expand adoption/API/coverage-join detail.
- When
golden_fixture_pathsexcludes clone groups from active review, CLI keeps that count inside theClonessummary line (fixtures=N) instead of adding a separate summary row. - Typing/docstring adoption metrics are computed in full mode.
--api-surfaceis opt-in in normal runs, but runtime auto-enables it when--fail-on-api-breakor--update-metrics-baselineneeds a public API snapshot.--fail-on-typing-regression/--fail-on-docstring-regressionrequire a metrics baseline that already contains adoption coverage data.--fail-on-api-breakrequires a metrics baseline that already containsapi_surfacedata.--coverageis a current-run external Cobertura input. It does not update or compare againstcodeclone.baseline.json.- Relative clone-baseline and metrics-baseline paths from defaults or
pyproject.tomlresolve from the analysis root. Explicit CLI paths are used as provided. - Invalid Cobertura XML is warning-only in normal runs: CLI prints
Coverage join ignored, keeps exit0, and showsCoverageas unavailable in the normalMetricsblock. It becomes a contract error only when--fail-on-untested-hotspotsrequires a valid join. --fail-on-untested-hotspotsrequires--coverageand a valid Cobertura XML input. It exits3when medium/high-risk functions measured by Coverage Join fall below--coverage-min(default50). Functions outside the suppliedcoverage.xmlscope are surfaced separately and do not trigger this gate. The flag name is retained for CLI compatibility.
Refs:
codeclone/contracts.py:cli_help_epilogcodeclone/ui_messages.py:fmt_contract_errorcodeclone/ui_messages.py:fmt_internal_error
Invariants (MUST)¶
- Report writes (
--html/--json/--md/--sarif/--text) are path-validated and write failures are contract errors. - Bare reporting flags write to default deterministic paths under
.cache/codeclone/. --open-html-reportrequires--html; invalid combination is a contract error.--timestamped-report-pathsrequires at least one requested report output; invalid combination is a contract error.--changed-onlyrequires either--diff-againstor--paths-from-git-diff.--diff-againstrequires--changed-only.--diff-againstand--paths-from-git-diffare mutually exclusive.- Git diff refs are validated as safe single revision expressions before subprocess execution.
- Browser-open failure after a successful HTML write is warning-only and does not change the process exit code.
- Baseline update write failure is contract error.
- In gating mode, unreadable source files are contract errors with higher priority than clone gating failure.
- Changed-scope flags do not create a second canonical report: they project clone summary/threshold decisions over the changed-files subset after the normal full analysis completes.
Refs:
codeclone/cli.py:_write_report_outputcodeclone/cli.py:_main_impl
Failure modes¶
| Condition | User-facing category | Exit |
|---|---|---|
| Invalid CLI flag | contract | 2 |
| Invalid output extension/path | contract | 2 |
--open-html-report without --html |
contract | 2 |
--timestamped-report-paths without reports |
contract | 2 |
--changed-only without diff source |
contract | 2 |
--diff-against without --changed-only |
contract | 2 |
--diff-against + --paths-from-git-diff |
contract | 2 |
| Baseline untrusted in CI/gating | contract | 2 |
| Coverage/API regression gate without required metrics-baseline capability | contract | 2 |
--fail-on-untested-hotspots without --coverage |
contract | 2 |
| Invalid Cobertura XML without hotspot gating | warning only | 0 |
| Invalid Cobertura XML for coverage hotspot gating | contract | 2 |
| Unreadable source in CI/gating | contract | 2 |
New clones with --fail-on-new |
gating | 3 |
| Threshold exceeded | gating | 3 |
Coverage hotspots with --fail-on-untested-hotspots |
gating | 3 |
| Unexpected exception | internal | 5 |
Determinism / canonicalization¶
- Summary metric ordering is fixed.
- Compact summary mode (
--quiet) is fixed-format text. - Help epilog is generated from static constants.
git diff --name-onlyinput is normalized to sorted repo-relative paths before changed-scope projection is applied.
Refs:
codeclone/_cli_summary.py:_print_summarycodeclone/contracts.py:EXIT_CODE_DESCRIPTIONS
Locked by tests¶
tests/test_cli_unit.py::test_cli_help_text_consistencytests/test_cli_unit.py::test_argument_parser_contract_error_marker_for_invalid_argstests/test_cli_inprocess.py::test_cli_summary_format_stabletests/test_cli_inprocess.py::test_cli_unreadable_source_fails_in_ci_with_contract_errortests/test_cli_inprocess.py::test_cli_contract_error_priority_over_gating_failure_for_unreadable_source
Non-guarantees¶
- Rich styling details are not part of machine-facing CLI contract.
- Warning phrasing may evolve if category markers and exit semantics stay stable.