This file tracks the original multi-phase refactor and what is already done in the current codebase. Use it for orientation and optional follow-ups, not as a from-scratch checklist.
| Area | Status | Where to look |
|---|---|---|
| Spinner / debug / errors | Done | test/utils.test.ts, test/utils/investec-errors.test.ts, src/utils/spinner.ts, src/utils/cli-errors.ts |
| Table / structured output | Done | test/utils/output.test.ts (printTable narrow width, NO_COLOR, formatOutput piped JSON) |
| Shared command runners | Done | src/utils/command-runners.ts (withSpinner, runListCommand, runWriteCommand, …) |
| Core list-style commands | Done | accounts, cards, balances, transactions, etc. use shared patterns |
utils.ts decomposition |
Done | src/utils/*.ts modules; src/utils.ts re-exports for compatibility |
--no-spinner + deprecation |
Done | src/utils/spinner-flags.ts, warning in src/index.ts |
| Integration-style CLI tests | Done | test/cmds/integration-safety.test.ts (TTY vs piped, spinner, core flows) |
| Config / profile UX | Done | src/cmds/config-subcommands.ts, test/cmds/config-subcommands.test.ts |
- Intent: Baseline tests for spinners, debug flags,
normalizeInvestecError,printTableedge cases. - Status: Covered by unit tests above; PB/Card errors go through
normalizeInvestecErrorwhere integrated.
- Intent:
withSpinner,runListCommand,runWriteCommand; migrate high-traffic commands. - Status: Helpers exist; core commands migrated over time.
- Intent: Extract terminal, output, api, history, credentials, etc.
- Status: Extraction done under
src/utils/; credential logic lives incredentials-store.ts/credentials-validation.ts(naming differs slightly from the original “credentials.ts” bullet).
- Intent:
--no-spinnerwith deprecation for legacy-s/--spinner. - Status: Implemented.
- Intent: Integration coverage for accounts, cards, deploy, fetch; TTY vs piped.
- Status:
integration-safety.test.tscovers several of these patterns; extend only if new regressions appear.
| Criterion | Notes |
|---|---|
| Less per-command spinner boilerplate | Largely met via command-runners + shared patterns |
utils.ts mostly re-exports |
Met |
| Error details only in verbose/debug | Spot-check when touching handleCliError / new commands |
| Spinner cleared after success/failure | Enforced by patterns + integration tests |
| Integration tests for core flows | Met for selected commands; expandable |
- Verbose error audit — When adding or refactoring commands, confirm stack traces / debug lines only appear with
--verboseorDEBUG, consistent withhandleCliError. - More integration cases — Add scenarios to
integration-safety.test.tsif a specific command regresses (e.g. new file-write or spinner path). - Docs — Run
npm run docsafter changing Commander definitions soGENERATED_README.mdstays aligned. - Coverage target (80%+) — Optional; use
vitest --coverageif you want a numeric goal in CI.
One logical change per PR, tests included, no unrelated refactors—same as before.
Phased plan from the architecture / error-handling review. Status is updated as work lands.
| Phase | Theme | Status |
|---|---|---|
| 1 | Break index ↔ cmds / utils/api cycle; single-source exit codes |
Done — src/runtime-credentials.ts; mocks + re-exports; dead _determineExitCode removed from src/utils.ts |
| 2 | Error model: fetch.ts codes, credentials-store CliError, determineExitCode heuristics, generateCompletionScript |
Done — ERROR_CODES.UNSUPPORTED_OPERATION; fetch uses UNSUPPORTED_OPERATION / INVESTEC_API_ERROR; readCredentialsFile / loadCredentialsFile throw CliError; determineExitCode uses code map then narrower heuristics (exported); completion uses CliError + determineExitCode |
| 3 | Static utils imports in commands; single configureChalk call site |
Done — all commands use static isStdoutPiped / readStdin from utils.js (no dynamic import('../utils.js')); configureChalk() only invoked from index.ts at startup |
| 4 | Tests: bank, register, login, ai; optional shared mock harness |
Done (tests added; shared mock harness still optional) |
| 5 | Polish: withCommandContext generics, docs.ts typing, INVESTEC_CLIENT_ID policy, split index.ts |
Done — see below |
src/runtime-credentials.ts—credentialLocation,credentials,printTitleBox,optionCredentials(same behavior as before; no import ofindex.js).src/index.ts— Imports and re-exports the above; config still usescredentialLocation.src/utils/api.ts—optionCredentialsfromruntime-credentials.js.src/utils.ts— Removed unused_determineExitCodeduplicate ofcli-errors.tslogic.- Tests —
vi.mock('.../runtime-credentials.ts', …)instead ofindex.tswhere credentials were stubbed.
fetch.ts— unsupported client →UNSUPPORTED_OPERATION; bad response →INVESTEC_API_ERROR.credentials-store.ts—throwCredentialPathErrorfor async read/load paths (FILE_NOT_FOUND,PERMISSION_DENIED,INVALID_CREDENTIALS).cli-errors.ts—EXIT_CODE_BY_CLI_CODEfor allERROR_CODES, then message heuristics (avoids treatingE4016as HTTP 401); exportdetermineExitCodeviautils.ts.index.tscompletion —CliError+INVALID_INPUT; exit code fromdetermineExitCode.- Tests —
test/utils/cli-errors.test.ts, extendedcredentials-store+fetchtests.
- Replaced dynamic
import('../utils.js')forisStdoutPiped/readStdinwith static imports across list/read/write commands. configureChalk()is not invoked atutils.tsload; entrypoint calls it once.
test/cmds/register.test.ts— success path + failed HTTP →INVALID_CREDENTIALS.test/cmds/login.test.ts— token merge into credentials file, failed login, preserves existing cred keys.test/cmds/bank.test.ts— mocked OpenAI text reply; API error →INVALID_INPUT.test/cmds/ai.test.ts— valid structured JSON →writeFile; missing/invalid response →INVALID_INPUT.- Optional:
test/helpers/cli-mocks.tsfor sharedruntime-credentials/ spinner mocks (not added).
src/register-cli-commands.ts— All Commander subcommand registration, shared option helpers (addApiCredentialOptions,addSpinnerVerboseOptions), anddisabledCommandAction;main()callsregisterCliCommands(program, { credentialLocation }).src/completion.ts—generateCompletionScript+ bash/zsh generators moved out ofindex.ts.withCommandContext— genericA extends readonly unknown[], R(noany); same runtime behavior.docs.ts—CommanderCommandInternals+asCommanderInternals()instead of(command as any).security.ts— JSDoc: whyINVESTEC_CLIENT_IDis not inSECRET_ENV_VARS; test asserts it is not flagged.test/helpers/cli-mocks.ts—getRuntimeCredentialsMock,testCredentials,createSpinnerControlMock; consumers use asyncvi.mock(…, async () => import('./cli-mocks.js'))to satisfy Vitest hoisting.test/utils/completion.test.ts— smoke tests for completion output + invalid shell.