|
1 | | -# Theory: Safe Internal Refactor for qbo-cli |
| 1 | +# Theory: Safe CLI Refactor With Format Contract Verification |
2 | 2 |
|
3 | 3 | ## Problem |
4 | 4 |
|
5 | | -`qbo_cli/cli.py` is still the center of the application, but before this pass it |
6 | | -mixed parser construction, command dispatch, repeated command glue, and output |
7 | | -normalization in ways that made small changes noisier than necessary. The risk |
8 | | -was not a broken CLI; the risk was that future edits would keep paying a |
9 | | -complexity tax in the least interesting parts of the file. |
| 5 | +`qbo_cli/cli.py` still carries the whole CLI surface, so small refactors can |
| 6 | +silently change behavior in places where parser defaults, command glue, and |
| 7 | +output selection interact. The structural cleanup already improved the file, but |
| 8 | +that kind of cleanup is only safe if the format contract stays consistent across |
| 9 | +commands. `gl-report` was the highest-risk spot because it has custom output |
| 10 | +modes and heavier orchestration than the basic CRUD/report commands. |
10 | 11 |
|
11 | 12 | ## Operating Theory |
12 | 13 |
|
13 | | -The project is safest to improve at its seams. Unit tests already protect the |
14 | | -public behavior of common command handlers, parser aliases, output formatting, |
15 | | -client pagination/retry behavior, and many pure report helpers. The less-tested |
16 | | -surfaces are OAuth callback handling, token persistence internals, and the |
17 | | -orchestration-heavy parts of `cmd_gl_report()`. That makes the correct lever a |
18 | | -local refactor of parser/dispatch and command plumbing, not a large module |
19 | | -split. |
| 14 | +The right way to stabilize this codebase is to keep changes at the seams and |
| 15 | +verify the public contract aggressively. The refactor reduced duplication by |
| 16 | +centralizing parser construction, runtime creation, dispatch, stdin handling, |
| 17 | +report param building, and output emission. That increases maintainability, but |
| 18 | +it also means one mismatch in format resolution can affect an entire command |
| 19 | +surface. The leverage point is therefore not more extraction; it is making sure |
| 20 | +every command follows the same format-selection rules unless it has an explicit |
| 21 | +reason not to. |
20 | 22 |
|
21 | 23 | ## Strategy |
22 | 24 |
|
23 | | -Keep behavior stable while reducing repetition: |
| 25 | +Treat the refactor as successful only if the CLI contract is demonstrably |
| 26 | +preserved: |
24 | 27 |
|
25 | | -- shrink `main()` into orchestration only |
26 | | -- move parser building and dispatch decisions into dedicated helpers |
27 | | -- centralize repeated command concerns: client creation, output emission, stdin |
28 | | - JSON loading, report param building |
29 | | -- centralize output normalization shared by text and TSV rendering |
30 | | -- document the logical architecture so the file can stay understandable even |
31 | | - before a future module split |
| 28 | +- review local commits and uncommitted fixes against `origin/main` |
| 29 | +- verify parser behavior directly, not only through handler unit tests |
| 30 | +- patch only the format-resolution gap at the source |
| 31 | +- add tests that lock the contract in place for both command-level and parser-level flows |
| 32 | +- validate with unit tests, live tests, and static checks that matter for the touched surface |
32 | 33 |
|
33 | | -The aim is structural clarity with minimal movement of risky code. |
| 34 | +The intent is still minimal movement. One root fix in the parser/output path is |
| 35 | +better than special-casing downstream rendering. |
34 | 36 |
|
35 | 37 | ## Key Discoveries |
36 | 38 |
|
37 | | -The earlier readonly pass was useful because it separated environment gaps from |
38 | | -real quality issues. Once the local toolchain was installed, the important fact |
39 | | -was that `pytest` was already green, `ruff` problems were isolated to tests, |
40 | | -and `mypy` failures in `qbo_cli/cli.py` were small enough to eliminate as part |
41 | | -of the refactor. That turned the work from "safe cleanup only" into "safe |
42 | | -cleanup plus static-check hardening" without widening the risk surface. |
43 | | - |
44 | | -The current shape now matches the intended strategy: `main()` delegates to |
45 | | -`_build_parser()`, `_build_runtime()`, and `_dispatch_command()`, command |
46 | | -handlers share helper glue, and output normalization is centralized. The code |
47 | | -is still a single-file CLI, but with cleaner internal boundaries. |
48 | | - |
49 | | -The strongest confidence signal came from the live suite. Running all |
50 | | -`@pytest.mark.live` tests against the configured QuickBooks account passed |
51 | | -without changes to test code or to the refactored command paths. That matters |
52 | | -because it validates the actual packaged CLI entrypoint, subprocess execution, |
53 | | -auth status, report endpoints, and General Ledger smoke flows against the real |
54 | | -service rather than only mocked unit-level contracts. |
| 39 | +The critical discovery from review was not inside report generation itself but |
| 40 | +at the CLI boundary: `gl-report` emitted JSON correctly when `args.output` was |
| 41 | +set to `json`, but it did not actually honor the shared format contract. Global |
| 42 | +`-f json` left `args.output` at `"text"`, and `gl-report --format json` was not |
| 43 | +accepted at all. That meant the refactor had made the common `_resolve_fmt()` |
| 44 | +path feel shared while `gl-report` still behaved like a special case. |
| 45 | + |
| 46 | +The fix is small and structural. `gl-report` now resolves output through the |
| 47 | +same format selector as other commands, accepts `--format` as an alias, and |
| 48 | +fails explicitly for `tsv`, which remains unsupported for that command. The |
| 49 | +tests now cover both direct handler behavior and parser wiring for `gl-report`, |
| 50 | +which was the missing safety net. |
| 51 | + |
| 52 | +Verification stayed strong after the fix. Full non-live `pytest`, full |
| 53 | +`@pytest.mark.live` smoke tests, and `mypy` all passed. Ruff also passes for the |
| 54 | +touched surface. Repo-wide Ruff failures still exist on `origin/main`, but they |
| 55 | +are pre-existing test-file issues unrelated to this refactor. |
55 | 56 |
|
56 | 57 | ## Open Questions |
57 | 58 |
|
58 | | -The next meaningful architectural step is still blocked on test depth, not on |
59 | | -mechanical extraction work. Before splitting `cli.py` into separate modules, the |
60 | | -project should add stronger tests around `TokenManager`, OAuth callback state |
61 | | -handling, and `cmd_gl_report()` output modes. Without that, larger movement |
62 | | -would trade readability gains for regression risk. |
| 59 | +The main remaining uncertainty is still test depth around the most stateful |
| 60 | +paths: `TokenManager`, OAuth callback handling, and the more exotic |
| 61 | +`cmd_gl_report()` modes such as `txns`, `expanded`, and explicit unsupported |
| 62 | +format cases. Future extraction work should wait until those contracts are |
| 63 | +covered more directly, otherwise readability gains will come with regression |
| 64 | +risk. |
0 commit comments