Skip to content
Merged
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
37 changes: 36 additions & 1 deletion packages/cli/src/repowise/cli/commands/init_cmd/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,25 @@ def _run_generation_phase(
# ---------------------------------------------------------------------------


def _interactive_gate(
*,
isatty: bool,
provider_name: str | None,
index_only: bool,
yes: bool,
resume: bool,
) -> bool:
"""Whether the interactive questionnaire runs.

Interactive requires a TTY, no explicit provider, docs on, and neither
--yes nor --resume. --resume skips the gate because the prior run
already answered every question and those answers are on disk
(config.yaml); re-asking would let a resumed run diverge from the pages
already written with them (issue #2098).
"""
return isatty and provider_name is None and not index_only and not yes and not resume


@click.command("init")
@click.argument("path", required=False, default=None)
@click.option(
Expand Down Expand Up @@ -948,7 +967,23 @@ def init_command(
# ---- Interactive mode (TTY, no explicit flags) ----
# --yes forces non-interactive even on a TTY (mirrors the workspace path),
# so a scripted `init -y` never blocks on the mode-selection menu.
is_interactive = sys.stdin.isatty() and provider_name is None and not index_only and not yes
# --resume skips the gate too: the prior run already answered every
# question, and those answers are on disk (config.yaml), so re-asking
# would let a resumed run diverge from the pages already written with
# them (issue #2098).
is_interactive = _interactive_gate(
isatty=sys.stdin.isatty(),
provider_name=provider_name,
index_only=index_only,
yes=yes,
resume=resume,
)

if resume:
console.print(
f"[bold]Resuming[/] the previous run in {repo_path}, "
"reusing the answers it already saved to config.yaml."
)

# Output language picked in the advanced-mode generation section; None
# until chosen. Resolved below: flag > this > config.yaml > English.
Expand Down
36 changes: 36 additions & 0 deletions tests/unit/cli/test_init_noninteractive.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,46 @@

from repowise.cli import cost_gate
from repowise.cli.commands.init_cmd._interactive import offer_hook_install
from repowise.cli.commands.init_cmd.command import _interactive_gate
from repowise.cli.helpers import NO_SAVE_KEY_ENV, save_config
from repowise.cli.main import cli


class TestInteractiveGate:
"""The questionnaire gate: --resume must skip it (issue #2098)."""

def test_tty_with_no_flags_is_interactive(self) -> None:
assert _interactive_gate(
isatty=True, provider_name=None, index_only=False, yes=False, resume=False
)

def test_resume_skips_the_gate_on_a_tty(self) -> None:
"""A resume must not re-ask questions the prior run already answered."""
assert not _interactive_gate(
isatty=True, provider_name=None, index_only=False, yes=False, resume=True
)

def test_yes_skips_the_gate(self) -> None:
assert not _interactive_gate(
isatty=True, provider_name=None, index_only=False, yes=True, resume=False
)

def test_explicit_provider_skips_the_gate(self) -> None:
assert not _interactive_gate(
isatty=True, provider_name="anthropic", index_only=False, yes=False, resume=False
)

def test_index_only_skips_the_gate(self) -> None:
assert not _interactive_gate(
isatty=True, provider_name=None, index_only=True, yes=False, resume=False
)

def test_non_tty_skips_the_gate(self) -> None:
assert not _interactive_gate(
isatty=False, provider_name=None, index_only=False, yes=False, resume=False
)


def _est(usd: float) -> SimpleNamespace:
"""A stand-in for a cost estimate, which the gate only reads two fields of."""
return SimpleNamespace(estimated_cost_usd=usd, cost_range=None, is_calibrated=False)
Expand Down
Loading