From 297d07f2a1c1f91f31e846774eb225678f8b862e Mon Sep 17 00:00:00 2001 From: ZdenekSrotyr Date: Mon, 4 May 2026 07:17:37 +0200 Subject: [PATCH] fix(cli): setup summary reflects actual CLAUDE.md write outcome (True/False return) --- cli/commands/analyst.py | 33 ++++++++++++++++++++-------- tests/test_analyst_bootstrap.py | 39 +++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/cli/commands/analyst.py b/cli/commands/analyst.py index 411a768..00be237 100644 --- a/cli/commands/analyst.py +++ b/cli/commands/analyst.py @@ -301,11 +301,15 @@ def _init_claude_workspace( workspace: Path, server_url: str = "", token: str = "", -) -> None: +) -> bool: """Initialise the .claude/ directory with placeholder files and hooks. Writes CLAUDE.md from the server (GET /api/welcome) unless ``server_url`` or ``token`` are empty, or the request fails (graceful degradation). + + Returns True if CLAUDE.md was written from the server, False otherwise + (skipped because caller passed empty server_url/token, or fetch failed — + in the latter case a warning was already printed to stderr). """ local_md = workspace / ".claude" / "CLAUDE.local.md" if not local_md.exists(): @@ -326,15 +330,19 @@ def _init_claude_workspace( # Write CLAUDE.md from the server if server_url and token: - _write_claude_md(workspace, server_url, token) + return _write_claude_md(workspace, server_url, token) + return False -def _write_claude_md(workspace: Path, server_url: str, token: str) -> None: +def _write_claude_md(workspace: Path, server_url: str, token: str) -> bool: """Fetch the rendered CLAUDE.md from the server and write it to the workspace. Gracefully handles: - 404: older server without the endpoint — skip with warning. - Other HTTP errors / network errors — skip with warning. + + Returns True iff a non-empty CLAUDE.md was successfully written; False on + any skipped or failed path (the caller already saw a stderr warning). """ from urllib.parse import urlencode import httpx @@ -353,27 +361,30 @@ def _write_claude_md(workspace: Path, server_url: str, token: str) -> None: "Warning: server does not support CLAUDE.md generation (older version). Skipping.", err=True, ) - return + return False if resp.status_code == 401 or resp.status_code == 403: typer.echo( f"Warning: CLAUDE.md fetch failed ({resp.status_code} {resp.reason_phrase}). Skipping.", err=True, ) - return + return False resp.raise_for_status() data = resp.json() content = data.get("content", "") if content: (workspace / "CLAUDE.md").write_text(content, encoding="utf-8") - else: - typer.echo("Warning: server returned empty CLAUDE.md content. Skipping.", err=True) + return True + typer.echo("Warning: server returned empty CLAUDE.md content. Skipping.", err=True) + return False except httpx.HTTPStatusError as e: typer.echo( f"Warning: CLAUDE.md fetch failed (HTTP {e.response.status_code}). Skipping.", err=True, ) + return False except Exception as e: typer.echo(f"Warning: CLAUDE.md fetch failed: {e}. Skipping.", err=True) + return False # --------------------------------------------------------------------------- @@ -443,7 +454,7 @@ def setup( # 7. Initialise Claude workspace (.claude/ hooks + placeholder + CLAUDE.md) typer.echo("Initializing Claude workspace...") - _init_claude_workspace( + claude_md_written = _init_claude_workspace( workspace, server_url=server_url if not no_claude_md else "", token=token if not no_claude_md else "", @@ -456,8 +467,12 @@ def setup( typer.echo(f" Tables : {n_downloaded} downloaded, {total_rows} total rows") typer.echo(f" Workspace: {workspace}") typer.echo(f" Hooks : SessionStart/End installed in {workspace}/.claude/settings.json") - if not no_claude_md: + if no_claude_md: + typer.echo(f" CLAUDE.md: skipped (--no-claude-md)") + elif claude_md_written: typer.echo(f" CLAUDE.md: written from server template") + else: + typer.echo(f" CLAUDE.md: skipped (see warnings above)") typer.echo("") typer.echo("Next steps:") typer.echo(" da sync — refresh data") diff --git a/tests/test_analyst_bootstrap.py b/tests/test_analyst_bootstrap.py index a960b6a..8c62629 100644 --- a/tests/test_analyst_bootstrap.py +++ b/tests/test_analyst_bootstrap.py @@ -153,7 +153,8 @@ class TestInitClaudeWorkspace: ) def test_writes_claude_md_when_server_returns_200(self, tmp_workspace): - """When /api/welcome returns 200, CLAUDE.md is written.""" + """When /api/welcome returns 200, CLAUDE.md is written and the helper + returns True so the caller can label its summary line accurately.""" from cli.commands.analyst import _create_workspace, _init_claude_workspace from unittest.mock import MagicMock, patch @@ -165,12 +166,46 @@ class TestInitClaudeWorkspace: mock_resp.raise_for_status = MagicMock() with patch("cli.commands.analyst.httpx.get", return_value=mock_resp): - _init_claude_workspace(tmp_workspace, server_url="https://example.com", token="tok") + written = _init_claude_workspace( + tmp_workspace, server_url="https://example.com", token="tok" + ) + assert written is True claude_md = tmp_workspace / "CLAUDE.md" assert claude_md.exists() assert "My CLAUDE.md" in claude_md.read_text(encoding="utf-8") + def test_returns_false_when_server_returns_404(self, tmp_workspace): + """When the server is too old for /api/welcome (404), the helper + returns False so the caller can render the summary as 'skipped' instead + of contradicting its own stderr warning.""" + from cli.commands.analyst import _create_workspace, _init_claude_workspace + from unittest.mock import MagicMock, patch + + _create_workspace(tmp_workspace) + + mock_resp = MagicMock() + mock_resp.status_code = 404 + mock_resp.raise_for_status = MagicMock() + + with patch("cli.commands.analyst.httpx.get", return_value=mock_resp): + written = _init_claude_workspace( + tmp_workspace, server_url="https://example.com", token="tok" + ) + + assert written is False + assert not (tmp_workspace / "CLAUDE.md").exists() + + def test_returns_false_when_no_server_url(self, tmp_workspace): + """Caller passing empty server_url/token (e.g. --no-claude-md) gets False back.""" + from cli.commands.analyst import _create_workspace, _init_claude_workspace + + _create_workspace(tmp_workspace) + written = _init_claude_workspace(tmp_workspace, server_url="", token="") + + assert written is False + assert not (tmp_workspace / "CLAUDE.md").exists() + def test_does_not_write_claude_md_when_no_claude_md_flag(self, tmp_workspace): """When server_url/token are empty (--no-claude-md path), CLAUDE.md is not written.""" from cli.commands.analyst import _create_workspace, _init_claude_workspace