From a70c466dd48fa1557d9f7bec795470cfb9c1ba1b Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah Date: Tue, 16 Jun 2026 10:45:37 -0700 Subject: [PATCH] Add bb init readiness checks --- .ai-context/COMMANDS.md | 4 +++- .ai-context/STATUS.md | 6 ++++-- CHANGELOG.md | 3 +++ README.md | 6 ++++++ src/bankbuddy/bb/cli.py | 44 ++++++++++++++++++++++++++++++++++++++++- tests/test_bb_cli.py | 34 +++++++++++++++++++++++++++++++ 6 files changed, 93 insertions(+), 4 deletions(-) diff --git a/.ai-context/COMMANDS.md b/.ai-context/COMMANDS.md index 4ee2e96..dbb8cac 100644 --- a/.ai-context/COMMANDS.md +++ b/.ai-context/COMMANDS.md @@ -33,12 +33,14 @@ Run the v2 financial intelligence CLI with: ```bash bb --help +bb init bb status ``` `bb` is the side-by-side command surface for new `BB_` schema work. It should grow new document/entity/observation workflows while the legacy command -surfaces remain available. +surfaces remain available. Use `bb init` to apply current migrations and +prepare v2 `financial/` storage roots for the active data home. ## BankBuddy CLI diff --git a/.ai-context/STATUS.md b/.ai-context/STATUS.md index e42dd93..f3ed8d8 100644 --- a/.ai-context/STATUS.md +++ b/.ai-context/STATUS.md @@ -14,8 +14,8 @@ section is `Unreleased`. - Local data homes for `prod`, `dev`, and named environments. - Banking CLI commands for banks, accounts, statement refs, imports, transactions, categories, reports, exports, storage migration, and status. -- Side-by-side `bb` CLI for v2 financial intelligence foundation status and - `BB_` schema visibility. +- Side-by-side `bb` CLI for v2 financial intelligence initialization, + foundation status, storage readiness, and `BB_` schema visibility. - Supported banking imports for Bank of America PDF/CSV, Apple Card PDF, ICICI `.xls`, and HDFC `.xls`. - Statement inventory and statement coverage auditing. @@ -30,6 +30,8 @@ section is `Unreleased`. questions documents. - Additive `bankbuddy.bb` source namespace and `bb` console script for v2 work, leaving existing `bankbuddy` and `taxbuddy` commands in place. +- `bb init` for applying migrations and preparing v2 financial storage roots + without depending on the legacy `bankbuddy` command surface. - Prospective relicensing from MIT to AGPL-3.0-or-later. - Canonical data-home layout with `database/`, `bank/`, and `tax/` directories. - First TaxBuddy CLI slice and `tax_documents` metadata index. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d8eec4..9d53b8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ and versions are tracked in the repo-root `VERSION` file. ### Added +- Added `bb init` and v2 storage readiness output in `bb status`, so the v2 + command surface can initialize migrations and financial storage roots + independently of the legacy `bankbuddy` CLI. - Added v2 financial document storage roots, document object metadata, generated human-readable document view metadata, path-resolution helpers, and read-only managed-file guardrails under the `bankbuddy.bb` namespace. diff --git a/README.md b/README.md index 0a9b333..74d6465 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Activate the project shell once, then run the CLI directly: ```bash basectl activate bankbuddy +bb init bb status bankbuddy --help bankbuddy status @@ -154,10 +155,15 @@ which maps to `~/BankBuddy-dev`. Use `status` to confirm the active environment and database: ```bash +bb init bb status bankbuddy status ``` +Use `bb init` for the v2 financial intelligence foundation. It applies the +current SQLite migrations and prepares the `financial/` storage roots used by +document-first workflows. + Switch the current shell by exporting `BANKBUDDY_ENV`: ```bash diff --git a/src/bankbuddy/bb/cli.py b/src/bankbuddy/bb/cli.py index 309cd2a..efcb05f 100644 --- a/src/bankbuddy/bb/cli.py +++ b/src/bankbuddy/bb/cli.py @@ -7,7 +7,9 @@ import click from bankbuddy import __version__ +from bankbuddy.database import initialize_database from bankbuddy.paths import resolve_app_paths +from bankbuddy.bb.storage import ensure_financial_storage_dirs from bankbuddy.runtime import CliRuntime from bankbuddy.runtime import RuntimeConfigError from bankbuddy.runtime import create_runtime @@ -108,17 +110,19 @@ def status(ctx: click.Context) -> None: initialized = "yes" if paths.database.exists() else "no" bb_table_count = len([name for name in table_names if name.startswith("BB_")]) has_v2_foundation = BB_FOUNDATION_TABLES.issubset(table_names) + has_v2_storage = _v2_storage_ready(paths) has_legacy_tables = bool(LEGACY_TABLES.intersection(table_names)) runtime.log.debug( "bb_status environment=%s data_home=%s database=%s initialized=%s " - "bb_table_count=%s v2_foundation=%s legacy_tables=%s", + "bb_table_count=%s v2_foundation=%s v2_storage=%s legacy_tables=%s", paths.environment, paths.root, paths.database, initialized, bb_table_count, has_v2_foundation, + has_v2_storage, has_legacy_tables, ) @@ -129,8 +133,31 @@ def status(ctx: click.Context) -> None: click.echo(f"Database: {paths.database}") click.echo(f"Initialized: {initialized}") click.echo(f"V2 foundation: {_yes_no(has_v2_foundation)}") + click.echo(f"V2 storage: {_yes_no(has_v2_storage)}") click.echo(f"BB tables: {bb_table_count}") click.echo(f"Legacy tables: {'present' if has_legacy_tables else 'absent'}") + if has_v2_storage: + click.echo(f"Financial canonical: {paths.financial_canonical}") + click.echo(f"Financial views: {paths.financial_views}") + + +@main.command("init") +@click.pass_context +def init_command(ctx: click.Context) -> None: + """Initialize the local BankBuddy v2 app directory and database.""" + + runtime = runtime_from_context(ctx) + paths = resolve_app_paths(environment=runtime.environment) + initialize_database(paths) + ensure_financial_storage_dirs(paths) + runtime.log.debug( + "bb_init home=%s database=%s financial_canonical=%s financial_views=%s", + paths.root, + paths.database, + paths.financial_canonical, + paths.financial_views, + ) + click.echo(f"Initialized BankBuddy v2 at {paths.root}") def _database_table_names(database_path) -> set[str]: @@ -149,6 +176,21 @@ def _yes_no(value: bool) -> str: return "yes" if value else "no" +def _v2_storage_ready(paths) -> bool: + return all( + path.is_dir() + for path in ( + paths.financial_inbox, + paths.financial_canonical, + paths.financial_failed, + paths.financial_duplicates, + paths.financial_review, + paths.financial_views, + paths.financial_exports, + ) + ) + + def runtime_from_context(ctx: click.Context) -> CliRuntime: """Return the root bb runtime context.""" diff --git a/tests/test_bb_cli.py b/tests/test_bb_cli.py index 0b6da1b..7ea5151 100644 --- a/tests/test_bb_cli.py +++ b/tests/test_bb_cli.py @@ -31,6 +31,7 @@ def test_bb_status_reports_environment_data_home_and_v2_schema(tmp_path) -> None assert f"Database: {tmp_path / 'database' / 'bankbuddy.sqlite3'}" in result.output assert "Initialized: yes" in result.output assert "V2 foundation: yes" in result.output + assert "V2 storage: yes" in result.output assert "BB tables: 20" in result.output assert "Legacy tables: present" in result.output @@ -42,4 +43,37 @@ def test_bb_status_reports_missing_v2_schema(tmp_path) -> None: assert "CLI: bb" in result.output assert "Initialized: no" in result.output assert "V2 foundation: no" in result.output + assert "V2 storage: no" in result.output assert "BB tables: 0" in result.output + + +def test_bb_init_creates_database_and_v2_storage_dirs(tmp_path) -> None: + home = tmp_path / "home" + + result = CliRunner().invoke(main, ["init"], env={"BANKBUDDY_HOME": str(home)}) + + assert result.exit_code == 0 + assert f"Initialized BankBuddy v2 at {home}" in result.output + assert (home / "database" / "bankbuddy.sqlite3").is_file() + assert (home / "financial" / "canonical").is_dir() + assert (home / "financial" / "views").is_dir() + assert (home / "financial" / "inbox").is_dir() + + +def test_bb_init_is_idempotent_and_status_reports_ready_storage(tmp_path) -> None: + home = tmp_path / "home" + runner = CliRunner() + env = {"BANKBUDDY_HOME": str(home)} + + first = runner.invoke(main, ["init"], env=env) + second = runner.invoke(main, ["init"], env=env) + status = runner.invoke(main, ["status"], env=env) + + assert first.exit_code == 0 + assert second.exit_code == 0 + assert status.exit_code == 0 + assert "Initialized: yes" in status.output + assert "V2 foundation: yes" in status.output + assert "V2 storage: yes" in status.output + assert f"Financial canonical: {home / 'financial' / 'canonical'}" in status.output + assert f"Financial views: {home / 'financial' / 'views'}" in status.output