Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/cli/src/repowise/cli/commands/init_cmd/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
run_async,
save_config_partial,
save_state,
warn,
)
from repowise.cli.platform import telemetry
from repowise.cli.providers import resolve_embedder
Expand Down Expand Up @@ -336,8 +337,8 @@ def _run_generation_phase(

# Warn when a local provider runs with default concurrency
if provider.provider_name in ("ollama", "codex_cli", "opencode") and concurrency > 4:
console.print(
f" [{WARN}]Warning:[/] {provider.provider_name} is a local provider "
warn(
f" {provider.provider_name} is a local provider "
f"running with concurrency={concurrency}. "
f"If you see timeout errors, try [bold]--concurrency 1[/bold]."
)
Expand Down
7 changes: 3 additions & 4 deletions packages/cli/src/repowise/cli/commands/mcp_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import click

from repowise.cli.helpers import console, find_repowise_repo_root, resolve_repo_path
from repowise.cli.helpers import console, find_repowise_repo_root, resolve_repo_path, warn
from repowise.cli.ui import load_dotenv
from repowise.core.workspace.config import WorkspaceConfig, find_workspace_root

Expand Down Expand Up @@ -160,9 +160,8 @@ def mcp_command(
workspace = _workspace_summary(repo_path)
repowise_dir = repo_path / ".repowise"
if workspace is None and not repowise_dir.exists():
console.print(
f"[yellow]Warning: No .repowise directory found at {repo_path}.[/yellow]\n"
"Run 'repowise init' first to generate documentation."
warn(
f"No .repowise directory found at {repo_path}.\nRun 'repowise init' first to generate documentation."
)

resolved_host = host or os.environ.get("REPOWISE_HOST", "127.0.0.1")
Expand Down
10 changes: 3 additions & 7 deletions packages/cli/src/repowise/cli/commands/reindex_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
get_db_url_for_repo,
resolve_repo_path,
run_async,
warn,
)
from repowise.cli.ui import BRAND_STYLE, OWL_SPINNER

Expand Down Expand Up @@ -155,9 +156,7 @@ async def _embed_slice(items: list[tuple[str, str, dict]]) -> None:
failed += 1
warned += 1
if warned <= 3:
console.print(
f"[yellow] Warning: failed to embed {page_id}: {exc}[/yellow]"
)
warn(f" failed to embed {page_id}: {exc}")

# Pages — one batched embed per slice instead of one embedder
# round-trip per page (a large wiki paid thousands of serial calls).
Expand All @@ -173,10 +172,7 @@ async def _embed_slice(items: list[tuple[str, str, dict]]) -> None:
failed += 1
warned += 1
if warned <= 3:
console.print(
f"[yellow] Warning: skipped {page.id}: no title to index it by"
"[/yellow]"
)
warn(f" skipped {page.id}: no title to index it by")
continue
item = embed_item(
page.id,
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/repowise/cli/commands/workspace_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
resolve_reasoning,
resolve_repo_path,
run_async,
warn,
)
from repowise.cli.output import emit_json, format_option, json_option, resolve_format
from repowise.core.docs_mode import docs_mode_state_fields, resolve_docs_mode
Expand Down Expand Up @@ -624,7 +625,7 @@ async def _do_index() -> tuple[int, int, int]:
file_count, symbol_count, _ = run_async(_do_index())
console.print(f" [green]✓[/green] {file_count} files, {symbol_count:,} symbols")
except Exception as exc:
console.print(f"[yellow]Warning:[/yellow] Indexing failed for '{alias}': {exc}")
warn(f"Indexing failed for '{alias}': {exc}")
return

# Run LLM doc generation through the existing single-repo init pathway
Expand Down
17 changes: 14 additions & 3 deletions packages/cli/src/repowise/cli/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@
console = Console(width=resolve_console_width(sys.stdout))
err_console = Console(stderr=True, width=resolve_console_width(sys.stderr))


def warn(text: str) -> None:
"""Print a warning to stderr with the shared yellow ``Warning:`` prefix.

Every CLI warning funnels through this helper so warnings render on the
same ``err_console`` stream (never stdout) and use one ``[yellow]Warning:[/yellow]``
spelling instead of hand-synced copies scattered across commands.
"""
err_console.print(f"[yellow]Warning:[/yellow] {text}")


STATE_FILENAME = "state.json"
REPOWISE_DIR = ".repowise"

Expand Down Expand Up @@ -693,8 +704,8 @@ def _persist_provider_key(repo_path: Path, provider: str) -> None:
try:
save_repo_env_key(repo_path, env_var, value)
except (OSError, ValueError) as exc:
err_console.print(
f"[yellow]Warning:[/yellow] could not save {env_var} to "
warn(
f"could not save {env_var} to "
f".repowise/.env ({exc}). The index is complete, but "
f"`repowise mcp` will need {env_var} in its environment."
)
Expand Down Expand Up @@ -961,7 +972,7 @@ def _build(name: str) -> Any:
warnings = validate_provider_config(provider_name)
if warnings:
for warning in warnings:
err_console.print(f"[yellow]Warning:[/yellow] {warning}")
warn(warning)
# For explicit provider requests, we still try to create it
# The provider constructor will fail if the API key is actually required

Expand Down
8 changes: 4 additions & 4 deletions packages/cli/src/repowise/cli/worktree.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import uuid
from pathlib import Path

from repowise.cli.helpers import console
from repowise.cli.helpers import console, warn


def _git_output(args: list[str], cwd: Path) -> str:
Expand Down Expand Up @@ -200,10 +200,10 @@ def seed_index_from_base(
if include_submodules is not None:
state_include = st_data.get("include_submodules", False)
if include_submodules != state_include:
console.print(
f"[yellow]Warning: --include-submodules={include_submodules} "
warn(
f"--include-submodules={include_submodules} "
f"conflicts with copied state ({state_include}). Seeded state "
f"will take precedence.[/yellow]"
f"will take precedence."
)

(temp_dir / "state.json").write_text(json.dumps(st_data, indent=2), encoding="utf-8")
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/cli/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,32 @@
run_async,
save_state,
validate_provider_config,
warn,
)

# ---------------------------------------------------------------------------
# run_async
# ---------------------------------------------------------------------------


class TestWarn:
def test_warn_prints_warning_prefix_to_stderr(self, capsys):
warn("something went wrong")
captured = capsys.readouterr()
assert "Warning:" in captured.err
assert "something went wrong" in captured.err
# Nothing leaks to stdout.
assert captured.out == ""

def test_warn_prefix_rendered(self, capsys):
warn("boom")
captured = capsys.readouterr()
# Rich renders the [yellow] markup away when stderr isn't a tty, but the
# human-facing "Warning:" prefix must survive on the stderr stream.
assert captured.err.startswith("Warning: boom\n")
assert captured.out == ""


class TestRunAsync:
def test_returns_coroutine_result(self):
async def _add(a, b):
Expand Down
Loading